From 06e870380328afef42cb857f7e421466df12aeaa Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Tue, 30 Sep 1997 20:43:30 +0000 Subject: The code as I received it from Michael Palmer. svn path=/trunk/; revision=1442 --- rpa.c | 882 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 882 insertions(+) diff --git a/rpa.c b/rpa.c index e69de29b..e417ad58 100644 --- a/rpa.c +++ b/rpa.c @@ -0,0 +1,882 @@ +/*********************************************************************** + module: rpa.c + program: fetchmail + programmer: Michael J. Palmer <106177.1156@compuserve.com> + date: 29 August 1997 + compiler: GCC 2.7.2 + environment: RedHat 4.0 Linux 2.0.18 + description: RPA authorisation code for POP3 client + ***********************************************************************/ + +#include +#include +#include + +#include "socket.h" +#include "fetchmail.h" +#include "md5.h" + + + +#ifdef TESTMODE +extern unsigned char line1[]; +extern unsigned char line2[]; +extern unsigned char line3[]; + +extern int linecount; +#endif + +#ifndef NO_PROTO + /* prototypes for internal functions */ + int POP3_rpa_resp(unsigned char* argbuf, int socket ); + void LenAppend(unsigned char** pptr, int len); + int LenSkip(unsigned char** pptr, int rxlen); + int DecBase64(unsigned char* bufp); + void EncBase64(unsigned char* bufp, int len); + void ToUnicode(unsigned char** pptr, unsigned char delim, + unsigned char* buf, int* plen, int conv); + int SetRealmService(unsigned char* bufp); + void GenChallenge(unsigned char* buf, int len); + int DigestPassphrase(unsigned char* passphrase,unsigned char* rbuf, int unicodeit); + void CompUserResp(); + int CheckUserAuth(); + void md5(unsigned char* in, int len, unsigned char* out); +#endif + +/* RPA protocol definitions */ + +#define EARLYVER "\x01\x00" /* Earliest supp version */ +#define LATEVER "\x03\x00" /* Latest supp version */ +#define HDR 0x60 /* ASN.1 SEQUENCE */ +#define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01" +#define FLAGS "\x00\x01" /* Mutual authentication */ +#define STRMAX 128 /* Bytes in Unicode */ +#define Tsl 14 /* Timestamp bytelen */ +#define Pul 16 /* Passphrase digest len */ +#define Cul 16 /* Usr challenge bytelen */ +#define Rul 16 /* Usr response bytelen */ +#define Aul 16 /* User auth bytelen */ +#define Kusl 16 /* Session key bytelen */ + +#define UNIPASS 1 /* 1=Unicode 0=iso8859 */ +#define PS_RPA 42 /* Return code */ + +/* RPA authentication items */ + +unsigned char Cs[256]; /* Service challenge */ +int Csl; /* Length of " " */ +unsigned char Ts[Tsl+1]; /* Timestamp incl \0 */ +unsigned char Nu[STRMAX]; /* Username in Unicode */ +int Nul; /* Length of " in bytes */ +unsigned char Ns[STRMAX]; /* Service in Unicode */ +int Nsl; /* Length of " in bytes */ +unsigned char Nr[STRMAX]; /* Realm in Unicode */ +int Nrl; /* Length of " in bytes */ +unsigned char Pu[Pul]; /* Passphrase after MD5 */ +unsigned char Cu[Cul]; /* User challenge */ +unsigned char Ru[Rul]; /* User response */ +unsigned char Au[Aul]; /* User auth from Deity */ +unsigned char Kusu[Kusl]; /* Obscured Session key */ +unsigned char Kus[Kusl]; /* Session key */ + +/********************************************************************* + function: POP3_auth_rpa + description: send the AUTH RPA commands to the server, and + get the server's response. Then progress through the + RPA challenge/response protocol until we are + (hopefully) granted authorisation. + arguments: + userid user's id@realm e.g. myuserid@csi.com + passphrase user's passphrase + (upper lower or mixed case as the realm has chosen. + spec allows various options :-( ) + socket socket to which the server is connected. + + return value: zero if success, else non-zero. + calls: SockPrintf, POP3_rpa_resp, EncBase64, DecBase64, + LenAppend, GenChallenge + globals: read outlevel. + *********************************************************************/ + +int POP3_auth_rpa (unsigned char *userid, unsigned char *passphrase, int socket) +{ + int ok,rxlen,verh,verl,i,rll; + unsigned char buf [POPBUFSIZE]; + unsigned char *bufp; + int status,aulin,kuslin; + char* stdec[4] = { "Success" , + "Restricted user (something wrong with account)" , + "Invalid userid or passphrase" , + "Deity error" }; + + /* Initiate RPA authorisation */ + + SockPrintf(socket,"AUTH RPA\r\n"); + + if (outlevel == O_VERBOSE) + fprintf(stderr,"> AUTH RPA\n"); + + /* Create unicode user name in Nu. */ + /* Create MD5 digest of user's passphrase in Pu */ + + bufp = userid; + ToUnicode(&bufp, '@', Nu, &Nul, 1); /* User (lowercase) */ + DigestPassphrase(passphrase, Pu, UNIPASS); + + /* Get + response from server (RPA ready) */ + + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_VERBOSE) + fprintf(stderr,"%s\n",buf); + + return(ok); + } + + /* Assemble Token 1 in buf */ + + bufp = buf; + *bufp++ = HDR; + LenAppend(&bufp, 17); + memcpy(bufp, MECH, 11); bufp += 11; + memcpy(bufp, EARLYVER, 2); bufp += 2; + memcpy(bufp, LATEVER, 2); bufp += 2; + memcpy(bufp, FLAGS, 2); bufp += 2; + + /* Send Token 1, receive Token 2 */ + + EncBase64(buf, bufp-buf); +#ifndef TESTMODE + SockPrintf(socket,"%s\r\n",buf); +#endif + if (outlevel == O_VERBOSE) + fprintf(stderr,"> %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_VERBOSE) + fprintf(stderr,"%s\n",buf); + return(ok); + } + if ((rxlen = DecBase64(buf)) == 0) + { + if (outlevel > O_SILENT) + fprintf(stderr,"RPA token 2: Base64 decode error\n"); + return(PS_RPA); + } + bufp = buf; + *(buf+rxlen) = 0; /* Terminates realm list */ + if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA); + + /* Interpret Token 2 */ + + verh = *(bufp++); verl = *(bufp++); + if (outlevel == O_VERBOSE) + fprintf(stderr,"Service chose RPA version %d.%d\n",verh,verl); + Csl = *(bufp++); + memcpy(Cs, bufp, Csl); + bufp += Csl; + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"Service challenge (l=%d):",Csl); + for (i=0; i O_SILENT) + fprintf(stderr,"RPA token 2 length error\n"); + return(PS_RPA); + } + if (outlevel == O_VERBOSE) + fprintf(stderr,"Realm list: %s\n",bufp); + if (SetRealmService(bufp) != 0) + { + if (outlevel > O_SILENT) + fprintf(stderr,"RPA error in service@realm string\n"); + return(PS_RPA); + } + + /* Assemble Token 3 in buf */ + + bufp = buf; + *(bufp++) = HDR; + LenAppend(&bufp, 11+2+strlen(userid)+1+Cul+1+Rul ); + memcpy(bufp, MECH, 11); bufp += 11; + *(bufp++) = 0; + *(bufp++) = strlen(userid); + memcpy(bufp,userid,strlen(userid)); bufp += strlen(userid); + GenChallenge(Cu,Cul); + *(bufp++) = Cul; + memcpy(bufp, Cu, Cul); bufp += Cul; + CompUserResp(); + *(bufp++) = Rul; + memcpy(bufp, Ru, Rul); bufp += Rul; + + /* Send Token 3, receive Token 4 */ + + EncBase64(buf,bufp-buf); +#ifndef TESTMODE + SockPrintf(socket,"%s\r\n",buf); +#endif + if (outlevel == O_VERBOSE) + fprintf(stderr,"> %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_VERBOSE) + fprintf(stderr,"%s\n",buf); + return(ok); + } + if ((rxlen = DecBase64(buf)) == 0) + { + if (outlevel > O_SILENT) + fprintf(stderr,"RPA token 4: Base64 decode error\n"); + return(PS_RPA); + } + bufp = buf; + if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA); + + /* Interpret Token 4 */ + + aulin = *(bufp++); + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"User authentication (l=%d):",aulin); + for (i=0; i O_SILENT) + fprintf(stderr,"RPA token 4 length error\n"); + return(PS_RPA); + } + if (status != 0) + { + if (outlevel > O_SILENT) + if (status < 4) + fprintf(stderr,"RPA rejects you: %s\n",stdec[status]); + else + fprintf(stderr,"RPA rejects you, reason unknown\n"); + return(PS_AUTHFAIL); + } + if (Aul != aulin) + { + fprintf(stderr,"RPA User Authentication length error: %d\n",aulin); + return(PS_RPA); + } + if (Kusl != kuslin) + { + fprintf(stderr,"RPA Session key length error: %d\n",kuslin); + return(PS_RPA); + } + if (CheckUserAuth() != 0) + { + if (outlevel > O_SILENT) + fprintf(stderr,"RPA _service_ auth fail. Spoof server?\n"); + return(PS_AUTHFAIL); + } + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"Session key established:"); + for (i=0; i %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_VERBOSE) + fprintf(stderr,"%s\n",buf); + return(ok); + } + } + + if (outlevel > O_SILENT) + fprintf(stderr,"RPA authorisation complete\n"); + + return(PS_SUCCESS); +} + + +/********************************************************************* + function: POP3_rpa_resp + description: get the server's response to an RPA action. + Return received base64 string if successful + arguments: + argbuf buffer to receive the string. + socket socket to which the server is connected. + + return value: zero if okay, else return code. + calls: SockGets + globals: reads outlevel. + *********************************************************************/ + +int POP3_rpa_resp (argbuf,socket) +unsigned char *argbuf; +int socket; +{ + int ok; + char buf [POPBUFSIZE]; + char *bufp; + int sockrc; + fprintf(stderr, "Get response\n"); +#ifndef TESTMODE + sockrc = SockRead(socket, buf, sizeof(buf)); +#else + linecount++; + if (linecount == 1) strcpy(buf,line1); + if (linecount == 2) strcpy(buf,line2); + if (linecount == 3) strcpy(buf,line3); +/* fprintf(stderr,"--> "); fflush(stderr); */ +/* scanf("%s",&buf) */ + sockrc = 0; +#endif + if (sockrc > 0) { + buf[sockrc] = 0; + if (outlevel == O_VERBOSE) + fprintf(stderr,"%s\n",buf); + + bufp = buf; + if ((*buf) == '+') + { + bufp++; +/* if (*bufp == ' ') bufp++; */ + if (argbuf != NULL) + strcpy(argbuf,bufp); + ok=0; + } + else if (strcmp(buf,"-ERR") == 0) + ok = PS_ERROR; + else ok = PS_PROTOCOL; + + } + else + ok = PS_SOCKET; + fprintf(stderr, "Get response return %d [%s]\n", ok, buf); + buf[sockrc] = 0; + return(ok); +} + +/********************************************************************* + function: LenAppend + description: Store token length encoded as per ASN.1 DER rules + buffer pointer stepped on appropriately. + Copes with numbers up to 32767 at least. + arguments: + buf pointer to buffer to receive result + len length value to encode + + return value: none + calls: none + globals: none + *********************************************************************/ + +void LenAppend(pptr,len) +unsigned char **pptr; +int len; +{ + if (len < 0x80) + { + **pptr = len; (*pptr)++; + } + else if (len < 0x100) + { + **pptr = 0x81; (*pptr)++; + **pptr = len; (*pptr)++; + } + else + { + **pptr = 0x82; (*pptr)++; + **pptr = len >> 8; (*pptr)++; + **pptr = len & 0xFF; (*pptr)++; + } +} + +/********************************************************************* + function: LenSkip + description: Check token header, length, and mechanism, and + skip past these. + arguments: + pptr pointer to buffer pointer + rxlen number of bytes after base64 decode + + return value: 0 if error, else token length value + calls: none + globals: reads outlevel. + *********************************************************************/ + +int LenSkip(pptr,rxlen) +unsigned char **pptr; +int rxlen; +{ + int len; + unsigned char *save; + save = *pptr; + if (**pptr != HDR) + { + if (outlevel > O_SILENT) fprintf(stderr,"Hdr not 60\n"); + return(0); + } + (*pptr)++; + if (((**pptr) & 0x80) == 0 ) + { + len = **pptr; (*pptr)++; + } + else if ((**pptr) == 0x81) + { + len = *(*pptr+1); (*pptr) += 2; + } + else if ((**pptr) == 0x82) + { + len = ((*(*pptr+1)) << 8) | *(*pptr+2); + (*pptr) += 3; + } + else len = 0; + if (len==0) + { + if (outlevel>O_SILENT) + fprintf(stderr,"Token length error\n"); + } + else if (((*pptr-save)+len) != rxlen) + { + if (outlevel>O_SILENT) + fprintf(stderr,"Token Length %d disagrees with rxlen %d\n",len,rxlen); + len = 0; + } + else if (memcmp(*pptr,MECH,11)) + { + if (outlevel > O_SILENT) + fprintf(stderr,"Mechanism field incorrect\n"); + len = 0; + } + else (*pptr) += 11; /* Skip mechanism field */ + return(len); +} + +/********************************************************************* + function: DecBase64 + description: Decode a Base64 string, overwriting the original. + Note that result cannot be longer than input. + + arguments: + bufp buffer + + return value: 0 if error, else number of bytes in decoded result + calls: none + globals: reads outlevel. + *********************************************************************/ + +int DecBase64(bufp) +unsigned char *bufp; +{ + unsigned int new, bits=0, cnt=0, i, part=0; + unsigned char ch; + unsigned char* outp=bufp; + unsigned char* inp=bufp; + while((ch=*(inp++)) != 0) + { + if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r')) + { + if ((ch>='A') && (ch <= 'Z')) new = ch - 'A'; + else if ((ch>='a') && (ch <= 'z')) new = ch - 'a' + 26; + else if ((ch>='0') && (ch <= '9')) new = ch - '0' + 52; + else if ( ch=='+' ) new = 62; + else if ( ch=='/' ) new = 63; + else { + fprintf(stderr, "dec64 error at char %d: %x\n", inp - bufp, ch); + return(0); + } + part=((part & 0x3F)*64) + new; + bits += 6; + if (bits >= 8) + { + bits -= 8; + *outp = (part >> bits); + cnt++; outp++; + } + } + } + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"Inbound binary data:\n"); + for (i=0; i0) + + return value: none + calls: none + globals: reads outlevel; + *********************************************************************/ + +void EncBase64(bufp,len) +unsigned char *bufp; +int len; +{ + unsigned char* outp; + unsigned char c1,c2,c3; + char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i; + + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"Outbound data:\n"); + for (i=0; i=0; i-=3) + { + c1 = bufp[i]; + if ((i+1) < len) c2 = bufp[i+1]; else c2=0; + if ((i+2) < len) c3 = bufp[i+2]; else c3=0; + *(outp) = x[c1/4]; + *(outp+1) = x[((c1 & 3)*16) + (c2/16)]; + if ((i+1) < len) *(outp+2) = x[((c2 & 0x0F)*4) + (c3/64)]; + else *(outp+2) = '='; + if ((i+2) < len) *(outp+3) = x[c3 & 0x3F]; + else *(outp+3) = '='; + outp -= 4; + } +} + +/********************************************************************* + function: ToUnicode + description: Convert ASCII (or iso-8859-1) byte string into + Unicode. Ensure length isn't too long (STRMAX). + + arguments: + pptr pointer to input buffer + delim delimiter character (in addition to \0) + buf buffer where Unicode will go + plen pointer to length variable (# bytes output) + conv 1 to convert to lowercase, 0 leaves alone + + return value: none + calls: none + globals: reads outlevel; + *********************************************************************/ + +void ToUnicode(pptr,delim,buf,plen,conv) +unsigned char **pptr; /* input string */ +unsigned char delim; +unsigned char *buf; /* output buffer */ +int *plen; +int conv; +{ + unsigned char *p; + int i; + *plen = 0; p=buf; + while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen) O_SILENT) + fprintf(stderr,"RPA String too long\n"); + *plen = 0; + } + if (outlevel == O_VERBOSE) + { + fprintf(stderr,"Unicode:"); + for (i=0; i<(*plen); i++) fprintf(stderr,"%02X ",buf[i]); + fprintf(stderr,"\n"); + } +} + +/********************************************************************* + function: SetRealmService + description: Select a realm from list, and store it. + + arguments: + bufp pointer to buffer + + return value: none + calls: none + globals: reads outlevel. + writes Ns Nsl Nr Nrl + *********************************************************************/ + +int SetRealmService(bufp) +unsigned char* bufp; +{ + /* For the moment we pick the first available realm. It would */ + /* make more sense to verify that the realm which the user */ + /* has given (as part of id) is in the list, and select it's */ + /* corresponding service name. */ + ToUnicode(&bufp, '@', Ns, &Nsl, 1); /* Service */ + bufp++; /* Skip the @ */ + ToUnicode(&bufp, ' ', Nr, &Nrl, 1); /* Realm name */ + if ((Nrl == 0) || (Nsl == 0)) + return(PS_RPA); + return(0); +} + +/********************************************************************* + function: GenChallenge + description: Generate a random User challenge + + arguments: + buf pointer to buffer + len length in bytes + + return value: none + calls: none + globals: reads outlevel. + reads /dev/random + *********************************************************************/ + +void GenChallenge(buf,len) +unsigned char *buf; +int len; +{ + int i; + FILE *devrandom; + devrandom = fopen("/dev/urandom","rb"); + if (devrandom == NULL) + { + if (outlevel > O_SILENT) + fprintf(stderr,"RPA Failed open of /dev/random. This shouldn't\n"); + fprintf(stderr," prevent you logging in, but means you\n"); + fprintf(stderr," cannot be sure you are talking to the\n"); + fprintf(stderr," service that you think you are (replay\n"); + fprintf(stderr," attacks by a dishonest service are possible.)\n"); + } + for (i=0; i