X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fserv_pop3.c;h=17255d1d16b65ee4e7c7362f90b7784de79d76df;hb=02d9dbca862ffa00b2041287596119a54867762c;hp=a9d77be7197a0eaa95f572112843acccde9e39a8;hpb=9164027a0a2ff00c949d9a8ba40c70916c276270;p=citadel.git diff --git a/citadel/serv_pop3.c b/citadel/serv_pop3.c index a9d77be71..17255d1d1 100644 --- a/citadel/serv_pop3.c +++ b/citadel/serv_pop3.c @@ -1,24 +1,18 @@ /* * $Id$ * - * POP3 server for the Citadel/UX system - * Copyright (C) 1998-2000 by Art Cancro and others. + * POP3 service for the Citadel system + * Copyright (C) 1998-2001 by Art Cancro and others. * This code is released under the terms of the GNU General Public License. * * Current status of standards conformance: * * -> All required POP3 commands described in RFC1939 are implemented. - * - * -> Nearly all of the optional commands in RFC1939 are also implemented. - * The only one missing is APOP, because it implements a "shared secret" - * method of authentication which would require some major changes to the - * Citadel server core. - * - * This is no longer true- APOP is implemented. - * + * -> All optional POP3 commands described in RFC1939 are also implemented. * -> The deprecated "LAST" command is included in this implementation, because * there exist mail clients which insist on using it (such as Bynari * TradeMail, and certain versions of Eudora). + * -> Capability detection via the method described in RFC2449 is implemented. * */ @@ -53,7 +47,7 @@ #include "citserver.h" #include "support.h" #include "config.h" -#include "dynloader.h" +#include "serv_extensions.h" #include "room_ops.h" #include "user_ops.h" #include "policy.h" @@ -64,7 +58,10 @@ #include "serv_pop3.h" #include "md5.h" -long SYM_POP3; +#ifdef HAVE_OPENSSL +#include "serv_crypto.h" +#endif + /* @@ -72,19 +69,14 @@ long SYM_POP3; * the POP3 server. */ void pop3_cleanup_function(void) { - int i; /* Don't do this stuff if this is not a POP3 session! */ if (CC->h_command_function != pop3_command_loop) return; - lprintf(9, "Performing POP3 cleanup hook\n"); + lprintf(CTDL_DEBUG, "Performing POP3 cleanup hook\n"); + if (POP3->msgs != NULL) free(POP3->msgs); - if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { - fclose(POP3->msgs[i].temp); - } - if (POP3->msgs != NULL) phree(POP3->msgs); - - lprintf(9, "Finished POP3 cleanup hook\n"); + free(POP3); } @@ -95,15 +87,26 @@ void pop3_cleanup_function(void) { void pop3_greeting(void) { strcpy(CC->cs_clientname, "POP3 session"); CC->internal_pgm = 1; - CtdlAllocUserData(SYM_POP3, sizeof(struct citpop3)); - POP3->msgs = NULL; - POP3->num_msgs = 0; + POP3 = malloc(sizeof(struct citpop3)); + memset(POP3, 0, sizeof(struct citpop3)); - cprintf("+OK Citadel/UX POP3 server %s\r\n", + cprintf("+OK Citadel POP3 server %s\r\n", CC->cs_nonce); } +/* + * POP3S is just like POP3, except it goes crypto right away. + */ +#ifdef HAVE_OPENSSL +void pop3s_greeting(void) { + CtdlStartTLS(NULL, NULL, NULL); + pop3_greeting(); +} +#endif + + + /* * Specify user name (implements POP3 "USER" command) */ @@ -118,7 +121,7 @@ void pop3_user(char *argbuf) { strcpy(username, argbuf); striplt(username); - lprintf(9, "Trying <%s>\n", username); + /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */ if (CtdlLoginExistingUser(username) == login_ok) { cprintf("+OK Password required for %s\r\n", username); } @@ -133,23 +136,34 @@ void pop3_user(char *argbuf) { * Back end for pop3_grab_mailbox() */ void pop3_add_message(long msgnum, void *userdata) { - FILE *fp; - lprintf(9, "in pop3_add_message()\n"); + struct MetaData smi; ++POP3->num_msgs; - if (POP3->num_msgs < 2) POP3->msgs = mallok(sizeof(struct pop3msg)); - else POP3->msgs = reallok(POP3->msgs, + if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg)); + else POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ; POP3->msgs[POP3->num_msgs-1].msgnum = msgnum; POP3->msgs[POP3->num_msgs-1].deleted = 0; - fp = tmpfile(); - POP3->msgs[POP3->num_msgs-1].temp = fp; - - CtdlRedirectOutput(fp, -1); - CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1); - CtdlRedirectOutput(NULL, -1); - POP3->msgs[POP3->num_msgs-1].rfc822_length = ftell(fp); + /* We need to know the length of this message when it is printed in + * RFC822 format. Perhaps we have cached this length in the message's + * metadata record. If so, great; if not, measure it and then cache + * it for next time. + */ + GetMetaData(&smi, msgnum); + if (smi.meta_rfc822_length <= 0L) { + CC->redirect_buffer = malloc(SIZ); + CC->redirect_len = 0; + CC->redirect_alloc = SIZ; + CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL); + smi.meta_rfc822_length = CC->redirect_len; + free(CC->redirect_buffer); + CC->redirect_buffer = NULL; + CC->redirect_len = 0; + CC->redirect_alloc = 0; + PutMetaData(&smi); + } + POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length; } @@ -163,17 +177,17 @@ int pop3_grab_mailbox(void) { struct visit vbuf; int i; - if (getroom(&CC->quickroom, MAILROOM) != 0) return(-1); + if (getroom(&CC->room, MAILROOM) != 0) return(-1); /* Load up the messages */ - CtdlForEachMessage(MSGS_ALL, 0L, (-63), NULL, NULL, + CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL); /* Figure out which are old and which are new */ - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + CtdlGetRelationship(&vbuf, &CC->user, &CC->room); POP3->lastseen = (-1); if (POP3->num_msgs) for (i=0; inum_msgs; ++i) { - if (is_msg_in_mset(vbuf.v_seen, + if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) { POP3->lastseen = i; } @@ -189,8 +203,8 @@ void pop3_login(void) msgs = pop3_grab_mailbox(); if (msgs >= 0) { cprintf("+OK %s is logged in (%d messages)\r\n", - CC->usersupp.fullname, msgs); - lprintf(9, "POP3 password login successful\n"); + CC->user.fullname, msgs); + lprintf(CTDL_NOTICE, "POP3 authenticated %s\n", CC->user.fullname); } else { cprintf("-ERR Can't open your mailbox\r\n"); @@ -234,13 +248,13 @@ void pop3_apop(char *argbuf) return; } - if (getuser(&CC->usersupp, CC->curr_user)) + if (getuser(&CC->user, CC->curr_user)) { cprintf("-ERR No such user.\r\n"); return; } - make_apop_string(CC->usersupp.password, CC->cs_nonce, realdigest); + make_apop_string(CC->user.password, CC->cs_nonce, realdigest, sizeof realdigest); if (!strncasecmp(realdigest, userdigest, MD5_HEXSTRING_SIZE-1)) { do_login(); @@ -262,7 +276,7 @@ void pop3_pass(char *argbuf) { strcpy(password, argbuf); striplt(password); - lprintf(9, "Trying <%s>\n", password); + /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */ if (CtdlTryPassword(password) == pass_ok) { pop3_login(); } @@ -342,8 +356,6 @@ void pop3_stat(char *argbuf) { */ void pop3_retr(char *argbuf) { int which_one; - int ch = 0; - size_t bytes_remaining; which_one = atoi(argbuf); if ( (which_one < 1) || (which_one > POP3->num_msgs) ) { @@ -357,15 +369,7 @@ void pop3_retr(char *argbuf) { } cprintf("+OK Message %d:\r\n", which_one); - bytes_remaining = POP3->msgs[which_one -1].rfc822_length; - rewind(POP3->msgs[which_one - 1].temp); - while (bytes_remaining-- > 0) { - ch = getc(POP3->msgs[which_one - 1].temp); - cprintf("%c", ch); - } - if (ch != 10) { - lprintf(5, "Problem: message ends with 0x%2x, not 0x0a\n", ch); - } + CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL); cprintf(".\r\n"); } @@ -378,6 +382,7 @@ void pop3_top(char *argbuf) { int lines_requested = 0; int lines_dumped = 0; char buf[1024]; + char *msgtext; char *ptr; int in_body = 0; int done = 0; @@ -393,18 +398,40 @@ void pop3_top(char *argbuf) { return; } + CC->redirect_buffer = malloc(SIZ); + CC->redirect_len = 0; + CC->redirect_alloc = SIZ; + CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, + MT_RFC822, HEADERS_ALL, 0, 1, NULL); + msgtext = CC->redirect_buffer; + CC->redirect_buffer = NULL; + CC->redirect_len = 0; + CC->redirect_alloc = 0; + cprintf("+OK Message %d:\r\n", which_one); - rewind(POP3->msgs[which_one - 1].temp); - while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp), - ( (ptr!=NULL) && (done == 0))) { - if (in_body == 1) - if (lines_dumped >= lines_requested) done = 1; - if ((in_body == 0) || (done == 0)) + + ptr = msgtext; + + while (ptr = memreadline(ptr, buf, (sizeof buf - 2)), + ( (*ptr != 0) && (done == 0))) { + strcat(buf, "\r\n"); + if (in_body == 1) { + if (lines_dumped >= lines_requested) { + done = 1; + } + } + if ((in_body == 0) || (done == 0)) { client_write(buf, strlen(buf)); - if (in_body) ++lines_dumped; + } + if (in_body) { + ++lines_dumped; + } if ((buf[0]==13)||(buf[0]==10)) in_body = 1; } + if (buf[strlen(buf)-1] != 10) cprintf("\n"); + free(msgtext); + cprintf(".\r\n"); } @@ -439,24 +466,33 @@ void pop3_update(void) { int i; struct visit vbuf; + long *deletemsgs = NULL; + int num_deletemsgs = 0; + /* Remove messages marked for deletion */ - if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { - if (POP3->msgs[i].deleted) { - CtdlDeleteMessages(MAILROOM, - POP3->msgs[i].msgnum, ""); + if (POP3->num_msgs > 0) { + deletemsgs = malloc(POP3->num_msgs * sizeof(long)); + for (i=0; inum_msgs; ++i) { + if (POP3->msgs[i].deleted) { + deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum; + } + } + if (num_deletemsgs > 0) { + CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, ""); } + free(deletemsgs); } /* Set last read pointer */ if (POP3->num_msgs > 0) { - lgetuser(&CC->usersupp, CC->curr_user); + lgetuser(&CC->user, CC->curr_user); - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); - sprintf(vbuf.v_seen, "*:%ld", + CtdlGetRelationship(&vbuf, &CC->user, &CC->room); + snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum); - CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + CtdlSetRelationship(&vbuf, &CC->user, &CC->room); - lputuser(&CC->usersupp); + lputuser(&CC->user); } } @@ -486,6 +522,23 @@ void pop3_last(char *argbuf) { } +/* + * CAPA is a command which tells the client which POP3 extensions + * are supported. + */ +void pop3_capa(void) { + cprintf("+OK Capability list follows\r\n" + "TOP\r\n" + "USER\r\n" + "UIDL\r\n" + "IMPLEMENTATION %s\r\n" + ".\r\n" + , + CITADEL + ); +} + + /* * UIDL (Universal IDentifier Listing) is easy. Our 'unique' message @@ -532,6 +585,30 @@ void pop3_uidl(char *argbuf) { } +/* + * implements the STLS command (Citadel API version) + */ +#ifdef HAVE_OPENSSL +void pop3_stls(void) +{ + char ok_response[SIZ]; + char nosup_response[SIZ]; + char error_response[SIZ]; + + sprintf(ok_response, + "+OK Begin TLS negotiation now\r\n"); + sprintf(nosup_response, + "-ERR TLS not supported here\r\n"); + sprintf(error_response, + "-ERR Internal error\r\n"); + CtdlStartTLS(ok_response, nosup_response, error_response); +} +#endif + + + + + /* @@ -542,18 +619,27 @@ void pop3_command_loop(void) { time(&CC->lastcmd); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ - if (client_gets(cmdbuf) < 1) { - lprintf(3, "POP3 socket is broken. Ending session.\r\n"); + if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { + lprintf(CTDL_ERR, "Client disconnected: ending session.\r\n"); CC->kill_me = 1; return; } - lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf); + if (!strncasecmp(cmdbuf, "PASS", 4)) { + lprintf(CTDL_INFO, "POP3: PASS...\r\n"); + } + else { + lprintf(CTDL_INFO, "POP3: %s\r\n", cmdbuf); + } while (strlen(cmdbuf) < 5) strcat(cmdbuf, " "); if (!strncasecmp(cmdbuf, "NOOP", 4)) { cprintf("+OK No operation.\r\n"); } + else if (!strncasecmp(cmdbuf, "CAPA", 4)) { + pop3_capa(); + } + else if (!strncasecmp(cmdbuf, "QUIT", 4)) { cprintf("+OK Goodbye...\r\n"); pop3_update(); @@ -574,6 +660,12 @@ void pop3_command_loop(void) { pop3_apop(&cmdbuf[5]); } +#ifdef HAVE_OPENSSL + else if (!strncasecmp(cmdbuf, "STLS", 4)) { + pop3_stls(); + } +#endif + else if (!CC->logged_in) { cprintf("-ERR Not logged in.\r\n"); } @@ -618,13 +710,20 @@ void pop3_command_loop(void) { -char *Dynamic_Module_Init(void) +char *serv_pop3_init(void) { - SYM_POP3 = CtdlGetDynamicSymbol(); CtdlRegisterServiceHook(config.c_pop3_port, NULL, pop3_greeting, - pop3_command_loop); + pop3_command_loop, + NULL); +#ifdef HAVE_OPENSSL + CtdlRegisterServiceHook(config.c_pop3s_port, + NULL, + pop3s_greeting, + pop3_command_loop, + NULL); +#endif CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP); return "$Id$"; }