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()
11 ***********************************************************************/
15 #if defined(POP3_ENABLE) && defined(RPA_ENABLE)
22 #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] = { N_("Success") ,
114 N_("Restricted user (something wrong with account)") ,
115 N_("Invalid userid or passphrase") ,
118 /* Initiate RPA authorisation */
120 SockPrintf(socket,"AUTH RPA\r\n");
122 if (outlevel >= O_MONITOR)
123 report(stdout, "> 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_MONITOR)
137 report(stdout, "%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_MONITOR)
159 report(stdout, "> %s\n",buf);
160 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
162 if (outlevel > O_SILENT && outlevel < O_MONITOR)
163 report(stdout, "%s\n",buf);
166 if ((rxlen = DecBase64(buf)) == 0)
168 if (outlevel > O_SILENT)
169 report(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_DEBUG)
180 report(stdout, _("Service chose RPA version %d.%d\n"),verh,verl);
182 memcpy(Cs, bufp, Csl);
184 if (outlevel >= O_DEBUG)
186 report(stdout, _("Service challenge (l=%d):\n"),Csl);
187 for (i=0; i<Csl; i++)
188 report_build(stdout, "%02X ",Cs[i]);
189 report_complete(stdout, "\n");
191 memcpy(Ts, bufp, Tsl);
194 if (outlevel >= O_DEBUG)
195 report(stdout, _("Service timestamp %s\n"),Ts);
196 rll = *(bufp++) << 8; rll = rll | *(bufp++);
197 if ((bufp-buf+rll) != rxlen)
199 if (outlevel > O_SILENT)
200 report(stderr, _("RPA token 2 length error\n"));
203 if (outlevel >= O_DEBUG)
204 report(stdout, _("Realm list: %s\n"),bufp);
205 if (SetRealmService(bufp) != 0)
207 if (outlevel > O_SILENT)
208 report(stderr, _("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_MONITOR)
235 report(stdout, "> %s\n",buf);
236 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
238 if (outlevel > O_SILENT && outlevel < O_MONITOR)
239 report(stdout, "%s\n",buf);
242 if ((rxlen = DecBase64(buf)) == 0)
244 if (outlevel > O_SILENT)
245 report(stderr, _("RPA token 4: Base64 decode error\n"));
249 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
251 /* Interpret Token 4 */
254 if (outlevel >= O_DEBUG)
256 report(stdout, _("User authentication (l=%d):\n"),aulin);
257 for (i=0; i<aulin; i++)
258 report_build(stdout, "%02X ",bufp[i]);
259 report_complete(stdout, "\n");
261 if (aulin == Aul) memcpy(Au, bufp, Aul);
264 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
269 if (outlevel >= O_DEBUG)
270 report(stdout, _("RPA status: %02X\n"),status);
273 if ((bufp - buf) != rxlen)
275 if (outlevel > O_SILENT)
276 report(stderr, _("RPA token 4 length error\n"));
281 if (outlevel > O_SILENT)
283 report(stderr, _("RPA rejects you: %s\n"),_(stdec[status]));
285 report(stderr, _("RPA rejects you, reason unknown\n"));
291 _("RPA User Authentication length error: %d\n"),aulin);
296 report(stderr, _("RPA Session key length error: %d\n"),kuslin);
299 if (CheckUserAuth() != 0)
301 if (outlevel > O_SILENT)
302 report(stderr, _("RPA _service_ auth fail. Spoof server?\n"));
305 if (outlevel >= O_DEBUG)
307 report(stdout, _("Session key established:\n"));
308 for (i=0; i<Kusl; i++)
309 report_build(stdout, "%02X ",Kus[i]);
310 report_complete(stdout, "\n");
313 /* Assemble Token 5 in buf and send (not in ver 2 though) */
314 /* Version 3.0 definitely replies with +OK to this. I have */
315 /* no idea what sort of response previous versions gave. */
321 LenAppend(&bufp, 1 );
323 EncBase64(buf,bufp-buf);
325 SockPrintf(socket,"%s\r\n",buf);
327 if (outlevel >= O_MONITOR)
328 report(stdout, "> %s\n",buf);
329 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
331 if (outlevel > O_SILENT && outlevel < O_MONITOR)
332 report(stdout, "%s\n",buf);
337 if (outlevel > O_SILENT)
338 report(stdout, _("RPA authorisation complete\n"));
344 /*********************************************************************
345 function: POP3_rpa_resp
346 description: get the server's response to an RPA action.
347 Return received base64 string if successful
349 argbuf buffer to receive the string.
350 socket socket to which the server is connected.
352 return value: zero if okay, else return code.
354 globals: reads outlevel.
355 *********************************************************************/
357 static int POP3_rpa_resp (argbuf,socket)
358 unsigned char *argbuf;
362 char buf [POPBUFSIZE];
366 if (outlevel >= O_DEBUG)
367 report(stdout, _("Get response\n"));
369 sockrc = gen_recv(socket, buf, sizeof(buf));
372 if (linecount == 1) strcpy(buf,line1);
373 if (linecount == 2) strcpy(buf,line2);
374 if (linecount == 3) strcpy(buf,line3);
375 /* report(stdout, "--> "); fflush(stderr); */
376 /* scanf("%s",&buf) */
379 if (sockrc == PS_SUCCESS) {
384 /* if (*bufp == ' ') bufp++; */
389 else if (strcmp(buf,"-ERR") == 0)
391 else ok = PS_PROTOCOL;
396 if (outlevel >= O_DEBUG)
397 report(stdout, _("Get response return %d [%s]\n"), ok, buf);
402 /*********************************************************************
404 description: Store token length encoded as per ASN.1 DER rules
405 buffer pointer stepped on appropriately.
406 Copes with numbers up to 32767 at least.
408 buf pointer to buffer to receive result
409 len length value to encode
414 *********************************************************************/
416 static void LenAppend(pptr,len)
417 unsigned char **pptr;
422 **pptr = len; (*pptr)++;
424 else if (len < 0x100)
426 **pptr = 0x81; (*pptr)++;
427 **pptr = len; (*pptr)++;
431 **pptr = 0x82; (*pptr)++;
432 **pptr = len >> 8; (*pptr)++;
433 **pptr = len & 0xFF; (*pptr)++;
437 /*********************************************************************
439 description: Check token header, length, and mechanism, and
442 pptr pointer to buffer pointer
443 rxlen number of bytes after base64 decode
445 return value: 0 if error, else token length value
447 globals: reads outlevel.
448 *********************************************************************/
450 int LenSkip(pptr,rxlen)
451 unsigned char **pptr;
459 if (outlevel > O_SILENT)
460 report(stderr, _("Hdr not 60\n"));
464 if (((**pptr) & 0x80) == 0 )
466 len = **pptr; (*pptr)++;
468 else if ((**pptr) == 0x81)
470 len = *(*pptr+1); (*pptr) += 2;
472 else if ((**pptr) == 0x82)
474 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
480 if (outlevel>O_SILENT)
481 report(stderr, _("Token length error\n"));
483 else if (((*pptr-save)+len) != rxlen)
485 if (outlevel>O_SILENT)
486 report(stderr, _("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
489 else if (memcmp(*pptr,MECH,11))
491 if (outlevel > O_SILENT)
492 report(stderr, _("Mechanism field incorrect\n"));
495 else (*pptr) += 11; /* Skip mechanism field */
499 /*********************************************************************
501 description: Decode a Base64 string, overwriting the original.
502 Note that result cannot be longer than input.
507 return value: 0 if error, else number of bytes in decoded result
509 globals: reads outlevel.
510 *********************************************************************/
512 static int DecBase64(bufp)
515 unsigned int new, bits=0, cnt=0, i, part=0;
517 unsigned char* outp=bufp;
518 unsigned char* inp=bufp;
519 while((ch=*(inp++)) != 0)
521 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
523 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
524 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
525 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
526 else if ( ch=='+' ) new = 62;
527 else if ( ch=='/' ) new = 63;
529 report(stderr, _("dec64 error at char %d: %x\n"), inp - bufp, ch);
532 part=((part & 0x3F)*64) + new;
537 *outp = (part >> bits);
542 if (outlevel >= O_MONITOR)
544 report(stdout, _("Inbound binary data:\n"));
545 for (i=0; i<cnt; i++)
547 report_build(stdout, "%02X ",bufp[i]);
548 if (((i % 16)==15) || (i==(cnt-1)))
549 report_complete(stdout, "\n");
555 /*********************************************************************
557 description: Encode into Base64 string, overwriting the original.
558 Note that result CAN be longer than input, the buffer
559 is assumed to be big enough. Result string is
564 len number of bytes in buffer (>0)
568 globals: reads outlevel;
569 *********************************************************************/
571 static void EncBase64(bufp,len)
576 unsigned char c1,c2,c3;
577 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
580 if (outlevel >= O_MONITOR)
582 report(stdout, _("Outbound data:\n"));
583 for (i=0; i<len; i++)
585 report_build(stdout, "%02X ",bufp[i]);
586 if (((i % 16)==15) || (i==(len-1)))
587 report_complete(stdout, "\n");
590 outp = bufp + (((len-1)/3)*4);
592 /* So we can do the update in place, start at the far end! */
593 for (i=((len-1)/3)*3; i>=0; i-=3)
596 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
597 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
599 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
600 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
601 else *(outp+2) = '=';
602 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
603 else *(outp+3) = '=';
608 /*********************************************************************
610 description: Convert ASCII (or iso-8859-1) byte string into
611 Unicode. Ensure length isn't too long (STRMAX).
614 pptr pointer to input buffer
615 delim delimiter character (in addition to \0)
616 buf buffer where Unicode will go
617 plen pointer to length variable (# bytes output)
618 conv 1 to convert to lowercase, 0 leaves alone
622 globals: reads outlevel;
623 *********************************************************************/
625 static void ToUnicode(pptr,delim,buf,plen,conv)
626 unsigned char **pptr; /* input string */
628 unsigned char *buf; /* output buffer */
635 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
639 *(p++) = tolower(**pptr);
645 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
647 if (outlevel > O_SILENT)
648 report(stderr, _("RPA String too long\n"));
651 if (outlevel >= O_DEBUG)
653 report(stdout, _("Unicode:\n"));
654 for (i=0; i<(*plen); i++)
656 report_build(stdout, "%02X ",buf[i]);
657 if (((i % 16)==15) || (i==((*plen)-1)))
658 report_complete(stdout, "\n");
663 /*********************************************************************
664 function: SetRealmService
665 description: Select a realm from list, and store it.
668 bufp pointer to buffer
672 globals: reads outlevel.
674 *********************************************************************/
676 static int SetRealmService(bufp)
679 /* For the moment we pick the first available realm. It would */
680 /* make more sense to verify that the realm which the user */
681 /* has given (as part of id) is in the list, and select it's */
682 /* corresponding service name. */
683 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
684 bufp++; /* Skip the @ */
685 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
686 if ((Nrl == 0) || (Nsl == 0))
691 /*********************************************************************
692 function: GenChallenge
693 description: Generate a random User challenge
696 buf pointer to buffer
701 globals: reads outlevel.
703 *********************************************************************/
705 static void GenChallenge(buf,len)
712 devrandom = fopen("/dev/urandom","rb");
713 if (devrandom == NULL && outlevel > O_SILENT)
715 report(stdout, _("RPA Failed open of /dev/urandom. This shouldn't\n"));
716 report(stdout, _(" prevent you logging in, but means you\n"));
717 report(stdout, _(" cannot be sure you are talking to the\n"));
718 report(stdout, _(" service that you think you are (replay\n"));
719 report(stdout, _(" 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, _("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(passphrase,rbuf,unicodeit)
757 unsigned char *passphrase;
762 unsigned char workarea[STRMAX];
765 if (unicodeit) /* Option in spec. Yuck. */
768 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
776 len = strlen(passphrase);
782 /*********************************************************************
783 function: CompUserResp
784 description: Use MD5 to compute User Response (Ru) from
785 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
791 globals: reads authentication items listed above.
793 *********************************************************************/
795 static void CompUserResp()
797 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
800 memcpy(p , Pu, Pul); p += Pul;
801 memset(p , '\0', 48); p += 48;
802 memcpy(p , Nu, Nul); p += Nul;
803 memcpy(p , Ns, Nsl); p += Nsl;
804 memcpy(p , Nr, Nrl); p += Nrl;
805 memcpy(p , Cu, Cul); p += Cul;
806 memcpy(p , Cs, Csl); p += Csl;
807 memcpy(p , Ts, Tsl); p += Tsl;
808 memcpy(p , Pu, Pul); p += Pul;
809 md5(workarea,p-workarea,Ru);
812 /*********************************************************************
813 function: CheckUserAuth
814 description: Use MD5 to verify Authentication Response to User (Au)
815 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
816 Also creates unobscured session key Kus from obscured
821 return value: 0 if ok, PS_RPA if mismatch
823 globals: reads authentication items listed above.
825 *********************************************************************/
827 static int CheckUserAuth()
829 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
831 unsigned char md5ans[16];
833 /* Create unobscured Kusu */
835 memcpy(p , Pu, Pul); p += Pul;
836 memset(p , '\0', 48); p += 48;
837 memcpy(p , Ns, Nsl); p += Nsl;
838 memcpy(p , Nu, Nul); p += Nul;
839 memcpy(p , Nr, Nrl); p += Nrl;
840 memcpy(p , Cs, Csl); p += Csl;
841 memcpy(p , Cu, Cul); p += Cul;
842 memcpy(p , Ts, Tsl); p += Tsl;
843 memcpy(p , Pu, Pul); p += Pul;
844 md5(workarea,p-workarea,md5ans);
845 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
846 /* Compute Au from our information */
848 memcpy(p , Pu, Pul); p += Pul;
849 memset(p , '\0', 48); p += 48;
850 memcpy(p , Ns, Nsl); p += Nsl;
851 memcpy(p , Nu, Nul); p += Nul;
852 memcpy(p , Nr, Nrl); p += Nrl;
853 memcpy(p , Kusu,Kusl);p += Kusl;
854 memcpy(p , Cs, Csl); p += Csl;
855 memcpy(p , Cu, Cul); p += Cul;
856 memcpy(p , Ts, Tsl); p += Tsl;
857 memcpy(p , Kus, Kusl);p += Kusl;
858 memcpy(p , Pu, Pul); p += Pul;
859 md5(workarea,p-workarea,md5ans);
860 /* Compare the two */
862 if (Au[i] != md5ans[i]) return(PS_RPA);
866 /*********************************************************************
868 description: Apply MD5
872 out 128 bit result buffer
874 calls: MD5 primitives
875 globals: reads outlevel
876 *********************************************************************/
878 static void md5(in,len,out)
886 if (outlevel >= O_DEBUG)
888 report(stdout, _("MD5 being applied to data block:\n"));
889 for (i=0; i<len; i++)
891 report_build(stdout, "%02X ",in[i]);
892 if (((i % 16)==15) || (i==(len-1)))
893 report_complete(stdout, "\n");
896 MD5Init( &md5context );
897 MD5Update( &md5context, in, len );
898 MD5Final( out, &md5context );
899 if (outlevel >= O_DEBUG)
901 report(stdout, _("MD5 result is: \n"));
904 report_build(stdout, "%02X ",out[i]);
906 report_complete(stdout, "\n");
909 #endif /* POP3_ENABLE && RPA_ENABLE */
911 /* rpa.c ends here */