#include <network.h>
#include <mail.h>
#include <pop3.h>
#include <mime.h>
#include <player_handler.h>
inherit SERVER;
private void parse_comm(class pop_session sess, string str);
private string *pop_states = ({
"AUTHORIZATION",
"AUTHORIZATION",
"TRANSACTION",
"UPDATE"
});
private mapping Sockets;
private nosave mapping cache;
private nosave int reads, hits;
private mapping last;
protected void create() {
server::create();
SetSocketType(STREAM);
SetDestructOnClose(1);
Sockets = ([]);
call_out("setup", 2);
call_out("clean_sockets", 180);
unguarded((: restore_object, "/net/save/pop3" :));
if(!last)
last = ([ ]);
} /* create() */
/** @ignore yes */
protected void setup() {
if (eventCreateSocket(PORT_POP3) < 0) {
if (this_object()) {
destruct(this_object());
}
}
} /* setup() */
/** @ignore yes */
protected void eventNewConnection(int fd) {
class pop_session sess;
server::eventNewConnection(fd);
sess = new(class pop_session, fd : fd, state : POP_AUTH_USER, command : "",
deleted : ({}), messages : ({}));
Sockets[fd] = sess;
eventWrite(fd, sprintf("+OK POP3 %s Discworld v%s server ready\r\n",
query_host_name(), POP3_VERSION));
} /* eventNewConnection() */
/** @ignore yes */
protected void eventRead(int fd, string str) {
string *bits, bit;
class pop_session sess = Sockets[fd];
if (!sess) {
return;
}
if (str) {
sess->command += str;
if (strsrch(str, '\n') == -1) {
return;
}
}
sess->command = replace_string(sess->command, "\r", "");
bits = explode(sess->command, "\n");
if (!sizeof(bits)) {
eventWrite(fd, "-ERR Null command\r\n");
} else {
foreach (bit in bits) {
parse_comm(sess, bit);
}
}
sess->command = "";
} /* eventRead() */
/** @ignore yes */
protected void eventSocketClosed(int fd) {
class pop_session sess = Sockets[fd];
if (!sess) {
return;
}
map_delete(Sockets, fd);
} /* eventSocketClosed() */
private void sign_off(class pop_session sess) {
int *deleted;
if (sizeof(sess->deleted)) {
deleted = map(sess->deleted, (: $2->headers[$1-1]->number :), sess);
FOLDER_H->delete_it(sess->user_name, "inbox", deleted);
}
eventWrite(sess->fd, "+OK Sayonara\r\n", 1);
unguarded((: save_object, "/net/save/pop3" :));
} /* sign_off() */
private void load_message(class pop_session sess, int message) {
string mess;
int which;
reads++;
which = sess->headers[message]->number;
if(!cache)
cache = ([ ]);
if(cache[which]) {
hits++;
sess->messages[message] = cache[which];
return;
}
mess = FOLDER_H->load_message(sess->user_name, "inbox",
sess->headers[message]->number);
mess = MIME->rewrite_header(mess);
mess = replace_string(mess, "\n", "\r\n");
if (mess[<1] != '\n') {
mess += "\r\n";
}
sess->messages[message] = mess;
sess->sizes[message] = strlen(mess);
cache[which] = copy(mess);
} /* load_message() */
private void load_folder(class pop_session sess) {
int *tmp, max, i;
sess->headers = FOLDER_H->get_messages(sess->user_name, "inbox");
tell_creator("ceres", "Headers: %d\n", sizeof(sess->headers));
sess->messages = allocate(sizeof(sess->headers));
sess->sizes = allocate(sizeof(sess->headers));
sess->num_messages = sizeof(sess->headers);
// the sizes will start out as an approximation. As we read actual messages
// the real size will be put in place.
tmp = sess->sizes;
for(i=0; i<sizeof(sess->headers); i++)
sess->sizes[i] = 1000;
// If we'd already got some sizes then copy them into the new size array.
if(tmp) {
(sizeof(tmp) > sizeof(sess->headers)) ? max = sizeof(tmp) :
max = sizeof(sess->headers);
for(i=0; i<max; i++)
sess->sizes[i] = tmp[i];
}
} /* load_folder() */
private void parse_comm(class pop_session sess, string str) {
string *bits, rest, cmd, message, header;
int *sizes, lines, i, number, fd = sess->fd;
if (strsrch(lower_case(str), "pass") == -1) {
TP("Parsing " + str + ".\n");
}
bits = explode(str, " ");
cmd = bits[0];
if (sizeof(bits) > 1 ) {
rest = implode(bits[1..], " ");
} else {
rest = "";
}
sess->time = time();
switch(lower_case(cmd)) {
case "dele":
tell_creator("ceres", "Requesting deletion of %d\n", number);
CHECK_STATE(POP_TRANSACTION);
CHECK_CMD(1, "-ERR Missing message number argument\r\n");
if ((sscanf(bits[1], "%d", number) != 1) || number < 1 ||
number > sess->num_messages) {
eventWrite(fd, "-ERR No such message\r\n");
} else if (member_array(number, sess->deleted) != -1) {
eventWrite(fd, sprintf("-ERR Message number %d already deleted\r\n",
number));
} else {
tell_creator("ceres", "Deleting %d\n", number);
sess->deleted += ({ number });
eventWrite(fd, "+OK Message deleted\r\n");
}
break;
case "last":
CHECK_STATE(POP_TRANSACTION);
eventWrite(fd, sprintf("+OK %d\r\n", last[sess->user_name]));
tell_creator("ceres", "Returning %d for last\n", last[sess->user_name]);
break;
case "list":
CHECK_STATE(POP_TRANSACTION);
if (sizeof(bits) > 1) {
if ((sscanf(bits[1], "%d", number) != 1) || number < 1 ||
number > sess->num_messages) {
eventWrite(fd, "-ERR No such message\r\n");
} else if (member_array(number, sess->deleted) != -1) {
eventWrite(fd, sprintf("-ERR Message number %d already deleted\r\n",
number));
} else {
if (!sizeof(sess->headers)) {
load_folder(sess);
}
eventWrite(fd, sprintf("+OK %d %d\r\n", number,
sess->sizes[number-1]));
}
} else {
eventWrite(fd, "+OK Mailbox scan listing follows\r\n");
if (!sizeof(sess->headers)) {
load_folder(sess);
}
i = 0;
if (!sizeof(sess->deleted)) {
map(sess->sizes,
function(int size, int ref idx, int fd) {
reset_eval_cost();
eventWrite(fd, sprintf("%d %d\r\n", ++idx, size));
}, ref i, fd);
} else {
map(sess->sizes,
function (int size, int ref idx, int fd, int *deleted) {
reset_eval_cost();
idx++;
if (member_array(idx, deleted) == -1) {
eventWrite(fd, sprintf("%d %d\r\n", idx, size));
}
}, ref i, fd, sess->deleted);
}
eventWrite(fd, ".\r\n");
}
break;
case "noop":
CHECK_STATE(POP_TRANSACTION);
eventWrite(fd, "+OK No-op to you too!\r\n");
break;
case "pass":
CHECK_STATE(POP_AUTH_PASS);
CHECK_CMD(1, "-ERR Missing password argument\r\n");
if (!PLAYER_HANDLER->test_password(sess->user_name, rest)) {
sess->state = POP_AUTH_USER;
eventWrite(fd, "-ERR Bad login\r\n");
} else {
sess->state = POP_TRANSACTION;
load_folder(sess);
// sess->headers = FOLDER_H->get_messages(sess->user_name, "inbox");
// sess->num_messages = sizeof(sess->headers);
eventWrite(fd, sprintf("+OK Mailbox open, %d messages\r\n",
sess->num_messages));
}
break;
case "quit":
switch (sess->state) {
case POP_AUTH_USER:
case POP_AUTH_PASS:
eventWrite(fd, "+OK Sayonara\r\n", 1);
break;
case POP_TRANSACTION:
sess->state = POP_UPDATE;
sign_off(sess);
break;
}
break;
case "retr":
CHECK_STATE(POP_TRANSACTION);
CHECK_CMD(1, "-ERR Missing message number argument\r\n");
if ((sscanf(bits[1], "%d", number) != 1) || number < 1 ||
number > sess->num_messages) {
eventWrite(fd, "-ERR No such message\r\n");
} else if (member_array(number, sess->deleted) != -1) {
eventWrite(fd, sprintf("-ERR Message number %d already deleted\r\n",
number));
} else {
if (!sizeof(sess->headers)) {
load_folder(sess);
}
last[sess->user_name] = number;
if(!sess->messages[number-1])
load_message(sess, number-1);
message = replace_string(sess->messages[number-1], "\n.", "\n..");
eventWrite(fd, sprintf("+OK %d octets\r\n", sess->sizes[number-1]));
eventWrite(fd, message + ".\r\n");
}
break;
case "rset":
CHECK_STATE(POP_TRANSACTION);
sess->deleted = ({});
last[sess->user_name] = 0;
eventWrite(fd, "+OK Reset state\r\n");
break;
case "stat":
CHECK_STATE(POP_TRANSACTION);
if (!sizeof(sess->headers)) {
load_folder(sess);
}
if (!sizeof(sess->deleted)) {
tell_creator("ceres", "headers: %d, sizes %d\n", sizeof(sess->headers),
sizeof(sess->sizes));
eventWrite(fd, sprintf("+OK %d %d\r\n", sess->num_messages,
implode(sess->sizes, (: $1 + $2 :))));
} else {
i = 0;
sizes = map(sess->sizes, function(int size, int ref idx, int *deleted) {
reset_eval_cost();
idx++;
if (member_array(idx, deleted) != -1) {
return 0;
} else {
return size;
}
}, ref i, sess->deleted);
eventWrite(fd, sprintf("+OK %d %d\r\n",
(sess->num_messages - sizeof(sess->deleted)),
implode(sizes,
(: reset_eval_cost(), $1 + $2 :))));
}
break;
case "top":
CHECK_STATE(POP_TRANSACTION);
CHECK_CMD(1, "-ERR Missing message number argument\r\n");
if ((sscanf(bits[1], "%d", number) != 1) || number < 1 ||
number > sess->num_messages) {
eventWrite(fd, "-ERR No such message\r\n");
} else if (member_array(number, sess->deleted) != -1) {
eventWrite(fd, sprintf("-ERR Message number %d already deleted\r\n",
number));
} else {
if (!sizeof(sess->headers)) {
load_folder(sess);
}
last[sess->user_name] = number;
if(!sess->messages[number-1])
load_message(sess, number-1);
if (sizeof(bits) > 2 && (sscanf(bits[2], "%d", lines) == 1) &&
lines < sess->sizes[number-1]) {
i = strsrch(sess->messages[number-1], "\r\n\r\n");
header = sess->messages[number-1][0..i+3];
message = replace_string(sess->messages[number-1][i+4..], "\n.",
"\n..");
eventWrite(fd, sprintf("+OK %d octets\r\n", sess->sizes[number-1]));
eventWrite(fd, header);
if (lines) {
bits = explode(message, "\r\n");
eventWrite(fd, implode(bits[0..lines-1], "\r\n") + "\r\n.\r\n");
} else {
eventWrite(fd, ".\r\n");
}
} else {
message = replace_string(sess->messages[number-1], "\n.", "\n..");
eventWrite(fd, sprintf("+OK %d octets\r\n", sess->sizes[number-1]));
eventWrite(fd, message + ".\r\n");
}
}
break;
case "uidl":
CHECK_STATE(POP_TRANSACTION);
if (sizeof(bits) > 1) {
if ((sscanf(bits[1], "%d", number) != 1) || number < 1 ||
number > sess->num_messages) {
eventWrite(fd, "-ERR No such message\r\n");
} else if (member_array(number, sess->deleted) != -1) {
eventWrite(fd, sprintf("-ERR Message number %d already deleted\r\n",
number));
} else {
if (!sizeof(sess->headers)) {
load_folder(sess);
}
eventWrite(fd, sprintf("+OK %d %d\r\n", number,
sess->headers[number-1]->number));
}
} else {
eventWrite(fd, "+OK Unique-ID listing follows\r\n");
if (!sizeof(sess->headers)) {
load_folder(sess);
}
i = 0;
if (!sizeof(sess->deleted)) {
map(sess->headers,
function(class mail_header hdr, int ref idx, int fd) {
reset_eval_cost();
eventWrite(fd, sprintf("%d %d\r\n", ++idx, hdr->number));
}, ref i, fd);
} else {
map(sess->headers,
function (class mail_header hdr, int ref idx, int fd, int *deleted) {
reset_eval_cost();
idx++;
if (member_array(idx, deleted) == -1) {
eventWrite(fd, sprintf("%d %d\r\n", idx, hdr->number));
}
}, ref i, fd, sess->deleted);
}
eventWrite(fd, ".\r\n");
}
break;
case "user":
CHECK_STATE(POP_AUTH_USER);
CHECK_CMD(1, "-ERR Missing username argument\r\n");
sess->user_name = bits[1];
sess->state = POP_AUTH_PASS;
eventWrite(fd, "+OK User name accepted, password please\r\n");
break;
case "":
eventWrite(fd, "-ERR Null command\r\n");
break;
default:
eventWrite(fd, sprintf("-ERR Unknown command in %s state\r\n",
pop_states[sess->state]));
break;
}
} /* parse_comm() */
/** @ignore yes */
protected void close_connection(class pop_session sess) {
if (!sess) {
return;
}
eventWrite(sess->fd, "-ERR Autologout; idle for too long\r\n", 1);
} /* close_connection() */
/** @ignore yes */
protected void clean_sockets() {
class pop_session sess;
foreach (sess in values(Sockets)) {
if (!sess->time) {
sess->time = time();
continue;
}
if (time() - sess->time > 1800)
close_connection(sess);
}
call_out("clean_sockets", 180);
} /* clean_sockets() */
mixed *stats() {
if(!cache)
cache = ([ ]);
return ({
({ "reads", reads, }),
({ "cache hit percent", reads != 0 ? (hits * 100) / reads : 0, }),
({ "messages in cache", sizeof(keys(cache)), }),
});
}