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 fprintf(stderr,"> 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 fprintf(stderr,"%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 fprintf(stderr,"> %s\n",buf);
160 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
162 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
163 fprintf(stderr,"%s\n",buf);
166 if ((rxlen = DecBase64(buf)) == 0)
168 if (outlevel > O_SILENT)
169 fprintf(stderr,"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 fprintf(stderr,"Service chose RPA version %d.%d\n",verh,verl);
182 memcpy(Cs, bufp, Csl);
184 if (outlevel == O_VERBOSE)
186 fprintf(stderr,"Service challenge (l=%d):",Csl);
187 for (i=0; i<Csl; i++) fprintf(stderr," %02X",Cs[i]);
188 fprintf(stderr,"\n");
190 memcpy(Ts, bufp, Tsl);
193 if (outlevel == O_VERBOSE)
194 fprintf(stderr,"Service timestamp %s\n",Ts);
195 rll = *(bufp++) << 8; rll = rll | *(bufp++);
196 if ((bufp-buf+rll) != rxlen)
198 if (outlevel > O_SILENT)
199 fprintf(stderr,"RPA token 2 length error\n");
202 if (outlevel == O_VERBOSE)
203 fprintf(stderr,"Realm list: %s\n",bufp);
204 if (SetRealmService(bufp) != 0)
206 if (outlevel > O_SILENT)
207 fprintf(stderr,"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 fprintf(stderr,"> %s\n",buf);
235 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
237 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
238 fprintf(stderr,"%s\n",buf);
241 if ((rxlen = DecBase64(buf)) == 0)
243 if (outlevel > O_SILENT)
244 fprintf(stderr,"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 fprintf(stderr,"User authentication (l=%d):",aulin);
256 for (i=0; i<aulin; i++) fprintf(stderr," %02X",bufp[i]);
257 fprintf(stderr,"\n");
259 if (aulin == Aul) memcpy(Au, bufp, Aul);
262 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
267 if (outlevel == O_VERBOSE)
268 fprintf(stderr,"RPA status: %02X\n",status);
271 if ((bufp - buf) != rxlen)
273 if (outlevel > O_SILENT)
274 fprintf(stderr,"RPA token 4 length error\n");
279 if (outlevel > O_SILENT)
281 fprintf(stderr,"RPA rejects you: %s\n",stdec[status]);
283 fprintf(stderr,"RPA rejects you, reason unknown\n");
288 fprintf(stderr,"RPA User Authentication length error: %d\n",aulin);
293 fprintf(stderr,"RPA Session key length error: %d\n",kuslin);
296 if (CheckUserAuth() != 0)
298 if (outlevel > O_SILENT)
299 fprintf(stderr,"RPA _service_ auth fail. Spoof server?\n");
302 if (outlevel == O_VERBOSE)
304 fprintf(stderr,"Session key established:");
305 for (i=0; i<Kusl; i++) fprintf(stderr," %02X",Kus[i]);
306 fprintf(stderr,"\n");
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 fprintf(stderr,"> %s\n",buf);
325 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
327 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
328 fprintf(stderr,"%s\n",buf);
333 if (outlevel > O_SILENT)
334 fprintf(stderr,"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];
361 fprintf(stderr, "Get response\n");
363 sockrc = gen_recv(socket, buf, sizeof(buf));
366 if (linecount == 1) strcpy(buf,line1);
367 if (linecount == 2) strcpy(buf,line2);
368 if (linecount == 3) strcpy(buf,line3);
369 /* fprintf(stderr,"--> "); fflush(stderr); */
370 /* scanf("%s",&buf) */
373 if (sockrc == PS_SUCCESS) {
378 /* if (*bufp == ' ') bufp++; */
383 else if (strcmp(buf,"-ERR") == 0)
385 else ok = PS_PROTOCOL;
390 fprintf(stderr, "Get response return %d [%s]\n", ok, buf);
395 /*********************************************************************
397 description: Store token length encoded as per ASN.1 DER rules
398 buffer pointer stepped on appropriately.
399 Copes with numbers up to 32767 at least.
401 buf pointer to buffer to receive result
402 len length value to encode
407 *********************************************************************/
409 static void LenAppend(pptr,len)
410 unsigned char **pptr;
415 **pptr = len; (*pptr)++;
417 else if (len < 0x100)
419 **pptr = 0x81; (*pptr)++;
420 **pptr = len; (*pptr)++;
424 **pptr = 0x82; (*pptr)++;
425 **pptr = len >> 8; (*pptr)++;
426 **pptr = len & 0xFF; (*pptr)++;
430 /*********************************************************************
432 description: Check token header, length, and mechanism, and
435 pptr pointer to buffer pointer
436 rxlen number of bytes after base64 decode
438 return value: 0 if error, else token length value
440 globals: reads outlevel.
441 *********************************************************************/
443 int LenSkip(pptr,rxlen)
444 unsigned char **pptr;
452 if (outlevel > O_SILENT) fprintf(stderr,"Hdr not 60\n");
456 if (((**pptr) & 0x80) == 0 )
458 len = **pptr; (*pptr)++;
460 else if ((**pptr) == 0x81)
462 len = *(*pptr+1); (*pptr) += 2;
464 else if ((**pptr) == 0x82)
466 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
472 if (outlevel>O_SILENT)
473 fprintf(stderr,"Token length error\n");
475 else if (((*pptr-save)+len) != rxlen)
477 if (outlevel>O_SILENT)
478 fprintf(stderr,"Token Length %d disagrees with rxlen %d\n",len,rxlen);
481 else if (memcmp(*pptr,MECH,11))
483 if (outlevel > O_SILENT)
484 fprintf(stderr,"Mechanism field incorrect\n");
487 else (*pptr) += 11; /* Skip mechanism field */
491 /*********************************************************************
493 description: Decode a Base64 string, overwriting the original.
494 Note that result cannot be longer than input.
499 return value: 0 if error, else number of bytes in decoded result
501 globals: reads outlevel.
502 *********************************************************************/
504 static int DecBase64(bufp)
507 unsigned int new, bits=0, cnt=0, i, part=0;
509 unsigned char* outp=bufp;
510 unsigned char* inp=bufp;
511 while((ch=*(inp++)) != 0)
513 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
515 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
516 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
517 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
518 else if ( ch=='+' ) new = 62;
519 else if ( ch=='/' ) new = 63;
521 fprintf(stderr, "dec64 error at char %d: %x\n", inp - bufp, ch);
524 part=((part & 0x3F)*64) + new;
529 *outp = (part >> bits);
534 if (outlevel == O_VERBOSE)
536 fprintf(stderr,"Inbound binary data:\n");
537 for (i=0; i<cnt; i++)
539 fprintf(stderr," %02X",bufp[i]);
540 if (((i % 16)==15) || (i==(cnt-1)))
541 fprintf(stderr,"\n");
547 /*********************************************************************
549 description: Encode into Base64 string, overwriting the original.
550 Note that result CAN be longer than input, the buffer
551 is assumed to be big enough. Result string is
556 len number of bytes in buffer (>0)
560 globals: reads outlevel;
561 *********************************************************************/
563 static void EncBase64(bufp,len)
568 unsigned char c1,c2,c3;
569 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
572 if (outlevel == O_VERBOSE)
574 fprintf(stderr,"Outbound data:\n");
575 for (i=0; i<len; i++)
577 fprintf(stderr," %02X",bufp[i]);
578 if (((i % 16)==15) || (i==(len-1)))
579 fprintf(stderr,"\n");
582 outp = bufp + (((len-1)/3)*4);
584 /* So we can do the update in place, start at the far end! */
585 for (i=((len-1)/3)*3; i>=0; i-=3)
588 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
589 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
591 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
592 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
593 else *(outp+2) = '=';
594 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
595 else *(outp+3) = '=';
600 /*********************************************************************
602 description: Convert ASCII (or iso-8859-1) byte string into
603 Unicode. Ensure length isn't too long (STRMAX).
606 pptr pointer to input buffer
607 delim delimiter character (in addition to \0)
608 buf buffer where Unicode will go
609 plen pointer to length variable (# bytes output)
610 conv 1 to convert to lowercase, 0 leaves alone
614 globals: reads outlevel;
615 *********************************************************************/
617 static void ToUnicode(pptr,delim,buf,plen,conv)
618 unsigned char **pptr; /* input string */
620 unsigned char *buf; /* output buffer */
627 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
631 *(p++) = tolower(**pptr);
637 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
639 if (outlevel > O_SILENT)
640 fprintf(stderr,"RPA String too long\n");
643 if (outlevel == O_VERBOSE)
645 fprintf(stderr,"Unicode:");
646 for (i=0; i<(*plen); i++) fprintf(stderr,"%02X ",buf[i]);
647 fprintf(stderr,"\n");
651 /*********************************************************************
652 function: SetRealmService
653 description: Select a realm from list, and store it.
656 bufp pointer to buffer
660 globals: reads outlevel.
662 *********************************************************************/
664 static int SetRealmService(bufp)
667 /* For the moment we pick the first available realm. It would */
668 /* make more sense to verify that the realm which the user */
669 /* has given (as part of id) is in the list, and select it's */
670 /* corresponding service name. */
671 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
672 bufp++; /* Skip the @ */
673 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
674 if ((Nrl == 0) || (Nsl == 0))
679 /*********************************************************************
680 function: GenChallenge
681 description: Generate a random User challenge
684 buf pointer to buffer
689 globals: reads outlevel.
691 *********************************************************************/
693 static void GenChallenge(buf,len)
699 devrandom = fopen("/dev/urandom","rb");
700 if (devrandom == NULL)
702 if (outlevel > O_SILENT)
703 fprintf(stderr,"RPA Failed open of /dev/random. This shouldn't\n");
704 fprintf(stderr," prevent you logging in, but means you\n");
705 fprintf(stderr," cannot be sure you are talking to the\n");
706 fprintf(stderr," service that you think you are (replay\n");
707 fprintf(stderr," attacks by a dishonest service are possible.)\n");
709 for (i=0; i<len; i++) buf[i] = fgetc(devrandom);
710 // for (i=0; i<len; i++) buf[i] = random();
712 if (outlevel == O_VERBOSE)
714 fprintf(stderr,"User challenge:");
715 for (i=0; i<len; i++) fprintf(stderr," %02X",buf[i]);
716 fprintf(stderr,"\n");
720 /*********************************************************************
721 function: DigestPassphrase
722 description: Use MD5 to compute digest (Pu) of Passphrase
723 Don't map to lower case. We assume the user is
724 aware of the case requirement of the realm.
725 (Why oh why have options in the spec?!)
727 passphrase buffer containing string, \0 terminated
728 rbuf buffer into which digest goes
730 return value: 0 if ok, else error code
732 globals: reads authentication items listed above.
734 *********************************************************************/
736 static int DigestPassphrase(passphrase,rbuf,unicodeit)
737 unsigned char *passphrase;
742 unsigned char workarea[STRMAX];
745 if (unicodeit) /* Option in spec. Yuck. */
748 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
756 len = strlen(passphrase);
762 /*********************************************************************
763 function: CompUserResp
764 description: Use MD5 to compute User Response (Ru) from
765 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
771 globals: reads authentication items listed above.
773 *********************************************************************/
775 static void CompUserResp()
777 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
780 memcpy(p , Pu, Pul); p += Pul;
781 memset(p , '\0', 48); p += 48;
782 memcpy(p , Nu, Nul); p += Nul;
783 memcpy(p , Ns, Nsl); p += Nsl;
784 memcpy(p , Nr, Nrl); p += Nrl;
785 memcpy(p , Cu, Cul); p += Cul;
786 memcpy(p , Cs, Csl); p += Csl;
787 memcpy(p , Ts, Tsl); p += Tsl;
788 memcpy(p , Pu, Pul); p += Pul;
789 md5(workarea,p-workarea,Ru);
792 /*********************************************************************
793 function: CheckUserAuth
794 description: Use MD5 to verify Authentication Response to User (Au)
795 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
796 Also creates unobscured session key Kus from obscured
801 return value: 0 if ok, PS_RPA if mismatch
803 globals: reads authentication items listed above.
805 *********************************************************************/
807 static int CheckUserAuth()
809 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
811 unsigned char md5ans[16];
813 /* Create unobscured Kusu */
815 memcpy(p , Pu, Pul); p += Pul;
816 memset(p , '\0', 48); p += 48;
817 memcpy(p , Ns, Nsl); p += Nsl;
818 memcpy(p , Nu, Nul); p += Nul;
819 memcpy(p , Nr, Nrl); p += Nrl;
820 memcpy(p , Cs, Csl); p += Csl;
821 memcpy(p , Cu, Cul); p += Cul;
822 memcpy(p , Ts, Tsl); p += Tsl;
823 memcpy(p , Pu, Pul); p += Pul;
824 md5(workarea,p-workarea,md5ans);
825 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
826 /* Compute Au from our information */
828 memcpy(p , Pu, Pul); p += Pul;
829 memset(p , '\0', 48); p += 48;
830 memcpy(p , Ns, Nsl); p += Nsl;
831 memcpy(p , Nu, Nul); p += Nul;
832 memcpy(p , Nr, Nrl); p += Nrl;
833 memcpy(p , Kusu,Kusl);p += Kusl;
834 memcpy(p , Cs, Csl); p += Csl;
835 memcpy(p , Cu, Cul); p += Cul;
836 memcpy(p , Ts, Tsl); p += Tsl;
837 memcpy(p , Kus, Kusl);p += Kusl;
838 memcpy(p , Pu, Pul); p += Pul;
839 md5(workarea,p-workarea,md5ans);
840 /* Compare the two */
842 if (Au[i] != md5ans[i]) return(PS_RPA);
846 /*********************************************************************
848 description: Apply MD5
852 out 128 bit result buffer
854 calls: MD5 primitives
855 globals: reads outlevel
856 *********************************************************************/
858 static void md5(in,len,out)
866 if (outlevel == O_VERBOSE)
868 fprintf(stderr,"MD5 being applied to data block:\n");
869 for (i=0; i<len; i++)
871 fprintf(stderr," %02X",in[i]);
872 if (((i % 16)==15) || (i==(len-1))) fprintf(stderr,"\n");
875 MD5Init( &md5context );
876 MD5Update( &md5context, in, len );
877 MD5Final( out, &md5context );
878 if (outlevel == O_VERBOSE)
880 fprintf(stderr,"MD5 result is: ");
881 for (i=0; i<16; i++) fprintf(stderr,"%02X ",out[i]);
882 fprintf(stderr,"\n");
885 #endif /* POP3_ENABLE && RPA_ENABLE */
887 /* rpa.c ends here */