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(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 (argbuf,socket)
368 char buf [POPBUFSIZE];
372 if (outlevel >= O_DEBUG)
373 report(stdout, GT_("Get response\n"));
375 sockrc = gen_recv(socket, buf, sizeof(buf));
378 if (linecount == 1) strcpy(buf,line1);
379 if (linecount == 2) strcpy(buf,line2);
380 if (linecount == 3) strcpy(buf,line3);
381 /* report(stdout, "--> "); fflush(stderr); */
382 /* scanf("%s",&buf) */
385 if (sockrc == PS_SUCCESS) {
390 /* if (*bufp == ' ') bufp++; */
395 else if (strcmp(buf,"-ERR") == 0)
397 else ok = PS_PROTOCOL;
402 if (outlevel >= O_DEBUG)
403 report(stdout, GT_("Get response return %d [%s]\n"), ok, buf);
408 /*********************************************************************
410 description: Store token length encoded as per ASN.1 DER rules
411 buffer pointer stepped on appropriately.
412 Copes with numbers up to 32767 at least.
414 buf pointer to buffer to receive result
415 len length value to encode
420 *********************************************************************/
422 static void LenAppend(pptr_,len)
426 unsigned char **pptr = (unsigned char **)pptr_;
430 **pptr = len; (*pptr)++;
432 else if (len < 0x100)
434 **pptr = 0x81; (*pptr)++;
435 **pptr = len; (*pptr)++;
439 **pptr = 0x82; (*pptr)++;
440 **pptr = len >> 8; (*pptr)++;
441 **pptr = len & 0xFF; (*pptr)++;
445 /*********************************************************************
447 description: Check token header, length, and mechanism, and
450 pptr pointer to buffer pointer
451 rxlen number of bytes after base64 decode
453 return value: 0 if error, else token length value
455 globals: reads outlevel.
456 *********************************************************************/
458 int LenSkip(pptr,rxlen)
465 if ((unsigned char)**pptr != HDR)
467 if (outlevel > O_SILENT)
468 report(stderr, GT_("Hdr not 60\n"));
472 if (((unsigned char)(**pptr) & 0x80) == 0 )
474 len = (unsigned char)**pptr; (*pptr)++;
476 else if ((unsigned char)(**pptr) == 0x81)
478 len = (unsigned char)*(*pptr+1); (*pptr) += 2;
480 else if ((unsigned char)(**pptr) == 0x82)
482 len = ((unsigned char)(*(*pptr+1)) << 8) | (unsigned char)*(*pptr+2);
488 if (outlevel>O_SILENT)
489 report(stderr, GT_("Token length error\n"));
491 else if (((*pptr-save)+len) != rxlen)
493 if (outlevel>O_SILENT)
494 report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
497 else if (memcmp(*pptr,MECH,11))
499 if (outlevel > O_SILENT)
500 report(stderr, GT_("Mechanism field incorrect\n"));
503 else (*pptr) += 11; /* Skip mechanism field */
507 /*********************************************************************
509 description: Decode a Base64 string, overwriting the original.
510 Note that result cannot be longer than input.
515 return value: 0 if error, else number of bytes in decoded result
517 globals: reads outlevel.
518 *********************************************************************/
520 static int DecBase64(bufp)
523 unsigned int newx, bits=0, cnt=0, i, part=0;
527 while((ch=(unsigned char)*(inp++)) != 0)
529 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
531 if ((ch>='A') && (ch <= 'Z')) newx = ch - 'A';
532 else if ((ch>='a') && (ch <= 'z')) newx = ch - 'a' + 26;
533 else if ((ch>='0') && (ch <= '9')) newx = ch - '0' + 52;
534 else if ( ch=='+' ) newx = 62;
535 else if ( ch=='/' ) newx = 63;
537 report(stderr, GT_("dec64 error at char %d: %x\n"), inp - bufp, ch);
540 part=((part & 0x3F)*64) + newx;
545 *outp = (part >> bits);
550 if (outlevel >= O_MONITOR)
552 report(stdout, GT_("Inbound binary data:\n"));
553 for (i=0; i<cnt; i++)
555 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
556 if (((i % 16)==15) || (i==(cnt-1)))
557 report_complete(stdout, "\n");
563 /*********************************************************************
565 description: Encode into Base64 string, overwriting the original.
566 Note that result CAN be longer than input, the buffer
567 is assumed to be big enough. Result string is
572 len number of bytes in buffer (>0)
576 globals: reads outlevel;
577 *********************************************************************/
579 static void EncBase64(bufp,len)
584 unsigned char c1,c2,c3;
585 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
588 if (outlevel >= O_MONITOR)
590 report(stdout, GT_("Outbound data:\n"));
591 for (i=0; i<len; i++)
593 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
594 if (((i % 16)==15) || (i==(len-1)))
595 report_complete(stdout, "\n");
598 outp = bufp + (((len-1)/3)*4);
600 /* So we can do the update in place, start at the far end! */
601 for (i=((len-1)/3)*3; i>=0; i-=3)
603 c1 = (unsigned char)bufp[i];
604 if ((i+1) < len) c2 = (unsigned char)bufp[i+1]; else c2=0;
605 if ((i+2) < len) c3 = (unsigned char)bufp[i+2]; else c3=0;
607 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
608 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
609 else *(outp+2) = '=';
610 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
611 else *(outp+3) = '=';
616 /*********************************************************************
618 description: Convert ASCII (or iso-8859-1) byte string into
619 Unicode. Ensure length isn't too long (STRMAX).
622 pptr pointer to input buffer
623 delim delimiter character (in addition to \0)
624 buf buffer where Unicode will go
625 plen pointer to length variable (# bytes output)
626 conv 1 to convert to lowercase, 0 leaves alone
630 globals: reads outlevel;
631 *********************************************************************/
633 static void ToUnicode(char **pptr /* input string*/,
634 char delim, unsigned char *buf /* output buffer */,
640 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
644 *(p++) = tolower((unsigned char)**pptr);
650 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
652 if (outlevel > O_SILENT)
653 report(stderr, GT_("RPA String too long\n"));
656 if (outlevel >= O_DEBUG)
658 report(stdout, GT_("Unicode:\n"));
659 for (i=0; i<(*plen); i++)
661 report_build(stdout, "%02X ",buf[i]);
662 if (((i % 16)==15) || (i==((*plen)-1)))
663 report_complete(stdout, "\n");
668 /*********************************************************************
669 function: SetRealmService
670 description: Select a realm from list, and store it.
673 bufp pointer to buffer
677 globals: reads outlevel.
679 *********************************************************************/
681 static int SetRealmService(char *bufp)
683 /* For the moment we pick the first available realm. It would */
684 /* make more sense to verify that the realm which the user */
685 /* has given (as part of id) is in the list, and select it's */
686 /* corresponding service name. */
687 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
688 bufp++; /* Skip the @ */
689 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
690 if ((Nrl == 0) || (Nsl == 0))
695 /*********************************************************************
696 function: GenChallenge
697 description: Generate a random User challenge
700 buf pointer to buffer
705 globals: reads outlevel.
707 *********************************************************************/
709 static void GenChallenge(unsigned char *buf, int len)
714 devrandom = fopen("/dev/urandom","rb");
715 if (devrandom == NULL && outlevel > O_SILENT)
717 report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n"));
718 report(stdout, GT_(" prevent you logging in, but means you\n"));
719 report(stdout, GT_(" cannot be sure you are talking to the\n"));
720 report(stdout, GT_(" service that you think you are (replay\n"));
721 report(stdout, GT_(" attacks by a dishonest service are possible.)\n"));
725 buf[i] = devrandom ? fgetc(devrandom) : random();
728 fclose(devrandom); /* should be safe, file mode was "r" */
730 if (outlevel >= O_DEBUG)
732 report(stdout, GT_("User challenge:\n"));
733 for (i=0; i<len; i++)
735 report_build(stdout, "%02X ",buf[i]);
736 if (((i % 16)==15) || (i==(len-1)))
737 report_complete(stdout, "\n");
742 /*********************************************************************
743 function: DigestPassphrase
744 description: Use MD5 to compute digest (Pu) of Passphrase
745 Don't map to lower case. We assume the user is
746 aware of the case requirement of the realm.
747 (Why oh why have options in the spec?!)
749 passphrase buffer containing string, \0 terminated
750 rbuf buffer into which digest goes
752 return value: 0 if ok, else error code
754 globals: reads authentication items listed above.
756 *********************************************************************/
758 static int DigestPassphrase(char *passphrase,unsigned char *rbuf,
762 unsigned char workarea[STRMAX];
765 if (unicodeit) /* Option in spec. Yuck. */
768 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
771 md5(workarea,len,rbuf);
774 md5(rbuf,strlen(passphrase),rbuf);
778 /*********************************************************************
779 function: CompUserResp
780 description: Use MD5 to compute User Response (Ru) from
781 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
787 globals: reads authentication items listed above.
789 *********************************************************************/
791 static void CompUserResp(void)
793 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
796 memcpy(p , Pu, Pul); p += Pul;
797 memset(p , '\0', 48); p += 48;
798 memcpy(p , Nu, Nul); p += Nul;
799 memcpy(p , Ns, Nsl); p += Nsl;
800 memcpy(p , Nr, Nrl); p += Nrl;
801 memcpy(p , Cu, Cul); p += Cul;
802 memcpy(p , Cs, Csl); p += Csl;
803 memcpy(p , Ts, Tsl); p += Tsl;
804 memcpy(p , Pu, Pul); p += Pul;
805 md5(workarea,p-workarea,Ru);
808 /*********************************************************************
809 function: CheckUserAuth
810 description: Use MD5 to verify Authentication Response to User (Au)
811 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
812 Also creates unobscured session key Kus from obscured
817 return value: 0 if ok, PS_RPA if mismatch
819 globals: reads authentication items listed above.
821 *********************************************************************/
823 static int CheckUserAuth(void)
825 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
827 unsigned char md5ans[16];
829 /* Create unobscured Kusu */
831 memcpy(p , Pu, Pul); p += Pul;
832 memset(p , '\0', 48); p += 48;
833 memcpy(p , Ns, Nsl); p += Nsl;
834 memcpy(p , Nu, Nul); p += Nul;
835 memcpy(p , Nr, Nrl); p += Nrl;
836 memcpy(p , Cs, Csl); p += Csl;
837 memcpy(p , Cu, Cul); p += Cul;
838 memcpy(p , Ts, Tsl); p += Tsl;
839 memcpy(p , Pu, Pul); p += Pul;
840 md5(workarea,p-workarea,md5ans);
841 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
842 /* Compute Au from our information */
844 memcpy(p , Pu, Pul); p += Pul;
845 memset(p , '\0', 48); p += 48;
846 memcpy(p , Ns, Nsl); p += Nsl;
847 memcpy(p , Nu, Nul); p += Nul;
848 memcpy(p , Nr, Nrl); p += Nrl;
849 memcpy(p , Kusu,Kusl);p += Kusl;
850 memcpy(p , Cs, Csl); p += Csl;
851 memcpy(p , Cu, Cul); p += Cul;
852 memcpy(p , Ts, Tsl); p += Tsl;
853 memcpy(p , Kus, Kusl);p += Kusl;
854 memcpy(p , Pu, Pul); p += Pul;
855 md5(workarea,p-workarea,md5ans);
856 /* Compare the two */
858 if (Au[i] != md5ans[i]) return(PS_RPA);
862 /*********************************************************************
864 description: Apply MD5
868 out 128 bit result buffer
870 calls: MD5 primitives
871 globals: reads outlevel
872 *********************************************************************/
874 static void md5(void *in_,int len,unsigned char *out)
878 unsigned char *in = in_;
880 if (outlevel >= O_DEBUG)
882 report(stdout, GT_("MD5 being applied to data block:\n"));
883 for (i=0; i<len; i++)
885 report_build(stdout, "%02X ",in[i]);
886 if (((i % 16)==15) || (i==(len-1)))
887 report_complete(stdout, "\n");
890 MD5Init( &md5context );
891 MD5Update( &md5context, in, len );
892 MD5Final( out, &md5context );
893 if (outlevel >= O_DEBUG)
895 report(stdout, GT_("MD5 result is: \n"));
898 report_build(stdout, "%02X ",out[i]);
900 report_complete(stdout, "\n");
903 #endif /* POP3_ENABLE && RPA_ENABLE */
905 /* rpa.c ends here */