Removed the 'master user' feature ... if we still need this we'll find another way
[citadel.git] / citadel / modules / nntp / serv_nntp.c
index 26b0ccd4f9a7bc55e22846353e7d0800d15a4fba..14f75db6ce32328f38133dec8e5a044648707270 100644 (file)
@@ -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);
@@ -661,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");
@@ -745,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;
        }
 
@@ -940,15 +966,88 @@ 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;
+       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("500 not implemented yet FIXME\r\n");
+       cprintf("224 Overview information follows\r\n");
+       CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
+       cprintf(".\r\n");
 }
 
 
@@ -1034,7 +1133,11 @@ void nntp_command_loop(void)
                nntp_last_next(ChrPtr(Cmd));
        }
 
-       else if (!strcasecmp(cmdname, "xover")) {
+       else if (
+                       (!strcasecmp(cmdname, "xover"))
+                       || (!strcasecmp(cmdname, "over"))
+               )
+       {
                nntp_xover(ChrPtr(Cmd));
        }
 
@@ -1069,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,
@@ -1082,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);