1 /***********************************************************************
4 programmer: Michael J. Palmer <106177.1156@compuserve.com>
7 environment: RedHat 4.0 Linux 2.0.18
8 description: RPA authorisation code for POP3 client
10 The sole entry point is POP3_auth_rpa()
12 For license terms, see the file COPYING in this directory.
14 ***********************************************************************/
18 #if defined(POP3_ENABLE) && defined(RPA_ENABLE)
26 #include "fetchmail.h"
31 extern unsigned char line1[];
32 extern unsigned char line2[];
33 extern unsigned char line3[];
39 /* prototypes for internal functions */
40 static int POP3_rpa_resp(char* argbuf, int socket );
41 static void LenAppend(char** pptr, int len);
42 static int LenSkip(char** pptr, int rxlen);
43 static int DecBase64(char* bufp);
44 static void EncBase64(char* bufp, int len);
45 static void ToUnicode(char** pptr, char delim, unsigned char* buf, int* plen,
47 static int SetRealmService(char* bufp);
48 static void GenChallenge(unsigned char* buf, int len);
49 static int DigestPassphrase(char* passphrase,
50 unsigned char* rbuf, int unicodeit);
51 static void CompUserResp();
52 static int CheckUserAuth();
53 static void md5(const void* in, int len, unsigned char* out);
56 /* RPA protocol definitions */
58 #define EARLYVER "\x01\x00" /* Earliest supp version */
59 #define LATEVER "\x03\x00" /* Latest supp version */
60 #define HDR 0x60 /* ASN.1 SEQUENCE */
61 #define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
62 #define FLAGS "\x00\x01" /* Mutual authentication */
63 #define STRMAX 128 /* Bytes in Unicode */
64 #define Tsl 14 /* Timestamp bytelen */
65 #define Pul 16 /* Passphrase digest len */
66 #define Cul 16 /* Usr challenge bytelen */
67 #define Rul 16 /* Usr response bytelen */
68 #define Aul 16 /* User auth bytelen */
69 #define Kusl 16 /* Session key bytelen */
71 #define UNIPASS 1 /* 1=Unicode 0=iso8859 */
72 #define PS_RPA 42 /* Return code */
74 /* RPA authentication items */
76 unsigned char Cs[256]; /* Service challenge */
77 int Csl; /* Length of " " */
78 unsigned char Ts[Tsl+1]; /* Timestamp incl \0 */
79 unsigned char Nu[STRMAX]; /* Username in Unicode */
80 int Nul; /* Length of " in bytes */
81 unsigned char Ns[STRMAX]; /* Service in Unicode */
82 int Nsl; /* Length of " in bytes */
83 unsigned char Nr[STRMAX]; /* Realm in Unicode */
84 int Nrl; /* Length of " in bytes */
85 unsigned char Pu[Pul]; /* Passphrase after MD5 */
86 unsigned char Cu[Cul]; /* User challenge */
87 unsigned char Ru[Rul]; /* User response */
88 unsigned char Au[Aul]; /* User auth from Deity */
89 unsigned char Kusu[Kusl]; /* Obscured Session key */
90 unsigned char Kus[Kusl]; /* Session key */
92 /*********************************************************************
93 function: POP3_auth_rpa
94 description: send the AUTH RPA commands to the server, and
95 get the server's response. Then progress through the
96 RPA challenge/response protocol until we are
97 (hopefully) granted authorisation.
99 userid user's id@realm e.g. myuserid@csi.com
100 passphrase user's passphrase
101 (upper lower or mixed case as the realm has chosen.
102 spec allows various options :-( )
103 socket socket to which the server is connected.
105 return value: zero if success, else non-zero.
106 calls: SockPrintf, POP3_rpa_resp, EncBase64, DecBase64,
107 LenAppend, GenChallenge
108 globals: read outlevel.
109 *********************************************************************/
111 int POP3_auth_rpa (char *userid, char *passphrase, int socket)
113 int ok,rxlen,verh,verl,i,rll;
114 char buf [POPBUFSIZE];
116 int status,aulin,kuslin;
117 char* stdec[4] = { N_("Success") ,
118 N_("Restricted user (something wrong with account)") ,
119 N_("Invalid userid or passphrase") ,
122 /* Initiate RPA authorisation */
124 SockPrintf(socket,"AUTH RPA\r\n");
126 if (outlevel >= O_MONITOR)
127 report(stdout, "> AUTH RPA\n");
129 /* Create unicode user name in Nu. */
130 /* Create MD5 digest of user's passphrase in Pu */
133 ToUnicode(&bufp, '@', Nu, &Nul, 1); /* User (lowercase) */
134 DigestPassphrase(passphrase, Pu, UNIPASS);
136 /* Get + response from server (RPA ready) */
138 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
140 if (outlevel > O_SILENT && outlevel < O_MONITOR)
141 report(stdout, "%s\n",buf);
146 /* Assemble Token 1 in buf */
150 LenAppend(&bufp, 17);
151 memcpy(bufp, MECH, 11); bufp += 11;
152 memcpy(bufp, EARLYVER, 2); bufp += 2;
153 memcpy(bufp, LATEVER, 2); bufp += 2;
154 memcpy(bufp, FLAGS, 2); bufp += 2;
156 /* Send Token 1, receive Token 2 */
158 EncBase64(buf, bufp-buf);
160 SockPrintf(socket,"%s\r\n",buf);
162 if (outlevel >= O_MONITOR)
163 report(stdout, "> %s\n",buf);
164 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
166 if (outlevel > O_SILENT && outlevel < O_MONITOR)
167 report(stdout, "%s\n",buf);
170 if ((rxlen = DecBase64(buf)) == 0)
172 if (outlevel > O_SILENT)
173 report(stderr, GT_("RPA token 2: Base64 decode error\n"));
177 *(buf+rxlen) = 0; /* Terminates realm list */
178 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
180 /* Interpret Token 2 */
182 verh = (unsigned char)*(bufp++); verl = (unsigned char)*(bufp++);
183 if (outlevel >= O_DEBUG)
184 report(stdout, GT_("Service chose RPA version %d.%d\n"),verh,verl);
185 Csl = (unsigned char)*(bufp++);
186 memcpy(Cs, bufp, Csl);
188 if (outlevel >= O_DEBUG)
190 report(stdout, GT_("Service challenge (l=%d):\n"),Csl);
191 for (i=0; i<Csl; i++)
192 report_build(stdout, "%02X ",Cs[i]);
193 report_complete(stdout, "\n");
195 memcpy(Ts, bufp, Tsl);
198 if (outlevel >= O_DEBUG)
199 report(stdout, GT_("Service timestamp %s\n"),Ts);
200 rll = (unsigned char)*(bufp++) << 8; rll = rll | (unsigned char)*(bufp++);
201 if ((bufp-buf+rll) != rxlen)
203 if (outlevel > O_SILENT)
204 report(stderr, GT_("RPA token 2 length error\n"));
207 if (outlevel >= O_DEBUG)
208 report(stdout, GT_("Realm list: %s\n"),bufp);
209 if (SetRealmService(bufp) != 0)
211 if (outlevel > O_SILENT)
212 report(stderr, GT_("RPA error in service@realm string\n"));
216 /* Assemble Token 3 in buf */
220 LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
221 memcpy(bufp, MECH, 11); bufp += 11;
223 *(bufp++) = strlen(userid);
224 memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
225 GenChallenge(Cu,Cul);
227 memcpy(bufp, Cu, Cul); bufp += Cul;
230 memcpy(bufp, Ru, Rul); bufp += Rul;
232 /* Send Token 3, receive Token 4 */
234 EncBase64(buf,bufp-buf);
236 SockPrintf(socket,"%s\r\n",buf);
238 if (outlevel >= O_MONITOR)
239 report(stdout, "> %s\n",buf);
240 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
242 if (outlevel > O_SILENT && outlevel < O_MONITOR)
243 report(stdout, "%s\n",buf);
246 if ((rxlen = DecBase64(buf)) == 0)
248 if (outlevel > O_SILENT)
249 report(stderr, GT_("RPA token 4: Base64 decode error\n"));
253 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
255 /* Interpret Token 4 */
257 aulin = (unsigned char)*(bufp++);
258 if (outlevel >= O_DEBUG)
260 report(stdout, GT_("User authentication (l=%d):\n"),aulin);
261 for (i=0; i<aulin; i++)
262 report_build(stdout, "%02X ",bufp[i]);
263 report_complete(stdout, "\n");
265 if (aulin == Aul) memcpy(Au, bufp, Aul);
268 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
273 if (outlevel >= O_DEBUG)
274 report(stdout, GT_("RPA status: %02X\n"),status);
277 if ((bufp - buf) != rxlen)
279 if (outlevel > O_SILENT)
280 report(stderr, GT_("RPA token 4 length error\n"));
285 if (outlevel > O_SILENT) {
287 report(stderr, GT_("RPA rejects you: %s\n"),GT_(stdec[status]));
289 report(stderr, GT_("RPA rejects you, reason unknown\n"));
297 GT_("RPA User Authentication length error: %d\n"),aulin);
302 report(stderr, GT_("RPA Session key length error: %d\n"),kuslin);
305 if (CheckUserAuth() != 0)
307 if (outlevel > O_SILENT)
308 report(stderr, GT_("RPA _service_ auth fail. Spoof server?\n"));
311 if (outlevel >= O_DEBUG)
313 report(stdout, GT_("Session key established:\n"));
314 for (i=0; i<Kusl; i++)
315 report_build(stdout, "%02X ",Kus[i]);
316 report_complete(stdout, "\n");
319 /* Assemble Token 5 in buf and send (not in ver 2 though) */
320 /* Version 3.0 definitely replies with +OK to this. I have */
321 /* no idea what sort of response previous versions gave. */
327 LenAppend(&bufp, 1 );
329 EncBase64(buf,bufp-buf);
331 SockPrintf(socket,"%s\r\n",buf);
333 if (outlevel >= O_MONITOR)
334 report(stdout, "> %s\n",buf);
335 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
337 if (outlevel > O_SILENT && outlevel < O_MONITOR)
338 report(stdout, "%s\n",buf);
343 if (outlevel > O_SILENT)
344 report(stdout, GT_("RPA authorisation complete\n"));
350 /*********************************************************************
351 function: POP3_rpa_resp
352 description: get the server's response to an RPA action.
353 Return received base64 string if successful
355 argbuf buffer to receive the string.
356 socket socket to which the server is connected.
358 return value: zero if okay, else return code.
360 globals: reads outlevel.
361 *********************************************************************/
363 static int POP3_rpa_resp (char *argbuf, int socket)
366 char buf [POPBUFSIZE];
370 if (outlevel >= O_DEBUG)
371 report(stdout, GT_("Get response\n"));
373 sockrc = gen_recv(socket, buf, sizeof(buf));
376 if (linecount == 1) strcpy(buf,line1);
377 if (linecount == 2) strcpy(buf,line2);
378 if (linecount == 3) strcpy(buf,line3);
379 /* report(stdout, "--> "); fflush(stderr); */
380 /* scanf("%s",&buf) */
383 if (sockrc == PS_SUCCESS) {
388 /* if (*bufp == ' ') bufp++; */
393 else if (strcmp(buf,"-ERR") == 0)
395 else ok = PS_PROTOCOL;
400 if (outlevel >= O_DEBUG)
401 report(stdout, GT_("Get response return %d [%s]\n"), ok, buf);
406 /*********************************************************************
408 description: Store token length encoded as per ASN.1 DER rules
409 buffer pointer stepped on appropriately.
410 Copes with numbers up to 32767 at least.
412 buf pointer to buffer to receive result
413 len length value to encode
418 *********************************************************************/
420 static void LenAppend(char **pptr_, int len)
422 unsigned char **pptr = (unsigned char **)pptr_;
426 **pptr = len; (*pptr)++;
428 else if (len < 0x100)
430 **pptr = 0x81; (*pptr)++;
431 **pptr = len; (*pptr)++;
435 **pptr = 0x82; (*pptr)++;
436 **pptr = len >> 8; (*pptr)++;
437 **pptr = len & 0xFF; (*pptr)++;
441 /*********************************************************************
443 description: Check token header, length, and mechanism, and
446 pptr pointer to buffer pointer
447 rxlen number of bytes after base64 decode
449 return value: 0 if error, else token length value
451 globals: reads outlevel.
452 *********************************************************************/
454 int LenSkip(char **pptr, int rxlen)
459 if ((unsigned char)**pptr != HDR)
461 if (outlevel > O_SILENT)
462 report(stderr, GT_("Hdr not 60\n"));
466 if (((unsigned char)(**pptr) & 0x80) == 0 )
468 len = (unsigned char)**pptr; (*pptr)++;
470 else if ((unsigned char)(**pptr) == 0x81)
472 len = (unsigned char)*(*pptr+1); (*pptr) += 2;
474 else if ((unsigned char)(**pptr) == 0x82)
476 len = ((unsigned char)(*(*pptr+1)) << 8) | (unsigned char)*(*pptr+2);
482 if (outlevel>O_SILENT)
483 report(stderr, GT_("Token length error\n"));
485 else if (((*pptr-save)+len) != rxlen)
487 if (outlevel>O_SILENT)
488 report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
491 else if (memcmp(*pptr,MECH,11))
493 if (outlevel > O_SILENT)
494 report(stderr, GT_("Mechanism field incorrect\n"));
497 else (*pptr) += 11; /* Skip mechanism field */
501 /*********************************************************************
503 description: Decode a Base64 string, overwriting the original.
504 Note that result cannot be longer than input.
509 return value: 0 if error, else number of bytes in decoded result
511 globals: reads outlevel.
512 *********************************************************************/
514 static int DecBase64(char *bufp)
516 unsigned int newx, bits=0, cnt=0, i, part=0;
520 while((ch=(unsigned char)*(inp++)) != 0)
522 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
524 if ((ch>='A') && (ch <= 'Z')) newx = ch - 'A';
525 else if ((ch>='a') && (ch <= 'z')) newx = ch - 'a' + 26;
526 else if ((ch>='0') && (ch <= '9')) newx = ch - '0' + 52;
527 else if ( ch=='+' ) newx = 62;
528 else if ( ch=='/' ) newx = 63;
530 report(stderr, GT_("dec64 error at char %d: %x\n"), (int)(inp - bufp), ch);
533 part=((part & 0x3F)*64) + newx;
538 *outp = (part >> bits);
543 if (outlevel >= O_MONITOR)
545 report(stdout, GT_("Inbound binary data:\n"));
546 for (i=0; i<cnt; i++)
548 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
549 if (((i % 16)==15) || (i==(cnt-1)))
550 report_complete(stdout, "\n");
556 /*********************************************************************
558 description: Encode into Base64 string, overwriting the original.
559 Note that result CAN be longer than input, the buffer
560 is assumed to be big enough. Result string is
565 len number of bytes in buffer (>0)
569 globals: reads outlevel;
570 *********************************************************************/
572 static void EncBase64(char *bufp, int len)
575 unsigned char c1,c2,c3;
576 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
579 if (outlevel >= O_MONITOR)
581 report(stdout, GT_("Outbound data:\n"));
582 for (i=0; i<len; i++)
584 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
585 if (((i % 16)==15) || (i==(len-1)))
586 report_complete(stdout, "\n");
589 outp = bufp + (((len-1)/3)*4);
591 /* So we can do the update in place, start at the far end! */
592 for (i=((len-1)/3)*3; i>=0; i-=3)
594 c1 = (unsigned char)bufp[i];
595 if ((i+1) < len) c2 = (unsigned char)bufp[i+1]; else c2=0;
596 if ((i+2) < len) c3 = (unsigned char)bufp[i+2]; else c3=0;
598 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
599 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
600 else *(outp+2) = '=';
601 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
602 else *(outp+3) = '=';
607 /*********************************************************************
609 description: Convert ASCII (or iso-8859-1) byte string into
610 Unicode. Ensure length isn't too long (STRMAX).
613 pptr pointer to input buffer
614 delim delimiter character (in addition to \0)
615 buf buffer where Unicode will go
616 plen pointer to length variable (# bytes output)
617 conv 1 to convert to lowercase, 0 leaves alone
621 globals: reads outlevel;
622 *********************************************************************/
624 static void ToUnicode(char **pptr /* input string*/,
625 char delim, unsigned char *buf /* output buffer */,
631 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
635 *(p++) = tolower((unsigned char)**pptr);
641 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
643 if (outlevel > O_SILENT)
644 report(stderr, GT_("RPA String too long\n"));
647 if (outlevel >= O_DEBUG)
649 report(stdout, GT_("Unicode:\n"));
650 for (i=0; i<(*plen); i++)
652 report_build(stdout, "%02X ",buf[i]);
653 if (((i % 16)==15) || (i==((*plen)-1)))
654 report_complete(stdout, "\n");
659 /*********************************************************************
660 function: SetRealmService
661 description: Select a realm from list, and store it.
664 bufp pointer to buffer
668 globals: reads outlevel.
670 *********************************************************************/
672 static int SetRealmService(char *bufp)
674 /* For the moment we pick the first available realm. It would */
675 /* make more sense to verify that the realm which the user */
676 /* has given (as part of id) is in the list, and select it's */
677 /* corresponding service name. */
678 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
679 bufp++; /* Skip the @ */
680 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
681 if ((Nrl == 0) || (Nsl == 0))
686 /*********************************************************************
687 function: GenChallenge
688 description: Generate a random User challenge
691 buf pointer to buffer
696 globals: reads outlevel.
698 *********************************************************************/
700 static void GenChallenge(unsigned char *buf, int len)
705 devrandom = fopen("/dev/urandom","rb");
706 if (devrandom == NULL && outlevel > O_SILENT)
708 report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n"));
709 report(stdout, GT_(" prevent you logging in, but means you\n"));
710 report(stdout, GT_(" cannot be sure you are talking to the\n"));
711 report(stdout, GT_(" service that you think you are (replay\n"));
712 report(stdout, GT_(" attacks by a dishonest service are possible.)\n"));
716 buf[i] = devrandom ? fgetc(devrandom) : random();
719 fclose(devrandom); /* should be safe, file mode was "r" */
721 if (outlevel >= O_DEBUG)
723 report(stdout, GT_("User challenge:\n"));
724 for (i=0; i<len; i++)
726 report_build(stdout, "%02X ",buf[i]);
727 if (((i % 16)==15) || (i==(len-1)))
728 report_complete(stdout, "\n");
733 /*********************************************************************
734 function: DigestPassphrase
735 description: Use MD5 to compute digest (Pu) of Passphrase
736 Don't map to lower case. We assume the user is
737 aware of the case requirement of the realm.
738 (Why oh why have options in the spec?!)
740 passphrase buffer containing string, \0 terminated
741 rbuf buffer into which digest goes
743 return value: 0 if ok, else error code
745 globals: reads authentication items listed above.
747 *********************************************************************/
749 static int DigestPassphrase(char *passphrase,unsigned char *rbuf,
753 unsigned char workarea[STRMAX];
756 if (unicodeit) /* Option in spec. Yuck. */
759 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
762 md5(workarea,len,rbuf);
765 md5(rbuf,strlen(passphrase),rbuf);
769 /*********************************************************************
770 function: CompUserResp
771 description: Use MD5 to compute User Response (Ru) from
772 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
778 globals: reads authentication items listed above.
780 *********************************************************************/
782 static void CompUserResp(void)
784 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
787 memcpy(p , Pu, Pul); p += Pul;
788 memset(p , '\0', 48); p += 48;
789 memcpy(p , Nu, Nul); p += Nul;
790 memcpy(p , Ns, Nsl); p += Nsl;
791 memcpy(p , Nr, Nrl); p += Nrl;
792 memcpy(p , Cu, Cul); p += Cul;
793 memcpy(p , Cs, Csl); p += Csl;
794 memcpy(p , Ts, Tsl); p += Tsl;
795 memcpy(p , Pu, Pul); p += Pul;
796 md5(workarea,p-workarea,Ru);
799 /*********************************************************************
800 function: CheckUserAuth
801 description: Use MD5 to verify Authentication Response to User (Au)
802 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
803 Also creates unobscured session key Kus from obscured
808 return value: 0 if ok, PS_RPA if mismatch
810 globals: reads authentication items listed above.
812 *********************************************************************/
814 static int CheckUserAuth(void)
816 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
818 unsigned char md5ans[16];
820 /* Create unobscured Kusu */
822 memcpy(p , Pu, Pul); p += Pul;
823 memset(p , '\0', 48); p += 48;
824 memcpy(p , Ns, Nsl); p += Nsl;
825 memcpy(p , Nu, Nul); p += Nul;
826 memcpy(p , Nr, Nrl); p += Nrl;
827 memcpy(p , Cs, Csl); p += Csl;
828 memcpy(p , Cu, Cul); p += Cul;
829 memcpy(p , Ts, Tsl); p += Tsl;
830 memcpy(p , Pu, Pul); p += Pul;
831 md5(workarea,p-workarea,md5ans);
832 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
833 /* Compute Au from our information */
835 memcpy(p , Pu, Pul); p += Pul;
836 memset(p , '\0', 48); p += 48;
837 memcpy(p , Ns, Nsl); p += Nsl;
838 memcpy(p , Nu, Nul); p += Nul;
839 memcpy(p , Nr, Nrl); p += Nrl;
840 memcpy(p , Kusu,Kusl);p += Kusl;
841 memcpy(p , Cs, Csl); p += Csl;
842 memcpy(p , Cu, Cul); p += Cul;
843 memcpy(p , Ts, Tsl); p += Tsl;
844 memcpy(p , Kus, Kusl);p += Kusl;
845 memcpy(p , Pu, Pul); p += Pul;
846 md5(workarea,p-workarea,md5ans);
847 /* Compare the two */
849 if (Au[i] != md5ans[i]) return(PS_RPA);
853 /*********************************************************************
855 description: Apply MD5
859 out 128 bit result buffer
861 calls: MD5 primitives
862 globals: reads outlevel
863 *********************************************************************/
865 static void md5(const void *in_,int len,unsigned char *out)
869 const unsigned char *in = (const unsigned char *)in_;
871 if (outlevel >= O_DEBUG)
873 report(stdout, GT_("MD5 being applied to data block:\n"));
874 for (i=0; i<len; i++)
876 report_build(stdout, "%02X ",in[i]);
877 if (((i % 16)==15) || (i==(len-1)))
878 report_complete(stdout, "\n");
881 MD5Init( &md5context );
882 MD5Update( &md5context, in, len );
883 MD5Final( out, &md5context );
884 if (outlevel >= O_DEBUG)
886 report(stdout, GT_("MD5 result is: \n"));
889 report_build(stdout, "%02X ",out[i]);
891 report_complete(stdout, "\n");
894 #endif /* POP3_ENABLE && RPA_ENABLE */
896 /* rpa.c ends here */