#include "config.h"
#if defined(POP3_ENABLE) && defined(RPA_ENABLE)
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include "socket.h"
#include "fetchmail.h"
#include "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
static int POP3_rpa_resp(unsigned char* argbuf, int socket );
static void LenAppend(unsigned char** pptr, int len);
static int LenSkip(unsigned char** pptr, int rxlen);
static int DecBase64(unsigned char* bufp);
static void EncBase64(unsigned char* bufp, int len);
static void ToUnicode(unsigned char** pptr, unsigned char delim,
unsigned char* buf, int* plen, int conv);
static int SetRealmService(unsigned char* bufp);
static void GenChallenge(unsigned char* buf, int len);
static int DigestPassphrase(unsigned char* passphrase,
unsigned char* rbuf, int unicodeit);
static void CompUserResp();
static int CheckUserAuth();
static void md5(unsigned char* in, int len, unsigned char* out);
#endif
#define EARLYVER "\x01\x00"
#define LATEVER "\x03\x00"
#define HDR 0x60
#define MECH "\x06\x09\x60\x86\x48\x01\x86\xF8\x73\x01\x01"
#define FLAGS "\x00\x01"
#define STRMAX 128
#define Tsl 14
#define Pul 16
#define Cul 16
#define Rul 16
#define Aul 16
#define Kusl 16
#define UNIPASS 1
#define PS_RPA 42
unsigned char Cs[256];
int Csl;
unsigned char Ts[Tsl+1];
unsigned char Nu[STRMAX];
int Nul;
unsigned char Ns[STRMAX];
int Nsl;
unsigned char Nr[STRMAX];
int Nrl;
unsigned char Pu[Pul];
unsigned char Cu[Cul];
unsigned char Ru[Rul];
unsigned char Au[Aul];
unsigned char Kusu[Kusl];
unsigned char Kus[Kusl];
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] = { NGT_("Success") ,
NGT_("Restricted user (something wrong with account)") ,
NGT_("Invalid userid or passphrase") ,
NGT_("Deity error") };
SockPrintf(socket,"AUTH RPA\r\n");
if (outlevel >= O_MONITOR)
report(stdout, "> AUTH RPA\n");
bufp = userid;
ToUnicode(&bufp, '@', Nu, &Nul, 1);
DigestPassphrase(passphrase, Pu, UNIPASS);
if ((ok = POP3_rpa_resp(buf,socket)) != 0)
{
if (outlevel > O_SILENT && outlevel < O_MONITOR)
report(stdout, "%s\n",buf);
return(ok);
}
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;
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;
if (LenSkip(&bufp,rxlen) == 0) return(PS_RPA);
verh = *(bufp++); verl = *(bufp++);
if (outlevel >= O_DEBUG)
report(stdout, GT_("Service chose RPA version %d.%d\n"),verh,verl);
Csl = *(bufp++);
memcpy(Cs, bufp, Csl);
bufp += Csl;
if (outlevel >= O_DEBUG)
{
report(stdout, GT_("Service challenge (l=%d):\n"),Csl);
for (i=0; i<Csl; i++)
report_build(stdout, "%02X ",Cs[i]);
report_complete(stdout, "\n");
}
memcpy(Ts, bufp, Tsl);
Ts[Tsl] = 0;
bufp += Tsl;
if (outlevel >= O_DEBUG)
report(stdout, GT_("Service timestamp %s\n"),Ts);
rll = *(bufp++) << 8; rll = rll | *(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);
}
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;
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);
aulin = *(bufp++);
if (outlevel >= O_DEBUG)
{
report(stdout, GT_("User authentication (l=%d):\n"),aulin);
for (i=0; i<aulin; i++)
report_build(stdout, "%02X ",bufp[i]);
report_complete(stdout, "\n");
}
if (aulin == Aul) memcpy(Au, bufp, Aul);
bufp += aulin;
kuslin = *(bufp++);
if (kuslin == Kusl) memcpy(Kusu, bufp, Kusl);
bufp += kuslin;
if (verh == 3)
{
status = *(bufp++);
if (outlevel >= 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<Kusl; i++)
report_build(stdout, "%02X ",Kus[i]);
report_complete(stdout, "\n");
}
if (verh != 2)
{
bufp = buf;
*(bufp++) = HDR;
LenAppend(&bufp, 1 );
*(bufp++) = 0x42;
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 (outlevel > O_SILENT)
report(stdout, GT_("RPA authorisation complete\n"));
return(PS_SUCCESS);
}
static int POP3_rpa_resp (argbuf,socket)
unsigned 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);
sockrc = PS_SUCCESS;
#endif
if (sockrc == PS_SUCCESS) {
bufp = buf;
if ((*buf) == '+')
{
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);
}
static 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)++;
}
}
int LenSkip(pptr,rxlen)
unsigned char **pptr;
int rxlen;
{
int len;
unsigned char *save;
save = *pptr;
if (**pptr != HDR)
{
if (outlevel > O_SILENT)
report(stderr, GT_("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)
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;
return(len);
}
static 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 {
report(stderr, GT_("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_MONITOR)
{
report(stdout, GT_("Inbound binary data:\n"));
for (i=0; i<cnt; i++)
{
report_build(stdout, "%02X ",bufp[i]);
if (((i % 16)==15) || (i==(cnt-1)))
report_complete(stdout, "\n");
}
}
return(cnt);
}
static 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_MONITOR)
{
report(stdout, GT_("Outbound data:\n"));
for (i=0; i<len; i++)
{
report_build(stdout, "%02X ",bufp[i]);
if (((i % 16)==15) || (i==(len-1)))
report_complete(stdout, "\n");
}
}
outp = bufp + (((len-1)/3)*4);
*(outp+4) = 0;
for (i=((len-1)/3)*3; 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;
}
}
static void ToUnicode(pptr,delim,buf,plen,conv)
unsigned char **pptr;
unsigned char delim;
unsigned char *buf;
int *plen;
int conv;
{
unsigned char *p;
int i;
*plen = 0; p=buf;
while ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)<STRMAX) )
{
*(p++) = 0;
if (conv)
*(p++) = tolower(**pptr);
else
*(p++) = (**pptr);
(*plen) += 2;
(*pptr)++;
}
if ( ((**pptr)!=delim) && ((**pptr)!=0) && ((*plen)==STRMAX) )
{
if (outlevel > 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");
}
}
}
static int SetRealmService(bufp)
unsigned char* bufp;
{
ToUnicode(&bufp, '@', Ns, &Nsl, 1);
bufp++;
ToUnicode(&bufp, ' ', Nr, &Nrl, 1);
if ((Nrl == 0) || (Nsl == 0))
return(PS_RPA);
return(0);
}
static void GenChallenge(buf,len)
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<len; i++)
buf[i] = devrandom ? fgetc(devrandom) : random();
if (devrandom)
fclose(devrandom);
if (outlevel >= O_DEBUG)
{
report(stdout, GT_("User challenge:\n"));
for (i=0; i<len; i++)
{
report_build(stdout, "%02X ",buf[i]);
if (((i % 16)==15) || (i==(len-1)))
report_complete(stdout, "\n");
}
}
}
static int DigestPassphrase(passphrase,rbuf,unicodeit)
unsigned char *passphrase;
unsigned char *rbuf;
int unicodeit;
{
int len;
unsigned char workarea[STRMAX];
unsigned char* ptr;
if (unicodeit)
{
ptr = passphrase;
ToUnicode(&ptr, '\0', workarea, &len, 0);
if (len == 0)
return(PS_SYNTAX);
ptr = workarea;
}
else
{
ptr = rbuf;
len = strlen(passphrase);
}
md5(ptr,len,rbuf);
return(0);
}
static void CompUserResp()
{
unsigned char workarea[Pul+48+STRMAX*5+Tsl+Pul];
unsigned char* p;
p = workarea;
memcpy(p , Pu, Pul); p += Pul;
memset(p , '\0', 48); p += 48;
memcpy(p , Nu, Nul); p += Nul;
memcpy(p , Ns, Nsl); p += Nsl;
memcpy(p , Nr, Nrl); p += Nrl;
memcpy(p , Cu, Cul); p += Cul;
memcpy(p , Cs, Csl); p += Csl;
memcpy(p , Ts, Tsl); p += Tsl;
memcpy(p , Pu, Pul); p += Pul;
md5(workarea,p-workarea,Ru);
}
static int CheckUserAuth()
{
unsigned char workarea[Pul+48+STRMAX*7+Tsl+Pul];
unsigned char* p;
unsigned char md5ans[16];
int i;
p = workarea;
memcpy(p , Pu, Pul); p += Pul;
memset(p , '\0', 48); p += 48;
memcpy(p , Ns, Nsl); p += Nsl;
memcpy(p , Nu, Nul); p += Nul;
memcpy(p , Nr, Nrl); p += Nrl;
memcpy(p , Cs, Csl); p += Csl;
memcpy(p , Cu, Cul); p += Cul;
memcpy(p , Ts, Tsl); p += Tsl;
memcpy(p , Pu, Pul); p += Pul;
md5(workarea,p-workarea,md5ans);
for (i=0; i<16; i++) Kus[i] = Kusu[i] ^ md5ans[i];
p = workarea;
memcpy(p , Pu, Pul); p += Pul;
memset(p , '\0', 48); p += 48;
memcpy(p , Ns, Nsl); p += Nsl;
memcpy(p , Nu, Nul); p += Nul;
memcpy(p , Nr, Nrl); p += Nrl;
memcpy(p , Kusu,Kusl);p += Kusl;
memcpy(p , Cs, Csl); p += Csl;
memcpy(p , Cu, Cul); p += Cul;
memcpy(p , Ts, Tsl); p += Tsl;
memcpy(p , Kus, Kusl);p += Kusl;
memcpy(p , Pu, Pul); p += Pul;
md5(workarea,p-workarea,md5ans);
for (i=0; i<16; i++)
if (Au[i] != md5ans[i]) return(PS_RPA);
return(0);
}
static void md5(in,len,out)
unsigned char* in;
int len;
unsigned char* out;
{
int i;
MD5_CTX md5context;
if (outlevel >= O_DEBUG)
{
report(stdout, GT_("MD5 being applied to data block:\n"));
for (i=0; i<len; i++)
{
report_build(stdout, "%02X ",in[i]);
if (((i % 16)==15) || (i==(len-1)))
report_complete(stdout, "\n");
}
}
MD5Init( &md5context );
MD5Update( &md5context, in, len );
MD5Final( out, &md5context );
if (outlevel >= 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