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++) error(0, 0, " %02X",Cs[i]);
190 memcpy(Ts, bufp, Tsl);
193 if (outlevel == O_VERBOSE)
194 error(0, 0, "Service timestamp %s\n",Ts);
195 rll = *(bufp++) << 8; rll = rll | *(bufp++);
196 if ((bufp-buf+rll) != rxlen)
198 if (outlevel > O_SILENT)
199 error(0, 0, "RPA token 2 length error\n");
202 if (outlevel == O_VERBOSE)
203 error(0, 0, "Realm list: %s\n",bufp);
204 if (SetRealmService(bufp) != 0)
206 if (outlevel > O_SILENT)
207 error(0, 0, "RPA error in service@realm string\n");
211 /* Assemble Token 3 in buf */
215 LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
216 memcpy(bufp, MECH, 11); bufp += 11;
218 *(bufp++) = strlen(userid);
219 memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
220 GenChallenge(Cu,Cul);
222 memcpy(bufp, Cu, Cul); bufp += Cul;
225 memcpy(bufp, Ru, Rul); bufp += Rul;
227 /* Send Token 3, receive Token 4 */
229 EncBase64(buf,bufp-buf);
231 SockPrintf(socket,"%s\r\n",buf);
233 if (outlevel == O_VERBOSE)
234 error(0, 0, "> %s\n",buf);
235 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
237 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
238 error(0, 0, "%s\n",buf);
241 if ((rxlen = DecBase64(buf)) == 0)
243 if (outlevel > O_SILENT)
244 error(0, 0, "RPA token 4: Base64 decode error\n");
248 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
250 /* Interpret Token 4 */
253 if (outlevel == O_VERBOSE)
255 error(0, 0, "User authentication (l=%d):",aulin);
256 for (i=0; i<aulin; i++) error(0, 0, " %02X",bufp[i]);
259 if (aulin == Aul) memcpy(Au, bufp, Aul);
262 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
267 if (outlevel == O_VERBOSE)
268 error(0, 0, "RPA status: %02X\n",status);
271 if ((bufp - buf) != rxlen)
273 if (outlevel > O_SILENT)
274 error(0, 0, "RPA token 4 length error\n");
279 if (outlevel > O_SILENT)
281 error(0, 0, "RPA rejects you: %s\n",stdec[status]);
283 error(0, 0, "RPA rejects you, reason unknown\n");
288 error(0, 0, "RPA User Authentication length error: %d\n",aulin);
293 error(0, 0, "RPA Session key length error: %d\n",kuslin);
296 if (CheckUserAuth() != 0)
298 if (outlevel > O_SILENT)
299 error(0, 0, "RPA _service_ auth fail. Spoof server?\n");
302 if (outlevel == O_VERBOSE)
304 error(0, 0, "Session key established:");
305 for (i=0; i<Kusl; i++) error(0, 0, " %02X",Kus[i]);
309 /* Assemble Token 5 in buf and send (not in ver 2 though) */
310 /* Version 3.0 definitely replies with +OK to this. I have */
311 /* no idea what sort of response previous versions gave. */
317 LenAppend(&bufp, 1 );
319 EncBase64(buf,bufp-buf);
321 SockPrintf(socket,"%s\r\n",buf);
323 if (outlevel == O_VERBOSE)
324 error(0, 0, "> %s\n",buf);
325 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
327 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
328 error(0, 0, "%s\n",buf);
333 if (outlevel > O_SILENT)
334 error(0, 0, "RPA authorisation complete\n");
340 /*********************************************************************
341 function: POP3_rpa_resp
342 description: get the server's response to an RPA action.
343 Return received base64 string if successful
345 argbuf buffer to receive the string.
346 socket socket to which the server is connected.
348 return value: zero if okay, else return code.
350 globals: reads outlevel.
351 *********************************************************************/
353 static int POP3_rpa_resp (argbuf,socket)
354 unsigned char *argbuf;
358 char buf [POPBUFSIZE];
362 if (outlevel == O_VERBOSE)
363 error(0, 0, "Get response\n");
365 sockrc = gen_recv(socket, buf, sizeof(buf));
368 if (linecount == 1) strcpy(buf,line1);
369 if (linecount == 2) strcpy(buf,line2);
370 if (linecount == 3) strcpy(buf,line3);
371 /* error(0, 0, "--> "); fflush(stderr); */
372 /* scanf("%s",&buf) */
375 if (sockrc == PS_SUCCESS) {
380 /* if (*bufp == ' ') bufp++; */
385 else if (strcmp(buf,"-ERR") == 0)
387 else ok = PS_PROTOCOL;
392 if (outlevel == O_VERBOSE)
393 error(0, 0, "Get response return %d [%s]\n", ok, buf);
398 /*********************************************************************
400 description: Store token length encoded as per ASN.1 DER rules
401 buffer pointer stepped on appropriately.
402 Copes with numbers up to 32767 at least.
404 buf pointer to buffer to receive result
405 len length value to encode
410 *********************************************************************/
412 static void LenAppend(pptr,len)
413 unsigned char **pptr;
418 **pptr = len; (*pptr)++;
420 else if (len < 0x100)
422 **pptr = 0x81; (*pptr)++;
423 **pptr = len; (*pptr)++;
427 **pptr = 0x82; (*pptr)++;
428 **pptr = len >> 8; (*pptr)++;
429 **pptr = len & 0xFF; (*pptr)++;
433 /*********************************************************************
435 description: Check token header, length, and mechanism, and
438 pptr pointer to buffer pointer
439 rxlen number of bytes after base64 decode
441 return value: 0 if error, else token length value
443 globals: reads outlevel.
444 *********************************************************************/
446 int LenSkip(pptr,rxlen)
447 unsigned char **pptr;
455 if (outlevel > O_SILENT) error(0, 0, "Hdr not 60\n");
459 if (((**pptr) & 0x80) == 0 )
461 len = **pptr; (*pptr)++;
463 else if ((**pptr) == 0x81)
465 len = *(*pptr+1); (*pptr) += 2;
467 else if ((**pptr) == 0x82)
469 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
475 if (outlevel>O_SILENT)
476 error(0, 0, "Token length error\n");
478 else if (((*pptr-save)+len) != rxlen)
480 if (outlevel>O_SILENT)
481 error(0, 0, "Token Length %d disagrees with rxlen %d\n",len,rxlen);
484 else if (memcmp(*pptr,MECH,11))
486 if (outlevel > O_SILENT)
487 error(0, 0, "Mechanism field incorrect\n");
490 else (*pptr) += 11; /* Skip mechanism field */
494 /*********************************************************************
496 description: Decode a Base64 string, overwriting the original.
497 Note that result cannot be longer than input.
502 return value: 0 if error, else number of bytes in decoded result
504 globals: reads outlevel.
505 *********************************************************************/
507 static int DecBase64(bufp)
510 unsigned int new, bits=0, cnt=0, i, part=0;
512 unsigned char* outp=bufp;
513 unsigned char* inp=bufp;
514 while((ch=*(inp++)) != 0)
516 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
518 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
519 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
520 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
521 else if ( ch=='+' ) new = 62;
522 else if ( ch=='/' ) new = 63;
524 error(0, 0, "dec64 error at char %d: %x\n", inp - bufp, ch);
527 part=((part & 0x3F)*64) + new;
532 *outp = (part >> bits);
537 if (outlevel == O_VERBOSE)
539 error(0, 0, "Inbound binary data:\n");
540 for (i=0; i<cnt; i++)
542 error(0, 0, " %02X",bufp[i]);
543 if (((i % 16)==15) || (i==(cnt-1)))
550 /*********************************************************************
552 description: Encode into Base64 string, overwriting the original.
553 Note that result CAN be longer than input, the buffer
554 is assumed to be big enough. Result string is
559 len number of bytes in buffer (>0)
563 globals: reads outlevel;
564 *********************************************************************/
566 static void EncBase64(bufp,len)
571 unsigned char c1,c2,c3;
572 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
575 if (outlevel == O_VERBOSE)
577 error(0, 0, "Outbound data:\n");
578 for (i=0; i<len; i++)
580 error(0, 0, " %02X",bufp[i]);
581 if (((i % 16)==15) || (i==(len-1)))
585 outp = bufp + (((len-1)/3)*4);
587 /* So we can do the update in place, start at the far end! */
588 for (i=((len-1)/3)*3; i>=0; i-=3)
591 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
592 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
594 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
595 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
596 else *(outp+2) = '=';
597 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
598 else *(outp+3) = '=';
603 /*********************************************************************
605 description: Convert ASCII (or iso-8859-1) byte string into
606 Unicode. Ensure length isn't too long (STRMAX).
609 pptr pointer to input buffer
610 delim delimiter character (in addition to \0)
611 buf buffer where Unicode will go
612 plen pointer to length variable (# bytes output)
613 conv 1 to convert to lowercase, 0 leaves alone
617 globals: reads outlevel;
618 *********************************************************************/
620 static void ToUnicode(pptr,delim,buf,plen,conv)
621 unsigned char **pptr; /* input string */
623 unsigned char *buf; /* output buffer */
630 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
634 *(p++) = tolower(**pptr);
640 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
642 if (outlevel > O_SILENT)
643 error(0, 0, "RPA String too long\n");
646 if (outlevel == O_VERBOSE)
648 error(0, 0, "Unicode:");
649 for (i=0; i<(*plen); i++) error(0, 0, "%02X ",buf[i]);
654 /*********************************************************************
655 function: SetRealmService
656 description: Select a realm from list, and store it.
659 bufp pointer to buffer
663 globals: reads outlevel.
665 *********************************************************************/
667 static int SetRealmService(bufp)
670 /* For the moment we pick the first available realm. It would */
671 /* make more sense to verify that the realm which the user */
672 /* has given (as part of id) is in the list, and select it's */
673 /* corresponding service name. */
674 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
675 bufp++; /* Skip the @ */
676 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
677 if ((Nrl == 0) || (Nsl == 0))
682 /*********************************************************************
683 function: GenChallenge
684 description: Generate a random User challenge
687 buf pointer to buffer
692 globals: reads outlevel.
694 *********************************************************************/
696 static void GenChallenge(buf,len)
702 devrandom = fopen("/dev/urandom","rb");
703 if (devrandom == NULL)
705 if (outlevel > O_SILENT)
706 error(0, 0, "RPA Failed open of /dev/random. This shouldn't\n");
707 error(0, 0, " prevent you logging in, but means you\n");
708 error(0, 0, " cannot be sure you are talking to the\n");
709 error(0, 0, " service that you think you are (replay\n");
710 error(0, 0, " attacks by a dishonest service are possible.)\n");
712 for (i=0; i<len; i++) buf[i] = fgetc(devrandom);
713 // for (i=0; i<len; i++) buf[i] = random();
715 if (outlevel == O_VERBOSE)
717 error(0, 0, "User challenge:");
718 for (i=0; i<len; i++) error(0, 0, " %02X",buf[i]);
723 /*********************************************************************
724 function: DigestPassphrase
725 description: Use MD5 to compute digest (Pu) of Passphrase
726 Don't map to lower case. We assume the user is
727 aware of the case requirement of the realm.
728 (Why oh why have options in the spec?!)
730 passphrase buffer containing string, \0 terminated
731 rbuf buffer into which digest goes
733 return value: 0 if ok, else error code
735 globals: reads authentication items listed above.
737 *********************************************************************/
739 static int DigestPassphrase(passphrase,rbuf,unicodeit)
740 unsigned char *passphrase;
745 unsigned char workarea[STRMAX];
748 if (unicodeit) /* Option in spec. Yuck. */
751 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
759 len = strlen(passphrase);
765 /*********************************************************************
766 function: CompUserResp
767 description: Use MD5 to compute User Response (Ru) from
768 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
774 globals: reads authentication items listed above.
776 *********************************************************************/
778 static void CompUserResp()
780 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
783 memcpy(p , Pu, Pul); p += Pul;
784 memset(p , '\0', 48); p += 48;
785 memcpy(p , Nu, Nul); p += Nul;
786 memcpy(p , Ns, Nsl); p += Nsl;
787 memcpy(p , Nr, Nrl); p += Nrl;
788 memcpy(p , Cu, Cul); p += Cul;
789 memcpy(p , Cs, Csl); p += Csl;
790 memcpy(p , Ts, Tsl); p += Tsl;
791 memcpy(p , Pu, Pul); p += Pul;
792 md5(workarea,p-workarea,Ru);
795 /*********************************************************************
796 function: CheckUserAuth
797 description: Use MD5 to verify Authentication Response to User (Au)
798 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
799 Also creates unobscured session key Kus from obscured
804 return value: 0 if ok, PS_RPA if mismatch
806 globals: reads authentication items listed above.
808 *********************************************************************/
810 static int CheckUserAuth()
812 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
814 unsigned char md5ans[16];
816 /* Create unobscured Kusu */
818 memcpy(p , Pu, Pul); p += Pul;
819 memset(p , '\0', 48); p += 48;
820 memcpy(p , Ns, Nsl); p += Nsl;
821 memcpy(p , Nu, Nul); p += Nul;
822 memcpy(p , Nr, Nrl); p += Nrl;
823 memcpy(p , Cs, Csl); p += Csl;
824 memcpy(p , Cu, Cul); p += Cul;
825 memcpy(p , Ts, Tsl); p += Tsl;
826 memcpy(p , Pu, Pul); p += Pul;
827 md5(workarea,p-workarea,md5ans);
828 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
829 /* Compute Au from our information */
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 , Kusu,Kusl);p += Kusl;
837 memcpy(p , Cs, Csl); p += Csl;
838 memcpy(p , Cu, Cul); p += Cul;
839 memcpy(p , Ts, Tsl); p += Tsl;
840 memcpy(p , Kus, Kusl);p += Kusl;
841 memcpy(p , Pu, Pul); p += Pul;
842 md5(workarea,p-workarea,md5ans);
843 /* Compare the two */
845 if (Au[i] != md5ans[i]) return(PS_RPA);
849 /*********************************************************************
851 description: Apply MD5
855 out 128 bit result buffer
857 calls: MD5 primitives
858 globals: reads outlevel
859 *********************************************************************/
861 static void md5(in,len,out)
869 if (outlevel == O_VERBOSE)
871 error(0, 0, "MD5 being applied to data block:\n");
872 for (i=0; i<len; i++)
874 error(0, 0, " %02X",in[i]);
875 if (((i % 16)==15) || (i==(len-1))) error(0, 0, "\n");
878 MD5Init( &md5context );
879 MD5Update( &md5context, in, len );
880 MD5Final( out, &md5context );
881 if (outlevel == O_VERBOSE)
883 error(0, 0, "MD5 result is: ");
884 for (i=0; i<16; i++) error(0, 0, "%02X ",out[i]);
888 #endif /* POP3_ENABLE && RPA_ENABLE */
890 /* rpa.c ends here */