X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fnntp%2Fserv_nntp.c;h=fe861374c88324b51502af74ef462f789dcd86b4;hb=9779faf1053f65d3c1bca333bf3deabcd185701d;hp=9147691a8147e8bf21f8643e77f2d595b49a0ac9;hpb=29bc46c83ca6f185a29de4ac3bf7db67c04abd7c;p=citadel.git diff --git a/citadel/modules/nntp/serv_nntp.c b/citadel/modules/nntp/serv_nntp.c index 9147691a8..fe861374c 100644 --- a/citadel/modules/nntp/serv_nntp.c +++ b/citadel/modules/nntp/serv_nntp.c @@ -60,9 +60,9 @@ #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" +#include "serv_nntp.h" - - +extern long timezone; /****************** BEGIN UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER **************/ @@ -231,15 +231,13 @@ void nntp_starttls(void) } -void nntp_noop(void) -{ - cprintf("250 NOOP\r\n"); -} - - void nntp_capabilities(void) { cprintf("101 Capability list:\r\n"); + cprintf("VERSION 2\r\n"); + cprintf("READER\r\n"); + cprintf("LIST ACTIVE NEWSGROUPS\r\n"); + cprintf("IMPLEMENTATION Citadel v%d.%02d\r\n", (REV_LEVEL/100), (REV_LEVEL%100)); #ifdef HAVE_OPENSSL cprintf("STARTTLS\r\n"); #endif @@ -335,43 +333,287 @@ void nntp_authinfo(const char *cmd) { } +/* + * 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; + + cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long)); + if (cdbfr != NULL) { + nm.msgnums = (long*)cdbfr->ptr; + cdbfr->ptr = NULL; + nm.num_msgs = cdbfr->len / sizeof(long); + cdbfr->len = 0; + cdb_free(cdbfr); + } else { + nm.num_msgs = 0; + nm.msgnums = NULL; + } + return(nm); +} + + +/* + * 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 + */ +void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) { + char n_name[1024]; + struct nntp_msglist nm; + long low_water_mark = 0; + long high_water_mark = 0; + + room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name); + + if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) { + if (!wildmat(n_name, wildmat_pattern)) { + return; + } + } + + nm = nntp_fetch_msglist(qrbuf); + if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) { + low_water_mark = nm.msgnums[0]; + high_water_mark = nm.msgnums[nm.num_msgs - 1]; + } + + // Only the mandatory formats are supported + switch(which_format) { + case NNTP_LIST_ACTIVE: + // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting + cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark); + break; + case NNTP_LIST_NEWSGROUPS: + cprintf("%s %s\r\n", n_name, qrbuf->QRname); + break; + } + + if (nm.msgnums != NULL) { + free(nm.msgnums); + } +} + + + +/* + * 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; int view; + time_t thetime = *(time_t *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); + /* + * The "created after " heuristics depend on the happy coincidence + * that for a very long time we have used a unix timestamp as the room record's + * generation number (QRgen). When this module is merged into the master + * source tree we should rename QRgen to QR_create_time or something like that. + */ + if (ra & UA_KNOWN) { - char n_name[1024]; - room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name); - cprintf("%s\r\n", n_name); + if (qrbuf->QRgen >= thetime) { + output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL); + } } } + /* - * Implements the NEWGROUPS command (FIXME not finished) + * Implements the NEWGROUPS command */ void nntp_newgroups(const char *cmd) { /* - * HACK: this works because the 5XX series error codes from citadeli + * 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; - cprintf("231 imma show you everything FIXME\r\n"); + + char stringy_date[16]; + char stringy_time[16]; + char stringy_gmt[16]; + struct tm tm; + time_t thetime; + + extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date); + extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time); + extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt); + + memset(&tm, 0, sizeof tm); + if (strlen(stringy_date) == 6) { + sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); + tm.tm_year += 100; + } + else { + sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); + tm.tm_year -= 1900; + } + tm.tm_mon -= 1; // tm_mon is zero based (0=January) + tm.tm_isdst = (-1); // let the C library figure out whether DST is in effect + sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec); + thetime = mktime(&tm); + if (!strcasecmp(stringy_gmt, "GMT")) { + tzset(); + thetime += timezone; + } + + + cprintf("231 list of new newsgroups follows\r\n"); CtdlGetUser(&CC->user, CC->curr_user); - cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS); - CtdlForEachRoom(nntp_newgroups_backend, NULL); + CtdlForEachRoom(nntp_newgroups_backend, &thetime); cprintf(".\r\n"); } +/* + * 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; + int view; + struct nntp_list_data *nld = (struct nntp_list_data *)data; + + CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); + if (ra & UA_KNOWN) { + output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern); + } +} + + +/* + * 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]; + char wildmat_pattern[1024]; + struct nntp_list_data nld; + + extract_token(list_format, cmd, 1, ' ', sizeof list_format); + extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern); + + if (strlen(wildmat_pattern) > 0) { + nld.wildmat_pattern = wildmat_pattern; + } + else { + nld.wildmat_pattern = NULL; + } + + if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) { + nld.list_format = NNTP_LIST_ACTIVE; + } + else if (!strcasecmp(list_format, "NEWSGROUPS")) { + nld.list_format = NNTP_LIST_NEWSGROUPS; + } + else { + cprintf("501 syntax error , unsupported list format\r\n"); + return; + } + + cprintf("231 list of newsgroups follows\r\n"); + CtdlGetUser(&CC->user, CC->curr_user); + CtdlForEachRoom(nntp_list_backend, &nld); + cprintf(".\r\n"); +} + + +/* + * 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"); + cprintf(".\r\n"); +} + + +/* + * Implements the GROUP command + */ +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; + + char requested_group[1024]; + char requested_room[ROOMNAMELEN]; + char augmented_roomname[ROOMNAMELEN]; + int c = 0; + int ok = 0; + int ra = 0; + struct ctdlroom QRscratch; + int msgs, new; + long oldest,newest; + + extract_token(requested_group, cmd, 1, ' ', sizeof requested_group); + newsgroup_to_room(requested_room, requested_group, sizeof requested_room); + + /* First try a regular match */ + c = CtdlGetRoom(&QRscratch, requested_room); + + /* Then try a mailbox name match */ + if (c != 0) { + CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room); + c = CtdlGetRoom(&QRscratch, augmented_roomname); + if (c == 0) { + safestrncpy(requested_room, augmented_roomname, sizeof(requested_room)); + } + } + + /* If the room exists, check security/access */ + if (c == 0) { + /* See if there is an existing user/room relationship */ + CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); + + /* normal clients have to pass through security */ + if (ra & UA_KNOWN) { + ok = 1; + } + } + + /* Fail here if no such room */ + if (!ok) { + cprintf("411 no such newsgroup\r\n"); + return; + } + + + /* + * CtdlUserGoto() formally takes us to the desired room, happily returning + * the number of messages and number of new messages. + */ + memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); + CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest); + cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group); +} + + /* * Main command loop for NNTP server sessions. */ @@ -398,6 +640,10 @@ void nntp_command_loop(void) nntp_quit(); } + else if (!strcasecmp(cmdname, "help")) { + nntp_help(); + } + else if (!strcasecmp(cmdname, "capabilities")) { nntp_capabilities(); } @@ -406,10 +652,6 @@ void nntp_command_loop(void) nntp_starttls(); } - else if (!strcasecmp(cmdname, "noop")) { - nntp_noop(); - } - else if (!strcasecmp(cmdname, "authinfo")) { nntp_authinfo(ChrPtr(Cmd)); } @@ -418,6 +660,14 @@ void nntp_command_loop(void) nntp_newgroups(ChrPtr(Cmd)); } + else if (!strcasecmp(cmdname, "list")) { + nntp_list(ChrPtr(Cmd)); + } + + else if (!strcasecmp(cmdname, "group")) { + nntp_group(ChrPtr(Cmd)); + } + else { cprintf("500 I'm afraid I can't do that.\r\n"); } @@ -472,3 +722,9 @@ CTDL_MODULE_INIT(nntp) /* return our module name for the log */ return "nntp"; } + + + + + +