/* imtest.c -- IMAP/POP3/NNTP/LMTP/SMTP/MUPDATE/MANAGESIEVE test client
* Ken Murchison (multi-protocol implementation)
* Tim Martin (SASL implementation)
*
* 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 <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <ctype.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "hash.h"
#include "imparse.h"
#include "iptostring.h"
#include "md5.h"
#include "prot.h"
#include "retry.h"
#include "strarray.h"
#include "stristr.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcat.h"
#include "xstrlcpy.h"
#ifdef HAVE_SSL
#include <openssl/ssl.h>
static SSL_CTX *tls_ctx = NULL;
static SSL *tls_conn = NULL;
static SSL_SESSION *tls_sess = NULL;
#endif /* HAVE_SSL */
#define IMTEST_OK 0
#define IMTEST_FAIL -1
#define IMTEST_CLOSEME -2
typedef enum {
STAT_CONT = 0,
STAT_NO = 1,
STAT_OK = 2
} imt_stat;
/* global vars */
static sasl_conn_t *conn;
static int sock; /* socket descriptor */
static int verbose=0;
static struct protstream *pout, *pin;
static char *authname = NULL;
static char *username = NULL;
static char *realm = NULL;
static char *cmdline_password = NULL;
static char *output_socket = NULL;
static int output_socket_opened = 0;
static ino_t output_socket_ino = 0;
#define CONFIGHASHSIZE 30 /* relatively small */
static struct hash_table confighash;
static int mysasl_config(void*, const char*, const char*, const char**, unsigned*);
extern int _sasl_debug;
extern char *optarg;
static strarray_t stashed_strings = STRARRAY_INITIALIZER;
/* callbacks we support */
static sasl_callback_t callbacks[] = {
{
SASL_CB_ECHOPROMPT, NULL, NULL
}, {
SASL_CB_NOECHOPROMPT, NULL, NULL
}, {
#ifdef SASL_CB_GETREALM
SASL_CB_GETREALM, NULL, NULL
}, {
#endif
SASL_CB_USER, NULL, NULL
}, {
SASL_CB_AUTHNAME, NULL, NULL
}, {
SASL_CB_PASS, NULL, NULL
}, {
SASL_CB_GETOPT, (int (*)(void))&mysasl_config, NULL
}, {
SASL_CB_LIST_END, NULL, NULL
}
};
struct protocol_t;
struct banner_t {
u_char is_capa; /* banner is capability response */
char *resp; /* end of banner response */
void *(*parse_banner)(char *str);
/* [OPTIONAL] parse banner, returns 'rock' */
};
struct capa_cmd_t {
char *cmd; /* capability command string (NULL = no capa cmd) */
char *resp; /* end of capability response */
char *tls; /* [OPTIONAL] TLS capability string */
char *login; /* [OPTIONAL] plaintext login cmd capability string */
char *auth; /* [OPTIONAL] AUTH (SASL) capability string */
char *compress; /* [OPTIONAL] COMPRESS capability string */
char *(*parse_mechlist)(const char *str, struct protocol_t *prot);
/* [OPTIONAL] parse capability string,
returns space-separated list of mechs */
};
struct tls_cmd_t {
char *cmd; /* tls command string */
char *ok; /* start tls prompt */
char *fail; /* failure response */
u_char auto_capa; /* capability response sent automatically after TLS */
};
struct sasl_cmd_t {
char *cmd; /* auth command string */
u_short maxlen; /* maximum command line length,
(0 = initial response unsupported by protocol) */
u_char quote; /* quote arguments (literal for base64 data) */
char *ok; /* success response string */
char *fail; /* failure response string */
char *cont; /* continue response string
(NULL = send/receive literals) */
char *cancel; /* cancel auth string */
char *(*parse_success)(char *str);
/* [OPTIONAL] parse response for success data */
u_char auto_capa; /* capability response sent automatically
after AUTH with SASL security layer */
};
struct compress_cmd_t {
char *cmd; /* compress command string */
char *ok; /* success response string */
char *fail; /* failure response string */
};
struct logout_cmd_t {
char *cmd; /* logout command string */
char *resp; /* logout response */
};
struct protocol_t {
char *protocol; /* protocol service name */
char *sprotocol; /* SSL-wrapped service name (NULL = unsupported) */
char *service; /* SASL service name */
int login_enabled; /* [OPTIONAL] login command on/off by default;
toggled by capability string */
struct banner_t banner;
struct capa_cmd_t capa_cmd;
struct tls_cmd_t tls_cmd;
struct sasl_cmd_t sasl_cmd;
struct compress_cmd_t compress_cmd;
int (*do_auth)(struct sasl_cmd_t *sasl_cmd, void *rock,
int login_enabled, char *mech, char *mechlist);
/* [OPTIONAL] perform protocol-specific authentication;
based on rock, login_enabled, mech, mechlist */
struct logout_cmd_t logout_cmd;
/* these 3 are used for maintaining connection state */
void *(*init_conn)(void); /* generate a context (if needed). This context
* must be malloc()ed and will be freed by
* interactive() as each connection is reused */
int (*pipe)(char *buf, int len, void *rock); /* pipe a buffer to pout
* may be necessary to keep
* connection state */
int (*reset)(void *rock); /* perform any protocol-specific reset when we
* lose connection on a unix domain socket
* during interactive mode. If this is NULL we
* assume that we should not attempt to reuse
* connections (and just die at the end of one)
*/
};
static void imtest_fatal(const char *msg, ...) __attribute__((noreturn));
static void imtest_fatal(const char *msg, ...)
{
struct stat sbuf;
if (output_socket && output_socket_opened &&
stat(output_socket, &sbuf) != -1 &&
sbuf.st_ino == output_socket_ino) {
unlink(output_socket);
}
if (msg != NULL) {
va_list ap;
va_start(ap, msg);
fprintf(stderr, "failure: ");
vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
va_end(ap);
}
exit(1);
}
/* libcyrus makes us define this */
EXPORTED void fatal(const char *msg, int code __attribute__((unused)))
{
imtest_fatal(msg);
}
int mysasl_config(void *context __attribute__((unused)),
const char *plugin_name,
const char *option,
const char **result,
unsigned *len)
{
*result = NULL;
if (plugin_name) {
/* first try it with the plugin name */
char opt[1024];
strlcpy(opt, plugin_name, sizeof(opt));
strlcat(opt, "_", sizeof(opt));
strlcat(opt, option, sizeof(opt));
*result = hash_lookup(opt, &confighash);
}
if (*result == NULL) {
/* try without the plugin name */
*result = hash_lookup(option, &confighash);
}
if (*result != NULL) {
if (len) { *len = strlen(*result); }
return SASL_OK;
}
return SASL_FAIL;
}
#ifdef HAVE_SSL
static int verify_depth;
static int verify_error = X509_V_OK;
static int do_dump = 0;
#define CCERT_BUFSIZ 256
static char peer_CN[CCERT_BUFSIZ];
static char issuer_CN[CCERT_BUFSIZ];
static char *tls_peer_CN = NULL;
static char *tls_issuer_CN = NULL;
static const char *tls_protocol = NULL;
static const char *tls_cipher_name = NULL;
static int tls_cipher_usebits = 0;
static int tls_cipher_algbits = 0;
/*
* 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, 256);
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, 256);
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
/* taken from OpenSSL apps/s_cb.c
* tim - this seems to just be giving logging messages
*/
static void apps_ssl_info_callback(const SSL * s, int where, int ret)
{
char *str;
int w;
if (verbose==0) return;
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);
}
}
}
/*
* 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
}
static char *var_tls_CAfile="";
static char *var_tls_CApath="";
/*
* 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(int verifydepth, char *var_tls_cert_file, char *var_tls_key_file)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
char *CApath;
char *CAfile;
char *c_cert_file;
char *c_key_file;
if (verbose==1)
printf("starting TLS engine\n");
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
if (tls_rand_init() == -1) {
printf("TLS engine: cannot seed PRNG\n");
return IMTEST_FAIL;
}
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
tls_ctx = SSL_CTX_new(TLS_client_method());
#else
tls_ctx = SSL_CTX_new(SSLv23_client_method());
#endif
if (tls_ctx == NULL) {
return IMTEST_FAIL;
};
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(tls_ctx, off);
SSL_CTX_set_info_callback(tls_ctx, apps_ssl_info_callback);
if (strlen(var_tls_CAfile) == 0)
CAfile = NULL;
else
CAfile = var_tls_CAfile;
if (strlen(var_tls_CApath) == 0)
CApath = NULL;
else
CApath = var_tls_CApath;
if (CAfile || CApath)
if ((!SSL_CTX_load_verify_locations(tls_ctx, CAfile, CApath)) ||
(!SSL_CTX_set_default_verify_paths(tls_ctx))) {
printf("TLS engine: cannot load CA data\n");
return IMTEST_FAIL;
}
if (strlen(var_tls_cert_file) == 0)
c_cert_file = NULL;
else
c_cert_file = var_tls_cert_file;
if (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(tls_ctx, c_cert_file, c_key_file)) {
printf("TLS engine: cannot load cert/key data, may be a cert/key mismatch?\n");
return IMTEST_FAIL;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSL_CTX_set_tmp_rsa_callback(tls_ctx, tmp_rsa_cb);
#endif
verify_depth = verifydepth;
SSL_CTX_set_verify(tls_ctx, verify_flags, verify_callback);
return IMTEST_OK;
}
/*
* 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", ch, j == 7 ? '|' : ' ');
ss += 3;
}
}
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);
}
/* 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 %08lX [%08lX] (%d bytes => %ld (0x%lX))\n",
(unsigned long) bio, (unsigned long) argp,
argi, ret, ret);
tls_dump(argp, (int) ret);
return (ret);
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
printf("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))\n",
(unsigned long) bio, (unsigned long) argp,
argi, ret, ret);
tls_dump(argp, (int) ret);
}
return (ret);
}
static int tls_start_clienttls(unsigned *layer, char **authid)
{
int sts;
const SSL_CIPHER *cipher;
X509 *peer;
if (verbose==1)
printf("setting up TLS connection\n");
if (tls_conn == NULL) {
tls_conn = (SSL *) SSL_new(tls_ctx);
}
if (tls_conn == NULL) {
printf("Could not allocate 'con' with SSL_new()\n");
return IMTEST_FAIL;
}
SSL_clear(tls_conn);
if (!SSL_set_fd(tls_conn, sock)) {
printf("SSL_set_fd failed\n");
return IMTEST_FAIL;
}
/*
* This is the actual handshake routine. It will do all the negotiations
* and will check the client cert etc.
*/
SSL_set_connect_state(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.
*/
if (verbose==1)
BIO_set_callback(SSL_get_rbio(tls_conn), bio_dump_cb);
/* Dump the negotiation for loglevels 3 and 4 */
if (verbose==1)
do_dump = 1;
if (tls_sess) /* Reuse a session if we have one */
SSL_set_session(tls_conn, tls_sess);
if ((sts = SSL_connect(tls_conn)) <= 0) {
printf("SSL_connect error %d\n", sts);
tls_sess = SSL_get_session(tls_conn);
if (tls_sess) {
SSL_CTX_remove_session(tls_ctx, tls_sess);
tls_sess = NULL;
printf("SSL session removed\n");
}
if (tls_conn!=NULL)
SSL_free(tls_conn);
tls_conn = NULL;
return IMTEST_FAIL;
}
/*
* Lets see, whether a peer certificate is available and what is
* the actual information. We want to save it for later use.
*/
peer = SSL_get_peer_certificate(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);
if (verbose==1)
printf("subject_CN=%s, issuer_CN=%s\n", peer_CN, issuer_CN);
tls_issuer_CN = issuer_CN;
}
tls_protocol = SSL_get_version(tls_conn);
cipher = SSL_get_current_cipher(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;
printf("TLS connection established: %s with cipher %s (%d/%d bits)\n",
tls_protocol, tls_cipher_name,
tls_cipher_usebits, tls_cipher_algbits);
return IMTEST_OK;
}
static void do_starttls(int ssl, char *keyfile, unsigned *ssf)
{
int result;
char *auth_id;
result=tls_init_clientengine(10, keyfile, keyfile);
if (result!=IMTEST_OK)
{
if (ssl) {
imtest_fatal("Start TLS engine failed\n");
} else {
printf("Start TLS engine failed\n");
return;
}
} else {
result=tls_start_clienttls(ssf, &auth_id);
if (result!=IMTEST_OK)
imtest_fatal("TLS negotiation failed!\n");
}
/* TLS negotiation suceeded */
tls_sess = SSL_get_session(tls_conn); /* Save the session for reuse */
/* tell SASL about the negotiated layer */
result=sasl_setprop(conn,
SASL_SSF_EXTERNAL,
ssf);
if (result!=SASL_OK)
imtest_fatal("Error setting SASL property (external ssf)");
result=sasl_setprop(conn,
SASL_AUTH_EXTERNAL,
auth_id);
if (result!=SASL_OK)
imtest_fatal("Error setting SASL property (external auth_id)");
prot_settls (pin, tls_conn);
prot_settls (pout, tls_conn);
}
#endif /* HAVE_SSL */
static sasl_security_properties_t *make_secprops(int min,int max)
{
sasl_security_properties_t *ret=(sasl_security_properties_t *)
malloc(sizeof(sasl_security_properties_t));
ret->maxbufsize=1024;
ret->min_ssf=min;
ret->max_ssf=max;
ret->security_flags=0;
ret->property_names=NULL;
ret->property_values=NULL;
return ret;
}
/*
* Initialize SASL and set necessary options
*/
static int init_sasl(char *service, char *serverFQDN, int minssf, int maxssf,
unsigned flags)
{
int saslresult;
sasl_security_properties_t *secprops=NULL;
socklen_t addrsize;
char localip[60], remoteip[60];
struct sockaddr_storage saddr_l;
struct sockaddr_storage saddr_r;
addrsize=sizeof(struct sockaddr_storage);
if (getpeername(sock,(struct sockaddr *)&saddr_r,&addrsize)!=0)
return IMTEST_FAIL;
addrsize=sizeof(struct sockaddr_storage);
if (getsockname(sock,(struct sockaddr *)&saddr_l,&addrsize)!=0)
return IMTEST_FAIL;
if(iptostring((struct sockaddr *)&saddr_l, addrsize, localip, 60))
return IMTEST_FAIL;
if(iptostring((struct sockaddr *)&saddr_r, addrsize, remoteip, 60))
return IMTEST_FAIL;
/* client new connection */
saslresult=sasl_client_new(service,
serverFQDN,
localip,
remoteip,
NULL,
flags,
&conn);
if (saslresult!=SASL_OK) return IMTEST_FAIL;
/* create a security structure and give it to sasl */
secprops = make_secprops(minssf, maxssf);
if (secprops != NULL)
{
sasl_setprop(conn, SASL_SEC_PROPS, secprops);
free(secprops);
}
return IMTEST_OK;
}
#define BUFSIZE 16384
static imt_stat getauthline(struct sasl_cmd_t *sasl_cmd, char **line, int *linelen)
{
char buf[BUFSIZE];
int saslresult;
unsigned len;
char *str=(char *) buf;
int ret = STAT_CONT;
*line = NULL;
*linelen = 0;
do {
str = prot_fgets(str, BUFSIZE, pin);
if (str == NULL) imtest_fatal("prot layer failure");
printf("S: %s",str);
} while(str[0] == '*'); /* Ignore potential untagged responses */
if (!strncasecmp(str, sasl_cmd->ok, strlen(sasl_cmd->ok))) {
if (sasl_cmd->parse_success) {
str = sasl_cmd->parse_success(str);
if (!str) return STAT_OK;
ret = STAT_OK;
}
else {
return STAT_OK;
}
}
else if (!strncasecmp(str, sasl_cmd->fail, strlen(sasl_cmd->fail))) {
return STAT_NO;
}
else if (sasl_cmd->cont) {
str += strlen(sasl_cmd->cont); /* jump past the continuation */
}
else {
/* literal */
len = atoi(str+1);
str = prot_fgets(str, BUFSIZE, pin);
if (str == NULL || strlen(str) < len)
imtest_fatal("prot layer failure");
printf("S: %s", str);
}
if (*str != '\r') {
/* trim CRLF */
char *p = str + strlen(str) - 1;
if (p >= str && *p == '\n') *p-- = '\0';
if (p >= str && *p == '\r') *p-- = '\0';
/* alloc space for decoded response */
len = strlen(str) + 1;
*line = malloc(len);
if ((*line) == NULL) {
return STAT_NO;
}
/* decode this line */
saslresult = sasl_decode64(str, strlen(str),
*line, len, (unsigned *) linelen);
if (saslresult != SASL_OK) {
printf("base64 decoding error\n");
return STAT_NO;
}
} else {
/* this is a blank */
*line = NULL;
*linelen = 0;
}
return ret;
}
static void interaction (int id, const char *challenge, const char *prompt,
char **tresult, unsigned int *tlen)
{
char *s;
char result[1024];
if (id==SASL_CB_PASS && !cmdline_password) {
printf("%s: ", prompt);
s = xstrdup(cyrus_getpass(""));
strarray_appendm(&stashed_strings, s);
*tlen = strlen(s);
*tresult = s;
return;
} else if (id==SASL_CB_PASS && cmdline_password) {
strcpy(result, cmdline_password);
} else if (id==SASL_CB_USER) {
if (username != NULL) {
strcpy(result, username);
} else {
strcpy(result, "");
}
} else if (id==SASL_CB_AUTHNAME) {
if (authname != NULL) {
strcpy(result, authname);
} else {
strcpy(result, getpwuid(getuid())->pw_name);
}
#ifdef SASL_CB_GETREALM
} else if ((id==SASL_CB_GETREALM) && (realm != NULL)) {
strcpy(result, realm);
#endif
} else {
int c;
if (((id==SASL_CB_ECHOPROMPT) || (id==SASL_CB_NOECHOPROMPT)) &&
(challenge != NULL)) {
printf("Server challenge: %s\n", challenge);
}
printf("%s: ",prompt);
if (id==SASL_CB_NOECHOPROMPT) {
strcpy(result, cyrus_getpass(""));
} else {
result[0] = '\0';
if (fgets(result, sizeof(result) - 1, stdin) != NULL) {
c = strlen(result);
result[c - 1] = '\0';
}
}
}
s = xstrdup(result);
strarray_appendm(&stashed_strings, s);
*tlen = strlen(s);
*tresult = s;
}
static void fillin_interactions(sasl_interact_t *tlist)
{
while (tlist->id!=SASL_CB_LIST_END)
{
interaction(tlist->id, tlist->challenge, tlist->prompt,
(void *) &(tlist->result),
&(tlist->len));
tlist++;
}
}
static char *waitfor(char *tag, char *tag2, int echo)
{
static char str[1024];
do {
if (prot_fgets(str, sizeof(str), pin) == NULL) {
imtest_fatal("prot layer failure");
}
if(echo) printf("S: %s", str);
} while (strncmp(str, tag, strlen(tag)) &&
(tag2 ? strncmp(str, tag2, strlen(tag2)) : 1));
return str;
}
static int auth_sasl(struct sasl_cmd_t *sasl_cmd, char *mechlist)
{
sasl_interact_t *client_interact = NULL;
int saslresult;
const char *out = NULL;
unsigned int outlen = 0;
char *in;
int inlen;
const char *mechusing;
char inbase64[4096];
int inbase64len;
char cmdbuf[40];
int sendliteral;
int initial_response = 1;
imt_stat status;
if (!sasl_cmd || !sasl_cmd->cmd) return IMTEST_FAIL;
sendliteral = sasl_cmd->quote;
do { /* start authentication */
saslresult = sasl_client_start(conn, mechlist, &client_interact,
/* do we support initial response? */
sasl_cmd->maxlen ? &out : NULL,
&outlen, &mechusing);
if (saslresult == SASL_INTERACT)
fillin_interactions(client_interact); /* fill in prompts */
} while (saslresult == SASL_INTERACT);
if ((saslresult != SASL_OK) && (saslresult != SASL_CONTINUE)) {
return saslresult;
}
/* build the auth command */
if (sasl_cmd->quote) {
sprintf(cmdbuf, "%s \"%s\"", sasl_cmd->cmd, mechusing);
}
else {
sprintf(cmdbuf, "%s %s", sasl_cmd->cmd, mechusing);
}
printf("C: %s", cmdbuf);
prot_printf(pout, "%s", cmdbuf);
if (out) { /* initial response */
if (!outlen) { /* empty initial response */
printf(" =");
prot_printf(pout, " =");
out = NULL;
}
else if (!sendliteral &&
((int) (strlen(cmdbuf) + outlen + 3) > sasl_cmd->maxlen)) {
/* initial response is too long for auth command,
so wait for a server challenge before sending it */
goto noinitresp;
}
else { /* full response -- encoded below */
printf(" ");
prot_printf(pout, " ");
}
} else {
goto noinitresp;
}
do {
if (out) { /* response */
/* convert to base64 */
saslresult = sasl_encode64(out, outlen, inbase64, sizeof(inbase64),
(unsigned *) &inbase64len);
if (saslresult != SASL_OK) return saslresult;
/* send to server */
if (sendliteral) {
printf("%s{%d+}\r\n",
initial_response ? "" : "C: ", inbase64len);
prot_printf(pout, "{%d+}\r\n", inbase64len);
prot_flush(pout);
}
printf("%s%s", initial_response ? "" : "C: ", inbase64);
prot_write(pout, inbase64, inbase64len);
out = NULL;
} else if (sendliteral) {
/* If we had no response, we still need to send the
empty literal in this case */
printf("{0+}\r\nC: ");
prot_printf(pout, "{0+}\r\n");
} else if (!initial_response) {
printf("C: ");
}
noinitresp:
initial_response = 0;
printf("\r\n");
prot_printf(pout, "\r\n");
prot_flush(pout);
/* get challenge/reply from the server */
status = getauthline(sasl_cmd, &in, &inlen);
if ((status == STAT_CONT || (status == STAT_OK && in)) &&
(inlen || !out)) { /* no delayed initial response */
do { /* do the next step */
saslresult = sasl_client_step(conn, in, inlen,
&client_interact,
&out, &outlen);
if (saslresult == SASL_INTERACT)
fillin_interactions(client_interact); /* fill in prompts */
} while (saslresult == SASL_INTERACT);
if (in) free(in);
}
if ((saslresult != SASL_OK) && (saslresult != SASL_CONTINUE)) {
/* cancel the exchange */
printf("C: %s\r\n", sasl_cmd->cancel);
prot_printf(pout, "%s\r\n", sasl_cmd->cancel);
prot_flush(pout);
return saslresult;
}
sendliteral = !sasl_cmd->cont;
} while (status == STAT_CONT);
return (status == STAT_OK) ? IMTEST_OK : IMTEST_FAIL;
}
/* initialize the network */
static int init_net(char *serverFQDN, char *port)
{
struct addrinfo hints, *res0 = NULL, *res;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
if ((err = getaddrinfo(serverFQDN, port, &hints, &res0)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
return IMTEST_FAIL;
}
if (res0->ai_canonname)
strncpy(serverFQDN, res0->ai_canonname, 1023);
for (res = res0; res; res = res->ai_next) {
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock < 0)
continue;
if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
break;
close(sock);
sock = -1;
}
freeaddrinfo(res0);
if(sock < 0) {
perror("connect");
return IMTEST_FAIL;
}
return IMTEST_OK;
}
static void logout(struct logout_cmd_t *logout_cmd, int wait)
{
printf("C: %s\r\n", logout_cmd->cmd);
prot_printf(pout, "%s\r\n", logout_cmd->cmd);
prot_flush(pout);
/* only wait if we are explicitly told to */
if(wait) waitfor(logout_cmd->resp, NULL, 1);
}
static int gotsigint = 0;
static void sigint_handler(int sig __attribute__((unused)))
{
gotsigint = 1;
}
static int haveinput(struct protstream *s)
{
/* Is something currently pending in our protstream's buffer? */
#ifdef HAVE_SSL
if (s->cnt == 0 && s->tls_conn != NULL) {
/* Maybe there's data pending in the SSL buffer? */
int n = SSL_pending(s->tls_conn);
if (verbose) printf("SSL_pending=%d\n", n);
return n;
}
#endif
return s->cnt;
}
/* This needs to support 3 modes:
*
* 1. Terminal Interface Only
* 2. File input
* 3. Redirect to a unix socket - This mode needs to be sure that the
* IMAP session is in an unselected state whenever the unix socket is
* disconnected.
*
*/
static void interactive(struct protocol_t *protocol, char *filename)
{
char buf[2048];
fd_set read_set, rset;
fd_set write_set, wset;
fd_set accept_set, aset;
int nfds;
int nfound;
int count;
int fd = 0, fd_out = 1, listen_sock = -1;
void *rock = NULL;
int donewritingfile = 0;
struct sockaddr_un sunsock;
int salen;
/* open the file if available */
if (filename != NULL) {
if ((fd = open(filename, O_RDONLY)) == -1) {
fprintf(stderr,"Unable to open file: %s:", filename);
perror("");
exit(1);
}
} else if(output_socket) {
struct timeval tv;
struct stat sbuf;
/* can't have this and a file for input */
sunsock.sun_family = AF_UNIX;
strlcpy(sunsock.sun_path, output_socket, sizeof(sunsock.sun_path));
unlink(output_socket);
listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if(listen_sock < 0) imtest_fatal("could not create output socket");
salen = sizeof(sunsock.sun_family) + strlen(sunsock.sun_path) + 1;
if((bind(listen_sock, (struct sockaddr *)&sunsock, salen)) < 0) {
imtest_fatal("could not bind output socket");
}
if((listen(listen_sock, 5)) < 0) {
imtest_fatal("could not listen to output socket");
}
if(stat(output_socket, &sbuf) == -1) {
imtest_fatal("could not stat output socket");
}
output_socket_opened = 1;
output_socket_ino = sbuf.st_ino;
FD_ZERO(&accept_set);
FD_SET(listen_sock, &accept_set);
accept_again:
if(rock) {
free(rock);
rock = NULL;
}
tv.tv_sec = 600; /* 10 minute timeout - xxx protocol specific? */
tv.tv_usec = 0;
aset = accept_set;
/* Have the separate select so that signals will wake us up
* and we get a timeout to use on our own imap connection */
if(select(listen_sock + 1, &aset, NULL, NULL, &tv) <= 0) {
/* either we timed out or had an error */
goto cleanup;
}
fd = fd_out = accept(listen_sock, NULL, NULL);
if(fd < 0) imtest_fatal("accept failure");
if(protocol->init_conn) rock = protocol->init_conn();
}
FD_ZERO(&read_set);
FD_SET(fd, &read_set); /* In the terminal case fd == 0 */
FD_SET(sock, &read_set);
FD_ZERO(&write_set);
FD_SET(fd_out, &write_set);
FD_SET(sock, &write_set);
nfds = fd;
if (nfds < sock) nfds = sock;
if (nfds < fd_out) nfds = fd_out;
nfds++;
if (filename != NULL) {
donewritingfile = 0;
}
/* add handler for SIGINT */
signal(SIGINT, sigint_handler);
/* loop reading from network and from stdin as applicable */
while (1) {
rset = read_set;
wset = write_set;
nfound = select(nfds, &rset, &wset, NULL, NULL);
if (nfound < 0) {
perror("select");
imtest_fatal("select");
}
if (!output_socket &&
(FD_ISSET(0, &rset)) && (FD_ISSET(sock, &wset))) {
/* There is explicit terminal input -- note this is only possible
* if fd is 0 (and we are in terminal mode!).
* We need to use stream API for this, which is why it
* is different */
if (fgets(buf, sizeof (buf) - 1, stdin) == NULL) {
logout(&protocol->logout_cmd, 0);
FD_CLR(0, &read_set);
} else {
count = strlen(buf);
/* If we read a full line, translate the newline
* if necessary. */
if (buf[count - 1] == '\n' && (count < 2 || buf[count - 2] != '\r')) {
buf[count - 1] = '\r';
buf[count] = '\n';
buf[count + 1] = '\0';
count++;
}
prot_write(pout, buf, count);
}
prot_flush(pout);
} else if (FD_ISSET(sock, &rset) && (FD_ISSET(fd_out, &wset))) {
/* This does input from remote for all modes */
do {
count = prot_read(pin, buf, sizeof (buf) - 1);
if (count == 0) {
const char *str = prot_error(pin);
if (str && strcmp(str, PROT_EOF_STRING)) {
printf("Protection error: %s\n", prot_error(pin));
}
close(sock);
printf("Connection closed.\n");
return;
}
if (count < 0) {
perror("read");
imtest_fatal("prot_read");
}
if(output_socket)
retry_write(fd_out, buf, count);
else {
/* use the stream API */
buf[count] = '\0';
printf("%s", buf);
}
} while (haveinput(pin) > 0);
} else if ((FD_ISSET(fd, &rset)) && (FD_ISSET(sock, &wset))
&& (donewritingfile == 0)) {
/* This does input for both socket and file modes */
int numr = read(fd, buf, sizeof(buf));
/* and send out over wire */
if (numr < 0)
{
perror("read");
imtest_fatal("read");
} else if (numr==0) {
if(output_socket) {
if(protocol->reset) {
if(protocol->reset(rock) != IMTEST_OK)
goto cleanup;
} else
/* no protocol->reset, we're done */
goto cleanup;
close(fd);
fd = 0;
fd_out = 1;
goto accept_again;
} else {
/* we're done, cleanup */
donewritingfile = 1;
FD_CLR(fd,&read_set);
/* send LOGOUT */
logout(&protocol->logout_cmd, 0);
}
} else {
if (!output_socket) {
/* echo for the user - if not in socket mode */
retry_write(1, buf, numr);
}
if (output_socket && protocol->pipe) {
if(protocol->pipe(buf, numr, rock) == IMTEST_CLOSEME) {
if(protocol->reset) {
if(protocol->reset(rock) != IMTEST_OK)
goto cleanup;
} else
/* no protocol->reset, we're done */
goto cleanup;
close(fd);
fd = 0;
fd_out = 1;
goto accept_again;
}
} else {
/* echo to remote */
prot_write(pout, buf, numr);
prot_flush(pout);
}
}
} else {
/* if can't do anything else sleep */
usleep(1000);
}
/* received interrupt signal, logout */
if (gotsigint) goto cleanup;
}
cleanup:
if(rock) free(rock);
if(output_socket && output_socket_opened) {
struct stat sbuf;
close(fd);
if (listen_sock != -1) close(listen_sock);
if(stat(output_socket, &sbuf) != -1
&& sbuf.st_ino == output_socket_ino) {
unlink(output_socket);
}
}
logout(&protocol->logout_cmd, 0);
close(sock);
printf("Connection closed.\n");
/* remove handler for SIGINT */
signal(SIGINT, SIG_DFL);
return;
}
enum {
AUTO_BANNER = -1,
AUTO_NO = 0,
AUTO_YES = 1
};
enum {
CAPA_LOGIN = (1 << 0),
CAPA_STARTTLS = (1 << 1),
CAPA_COMPRESS = (1 << 2)
};
static char *ask_capability(struct protocol_t *prot,
unsigned long *capabilities, int automatic)
{
char str[1024] = "";
char *ret = NULL, *tmp, *resp;
/* default state of login command unless toggled by capabilities */
*capabilities = prot->login_enabled;
resp = (automatic == AUTO_BANNER) ? prot->banner.resp : prot->capa_cmd.resp;
if (!automatic) {
/* no capability command */
if (!prot->capa_cmd.cmd) return NULL;
/* request capabilities of server */
printf("C: %s\r\n", prot->capa_cmd.cmd);
prot_printf(pout, "%s\r\n", prot->capa_cmd.cmd);
prot_flush(pout);
}
do { /* look for the end of the capabilities */
if (prot_fgets(str, sizeof(str), pin) == NULL) {
if (!*str) imtest_fatal("prot layer failure");
else break;
}
printf("S: %s", str);
/* check for login - toggles existing state */
if (prot->capa_cmd.login &&
strstr(str, prot->capa_cmd.login) != NULL) {
*capabilities ^= CAPA_LOGIN;
}
/* check for starttls */
if (prot->capa_cmd.tls &&
strstr(str, prot->capa_cmd.tls) != NULL) {
*capabilities |= CAPA_STARTTLS;
}
/* check for compress */
if (prot->capa_cmd.compress &&
strstr(str, prot->capa_cmd.compress) != NULL) {
*capabilities |= CAPA_COMPRESS;
}
/* check for auth */
if (prot->capa_cmd.auth &&
(tmp = strstr(str, prot->capa_cmd.auth)) != NULL) {
if (ret) free(ret);
if (prot->capa_cmd.parse_mechlist)
ret = prot->capa_cmd.parse_mechlist(str, prot);
else
ret = strdup(tmp+strlen(prot->capa_cmd.auth));
}
if (!resp) {
/* multiline response with no distinct end (IMAP banner) */
prot_NONBLOCK(pin);
}
/* look for the end of the capabilities */
} while (!resp || strncasecmp(str, resp, strlen(resp)));
prot_BLOCK(pin);
return ret;
}
/* generic pipe functionality - break it into one line at a time, and
* pass that into a per-protocol pipe function. */
struct generic_context_t
{
int (*pipe_oneline)(char *buf, int len, void *rock);
void *rock;
/* Deal with half-finished lines */
char *midLine;
size_t midLineLen;
};
static int generic_pipe(char *buf, int len, void *rock)
{
struct generic_context_t *text = (struct generic_context_t *)rock;
char *toWrite = NULL, *toSend = NULL;
int toWriteLen = 0;
char *lineEnd = NULL;
int ret = IMTEST_OK;
/* do we have leftovers? -- if so, we append the new stuff */
if(text->midLine) {
text->midLine =
(char *)xrealloc(text->midLine, text->midLineLen+len+1);
memcpy(text->midLine+text->midLineLen, buf, len);
text->midLineLen += len;
text->midLine[text->midLineLen] = '\0';
toWrite = text->midLine;
toWriteLen = text->midLineLen;
} else {
toWrite = buf;
toWriteLen = len;
}
/* one line at a time now */
while(toWrite && (lineEnd = memchr(toWrite, '\n', toWriteLen)) != NULL) {
size_t len_todo;
len_todo = lineEnd - toWrite + 1; /* +1 is to include the newline! */
toSend = (char *)xrealloc(toSend, len_todo + 1);
memcpy(toSend, toWrite, len_todo);
toSend[len_todo] = '\0';
ret = text->pipe_oneline(toSend, len_todo, text->rock);
if(ret != IMTEST_OK) break;
toWrite = lineEnd+1; /* +1 is to skip the newline! */
toWriteLen -= len_todo;
if(toWriteLen <= 0) toWrite = NULL;
}
if(toWrite && ret == IMTEST_OK) {
char *newMidLine;
/* we need to save the leftover for next time */
newMidLine = (char *)xmalloc(toWriteLen);
memcpy(newMidLine, toWrite, toWriteLen);
if(text->midLine) free(text->midLine);
text->midLine = newMidLine;
text->midLineLen = toWriteLen;
} else if (text->midLine || ret != IMTEST_OK) {
free(text->midLine);
text->midLine = NULL;
text->midLineLen = 0;
}
free(toSend);
return ret;
}
/*********************************** IMAP ************************************/
/*
* Parse a mech list of the form: ... AUTH=foo AUTH=bar ...
*
* Return: string with mechs separated by spaces
*
*/
static char *imap_parse_mechlist(const char *str, struct protocol_t *prot)
{
char *ret = xzmalloc(strlen(str)+1);
char *tmp;
int num = 0;
if (strstr(str, " SASL-IR")) {
/* server supports initial response in AUTHENTICATE command */
prot->sasl_cmd.maxlen = USHRT_MAX;
}
while ((tmp = strstr(str, " AUTH="))) {
char *end = (tmp += 6);
while((*end != ' ') && (*end != '\0')) end++;
/* add entry to list */
if (num++ > 0) strcat(ret, " ");
strlcat(ret, tmp, strlen(ret) + (end - tmp) + 1);
/* reset the string */
str = end;
}
return ret;
}
static int auth_imap(void)
{
char str[1024];
/* we need username and password to do "login" */
char *username;
unsigned int userlen;
char *pass;
unsigned int passlen;
char *tag = "L01 ";
str[0] = '\0';
interaction(SASL_CB_AUTHNAME, NULL, "Authname", &username, &userlen);
interaction(SASL_CB_PASS, NULL, "Please enter your password",
&pass, &passlen);
printf("C: %sLOGIN %s {%d}\r\n", tag, username, passlen);
prot_printf(pout,"%sLOGIN %s {%d}\r\n", tag, username, passlen);
prot_flush(pout);
if (!strncmp(waitfor("+", tag, 1), "+", 1)) {
printf("C: <omitted>\r\n");
prot_printf(pout,"%s\r\n", pass);
prot_flush(pout);
do {
if (prot_fgets(str, sizeof(str), pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
} while (strncmp(str, tag, strlen(tag)));
}
if (!strncasecmp(str+strlen(tag), "OK", 2)) {
return IMTEST_OK;
} else {
return IMTEST_FAIL;
}
}
static int imap_do_auth(struct sasl_cmd_t *sasl_cmd,
void *rock __attribute__((unused)),
int login_enabled,
char *mech, char *mechlist)
{
int result = IMTEST_FAIL;
if (mech) {
if (!strcasecmp(mech, "login")) {
if (!login_enabled) {
printf("[Server advertised LOGINDISABLED]\n");
} else {
result = auth_imap();
}
} else if (!mechlist || !stristr(mechlist, mech)) {
printf("[Server did not advertise AUTH=%s]\n", ucase(mech));
} else {
result = auth_sasl(sasl_cmd, mech);
}
} else {
if (mechlist) {
result = auth_sasl(sasl_cmd, mechlist);
} else if (login_enabled) {
result = auth_imap();
}
}
return result;
}
struct imap_context_t
{
int inLiteral;
};
static int imap_pipe_oneline(char *buf, int len, void *rock) {
struct imap_context_t *text = (struct imap_context_t *)rock;
int add_crlf = 0; /* hack for terminals */
if(text->inLiteral) {
if(len <= text->inLiteral) {
text->inLiteral -= len;
} else {
prot_write(pout, buf, text->inLiteral);
buf += text->inLiteral;
len -= text->inLiteral;
text->inLiteral = 0;
}
}
if(!text->inLiteral) {
char c, *tag, *cmd, *tmp, *sparebuf = (char *)xstrdup(buf);
int i;
tmp = sparebuf;
if(len > 4 &&
buf[len-1] == '\n' && buf[len-1] == '\r' && buf[len-2] == '}') {
/* possible literal, with \r */
i = len-4;
while(i > 0 && buf[i] != '{' && Uisdigit(buf[i])) i--;
if(buf[i] == '{') text->inLiteral = atoi(buf + i + 1);
} else if(len > 3 && buf[len-1] == '\n' && buf[len-2] == '}') {
/* possible literal, no \r -- hack for terminals*/
i = len-3;
while(i > 0 && buf[i] != '{' && Uisdigit(buf[i])) i--;
if(buf[i] == '{') text->inLiteral = atoi(buf + i + 1);
}
/* We could still have another special case! */
c = imparse_word(&tmp, &tag);
if(c == ' ') {
c = imparse_word(&tmp, &cmd);
if(c == '\n' || (c == '\r' && *tmp == '\n')){
/* Are we logging out? */
if(!strncasecmp(cmd, "LOGOUT", 6)) {
free(sparebuf);
return IMTEST_CLOSEME;
}
}
}
free(sparebuf);
/* If the remote is sending only \n, clean it up for them */
if((len == 1 && buf[0] == '\n') ||
(len >= 2 && buf[len-2] != '\r')) {
len -= 1; /* truncate \n */
add_crlf = 1;
}
}
prot_write(pout, buf, len);
if(add_crlf) prot_write(pout, "\r\n", 2);
prot_flush(pout);
return IMTEST_OK;
}
static void * imap_init_conn(void)
{
struct generic_context_t *ret;
ret =
(void *)xmalloc(sizeof(struct generic_context_t));
memset(ret, 0, sizeof(struct generic_context_t));
ret->rock =
(void *)xmalloc(sizeof(struct imap_context_t));
memset(ret->rock, 0, sizeof(struct imap_context_t));
ret->pipe_oneline = &imap_pipe_oneline;
return ret;
}
static int imap_reset(void *rock)
{
struct generic_context_t *gentext = (struct generic_context_t *)rock;
struct imap_context_t *text = (struct imap_context_t *)gentext->rock;
char tag[64];
static int i=0;
if(text->inLiteral || gentext->midLine) return IMTEST_FAIL;
snprintf(tag, sizeof(tag) - 1, "UN%d", i);
prot_printf(pout, "%s UNSELECT\r\n", tag);
prot_flush(pout);
waitfor(tag, NULL, 0);
return IMTEST_OK;
}
#define HEADERS "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n \
From: Fred Foobar <foobar@Blurdybloop.COM>\r\n \
Subject: afternoon meeting\r\n \
To: mooch@owatagu.siam.edu\r\n \
Message-Id: <B27397-0100000@Blurdybloop.COM>\r\n \
MIME-Version: 1.0\r\n \
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n\r\n"
static int append_msg(char *mbox, int size)
{
int lup;
prot_printf(pout,"A003 APPEND %s (\\Seen) {" SIZE_T_FMT "}\r\n",
mbox,size+strlen(HEADERS));
/* do normal header foo */
prot_printf(pout,HEADERS);
for (lup=0;lup<size/10;lup++)
prot_printf(pout,"0123456789");
prot_printf(pout,"\r\n");
prot_flush(pout);
waitfor("A003", NULL, 1);
return IMTEST_OK;
}
/**************
*
* This tests throughput of IMAP server
*
* Steps:
* Creat mailbox
* Append message of 200 bytes, 2000 bytes, 20k, 200k, 2M
* Delete mailbox
*
*************/
static void send_recv_test(void)
{
char *mboxname="inbox.imtest";
time_t start, end;
int lup;
start=time(NULL);
for (lup=0;lup<10;lup++)
{
prot_printf(pout,"C01 CREATE %s\r\n",mboxname);
prot_flush(pout);
waitfor("C01", NULL, 1);
append_msg(mboxname,200);
append_msg(mboxname,2000);
append_msg(mboxname,20000);
append_msg(mboxname,200000);
append_msg(mboxname,2000000);
prot_printf(pout,"D01 DELETE %s\r\n",mboxname);
prot_flush(pout);
waitfor("D01", NULL, 1);
}
end=time(NULL);
printf("took %ld seconds\n", end - start);
}
/*********************************** POP3 ************************************/
static void *pop3_parse_banner(char *str)
{
char *cp, *start;
char *chal = NULL;
/* look for APOP challenge in banner '<...@...>' */
cp = str+3;
while (cp && (start = strchr(cp, '<'))) {
cp = start + 1;
while (*cp && *cp != '@' && *cp != '<' && *cp != '>') cp++;
if (*cp != '@') continue;
while (*cp && *cp != '<' && *cp != '>') cp++;
if (*cp == '>') {
*(++cp) = '\0';
chal = strdup(start);
if (!chal) imtest_fatal("memory error");
break;
}
}
return chal;
}
static int auth_pop(void)
{
char str[1024];
/* we need username and password to do USER/PASS */
char *username;
unsigned int userlen;
char *pass;
unsigned int passlen;
interaction(SASL_CB_AUTHNAME, NULL, "Authname", &username, &userlen);
printf("C: USER %s\r\n", username);
prot_printf(pout,"USER %s\r\n", username);
prot_flush(pout);
if (prot_fgets(str, 1024, pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
if (strncasecmp(str, "+OK", 3)) return IMTEST_FAIL;
interaction(SASL_CB_PASS, NULL, "Please enter your password",
&pass, &passlen);
printf("C: PASS <omitted>\r\n");
prot_printf(pout,"PASS %s\r\n",pass);
prot_flush(pout);
if (prot_fgets(str, 1024, pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
if (!strncasecmp(str, "+OK", 3)) {
return IMTEST_OK;
} else {
return IMTEST_FAIL;
}
}
static int auth_apop(char *apop_chal)
{
char str[1024];
/* we need username and password to do "APOP" */
char *username;
unsigned int userlen;
char *pass;
unsigned int passlen;
int i;
MD5_CTX ctx;
unsigned char digest[MD5_DIGEST_LENGTH];
char digeststr[2*MD5_DIGEST_LENGTH+1];
interaction(SASL_CB_AUTHNAME, NULL, "Authname", &username, &userlen);
interaction(SASL_CB_PASS,NULL, "Please enter your password",
&pass, &passlen);
MD5Init(&ctx);
MD5Update(&ctx,apop_chal,strlen(apop_chal));
MD5Update(&ctx,pass,passlen);
MD5Final(digest, &ctx);
/* convert digest from binary to ASCII hex */
for (i = 0; i < 16; i++)
sprintf(digeststr + (i*2), "%02x", digest[i]);
printf("C: APOP %s %s\r\n", username, digeststr);
prot_printf(pout,"APOP %s %s\r\n", username, digeststr);
prot_flush(pout);
if(prot_fgets(str, 1024, pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
if (!strncasecmp(str, "+OK", 3)) {
return IMTEST_OK;
} else {
return IMTEST_FAIL;
}
}
static int pop3_do_auth(struct sasl_cmd_t *sasl_cmd, void *apop_chal,
int user_enabled, char *mech, char *mechlist)
{
int result = IMTEST_FAIL;
if (mech) {
if (!strcasecmp(mech, "apop")) {
if (!apop_chal) {
printf("[Server did not advertise APOP challenge]\n");
} else {
result = auth_apop((char *) apop_chal);
}
} else if (!strcasecmp(mech, "user")) {
if (!user_enabled) {
printf("[Server did not advertise USER]\n");
} else {
result = auth_pop();
}
} else if (!mechlist || !stristr(mechlist, mech)) {
printf("[Server did not advertise SASL %s]\n", ucase(mech));
} else {
result = auth_sasl(sasl_cmd, mech);
}
} else {
if (mechlist) {
result = auth_sasl(sasl_cmd, mechlist);
} else if (apop_chal) {
result = auth_apop((char *) apop_chal);
} else if (user_enabled) {
result = auth_pop();
}
}
return result;
}
/********************************** NNTP *************************************/
static int auth_nntp()
{
char str[1024];
/* we need username and password to do AUTHINFO USER/PASS */
char *username;
unsigned int userlen;
char *pass;
unsigned int passlen;
interaction(SASL_CB_AUTHNAME, NULL, "Authname", &username, &userlen);
printf("C: AUTHINFO USER %s\r\n", username);
prot_printf(pout,"AUTHINFO USER %s\r\n", username);
prot_flush(pout);
if (prot_fgets(str, 1024, pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
if (!strncmp(str, "381", 3)) {
interaction(SASL_CB_PASS, NULL, "Please enter your password",
&pass, &passlen);
printf("C: AUTHINFO PASS <omitted>\r\n");
prot_printf(pout,"AUTHINFO PASS %s\r\n",pass);
prot_flush(pout);
if (prot_fgets(str, 1024, pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
}
if (!strncmp(str, "281", 3)) {
return IMTEST_OK;
} else {
return IMTEST_FAIL;
}
}
static int nntp_do_auth(struct sasl_cmd_t *sasl_cmd,
void *rock __attribute__((unused)),
int user_enabled, char *mech, char *mechlist)
{
int result = IMTEST_OK;
if (mech) {
if (!strcasecmp(mech, "user")) {
if (!user_enabled) {
printf("[Server did not advertise AUTHINFO USER]\n");
result = IMTEST_FAIL;
} else {
result = auth_nntp();
}
} else if (!mechlist || !stristr(mechlist, mech)) {
printf("[Server did not advertise SASL %s]\n", ucase(mech));
result = IMTEST_FAIL;
} else {
result = auth_sasl(sasl_cmd, mech);
}
} else {
if (mechlist) {
result = auth_sasl(sasl_cmd, mechlist);
} else if (user_enabled) {
result = auth_nntp();
}
}
return result;
}
static char *nntp_parse_success(char *str)
{
char *success = NULL, *tmp;
if (!strncmp(str, "283 ", 4)) {
success = str+4;
if ((tmp = strchr(success, ' ')))
*tmp = '\0'; /* clip trailing comment */
}
return success;
}
/******************************** LMTP/SMTP **********************************/
static int xmtp_do_auth(struct sasl_cmd_t *sasl_cmd,
void *rock __attribute__((unused)),
int login_enabled __attribute__((unused)),
char *mech, char *mechlist)
{
int result = IMTEST_OK;
if (mech) {
result = auth_sasl(sasl_cmd, mech);
} else if (mechlist) {
result = auth_sasl(sasl_cmd, mechlist);
}
return result;
}
struct xmtp_context_t
{
int inData;
};
/* This takes a NUL-terminated full line (including any trailing \r\n) */
static int xmtp_pipe_oneline(char *buf, int len, void *rock) {
struct xmtp_context_t *text = (struct xmtp_context_t *)rock;
if(text->inData && len <= 3) {
if(buf[0] == '.' &&
(buf[1] == '\n' || (buf[1] == '\r' && buf[2] == '\n'))) {
text->inData = 0;
}
} else if(!text->inData && len > 4 && len <= 6) {
if(!strncasecmp(buf, "DATA", 4) &&
(buf[4] == '\n' || (buf[4] == '\r' && buf[5] == '\n'))) {
text->inData = 1;
} else if(!strncasecmp(buf, "QUIT", 4) &&
(buf[4] == '\n' || (buf[4] == '\r' && buf[5] == '\n'))) {
return IMTEST_CLOSEME;
}
}
prot_write(pout, buf, len);
prot_flush(pout);
return IMTEST_OK;
}
static void *xmtp_init_conn(void)
{
struct generic_context_t *ret;
ret =
(void *)xmalloc(sizeof(struct generic_context_t));
memset(ret, 0, sizeof(struct generic_context_t));
ret->rock =
(void *)xmalloc(sizeof(struct xmtp_context_t));
memset(ret->rock, 0, sizeof(struct xmtp_context_t));
ret->pipe_oneline = &xmtp_pipe_oneline;
return ret;
}
static int xmtp_reset(void *rock)
{
struct generic_context_t *gentext = (struct generic_context_t *)rock;
struct xmtp_context_t *text = (struct xmtp_context_t *)gentext->rock;
if(text->inData || gentext->midLine) return IMTEST_FAIL;
prot_printf(pout, "RSET\r\n");
prot_flush(pout);
waitfor("250", NULL, 1);
return IMTEST_OK;
}
/******************************** MUPDATE ************************************/
/********************************* SIEVE *************************************/
static char *sieve_parse_success(char *str)
{
char *success = NULL, *tmp;
if (!strncmp(str, "OK (", 4) &&
(tmp = strstr(str+4, "SASL \"")) != NULL) {
success = tmp+6; /* skip SASL " */
tmp = strstr(success, "\"");
if (tmp) *tmp = '\0'; /* clip " */
}
return success;
}
/*****************************************************************************/
/* didn't give correct parameters; let's exit */
static void usage(char *prog, char *prot)
{
printf("Usage: %s [options] hostname\n", prog);
printf(" -p port : port to use (default=standard port for protocol)\n");
if (!strcasecmp(prot, "imap"))
printf(" -z : timing test\n");
printf(" -k # : minimum protection layer required\n");
printf(" -l # : max protection layer (0=none; 1=integrity; etc)\n");
printf(" -u user : authorization name to use\n");
printf(" -a user : authentication name to use\n");
printf(" -w pass : password to use (if not supplied, we will prompt)\n");
printf(" -v : verbose\n");
printf(" -m mech : SASL mechanism to use\n");
if (!strcasecmp(prot, "imap"))
printf(" (\"login\" for IMAP LOGIN)\n");
else if (!strcasecmp(prot, "pop3"))
printf(" (\"user\" for USER/PASS, \"apop\" for APOP)\n");
else if (!strcasecmp(prot, "nntp"))
printf(" (\"user\" for AUTHINFO USER/PASS\n");
printf(" -f file : pipe file into connection after authentication\n");
printf(" -r realm : realm\n");
#ifdef HAVE_SSL
if (!strcasecmp(prot, "imap") || !strcasecmp(prot, "pop3") ||
!strcasecmp(prot, "nntp") || !strcasecmp(prot, "smtp"))
printf(" -s : Enable %s over SSL (%ss)\n", prot, prot);
if (strcasecmp(prot, "mupdate"))
printf(" -t file : Enable TLS. file has the TLS public and private keys\n"
" (specify \"\" to not use TLS for authentication)\n");
#endif /* HAVE_SSL */
#ifdef HAVE_ZLIB
if (!strcasecmp(prot, "imap") || !strcasecmp(prot, "mupdate") ||
!strcasecmp(prot, "csync")) {
printf(" -q : Enable %s COMPRESSion"
" (before last authentication attempt)\n", prot);
}
#endif /* HAVE_ZLIB */
printf(" -c : enable challenge prompt callbacks\n"
" (enter one-time password instead of secret pass-phrase)\n");
printf(" -n : number of auth attempts (default=1)\n");
printf(" -I file : output my PID to (file) (useful with -X)\n");
printf(" -x file : open the named socket for the interactive portion\n");
printf(" -X file : same as -X, except close all file descriptors & dameonize\n");
exit(1);
}
static struct protocol_t protocols[] = {
{ "imap", "imaps", "imap", 1, /* LOGIN available until LOGINDISABLED */
{ 1, NULL, NULL },
{ "C01 CAPABILITY", "C01 ", " STARTTLS", " LOGINDISABLED", " AUTH=",
" COMPRESS=DEFLATE", &imap_parse_mechlist },
{ "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
{ "A01 AUTHENTICATE", 0, /* no init resp until SASL-IR advertised */
0, "A01 OK", "A01 NO", "+ ", "*", NULL, 0 },
{ "Z01 COMPRESS DEFLATE", "Z01 OK", "Z01 NO" },
&imap_do_auth, { "Q01 LOGOUT", "Q01 " },
&imap_init_conn, &generic_pipe, &imap_reset
},
{ "pop3", "pop3s", "pop", 0, /* USER unavailable until advertised */
{ 0, "+OK", &pop3_parse_banner },
{ "CAPA", ".", "STLS", "USER", "SASL ", NULL, NULL },
{ "STLS", "+OK", "-ERR", 0 },
{ "AUTH", 255, 0, "+OK", "-ERR", "+ ", "*", NULL, 0 },
{ NULL, NULL, NULL, },
&pop3_do_auth, { "QUIT", "+OK" }, NULL, NULL, NULL
},
{ "nntp", "nntps", "nntp", 0, /* AUTHINFO USER unavail until advertised */
{ 0, "20", NULL },
{ "CAPABILITIES", ".", "STARTTLS", "AUTHINFO USER", "SASL ", NULL, NULL },
{ "STARTTLS", "382", "580", 0 },
{ "AUTHINFO SASL", 512, 0, "28", "48", "383 ", "*",
&nntp_parse_success, 0 },
{ NULL, NULL, NULL, },
&nntp_do_auth, { "QUIT", "205" }, NULL, NULL, NULL
},
{ "lmtp", NULL, "lmtp", 0,
{ 0, "220 ", NULL },
{ "LHLO lmtptest", "250 ", "STARTTLS", NULL, "AUTH ", NULL, NULL },
{ "STARTTLS", "220", "454", 0 },
{ "AUTH", 512, 0, "235", "5", "334 ", "*", NULL, 0 },
{ NULL, NULL, NULL, },
&xmtp_do_auth, { "QUIT", "221" },
&xmtp_init_conn, &generic_pipe, &xmtp_reset
},
{ "smtp", "smtps", "smtp", 0,
{ 0, "220 ", NULL },
{ "EHLO smtptest", "250 ", "STARTTLS", NULL, "AUTH ", NULL, NULL },
{ "STARTTLS", "220", "454", 0 },
{ "AUTH", 512, 0, "235", "5", "334 ", "*", NULL, 0 },
{ NULL, NULL, NULL, },
&xmtp_do_auth, { "QUIT", "221" },
&xmtp_init_conn, &generic_pipe, &xmtp_reset
},
{ "mupdate", NULL, "mupdate", 0,
{ 1, "* OK", NULL },
{ NULL , "* OK", "* STARTTLS", NULL, "* AUTH ", "* COMPRESS \"DEFLATE\"", NULL },
{ "S01 STARTTLS", "S01 OK", "S01 NO", 1 },
{ "A01 AUTHENTICATE", USHRT_MAX, 1, "A01 OK", "A01 NO", "", "*", NULL, 0 },
{ "Z01 COMPRESS \"DEFLATE\"", "Z01 OK", "Z01 NO" },
NULL, { "Q01 LOGOUT", "Q01 " }, NULL, NULL, NULL
},
{ "sieve", NULL, SIEVE_SERVICE_NAME, 0,
{ 1, "OK", NULL },
{ "CAPABILITY", "OK", "\"STARTTLS\"", NULL, "\"SASL\" ", NULL, NULL },
{ "STARTTLS", "OK", "NO", 1 },
{ "AUTHENTICATE", USHRT_MAX, 1, "OK", "NO", NULL, "*",
&sieve_parse_success, 1 },
{ NULL, NULL, NULL, },
NULL, { "LOGOUT", "OK" }, NULL, NULL, NULL
},
{ "csync", NULL, "csync", 0,
{ 1, "* OK", NULL },
{ NULL , "* OK", "* STARTTLS", NULL, "* SASL ", "* COMPRESS DEFLATE", NULL },
{ "STARTTLS", "OK", "NO", 1 },
{ "AUTHENTICATE", USHRT_MAX, 0, "OK", "NO", "+ ", "*", NULL, 0 },
{ "COMPRESS DEFLATE", "OK", "NO" },
NULL, { "EXIT", "OK" }, NULL, NULL, NULL
},
{ NULL, NULL, NULL, 0,
{ 0, NULL, NULL },
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL },
{ NULL, NULL, NULL, 0 },
{ NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, 0 },
{ NULL, NULL, NULL, },
NULL, { NULL, NULL }, NULL, NULL, NULL
}
};
int main(int argc, char **argv)
{
struct protocol_t *protocol;
char *mechanism = NULL;
char servername[1024];
char *filename=NULL;
char *mechlist = NULL;
unsigned ext_ssf = 0;
const void *ssfp;
sasl_ssf_t ssf;
int maxssf = 128;
int minssf = 0;
int c;
int result;
int errflg = 0;
char *prog;
char *tls_keyfile="";
char *port = "", *prot = "";
int run_stress_test=0;
int dotls=0, dossl=0, docompress=0;
unsigned long capabilities = 0;
char str[1024];
const char *pidfile = NULL;
void *rock = NULL;
int reauth = 1;
int dochallenge = 0, noinitresp = 0;
char *val;
if (!construct_hash_table(&confighash, CONFIGHASHSIZE, 1)) {
imtest_fatal("could not construct config hash table");
}
/* do not buffer */
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
prog = strrchr(argv[0], '/') ? strrchr(argv[0], '/')+1 : argv[0];
/* look at all the extra args */
while ((c = getopt(argc, argv, "P:qscizvk:l:p:u:a:m:f:r:t:n:I:x:X:w:o:?h")) != EOF)
switch (c) {
case 'P':
prot = optarg;
break;
case 'q':
#ifdef HAVE_ZLIB
docompress=1;
#else
imtest_fatal("imtest was not compiled with zlib support\n");
#endif
break;
case 's':
#ifdef HAVE_SSL
dossl=1;
#else
imtest_fatal("imtest was not compiled with SSL/TLS support\n");
#endif
break;
case 'c':
dochallenge=1;
break;
case 'i':
noinitresp=1;
break;
case 'z':
run_stress_test=1;
break;
case 'v':
verbose=1;
break;
case 'k':
minssf=atoi(optarg);
break;
case 'l':
maxssf=atoi(optarg);
break;
case 'p':
port = optarg;
break;
case 'u':
username = optarg;
break;
case 'a':
authname = optarg;
break;
case 'w':
cmdline_password = optarg;
break;
case 'm':
mechanism=optarg;
break;
case 'f':
if(output_socket)
imtest_fatal("cannot pipe a file when using unix domain socket output");
filename=optarg;
break;
case 'r':
realm=optarg;
break;
case 't':
#ifdef HAVE_SSL
dotls=1;
tls_keyfile=optarg;
#else
imtest_fatal("imtest was not compiled with SSL/TLS support\n");
#endif
break;
case 'n':
reauth = atoi(optarg);
if (reauth <= 0)
imtest_fatal("number of auth attempts must be > 0\n");
break;
case 'I':
pidfile = optarg;
break;
case 'X':
case 'x':
if(filename)
imtest_fatal("cannot pipe a file when using unix domain socket output");
if(output_socket)
imtest_fatal("cannot specify both -X and -x");
output_socket = optarg;
if(c == 'X'){
/* close all already-open file descriptors that are
* not stdin/stdout/stderr */
int i, dsize = getdtablesize();
/* close all file descriptors */
for(i=0; i<dsize; i++) close(i);
/* background ourselves and lose the process group info */
for(i=0;i<3;i++) if(fork()) exit(0);
}
break;
case 'o':
/* parse the opt=val string. if no value is given, assume '1' */
if ((val = strchr(optarg, '=')))
*val++ = '\0';
else
val = "1";
/* insert the opt/val pair into the hash table */
hash_insert(optarg, xstrdup(val), &confighash);
break;
case 'h':
case '?':
default:
errflg = 1;
break;
}
if (!*prot) {
if (!strcasecmp(prog, "imtest"))
prot = "imap";
else if (!strcasecmp(prog, "pop3test"))
prot = "pop3";
else if (!strcasecmp(prog, "nntptest"))
prot = "nntp";
else if (!strcasecmp(prog, "lmtptest"))
prot = "lmtp";
else if (!strcasecmp(prog, "smtptest"))
prot = "smtp";
else if (!strcasecmp(prog, "mupdatetest"))
prot = "mupdate";
else if (!strcasecmp(prog, "sivtest"))
prot = "sieve";
else if (!strcasecmp(prog, "synctest"))
prot = "csync";
}
protocol = protocols;
while (protocol->protocol && strcasecmp(prot, protocol->protocol))
protocol++;
if (!protocol->protocol)
imtest_fatal("unknown protocol\n");
if (dossl && !protocol->sprotocol)
imtest_fatal("protocol cannot be SSL-wrapped\n");
if (run_stress_test && strcmp(protocol->protocol, "imap"))
imtest_fatal("stress test can only be run for IMAP\n");
if (errflg) {
usage(prog, protocol->protocol);
}
if (!*port) {
if (dossl) {
port=protocol->sprotocol;
} else {
port=protocol->protocol;
}
}
/* last arg is server name */
if (optind < argc)
strncpy(servername, argv[optind], 1023);
else {
fprintf(stderr, "WARNING: no hostname supplied, assuming localhost\n\n");
strncpy(servername, "localhost", 1023);
}
if(pidfile) {
FILE *pf;
pf = fopen(pidfile, "w");
if(!pf) {
fprintf(stderr, "could not open %s for writing\n",pidfile);
perror("error");
exit(1);
}
fprintf(pf, "%d", getpid());
fclose(pf);
}
/* attempt to start sasl */
if (sasl_client_init(callbacks+(!dochallenge ? 2 : 0)) != IMTEST_OK) {
imtest_fatal("SASL initialization");
}
conn = NULL;
do {
if (conn) {
/* send LOGOUT */
logout(&protocol->logout_cmd, 1);
printf("Connection closed.\n\n");
prot_free(pin);
prot_free(pout);
#ifdef HAVE_SSL
/* Properly shutdown TLS so that session can be reused */
if (tls_conn) {
SSL_shutdown(tls_conn);
SSL_set_shutdown(tls_conn, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
}
#endif
close(sock);
sasl_dispose(&conn);
}
if (init_net(servername, port) != IMTEST_OK) {
imtest_fatal("Network initialization - cannot connect to %s:%s",
servername, port);
}
if (init_sasl(protocol->service, servername, minssf, maxssf,
protocol->sasl_cmd.parse_success ?
SASL_SUCCESS_DATA : 0) != IMTEST_OK) {
imtest_fatal("SASL initialization");
}
/* set up the prot layer */
pin = prot_new(sock, 0);
pout = prot_new(sock, 1);
#ifdef HAVE_SSL
if (dossl==1) {
do_starttls(1, "", &ext_ssf);
}
#endif /* HAVE_SSL */
if (protocol->banner.is_capa) {
/* try to get the capabilities from the banner */
mechlist = ask_capability(protocol, &capabilities, AUTO_BANNER);
if (!mechlist && !(capabilities & CAPA_STARTTLS)) {
/* found no capabilities in banner -> get them explicitly */
protocol->banner.is_capa = 0;
}
}
else {
do { /* look for the banner response */
if (prot_fgets(str, sizeof(str), pin) == NULL) {
imtest_fatal("prot layer failure");
}
printf("S: %s", str);
/* parse it if need be */
if (protocol->banner.parse_banner)
rock = protocol->banner.parse_banner(str);
} while (strncasecmp(str, protocol->banner.resp,
strlen(protocol->banner.resp)));
}
if (!protocol->banner.is_capa) {
mechlist = ask_capability(protocol, &capabilities, AUTO_NO);
}
#ifdef HAVE_SSL
if ((dossl==0) && (dotls==1) && (capabilities & CAPA_STARTTLS)) {
char *resp;
printf("C: %s\r\n", protocol->tls_cmd.cmd);
prot_printf(pout, "%s\r\n", protocol->tls_cmd.cmd);
prot_flush(pout);
resp = waitfor(protocol->tls_cmd.ok, protocol->tls_cmd.fail, 1);
if (!strncasecmp(resp, protocol->tls_cmd.ok,
strlen(protocol->tls_cmd.ok))) {
do_starttls(0, tls_keyfile, &ext_ssf);
/* ask for the capabilities again */
if (verbose==1)
printf("Asking for capabilities again "
"since they might have changed\n");
if (mechlist) free(mechlist);
mechlist = ask_capability(protocol, &capabilities,
protocol->tls_cmd.auto_capa);
}
} else if ((dotls==1) && !(capabilities & CAPA_STARTTLS)) {
imtest_fatal("STARTTLS not supported by the server!\n");
}
#endif /* HAVE_SSL */
#ifdef HAVE_ZLIB
if ((reauth == 1) && (docompress==1) && (capabilities & CAPA_COMPRESS)) {
char *resp;
printf("C: %s\r\n", protocol->compress_cmd.cmd);
prot_printf(pout, "%s\r\n", protocol->compress_cmd.cmd);
prot_flush(pout);
resp = waitfor(protocol->compress_cmd.ok, protocol->compress_cmd.fail, 1);
if (!strncasecmp(resp, protocol->compress_cmd.ok,
strlen(protocol->compress_cmd.ok))) {
prot_setcompress(pin);
prot_setcompress(pout);
}
}
#endif /* HAVE_ZLIB */
if (noinitresp) {
/* don't use an initial response, even if its supported */
protocol->sasl_cmd.maxlen = 0;
}
if (protocol->do_auth)
result = protocol->do_auth(&protocol->sasl_cmd, rock,
capabilities & CAPA_LOGIN,
mechanism, mechlist);
else {
if (mechanism) {
result = auth_sasl(&protocol->sasl_cmd, mechanism);
} else if (mechlist) {
result = auth_sasl(&protocol->sasl_cmd, mechlist);
} else {
result = IMTEST_FAIL;
}
}
if (rock) free(rock);
if (result == IMTEST_OK) {
printf("Authenticated.\n");
/* turn on layer if need be */
prot_setsasl(pin, conn);
prot_setsasl(pout, conn);
} else {
const char *s = sasl_errstring(result, NULL, NULL);
printf("Authentication failed. %s\n", s);
}
result = sasl_getprop(conn, SASL_SSF, &ssfp);
ssf = *((sasl_ssf_t *) ssfp);
if (result != SASL_OK) {
printf("SSF: unable to determine (SASL ERROR %d)\n", result);
} else {
printf("Security strength factor: %d\n", ext_ssf + ssf);
if (ssf) {
/* ask for the capabilities again */
char *new_mechlist;
if (verbose==1)
printf("Asking for capabilities again "
"since they might have changed\n");
if (!strcmp(protocol->protocol, "sieve")) {
/* XXX Hack to handle ManageSieve servers.
* No way to tell from protocol if server will
* automatically send capabilities, so we treat it
* as optional.
*/
char ch;
/* wait and probe for possible auto-capability response*/
usleep(250000);
prot_NONBLOCK(pin);
if ((ch = prot_getc(pin)) != EOF) {
prot_ungetc(ch, pin);
} else {
protocol->sasl_cmd.auto_capa = 0;
}
prot_BLOCK(pin);
}
new_mechlist = ask_capability(protocol, &capabilities,
protocol->sasl_cmd.auto_capa);
if (new_mechlist && strcmp(new_mechlist, mechlist)) {
printf("WARNING: possible MITM attack: "
"list of available SASL mechanisms changed\n");
free(new_mechlist);
}
}
}
if (mechlist) free(mechlist);
} while (--reauth);
if (run_stress_test == 1) {
send_recv_test();
} else {
/* else run in interactive mode or
pipe in a filename if applicable */
interactive(protocol, filename);
}
while (stashed_strings.count)
free(strarray_pop(&stashed_strings));
free_hash_table(&confighash, free);
exit(0);
}