/* * Copyright (c) 1998,1999,2000 Kazushi (Jam) Marukawa * All rights of my changes are reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice in the documentation and/or other materials provided with * the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $Orig-Id: nntpd.c,v 1.27 1997/07/21 14:08:40 agulbra Exp $ */ /* Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Use, modification and distribution is allowed without limitation, warranty, or liability of any kind. */ #ifdef SOCKS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "leafnode.h" int write2(int fd, const char* msg); int hash(const char*); FILE* fopenart(const char*); FILE* fopenpseudoart(const char* arg, const int article_num); void list(struct newsgroup*, int); void rereadactive(void); void parser(void); void error(const char*msg); void doarticle(const char*, int); void dostat(const char*); void dogroup(const char*); void dohelp(void); void domove(int); void domove(int); void dolist(const char*); void donewgroups(const char*); void donewnews(const char*); void dopost(void); void doxhdr(char*); void doxover(const char*); void dolistgroup(const char*); void markinterest(void); struct newsgroup* group; /* current group, initially none */ struct newsgroup* xovergroup; unsigned long artno = 0; /* current article number, initially 0 and almost unused */ char* cmd; /* current command line */ struct newsgroup* active; time_t activetime; static int justaftergroup; void rereadactive(void) { const char* s; struct stat st; s = getinfofname(); if ((stat(s, &st) == 0 && st.st_mtime > activetime) || active == NULL) { mysyslog(LOG_DEBUG, "rereading %s", s); readactive(); activetime = st.st_mtime; } } void error(const char* msg) { printf("%s: %s\r\n", msg, strerror(errno)); } void parser(void) { char* arg; int n; while(1) { if (!(cmd = getaline(stdin))) { error("421 Network error"); return; } n = 0; while (isalpha(cmd[n]) && n < 1000) { cmd[n] = tolower(cmd[n]); n++; } if (n == 0) continue; /* outside the spec, but let's do it anyway */ if (n > 999) { printf("400 Dazed and confused\r\n"); return; } while (isspace(cmd[n]) && n < 1000) cmd[n++] = '\0'; arg = cmd + n; mysyslog(LOG_DEBUG, "%s %s", cmd, arg); while (cmd[n] && n < 1000) n++; n--; while (isspace(cmd[n])) cmd[n--] = '\0'; if (!strcasecmp(cmd, "article")) { doarticle(arg, 3); } else if (!strcasecmp(cmd, "head")) { doarticle(arg, 2); } else if (!strcasecmp(cmd, "body")) { doarticle(arg, 1); } else if (!strcasecmp(cmd, "stat")) { doarticle(arg, 0); } else if (!strcasecmp(cmd, "help")) { dohelp(); } else if (!strcasecmp(cmd, "ihave")) { printf("500 IHAVE is for big news servers\r\n"); } else if (!strcasecmp(cmd, "last")) { domove(-1); } else if (!strcasecmp(cmd, "next")) { domove(1); } else if (!strcasecmp(cmd, "list")) { dolist(arg); } else if (!strcasecmp(cmd, "mode")) { printf("200 Modal interfaces are for losers\r\n"); } else if (!strcasecmp(cmd, "newgroups")) { donewgroups(arg); } else if (!strcasecmp(cmd, "newnews")) { donewnews(arg); } else if (!strcasecmp(cmd, "post")) { dopost(); } else if (!strcasecmp(cmd, "quit")) { printf("205 Always happy to serve!\r\n"); return; } else if (!strcasecmp(cmd, "slave")) { printf("202 Cool - I always wanted a slave\r\n"); } else if (!strcasecmp(cmd, "xhdr")) { doxhdr(arg); } else if (!strcasecmp(cmd, "xover")) { doxover(arg); } else if (!strcasecmp(cmd, "listgroup")) { dolistgroup(arg); } else if (!strcasecmp(cmd, "group")) { dogroup(arg); } else printf("500 Unknown command\r\n"); fflush(stdout); } } /* open a pseudo art */ FILE* fopenpseudoart(const char* arg, const int article_num) { FILE* f = NULL; char msgidbuf[128]; char* c; struct newsgroup* g; if (article_num > 0 && article_num < group->first) { f = buildpseudoart(group->name); } else if (!article_num) { if (!strncmp(arg, "name); } } } } return f; } /* open an article by number or message-id */ FILE* fopenart(const char* arg) { unsigned long int a; FILE* f; char* t; t = NULL; a = strtoul(arg, &t, 10); if (a && t && !*t && group) { f = fopen(arg, "r"); if (f) artno = a; else f = fopenpseudoart(arg, a); markinterest(); /* else try message-id lookup */ } else if (arg && *arg=='<') { f = fopen(getmsgidfname(arg), "r"); if (!f) f = fopenpseudoart(arg, a); } else if (group && artno) { char s[256]; sprintf(s, "%lu", artno); f = fopen(s, "r"); if (!f) f = fopenpseudoart(s, a); markinterest(); } else { f = NULL; } return f; } /* display an article or somesuch */ void doarticle(const char* arg, int what) { FILE* f; char* p; unsigned long yuck; char s[1024]; if (!group) { printf("412 No newsgroup selected\r\n"); return; } f = fopenart(arg); if (!f) { if (strlen(arg)) printf("430 No such article: %s\r\n", arg); else printf("430 No such article: %lu\r\n", artno); return; } yuck = strtoul(arg, &p, 10); if (!p || *p) { /* parse an article to get artno. */ char* xref = getaheader(f, "Xref"); if (xref && (xref = strstr(xref, group->name)) != NULL) { xref += strlen(group->name) + 1; yuck = strtoul(xref, &p, 10); if (!p || *p) yuck = 0; } else { yuck = 0; } } printf("%3d %lu %s Text follows, then a line consisting of a single '.'\r\n", 223 - what, yuck, getaheader(f, "Message-ID")); while (fgets(s, 1024, f) && *s && *s != '\n') { if (what & 2) { p = s; if ((p = strchr(p, '\n'))) *p = '\0'; printf("%s%s\r\n", *s=='.' ? "." : "", s); /* . on headers :( */ } } if (what==3) printf("\r\n"); /* empty separator line */ while ((what & 1) && fgets(s, 1024, f) && *s) { p = s; if ((p = strchr(p, '\n'))) *p = '\0'; printf("%s%s\r\n", *s=='.' ? "." : "", s); } if (what) printf(".\r\n"); fclose(f); return; /* OF COURSE there were no errors */ } /* note bug.. need not be _immediately after_ GROUP */ void markinterest(void) { int f; const char* s = getinterestingngfname(group); if (!justaftergroup) return; if ((f = open(s, O_RDONLY, 0)) >= 0) { /* if there is file, changed access time by reading it */ char a; (void)read(f, &a, 1); close(f); } else { /* Otherwise, create it */ if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) { (void)write(f, "*", 1); close(f); } } justaftergroup = FALSE; } /* change to group - note no checks on group name */ void dogroup(const char* arg) { struct newsgroup* g; rereadactive(); g = findgroup(arg); if (g) { group = g; chdirgroup(g, 1); if (g->last < g->first) printf("211 %lu %lu %lu %s group selected (pseudo article)\r\n", 1LU, g->last, g->last, g->name); else printf("211 %lu %lu %lu %s group selected\r\n", g->last >= g->first ? g->last-g->first+1 : 0, g->first, g->last, g->name); artno = g->first; fflush(stdout); justaftergroup = TRUE; #if DOTNGFILE { int f; const char* s = getinterestingngdotfname(group); if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) close(f); } #endif } else { printf("411 No such group\r\n"); } } void dohelp(void) { printf("500 Read RFC 977 and 1036 for elucidation\r\n"); } void domove(int by) { if (group) { if (artno) { unsigned long art = artno; FILE* f = NULL; char buffer[256]; while (f == NULL) { art += by; sprintf(buffer, "%lu", art); f = fopenart(buffer); /* hit boundaries */ if (art > group->last) { printf("421 There is no next article\r\n"); return; } if (art < group->first) { printf("422 There is no previous article\r\n"); return; } } printf("223 %lu %s Article number is now %lu\r\n", artno, getaheader(f, "Message-ID"), artno); fclose(f); } else { printf("420 There is no current article\r\n"); } } else { printf("412 No newsgroup selected\r\n"); } } /* LIST ACTIVE if what==0, else LIST NEWSGROUPS */ void list(struct newsgroup* g, int what) { if (g) { list(g->right, what); if (what) { printf("%s\t%s\r\n", g->name, g->desc ? g->desc : ""); } else { /* If the group is empty, but has server arts, fudge a dummy article in... */ if (g->last < g->first) printf("%s %010lu %010lu y\r\n", g->name, g->last, g->last); else printf("%s %010lu %010lu y\r\n", g->name, g->last, g->first); } list (g->left, what); } } void dolist(const char* arg) { rereadactive(); initconfig(); if (chdir(spooldir)) { printf("503 News spool directory does not exist!\r\n"); } else if (!active) { printf("503 Group information file does not exist!\r\n"); } else { if (!*arg || !strcasecmp(arg, "active")) { printf("215 Newsgroups in form \"group high low flags\".\r\n"); list (active, 0); printf(".\r\n"); } else if (!strcasecmp(arg, "newsgroups")) { printf("215 Descriptions in form \"group description\".\r\n"); list (active, 1); printf(".\r\n"); } else if (!strcasecmp(arg, "extensions")) { printf("501 No extensions available.\r\n"); } else if (!strcasecmp(arg, "overview.fmt")) { printf("215 Order of fields in overview database.\r\n" "Subject:\r\nFrom:\r\nDate:\r\nMessage-ID:\r\n" "References:\r\nBytes:\r\nLines:\r\n" "Xref:full\r\n.\r\n"); } else if (!strcasecmp(arg, "active.times")) { printf("215 Placeholder - Leafnode will fetch groups on demand\r\n" "news.announce.newusers 42 tale@uunet.uu.net\r\n" "news.answers 42 tale@uunet.uu.net\r\n" ".\r\n"); #if 0 } else if (!strcasecmp(arg, "xactive")) { } else if (!strcasecmp(arg, "distributions")) { } else if (!strcasecmp(arg, "searches")) { } else if (!strcasecmp(arg, "searchable")) { } else if (!strcasecmp(arg, "srchfields")) { } else if (!strcasecmp(arg, "motd")) { } else if (!strcasecmp(arg, "prettynames")) { } else if (!strcasecmp(arg, "subscriptions")) { #endif } else { printf("501 Syntax error\r\n"); } } } void donewgroups(const char* arg) { printf("231 Command not supported\r\n.\r\n"); } void donewnews(const char* arg) { printf("500 NEWNEWS is meaningless for this server\r\n"); } /* next bit is copied from INN 1.4 and modified ("broken") by agulbra mail to Rich $alz bounced */ /* Scale time back a bit, for shorter Message-ID's. */ #define OFFSET 673416000L static char ALPHABET[] = "0123456789abcdefghijklmnopqrstuv"; char* generateMessageID(void); char* generateMessageID(void) { static char buff[80]; static time_t then; static unsigned int fudge; time_t now; char* p; int n; if (fqdn == NULL) { return NULL; /* this must be not called if fqdn is NULL. */ } now = time(0); /* might be 0, in which case fudge will almost fix it */ if (now != then) fudge = 0; else fudge++; then = now; p = buff; *p++ = '<'; n = (unsigned int)now - OFFSET; while (n) { *p++ = ALPHABET[(int)(n & 31)]; n >>= 5; } *p++ = '.'; n = fudge * 32768 + (unsigned long)getpid(); while (n) { *p++ = ALPHABET[(int)(n & 31)]; n >>= 5; } sprintf(p, ".ln@%s>", fqdn); return buff; } /* the end of what I stole from rsalz and then mangled */ void dopost(void) { char* line; int havefrom = 0; int havepath = 0; int havedate = 0; int havenewsgroups = 0; int havemessageid = 0; int havesubject = 0; int err = 0; int len; int out; char filename[80]; const char* outname; static int postingno; /* starts as 0 */ sprintf(filename, "%d-%d-%d", (int)getpid(), (int)time(NULL), ++postingno); outname = getoutgoingfname(filename); out = open(outname, O_WRONLY|O_EXCL|O_CREAT, 0444); if (out < 0) { char dir[80]; sprintf(dir, "%s/out.going", spooldir); mkdir(dir, 0755); out = open(outname, O_WRONLY|O_EXCL|O_CREAT, 0444); if (out < 0) { printf("440 Unable to open spool file %s\r\n", outname); return; } } printf("340 Go ahead.\r\n"); fflush(stdout); do { int len; line = getaline(stdin); if (!line) { unlink(outname); exit(0); } if (!strncasecmp(line, "From: ", 6)) { if (havefrom) err = TRUE; else havefrom = TRUE; } if (!strncasecmp(line, "Path: ", 6)) { if (havepath) err = TRUE; else havepath = TRUE; } if (!strncasecmp(line, "Message-ID: ", 12)) { if (havemessageid) err = TRUE; else havemessageid = TRUE; } if (!strncasecmp(line, "Subject: ", 9)) { if (havesubject) err = TRUE; else havesubject = TRUE; } if (!strncasecmp(line, "Newsgroups: ", 12)) { if (havenewsgroups) err = TRUE; else havenewsgroups = TRUE; } if (!strncasecmp(line, "Date: ", 6)) { if (havedate) err = TRUE; else havedate = TRUE; } len = strlen(line); if (len && line[len - 1] == '\n') line[--len] = '\0'; if (len && line[len - 1] == '\r') line[--len] = '\0'; if (len && line[len - 1] == '\r') line[--len] = '\0'; if (len) write(out, line, len); else { if (!havepath) { write(out, "Path: ", 6); if (fqdn != NULL) { write(out, fqdn, strlen(fqdn)); write(out, "!nobody\r\n", 9); } else { write(out, "nobody\r\n", 8); } } if (!havedate) { const char* l = rfctime(); write(out, "Date: ", 6); write(out, l, strlen(l)); write(out, "\r\n", 2); } #ifndef NOMSGID if (!havemessageid && fqdn != NULL) { char tmp[80]; sprintf(tmp, "Message-ID: %s\r\n", generateMessageID()); write(out, tmp, strlen(tmp)); } #endif } write(out, "\r\n", 2); } while (*line); do { line = getaline(stdin); if (!line) { unlink(outname); exit(1); } len = strlen(line); if (len && line[len - 1] == '\n') line[--len] = '\0'; if (len && line[len - 1] == '\r') line[--len] = '\0'; if (len && line[len - 1] == '\r') line[--len] = '\0'; if (len) write(out, line, len); write(out, "\r\n", 2); } while (line[0] != '.' || line[1] != '\0'); close(out); if (havefrom && havesubject && havenewsgroups && !err) { printf("240 Article posted, now be patient\r\n"); return; } unlink(outname); if (!havefrom) { printf("440 No From field, article not posted\r\n"); } else if (!havesubject) { printf("440 No Subject field, article not posted\r\n"); } else if (!havenewsgroups) { printf("440 No Newsgroups field, article not posted\r\n"); } else { printf("440 Formatting error, article not posted\r\n"); } } void doxhdr(char* arg) { int arglen = strlen(arg); char* h[] = { "Subject", "From", "Date", "Message-ID", "References", "Bytes", "Lines", "Xref" }; int n = 8; char* l; unsigned long int a,b,c; FILE* f; /* have to keep arg since it is a part of a buffer of getaline(). */ arg = strdup(arg); l = skipnonspaces(arg); if (l && *l) *l++ = '\0'; l = skipspaces(l); if (l && *l == '<') { /* handle message-id form (well) */ FILE* f; f = fopenart(l); if (!f) { printf("430 No such article\r\n"); free(arg); return; } do { l = getaline(f); } while (l && *l && /* uh, space before : allowed? */ (l[arglen] != ':' || strncasecmp(arg, l, arglen))); l = skipspaces(skipnonspaces(l)); printf("221 First line of %s header follows:\r\n%s\r\n.\r\n", arg, l ? l : ""); /* dubious - if not found, "" */ fclose(f); free(arg); return; } else if (l && isdigit(*l)) { b = a = strtoul(l, &l, 10); if (a && l && *l) { l = skipspaces(l); if (*l == '-') { l++; if (l && *l) b = strtoul(l, &l, 10); else b = xlast; } l = skipspaces(l); if (l && *l) { printf("502 Usage: XHDR header first[-last] or XHDR header message-id\r\n"); free(arg); return; } if (b == 0) b = xlast; } } else { a = b = artno; } if (!group) { printf("412 Use the GROUP command first\r\n"); free(arg); return; } if (!arg || !*arg) { printf("502 Usage: XHDR header first[-last] or XHDR header message-id\r\n"); free(arg); return; } do { n--; } while (n > -1 && strncasecmp(arg, h[n], strlen(h[n]))); markinterest(); if (a < group->first) a = group->first; else if (a > group->last) a = group->last; if (b < a) b = a; else if (b > group->last) b = group->last; if (n >= 0) { printf("221 %s header (from overview) for postings %lu-%lu:\r\n", h[n], a, b); if (xovergroup != group) { getxover(); xovergroup = group; } for(c = a; c <= b; c++) { if (xoverinfo && c >= xfirst && c <= xlast && xoverinfo[c - xfirst].text) { char* l = xoverinfo[c - xfirst].text; int d; for(d = 0; l && d <= n; d++) l = strchr(l + 1, '\t'); if (l) { if (n == 7) /* dirty Xref: hack */ l = l + 6 + 1; else l = l + 1; } printf("%lu ", c); if (l) { while (*l && *l != '\t') putchar(*l++); } printf("\r\n"); } } } else { printf("221 %s header (from article files) for postings %lu-%lu:\r\n", arg, a, b); n = strlen(arg); for(c = a; c <= b; c++) { char s[256]; sprintf(s, "%lu", c); f = fopen(s, "r"); while (f) { l = getaline(f); if (!l || !*l || *l == '\r' || *l == '\n') { fclose(f); f = 0; } else if (l[n] == ':' && strncasecmp(arg, l, n) == 0) { printf("%lu %s\r\n", c, l + n + 2); } } } } printf(".\r\n"); free(arg); return; } void doxover(const char* arg) { char* l; unsigned long int a,b, art; if (!group) { printf("412 Use the GROUP command first\r\n"); return; } markinterest(); l = NULL; b = a = strtoul(arg, &l, 10); if (a && l && *l) { l = skipspaces(l); if (*l=='-') b = strtoul(++l, &l, 10); l = skipspaces(l); if (l && *l) { printf("502 Usage: XOVER first[-last]\r\n"); return; } } if (xovergroup != group && !getxover()) { printf("224 Overview information not available\r\n.\r\n"); return; } xovergroup = group; if (b == 0) b = xlast; if (a > xlast) b = xlast; if (b > xlast) b = xlast; if (a < xfirst) a = xfirst; if (b < xfirst) b = xfirst; printf("224 Overview information for postings %lu-%lu:\r\n", a, b); for (art = a; art <= b; art++) { if (xoverinfo[art - xfirst].text) printf("%s\r\n", xoverinfo[art-xfirst].text); } printf(".\r\n"); } void dolistgroup(const char* arg) { struct newsgroup* g; unsigned long int art; if (justaftergroup && group && group->name) { #if DOTNGFILE int f; const char* s = getinterestingngdotfname(group); /* mark the previous group as _possibly_ interesting */ if ((f = open(s, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) close(f); #endif } rereadactive(); if (arg && strlen(arg)) { g = findgroup(arg); if (!g) { printf("481 No such group: %s\r\n", arg); return; } else { group = g; artno = g->first; justaftergroup = TRUE; } } else if (group) { g = group; } else { printf("481 No group specified\r\n"); return; } markinterest(); group = g; chdirgroup(g, 1); if ((xovergroup != group || xovergroup == NULL) && !getxover()) { printf("481 Overview information not available\r\n"); return; } xovergroup = group; printf("211 Article list for %s follows\r\n", g->name); for(art = xfirst; art <= xlast; art++) { if (xoverinfo[art - xfirst].text) printf("%lu \r\n", art); } printf(".\r\n"); if (group) chdirgroup(group, 1); } int main(int argc, char** argv) { int fodder; #ifdef INET6 struct sockaddr_storage sa; #else struct hostent* he; struct sockaddr_in sa; #endif #ifdef NI_MAXHOST char name[NI_MAXHOST]; #else char name[1025]; #endif /* initialize FQDN. */ fodder = sizeof(sa); if (getsockname(0, (struct sockaddr *)&sa, &fodder)) { strcpy(name, "localhost"); } else { #ifdef INET6 name[0] = '\0'; getnameinfo((struct sockaddr *)&sa, fodder, name, sizeof(name), NULL, 0, 0); #else he = gethostbyaddr((char *)&sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr), AF_INET); strncpy(name, he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr), sizeof(name)); #endif name[sizeof(name)-1] = '\0'; } if (strncasecmp(name, "localhost", 9) == 0) { whoami(); /* initialize FQDN by general way. */ } else { fqdn = strdup(name); } umask(2); openlog("leafnode", LOG_PID|LOG_CONS, LOG_NEWS); printf("200 Leafnode+ NNTP Daemon, version %s running at %s\r\n", version, fqdn ? fqdn : "nowhere"); fflush(stdout); readconfig(); rereadactive(); parser(); fflush(stdout); if (nntpout) fprintf(nntpout, "QUIT\r\n"); exit(0); }