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 For license terms, see the file COPYING in this directory.
14 ***********************************************************************/
18 #if defined(POP3_ENABLE) && defined(RPA_ENABLE)
25 #include "fetchmail.h"
30 extern unsigned char line1[];
31 extern unsigned char line2[];
32 extern unsigned char line3[];
38 /* prototypes for internal functions */
39 static int POP3_rpa_resp(unsigned char* argbuf, int socket );
40 static void LenAppend(unsigned char** pptr, int len);
41 static int LenSkip(unsigned char** pptr, int rxlen);
42 static int DecBase64(unsigned char* bufp);
43 static void EncBase64(unsigned char* bufp, int len);
44 static void ToUnicode(unsigned char** pptr, unsigned char delim,
45 unsigned char* buf, int* plen, int conv);
46 static int SetRealmService(unsigned char* bufp);
47 static void GenChallenge(unsigned char* buf, int len);
48 static int DigestPassphrase(unsigned char* passphrase,
49 unsigned char* rbuf, int unicodeit);
50 static void CompUserResp();
51 static int CheckUserAuth();
52 static void md5(unsigned char* in, int len, unsigned char* out);
55 /* RPA protocol definitions */
57 #define EARLYVER "\x01\x00" /* Earliest supp version */
58 #define LATEVER "\x03\x00" /* Latest supp version */
59 #define HDR 0x60 /* ASN.1 SEQUENCE */
60 #define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
61 #define FLAGS "\x00\x01" /* Mutual authentication */
62 #define STRMAX 128 /* Bytes in Unicode */
63 #define Tsl 14 /* Timestamp bytelen */
64 #define Pul 16 /* Passphrase digest len */
65 #define Cul 16 /* Usr challenge bytelen */
66 #define Rul 16 /* Usr response bytelen */
67 #define Aul 16 /* User auth bytelen */
68 #define Kusl 16 /* Session key bytelen */
70 #define UNIPASS 1 /* 1=Unicode 0=iso8859 */
71 #define PS_RPA 42 /* Return code */
73 /* RPA authentication items */
75 unsigned char Cs[256]; /* Service challenge */
76 int Csl; /* Length of " " */
77 unsigned char Ts[Tsl+1]; /* Timestamp incl \0 */
78 unsigned char Nu[STRMAX]; /* Username in Unicode */
79 int Nul; /* Length of " in bytes */
80 unsigned char Ns[STRMAX]; /* Service in Unicode */
81 int Nsl; /* Length of " in bytes */
82 unsigned char Nr[STRMAX]; /* Realm in Unicode */
83 int Nrl; /* Length of " in bytes */
84 unsigned char Pu[Pul]; /* Passphrase after MD5 */
85 unsigned char Cu[Cul]; /* User challenge */
86 unsigned char Ru[Rul]; /* User response */
87 unsigned char Au[Aul]; /* User auth from Deity */
88 unsigned char Kusu[Kusl]; /* Obscured Session key */
89 unsigned char Kus[Kusl]; /* Session key */
91 /*********************************************************************
92 function: POP3_auth_rpa
93 description: send the AUTH RPA commands to the server, and
94 get the server's response. Then progress through the
95 RPA challenge/response protocol until we are
96 (hopefully) granted authorisation.
98 userid user's id@realm e.g. myuserid@csi.com
99 passphrase user's passphrase
100 (upper lower or mixed case as the realm has chosen.
101 spec allows various options :-( )
102 socket socket to which the server is connected.
104 return value: zero if success, else non-zero.
105 calls: SockPrintf, POP3_rpa_resp, EncBase64, DecBase64,
106 LenAppend, GenChallenge
107 globals: read outlevel.
108 *********************************************************************/
110 int POP3_auth_rpa (unsigned char *userid, unsigned char *passphrase, int socket)
112 int ok,rxlen,verh,verl,i,rll;
113 unsigned char buf [POPBUFSIZE];
115 int status,aulin,kuslin;
116 char* stdec[4] = { NGT_("Success") ,
117 NGT_("Restricted user (something wrong with account)") ,
118 NGT_("Invalid userid or passphrase") ,
119 NGT_("Deity error") };
121 /* Initiate RPA authorisation */
123 SockPrintf(socket,"AUTH RPA\r\n");
125 if (outlevel >= O_MONITOR)
126 report(stdout, "> AUTH RPA\n");
128 /* Create unicode user name in Nu. */
129 /* Create MD5 digest of user's passphrase in Pu */
132 ToUnicode(&bufp, '@', Nu, &Nul, 1); /* User (lowercase) */
133 DigestPassphrase(passphrase, Pu, UNIPASS);
135 /* Get + response from server (RPA ready) */
137 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
139 if (outlevel > O_SILENT && outlevel < O_MONITOR)
140 report(stdout, "%s\n",buf);
145 /* Assemble Token 1 in buf */
149 LenAppend(&bufp, 17);
150 memcpy(bufp, MECH, 11); bufp += 11;
151 memcpy(bufp, EARLYVER, 2); bufp += 2;
152 memcpy(bufp, LATEVER, 2); bufp += 2;
153 memcpy(bufp, FLAGS, 2); bufp += 2;
155 /* Send Token 1, receive Token 2 */
157 EncBase64(buf, bufp-buf);
159 SockPrintf(socket,"%s\r\n",buf);
161 if (outlevel >= O_MONITOR)
162 report(stdout, "> %s\n",buf);
163 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
165 if (outlevel > O_SILENT && outlevel < O_MONITOR)
166 report(stdout, "%s\n",buf);
169 if ((rxlen = DecBase64(buf)) == 0)
171 if (outlevel > O_SILENT)
172 report(stderr, GT_("RPA token 2: Base64 decode error\n"));
176 *(buf+rxlen) = 0; /* Terminates realm list */
177 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
179 /* Interpret Token 2 */
181 verh = *(bufp++); verl = *(bufp++);
182 if (outlevel >= O_DEBUG)
183 report(stdout, GT_("Service chose RPA version %d.%d\n"),verh,verl);
185 memcpy(Cs, bufp, Csl);
187 if (outlevel >= O_DEBUG)
189 report(stdout, GT_("Service challenge (l=%d):\n"),Csl);
190 for (i=0; i<Csl; i++)
191 report_build(stdout, "%02X ",Cs[i]);
192 report_complete(stdout, "\n");
194 memcpy(Ts, bufp, Tsl);
197 if (outlevel >= O_DEBUG)
198 report(stdout, GT_("Service timestamp %s\n"),Ts);
199 rll = *(bufp++) << 8; rll = rll | *(bufp++);
200 if ((bufp-buf+rll) != rxlen)
202 if (outlevel > O_SILENT)
203 report(stderr, GT_("RPA token 2 length error\n"));
206 if (outlevel >= O_DEBUG)
207 report(stdout, GT_("Realm list: %s\n"),bufp);
208 if (SetRealmService(bufp) != 0)
210 if (outlevel > O_SILENT)
211 report(stderr, GT_("RPA error in service@realm string\n"));
215 /* Assemble Token 3 in buf */
219 LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul );
220 memcpy(bufp, MECH, 11); bufp += 11;
222 *(bufp++) = strlen(userid);
223 memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid);
224 GenChallenge(Cu,Cul);
226 memcpy(bufp, Cu, Cul); bufp += Cul;
229 memcpy(bufp, Ru, Rul); bufp += Rul;
231 /* Send Token 3, receive Token 4 */
233 EncBase64(buf,bufp-buf);
235 SockPrintf(socket,"%s\r\n",buf);
237 if (outlevel >= O_MONITOR)
238 report(stdout, "> %s\n",buf);
239 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
241 if (outlevel > O_SILENT && outlevel < O_MONITOR)
242 report(stdout, "%s\n",buf);
245 if ((rxlen = DecBase64(buf)) == 0)
247 if (outlevel > O_SILENT)
248 report(stderr, GT_("RPA token 4: Base64 decode error\n"));
252 if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
254 /* Interpret Token 4 */
257 if (outlevel >= O_DEBUG)
259 report(stdout, GT_("User authentication (l=%d):\n"),aulin);
260 for (i=0; i<aulin; i++)
261 report_build(stdout, "%02X ",bufp[i]);
262 report_complete(stdout, "\n");
264 if (aulin == Aul) memcpy(Au, bufp, Aul);
267 if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl); /* blinded */
272 if (outlevel >= O_DEBUG)
273 report(stdout, GT_("RPA status: %02X\n"),status);
276 if ((bufp - buf) != rxlen)
278 if (outlevel > O_SILENT)
279 report(stderr, GT_("RPA token 4 length error\n"));
284 if (outlevel > O_SILENT)
286 report(stderr, GT_("RPA rejects you: %s\n"),GT_(stdec[status]));
288 report(stderr, GT_("RPA rejects you, reason unknown\n"));
294 GT_("RPA User Authentication length error: %d\n"),aulin);
299 report(stderr, GT_("RPA Session key length error: %d\n"),kuslin);
302 if (CheckUserAuth() != 0)
304 if (outlevel > O_SILENT)
305 report(stderr, GT_("RPA _service_ auth fail. Spoof server?\n"));
308 if (outlevel >= O_DEBUG)
310 report(stdout, GT_("Session key established:\n"));
311 for (i=0; i<Kusl; i++)
312 report_build(stdout, "%02X ",Kus[i]);
313 report_complete(stdout, "\n");
316 /* Assemble Token 5 in buf and send (not in ver 2 though) */
317 /* Version 3.0 definitely replies with +OK to this. I have */
318 /* no idea what sort of response previous versions gave. */
324 LenAppend(&bufp, 1 );
326 EncBase64(buf,bufp-buf);
328 SockPrintf(socket,"%s\r\n",buf);
330 if (outlevel >= O_MONITOR)
331 report(stdout, "> %s\n",buf);
332 if ((ok = POP3_rpa_resp(buf,socket)) != 0)
334 if (outlevel > O_SILENT && outlevel < O_MONITOR)
335 report(stdout, "%s\n",buf);
340 if (outlevel > O_SILENT)
341 report(stdout, GT_("RPA authorisation complete\n"));
347 /*********************************************************************
348 function: POP3_rpa_resp
349 description: get the server's response to an RPA action.
350 Return received base64 string if successful
352 argbuf buffer to receive the string.
353 socket socket to which the server is connected.
355 return value: zero if okay, else return code.
357 globals: reads outlevel.
358 *********************************************************************/
360 static int POP3_rpa_resp (argbuf,socket)
361 unsigned char *argbuf;
365 char buf [POPBUFSIZE];
369 if (outlevel >= O_DEBUG)
370 report(stdout, GT_("Get response\n"));
372 sockrc = gen_recv(socket, buf, sizeof(buf));
375 if (linecount == 1) strcpy(buf,line1);
376 if (linecount == 2) strcpy(buf,line2);
377 if (linecount == 3) strcpy(buf,line3);
378 /* report(stdout, "--> "); fflush(stderr); */
379 /* scanf("%s",&buf) */
382 if (sockrc == PS_SUCCESS) {
387 /* if (*bufp == ' ') bufp++; */
392 else if (strcmp(buf,"-ERR") == 0)
394 else ok = PS_PROTOCOL;
399 if (outlevel >= O_DEBUG)
400 report(stdout, GT_("Get response return %d [%s]\n"), ok, buf);
405 /*********************************************************************
407 description: Store token length encoded as per ASN.1 DER rules
408 buffer pointer stepped on appropriately.
409 Copes with numbers up to 32767 at least.
411 buf pointer to buffer to receive result
412 len length value to encode
417 *********************************************************************/
419 static void LenAppend(pptr,len)
420 unsigned char **pptr;
425 **pptr = len; (*pptr)++;
427 else if (len < 0x100)
429 **pptr = 0x81; (*pptr)++;
430 **pptr = len; (*pptr)++;
434 **pptr = 0x82; (*pptr)++;
435 **pptr = len >> 8; (*pptr)++;
436 **pptr = len & 0xFF; (*pptr)++;
440 /*********************************************************************
442 description: Check token header, length, and mechanism, and
445 pptr pointer to buffer pointer
446 rxlen number of bytes after base64 decode
448 return value: 0 if error, else token length value
450 globals: reads outlevel.
451 *********************************************************************/
453 int LenSkip(pptr,rxlen)
454 unsigned char **pptr;
462 if (outlevel > O_SILENT)
463 report(stderr, GT_("Hdr not 60\n"));
467 if (((**pptr) & 0x80) == 0 )
469 len = **pptr; (*pptr)++;
471 else if ((**pptr) == 0x81)
473 len = *(*pptr+1); (*pptr) += 2;
475 else if ((**pptr) == 0x82)
477 len = ((*(*pptr+1)) << 8) | *(*pptr+2);
483 if (outlevel>O_SILENT)
484 report(stderr, GT_("Token length error\n"));
486 else if (((*pptr-save)+len) != rxlen)
488 if (outlevel>O_SILENT)
489 report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen);
492 else if (memcmp(*pptr,MECH,11))
494 if (outlevel > O_SILENT)
495 report(stderr, GT_("Mechanism field incorrect\n"));
498 else (*pptr) += 11; /* Skip mechanism field */
502 /*********************************************************************
504 description: Decode a Base64 string, overwriting the original.
505 Note that result cannot be longer than input.
510 return value: 0 if error, else number of bytes in decoded result
512 globals: reads outlevel.
513 *********************************************************************/
515 static int DecBase64(bufp)
518 unsigned int new, bits=0, cnt=0, i, part=0;
520 unsigned char* outp=bufp;
521 unsigned char* inp=bufp;
522 while((ch=*(inp++)) != 0)
524 if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r'))
526 if ((ch>='A') && (ch <= 'Z')) new = ch - 'A';
527 else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26;
528 else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52;
529 else if ( ch=='+' ) new = 62;
530 else if ( ch=='/' ) new = 63;
532 report(stderr, GT_("dec64 error at char %d: %x\n"), inp - bufp, ch);
535 part=((part & 0x3F)*64) + new;
540 *outp = (part >> bits);
545 if (outlevel >= O_MONITOR)
547 report(stdout, GT_("Inbound binary data:\n"));
548 for (i=0; i<cnt; i++)
550 report_build(stdout, "%02X ",bufp[i]);
551 if (((i % 16)==15) || (i==(cnt-1)))
552 report_complete(stdout, "\n");
558 /*********************************************************************
560 description: Encode into Base64 string, overwriting the original.
561 Note that result CAN be longer than input, the buffer
562 is assumed to be big enough. Result string is
567 len number of bytes in buffer (>0)
571 globals: reads outlevel;
572 *********************************************************************/
574 static void EncBase64(bufp,len)
579 unsigned char c1,c2,c3;
580 char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
583 if (outlevel >= O_MONITOR)
585 report(stdout, GT_("Outbound data:\n"));
586 for (i=0; i<len; i++)
588 report_build(stdout, "%02X ",bufp[i]);
589 if (((i % 16)==15) || (i==(len-1)))
590 report_complete(stdout, "\n");
593 outp = bufp + (((len-1)/3)*4);
595 /* So we can do the update in place, start at the far end! */
596 for (i=((len-1)/3)*3; i>=0; i-=3)
599 if ((i+1) < len) c2 = bufp[i+1]; else c2=0;
600 if ((i+2) < len) c3 = bufp[i+2]; else c3=0;
602 *(outp+1) = x[((c1 & 3)*16) + (c2/16)];
603 if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)];
604 else *(outp+2) = '=';
605 if ((i+2) < len) *(outp+3) = x[c3 & 0x3F];
606 else *(outp+3) = '=';
611 /*********************************************************************
613 description: Convert ASCII (or iso-8859-1) byte string into
614 Unicode. Ensure length isn't too long (STRMAX).
617 pptr pointer to input buffer
618 delim delimiter character (in addition to \0)
619 buf buffer where Unicode will go
620 plen pointer to length variable (# bytes output)
621 conv 1 to convert to lowercase, 0 leaves alone
625 globals: reads outlevel;
626 *********************************************************************/
628 static void ToUnicode(pptr,delim,buf,plen,conv)
629 unsigned char **pptr; /* input string */
631 unsigned char *buf; /* output buffer */
638 while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
642 *(p++) = tolower(**pptr);
648 if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
650 if (outlevel > O_SILENT)
651 report(stderr, GT_("RPA String too long\n"));
654 if (outlevel >= O_DEBUG)
656 report(stdout, GT_("Unicode:\n"));
657 for (i=0; i<(*plen); i++)
659 report_build(stdout, "%02X ",buf[i]);
660 if (((i % 16)==15) || (i==((*plen)-1)))
661 report_complete(stdout, "\n");
666 /*********************************************************************
667 function: SetRealmService
668 description: Select a realm from list, and store it.
671 bufp pointer to buffer
675 globals: reads outlevel.
677 *********************************************************************/
679 static int SetRealmService(bufp)
682 /* For the moment we pick the first available realm. It would */
683 /* make more sense to verify that the realm which the user */
684 /* has given (as part of id) is in the list, and select it's */
685 /* corresponding service name. */
686 ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */
687 bufp++; /* Skip the @ */
688 ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */
689 if ((Nrl == 0) || (Nsl == 0))
694 /*********************************************************************
695 function: GenChallenge
696 description: Generate a random User challenge
699 buf pointer to buffer
704 globals: reads outlevel.
706 *********************************************************************/
708 static void GenChallenge(buf,len)
715 devrandom = fopen("/dev/urandom","rb");
716 if (devrandom == NULL && outlevel > O_SILENT)
718 report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n"));
719 report(stdout, GT_(" prevent you logging in, but means you\n"));
720 report(stdout, GT_(" cannot be sure you are talking to the\n"));
721 report(stdout, GT_(" service that you think you are (replay\n"));
722 report(stdout, GT_(" attacks by a dishonest service are possible.)\n"));
726 buf[i] = devrandom ? fgetc(devrandom) : random();
729 fclose(devrandom); /* should be safe, file mode was "r" */
731 if (outlevel >= O_DEBUG)
733 report(stdout, GT_("User challenge:\n"));
734 for (i=0; i<len; i++)
736 report_build(stdout, "%02X ",buf[i]);
737 if (((i % 16)==15) || (i==(len-1)))
738 report_complete(stdout, "\n");
743 /*********************************************************************
744 function: DigestPassphrase
745 description: Use MD5 to compute digest (Pu) of Passphrase
746 Don't map to lower case. We assume the user is
747 aware of the case requirement of the realm.
748 (Why oh why have options in the spec?!)
750 passphrase buffer containing string, \0 terminated
751 rbuf buffer into which digest goes
753 return value: 0 if ok, else error code
755 globals: reads authentication items listed above.
757 *********************************************************************/
759 static int DigestPassphrase(passphrase,rbuf,unicodeit)
760 unsigned char *passphrase;
765 unsigned char workarea[STRMAX];
768 if (unicodeit) /* Option in spec. Yuck. */
771 ToUnicode(&ptr, '\0', workarea, &len, 0); /* No case conv here */
779 len = strlen(passphrase);
785 /*********************************************************************
786 function: CompUserResp
787 description: Use MD5 to compute User Response (Ru) from
788 Pu Z(48) Nu Ns Nr Cu Cs Ts Pu
794 globals: reads authentication items listed above.
796 *********************************************************************/
798 static void CompUserResp()
800 unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
803 memcpy(p , Pu, Pul); p += Pul;
804 memset(p , '\0', 48); p += 48;
805 memcpy(p , Nu, Nul); p += Nul;
806 memcpy(p , Ns, Nsl); p += Nsl;
807 memcpy(p , Nr, Nrl); p += Nrl;
808 memcpy(p , Cu, Cul); p += Cul;
809 memcpy(p , Cs, Csl); p += Csl;
810 memcpy(p , Ts, Tsl); p += Tsl;
811 memcpy(p , Pu, Pul); p += Pul;
812 md5(workarea,p-workarea,Ru);
815 /*********************************************************************
816 function: CheckUserAuth
817 description: Use MD5 to verify Authentication Response to User (Au)
818 using Pu Z(48) Ns Nu Nr Kusu Cs Cu Ts Kus Pu
819 Also creates unobscured session key Kus from obscured
824 return value: 0 if ok, PS_RPA if mismatch
826 globals: reads authentication items listed above.
828 *********************************************************************/
830 static int CheckUserAuth()
832 unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
834 unsigned char md5ans[16];
836 /* Create unobscured Kusu */
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 , Cs, Csl); p += Csl;
844 memcpy(p , Cu, Cul); p += Cul;
845 memcpy(p , Ts, Tsl); p += Tsl;
846 memcpy(p , Pu, Pul); p += Pul;
847 md5(workarea,p-workarea,md5ans);
848 for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
849 /* Compute Au from our information */
851 memcpy(p , Pu, Pul); p += Pul;
852 memset(p , '\0', 48); p += 48;
853 memcpy(p , Ns, Nsl); p += Nsl;
854 memcpy(p , Nu, Nul); p += Nul;
855 memcpy(p , Nr, Nrl); p += Nrl;
856 memcpy(p , Kusu,Kusl);p += Kusl;
857 memcpy(p , Cs, Csl); p += Csl;
858 memcpy(p , Cu, Cul); p += Cul;
859 memcpy(p , Ts, Tsl); p += Tsl;
860 memcpy(p , Kus, Kusl);p += Kusl;
861 memcpy(p , Pu, Pul); p += Pul;
862 md5(workarea,p-workarea,md5ans);
863 /* Compare the two */
865 if (Au[i] != md5ans[i]) return(PS_RPA);
869 /*********************************************************************
871 description: Apply MD5
875 out 128 bit result buffer
877 calls: MD5 primitives
878 globals: reads outlevel
879 *********************************************************************/
881 static void md5(in,len,out)
889 if (outlevel >= O_DEBUG)
891 report(stdout, GT_("MD5 being applied to data block:\n"));
892 for (i=0; i<len; i++)
894 report_build(stdout, "%02X ",in[i]);
895 if (((i % 16)==15) || (i==(len-1)))
896 report_complete(stdout, "\n");
899 MD5Init( &md5context );
900 MD5Update( &md5context, in, len );
901 MD5Final( out, &md5context );
902 if (outlevel >= O_DEBUG)
904 report(stdout, GT_("MD5 result is: \n"));
907 report_build(stdout, "%02X ",out[i]);
909 report_complete(stdout, "\n");
912 #endif /* POP3_ENABLE && RPA_ENABLE */
914 /* rpa.c ends here */