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)
428 **pptr = len; (*pptr)++;
430 else if (len < 0x100)
432 **pptr = 0x81; (*pptr)++;
433 **pptr = len; (*pptr)++;
437 **pptr = 0x82; (*pptr)++;
438 **pptr = len >> 8; (*pptr)++;
439 **pptr = len & 0xFF; (*pptr)++;
443 /*********************************************************************
445 description: Check token header, length, and mechanism, and
448 pptr pointer to buffer pointer
449 rxlen number of bytes after base64 decode
451 return value: 0 if error, else token length value
453 globals: reads outlevel.
454 *********************************************************************/
456 int LenSkip(pptr,rxlen)
463 if ((unsigned char)**pptr != HDR)
465 if (outlevel > O_SILENT)
466 report(stderr, GT_("Hdr not 60\n"));
470 if (((unsigned char)(**pptr) & 0x80) == 0 )
472 len = (unsigned char)**pptr; (*pptr)++;
474 else if ((unsigned char)(**pptr) == 0x81)
476 len = (unsigned char)*(*pptr+1); (*pptr) += 2;
478 else if ((unsigned char)(**pptr) == 0x82)
480 len = ((unsigned char)(*(*pptr+1)) << 8) | (unsigned char)*(*pptr+2);
486 if (outlevel>O_SILENT)
487 report(stderr, GT_("Token length error\n"));
489 else if (((*pptr-save)+len) != rxlen)
491 if (outlevel>O_SILENT)
492 report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
495 else if (memcmp(*pptr,MECH,11))
497 if (outlevel > O_SILENT)
498 report(stderr, GT_("Mechanism field incorrect\n"));
501 else (*pptr) += 11; /* Skip mechanism field */
505 /*********************************************************************
507 description: Decode a Base64 string, overwriting the original.
508 Note that result cannot be longer than input.
513 return value: 0 if error, else number of bytes in decoded result
515 globals: reads outlevel.
516 *********************************************************************/
518 static int DecBase64(bufp)
521 unsigned int newx, bits=0, cnt=0, i, part=0;
525 while((ch=(unsigned char)*(inp++)) != 0)
527 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
529 if ((ch>='A') && (ch <= 'Z')) newx = ch - 'A';
530 else if ((ch>='a') && (ch <= 'z')) newx = ch - 'a' + 26;
531 else if ((ch>='0') && (ch <= '9')) newx = ch - '0' + 52;
532 else if ( ch=='+' ) newx = 62;
533 else if ( ch=='/' ) newx = 63;
535 report(stderr, GT_("dec64 error at char %d: %x\n"), inp - bufp, ch);
538 part=((part & 0x3F)*64) + newx;
543 *outp = (part >> bits);
548 if (outlevel >= O_MONITOR)
550 report(stdout, GT_("Inbound binary data:\n"));
551 for (i=0; i<cnt; i++)
553 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
554 if (((i % 16)==15) || (i==(cnt-1)))
555 report_complete(stdout, "\n");
561 /*********************************************************************
563 description: Encode into Base64 string, overwriting the original.
564 Note that result CAN be longer than input, the buffer
565 is assumed to be big enough. Result string is
570 len number of bytes in buffer (>0)
574 globals: reads outlevel;
575 *********************************************************************/
577 static void EncBase64(bufp,len)
582 unsigned char c1,c2,c3;
583 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
586 if (outlevel >= O_MONITOR)
588 report(stdout, GT_("Outbound data:\n"));
589 for (i=0; i<len; i++)
591 report_build(stdout, "%02X ",(unsigned char)bufp[i]);
592 if (((i % 16)==15) || (i==(len-1)))
593 report_complete(stdout, "\n");
596 outp = bufp + (((len-1)/3)*4);
598 /* So we can do the update in place, start at the far end! */
599 for (i=((len-1)/3)*3; i>=0; i-=3)
601 c1 = (unsigned char)bufp[i];
602 if ((i+1) < len) c2 = (unsigned char)bufp[i+1]; else c2=0;
603 if ((i+2) < len) c3 = (unsigned char)bufp[i+2]; else c3=0;
605 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
606 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
607 else *(outp+2) = '=';
608 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
609 else *(outp+3) = '=';
614 /*********************************************************************
616 description: Convert ASCII (or iso-8859-1) byte string into
617 Unicode. Ensure length isn't too long (STRMAX).
620 pptr pointer to input buffer
621 delim delimiter character (in addition to \0)
622 buf buffer where Unicode will go
623 plen pointer to length variable (# bytes output)
624 conv 1 to convert to lowercase, 0 leaves alone
628 globals: reads outlevel;
629 *********************************************************************/
631 static void ToUnicode(char **pptr /* input string*/,
632 char delim, unsigned char *buf /* output buffer */,
638 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
642 *(p++) = tolower((unsigned char)**pptr);
648 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
650 if (outlevel > O_SILENT)
651 report(stderr, GT_("RPA String too long\n"));
654 if (outlevel >= O_DEBUG)
656 report(stdout, GT_("Unicode:\n"));
657 for (i=0; i<(*plen); i++)
659 report_build(stdout, "%02X ",buf[i]);
660 if (((i % 16)==15) || (i==((*plen)-1)))
661 report_complete(stdout, "\n");
666 /*********************************************************************
667 function: SetRealmService
668 description: Select a realm from list, and store it.
671 bufp pointer to buffer
675 globals: reads outlevel.
677 *********************************************************************/
679 static int SetRealmService(char *bufp)
681 /* For the moment we pick the first available realm. It would */
682 /* make more sense to verify that the realm which the user */
683 /* has given (as part of id) is in the list, and select it's */
684 /* corresponding service name. */
685 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
686 bufp++; /* Skip the @ */
687 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
688 if ((Nrl == 0) || (Nsl == 0))
693 /*********************************************************************
694 function: GenChallenge
695 description: Generate a random User challenge
698 buf pointer to buffer
703 globals: reads outlevel.
705 *********************************************************************/
707 static void GenChallenge(unsigned char *buf, int len)
712 devrandom = fopen("/dev/urandom","rb");
713 if (devrandom == NULL && outlevel > O_SILENT)
715 report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n"));
716 report(stdout, GT_(" prevent you logging in, but means you\n"));
717 report(stdout, GT_(" cannot be sure you are talking to the\n"));
718 report(stdout, GT_(" service that you think you are (replay\n"));
719 report(stdout, GT_(" attacks by a dishonest service are possible.)\n"));
723 buf[i] = devrandom ? fgetc(devrandom) : random();
726 fclose(devrandom); /* should be safe, file mode was "r" */
728 if (outlevel >= O_DEBUG)
730 report(stdout, GT_("User challenge:\n"));
731 for (i=0; i<len; i++)
733 report_build(stdout, "%02X ",buf[i]);
734 if (((i % 16)==15) || (i==(len-1)))
735 report_complete(stdout, "\n");
740 /*********************************************************************
741 function: DigestPassphrase
742 description: Use MD5 to compute digest (Pu) of Passphrase
743 Don't map to lower case. We assume the user is
744 aware of the case requirement of the realm.
745 (Why oh why have options in the spec?!)
747 passphrase buffer containing string, \0 terminated
748 rbuf buffer into which digest goes
750 return value: 0 if ok, else error code
752 globals: reads authentication items listed above.
754 *********************************************************************/
756 static int DigestPassphrase(char *passphrase,unsigned char *rbuf,
760 unsigned char workarea[STRMAX];
763 if (unicodeit) /* Option in spec. Yuck. */
766 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
769 md5(workarea,len,rbuf);
772 md5(rbuf,strlen(passphrase),rbuf);
776 /*********************************************************************
777 function: CompUserResp
778 description: Use MD5 to compute User Response (Ru) from
779 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
785 globals: reads authentication items listed above.
787 *********************************************************************/
789 static void CompUserResp(void)
791 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
794 memcpy(p , Pu, Pul); p += Pul;
795 memset(p , '\0', 48); p += 48;
796 memcpy(p , Nu, Nul); p += Nul;
797 memcpy(p , Ns, Nsl); p += Nsl;
798 memcpy(p , Nr, Nrl); p += Nrl;
799 memcpy(p , Cu, Cul); p += Cul;
800 memcpy(p , Cs, Csl); p += Csl;
801 memcpy(p , Ts, Tsl); p += Tsl;
802 memcpy(p , Pu, Pul); p += Pul;
803 md5(workarea,p-workarea,Ru);
806 /*********************************************************************
807 function: CheckUserAuth
808 description: Use MD5 to verify Authentication Response to User (Au)
809 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
810 Also creates unobscured session key Kus from obscured
815 return value: 0 if ok, PS_RPA if mismatch
817 globals: reads authentication items listed above.
819 *********************************************************************/
821 static int CheckUserAuth(void)
823 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
825 unsigned char md5ans[16];
827 /* Create unobscured Kusu */
829 memcpy(p , Pu, Pul); p += Pul;
830 memset(p , '\0', 48); p += 48;
831 memcpy(p , Ns, Nsl); p += Nsl;
832 memcpy(p , Nu, Nul); p += Nul;
833 memcpy(p , Nr, Nrl); p += Nrl;
834 memcpy(p , Cs, Csl); p += Csl;
835 memcpy(p , Cu, Cul); p += Cul;
836 memcpy(p , Ts, Tsl); p += Tsl;
837 memcpy(p , Pu, Pul); p += Pul;
838 md5(workarea,p-workarea,md5ans);
839 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
840 /* Compute Au from our information */
842 memcpy(p , Pu, Pul); p += Pul;
843 memset(p , '\0', 48); p += 48;
844 memcpy(p , Ns, Nsl); p += Nsl;
845 memcpy(p , Nu, Nul); p += Nul;
846 memcpy(p , Nr, Nrl); p += Nrl;
847 memcpy(p , Kusu,Kusl);p += Kusl;
848 memcpy(p , Cs, Csl); p += Csl;
849 memcpy(p , Cu, Cul); p += Cul;
850 memcpy(p , Ts, Tsl); p += Tsl;
851 memcpy(p , Kus, Kusl);p += Kusl;
852 memcpy(p , Pu, Pul); p += Pul;
853 md5(workarea,p-workarea,md5ans);
854 /* Compare the two */
856 if (Au[i] != md5ans[i]) return(PS_RPA);
860 /*********************************************************************
862 description: Apply MD5
866 out 128 bit result buffer
868 calls: MD5 primitives
869 globals: reads outlevel
870 *********************************************************************/
872 static void md5(void *in_,int len,unsigned char *out)
876 unsigned char *in = in_;
878 if (outlevel >= O_DEBUG)
880 report(stdout, GT_("MD5 being applied to data block:\n"));
881 for (i=0; i<len; i++)
883 report_build(stdout, "%02X ",in[i]);
884 if (((i % 16)==15) || (i==(len-1)))
885 report_complete(stdout, "\n");
888 MD5Init( &md5context );
889 MD5Update( &md5context, in, len );
890 MD5Final( out, &md5context );
891 if (outlevel >= O_DEBUG)
893 report(stdout, GT_("MD5 result is: \n"));
896 report_build(stdout, "%02X ",out[i]);
898 report_complete(stdout, "\n");
901 #endif /* POP3_ENABLE && RPA_ENABLE */
903 /* rpa.c ends here */