/* imclient.c -- Streaming IMxP client library
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdarg.h>
#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>
#else
#include <errno.h>
#endif /* HAVE_SSL */
#include "assert.h"
#include "exitcodes.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "strarray.h"
#include "imclient.h"
#include "nonblock.h"
#include "util.h"
#include "iptostring.h"
/* I/O buffer size */
#define IMCLIENT_BUFSIZE 4096
/* Command completion callback record */
struct imclient_cmdcallback {
struct imclient_cmdcallback *next;
unsigned long tag; /* Command tag # */
imclient_proc_t *proc; /* Callback function */
void *rock; /* Callback rock */
};
/* Untagged data callback record */
struct imclient_callback {
int flags; /* Information about untagged data */
char *keyword; /* Untagged data protocol keyword */
imclient_proc_t *proc; /* Callback function */
void *rock; /* Callback rock */
};
/* Connection data */
struct imclient {
/* TCP stream */
int fd;
char *servername;
int flags;
/* Data to be output to server */
char outbuf[IMCLIENT_BUFSIZE];
char *outptr;
size_t outleft;
char *outstart;
/* Replies being received from server */
char *replybuf;
char *replystart;
size_t replyliteralleft;
size_t replylen;
size_t alloc_replybuf;
/* Protection mechanism data */
/* struct sasl_client *mech;
sasl_encodefunc_t *encodefunc;
sasl_decodefunc_t *decodefunc;*/
void *state;
int maxplain;
unsigned long gensym; /* Tag value for previous command */
unsigned long readytag; /* Tag of command waiting for ready response */
/* 0 if wait over or not pending */
char *readytxt; /* Text of ready response, NULL if got
tagged reply for command */
/* Command callbacks */
struct imclient_cmdcallback *cmdcallback;
/* Untagged data callbacks */
int callback_num;
int callback_alloc;
struct imclient_callback *callback;
strarray_t interact_results;
sasl_conn_t *saslconn;
int saslcompleted;
#ifdef HAVE_SSL
SSL_CTX *tls_ctx;
SSL *tls_conn;
int tls_on; /* wheather we are under a layer or not */
#endif /* HAVE_SSL */
};
/*
* Syntactic class of a character
* 0 - literal, 1 - quoted-string, 2 - atom
*/
static const char charclass[256] = {
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, /* 00 - 0f */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1f */
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, /* '0' - '?' */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* '@' - 'O' */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, /* 'P' - '_' */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* '`' - 'o' */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, /* 'p' - DEL */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a0 - af */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b0 - bf */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c0 - cf */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d0 - df */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e0 - ef */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f0 - ff */
};
/* Free list of command callback records */
static struct imclient_cmdcallback *cmdcallback_freelist;
/* Forward declarations */
static int imclient_writeastring(struct imclient *imclient, const char *str);
static void imclient_writebase64(struct imclient *imclient,
const char *output, size_t len);
static void imclient_eof(struct imclient *imclient);
static int imclient_decodebase64(char *input);
/* callbacks we support */
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 }
};
/*
* Connect to server on 'host'. Optional 'port' specifies the service
* to use. On success, returns zero and fills in the pointer pointed
* to by 'imclient' with a newly allocated connection pointer. On
* failure, returns errno if a system call failed, -1 if the hostname
* was not found, or -2 if the service name was not found.
* use sasl callbacks 'cbs'
*/
EXPORTED 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;
/* nonblock(s, 1); */
*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);
strarray_init(&(*imclient)->interact_results);
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 /* HAVE_SSL */
if (!didinit) {
/* attempt to start sasl */
saslresult = sasl_client_init(NULL);
if (saslresult!=SASL_OK) return 1;
didinit = 1;
}
/* client new connection */
saslresult=sasl_client_new("imap", /* xxx ideally this should be configurable */
(*imclient)->servername,
NULL, NULL,
cbs ? cbs : callbacks,
0,
&((*imclient)->saslconn));
if (saslresult!=SASL_OK) return 1;
return 0;
}
/*
* Close and free the connection 'imclient'
*/
void
EXPORTED imclient_close(struct imclient *imclient)
{
int i;
assert(imclient);
imclient_eof(imclient);
close(imclient->fd);
free(imclient->servername);
if (imclient->replybuf) free(imclient->replybuf);
/* if (imclient->state) imclient->mech->free_state(imclient->state);*/
sasl_dispose(&(imclient->saslconn));
for (i = 0; i < imclient->callback_num; i++) {
free(imclient->callback[i].keyword);
}
if (imclient->callback) free(imclient->callback);
strarray_fini(&imclient->interact_results);
free(imclient);
}
EXPORTED void imclient_setflags(struct imclient *imclient, int flags)
{
assert(imclient);
imclient->flags |= flags;
}
EXPORTED void imclient_clearflags(struct imclient *imclient, int flags)
{
assert(imclient);
imclient->flags &= ~flags;
}
EXPORTED char *
imclient_servername(struct imclient *imclient)
{
assert(imclient);
return imclient->servername;
}
#define CALLBACKGROW 5
/*
* Add untagged data callbacks to a connection.
* After the first argument 'imclient', there can be zero or more
* 4-tuples of 'keyword', 'flags', 'proc', 'rock', each adding or changing
* a single callback. The last 4-tuple is terminated by a single null pointer.
*
* Each 4-tuple adds or changes the callback for 'keyword'. 'flags' specifies
* information about the parsing of the untagged data. 'proc' and 'rock'
* specify the callback function and rock to invoke when the untagged data
* is received. 'proc' may be a null pointer, in which case no function is
* invoked.
*
* The callback function may not call the functions imclient_close(),
* imclient_send(), imclient_eof(), imclient_processoneevent(), or
* imclient_authenticate() on the connection. The callback function
* may scribble on the text of the untagged data.
*
*/
EXPORTED void imclient_addcallback(struct imclient *imclient, ...)
{
va_list pvar;
char *keyword;
int flags;
imclient_proc_t *proc;
void *rock;
int i;
va_start(pvar, imclient);
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 *);
/* Search for existing callback matching keyword and flags */
for (i = 0; i < imclient->callback_num; i++) {
if (imclient->callback[i].flags == flags &&
!strcmp(imclient->callback[i].keyword, keyword)) break;
}
/* If not found, allocate new callback entry */
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);
}
/*
* Send a new command on the connection 'imclient'.
*
* 'finishproc' and 'finishrock' are the function and rock called when
* the command completes. 'functionproc' may be a null pointer, in
* which case no callback is made. The callback function may not call
* the functions imclient_close(), imclient_send(), imclient_eof(),
* imclient_processoneevent(), or imclient_authenticate() on the
* connection. The callback function is guaranteed to be invoked, the
* special result type "EOF" is used in the case where the connection
* dies before a result is received from the server.
*
* 'fmt' is a printf-like specification of the command. It must not
* include the tag--that is automatically added by imclient_send().
* The defined %-sequences are as follows:
*
* %% -- %
* %a -- atom
* %s -- astring (will be quoted or literalized as needed)
* %d -- decimal
* %u -- unsigned decimal
* %v -- #astring (arg is an null-terminated array of (char *)
* which are written as space separated astrings)
* %B -- (internal use only) base64-encoded data at end of command line
*/
EXPORTED void
imclient_send(struct imclient *imclient, imclient_proc_t *finishproc,
void *finishrock, const char *fmt, ...)
{
va_list pvar;
struct imclient_cmdcallback *newcmdcallback;
char buf[30];
char *percent, *str, **v;
int num;
unsigned unum;
int abortcommand = 0;
va_start(pvar, fmt);
assert(imclient);
imclient->gensym++;
if (imclient->gensym <= 0) imclient->gensym = 1;
/*
* If there is a command completion callback, add it to the
* command callback list of the imclient struct.
*/
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;
}
/* Write the tag */
snprintf(buf, sizeof(buf), "%lu ", imclient->gensym);
imclient_write(imclient, buf, strlen(buf));
/* Process the command format */
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);
/* KLUDGE ALERT: imclientwritebase64() spit out a CRLF
* so fake things up to prevent our spitting out a second CRLF.
*/
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) {
/* Atom */
imclient_write(imclient, str, len);
}
else if (class) {
/* Quoted-string */
imclient_write(imclient, "\"", 1);
imclient_write(imclient, str, len);
imclient_write(imclient, "\"", 1);
}
else {
/* Literal */
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;
}
/*
* Write to the connection 'imclient' the data 's', of length 'len'
*/
void imclient_write(struct imclient *imclient, const char *s, size_t len)
{
assert(imclient);
assert(s);
/* If no data pending for output, reset the buffer */
if (imclient->outptr == imclient->outstart) {
imclient->outstart = imclient->outptr = imclient->outbuf;
imclient->outleft = imclient->maxplain;
}
/* While we don't have room to buffer all the output */
while (len > imclient->outleft) {
/* Copy as much data as will fit in output buffer */
memcpy(imclient->outptr, s, imclient->outleft);
imclient->outptr += imclient->outleft;
s += imclient->outleft;
len -= imclient->outleft;
imclient->outleft = 0;
/* Process events until output buffer is flushed */
while (imclient->outptr != imclient->outstart) {
imclient_processoneevent(imclient);
}
/* Reset the buffer */
imclient->outstart = imclient->outptr = imclient->outbuf;
imclient->outleft = imclient->maxplain;
}
/* Copy remaining data to output buffer */
memcpy(imclient->outptr, s, len);
imclient->outptr += len;
imclient->outleft -= len;
}
/*
* On the connection 'imclient', handle the input 'buf' of size 'len'
* from the server. Invoke callbacks as appropriate.
*/
#define REPLYSLACK 80 /* When growing, allocate this extra slack */
#define REPLYSHRINK (4096+500) /* If more than this free, shrink buffer */
static void imclient_input(struct imclient *imclient, char *buf, int len)
{
unsigned long replytag;
struct imclient_reply reply;
char *endreply;
const char *p;
size_t parsed;
uint32_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) {
/* decrypt what we have */
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;
}
/* Ensure replybuf has enough space to take the input */
if (imclient->replylen + plainlen >= imclient->alloc_replybuf) {
/* If there is unused space at the front, move the plaintext there */
if (imclient->replystart != imclient->replybuf) {
imclient->replylen -= imclient->replystart - imclient->replybuf;
memmove(imclient->replybuf, imclient->replystart,
imclient->replylen);
imclient->replystart = imclient->replybuf;
}
/* Shrink the reply buffer if it's too large */
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 there still isn't enough room, grow the buffer */
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;
}
}
/* Remember where new data starts */
parsed = imclient->replylen;
/* Copy the data to the buffer and NUL-terminate it */
memcpy(imclient->replybuf + imclient->replylen, plainbuf, plainlen);
imclient->replylen += plainlen;
imclient->replybuf[imclient->replylen] = '\0';
/* Process the new data (of length 'plainlen') */
while (parsed < imclient->replylen) {
/* If we're reading a literal, skip over it. */
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;
}
}
/* Look for the end of the line and skip over to it. */
endreply = (char *)memchr(imclient->replybuf + parsed, '\n',
imclient->replylen - parsed);
/* Don't have a complete line */
if (!endreply) return;
parsed = endreply - imclient->replybuf + 1;
/* parse tag */
p = imclient->replystart;
if (*p == '+' && p[1] == ' ') {
/* Ready response */
if (imclient->readytag) {
imclient->readytag = 0;
imclient->readytxt = (char *)p+2;
*(endreply-1) = '\0';
}
else {
/* XXX Got junk from the server */
}
/* Start parsing the next reply */
imclient->replystart = endreply + 1;
continue;
}
else if (*p == '*' && p[1] == ' ') {
replytag = 0;
p += 2;
}
else {
replytag = 0;
while (Uisdigit(*p)) {
replytag = replytag * 10 + *p++ - '0';
}
if (*p++ != ' ') {
/* XXX Got junk from the server */
/* Start parsing the next reply */
imclient->replystart = endreply + 1;
continue;
}
}
/* parse num, if there */
if (replytag == 0 && Uisdigit(*p)) {
reply.msgno = 0;
while (Uisdigit(*p)) {
reply.msgno = reply.msgno * 10 + *p++ - '0';
}
if (*p++ != ' ') {
/* XXX Got junk from the server */
/* Start parsing the next reply */
imclient->replystart = endreply + 1;
continue;
}
}
else {
reply.msgno = -1;
}
/* parse keyword */
reply.keyword = (char *)p;
while (*p && *p != ' ' && *p != '\n') p++;
keywordlen = p - reply.keyword;
reply.text = (char *)p + 1;
if (*p == '\n') {
if (keywordlen && p[-1] == '\r') {
keywordlen--;
reply.text--;
}
reply.text--;
}
/* Handle tagged replies */
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'))));
/* Scan back and see if the end of the line introduces a literal */
if (!iscompletion && endreply > imclient->replystart+2 &&
endreply[-1] == '\r' && endreply[-2] == '}' &&
Uisdigit(endreply[-3])) {
p = endreply - 4;
while (p > imclient->replystart &&
Uisdigit(*p)) {
p--;
}
if (p > imclient->replystart + 2 && *p == '{' &&
charclass[(unsigned char)p[-1]] != 2) {
/* Parse the size of the literal */
if (parseuint32(p+1, &p, &literallen))
literallen = 0;
/* Do a continue to read literal & following line */
imclient->replyliteralleft = literallen;
continue;
}
}
/* Start parsing the next reply */
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) {
/* Move callback struct to the freelist */
*cmdcb = cmdcbtemp->next;
cmdcbtemp->next = cmdcallback_freelist;
cmdcallback_freelist = cmdcbtemp;
}
/* Do the callback */
endreply[-1] = '\0';
reply.keyword[keywordlen] = '\0';
(*cmdcbtemp->proc)(imclient, cmdcbtemp->rock, &reply);
}
continue;
}
/* Must be an untagged reply, look up the keyword */
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;
}
/* Keyword index 0 is the default callback */
if (keywordindex == imclient->callback_num) keywordindex = 0;
/* Scan back and see if the end of the line introduces a literal */
if (!(imclient->callback[keywordindex].flags & CALLBACK_NOLITERAL)) {
if (endreply > imclient->replystart+2 &&
endreply[-1] == '\r' && endreply[-2] == '}' &&
Uisdigit(endreply[-3])) {
p = endreply - 4;
while (p > imclient->replystart &&
Uisdigit(*p)) {
p--;
}
if (p > imclient->replystart + 2 && *p == '{' &&
charclass[(unsigned char)p[-1]] != 2) {
/* Parse the size of the literal */
if (parseuint32(p+1, &p, &literallen))
literallen = 0;
/* Do a continue to read literal & following line */
imclient->replyliteralleft = literallen;
continue;
}
}
}
/* Do the callback, if the proc is non-null */
if (imclient->callback[keywordindex].proc) {
endreply[-1] = '\0';
reply.keyword[keywordlen] = '\0';
(imclient->callback[keywordindex].proc)
(imclient, imclient->callback[keywordindex].rock, &reply);
}
/* Start parsing the next reply */
imclient->replystart = endreply + 1;
}
}
/*
* Received an EOF on the connection 'imclient'
* Issue appropriate callbacks.
*/
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;
/* XXX make an untagged "EOF" callback? */
}
/*
* Get information for calling select
* 'fd' is filled in with file descriptor to select() for read
* 'wanttowrite' is filled in with nonzero value iff should
* select() for write as well.
*/
EXPORTED void imclient_getselectinfo(struct imclient *imclient, int *fd,
int *wanttowrite)
{
assert(imclient);
assert(fd);
assert(wanttowrite);
*fd = imclient->fd;
*wanttowrite = imclient->outptr - imclient->outstart;
}
/*
* Process one input or output event on the connection 'imclient'.
*/
EXPORTED 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)
{
/* XXX encoding error */
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 /* HAVE_SSL */
n = write(imclient->fd, cryptptr,
cryptlen);
#endif /* HAVE_SSL */
if (n > 0) {
imclient->outstart += writelen;
return;
}
/* XXX Also EPIPE & the like? */
/* Make sure we select() for writing */
}
else if (writelen) {
/* No protection mechanism, just write the plaintext */
#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 /* HAVE_SSL */
n = write(imclient->fd, imclient->outstart, writelen);
#endif /* HAVE_SSL */
if (n > 0) {
imclient->outstart += n;
return;
}
/* XXX Also EPIPE & the like? */
}
if (FD_ISSET(imclient->fd, &rfds))
{
#ifdef HAVE_SSL
/* just do a SSL read instead if we're under a tls layer */
if (imclient->tls_on==1)
{
n = SSL_read(imclient->tls_conn, buf, sizeof(buf));
} else {
n = read(imclient->fd, buf, sizeof(buf));
}
#else /* HAVE_SSL */
n = read(imclient->fd, buf, sizeof(buf));
#endif /* HAVE_SSL */
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;
};
/* Command completion callback for imclient_authenticate */
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;
}
/* Command completion for starttls */
#ifdef HAVE_SSL
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;
}
#endif /* HAVE_SSL */
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;
}
static void interaction(struct imclient *context, sasl_interact_t *t, char *user)
{
char result[1024];
char *str = NULL;
assert(context);
assert(t);
if ((t->id == SASL_CB_USER || t->id == SASL_CB_AUTHNAME)
&& user && user[0]) {
str = xstrdup(user);
} else {
printf("%s: ", t->prompt);
if (t->id == SASL_CB_PASS) {
char *ptr = cyrus_getpass("");
strlcpy(result, ptr, sizeof(result));
} else {
if (!fgets(result, sizeof(result)-1, stdin))
return;
}
str = xstrdup(result);
}
assert(str);
t->result = str;
t->len = strlen(str);
strarray_appendm(&context->interact_results, str);
}
EXPORTED 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++;
}
}
/*
* Params:
* mechlist: list of mechanisms seperated by spaces
*
* Returns:
* 0 - sucess
* 1 - failure
* 2 - severe failure?
*/
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);
/*******
* Now set the SASL properties
*******/
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;
/********
* SASL is setup. Now try the actual authentication
********/
saslresult=SASL_INTERACT;
/* call sasl client start */
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); /* fill in prompts */
}
}
if ((saslresult!=SASL_OK) && (saslresult!=SASL_CONTINUE)) return saslresult;
imclient_send(imclient, authresult, (void *)&result,
"AUTHENTICATE %a", *mechusing);
while (1) {
/* Wait for ready response or command completion */
imclient->readytag = imclient->gensym;
while (imclient->readytag) {
imclient_processoneevent(imclient);
}
/* stop looping on command completion */
if (!imclient->readytxt) break;
if (Uisspace(*imclient->readytxt)) {
inlen = 0;
} else {
inlen = imclient_decodebase64(imclient->readytxt);
}
if (inlen == -1) {
/* bad base64 string */
return replytype_bad;
}
if (inlen == 0 && outlen > 0) {
/* we have something from the initial thing to send */
} else {
/* perform a step */
saslresult = SASL_INTERACT;
while (saslresult == SASL_INTERACT) {
saslresult=sasl_client_step(imclient->saslconn,
imclient->readytxt,
inlen,
&client_interact,
&out,
&outlen);
if (saslresult == SASL_INTERACT) {
/* fill in prompts */
fillin_interactions(imclient,
client_interact, user);
}
}
}
/* send our reply to the server */
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);
}
/* xxx service is not needed here */
EXPORTED 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);
/* eliminate mtried (mechanism tried) from mlist */
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(newlist);
break;
}
*tmp = '\0';
strcpy(newlist,mlist);
/* Use tmp+1 here to skip the \0 we just put in.
* this is safe because even if the mechs are one character
* long there would still be another trailing \0 */
tmp = strchr(tmp+1,' ');
if (tmp) {
tmp++; /* skip the space */
strcat(newlist,tmp);
}
free(mtr);
free(mlist);
mlist = newlist;
}
} while ((r != 0) && (mtried));
if (r == 0) {
const void *maxp;
unsigned int max;
sasl_getprop(imclient->saslconn, SASL_MAXOUTBUF, &maxp);
max = *((const unsigned int *) maxp);
imclient->maxplain = max < IMCLIENT_BUFSIZE ? max : IMCLIENT_BUFSIZE;
}
free(mlist);
return r;
}
#define XX 127
/*
* Tables for encoding/decoding base64
*/
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)])
/*
* Decode in-place the base64 data in 'input'. Returns the length
* of the decoded data, or -1 if there was an error.
*/
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;
}
/*
* Write to the connection 'imclient' the base-64 encoded data
* 'output', of (unencoded) length '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);
}
/*************** All these functions help do the starttls; these are copied from imtest.c ********/
#ifdef HAVE_SSL
static int verify_depth;
static int verify_error = X509_V_OK;
#define CCERT_BUFSIZ 256
static char peer_CN[CCERT_BUFSIZ];
/*
* Set up the cert things on the server side. We do need both the
* private key (in key_file) and the cert (in cert_file).
* Both files may be identical.
*
* This function is taken from OpenSSL apps/s_cb.c
*/
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);
}
/* Now we know that a key and cert have been set against
* the SSL context */
if (!SSL_CTX_check_private_key(ctx)) {
printf("[ Private key does not match the certificate public key ]\n");
return (0);
}
}
return (1);
}
/* taken from OpenSSL apps/s_cb.c */
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 (verbose==1)
printf("Peer cert verify depth=%d %s\n", depth, 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 (err) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
X509_NAME_oneline(X509_get_issuer_name(err_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;
}
/* if (verbose==1)
printf("verify return:%d\n", ok);*/
return (ok);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
/* taken from OpenSSL apps/s_cb.c */
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);
}
#endif
/*
* Seed the random number generator.
*/
static int tls_rand_init(void)
{
#ifdef EGD_SOCKET
return (RAND_egd(EGD_SOCKET));
#else
/* otherwise let OpenSSL do it internally */
return 0;
#endif
}
/*
* This is the setup routine for the SSL client.
*
* The skeleton of this function is taken from OpenSSL apps/s_client.c.
*/
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;
}
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
imclient->tls_ctx = SSL_CTX_new(TLS_client_method());
#else
imclient->tls_ctx = SSL_CTX_new(SSLv23_client_method());
#endif
if (imclient->tls_ctx == NULL) {
return -1;
};
off |= SSL_OP_ALL; /* Work around all known bugs */
off |= SSL_OP_NO_SSLv2; /* Disable insecure SSLv2 */
off |= SSL_OP_NO_SSLv3; /* Disable insecure SSLv3 */
off |= SSL_OP_NO_COMPRESSION; /* Disable TLS compression */
SSL_CTX_set_options(imclient->tls_ctx, off);
/* debugging SSL_CTX_set_info_callback(imclient->tls_ctx, apps_ssl_info_callback); */
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, may be a cert/key mismatch]\n");
return -1;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSL_CTX_set_tmp_rsa_callback(imclient->tls_ctx, tmp_rsa_cb);
#endif
verify_depth = verifydepth;
SSL_CTX_set_verify(imclient->tls_ctx, verify_flags, verify_callback);
return 0;
}
#if 0 /* Dead code only for debugging */
static int do_dump = 1;
/*
* taken from OpenSSL crypto/bio/b_dump.c, modified to save a lot of strcpy
* and strcat by Matti Aarnio.
*/
#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'; /* start with empty string */
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;
/*
* if this is the last call then update the ddt_dump thing so that
* we will move the selection point in the debug window
*/
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);
}
/* these next two taken from OpenSSL apps/s_cb.c */
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
EXPORTED int tls_start_clienttls(struct imclient *imclient,
unsigned *layer, char **authid, int fd)
{
int sts;
SSL_SESSION *session;
const SSL_CIPHER *cipher;
X509 *peer;
int tls_cipher_usebits = 0;
int tls_cipher_algbits = 0;
char *tls_peer_CN = "";
if (!imclient->tls_conn)
imclient->tls_conn = (SSL *) SSL_new(imclient->tls_ctx);
if (!imclient->tls_conn) {
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;
}
/*
* This is the actual handshake routine. It will do all the negotiations
* and will check the client cert etc.
*/
SSL_set_connect_state(imclient->tls_conn);
/*
* We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
* Well there is a BIO below the SSL routines that is automatically
* created for us, so we can use it for debugging purposes.
*/
/* Dump the negotiation for loglevels 3 and 4 */
sts = SSL_connect(imclient->tls_conn);
if (sts <= 0) {
printf("[ SSL_connect error %d ]\n", sts); /* xxx get string error? */
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)
SSL_free(imclient->tls_conn);
imclient->tls_conn = NULL;
return -1;
}
/*
* Lets see, whether a peer certificate is availabe and what is
* the actual information. We want to save it for later use.
*/
peer = SSL_get_peer_certificate(imclient->tls_conn);
if (peer) {
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, peer_CN, CCERT_BUFSIZ);
tls_peer_CN = peer_CN;
}
cipher = SSL_get_current_cipher(imclient->tls_conn);
tls_cipher_usebits = SSL_CIPHER_get_bits(cipher,
&tls_cipher_algbits);
if (layer)
*layer = tls_cipher_usebits;
if (authid)
*authid = tls_peer_CN;
return 0;
}
#endif /* HAVE_SSL */
EXPORTED int imclient_havetls(void)
{
#ifdef HAVE_SSL
return 1;
#else
return 0;
#endif
}
#ifdef HAVE_SSL
EXPORTED int imclient_starttls(struct imclient *imclient,
char *cert_file,
char *key_file,
char *CAfile,
char *CApath)
{
int result;
struct authresult theresult;
sasl_ssf_t ssf;
char *auth_id;
imclient_send(imclient, tlsresult, (void *)&theresult,
"STARTTLS");
/* Wait for ready response or command completion */
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;
}
}
/* turn non-blocking i/o back on */
/* TLS negotiation succeeded */
imclient->tls_on = 1;
auth_id=""; /* xxx this really should be peer_CN or
issuer_CN but I can't figure out which is
which at the moment */
/* tell SASL about the negotiated layer */
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
EXPORTED int imclient_starttls(struct imclient *imclient __attribute__((unused)),
char *cert_file __attribute__((unused)),
char *key_file __attribute__((unused)),
char *CAfile __attribute__((unused)),
char *CApath __attribute__((unused)))
{
printf("[ TLS support not present (imclient_starttls) ]\n");
return 1;
}
#endif /* HAVE_SSL */