#include <config.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sasl/sasl.h>
#ifdef HAVE_SSL
#include <openssl/lhash.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#endif
#include "exitcodes.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "imparse.h"
#include "imclient.h"
#include "nonblock.h"
#include "util.h"
#include "iptostring.h"
#define IMCLIENT_BUFSIZE 4096
struct imclient_cmdcallback {
struct imclient_cmdcallback *next;
unsigned long tag;
imclient_proc_t *proc;
void *rock;
};
struct imclient_callback {
int flags;
char *keyword;
imclient_proc_t *proc;
void *rock;
};
struct stringlist
{
char *str;
struct stringlist *next;
};
struct imclient {
int fd;
char *servername;
int flags;
char outbuf[IMCLIENT_BUFSIZE];
char *outptr;
size_t outleft;
char *outstart;
char *replybuf;
char *replystart;
size_t replyliteralleft;
size_t replylen;
size_t alloc_replybuf;
void *state;
int maxplain;
unsigned long gensym;
unsigned long readytag;
char *readytxt;
struct imclient_cmdcallback *cmdcallback;
int callback_num;
int callback_alloc;
struct imclient_callback *callback;
struct stringlist *interact_results;
sasl_conn_t *saslconn;
int saslcompleted;
#ifdef HAVE_SSL
SSL_CTX *tls_ctx;
SSL *tls_conn;
int tls_on;
#endif
};
static const char charclass[256] = {
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 0, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static struct imclient_cmdcallback *cmdcallback_freelist;
void imclient_write(struct imclient *imclient,
const char *s, size_t len);
static int imclient_writeastring P((struct imclient *imclient,
const char *str));
static void imclient_writebase64 P((struct imclient *imclient,
const char *output, size_t len));
static void imclient_eof P((struct imclient *imclient));
static int imclient_decodebase64 P((char *input));
static const sasl_callback_t callbacks[] = {
{ SASL_CB_USER, NULL, NULL },
{ SASL_CB_GETREALM, NULL, NULL },
{ SASL_CB_AUTHNAME, NULL, NULL },
{ SASL_CB_PASS, NULL, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
int imclient_connect(struct imclient **imclient,
const char *host,
const char *port,
sasl_callback_t *cbs)
{
int s = -1;
struct addrinfo hints, *res0 = NULL, *res;
int saslresult;
static int didinit;
assert(imclient);
assert(host);
if (!port)
port = "143";
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
if (getaddrinfo(host, port, &hints, &res0))
return -1;
for (res = res0; res; res = res->ai_next) {
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (s < 0)
continue;
if (connect(s, res->ai_addr, res->ai_addrlen) >= 0)
break;
close(s);
s = -1;
}
if (s < 0)
return errno;
*imclient = (struct imclient *)xzmalloc(sizeof(struct imclient));
(*imclient)->fd = s;
(*imclient)->saslconn = NULL;
(*imclient)->saslcompleted = 0;
(*imclient)->servername = xstrdup(res0->ai_canonname ?
res0->ai_canonname : host);
freeaddrinfo(res0);
(*imclient)->outptr = (*imclient)->outstart = (*imclient)->outbuf;
(*imclient)->outleft = (*imclient)->maxplain = sizeof((*imclient)->outbuf);
(*imclient)->interact_results = NULL;
imclient_addcallback(*imclient,
"", 0, (imclient_proc_t *) 0, (void *)0,
"OK", CALLBACK_NOLITERAL, (imclient_proc_t *)0, (void *)0,
"NO", CALLBACK_NOLITERAL, (imclient_proc_t *)0, (void *)0,
"BAD", CALLBACK_NOLITERAL, (imclient_proc_t *)0, (void *)0,
"BYE", CALLBACK_NOLITERAL, (imclient_proc_t *)0, (void *)0,
(char *)0);
#ifdef HAVE_SSL
(*imclient)->tls_ctx=NULL;
(*imclient)->tls_conn=NULL;
(*imclient)->tls_on=0;
#endif
if (!didinit) {
saslresult = sasl_client_init(NULL);
if (saslresult!=SASL_OK) return 1;
didinit = 1;
}
saslresult=sasl_client_new("imap",
(*imclient)->servername,
NULL, NULL,
cbs ? cbs : callbacks,
0,
&((*imclient)->saslconn));
if (saslresult!=SASL_OK) return 1;
return 0;
}
void
imclient_close(struct imclient *imclient)
{
int i;
struct stringlist *cur, *cur_next;
assert(imclient);
imclient_eof(imclient);
close(imclient->fd);
free(imclient->servername);
if (imclient->replybuf) free(imclient->replybuf);
sasl_dispose(&(imclient->saslconn));
for (i = 0; i < imclient->callback_num; i++) {
free(imclient->callback[i].keyword);
}
if (imclient->callback) free((char *)imclient->callback);
for(cur=imclient->interact_results; cur; cur=cur_next) {
cur_next = cur->next;
free(cur->str);
free(cur);
}
free((char *)imclient);
}
void imclient_setflags(struct imclient *imclient, int flags)
{
assert(imclient);
imclient->flags |= flags;
}
void imclient_clearflags(struct imclient *imclient, int flags)
{
assert(imclient);
imclient->flags &= ~flags;
}
char *
imclient_servername(struct imclient *imclient)
{
assert(imclient);
return imclient->servername;
}
#define CALLBACKGROW 5
#ifdef __STDC__
void imclient_addcallback(struct imclient *imclient, ...)
#else
void imclient_addcallback(va_alist)
va_dcl
#endif
{
va_list pvar;
char *keyword;
int flags;
imclient_proc_t *proc;
void *rock;
int i;
#ifdef __STDC__
va_start(pvar, imclient);
#else
struct imclient *imclient;
va_start(pvar);
imclient = va_arg(pvar, struct imclient *);
#endif
assert(imclient);
while ((keyword = va_arg(pvar, char *))) {
flags = va_arg(pvar, int);
proc = va_arg(pvar, imclient_proc_t *);
rock = va_arg(pvar, void *);
for (i = 0; i < imclient->callback_num; i++) {
if (imclient->callback[i].flags == flags &&
!strcmp(imclient->callback[i].keyword, keyword)) break;
}
if (i == imclient->callback_num) {
if (imclient->callback_num == imclient->callback_alloc) {
imclient->callback_alloc += CALLBACKGROW;
imclient->callback = (struct imclient_callback *)
xrealloc((char *)imclient->callback,
imclient->callback_alloc*sizeof (struct imclient_callback));
}
imclient->callback_num++;
imclient->callback[i].keyword = xstrdup(keyword);
imclient->callback[i].flags = flags;
}
imclient->callback[i].proc = proc;
imclient->callback[i].rock = rock;
}
va_end(pvar);
}
#ifdef __STDC__
void
imclient_send(struct imclient *imclient, void (*finishproc)(),
void *finishrock, const char *fmt, ...)
#else
void
imclient_send(va_alist)
va_dcl
#endif
{
va_list pvar;
struct imclient_cmdcallback *newcmdcallback;
char buf[30];
char *percent, *str, **v;
int num;
unsigned unum;
int abortcommand = 0;
#ifdef __STDC__
va_start(pvar, fmt);
#else
struct imclient *imclient;
imclient_proc_t *finishproc;
void *finishrock;
char *fmt;
va_start(pvar);
imclient = va_arg(pvar, struct imclient *);
finishproc = va_arg(pvar, imclient_proc_t *);
finishrock = va_arg(pvar, void *);
fmt = va_arg(pvar, char *);
#endif
assert(imclient);
imclient->gensym++;
if (imclient->gensym <= 0) imclient->gensym = 1;
if (finishproc) {
if (cmdcallback_freelist) {
newcmdcallback = cmdcallback_freelist;
cmdcallback_freelist = newcmdcallback->next;
}
else {
newcmdcallback = (struct imclient_cmdcallback *)
xmalloc(sizeof (struct imclient_cmdcallback));
}
newcmdcallback->next = imclient->cmdcallback;
newcmdcallback->tag = imclient->gensym;
newcmdcallback->proc = finishproc;
newcmdcallback->rock = finishrock;
imclient->cmdcallback = newcmdcallback;
}
snprintf(buf, sizeof(buf), "%lu ", imclient->gensym);
imclient_write(imclient, buf, strlen(buf));
while ((percent = strchr(fmt, '%'))) {
imclient_write(imclient, fmt, percent-fmt);
switch (*++percent) {
case '%':
imclient_write(imclient, percent, 1);
break;
case 'a':
str = va_arg(pvar, char *);
imclient_write(imclient, str, strlen(str));
break;
case 's':
str = va_arg(pvar, char *);
abortcommand = imclient_writeastring(imclient, str);
if (abortcommand) goto fail;
break;
case 'd':
num = va_arg(pvar, int);
snprintf(buf, sizeof(buf), "%d", num);
imclient_write(imclient, buf, strlen(buf));
break;
case 'u':
unum = va_arg(pvar, unsigned);
snprintf(buf, sizeof(buf), "%lu", (unsigned long)unum);
imclient_write(imclient, buf, strlen(buf));
break;
case 'v':
v = va_arg(pvar, char **);
for (num = 0; v[num]; num++) {
if (num) imclient_write(imclient, " ", 1);
abortcommand = imclient_writeastring(imclient, v[num]);
if (abortcommand) goto fail;
}
break;
case 'B':
num = va_arg(pvar, int);
str = va_arg(pvar, char *);
imclient_writebase64(imclient, str, num);
abortcommand = 1;
goto fail;
default:
fatal("internal error: invalid format specifier in imclient_send",
EC_SOFTWARE);
}
fmt = percent + 1;
}
fail:
va_end(pvar);
if (!abortcommand) {
imclient_write(imclient, fmt, strlen(fmt));
imclient_write(imclient, "\r\n", 2);
}
}
static int imclient_writeastring(struct imclient *imclient, const char *str)
{
const char *p;
unsigned len = 0;
int class = 2;
char buf[30];
assert(imclient);
assert(str);
for (p = str; *p; p++) {
len++;
if (class > charclass[(unsigned char)*p]) {
class = charclass[(unsigned char)*p];
}
}
if (len >= 1024) class = 0;
if (len && class == 2) {
imclient_write(imclient, str, len);
}
else if (class) {
imclient_write(imclient, "\"", 1);
imclient_write(imclient, str, len);
imclient_write(imclient, "\"", 1);
}
else {
if (imclient->flags & IMCLIENT_CONN_NONSYNCLITERAL) {
snprintf(buf, sizeof(buf), "{%u+}\r\n", len);
imclient_write(imclient, buf, strlen(buf));
}
else {
imclient->readytag = imclient->gensym;
snprintf(buf, sizeof(buf), "{%u}\r\n", len);
imclient_write(imclient, buf, strlen(buf));
while (imclient->readytag) {
imclient_processoneevent(imclient);
}
if (!imclient->readytxt) return 1;
}
imclient_write(imclient, str, len);
}
return 0;
}
void imclient_write(struct imclient *imclient, const char *s, size_t len)
{
assert(imclient);
assert(s);
if (imclient->outptr == imclient->outstart) {
imclient->outstart = imclient->outptr = imclient->outbuf;
imclient->outleft = imclient->maxplain;
}
while (len > imclient->outleft) {
memcpy(imclient->outptr, s, imclient->outleft);
imclient->outptr += imclient->outleft;
s += imclient->outleft;
len -= imclient->outleft;
imclient->outleft = 0;
while (imclient->outptr != imclient->outstart) {
imclient_processoneevent(imclient);
}
imclient->outstart = imclient->outptr = imclient->outbuf;
imclient->outleft = imclient->maxplain;
}
memcpy(imclient->outptr, s, len);
imclient->outptr += len;
imclient->outleft -= len;
}
#define REPLYSLACK 80
#define REPLYSHRINK (4096+500)
static void imclient_input(struct imclient *imclient, char *buf, int len)
{
unsigned long replytag;
struct imclient_reply reply;
char *endreply;
char *p;
size_t parsed;
size_t literallen;
size_t keywordlen;
int keywordindex;
struct imclient_cmdcallback **cmdcb, *cmdcbtemp;
const char *plainbuf;
unsigned plainlen;
int result;
assert(imclient);
assert(buf);
if (imclient->saslcompleted == 1) {
if ((result = sasl_decode(imclient->saslconn, buf, len,
&plainbuf, &plainlen)) != SASL_OK) {
(void) shutdown(imclient->fd, 0);
}
if (plainlen == 0) return;
} else {
plainbuf = buf;
plainlen = len;
}
if (imclient->replylen + plainlen >= imclient->alloc_replybuf) {
if (imclient->replystart != imclient->replybuf) {
imclient->replylen -= imclient->replystart - imclient->replybuf;
memmove(imclient->replybuf, imclient->replystart,
imclient->replylen);
imclient->replystart = imclient->replybuf;
}
if (imclient->replylen + plainlen + REPLYSHRINK <
imclient->alloc_replybuf) {
imclient->alloc_replybuf = imclient->replylen + plainlen
+ REPLYSHRINK;
imclient->replybuf = xrealloc(imclient->replybuf,
imclient->alloc_replybuf);
imclient->replystart = imclient->replybuf;
}
if (imclient->replylen + plainlen >= imclient->alloc_replybuf) {
imclient->alloc_replybuf =
imclient->replylen + plainlen + REPLYSLACK;
imclient->replybuf = xrealloc(imclient->replybuf,
imclient->alloc_replybuf);
imclient->replystart = imclient->replybuf;
}
}
parsed = imclient->replylen;
memcpy(imclient->replybuf + imclient->replylen, plainbuf, plainlen);
imclient->replylen += plainlen;
imclient->replybuf[imclient->replylen] = '\0';
while (parsed < imclient->replylen) {
if (imclient->replyliteralleft) {
size_t avail;
avail = imclient->replylen - parsed;
if (avail > imclient->replyliteralleft) {
parsed += imclient->replyliteralleft;
imclient->replyliteralleft = 0;
continue;
} else {
parsed += avail;
imclient->replyliteralleft -= avail;
return;
}
}
endreply = (char *)memchr(imclient->replybuf + parsed, '\n',
imclient->replylen - parsed);
if (!endreply) return;
parsed = endreply - imclient->replybuf + 1;
p = imclient->replystart;
if (*p == '+' && p[1] == ' ') {
if (imclient->readytag) {
imclient->readytag = 0;
imclient->readytxt = p+2;
*(endreply-1) = '\0';
}
else {
}
imclient->replystart = endreply + 1;
continue;
}
else if (*p == '*' && p[1] == ' ') {
replytag = 0;
p += 2;
}
else {
replytag = 0;
while (isdigit((unsigned char) *p)) {
replytag = replytag * 10 + *p++ - '0';
}
if (*p++ != ' ') {
imclient->replystart = endreply + 1;
continue;
}
}
if (replytag == 0 && isdigit((unsigned char) *p)) {
reply.msgno = 0;
while (isdigit((unsigned char) *p)) {
reply.msgno = reply.msgno * 10 + *p++ - '0';
}
if (*p++ != ' ') {
imclient->replystart = endreply + 1;
continue;
}
}
else {
reply.msgno = -1;
}
reply.keyword = p;
while (*p && *p != ' ' && *p != '\n') p++;
keywordlen = p - reply.keyword;
reply.text = p + 1;
if (*p == '\n') {
if (keywordlen && p[-1] == '\r') {
keywordlen--;
reply.text--;
}
reply.text--;
}
if (replytag != 0) {
int iscompletion =
((keywordlen == 3 && reply.keyword[0] == 'B' &&
reply.keyword[1] == 'A' && reply.keyword[2] == 'D') ||
(keywordlen == 2 &&
((reply.keyword[0] == 'O' && reply.keyword[1] == 'K') ||
(reply.keyword[0] == 'N' && reply.keyword[1] == 'O'))));
if (!iscompletion && endreply > imclient->replystart+2 &&
endreply[-1] == '\r' && endreply[-2] == '}' &&
isdigit((unsigned char) endreply[-3])) {
p = endreply - 4;
while (p > imclient->replystart &&
isdigit((unsigned char) *p)) {
p--;
}
if (p > imclient->replystart + 2 && *p == '{' &&
charclass[(unsigned char)p[-1]] != 2) {
literallen = 0;
p++;
while (isdigit((unsigned char) *p)) {
literallen = literallen*10 + *p++ -'0';
}
imclient->replyliteralleft = literallen;
continue;
}
}
imclient->replystart = endreply + 1;
if (replytag == imclient->readytag) {
imclient->readytag = 0;
imclient->readytxt = 0;
}
cmdcb = &imclient->cmdcallback;
while (*cmdcb && (*cmdcb)->tag != replytag) {
cmdcb = &(*cmdcb)->next;
}
if ((cmdcbtemp = *cmdcb)) {
if (iscompletion) {
*cmdcb = cmdcbtemp->next;
cmdcbtemp->next = cmdcallback_freelist;
cmdcallback_freelist = cmdcbtemp;
}
endreply[-1] = '\0';
reply.keyword[keywordlen] = '\0';
(*cmdcbtemp->proc)(imclient, cmdcbtemp->rock, &reply);
}
continue;
}
for (keywordindex = 1; keywordindex < imclient->callback_num;
keywordindex++) {
if (imclient->callback[keywordindex].flags & CALLBACK_NUMBERED) {
if (reply.msgno == -1) continue;
}
else {
if (reply.msgno != -1) continue;
}
if (!strncmp(imclient->callback[keywordindex].keyword,
reply.keyword, keywordlen) &&
imclient->callback[keywordindex].keyword[keywordlen] == '\0'
&& imclient->callback[keywordindex].proc)
break;
}
if (keywordindex == imclient->callback_num) keywordindex = 0;
if (!(imclient->callback[keywordindex].flags & CALLBACK_NOLITERAL)) {
if (endreply > imclient->replystart+2 &&
endreply[-1] == '\r' && endreply[-2] == '}' &&
isdigit((unsigned char) endreply[-3])) {
p = endreply - 4;
while (p > imclient->replystart &&
isdigit((unsigned char) *p)) {
p--;
}
if (p > imclient->replystart + 2 && *p == '{' &&
charclass[(unsigned char)p[-1]] != 2) {
literallen = 0;
p++;
while (isdigit((unsigned char) *p)) {
literallen = literallen*10 + *p++ -'0';
}
imclient->replyliteralleft = literallen;
continue;
}
}
}
if (imclient->callback[keywordindex].proc) {
endreply[-1] = '\0';
reply.keyword[keywordlen] = '\0';
(imclient->callback[keywordindex].proc)
(imclient, imclient->callback[keywordindex].rock, &reply);
}
imclient->replystart = endreply + 1;
}
}
static void imclient_eof(struct imclient *imclient)
{
struct imclient_cmdcallback *cmdcb;
struct imclient_reply reply;
assert(imclient);
imclient->readytag = 0;
imclient->readytxt = 0;
for (cmdcb = imclient->cmdcallback; cmdcb; cmdcb = cmdcb->next) {
reply.keyword = "EOF";
reply.msgno = -1;
reply.text = "";
(*cmdcb->proc)(imclient, cmdcb->rock, &reply);
if (!cmdcb->next) {
cmdcb->next = cmdcallback_freelist;
cmdcallback_freelist = imclient->cmdcallback;
break;
}
}
imclient->cmdcallback = 0;
}
void imclient_getselectinfo(struct imclient *imclient, int *fd,
int *wanttowrite)
{
assert(imclient);
assert(fd);
assert(wanttowrite);
*fd = imclient->fd;
*wanttowrite = imclient->outptr - imclient->outstart;
}
void imclient_processoneevent(struct imclient *imclient)
{
char buf[IMCLIENT_BUFSIZE];
int n;
int writelen;
fd_set rfds, wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
assert(imclient);
for (;;) {
writelen = imclient->outptr - imclient->outstart;
if ((imclient->saslcompleted==1) && (writelen>0)) {
unsigned int cryptlen=0;
const char *cryptptr=NULL;
if (sasl_encode(imclient->saslconn, imclient->outstart, writelen,
&cryptptr,&cryptlen)!=SASL_OK)
{
n=0;
}
#ifdef HAVE_SSL
if (imclient->tls_on==1)
{
n = SSL_write(imclient->tls_conn, cryptptr, cryptlen);
} else {
n = write(imclient->fd, cryptptr, cryptlen);
}
#else
n = write(imclient->fd, cryptptr,
cryptlen);
#endif
if (n > 0) {
imclient->outstart += writelen;
return;
}
}
else if (writelen) {
#ifdef HAVE_SSL
if (imclient->tls_on==1)
{
n = SSL_write(imclient->tls_conn, imclient->outstart, writelen);
} else {
n = write(imclient->fd, imclient->outstart, writelen);
}
#else
n = write(imclient->fd, imclient->outstart, writelen);
#endif
if (n > 0) {
imclient->outstart += n;
return;
}
}
if (FD_ISSET(imclient->fd, &rfds))
{
#ifdef HAVE_SSL
if (imclient->tls_on==1)
{
n = SSL_read(imclient->tls_conn, buf, sizeof(buf));
} else {
n = read(imclient->fd, buf, sizeof(buf));
}
#else
n = read(imclient->fd, buf, sizeof(buf));
#endif
if (n >= 0) {
if (n == 0) {
imclient_eof(imclient);
}
else {
imclient_input(imclient, buf, n);
}
return;
}
}
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(imclient->fd, &rfds);
if (writelen) FD_SET(imclient->fd, &wfds);
(void) select(imclient->fd + 1, &rfds, &wfds, (fd_set *)0, 0);
}
}
enum replytype {replytype_inprogress, replytype_ok, replytype_no,
replytype_bad, replytype_prematureok};
struct authresult {
enum replytype replytype;
int r;
};
static void authresult(struct imclient *imclient __attribute__((unused)),
void *rock,
struct imclient_reply *reply)
{
struct authresult *result = (struct authresult *)rock;
assert(result);
assert(reply);
if (!strcmp(reply->keyword, "OK")) {
result->replytype = replytype_ok;
}
else if (!strcmp(reply->keyword, "NO")) {
result->replytype = replytype_no;
}
else result->replytype = replytype_bad;
}
static void tlsresult(struct imclient *imclient __attribute__((unused)),
void *rock,
struct imclient_reply *reply)
{
struct authresult *result = (struct authresult *)rock;
assert(result);
assert(reply);
if (!strcmp(reply->keyword, "OK")) {
result->replytype = replytype_ok;
}
else if (!strcmp(reply->keyword, "NO")) {
result->replytype = replytype_no;
}
else result->replytype = replytype_bad;
}
static sasl_security_properties_t *make_secprops(int min,int max)
{
sasl_security_properties_t *ret=
(sasl_security_properties_t *)xzmalloc(sizeof(sasl_security_properties_t));
ret->maxbufsize = IMCLIENT_BUFSIZE;
ret->min_ssf = min;
ret->max_ssf = max;
return ret;
}
void interaction (struct imclient *context, sasl_interact_t *t, char *user)
{
char result[1024];
struct stringlist *cur;
assert(context);
assert(t);
cur = malloc(sizeof(struct stringlist));
if(!cur) {
t->len=0;
t->result=NULL;
return;
}
cur->str = NULL;
cur->next = context->interact_results;
context->interact_results = cur;
if ((t->id == SASL_CB_USER || t->id == SASL_CB_AUTHNAME)
&& user && user[0]) {
t->len = strlen(user);
cur->str = xstrdup(user);
} else {
printf("%s: ", t->prompt);
if (t->id == SASL_CB_PASS) {
char *ptr = getpass("");
strlcpy(result, ptr, sizeof(result));
} else {
fgets(result, sizeof(result)-1, stdin);
result[strlen(result) - 1] = '\0';
}
t->len = strlen(result);
cur->str = (char *) xmalloc(t->len+1);
memset(cur->str, 0, t->len+1);
memcpy(cur->str, result, t->len);
}
t->result = cur->str;
}
void fillin_interactions(struct imclient *context,
sasl_interact_t *tlist, char *user)
{
assert(context);
assert(tlist);
while (tlist->id!=SASL_CB_LIST_END)
{
interaction(context, tlist, user);
tlist++;
}
}
static int imclient_authenticate_sub(struct imclient *imclient,
char *mechlist,
char *user,
int minssf,
int maxssf,
const char **mechusing)
{
int saslresult;
sasl_security_properties_t *secprops=NULL;
socklen_t addrsize;
struct sockaddr_storage saddr_l;
struct sockaddr_storage saddr_r;
char localip[60], remoteip[60];
sasl_interact_t *client_interact=NULL;
const char *out;
unsigned int outlen;
int inlen;
struct authresult result;
assert(imclient);
assert(mechlist);
secprops=make_secprops(minssf,maxssf);
if (secprops==NULL) return 1;
saslresult=sasl_setprop(imclient->saslconn, SASL_SEC_PROPS, secprops);
if (saslresult!=SASL_OK) return 1;
free(secprops);
addrsize=sizeof(struct sockaddr_storage);
if (getpeername(imclient->fd,(struct sockaddr *)&saddr_r,&addrsize)!=0)
return 1;
addrsize=sizeof(struct sockaddr_storage);
if (getsockname(imclient->fd,(struct sockaddr *)&saddr_l,&addrsize)!=0)
return 1;
if(iptostring((const struct sockaddr *)&saddr_l, addrsize,
localip, sizeof(localip)) != 0)
return 1;
if(iptostring((const struct sockaddr *)&saddr_r, addrsize,
remoteip, sizeof(remoteip)) != 0)
return 1;
saslresult=sasl_setprop(imclient->saslconn, SASL_IPREMOTEPORT, remoteip);
if (saslresult!=SASL_OK) return 1;
saslresult=sasl_setprop(imclient->saslconn, SASL_IPLOCALPORT, localip);
if (saslresult!=SASL_OK) return 1;
saslresult=SASL_INTERACT;
while (saslresult==SASL_INTERACT)
{
saslresult=sasl_client_start(imclient->saslconn, mechlist,
&client_interact,
&out, &outlen,
mechusing);
if (saslresult==SASL_INTERACT) {
fillin_interactions(imclient,
client_interact, user);
}
}
if ((saslresult!=SASL_OK) && (saslresult!=SASL_CONTINUE)) return saslresult;
imclient_send(imclient, authresult, (void *)&result,
"AUTHENTICATE %a", *mechusing);
while (1) {
imclient->readytag = imclient->gensym;
while (imclient->readytag) {
imclient_processoneevent(imclient);
}
if (!imclient->readytxt) break;
if (isspace((unsigned char) *imclient->readytxt)) {
inlen = 0;
} else {
inlen = imclient_decodebase64(imclient->readytxt);
}
if (inlen == -1) {
return replytype_bad;
}
if (inlen == 0 && outlen > 0) {
} else {
saslresult = SASL_INTERACT;
while (saslresult == SASL_INTERACT) {
saslresult=sasl_client_step(imclient->saslconn,
imclient->readytxt,
inlen,
&client_interact,
&out,
&outlen);
if (saslresult == SASL_INTERACT) {
fillin_interactions(imclient,
client_interact, user);
}
}
}
if ((saslresult==SASL_OK) || (saslresult==SASL_CONTINUE)) {
if (out == NULL || outlen == 0) {
imclient_write(imclient, "\r\n", 2);
} else {
imclient_writebase64(imclient, out, outlen);
}
} else {
imclient_write(imclient,"*\r\n", 3);
return saslresult;
}
outlen = 0;
}
if(result.replytype == replytype_ok) imclient->saslcompleted = 1;
return (result.replytype != replytype_ok);
}
int imclient_authenticate(struct imclient *imclient,
char *mechlist,
char *service __attribute__((unused)),
char *user,
int minssf,
int maxssf)
{
int r;
char *mlist;
const char *mtried;
assert(imclient);
assert(mechlist);
mlist = xstrdup(mechlist);
ucase(mlist);
do {
mtried = NULL;
r = imclient_authenticate_sub(imclient,
mlist,
user,
minssf,
maxssf,
&mtried);
if (r != 0 && mtried) {
char *newlist = xmalloc(strlen(mlist)+1);
char *mtr = xstrdup(mtried);
char *tmp;
ucase(mtr);
tmp = strstr(mlist,mtr);
if(!tmp) {
free(mtr);
free(mlist);
break;
}
*tmp = '\0';
strcpy(newlist,mlist);
tmp = strchr(tmp+1,' ');
if (tmp) {
tmp++;
strcat(newlist,tmp);
}
free(mtr);
free(mlist);
mlist = newlist;
}
} while ((r != 0) && (mtried));
if (r == 0) {
const int *ptr;
sasl_getprop(imclient->saslconn, SASL_MAXOUTBUF, (const void **) &ptr);
imclient->maxplain = *ptr < IMCLIENT_BUFSIZE ? *ptr : IMCLIENT_BUFSIZE;
}
free(mlist);
return r;
}
#define XX 127
static const char basis_64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char index_64[256] = {
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define CHAR64(c) (index_64[(unsigned char)(c)])
static int imclient_decodebase64(char *input)
{
int len = 0;
unsigned char *output = (unsigned char *)input;
int c1, c2, c3, c4;
assert(input);
while (*input) {
c1 = *input++;
if (CHAR64(c1) == XX) return -1;
c2 = *input++;
if (CHAR64(c2) == XX) return -1;
c3 = *input++;
if (c3 != '=' && CHAR64(c3) == XX) return -1;
c4 = *input++;
if (c4 != '=' && CHAR64(c4) == XX) return -1;
*output++ = (CHAR64(c1) << 2) | (CHAR64(c2) >> 4);
++len;
if (c3 == '=') break;
*output++ = ((CHAR64(c2) << 4) & 0xf0) | (CHAR64(c3) >> 2);
++len;
if (c4 == '=') break;
*output++ = ((CHAR64(c3) << 6) & 0xc0) | CHAR64(c4);
++len;
}
return len;
}
static void imclient_writebase64(struct imclient *imclient,
const char *output,
size_t len)
{
char buf[1024];
size_t buflen = 0;
int c1, c2, c3;
assert(imclient);
assert(output);
while (len) {
if (buflen >= (size_t)(sizeof(buf)-4)) {
imclient_write(imclient, buf, buflen);
buflen = 0;
}
c1 = (unsigned char)*output++;
buf[buflen++] = basis_64[c1>>2];
if (--len == 0) c2 = 0;
else c2 = (unsigned char)*output++;
buf[buflen++] = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
if (len == 0) {
buf[buflen++] = '=';
buf[buflen++] = '=';
break;
}
if (--len == 0) c3 = 0;
else c3 = (unsigned char)*output++;
buf[buflen++] = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
if (len == 0) {
buf[buflen++] = '=';
break;
}
--len;
buf[buflen++] = basis_64[c3 & 0x3F];
}
if (buflen >= sizeof(buf)-2) {
imclient_write(imclient, buf, buflen);
buflen = 0;
}
buf[buflen++] = '\r';
buf[buflen++] = '\n';
imclient_write(imclient, buf, buflen);
}
#ifdef HAVE_SSL
static int verify_depth;
static int verify_error = X509_V_OK;
#define CCERT_BUFSIZ 256
static char peer_CN[CCERT_BUFSIZ];
static char issuer_CN[CCERT_BUFSIZ];
static int set_cert_stuff(SSL_CTX * ctx, char *cert_file, char *key_file)
{
if (cert_file != NULL) {
if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
printf("[ unable to get certificate from '%s' ]\n", cert_file);
return (0);
}
if (key_file == NULL)
key_file = cert_file;
if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
SSL_FILETYPE_PEM) <= 0) {
printf("[ unable to get private key from '%s' ]\n", key_file);
return (0);
}
if (!SSL_CTX_check_private_key(ctx)) {
printf("[ Private key does not match the certificate public key ]\n");
return (0);
}
}
return (1);
}
static int verify_callback(int ok, X509_STORE_CTX * ctx)
{
char buf[256];
X509 *err_cert;
int err;
int depth;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
if (!ok) {
printf("verify error:num=%d:%s\n", err,
X509_verify_cert_error_string(err));
if (verify_depth >= depth) {
ok = 1;
verify_error = X509_V_OK;
} else {
ok = 0;
verify_error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
}
}
switch (ctx->error) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
buf, sizeof(buf));
printf("issuer= %s\n", buf);
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
printf("cert not yet valid\n");
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
printf("cert has expired\n");
break;
}
return (ok);
}
static RSA *tmp_rsa_cb(SSL *s __attribute__((unused)),
int export __attribute__((unused)),
int keylength)
{
static RSA *rsa_tmp = NULL;
if (rsa_tmp == NULL) {
rsa_tmp = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
}
return (rsa_tmp);
}
static int tls_rand_init(void)
{
#ifdef EGD_SOCKET
return (RAND_egd(EGD_SOCKET));
#else
return 0;
#endif
}
static int tls_init_clientengine(struct imclient *imclient,
int verifydepth, char *var_tls_cert_file,
char *var_tls_key_file,
char *var_tls_CAfile,
char *var_tls_CApath)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
char *CApath;
char *CAfile;
char *c_cert_file;
char *c_key_file;
assert(imclient);
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
if (tls_rand_init() == -1) {
printf("[ TLS engine: cannot seed PRNG ]\n");
return -1;
}
imclient->tls_ctx = SSL_CTX_new(TLSv1_client_method());
if (imclient->tls_ctx == NULL) {
return -1;
};
off |= SSL_OP_ALL;
SSL_CTX_set_options(imclient->tls_ctx, off);
if (var_tls_CAfile == NULL || strlen(var_tls_CAfile) == 0)
CAfile = NULL;
else
CAfile = var_tls_CAfile;
if (var_tls_CApath == NULL || strlen(var_tls_CApath) == 0)
CApath = NULL;
else
CApath = var_tls_CApath;
if (CAfile || CApath)
if ((!SSL_CTX_load_verify_locations(imclient->tls_ctx, CAfile, CApath)) ||
(!SSL_CTX_set_default_verify_paths(imclient->tls_ctx))) {
printf("[ TLS engine: cannot load CA data ]\n");
return -1;
}
if (var_tls_cert_file == NULL || strlen(var_tls_cert_file) == 0)
c_cert_file = NULL;
else
c_cert_file = var_tls_cert_file;
if (var_tls_key_file == NULL || strlen(var_tls_key_file) == 0)
c_key_file = NULL;
else
c_key_file = var_tls_key_file;
if (c_cert_file || c_key_file)
if (!set_cert_stuff(imclient->tls_ctx, c_cert_file, c_key_file)) {
printf("[ TLS engine: cannot load cert/key data ]\n");
return -1;
}
SSL_CTX_set_tmp_rsa_callback(imclient->tls_ctx, tmp_rsa_cb);
verify_depth = verifydepth;
SSL_CTX_set_verify(imclient->tls_ctx, verify_flags, verify_callback);
return 0;
}
#if 0
static int do_dump = 1;
#define TRUNCATE
#define DUMP_WIDTH 16
static int tls_dump(const char *s, int len)
{
int ret = 0;
char buf[160 + 1];
char *ss;
int i;
int j;
int rows;
int trunc;
unsigned char ch;
trunc = 0;
#ifdef TRUNCATE
for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--)
trunc++;
#endif
rows = (len / DUMP_WIDTH);
if ((rows * DUMP_WIDTH) < len)
rows++;
for (i = 0; i < rows; i++) {
buf[0] = '\0';
ss = buf;
sprintf(ss, "%04x ", i * DUMP_WIDTH);
ss += strlen(ss);
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len) {
strcpy(ss, " ");
} else {
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j))
& 0xff;
sprintf(ss, "%02x[%c]%c", ch, ch, j == 7 ? '|' : ' ');
ss += 6;
}
}
ss += strlen(ss);
*ss+= ' ';
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len)
break;
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) & 0xff;
*ss+= (((ch >= ' ') && (ch <= '~')) ? ch : '.');
if (j == 7) *ss+= ' ';
}
*ss = 0;
printf("%s\n", buf);
ret += strlen(buf);
}
#ifdef TRUNCATE
if (trunc > 0) {
sprintf(buf, "%04x - <SPACES/NULS>\n", len+ trunc);
printf("%s\n", buf);
ret += strlen(buf);
}
#endif
return (ret);
}
static long bio_dump_cb(BIO * bio,
int cmd,
const char *argp,
int argi,
long argl __attribute__((unused)),
long ret)
{
if (!do_dump)
return (ret);
if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
printf("read from %08X [%08lX] (%d bytes => %ld (0x%X))\n", (unsigned int) bio,
(unsigned long) argp,
argi, ret, (unsigned int) ret);
tls_dump(argp, (int) ret);
return (ret);
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
printf("write to %08X [%08lX] (%d bytes => %ld (0x%X))\n", (unsigned int) bio,
(unsigned long) argp,
argi, ret, (unsigned int) ret);
tls_dump(argp, (int) ret);
}
return (ret);
}
static void apps_ssl_info_callback(SSL * s, int where, int ret)
{
char *str;
int w;
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
str = "SSL_accept";
else
str = "undefined";
if (where & SSL_CB_LOOP) {
printf("%s:%s\n", str, SSL_state_string_long(s));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "read" : "write";
if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
printf("SSL3 alert %s:%s:%s\n", str,
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
} else if (where & SSL_CB_EXIT) {
if (ret == 0)
printf("%s:failed in %s\n",
str, SSL_state_string_long(s));
else if (ret < 0) {
printf("%s:error in %s %i\n",
str, SSL_state_string_long(s),ret);
}
}
}
#endif
int tls_start_clienttls(struct imclient *imclient,
unsigned *layer, char **authid, int fd)
{
int sts;
SSL_SESSION *session;
SSL_CIPHER *cipher;
X509 *peer;
const char *tls_protocol = NULL;
const char *tls_cipher_name = NULL;
int tls_cipher_usebits = 0;
int tls_cipher_algbits = 0;
char *tls_peer_CN = "";
char *tls_issuer_CN = NULL;
if (imclient->tls_conn == NULL) {
imclient->tls_conn = (SSL *) SSL_new(imclient->tls_ctx);
}
if (imclient->tls_conn == NULL) {
printf("Could not allocate 'con' with SSL_new()\n");
return -1;
}
SSL_clear(imclient->tls_conn);
if (!SSL_set_fd(imclient->tls_conn, fd)) {
printf("SSL_set_fd failed\n");
return -1;
}
SSL_set_connect_state(imclient->tls_conn);
if ((sts = SSL_connect(imclient->tls_conn)) <= 0) {
printf("[ SSL_connect error %d ]\n", sts);
session = SSL_get_session(imclient->tls_conn);
if (session) {
SSL_CTX_remove_session(imclient->tls_ctx, session);
printf("[ SSL session removed ]\n");
}
if (imclient->tls_conn!=NULL)
SSL_free(imclient->tls_conn);
imclient->tls_conn = NULL;
return -1;
}
peer = SSL_get_peer_certificate(imclient->tls_conn);
if (peer != NULL) {
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, peer_CN, CCERT_BUFSIZ);
tls_peer_CN = peer_CN;
X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_commonName, issuer_CN, CCERT_BUFSIZ);
tls_issuer_CN = issuer_CN;
}
tls_protocol = SSL_get_version(imclient->tls_conn);
cipher = SSL_get_current_cipher(imclient->tls_conn);
tls_cipher_name = SSL_CIPHER_get_name(cipher);
tls_cipher_usebits = SSL_CIPHER_get_bits(cipher,
&tls_cipher_algbits);
if (layer!=NULL)
*layer = tls_cipher_usebits;
if (authid!=NULL)
*authid = tls_peer_CN;
return 0;
}
#endif
int imclient_havetls () {
#ifdef HAVE_SSL
return 1;
#else
return 0;
#endif
}
int imclient_starttls(struct imclient *imclient,
char *cert_file,
char *key_file,
char *CAfile,
char *CApath)
{
#ifdef HAVE_SSL
int result;
struct authresult theresult;
unsigned ssf;
char *auth_id;
imclient_send(imclient, tlsresult, (void *)&theresult,
"STARTTLS");
imclient->readytag = imclient->gensym;
while (imclient->readytag) {
imclient_processoneevent(imclient);
}
result=tls_init_clientengine(imclient, 10, cert_file, key_file,
CAfile, CApath);
if (result!=0)
{
printf("[ TLS engine failed ]\n");
return 1;
} else {
result=tls_start_clienttls(imclient, &ssf, &auth_id, imclient->fd);
if (result!=0) {
printf("[ TLS negotiation did not succeed ]\n");
return 1;
}
}
imclient->tls_on = 1;
auth_id="";
result=sasl_setprop(imclient->saslconn,
SASL_SSF_EXTERNAL,
&ssf);
if (result!=SASL_OK) return 1;
result=sasl_setprop(imclient->saslconn,
SASL_AUTH_EXTERNAL,
auth_id);
if (result!=SASL_OK) return 1;
return 0;
#else
printf("[ TLS support not present (imclient_starttls) ]\n");
return 1;
#endif
}