(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;
}