[Dovecot] imaptest, with options!

Jens Laas jens.laas at data.slu.se
Fri Apr 28 16:08:29 EEST 2006


(06.04.28 kl.14:55) Jens Laas skrev följande till dovecot at dovecot.org:

>
> I hacked some command line options into imaptest.
>
> I dont think I broke it..
>

And here is the actual code :-)

Jens

-----------------------------------------------------------------------
     'Old C programmers don't die ... they're just cast into void*'
-----------------------------------------------------------------------
     Jens Låås                              Email: jens.laas at data.slu.se
     Department of Computer Services, SLU   Phone: +46 18 67 35 15
     Vindbrovägen 1
     P.O. Box 7079
     S-750 07 Uppsala
     SWEDEN
-----------------------------------------------------------------------
-------------- next part --------------
/*
   Place this file to compiled Dovecot sources' root directory and run:

   gcc imaptest.c -o imaptest -Wall -W -I. -Isrc/lib -DHAVE_CONFIG_H src/lib/liblib.a
*/
#include "lib.h"
#include "base64.h"
#include "buffer.h"
#include "str.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>

/* IP / port where to connect to */
#define IP "127.0.0.1"
#define PORT 14300
/* Username. You can give either a single user, or a number of users in which
   case the username gets randomized at each connection. */
//#define USERNAME_TEMPLATE "u%04d at d%04d.domain.org"
//#define USERNAME_TEMPLATE "test%ld"
#define USERNAME_TEMPLATE "tsiraine"
#define USER_RAND 99
#define DOMAIN_RAND 99
/* Password (for all users) */
#define PASSWORD "pass"
/* Number of simultaneous client connections */
#define CLIENTS_COUNT 20
/* Try to keep around this many messages in mailbox (in expunge + append) */
#define MESSAGE_COUNT_THRESHOLD 100
/* Append messages from this mbox file to mailboxes */
#define MBOX_PATH "/home/tsiraine/mail/dovecot.mbox"

/* Use AUTHENTICATE command instead of LOGIN command */
#define USE_AUTHENTICATE
/* Append mails */
#define APPENDS
/* Expunge mails */
#define EXPUNGES
/* Randomly disconnect clients */
#define DISCONNECTS 20

struct {
  char *ip, *username_template, *password, *mbox_path;
  int port;
  unsigned int clients_count;
  int use_authenticate;
  int message_count_threshold;
} conf;

enum client_state {
	STATE_BANNER,
	STATE_AUTHENTICATE,
	STATE_LOGIN,
	STATE_SELECT,
	STATE_FETCH,
	STATE_FETCH2,
	STATE_STORE,
	STATE_EXPUNGE,
	STATE_APPEND,
        STATE_LOGOUT,

        STATE_COUNT
};

char *states[STATE_COUNT] = { "BANNER", "AUTHENTICATE", "LOGIN", "SELECT",
			      "FETCH", "FETCH2", "STORE", "EXPUNGE",
			      "APPEND", "LOGOUT" };

struct client {
        unsigned int idx;
        unsigned int cur, messages, deleted;
	struct istream *input;
	struct ostream *output;
	struct io *io;
	enum client_state state;
        struct mbox *mbox;

	uoff_t literal_length;
        int literal_nextline;
        time_t last_io;

	char *username;
};

struct mbox {
	int fd;
	char *path;
	struct istream *input;
};

static int clients_count = 0;
static struct ioloop *ioloop;
static unsigned int counters[STATE_COUNT], disconnects;
static struct client **clients;

void client_free(struct client *client);

static struct mbox *mbox_open(const char *path)
{
	struct mbox *mbox;

	mbox = i_new(struct mbox, 1);
	mbox->path = i_strdup(path);
	mbox->fd = open(path, O_RDONLY);
	if (mbox->fd == -1)
		i_fatal("open(%s) failed: %m", path);
	mbox->input =
		i_stream_create_file(mbox->fd, default_pool, (size_t)-1, FALSE);
	return mbox;
}

static void mbox_close(struct mbox *mbox)
{
	i_stream_unref(&mbox->input);
	(void)close(mbox->fd);
	i_free(mbox->path);
	i_free(mbox);
}

static uoff_t mbox_get_next_size(struct mbox *mbox)
{
	const char *line;
	uoff_t offset, last_offset, size;

	line = i_stream_read_next_line(mbox->input);
	if (line == NULL) {
		if (mbox->input->v_offset == 0)
			i_fatal("Empty mbox file: %s", mbox->path);
		i_stream_seek(mbox->input, 0);
		return mbox_get_next_size(mbox);
	}

	/* should be From-line */
	if (strncmp(line, "From ", 5) != 0) {
		if (mbox->input->v_offset == 0)
			i_fatal("Not a valid mbox file: %s", mbox->path);
		i_panic("From-line not found at %"PRIuUOFF_T,
			mbox->input->v_offset);
	}

        offset = last_offset = mbox->input->v_offset;
        while ((line = i_stream_read_next_line(mbox->input)) != NULL) {
                if (strncmp(line, "From ", 5) == 0) {
                        if (offset != last_offset)
                                break;

                        /* empty body */
                        offset = last_offset;
                }
                last_offset = mbox->input->v_offset;
        }
        if (offset == last_offset)
                i_fatal("mbox file ends with From-line: %s", mbox->path);

        size = last_offset - offset;
        i_stream_seek(mbox->input, offset);
	return size;
}

static int client_parse_literal(struct client *client)
{
	const unsigned char *data;
	size_t size;

	data = i_stream_get_data(client->input, &size);
	if (size < client->literal_length) {
		i_stream_skip(client->input, size);
		client->literal_length -= size;
		return FALSE;
	}

	i_stream_skip(client->input, client->literal_length);
	client->literal_length = 0;
	client->literal_nextline = TRUE;
	return TRUE;
}

static int client_skip_line(struct client *client, const char *line)
{
	size_t len = strlen(line);

	if (line[len-1] != '}')
		return TRUE;
	len--;

	/* skip literal */
	while (len > 0 && i_isdigit(line[len-1]))
		len--;
	if (len == 0 || line[len-1] != '{') {
		i_error("Invalid literal in: %s", line);
		client_free(client);
		return FALSE;
	}
	client->literal_length = strtoul(t_strcut(line+len, '}'), NULL, 10);
	return client_parse_literal(client);
}

static void client_handle_untagged(struct client *client, const char *line)
{
	if (i_isdigit(line[3])) {
		const char *p = strchr(line+2, ' ');
		unsigned int num;

		if (p++ == NULL) {
			i_error("%s: Unexpected: %s", client->username, line);
			return;
		}

		num = strtoul(t_strcut(line+2, ' '), NULL, 10);
		if (strcasecmp(p, "EXISTS") == 0)
			client->messages = num;

                if (num > client->messages &&
		    client->state > STATE_SELECT) {
			i_fatal("%s: seq too high (%u > %u, state=%d): %s",
                                client->username, num, client->messages,
                                client->state, line);
		}

		if (strcasecmp(p, "EXPUNGE") == 0)
			client->messages--;
	} else if (strncasecmp(line+3, "BYE ", 4) == 0) {
		i_info("%s: %s", client->username, line + 3);
	}
}

static int client_append(struct client *client)
{
	const char *cmd;
	struct istream *input;
	uoff_t size, next_offset;
	off_t ret;

	size = mbox_get_next_size(client->mbox);
	cmd = t_strdup_printf("a APPEND INBOX {%"PRIuUOFF_T"+}\r\n", size);
        o_stream_send_str(client->output, cmd);

        next_offset = client->mbox->input->v_offset + size;
	input = i_stream_create_limit(default_pool, client->mbox->input,
				      client->mbox->input->v_offset, size);
	ret = o_stream_send_istream(client->output, input);
        i_stream_unref(&input);
        o_stream_send_str(client->output, "\r\n");

	if (ret != (off_t)size) {
		if (ret < 0)
			i_error("APPEND failed: %m");
		else {
			i_error("APPEND failed: Sent only %"
				PRIuUOFF_T" of %"PRIuUOFF_T,
				ret, size);
                }
                i_stream_seek(client->mbox->input, next_offset);
		return -1;
	}
	return 0;
}

static void client_input(void *context)
{
	struct client *client = context;
	const char *line, *str;
	string_t *cmd, *buf;
	unsigned int i;

        client->last_io = ioloop_time;

	switch (i_stream_read(client->input)) {
	case 0:
		return;
	case -1:
		/* disconnected */
		client_free(client);
		return;
	case -2:
		/* buffer full */
		i_error("line too long");
		client_free(client);
		return;
	}

	if (client->literal_length > 0) {
		if (!client_parse_literal(client))
			return;
	}

	while ((line = i_stream_next_line(client->input)) != NULL) {
		if (client->literal_nextline) {
			client->literal_nextline = FALSE;
			if (!client_skip_line(client, line))
				return;
			continue;
		}

		if (strncmp(line, "* ", 2) == 0 &&
		    client->state != STATE_BANNER) {
			client_handle_untagged(client, line);
			if (!client_skip_line(client, line))
				return;
			continue;
		}

		switch (client->state) {
		case STATE_BANNER:
		  if(conf.use_authenticate)
		    {
			str = "l AUTHENTICATE plain\r\n";
                        client->state = STATE_AUTHENTICATE;
		    }
		  else
		    {
                        str = t_strdup_printf("l LOGIN \"%s\" \"%s\"\r\n",
                                              client->username, conf.password);
                        client->state = STATE_LOGIN;
		    }
			o_stream_send_str(client->output, str);
			break;
		case STATE_AUTHENTICATE:
			buf = t_str_new(512);
			cmd = t_str_new(512);
			str_append_c(buf, '\0');
			str_append(buf, client->username);
			str_append_c(buf, '\0');
			str_append(buf, conf.password);
			base64_encode(buf->data, buf->used, cmd);
			str_append(cmd, "\r\n");

			o_stream_send_str(client->output, str_c(cmd));
			client->state = STATE_LOGIN;
			break;
		case STATE_LOGIN:
			if (strncasecmp(line, "l OK", 4) != 0) {
				i_error("Login failed: %s", line);
				client_free(client);
				return;
			}
			o_stream_send_str(client->output, "s SELECT INBOX\r\n");
			client->state = STATE_SELECT;
			break;
		case STATE_SELECT:
			if (strncasecmp(line, "s OK", 4) != 0) {
				i_error("SELECT failed: %s", line);
				client_free(client);
				return;
			}
			/*i_info("%s: %u messages", client->username,
			       client->messages);*/

			if (client->messages == 0)
				goto __append;

			o_stream_send_str(client->output,
				"f FETCH 1:* (UID FLAGS ENVELOPE BODY)\r\n");
			client->state = STATE_FETCH;
			break;
		case STATE_FETCH:
			if (strncasecmp(line, "f OK", 4) != 0) {
				i_error("FETCH failed: %s", line);
				client_free(client);
				return;
			}
			o_stream_send_str(client->output,
				t_strdup_printf("f2 FETCH %lu (BODY[])\r\n",
					       (random() % client->messages) + 1));
			client->state = STATE_FETCH2;
			break;
		case STATE_FETCH2:
			if (strncasecmp(line, "f2 OK", 5) != 0) {
				i_error("FETCH BODY[] failed: %s", line);
				client_free(client);
				return;
                        }

#ifndef EXPUNGES
                        goto __append;
#endif

			/* delete them randomly */
			cmd = t_str_new(512);
			str_append(cmd, "s STORE ");
			for (i = 1; i <= client->messages; i++) {
				if ((random() % 10) == 0) {
					str_printfa(cmd, "%u,", i);
					client->deleted++;
				}
			}
			if (client->deleted == 0)
				goto __append;

			str_truncate(cmd, str_len(cmd) - 1);
			str_append(cmd, " +FLAGS.SILENT \\Deleted\r\n");
			o_stream_send_str(client->output, str_c(cmd));
			client->state = STATE_STORE;
			break;
		case STATE_STORE:
			if (strncasecmp(line, "s OK", 4) != 0) {
				i_error("STORE failed: %s", line);
				client_free(client);
				return;
			}

			o_stream_send_str(client->output, "e EXPUNGE\r\n");
			client->state = STATE_EXPUNGE;
			break;
		case STATE_EXPUNGE:
			if (strncasecmp(line, "e OK", 4) != 0) {
				i_error("EXPUNGE failed: %s", line);
				client_free(client);
				return;
			}

                __append:
#ifndef APPENDS
                        goto __logout;
#endif
			if (client->messages >= conf.message_count_threshold)
				goto __logout;

			if (client_append(client) < 0) {
				client_free(client);
				return;
			}
			client->state = STATE_APPEND;
			break;
		case STATE_APPEND:
			if (strncasecmp(line, "a OK", 4) != 0) {
				i_error("APPEND failed: %s", line);
				client_free(client);
				return;
			}

			if (client->messages < conf.message_count_threshold &&
			    (rand() % 5) == 0) {
				/* append more */
				if (client_append(client) < 0) {
					client_free(client);
					return;
				}
				break;
			}

		__logout:
			o_stream_send_str(client->output, "x LOGOUT\r\n");
			client->state = STATE_LOGOUT;
			break;
		case STATE_LOGOUT:
                        break;
                case STATE_COUNT:
                        i_unreached();
                }
                counters[client->state]++;
#ifdef DISCONNECTS
		if ((rand() % DISCONNECTS) == 0) {
			/* random disconnection */
                        disconnects++;
                        client_free(client);
			break;
                }
#endif
	}
}

static int client_output(void *context)
{
        struct client *client = context;
	int ret;

	ret = o_stream_flush(client->output);
        client->last_io = ioloop_time;
        return ret;
}

struct client *client_new(unsigned int idx, struct mbox *mbox)
{
	struct client *client;
	struct ip_addr ip;
	int fd;

	net_addr2ip(conf.ip, &ip);
	fd = net_connect_ip(&ip, conf.port, NULL);
	if (fd < 0) {
		i_error("connect() failed: %m");
		return NULL;
	}

	client = i_new(struct client, 1);
        client->idx = idx;
        client->mbox = mbox;
	client->input = i_stream_create_file(fd, default_pool, 65536, TRUE);
	client->output = o_stream_create_file(fd, default_pool, (size_t)-1, FALSE);
	client->io = io_add(fd, IO_READ, client_input, client);
	client->username = i_strdup_printf(conf.username_template,
					   (random() % USER_RAND) + 1,
					   (random() % DOMAIN_RAND) + 1);
        client->last_io = ioloop_time;
        o_stream_set_flush_callback(client->output, client_output, client);
	clients_count++;

        clients[idx] = client;
        return client;
}

void client_free(struct client *client)
{
	struct mbox *mbox = client->mbox;
        unsigned int idx = client->idx;

	--clients_count;
        clients[idx] = NULL;

	/*if (--clients_count == 0)
		io_loop_stop(ioloop);*/
	io_remove(&client->io);
	o_stream_unref(&client->output);
	i_stream_unref(&client->input);
	i_free(client->username);
	i_free(client);

	client_new(idx, mbox);
}

static void print_header(void)
{
        printf("Auth Logi Sele Fetc Fet2 Stor Expu Appe Logo Disc\n");
}

static void print_timeout(void *context __attr_unused__)
{
        static int rowcount = 0;
        unsigned int i;

        if ((rowcount++ % 10) == 0)
                print_header();

        for (i = 1; i < STATE_COUNT; i++) {
                printf("%4d ", counters[i]);
                counters[i] = 0;
        }
        printf("%4d\n", disconnects);

        for (i = 0; i < conf.clients_count; i++) {
                if (clients[i] != NULL &&
                    clients[i]->last_io < ioloop_time - 15) {
                        printf(" - %d. stalled for %u secs in %s\n", i,
                               (unsigned)(ioloop_time - clients[i]->last_io),
                               states[clients[i]->state]);
                }
        }

        disconnects = 0;
}

int main(int argc, char **argv)
{
	struct mbox *mbox;
	char *p, *s;
	unsigned int i;

	while(argc-- > 1)
	  {
	    if( (!strcmp(argv[argc], "-h")) || (!strcmp(argv[argc], "--help")))
	      {
		printf("imaptest [USER at IP:PORTNO] [pass=PASSWORD] [mbox=MBOX] [clients=CC] [msgs=NMSG] [use_authenticate] [PORTNO]\n");
		printf("USER = template for username. \"u%%04d\" will generate\nusers \"u0001\" to \"u0100\"\n");
		printf("MBOX = path to mbox from which we read mails to append.\n");
		printf("CC   = number of concurrent clients. [20]\n");
		printf("NMSG = target number of messages in INBOX. [100]\n");
		printf("If use_authenticate is given AUTHENTICATE will be used instead of LOGIN.\n");
		exit(0);
	      }

	    if(strcmp(argv[argc], "use_authenticate")==0)
	      {
		conf.use_authenticate = 1;
		continue;
	      }

	    /* pass=password */
	    if(strncmp(argv[argc], "pass=", 5)==0)
	      {
		conf.password = argv[argc] + 5;
		continue;
	      }

	    /* mbox=path */
	    if(strncmp(argv[argc], "mbox=", 5)==0)
	      {
		conf.mbox_path = argv[argc] + 5;
		continue;
	      }

	    /* clients=# */
	    if(strncmp(argv[argc], "clients=", 8)==0)
	      {
		conf.clients_count = atoi(argv[argc] + 8);
		continue;
	      }

	    /* msgs=# */
	    if(strncmp(argv[argc], "msgs=", 5)==0)
	      {
		conf.message_count_threshold = atoi(argv[argc] + 5);
		continue;
	      }

	    /* user at ip:port */
	    if( (p = strchr(argv[argc], '@')))
	      {
		if(p != argv[argc])
		  {
		    conf.username_template = strdup(argv[argc]);
		    s = strchr(conf.username_template, '@');
		    *s = 0;
		  }
		if(*(p+1) && (*(p+1) != ':'))
		  {
		    conf.ip = strdup(p+1);
		    s = strchr(conf.ip, ':');
		    if(s)
		      {
			*s = 0;
			conf.port = atoi(s+1);
		      }
		  }
		if(*(p+1) && (*(p+1) == ':') && *(p+2))
		  conf.port = atoi(p+2);
		continue;
	      }

	    /* 143 */
	    if(!conf.port)
	      for(p=argv[argc];*p;p++)
		{
		  if(!isdigit(*p))
		    break;
		  if(!*(p+1))
		    conf.port = atoi(argv[argc]);
		}
	  }

	if(!conf.password) conf.password = "pass";
	if(!conf.username_template) conf.username_template = "test";
	if(!conf.port) conf.port = 143;
	if(!conf.ip) conf.ip = "127.0.0.1";
	if(!conf.mbox_path) conf.mbox_path = "/root/imaptest.mbox";
	if(!conf.clients_count) conf.clients_count = 20;
	if(!conf.message_count_threshold) conf.message_count_threshold = 100;
	
	clients = malloc(conf.clients_count * sizeof(struct client*));
	memset(clients, 0, conf.clients_count * sizeof(struct client*));
	
	printf("Using %s@%s:%d\n pass=\"%s\"\n mbox=\"%s\"\n client_count=%d\n authcmd=%s\n messages in mbox (target)=%d\n",
	       conf.username_template,
	       conf.ip,
	       conf.port,
	       conf.password,
	       conf.mbox_path,
	       conf.clients_count,
	       conf.use_authenticate?"AUTHENTICATE":"LOGIN",
	       conf.message_count_threshold);
	
	lib_init();
	ioloop = io_loop_create(system_pool);

	mbox = mbox_open(conf.mbox_path);

	timeout_add(1000, print_timeout, NULL);
	for (i = 0; i < conf.clients_count; i++)
		client_new(i, mbox);
        io_loop_run(ioloop);

	mbox_close(mbox);
	io_loop_destroy(&ioloop);
	lib_deinit();
	return 0;
}


More information about the dovecot mailing list