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
9 ***********************************************************************/
19 #include "fetchmail.h"
23 extern unsigned char line1[];
24 extern unsigned char line2[];
25 extern unsigned char line3[];
31 /* prototypes for internal functions */
32 int POP3_rpa_resp(unsigned char* argbuf, int socket );
33 void LenAppend(unsigned char** pptr, int len);
34 int LenSkip(unsigned char** pptr, int rxlen);
35 int DecBase64(unsigned char* bufp);
36 void EncBase64(unsigned char* bufp, int len);
37 void ToUnicode(unsigned char** pptr, unsigned char delim,
38 unsigned char* buf, int* plen, int conv);
39 int SetRealmService(unsigned char* bufp);
40 void GenChallenge(unsigned char* buf, int len);
41 int DigestPassphrase(unsigned char* passphrase,unsigned char* rbuf, int unicodeit);
44 void md5(unsigned char* in, int len, unsigned char* out);
47 /* RPA protocol definitions */
49 #define EARLYVER "\x01\x00" /* Earliest supp version */
50 #define LATEVER "\x03\x00" /* Latest supp version */
51 #define HDR 0x60 /* ASN.1 SEQUENCE */
52 #define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
53 #define FLAGS "\x00\x01" /* Mutual authentication */
54 #define STRMAX 128 /* Bytes in Unicode */
55 #define Tsl 14 /* Timestamp bytelen */
56 #define Pul 16 /* Passphrase digest len */
57 #define Cul 16 /* Usr challenge bytelen */
58 #define Rul 16 /* Usr response bytelen */
59 #define Aul 16 /* User auth bytelen */
60 #define Kusl 16 /* Session key bytelen */
62 #define UNIPASS 1 /* 1=Unicode 0=iso8859 */
63 #define PS_RPA 42 /* Return code */
65 /* RPA authentication items */
67 unsigned char Cs[256]; /* Service challenge */
68 int Csl; /* Length of " " */
69 unsigned char Ts[Tsl+1]; /* Timestamp incl \0 */
70 unsigned char Nu[STRMAX]; /* Username in Unicode */
71 int Nul; /* Length of " in bytes */
72 unsigned char Ns[STRMAX]; /* Service in Unicode */
73 int Nsl; /* Length of " in bytes */
74 unsigned char Nr[STRMAX]; /* Realm in Unicode */
75 int Nrl; /* Length of " in bytes */
76 unsigned char Pu[Pul]; /* Passphrase after MD5 */
77 unsigned char Cu[Cul]; /* User challenge */
78 unsigned char Ru[Rul]; /* User response */
79 unsigned char Au[Aul]; /* User auth from Deity */
80 unsigned char Kusu[Kusl]; /* Obscured Session key */
81 unsigned char Kus[Kusl]; /* Session key */
83 /*********************************************************************
84 function: POP3_auth_rpa
85 description: send the AUTH RPA commands to the server, and
86 get the server's response. Then progress through the
87 RPA challenge/response protocol until we are
88 (hopefully) granted authorisation.
90 userid user's id@realm e.g. myuserid@csi.com
91 passphrase user's passphrase
92 (upper lower or mixed case as the realm has chosen.
93 spec allows various options :-( )
94 socket socket to which the server is connected.
96 return value: zero if success, else non-zero.
97 calls: SockPrintf, POP3_rpa_resp, EncBase64, DecBase64,
98 LenAppend, GenChallenge
99 globals: read outlevel.
100 *********************************************************************/
102 int POP3_auth_rpa (unsigned char *userid, unsigned char *passphrase, int socket)
104 int ok,rxlen,verh,verl,i,rll;
105 unsigned char buf [POPBUFSIZE];
107 int status,aulin,kuslin;
108 char* stdec[4] = { "Success" ,
109 "Restricted user (something wrong with account)" ,
110 "Invalid userid or passphrase" ,
113 /* Initiate RPA authorisation */
115 SockPrintf(socket,"AUTH RPA\r\n");
117 if (outlevel == O_VERBOSE)
118 fprintf(stderr,"> AUTH RPA\n");
120 /* Create unicode user name in Nu. */
121 /* Create MD5 digest of user's passphrase in Pu */
124 ToUnicode(&bufp, '@', Nu, &Nul, 1); /* User (lowercase) */
125 DigestPassphrase(passphrase, Pu, UNIPASS);
127 /* Get + response from server (RPA ready) */
129 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
131 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
132 fprintf(stderr,"%s\n",buf);
137 /* Assemble Token 1 in buf */
141 LenAppend(&bufp, 17);
142 memcpy(bufp, MECH, 11); bufp += 11;
143 memcpy(bufp, EARLYVER, 2); bufp += 2;
144 memcpy(bufp, LATEVER, 2); bufp += 2;
145 memcpy(bufp, FLAGS, 2); bufp += 2;
147 /* Send Token 1, receive Token 2 */
149 EncBase64(buf, bufp-buf);
151 SockPrintf(socket,"%s\r\n",buf);
153 if (outlevel == O_VERBOSE)
154 fprintf(stderr,"> %s\n",buf);
155 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
157 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
158 fprintf(stderr,"%s\n",buf);
161 if ((rxlen = DecBase64(buf)) == 0)
163 if (outlevel > O_SILENT)
164 fprintf(stderr,"RPA token 2: Base64 decode error\n");
168 *(buf+rxlen) = 0; /* Terminates realm list */
169 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
171 /* Interpret Token 2 */
173 verh = *(bufp++); verl = *(bufp++);
174 if (outlevel == O_VERBOSE)
175 fprintf(stderr,"Service chose RPA version %d.%d\n",verh,verl);
177 memcpy(Cs, bufp, Csl);
179 if (outlevel == O_VERBOSE)
181 fprintf(stderr,"Service challenge (l=%d):",Csl);
182 for (i=0; i<Csl; i++) fprintf(stderr," %02X",Cs[i]);
183 fprintf(stderr,"\n");
185 memcpy(Ts, bufp, Tsl);
188 if (outlevel == O_VERBOSE)
189 fprintf(stderr,"Service timestamp %s\n",Ts);
190 rll = *(bufp++) << 8; rll = rll | *(bufp++);
191 if ((bufp-buf+rll) != rxlen)
193 if (outlevel > O_SILENT)
194 fprintf(stderr,"RPA token 2 length error\n");
197 if (outlevel == O_VERBOSE)
198 fprintf(stderr,"Realm list: %s\n",bufp);
199 if (SetRealmService(bufp) != 0)
201 if (outlevel > O_SILENT)
202 fprintf(stderr,"RPA error in service@realm string\n");
206 /* Assemble Token 3 in buf */
210 LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
211 memcpy(bufp, MECH, 11); bufp += 11;
213 *(bufp++) = strlen(userid);
214 memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
215 GenChallenge(Cu,Cul);
217 memcpy(bufp, Cu, Cul); bufp += Cul;
220 memcpy(bufp, Ru, Rul); bufp += Rul;
222 /* Send Token 3, receive Token 4 */
224 EncBase64(buf,bufp-buf);
226 SockPrintf(socket,"%s\r\n",buf);
228 if (outlevel == O_VERBOSE)
229 fprintf(stderr,"> %s\n",buf);
230 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
232 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
233 fprintf(stderr,"%s\n",buf);
236 if ((rxlen = DecBase64(buf)) == 0)
238 if (outlevel > O_SILENT)
239 fprintf(stderr,"RPA token 4: Base64 decode error\n");
243 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
245 /* Interpret Token 4 */
248 if (outlevel == O_VERBOSE)
250 fprintf(stderr,"User authentication (l=%d):",aulin);
251 for (i=0; i<aulin; i++) fprintf(stderr," %02X",bufp[i]);
252 fprintf(stderr,"\n");
254 if (aulin == Aul) memcpy(Au, bufp, Aul);
257 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
262 if (outlevel == O_VERBOSE)
263 fprintf(stderr,"RPA status: %02X\n",status);
266 if ((bufp - buf) != rxlen)
268 if (outlevel > O_SILENT)
269 fprintf(stderr,"RPA token 4 length error\n");
274 if (outlevel > O_SILENT)
276 fprintf(stderr,"RPA rejects you: %s\n",stdec[status]);
278 fprintf(stderr,"RPA rejects you, reason unknown\n");
283 fprintf(stderr,"RPA User Authentication length error: %d\n",aulin);
288 fprintf(stderr,"RPA Session key length error: %d\n",kuslin);
291 if (CheckUserAuth() != 0)
293 if (outlevel > O_SILENT)
294 fprintf(stderr,"RPA _service_ auth fail. Spoof server?\n");
297 if (outlevel == O_VERBOSE)
299 fprintf(stderr,"Session key established:");
300 for (i=0; i<Kusl; i++) fprintf(stderr," %02X",Kus[i]);
301 fprintf(stderr,"\n");
304 /* Assemble Token 5 in buf and send (not in ver 2 though) */
305 /* Version 3.0 definitely replies with +OK to this. I have */
306 /* no idea what sort of response previous versions gave. */
312 LenAppend(&bufp, 1 );
314 EncBase64(buf,bufp-buf);
316 SockPrintf(socket,"%s\r\n",buf);
318 if (outlevel == O_VERBOSE)
319 fprintf(stderr,"> %s\n",buf);
320 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
322 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
323 fprintf(stderr,"%s\n",buf);
328 if (outlevel > O_SILENT)
329 fprintf(stderr,"RPA authorisation complete\n");
335 /*********************************************************************
336 function: POP3_rpa_resp
337 description: get the server's response to an RPA action.
338 Return received base64 string if successful
340 argbuf buffer to receive the string.
341 socket socket to which the server is connected.
343 return value: zero if okay, else return code.
345 globals: reads outlevel.
346 *********************************************************************/
348 int POP3_rpa_resp (argbuf,socket)
349 unsigned char *argbuf;
353 char buf [POPBUFSIZE];
356 fprintf(stderr, "Get response\n");
358 sockrc = SockRead(socket, buf, sizeof(buf));
361 if (linecount == 1) strcpy(buf,line1);
362 if (linecount == 2) strcpy(buf,line2);
363 if (linecount == 3) strcpy(buf,line3);
364 /* fprintf(stderr,"--> "); fflush(stderr); */
365 /* scanf("%s",&buf) */
370 if (outlevel == O_VERBOSE)
371 fprintf(stderr,"%s\n",buf);
377 /* if (*bufp == ' ') bufp++; */
382 else if (strcmp(buf,"-ERR") == 0)
384 else ok = PS_PROTOCOL;
389 fprintf(stderr, "Get response return %d [%s]\n", ok, buf);
394 /*********************************************************************
396 description: Store token length encoded as per ASN.1 DER rules
397 buffer pointer stepped on appropriately.
398 Copes with numbers up to 32767 at least.
400 buf pointer to buffer to receive result
401 len length value to encode
406 *********************************************************************/
408 void LenAppend(pptr,len)
409 unsigned char **pptr;
414 **pptr = len; (*pptr)++;
416 else if (len < 0x100)
418 **pptr = 0x81; (*pptr)++;
419 **pptr = len; (*pptr)++;
423 **pptr = 0x82; (*pptr)++;
424 **pptr = len >> 8; (*pptr)++;
425 **pptr = len & 0xFF; (*pptr)++;
429 /*********************************************************************
431 description: Check token header, length, and mechanism, and
434 pptr pointer to buffer pointer
435 rxlen number of bytes after base64 decode
437 return value: 0 if error, else token length value
439 globals: reads outlevel.
440 *********************************************************************/
442 int LenSkip(pptr,rxlen)
443 unsigned char **pptr;
451 if (outlevel > O_SILENT) fprintf(stderr,"Hdr not 60\n");
455 if (((**pptr) & 0x80) == 0 )
457 len = **pptr; (*pptr)++;
459 else if ((**pptr) == 0x81)
461 len = *(*pptr+1); (*pptr) += 2;
463 else if ((**pptr) == 0x82)
465 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
471 if (outlevel>O_SILENT)
472 fprintf(stderr,"Token length error\n");
474 else if (((*pptr-save)+len) != rxlen)
476 if (outlevel>O_SILENT)
477 fprintf(stderr,"Token Length %d disagrees with rxlen %d\n",len,rxlen);
480 else if (memcmp(*pptr,MECH,11))
482 if (outlevel > O_SILENT)
483 fprintf(stderr,"Mechanism field incorrect\n");
486 else (*pptr) += 11; /* Skip mechanism field */
490 /*********************************************************************
492 description: Decode a Base64 string, overwriting the original.
493 Note that result cannot be longer than input.
498 return value: 0 if error, else number of bytes in decoded result
500 globals: reads outlevel.
501 *********************************************************************/
506 unsigned int new, bits=0, cnt=0, i, part=0;
508 unsigned char* outp=bufp;
509 unsigned char* inp=bufp;
510 while((ch=*(inp++)) != 0)
512 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
514 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
515 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
516 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
517 else if ( ch=='+' ) new = 62;
518 else if ( ch=='/' ) new = 63;
520 fprintf(stderr, "dec64 error at char %d: %x\n", inp - bufp, ch);
523 part=((part & 0x3F)*64) + new;
528 *outp = (part >> bits);
533 if (outlevel == O_VERBOSE)
535 fprintf(stderr,"Inbound binary data:\n");
536 for (i=0; i<cnt; i++)
538 fprintf(stderr," %02X",bufp[i]);
539 if (((i % 16)==15) || (i==(cnt-1)))
540 fprintf(stderr,"\n");
546 /*********************************************************************
548 description: Encode into Base64 string, overwriting the original.
549 Note that result CAN be longer than input, the buffer
550 is assumed to be big enough. Result string is
555 len number of bytes in buffer (>0)
559 globals: reads outlevel;
560 *********************************************************************/
562 void EncBase64(bufp,len)
567 unsigned char c1,c2,c3;
568 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
571 if (outlevel == O_VERBOSE)
573 fprintf(stderr,"Outbound data:\n");
574 for (i=0; i<len; i++)
576 fprintf(stderr," %02X",bufp[i]);
577 if (((i % 16)==15) || (i==(len-1)))
578 fprintf(stderr,"\n");
581 outp = bufp + (((len-1)/3)*4);
583 /* So we can do the update in place, start at the far end! */
584 for (i=((len-1)/3)*3; i>=0; i-=3)
587 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
588 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
590 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
591 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
592 else *(outp+2) = '=';
593 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
594 else *(outp+3) = '=';
599 /*********************************************************************
601 description: Convert ASCII (or iso-8859-1) byte string into
602 Unicode. Ensure length isn't too long (STRMAX).
605 pptr pointer to input buffer
606 delim delimiter character (in addition to \0)
607 buf buffer where Unicode will go
608 plen pointer to length variable (# bytes output)
609 conv 1 to convert to lowercase, 0 leaves alone
613 globals: reads outlevel;
614 *********************************************************************/
616 void ToUnicode(pptr,delim,buf,plen,conv)
617 unsigned char **pptr; /* input string */
619 unsigned char *buf; /* output buffer */
626 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
630 *(p++) = tolower(**pptr);
636 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
638 if (outlevel > O_SILENT)
639 fprintf(stderr,"RPA String too long\n");
642 if (outlevel == O_VERBOSE)
644 fprintf(stderr,"Unicode:");
645 for (i=0; i<(*plen); i++) fprintf(stderr,"%02X ",buf[i]);
646 fprintf(stderr,"\n");
650 /*********************************************************************
651 function: SetRealmService
652 description: Select a realm from list, and store it.
655 bufp pointer to buffer
659 globals: reads outlevel.
661 *********************************************************************/
663 int SetRealmService(bufp)
666 /* For the moment we pick the first available realm. It would */
667 /* make more sense to verify that the realm which the user */
668 /* has given (as part of id) is in the list, and select it's */
669 /* corresponding service name. */
670 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
671 bufp++; /* Skip the @ */
672 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
673 if ((Nrl == 0) || (Nsl == 0))
678 /*********************************************************************
679 function: GenChallenge
680 description: Generate a random User challenge
683 buf pointer to buffer
688 globals: reads outlevel.
690 *********************************************************************/
692 void GenChallenge(buf,len)
698 devrandom = fopen("/dev/urandom","rb");
699 if (devrandom == NULL)
701 if (outlevel > O_SILENT)
702 fprintf(stderr,"RPA Failed open of /dev/random. This shouldn't\n");
703 fprintf(stderr," prevent you logging in, but means you\n");
704 fprintf(stderr," cannot be sure you are talking to the\n");
705 fprintf(stderr," service that you think you are (replay\n");
706 fprintf(stderr," attacks by a dishonest service are possible.)\n");
708 for (i=0; i<len; i++) buf[i] = fgetc(devrandom);
709 // for (i=0; i<len; i++) buf[i] = random();
711 if (outlevel == O_VERBOSE)
713 fprintf(stderr,"User challenge:");
714 for (i=0; i<len; i++) fprintf(stderr," %02X",buf[i]);
715 fprintf(stderr,"\n");
719 /*********************************************************************
720 function: DigestPassphrase
721 description: Use MD5 to compute digest (Pu) of Passphrase
722 Don't map to lower case. We assume the user is
723 aware of the case requirement of the realm.
724 (Why oh why have options in the spec?!)
726 passphrase buffer containing string, \0 terminated
727 rbuf buffer into which digest goes
729 return value: 0 if ok, else error code
731 globals: reads authentication items listed above.
733 *********************************************************************/
735 int DigestPassphrase(passphrase,rbuf,unicodeit)
736 unsigned char *passphrase;
741 unsigned char workarea[STRMAX];
744 if (unicodeit) /* Option in spec. Yuck. */
747 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
755 len = strlen(passphrase);
761 /*********************************************************************
762 function: CompUserResp
763 description: Use MD5 to compute User Response (Ru) from
764 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
770 globals: reads authentication items listed above.
772 *********************************************************************/
776 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
779 memcpy(p , Pu, Pul); p += Pul;
780 memset(p , '\0', 48); p += 48;
781 memcpy(p , Nu, Nul); p += Nul;
782 memcpy(p , Ns, Nsl); p += Nsl;
783 memcpy(p , Nr, Nrl); p += Nrl;
784 memcpy(p , Cu, Cul); p += Cul;
785 memcpy(p , Cs, Csl); p += Csl;
786 memcpy(p , Ts, Tsl); p += Tsl;
787 memcpy(p , Pu, Pul); p += Pul;
788 md5(workarea,p-workarea,Ru);
791 /*********************************************************************
792 function: CheckUserAuth
793 description: Use MD5 to verify Authentication Response to User (Au)
794 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
795 Also creates unobscured session key Kus from obscured
800 return value: 0 if ok, PS_RPA if mismatch
802 globals: reads authentication items listed above.
804 *********************************************************************/
808 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
810 unsigned char md5ans[16];
812 /* Create unobscured Kusu */
814 memcpy(p , Pu, Pul); p += Pul;
815 memset(p , '\0', 48); p += 48;
816 memcpy(p , Ns, Nsl); p += Nsl;
817 memcpy(p , Nu, Nul); p += Nul;
818 memcpy(p , Nr, Nrl); p += Nrl;
819 memcpy(p , Cs, Csl); p += Csl;
820 memcpy(p , Cu, Cul); p += Cul;
821 memcpy(p , Ts, Tsl); p += Tsl;
822 memcpy(p , Pu, Pul); p += Pul;
823 md5(workarea,p-workarea,md5ans);
824 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
825 /* Compute Au from our information */
827 memcpy(p , Pu, Pul); p += Pul;
828 memset(p , '\0', 48); p += 48;
829 memcpy(p , Ns, Nsl); p += Nsl;
830 memcpy(p , Nu, Nul); p += Nul;
831 memcpy(p , Nr, Nrl); p += Nrl;
832 memcpy(p , Kusu,Kusl);p += Kusl;
833 memcpy(p , Cs, Csl); p += Csl;
834 memcpy(p , Cu, Cul); p += Cul;
835 memcpy(p , Ts, Tsl); p += Tsl;
836 memcpy(p , Kus, Kusl);p += Kusl;
837 memcpy(p , Pu, Pul); p += Pul;
838 md5(workarea,p-workarea,md5ans);
839 /* Compare the two */
841 if (Au[i] != md5ans[i]) return(PS_RPA);
845 /*********************************************************************
847 description: Apply MD5
851 out 128 bit result buffer
853 calls: MD5 primitives
854 globals: reads outlevel
855 *********************************************************************/
865 if (outlevel == O_VERBOSE)
867 fprintf(stderr,"MD5 being applied to data block:\n");
868 for (i=0; i<len; i++)
870 fprintf(stderr," %02X",in[i]);
871 if (((i % 16)==15) || (i==(len-1))) fprintf(stderr,"\n");
874 MD5Init( &md5context );
875 MD5Update( &md5context, in, len );
876 MD5Final( out, &md5context );
877 if (outlevel == O_VERBOSE)
879 fprintf(stderr,"MD5 result is: ");
880 for (i=0; i<16; i++) fprintf(stderr,"%02X ",out[i]);
881 fprintf(stderr,"\n");
884 #endif /* RPA_ENABLE */
886 /* rpa.c ends here */