X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=rpa.c;h=c365f0ded8b2338ed9a1b2792557c4d0da07b209;hb=2629c4511c68729d98acfd08637c1f00d3807f49;hp=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391;hpb=13dc548cb89de96c5906f7034cab5b59ae72dbea;p=~andy%2Ffetchmail diff --git a/rpa.c b/rpa.c index e69de29b..c365f0de 100644 --- a/rpa.c +++ b/rpa.c @@ -0,0 +1,897 @@ +/*********************************************************************** + 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 + + The sole entry point is POP3_auth_rpa() + + For license terms, see the file COPYING in this directory. + + ***********************************************************************/ + +#include "config.h" + +#if defined(POP3_ENABLE) && defined(RPA_ENABLE) +#include +#include +#include +#include +#include +#include + +#include "socket.h" +#include "fetchmail.h" +#include "fm_md5.h" +#include "i18n.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 */ + static int POP3_rpa_resp(char* argbuf, int socket ); + static void LenAppend(char** pptr, int len); + static int LenSkip(char** pptr, int rxlen); + static int DecBase64(char* bufp); + static void EncBase64(char* bufp, int len); + static void ToUnicode(char** pptr, char delim, unsigned char* buf, int* plen, + int conv); + static int SetRealmService(char* bufp); + static void GenChallenge(unsigned char* buf, int len); + static int DigestPassphrase(char* passphrase, + unsigned char* rbuf, int unicodeit); + static void CompUserResp(void); + static int CheckUserAuth(void); + static void md5(const void* 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 (char *userid, char *passphrase, int socket) +{ + int ok,rxlen,verh,verl,i,rll; + char buf [POPBUFSIZE]; + char *bufp; + int status,aulin,kuslin; + const char* stdec[4] = { N_("Success") , + N_("Restricted user (something wrong with account)") , + N_("Invalid userid or passphrase") , + N_("Deity error") }; + + /* Initiate RPA authorisation */ + + SockPrintf(socket,"AUTH RPA\r\n"); + + if (outlevel >= O_MONITOR) + report(stdout, "> 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_MONITOR) + report(stdout, "%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_MONITOR) + report(stdout, "> %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_MONITOR) + report(stdout, "%s\n",buf); + return(ok); + } + if ((rxlen = DecBase64(buf)) == 0) + { + if (outlevel > O_SILENT) + report(stderr, GT_("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 = (unsigned char)*(bufp++); verl = (unsigned char)*(bufp++); + if (outlevel >= O_DEBUG) + report(stdout, GT_("Service chose RPA version %d.%d\n"),verh,verl); + Csl = (unsigned char)*(bufp++); + memcpy(Cs, bufp, Csl); + bufp += Csl; + if (outlevel >= O_DEBUG) + { + report(stdout, GT_("Service challenge (l=%d):\n"),Csl); + for (i=0; i= O_DEBUG) + report(stdout, GT_("Service timestamp %s\n"),Ts); + rll = (unsigned char)*(bufp++) << 8; rll = rll | (unsigned char)*(bufp++); + if ((bufp-buf+rll) != rxlen) + { + if (outlevel > O_SILENT) + report(stderr, GT_("RPA token 2 length error\n")); + return(PS_RPA); + } + if (outlevel >= O_DEBUG) + report(stdout, GT_("Realm list: %s\n"),bufp); + if (SetRealmService(bufp) != 0) + { + if (outlevel > O_SILENT) + report(stderr, GT_("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_MONITOR) + report(stdout, "> %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_MONITOR) + report(stdout, "%s\n",buf); + return(ok); + } + if ((rxlen = DecBase64(buf)) == 0) + { + if (outlevel > O_SILENT) + report(stderr, GT_("RPA token 4: Base64 decode error\n")); + return(PS_RPA); + } + bufp = buf; + if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA); + + /* Interpret Token 4 */ + + aulin = (unsigned char)*(bufp++); + if (outlevel >= O_DEBUG) + { + report(stdout, GT_("User authentication (l=%d):\n"),aulin); + for (i=0; i= O_DEBUG) + report(stdout, GT_("RPA status: %02X\n"),status); + } + else status = 0; + if ((bufp - buf) != rxlen) + { + if (outlevel > O_SILENT) + report(stderr, GT_("RPA token 4 length error\n")); + return(PS_RPA); + } + if (status != 0) + { + if (outlevel > O_SILENT) { + if (status < 4) { + report(stderr, GT_("RPA rejects you: %s\n"),GT_(stdec[status])); + } else { + report(stderr, GT_("RPA rejects you, reason unknown\n")); + } + } + return(PS_AUTHFAIL); + } + if (Aul != aulin) + { + report(stderr, + GT_("RPA User Authentication length error: %d\n"),aulin); + return(PS_RPA); + } + if (Kusl != kuslin) + { + report(stderr, GT_("RPA Session key length error: %d\n"),kuslin); + return(PS_RPA); + } + if (CheckUserAuth() != 0) + { + if (outlevel > O_SILENT) + report(stderr, GT_("RPA _service_ auth fail. Spoof server?\n")); + return(PS_AUTHFAIL); + } + if (outlevel >= O_DEBUG) + { + report(stdout, GT_("Session key established:\n")); + for (i=0; i= O_MONITOR) + report(stdout, "> %s\n",buf); + if ((ok = POP3_rpa_resp(buf,socket)) != 0) + { + if (outlevel > O_SILENT && outlevel < O_MONITOR) + report(stdout, "%s\n",buf); + return(ok); + } + } + + if (outlevel > O_SILENT) + report(stdout, GT_("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. + *********************************************************************/ + +static int POP3_rpa_resp (char *argbuf, int socket) +{ + int ok; + char buf [POPBUFSIZE]; + char *bufp; + int sockrc; + + if (outlevel >= O_DEBUG) + report(stdout, GT_("Get response\n")); +#ifndef TESTMODE + sockrc = gen_recv(socket, buf, sizeof(buf)); +#else + linecount++; + if (linecount == 1) strcpy(buf,line1); + if (linecount == 2) strcpy(buf,line2); + if (linecount == 3) strcpy(buf,line3); +/* report(stdout, "--> "); fflush(stderr); */ +/* scanf("%s",&buf) */ + sockrc = PS_SUCCESS; +#endif + if (sockrc == PS_SUCCESS) { + 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; + if (outlevel >= O_DEBUG) + report(stdout, GT_("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 + *********************************************************************/ + +static void LenAppend(char **pptr_, int len) +{ + unsigned char **pptr = (unsigned char **)pptr_; + + 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(char **pptr, int rxlen) +{ + int len; + char *save; + save = *pptr; + if ((unsigned char)**pptr != HDR) + { + if (outlevel > O_SILENT) + report(stderr, GT_("Hdr not 60\n")); + return(0); + } + (*pptr)++; + if (((unsigned char)(**pptr) & 0x80) == 0 ) + { + len = (unsigned char)**pptr; (*pptr)++; + } + else if ((unsigned char)(**pptr) == 0x81) + { + len = (unsigned char)*(*pptr+1); (*pptr) += 2; + } + else if ((unsigned char)(**pptr) == 0x82) + { + len = ((unsigned char)(*(*pptr+1)) << 8) | (unsigned char)*(*pptr+2); + (*pptr) += 3; + } + else len = 0; + if (len==0) + { + if (outlevel>O_SILENT) + report(stderr, GT_("Token length error\n")); + } + else if (((*pptr-save)+len) != rxlen) + { + if (outlevel>O_SILENT) + report(stderr, GT_("Token Length %d disagrees with rxlen %d\n"),len,rxlen); + len = 0; + } + else if (memcmp(*pptr,MECH,11)) + { + if (outlevel > O_SILENT) + report(stderr, GT_("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. + *********************************************************************/ + +static int DecBase64(char *bufp) +{ + unsigned int newx, bits=0, cnt=0, i, part=0; + unsigned char ch; + char* outp=bufp; + char* inp=bufp; + while((ch=(unsigned char)*(inp++)) != 0) + { + if ((ch != '=') && (ch != ' ') && (ch != '\n') && (ch != '\r')) + { + if ((ch>='A') && (ch <= 'Z')) newx = ch - 'A'; + else if ((ch>='a') && (ch <= 'z')) newx = ch - 'a' + 26; + else if ((ch>='0') && (ch <= '9')) newx = ch - '0' + 52; + else if ( ch=='+' ) newx = 62; + else if ( ch=='/' ) newx = 63; + else { + report(stderr, GT_("dec64 error at char %d: %x\n"), (int)(inp - bufp), ch); + return(0); + } + part=((part & 0x3F)*64) + newx; + bits += 6; + if (bits >= 8) + { + bits -= 8; + *outp = (part >> bits); + cnt++; outp++; + } + } + } + if (outlevel >= O_MONITOR) + { + report(stdout, GT_("Inbound binary data:\n")); + for (i=0; i0) + + return value: none + calls: none + globals: reads outlevel; + *********************************************************************/ + +static void EncBase64(char *bufp, int len) +{ + char* outp; + unsigned char c1,c2,c3; + char x[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i; + + if (outlevel >= O_MONITOR) + { + report(stdout, GT_("Outbound data:\n")); + for (i=0; i=0; i-=3) + { + c1 = (unsigned char)bufp[i]; + if ((i+1) < len) c2 = (unsigned char)bufp[i+1]; else c2=0; + if ((i+2) < len) c3 = (unsigned char)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; + *********************************************************************/ + +static void ToUnicode(char **pptr /* input string*/, + 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) + report(stderr, GT_("RPA String too long\n")); + *plen = 0; + } + if (outlevel >= O_DEBUG) + { + report(stdout, GT_("Unicode:\n")); + for (i=0; i<(*plen); i++) + { + report_build(stdout, "%02X ",buf[i]); + if (((i % 16)==15) || (i==((*plen)-1))) + report_complete(stdout, "\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 + *********************************************************************/ + +static int SetRealmService(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 + *********************************************************************/ + +static void GenChallenge(unsigned char *buf, int len) +{ + int i; + FILE *devrandom; + + devrandom = fopen("/dev/urandom","rb"); + if (devrandom == NULL && outlevel > O_SILENT) + { + report(stdout, GT_("RPA Failed open of /dev/urandom. This shouldn't\n")); + report(stdout, GT_(" prevent you logging in, but means you\n")); + report(stdout, GT_(" cannot be sure you are talking to the\n")); + report(stdout, GT_(" service that you think you are (replay\n")); + report(stdout, GT_(" attacks by a dishonest service are possible.)\n")); + } + + for(i=0; i= O_DEBUG) + { + report(stdout, GT_("User challenge:\n")); + for (i=0; i= O_DEBUG) + { + report(stdout, GT_("MD5 being applied to data block:\n")); + for (i=0; i= O_DEBUG) + { + report(stdout, GT_("MD5 result is:\n")); + for (i=0; i<16; i++) + { + report_build(stdout, "%02X ",out[i]); + } + report_complete(stdout, "\n"); + } +} +#endif /* POP3_ENABLE && RPA_ENABLE */ + +/* rpa.c ends here */