X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fnntp%2Fserv_nntp.c;h=26b0ccd4f9a7bc55e22846353e7d0800d15a4fba;hb=01ac2ae1ffc8fa65e6459d7c6fd9a2a368093504;hp=86368596002aeb59d8aef9b99daa6d974b8010e3;hpb=2ffa7f5d58eec15aa17f6b7372740f0e15d81d64;p=citadel.git diff --git a/citadel/modules/nntp/serv_nntp.c b/citadel/modules/nntp/serv_nntp.c index 863685960..26b0ccd4f 100644 --- a/citadel/modules/nntp/serv_nntp.c +++ b/citadel/modules/nntp/serv_nntp.c @@ -1,16 +1,16 @@ -/* - * NNTP server module FIXME THIS IS NOT FINISHED - * - * Copyright (c) 2014 by the citadel.org team - * - * This program is open source software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// +// NNTP server module FIXME THIS IS NOT FINISHED +// +// Copyright (c) 2014 by the citadel.org team +// +// This program is open source software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// #include "sysdep.h" #include @@ -64,13 +64,13 @@ extern long timezone; -/****************** BEGIN UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER **************/ +// ***************** BEGIN UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER ************** -/* - * Tests whether the supplied string is a valid newsgroup name - * Returns true (nonzero) or false (0) - */ +// +// Tests whether the supplied string is a valid newsgroup name +// Returns true (nonzero) or false (0) +// int is_valid_newsgroup_name(char *name) { char *ptr = name; int has_a_letter = 0; @@ -99,10 +99,9 @@ int is_valid_newsgroup_name(char *name) { } - -/* - * Convert a Citadel room name to a valid newsgroup name - */ +// +// Convert a Citadel room name to a valid newsgroup name +// void room_to_newsgroup(char *target, char *source, size_t target_size) { if (!target) return; @@ -137,10 +136,10 @@ void room_to_newsgroup(char *target, char *source, size_t target_size) { } -/* - * Convert a newsgroup name to a Citadel room name. - * This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity. - */ +// +// Convert a newsgroup name to a Citadel room name. +// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity. +// void newsgroup_to_room(char *target, char *source, size_t target_size) { if (!target) return; @@ -173,21 +172,21 @@ void newsgroup_to_room(char *target, char *source, size_t target_size) { } -/****************** END UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER **************/ +// ***************** END UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER ************** -/* - * Here's where our NNTP session begins its happy day. - */ +// +// Here's where our NNTP session begins its happy day. +// void nntp_greeting(void) { strcpy(CC->cs_clientname, "NNTP session"); CC->cs_flags |= CS_STEALTH; - /* CC->session_specific_data = malloc(sizeof(citnntp)); - memset(NNTP, 0, sizeof(citnntp)); - */ + CC->session_specific_data = malloc(sizeof(citnntp)); + citnntp *nntpstate = (citnntp *) CC->session_specific_data; + memset(nntpstate, 0, sizeof(citnntp)); if (CC->nologin==1) { cprintf("451 Too many connections are already open; please try again later.\r\n"); @@ -195,16 +194,14 @@ void nntp_greeting(void) return; } - /* Note: the FQDN *must* appear as the first thing after the 220 code. - * Some clients (including citmail.c) depend on it being there. - */ + // Display the standard greeting cprintf("200 %s NNTP Citadel server is not finished yet\r\n", config.c_fqdn); } -/* - * NNTPS is just like NNTP, except it goes crypto right away. - */ +// +// NNTPS is just like NNTP, except it goes crypto right away. +// void nntps_greeting(void) { CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); #ifdef HAVE_OPENSSL @@ -214,10 +211,9 @@ void nntps_greeting(void) { } - -/* - * implements the STARTTLS command - */ +// +// implements the STARTTLS command +// void nntp_starttls(void) { char ok_response[SIZ]; @@ -231,6 +227,9 @@ void nntp_starttls(void) } +// +// Implements the CAPABILITY command +// void nntp_capabilities(void) { cprintf("101 Capability list:\r\n"); @@ -249,6 +248,9 @@ void nntp_capabilities(void) } +// +// Implements the QUIT command +// void nntp_quit(void) { cprintf("221 Goodbye...\r\n"); @@ -256,16 +258,18 @@ void nntp_quit(void) } +// +// Cleanup hook for this module +// void nntp_cleanup(void) { /* nothing here yet */ } - -/* - * Implements the AUTHINFO USER command (RFC 4643) - */ +// +// Implements the AUTHINFO USER command (RFC 4643) +// void nntp_authinfo_user(const char *username) { int a = CtdlLoginExistingUser(NULL, username); @@ -288,9 +292,9 @@ void nntp_authinfo_user(const char *username) } -/* - * Implements the AUTHINFO PASS command (RFC 4643) - */ +// +// Implements the AUTHINFO PASS command (RFC 4643) +// void nntp_authinfo_pass(const char *buf) { int a; @@ -314,10 +318,9 @@ void nntp_authinfo_pass(const char *buf) } - -/* - * Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode - */ +// +// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode +// void nntp_authinfo(const char *cmd) { if (!strncasecmp(cmd, "authinfo user ", 14)) { @@ -334,9 +337,9 @@ void nntp_authinfo(const char *cmd) { } -/* - * Utility function to fetch the current list of message numbers in a room - */ +// +// Utility function to fetch the current list of message numbers in a room +// struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) { struct nntp_msglist nm; struct cdbdata *cdbfr; @@ -356,23 +359,9 @@ struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) { } - -/* - * Various output formats for the LIST commands - */ -enum { - NNTP_LIST_ACTIVE, - NNTP_LIST_ACTIVE_TIMES, - NNTP_LIST_DISTRIB_PATS, - NNTP_LIST_HEADERS, - NNTP_LIST_NEWSGROUPS, - NNTP_LIST_OVERVIEW_FMT -}; - - -/* - * Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command - */ +// +// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command +// void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) { char n_name[1024]; struct nntp_msglist nm; @@ -410,10 +399,9 @@ void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, ch } - -/* - * Called once per room by nntp_newgroups() to qualify and possibly output a single room - */ +// +// Called once per room by nntp_newgroups() to qualify and possibly output a single room +// void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) { int ra; @@ -437,17 +425,12 @@ void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) } -/* - * Implements the NEWGROUPS command - */ +// +// Implements the NEWGROUPS command +// void nntp_newgroups(const char *cmd) { - /* - * HACK: this works because the 5XX series error codes from citadel - * protocol will also be considered error codes by an NNTP client - */ if (CtdlAccessCheck(ac_logged_in_or_guest)) return; - char stringy_date[16]; char stringy_time[16]; char stringy_gmt[16]; @@ -484,9 +467,9 @@ void nntp_newgroups(const char *cmd) { } -/* - * Called once per room by nntp_list() to qualify and possibly output a single room - */ +// +// Called once per room by nntp_list() to qualify and possibly output a single room +// void nntp_list_backend(struct ctdlroom *qrbuf, void *data) { int ra; @@ -500,14 +483,10 @@ void nntp_list_backend(struct ctdlroom *qrbuf, void *data) } -/* - * Implements the LIST commands - */ +// +// Implements the LIST commands +// void nntp_list(const char *cmd) { - /* - * HACK: this works because the 5XX series error codes from citadel - * protocol will also be considered error codes by an NNTP client - */ if (CtdlAccessCheck(ac_logged_in_or_guest)) return; char list_format[64]; @@ -535,16 +514,16 @@ void nntp_list(const char *cmd) { return; } - cprintf("231 list of newsgroups follows\r\n"); + cprintf("215 list of newsgroups follows\r\n"); CtdlGetUser(&CC->user, CC->curr_user); CtdlForEachRoom(nntp_list_backend, &nld); cprintf(".\r\n"); } -/* - * Implement HELP command. - */ +// +// Implement HELP command. +// void nntp_help(void) { cprintf("100 This is the Citadel NNTP service.\r\n"); cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n"); @@ -552,9 +531,28 @@ void nntp_help(void) { } -/* - * back end for the LISTGROUP command , called for each message number - */ +// +// Implement DATE command. +// +void nntp_date(void) { + time_t now; + struct tm nowLocal; + struct tm nowUtc; + char tsFromUtc[32]; + + now = time(NULL); + localtime_r(&now, &nowLocal); + gmtime_r(&now, &nowUtc); + + strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc); + + cprintf("111 %s\r\n", tsFromUtc); +} + + +// +// back end for the LISTGROUP command , called for each message number +// void nntp_listgroup_backend(long msgnum, void *userdata) { struct listgroup_range *lr = (struct listgroup_range *)userdata; @@ -567,16 +565,13 @@ void nntp_listgroup_backend(long msgnum, void *userdata) { } -/* - * Implements the GROUP and LISTGROUP commands - */ +// +// Implements the GROUP and LISTGROUP commands +// void nntp_group(const char *cmd) { - /* - * HACK: this works because the 5XX series error codes from citadel - * protocol will also be considered error codes by an NNTP client - */ if (CtdlAccessCheck(ac_logged_in_or_guest)) return; + citnntp *nntpstate = (citnntp *) CC->session_specific_data; char verb[16]; char requested_group[1024]; char message_range[256]; @@ -644,8 +639,9 @@ void nntp_group(const char *cmd) { CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest); cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group); - // If this is a GROUP command, we can stop here. + // If this is a GROUP command, set the "current article number" to zero, and then stop here. if (!strcasecmp(verb, "GROUP")) { + nntpstate->current_article_number = oldest; return; } @@ -655,9 +651,9 @@ void nntp_group(const char *cmd) { } -/* - * Implements the MODE command - */ +// +// Implements the MODE command +// void nntp_mode(const char *cmd) { char which_mode[16]; @@ -673,10 +669,292 @@ void nntp_mode(const char *cmd) { } +// +// Implements the ARTICLE, HEAD, BODY, and STAT commands. +// (These commands all accept the same parameters; they differ only in how they output the retrieved message.) +// +void nntp_article(const char *cmd) { + if (CtdlAccessCheck(ac_logged_in_or_guest)) return; + + citnntp *nntpstate = (citnntp *) CC->session_specific_data; + char which_command[16]; + int acmd = 0; + char requested_article[256]; + long requested_msgnum = 0; + char *lb, *rb = NULL; + int must_change_currently_selected_article = 0; + + // We're going to store one of these values in the variable 'acmd' so that + // we can quickly check later which version of the output we want. + enum { + ARTICLE, + HEAD, + BODY, + STAT + }; + + extract_token(which_command, cmd, 0, ' ', sizeof which_command); + + if (!strcasecmp(which_command, "article")) { + acmd = ARTICLE; + } + else if (!strcasecmp(which_command, "head")) { + acmd = HEAD; + } + else if (!strcasecmp(which_command, "body")) { + acmd = BODY; + } + else if (!strcasecmp(which_command, "stat")) { + acmd = STAT; + } + else { + cprintf("500 I'm afraid I can't do that.\r\n"); + return; + } + + // Which NNTP command was issued, determines whether we will fetch headers, body, or both. + int headers_only = HEADERS_ALL; + if (acmd == HEAD) headers_only = HEADERS_FAST; + else if (acmd == BODY) headers_only = HEADERS_NONE; + else if (acmd == STAT) headers_only = HEADERS_FAST; + + // now figure out what the client is asking for. + extract_token(requested_article, cmd, 1, ' ', sizeof requested_article); + lb = strchr(requested_article, '<'); + rb = strchr(requested_article, '>'); + requested_msgnum = atol(requested_article); + + // If no article number or message-id is specified, the client wants the "currently selected article" + if (IsEmptyStr(requested_article)) { + if (nntpstate->current_article_number < 1) { + cprintf("420 No current article selected\r\n"); + return; + } + requested_msgnum = nntpstate->current_article_number; + must_change_currently_selected_article = 1; + // got it -- now fall through and keep going + } + + // If the requested article is numeric, it maps directly to a message number. Good. + else if (requested_msgnum > 0) { + must_change_currently_selected_article = 1; + // good -- fall through and keep going + } + + // If the requested article has angle brackets, the client wants a specific message-id. + // We don't know how to do that yet. + else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) { + must_change_currently_selected_article = 0; + cprintf("500 FIXME I don't know how to fetch by message-id yet.\r\n"); + return; + } + + // Anything else is noncompliant gobbledygook and should die in a car fire. + else { + must_change_currently_selected_article = 0; + cprintf("500 syntax error\r\n"); + return; + } + + // At this point we know the message number of the "article" being requested. + // We have an awesome API call that does all the heavy lifting for us. + char *fetched_message_id = NULL; + CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); + int fetch = CtdlOutputMsg(requested_msgnum, + MT_RFC822, // output in RFC822 format ... sort of + headers_only, // headers, body, or both? + 0, // don't do Citadel protocol responses + 1, // CRLF newlines + NULL, // teh whole thing, not just a section + 0, // no flags yet ... maybe new ones for Path: etc ? + NULL, + NULL, + &fetched_message_id // extract the message ID from the message as we go... + ); + StrBuf *msgtext = CC->redirect_buffer; + CC->redirect_buffer = NULL; + + if (fetch != om_ok) { + cprintf("423 no article with that number\r\n"); + FreeStrBuf(&msgtext); + return; + } + + // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article" + // MUST or MUST NOT be set to the message we just referenced. + if (must_change_currently_selected_article) { + nntpstate->current_article_number = requested_msgnum; + } + + // Now give the client what it asked for. + if (acmd == ARTICLE) { + cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id); + } + if (acmd == HEAD) { + cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id); + } + if (acmd == BODY) { + cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id); + } + if (acmd == STAT) { + cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id); + FreeStrBuf(&msgtext); + return; + } + + client_write(SKEY(msgtext)); + cprintf(".\r\n"); // this protocol uses a dot terminator + FreeStrBuf(&msgtext); + if (fetched_message_id) free(fetched_message_id); +} + + +// +// Utility function for nntp_last_next() that turns a msgnum into a message ID. +// The memory for the returned string is pwnz0red by the caller. +// +char *message_id_from_msgnum(long msgnum) { + + char *fetched_message_id = NULL; + CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); + CtdlOutputMsg(msgnum, + MT_RFC822, // output in RFC822 format ... sort of + HEADERS_FAST, // headers, body, or both? + 0, // don't do Citadel protocol responses + 1, // CRLF newlines + NULL, // teh whole thing, not just a section + 0, // no flags yet ... maybe new ones for Path: etc ? + NULL, + NULL, + &fetched_message_id // extract the message ID from the message as we go... + ); + StrBuf *msgtext = CC->redirect_buffer; + CC->redirect_buffer = NULL; + + FreeStrBuf(&msgtext); + return(fetched_message_id); +} + + +// +// The LAST and NEXT commands are so similar that they are handled by a single function. +// +void nntp_last_next(const char *cmd) { + if (CtdlAccessCheck(ac_logged_in_or_guest)) return; + + citnntp *nntpstate = (citnntp *) CC->session_specific_data; + char which_command[16]; + int acmd = 0; + + // We're going to store one of these values in the variable 'acmd' so that + // we can quickly check later which version of the output we want. + enum { + NNTP_LAST, + NNTP_NEXT + }; + + extract_token(which_command, cmd, 0, ' ', sizeof which_command); + + if (!strcasecmp(which_command, "last")) { + acmd = NNTP_LAST; + } + else if (!strcasecmp(which_command, "next")) { + acmd = NNTP_NEXT; + } + else { + cprintf("500 I'm afraid I can't do that.\r\n"); + return; + } + + // ok, here we go ... fetch the msglist so we can figure out our place in the universe + struct nntp_msglist nm; + int i = 0; + long selected_msgnum = 0; + char *message_id = NULL; + + nm = nntp_fetch_msglist(&CC->room); + if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) { + cprintf("500 something bad happened\r\n"); + return; + } + + if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) { + cprintf("422 no previous article in this group\r\n"); // nothing here + } + + else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) { + cprintf("422 no previous article in this group\r\n"); // already at the beginning + } + + else if (acmd == NNTP_LAST) { + for (i=0; ((i= nntpstate->current_article_number) && (i > 0) ) { + selected_msgnum = nm.msgnums[i-1]; + } + } + if (selected_msgnum > 0) { + nntpstate->current_article_number = selected_msgnum; + message_id = message_id_from_msgnum(nntpstate->current_article_number); + cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id); + if (message_id) free(message_id); + } + else { + cprintf("422 no previous article in this group\r\n"); + } + } + + else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) { + cprintf("421 no next article in this group\r\n"); // nothing here + } + + else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) { + cprintf("421 no next article in this group\r\n"); // already at the end + } + + else if (acmd == NNTP_NEXT) { + for (i=0; ((i nntpstate->current_article_number) { + selected_msgnum = nm.msgnums[i]; + } + } + if (selected_msgnum > 0) { + nntpstate->current_article_number = selected_msgnum; + message_id = message_id_from_msgnum(nntpstate->current_article_number); + cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id); + if (message_id) free(message_id); + } + else { + cprintf("421 no next article in this group\r\n"); + } + } + + // should never get here + else { + cprintf("500 internal error\r\n"); + } + + if (nm.msgnums != NULL) { + free(nm.msgnums); + } + +} + + +// +// XOVER is used by some clients, even if we don't offer it +// +void nntp_xover(const char *cmd) { + if (CtdlAccessCheck(ac_logged_in_or_guest)) return; + + // citnntp *nntpstate = (citnntp *) CC->session_specific_data; + + cprintf("500 not implemented yet FIXME\r\n"); +} + -/* - * Main command loop for NNTP server sessions. - */ +// +// Main command loop for NNTP server sessions. +// void nntp_command_loop(void) { StrBuf *Cmd = NewStrBuf(); @@ -689,12 +967,10 @@ void nntp_command_loop(void) FreeStrBuf(&Cmd); return; } - syslog(LOG_DEBUG, "NNTP server: %s\n", ChrPtr(Cmd)); + syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd))); extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname); - /* - * Rumpelstiltskin lookups are awesome - */ + // Rumpelstiltskin lookups are *awesome* if (!strcasecmp(cmdname, "quit")) { nntp_quit(); @@ -704,6 +980,10 @@ void nntp_command_loop(void) nntp_help(); } + else if (!strcasecmp(cmdname, "date")) { + nntp_date(); + } + else if (!strcasecmp(cmdname, "capabilities")) { nntp_capabilities(); } @@ -736,6 +1016,28 @@ void nntp_command_loop(void) nntp_mode(ChrPtr(Cmd)); } + else if ( + (!strcasecmp(cmdname, "article")) + || (!strcasecmp(cmdname, "head")) + || (!strcasecmp(cmdname, "body")) + || (!strcasecmp(cmdname, "stat")) + ) + { + nntp_article(ChrPtr(Cmd)); + } + + else if ( + (!strcasecmp(cmdname, "last")) + || (!strcasecmp(cmdname, "next")) + ) + { + nntp_last_next(ChrPtr(Cmd)); + } + + else if (!strcasecmp(cmdname, "xover")) { + nntp_xover(ChrPtr(Cmd)); + } + else { cprintf("500 I'm afraid I can't do that.\r\n"); } @@ -744,21 +1046,26 @@ void nntp_command_loop(void) } -/*****************************************************************************/ -/* MODULE INITIALIZATION STUFF */ -/*****************************************************************************/ +// **************************************************************************** +// MODULE INITIALIZATION STUFF +// **************************************************************************** -/* - * This cleanup function blows away the temporary memory used by - * the NNTP server. - */ +// +// This cleanup function blows away the temporary memory used by +// the NNTP server. +// void nntp_cleanup_function(void) { /* Don't do this stuff if this is not an NNTP session! */ if (CC->h_command_function != nntp_command_loop) return; syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n"); + citnntp *nntpstate = (citnntp *) CC->session_specific_data; + if (nntpstate != NULL) { + free(nntpstate); + nntpstate = NULL; + } } const char *CitadelServiceNNTP="NNTP";