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 ***********************************************************************/
16 #if defined(POP3_ENABLE) && defined(RPA_ENABLE)
23 #include "fetchmail.h"
27 extern unsigned char line1[];
28 extern unsigned char line2[];
29 extern unsigned char line3[];
35 /* prototypes for internal functions */
36 static int POP3_rpa_resp(unsigned char* argbuf, int socket );
37 static void LenAppend(unsigned char** pptr, int len);
38 static int LenSkip(unsigned char** pptr, int rxlen);
39 static int DecBase64(unsigned char* bufp);
40 static void EncBase64(unsigned char* bufp, int len);
41 static void ToUnicode(unsigned char** pptr, unsigned char delim,
42 unsigned char* buf, int* plen, int conv);
43 static int SetRealmService(unsigned char* bufp);
44 static void GenChallenge(unsigned char* buf, int len);
45 static int DigestPassphrase(unsigned char* passphrase,
46 unsigned char* rbuf, int unicodeit);
47 static void CompUserResp();
48 static int CheckUserAuth();
49 static void md5(unsigned char* in, int len, unsigned char* out);
52 /* RPA protocol definitions */
54 #define EARLYVER "\x01\x00" /* Earliest supp version */
55 #define LATEVER "\x03\x00" /* Latest supp version */
56 #define HDR 0x60 /* ASN.1 SEQUENCE */
57 #define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
58 #define FLAGS "\x00\x01" /* Mutual authentication */
59 #define STRMAX 128 /* Bytes in Unicode */
60 #define Tsl 14 /* Timestamp bytelen */
61 #define Pul 16 /* Passphrase digest len */
62 #define Cul 16 /* Usr challenge bytelen */
63 #define Rul 16 /* Usr response bytelen */
64 #define Aul 16 /* User auth bytelen */
65 #define Kusl 16 /* Session key bytelen */
67 #define UNIPASS 1 /* 1=Unicode 0=iso8859 */
68 #define PS_RPA 42 /* Return code */
70 /* RPA authentication items */
72 unsigned char Cs[256]; /* Service challenge */
73 int Csl; /* Length of " " */
74 unsigned char Ts[Tsl+1]; /* Timestamp incl \0 */
75 unsigned char Nu[STRMAX]; /* Username in Unicode */
76 int Nul; /* Length of " in bytes */
77 unsigned char Ns[STRMAX]; /* Service in Unicode */
78 int Nsl; /* Length of " in bytes */
79 unsigned char Nr[STRMAX]; /* Realm in Unicode */
80 int Nrl; /* Length of " in bytes */
81 unsigned char Pu[Pul]; /* Passphrase after MD5 */
82 unsigned char Cu[Cul]; /* User challenge */
83 unsigned char Ru[Rul]; /* User response */
84 unsigned char Au[Aul]; /* User auth from Deity */
85 unsigned char Kusu[Kusl]; /* Obscured Session key */
86 unsigned char Kus[Kusl]; /* Session key */
88 /*********************************************************************
89 function: POP3_auth_rpa
90 description: send the AUTH RPA commands to the server, and
91 get the server's response. Then progress through the
92 RPA challenge/response protocol until we are
93 (hopefully) granted authorisation.
95 userid user's id@realm e.g. myuserid@csi.com
96 passphrase user's passphrase
97 (upper lower or mixed case as the realm has chosen.
98 spec allows various options :-( )
99 socket socket to which the server is connected.
101 return value: zero if success, else non-zero.
102 calls: SockPrintf, POP3_rpa_resp, EncBase64, DecBase64,
103 LenAppend, GenChallenge
104 globals: read outlevel.
105 *********************************************************************/
107 int POP3_auth_rpa (unsigned char *userid, unsigned char *passphrase, int socket)
109 int ok,rxlen,verh,verl,i,rll;
110 unsigned char buf [POPBUFSIZE];
112 int status,aulin,kuslin;
113 char* stdec[4] = { "Success" ,
114 "Restricted user (something wrong with account)" ,
115 "Invalid userid or passphrase" ,
118 /* Initiate RPA authorisation */
120 SockPrintf(socket,"AUTH RPA\r\n");
122 if (outlevel == O_VERBOSE)
123 error(0, 0, "> AUTH RPA\n");
125 /* Create unicode user name in Nu. */
126 /* Create MD5 digest of user's passphrase in Pu */
129 ToUnicode(&bufp, '@', Nu, &Nul, 1); /* User (lowercase) */
130 DigestPassphrase(passphrase, Pu, UNIPASS);
132 /* Get + response from server (RPA ready) */
134 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
136 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
137 error(0, 0, "%s\n",buf);
142 /* Assemble Token 1 in buf */
146 LenAppend(&bufp, 17);
147 memcpy(bufp, MECH, 11); bufp += 11;
148 memcpy(bufp, EARLYVER, 2); bufp += 2;
149 memcpy(bufp, LATEVER, 2); bufp += 2;
150 memcpy(bufp, FLAGS, 2); bufp += 2;
152 /* Send Token 1, receive Token 2 */
154 EncBase64(buf, bufp-buf);
156 SockPrintf(socket,"%s\r\n",buf);
158 if (outlevel == O_VERBOSE)
159 error(0, 0, "> %s\n",buf);
160 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
162 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
163 error(0, 0, "%s\n",buf);
166 if ((rxlen = DecBase64(buf)) == 0)
168 if (outlevel > O_SILENT)
169 error(0, 0, "RPA token 2: Base64 decode error\n");
173 *(buf+rxlen) = 0; /* Terminates realm list */
174 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
176 /* Interpret Token 2 */
178 verh = *(bufp++); verl = *(bufp++);
179 if (outlevel == O_VERBOSE)
180 error(0, 0, "Service chose RPA version %d.%d\n",verh,verl);
182 memcpy(Cs, bufp, Csl);
184 if (outlevel == O_VERBOSE)
186 error(0, 0, "Service challenge (l=%d):",Csl);
187 for (i=0; i<Csl; i++)
188 error_build("%02X ",Cs[i]);
189 error_complete(0, 0, "");
191 memcpy(Ts, bufp, Tsl);
194 if (outlevel == O_VERBOSE)
195 error(0, 0, "Service timestamp %s\n",Ts);
196 rll = *(bufp++) << 8; rll = rll | *(bufp++);
197 if ((bufp-buf+rll) != rxlen)
199 if (outlevel > O_SILENT)
200 error(0, 0, "RPA token 2 length error\n");
203 if (outlevel == O_VERBOSE)
204 error(0, 0, "Realm list: %s\n",bufp);
205 if (SetRealmService(bufp) != 0)
207 if (outlevel > O_SILENT)
208 error(0, 0, "RPA error in service@realm string\n");
212 /* Assemble Token 3 in buf */
216 LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
217 memcpy(bufp, MECH, 11); bufp += 11;
219 *(bufp++) = strlen(userid);
220 memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
221 GenChallenge(Cu,Cul);
223 memcpy(bufp, Cu, Cul); bufp += Cul;
226 memcpy(bufp, Ru, Rul); bufp += Rul;
228 /* Send Token 3, receive Token 4 */
230 EncBase64(buf,bufp-buf);
232 SockPrintf(socket,"%s\r\n",buf);
234 if (outlevel == O_VERBOSE)
235 error(0, 0, "> %s\n",buf);
236 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
238 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
239 error(0, 0, "%s\n",buf);
242 if ((rxlen = DecBase64(buf)) == 0)
244 if (outlevel > O_SILENT)
245 error(0, 0, "RPA token 4: Base64 decode error\n");
249 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
251 /* Interpret Token 4 */
254 if (outlevel == O_VERBOSE)
256 error(0, 0, "User authentication (l=%d):",aulin);
257 for (i=0; i<aulin; i++)
258 error_build("%02X ",bufp[i]);
259 error_complete(0, 0, "");
261 if (aulin == Aul) memcpy(Au, bufp, Aul);
264 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
269 if (outlevel == O_VERBOSE)
270 error(0, 0, "RPA status: %02X\n",status);
273 if ((bufp - buf) != rxlen)
275 if (outlevel > O_SILENT)
276 error(0, 0, "RPA token 4 length error\n");
281 if (outlevel > O_SILENT)
283 error(0, 0, "RPA rejects you: %s\n",stdec[status]);
285 error(0, 0, "RPA rejects you, reason unknown\n");
290 error(0, 0, "RPA User Authentication length error: %d\n",aulin);
295 error(0, 0, "RPA Session key length error: %d\n",kuslin);
298 if (CheckUserAuth() != 0)
300 if (outlevel > O_SILENT)
301 error(0, 0, "RPA _service_ auth fail. Spoof server?\n");
304 if (outlevel == O_VERBOSE)
306 error(0, 0, "Session key established:");
307 for (i=0; i<Kusl; i++)
308 error_build("%02X ",Kus[i]);
309 error_complete(0, 0, "");
312 /* Assemble Token 5 in buf and send (not in ver 2 though) */
313 /* Version 3.0 definitely replies with +OK to this. I have */
314 /* no idea what sort of response previous versions gave. */
320 LenAppend(&bufp, 1 );
322 EncBase64(buf,bufp-buf);
324 SockPrintf(socket,"%s\r\n",buf);
326 if (outlevel == O_VERBOSE)
327 error(0, 0, "> %s\n",buf);
328 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
330 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
331 error(0, 0, "%s\n",buf);
336 if (outlevel > O_SILENT)
337 error(0, 0, "RPA authorisation complete\n");
343 /*********************************************************************
344 function: POP3_rpa_resp
345 description: get the server's response to an RPA action.
346 Return received base64 string if successful
348 argbuf buffer to receive the string.
349 socket socket to which the server is connected.
351 return value: zero if okay, else return code.
353 globals: reads outlevel.
354 *********************************************************************/
356 static int POP3_rpa_resp (argbuf,socket)
357 unsigned char *argbuf;
361 char buf [POPBUFSIZE];
365 if (outlevel == O_VERBOSE)
366 error(0, 0, "Get response\n");
368 sockrc = gen_recv(socket, buf, sizeof(buf));
371 if (linecount == 1) strcpy(buf,line1);
372 if (linecount == 2) strcpy(buf,line2);
373 if (linecount == 3) strcpy(buf,line3);
374 /* error(0, 0, "--> "); fflush(stderr); */
375 /* scanf("%s",&buf) */
378 if (sockrc == PS_SUCCESS) {
383 /* if (*bufp == ' ') bufp++; */
388 else if (strcmp(buf,"-ERR") == 0)
390 else ok = PS_PROTOCOL;
395 if (outlevel == O_VERBOSE)
396 error(0, 0, "Get response return %d [%s]\n", ok, buf);
401 /*********************************************************************
403 description: Store token length encoded as per ASN.1 DER rules
404 buffer pointer stepped on appropriately.
405 Copes with numbers up to 32767 at least.
407 buf pointer to buffer to receive result
408 len length value to encode
413 *********************************************************************/
415 static void LenAppend(pptr,len)
416 unsigned char **pptr;
421 **pptr = len; (*pptr)++;
423 else if (len < 0x100)
425 **pptr = 0x81; (*pptr)++;
426 **pptr = len; (*pptr)++;
430 **pptr = 0x82; (*pptr)++;
431 **pptr = len >> 8; (*pptr)++;
432 **pptr = len & 0xFF; (*pptr)++;
436 /*********************************************************************
438 description: Check token header, length, and mechanism, and
441 pptr pointer to buffer pointer
442 rxlen number of bytes after base64 decode
444 return value: 0 if error, else token length value
446 globals: reads outlevel.
447 *********************************************************************/
449 int LenSkip(pptr,rxlen)
450 unsigned char **pptr;
458 if (outlevel > O_SILENT) error(0, 0, "Hdr not 60\n");
462 if (((**pptr) & 0x80) == 0 )
464 len = **pptr; (*pptr)++;
466 else if ((**pptr) == 0x81)
468 len = *(*pptr+1); (*pptr) += 2;
470 else if ((**pptr) == 0x82)
472 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
478 if (outlevel>O_SILENT)
479 error(0, 0, "Token length error\n");
481 else if (((*pptr-save)+len) != rxlen)
483 if (outlevel>O_SILENT)
484 error(0, 0, "Token Length %d disagrees with rxlen %d\n",len,rxlen);
487 else if (memcmp(*pptr,MECH,11))
489 if (outlevel > O_SILENT)
490 error(0, 0, "Mechanism field incorrect\n");
493 else (*pptr) += 11; /* Skip mechanism field */
497 /*********************************************************************
499 description: Decode a Base64 string, overwriting the original.
500 Note that result cannot be longer than input.
505 return value: 0 if error, else number of bytes in decoded result
507 globals: reads outlevel.
508 *********************************************************************/
510 static int DecBase64(bufp)
513 unsigned int new, bits=0, cnt=0, i, part=0;
515 unsigned char* outp=bufp;
516 unsigned char* inp=bufp;
517 while((ch=*(inp++)) != 0)
519 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
521 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
522 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
523 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
524 else if ( ch=='+' ) new = 62;
525 else if ( ch=='/' ) new = 63;
527 error(0, 0, "dec64 error at char %d: %x\n", inp - bufp, ch);
530 part=((part & 0x3F)*64) + new;
535 *outp = (part >> bits);
540 if (outlevel == O_VERBOSE)
542 error(0, 0, "Inbound binary data:\n");
543 for (i=0; i<cnt; i++)
545 error_build("%02X ",bufp[i]);
546 if (((i % 16)==15) || (i==(cnt-1)))
547 error_complete(0, 0, "");
553 /*********************************************************************
555 description: Encode into Base64 string, overwriting the original.
556 Note that result CAN be longer than input, the buffer
557 is assumed to be big enough. Result string is
562 len number of bytes in buffer (>0)
566 globals: reads outlevel;
567 *********************************************************************/
569 static void EncBase64(bufp,len)
574 unsigned char c1,c2,c3;
575 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
578 if (outlevel == O_VERBOSE)
580 error(0, 0, "Outbound data:\n");
581 for (i=0; i<len; i++)
583 error(0, 0, " %02X",bufp[i]);
584 if (((i % 16)==15) || (i==(len-1)))
588 outp = bufp + (((len-1)/3)*4);
590 /* So we can do the update in place, start at the far end! */
591 for (i=((len-1)/3)*3; i>=0; i-=3)
594 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
595 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
597 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
598 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
599 else *(outp+2) = '=';
600 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
601 else *(outp+3) = '=';
606 /*********************************************************************
608 description: Convert ASCII (or iso-8859-1) byte string into
609 Unicode. Ensure length isn't too long (STRMAX).
612 pptr pointer to input buffer
613 delim delimiter character (in addition to \0)
614 buf buffer where Unicode will go
615 plen pointer to length variable (# bytes output)
616 conv 1 to convert to lowercase, 0 leaves alone
620 globals: reads outlevel;
621 *********************************************************************/
623 static void ToUnicode(pptr,delim,buf,plen,conv)
624 unsigned char **pptr; /* input string */
626 unsigned char *buf; /* output buffer */
633 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
637 *(p++) = tolower(**pptr);
643 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
645 if (outlevel > O_SILENT)
646 error(0, 0, "RPA String too long\n");
649 if (outlevel == O_VERBOSE)
651 error(0, 0, "Unicode:");
652 for (i=0; i<(*plen); i++) error(0, 0, "%02X ",buf[i]);
657 /*********************************************************************
658 function: SetRealmService
659 description: Select a realm from list, and store it.
662 bufp pointer to buffer
666 globals: reads outlevel.
668 *********************************************************************/
670 static int SetRealmService(bufp)
673 /* For the moment we pick the first available realm. It would */
674 /* make more sense to verify that the realm which the user */
675 /* has given (as part of id) is in the list, and select it's */
676 /* corresponding service name. */
677 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
678 bufp++; /* Skip the @ */
679 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
680 if ((Nrl == 0) || (Nsl == 0))
685 /*********************************************************************
686 function: GenChallenge
687 description: Generate a random User challenge
690 buf pointer to buffer
695 globals: reads outlevel.
697 *********************************************************************/
699 static void GenChallenge(buf,len)
706 devrandom = fopen("/dev/urandom","rb");
707 if (devrandom == NULL && outlevel > O_SILENT)
709 error(0, 0, "RPA Failed open of /dev/urandom. This shouldn't\n");
710 error(0, 0, " prevent you logging in, but means you\n");
711 error(0, 0, " cannot be sure you are talking to the\n");
712 error(0, 0, " service that you think you are (replay\n");
713 error(0, 0, " attacks by a dishonest service are possible.)\n");
717 buf[i] = devrandom ? fgetc(devrandom) : random();
722 if (outlevel == O_VERBOSE)
724 error(0, 0, "User challenge:");
725 for (i=0; i<len; i++) error(0, 0, " %02X",buf[i]);
730 /*********************************************************************
731 function: DigestPassphrase
732 description: Use MD5 to compute digest (Pu) of Passphrase
733 Don't map to lower case. We assume the user is
734 aware of the case requirement of the realm.
735 (Why oh why have options in the spec?!)
737 passphrase buffer containing string, \0 terminated
738 rbuf buffer into which digest goes
740 return value: 0 if ok, else error code
742 globals: reads authentication items listed above.
744 *********************************************************************/
746 static int DigestPassphrase(passphrase,rbuf,unicodeit)
747 unsigned char *passphrase;
752 unsigned char workarea[STRMAX];
755 if (unicodeit) /* Option in spec. Yuck. */
758 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
766 len = strlen(passphrase);
772 /*********************************************************************
773 function: CompUserResp
774 description: Use MD5 to compute User Response (Ru) from
775 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
781 globals: reads authentication items listed above.
783 *********************************************************************/
785 static void CompUserResp()
787 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
790 memcpy(p , Pu, Pul); p += Pul;
791 memset(p , '\0', 48); p += 48;
792 memcpy(p , Nu, Nul); p += Nul;
793 memcpy(p , Ns, Nsl); p += Nsl;
794 memcpy(p , Nr, Nrl); p += Nrl;
795 memcpy(p , Cu, Cul); p += Cul;
796 memcpy(p , Cs, Csl); p += Csl;
797 memcpy(p , Ts, Tsl); p += Tsl;
798 memcpy(p , Pu, Pul); p += Pul;
799 md5(workarea,p-workarea,Ru);
802 /*********************************************************************
803 function: CheckUserAuth
804 description: Use MD5 to verify Authentication Response to User (Au)
805 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
806 Also creates unobscured session key Kus from obscured
811 return value: 0 if ok, PS_RPA if mismatch
813 globals: reads authentication items listed above.
815 *********************************************************************/
817 static int CheckUserAuth()
819 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
821 unsigned char md5ans[16];
823 /* Create unobscured Kusu */
825 memcpy(p , Pu, Pul); p += Pul;
826 memset(p , '\0', 48); p += 48;
827 memcpy(p , Ns, Nsl); p += Nsl;
828 memcpy(p , Nu, Nul); p += Nul;
829 memcpy(p , Nr, Nrl); p += Nrl;
830 memcpy(p , Cs, Csl); p += Csl;
831 memcpy(p , Cu, Cul); p += Cul;
832 memcpy(p , Ts, Tsl); p += Tsl;
833 memcpy(p , Pu, Pul); p += Pul;
834 md5(workarea,p-workarea,md5ans);
835 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
836 /* Compute Au from our information */
838 memcpy(p , Pu, Pul); p += Pul;
839 memset(p , '\0', 48); p += 48;
840 memcpy(p , Ns, Nsl); p += Nsl;
841 memcpy(p , Nu, Nul); p += Nul;
842 memcpy(p , Nr, Nrl); p += Nrl;
843 memcpy(p , Kusu,Kusl);p += Kusl;
844 memcpy(p , Cs, Csl); p += Csl;
845 memcpy(p , Cu, Cul); p += Cul;
846 memcpy(p , Ts, Tsl); p += Tsl;
847 memcpy(p , Kus, Kusl);p += Kusl;
848 memcpy(p , Pu, Pul); p += Pul;
849 md5(workarea,p-workarea,md5ans);
850 /* Compare the two */
852 if (Au[i] != md5ans[i]) return(PS_RPA);
856 /*********************************************************************
858 description: Apply MD5
862 out 128 bit result buffer
864 calls: MD5 primitives
865 globals: reads outlevel
866 *********************************************************************/
868 static void md5(in,len,out)
876 if (outlevel == O_VERBOSE)
878 error(0, 0, "MD5 being applied to data block:\n");
879 for (i=0; i<len; i++)
881 error(0, 0, " %02X",in[i]);
882 if (((i % 16)==15) || (i==(len-1))) error(0, 0, "\n");
885 MD5Init( &md5context );
886 MD5Update( &md5context, in, len );
887 MD5Final( out, &md5context );
888 if (outlevel == O_VERBOSE)
890 error(0, 0, "MD5 result is: ");
891 for (i=0; i<16; i++) error(0, 0, "%02X ",out[i]);
895 #endif /* POP3_ENABLE && RPA_ENABLE */
897 /* rpa.c ends here */