X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fnntp%2Fserv_nntp.c;h=14f75db6ce32328f38133dec8e5a044648707270;hb=e329db30593524cc2d8851a4500bac41f2340354;hp=198261005328147f5f2e03695377f99c9ab74425;hpb=da653352ce377ce6a896b863f33ee19e3da6184e;p=citadel.git diff --git a/citadel/modules/nntp/serv_nntp.c b/citadel/modules/nntp/serv_nntp.c index 198261005..14f75db6c 100644 --- a/citadel/modules/nntp/serv_nntp.c +++ b/citadel/modules/nntp/serv_nntp.c @@ -1,7 +1,7 @@ // -// NNTP server module FIXME THIS IS NOT FINISHED +// NNTP server module (RFC 3977) // -// Copyright (c) 2014 by the citadel.org team +// Copyright (c) 2014-2018 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. @@ -74,6 +74,7 @@ extern long timezone; int is_valid_newsgroup_name(char *name) { char *ptr = name; int has_a_letter = 0; + int num_dots = 0; if (!ptr) return(0); if (!strncasecmp(name, "ctdl.", 5)) return(0); @@ -84,6 +85,10 @@ int is_valid_newsgroup_name(char *name) { has_a_letter = 1; } + if (ptr[0] == '.') { + ++num_dots; + } + if ( (isalnum(ptr[0])) || (ptr[0] == '.') || (ptr[0] == '+') @@ -95,7 +100,7 @@ int is_valid_newsgroup_name(char *name) { return(0); } } - return(has_a_letter); + return( (has_a_letter) && (num_dots >= 1) ) ; } @@ -123,7 +128,7 @@ void room_to_newsgroup(char *target, char *source, size_t target_size) { || (ch == '.') || (ch == '-') ) { - target[len++] = ch; + target[len++] = tolower(ch); target[len] = 0; } else { @@ -195,7 +200,7 @@ void nntp_greeting(void) } // Display the standard greeting - cprintf("200 %s NNTP Citadel server is not finished yet\r\n", config.c_fqdn); + cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn")); } @@ -233,11 +238,12 @@ void nntp_starttls(void) void nntp_capabilities(void) { cprintf("101 Capability list:\r\n"); - cprintf("IMPLEMENTATION Citadel v%d.%02d\r\n", (REV_LEVEL/100), (REV_LEVEL%100)); + cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL); cprintf("VERSION 2\r\n"); cprintf("READER\r\n"); cprintf("MODE-READER\r\n"); cprintf("LIST ACTIVE NEWSGROUPS\r\n"); + cprintf("OVER\r\n"); #ifdef HAVE_OPENSSL cprintf("STARTTLS\r\n"); #endif @@ -272,13 +278,13 @@ void nntp_cleanup(void) // void nntp_authinfo_user(const char *username) { - int a = CtdlLoginExistingUser(NULL, username); + int a = CtdlLoginExistingUser(username); switch (a) { case login_already_logged_in: cprintf("482 Already logged in\r\n"); return; case login_too_many_users: - cprintf("481 Too many users are already online (maximum is %d)\r\n", config.c_maxsessions); + cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions")); return; case login_ok: cprintf("381 Password required for %s\r\n", CC->curr_user); @@ -509,11 +515,30 @@ void nntp_list(const char *cmd) { else if (!strcasecmp(list_format, "NEWSGROUPS")) { nld.list_format = NNTP_LIST_NEWSGROUPS; } + else if (!strcasecmp(list_format, "OVERVIEW.FMT")) { + nld.list_format = NNTP_LIST_OVERVIEW_FMT; + } else { cprintf("501 syntax error , unsupported list format\r\n"); return; } + // OVERVIEW.FMT delivers a completely different type of data than all of the + // other LIST commands. It's a stupid place to put it. But that's how it's + // written into RFC3977, so we have to handle it here. + if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) { + cprintf("215 Order of fields in overview database.\r\n"); + cprintf("Subject:\r\n"); + cprintf("From:\r\n"); + cprintf("Date:\r\n"); + cprintf("Message-ID:\r\n"); + cprintf("References:\r\n"); + cprintf("Bytes:\r\n"); + cprintf("Lines:\r\n"); + cprintf(".\r\n"); + return; + } + cprintf("215 list of newsgroups follows\r\n"); CtdlGetUser(&CC->user, CC->curr_user); CtdlForEachRoom(nntp_list_backend, &nld); @@ -531,6 +556,25 @@ void nntp_help(void) { } +// +// 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 // @@ -642,7 +686,8 @@ void nntp_mode(const char *cmd) { extract_token(which_mode, cmd, 1, ' ', sizeof which_mode); if (!strcasecmp(which_mode, "reader")) { - cprintf("201 Reader mode FIXME implement posting and change to 200\r\n"); + // FIXME implement posting and change to 200 + cprintf("201 Reader mode activated\r\n"); } else { cprintf("501 unknown mode\r\n"); @@ -726,7 +771,7 @@ void nntp_article(const char *cmd) { // 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"); + cprintf("500 I don't know how to fetch by message-id yet.\r\n"); // FIXME return; } @@ -921,6 +966,91 @@ void nntp_last_next(const char *cmd) { } +// +// back end for the XOVER command , called for each message number +// +void nntp_xover_backend(long msgnum, void *userdata) { + + struct listgroup_range *lr = (struct listgroup_range *)userdata; + + // check range if supplied + if (msgnum < lr->lo) return; + if ((lr->hi != 0) && (msgnum > lr->hi)) return; + + struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0, 1); + if (msg == NULL) { + return; + } + + // Teh RFC says we need: + // ------------------------- + // Subject header content + // From header content + // Date header content + // Message-ID header content + // References header content + // :bytes metadata item + // :lines metadata item + + time_t msgtime = atol(msg->cm_fields[eTimestamp]); + char strtimebuf[26]; + ctime_r(&msgtime, strtimebuf); + + // here we go -- print the line o'data + cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n", + msgnum, + msg->cm_fields[eMsgSubject], + msg->cm_fields[eAuthor], + msg->cm_fields[erFc822Addr], + strtimebuf, + msg->cm_fields[emessageId], + msg->cm_fields[eWeferences] + ); + + CM_Free(msg); +} + + +// +// +// 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; + char range[256]; + struct listgroup_range lr; + + extract_token(range, cmd, 1, ' ', sizeof range); + lr.lo = atol(range); + if (lr.lo <= 0) { + lr.lo = nntpstate->current_article_number; + lr.hi = nntpstate->current_article_number; + } + else { + char *dash = strchr(range, '-'); + if (dash != NULL) { + ++dash; + lr.hi = atol(dash); + if (lr.hi == 0) { + lr.hi = LONG_MAX; + } + if (lr.hi < lr.lo) { + lr.hi = lr.lo; + } + } + else { + lr.hi = lr.lo; + } + } + + cprintf("224 Overview information follows\r\n"); + CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr); + cprintf(".\r\n"); +} + + // // Main command loop for NNTP server sessions. // @@ -949,6 +1079,10 @@ void nntp_command_loop(void) nntp_help(); } + else if (!strcasecmp(cmdname, "date")) { + nntp_date(); + } + else if (!strcasecmp(cmdname, "capabilities")) { nntp_capabilities(); } @@ -999,6 +1133,14 @@ void nntp_command_loop(void) nntp_last_next(ChrPtr(Cmd)); } + else if ( + (!strcasecmp(cmdname, "xover")) + || (!strcasecmp(cmdname, "over")) + ) + { + nntp_xover(ChrPtr(Cmd)); + } + else { cprintf("500 I'm afraid I can't do that.\r\n"); } @@ -1030,12 +1172,13 @@ void nntp_cleanup_function(void) } const char *CitadelServiceNNTP="NNTP"; +const char *CitadelServiceNNTPS="NNTPS"; CTDL_MODULE_INIT(nntp) { if (!threading) { - CtdlRegisterServiceHook(119, // FIXME config.c_nntp_port, + CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntp_port"), NULL, nntp_greeting, nntp_command_loop, @@ -1043,12 +1186,12 @@ CTDL_MODULE_INIT(nntp) CitadelServiceNNTP); #ifdef HAVE_OPENSSL - CtdlRegisterServiceHook(563, // FIXME config.c_nntps_port, + CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"), NULL, nntps_greeting, nntp_command_loop, NULL, - CitadelServiceNNTP); + CitadelServiceNNTPS); #endif CtdlRegisterCleanupHook(nntp_cleanup);