"Fossies" - the Fresh Open Source Software Archive

Member "leafnode-1.12.0/nntpd.c" (28 Dec 2021, 49037 Bytes) of package /linux/misc/leafnode-1.12.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "nntpd.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.11.12_vs_1.12.0.

    1 /*
    2 nntpd -- the NNTP server
    3 
    4 Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
    5 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
    6 22646949.
    7 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
    8 and Randolf Skerka <Randolf.Skerka@gmx.de>.
    9 Copyright of the modifications 1997.
   10 Modified by Kent Robotti <robotti@erols.com>. Copyright of the
   11 modifications 1998.
   12 Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
   13 Copyright of the modifications 1998.
   14 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
   15 and Kazushi (Jam) Marukawa <jam@pobox.com>.
   16 Copyright of the modifications 1998, 1999.
   17 Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>
   18 Copyright of the modifications 2002.
   19 Modified by Jonathan Larmour <jifl@jifvik.org>
   20 Copyright of the modifications 2002.
   21 Modified by Matthias Andree <matthias.andree@gmx.de>
   22 Copyright of the modifications 2000 - 2021.
   23 
   24 See file COPYING for restrictions on the use of this software.
   25 */
   26 
   27 #include "leafnode.h"
   28 #include "masock.h"
   29 #include "mastring.h"
   30 #include "validatefqdn.h"
   31 #include "strlcpy.h"
   32 #include "ln_log.h"
   33 #include "nntpd.h"
   34 
   35 #ifdef SOCKS
   36 #include <socks.h>
   37 #endif
   38 
   39 #include <sys/types.h>
   40 #include <sys/stat.h>
   41 #include <netinet/in.h>
   42 #ifndef __LCLINT__
   43 #include <arpa/inet.h>
   44 #endif
   45 #include <ctype.h>
   46 #include "system.h"
   47 #include <errno.h>
   48 #include <fcntl.h>
   49 #include <limits.h>
   50 #include <netdb.h>
   51 #include <signal.h>
   52 #include <stdio.h>
   53 #include <stdlib.h>
   54 #include <string.h>
   55 #include <sys/socket.h>
   56 #include <syslog.h>
   57 #include <unistd.h>
   58 #include <utime.h>
   59 
   60 #define MAXLINELENGTH 1000
   61 
   62 #ifdef HAVE_IPV6
   63 /*
   64  *  * local union struct
   65  */
   66 union sockaddr_union {
   67     struct sockaddr sa;
   68     struct sockaddr_in sin;
   69     struct sockaddr_in6 sin6;
   70 };
   71 #endif
   72 
   73 /*@null@*/ static struct newsgroup *group;  /* current group, initially none */
   74 /*@null@*/ static struct newsgroup *xovergroup = NULL;
   75 static unsigned long artno;     /* current article number */
   76 /*@dependent@*/ /*@null@*/ static char *cmd;    /* current command line */
   77 static time_t activetime;
   78 
   79 int debug = 0;
   80 int verbose = 0;        /* verbose doesn't count here */
   81 
   82 static void
   83 fatal_write(void)
   84 {
   85     /*@observer@*/ static const char *const e = "Write error on stdout.";
   86     syslog(LOG_CRIT, "%s", e);
   87     fprintf(stderr, "%s\n", e);
   88     exit(EXIT_FAILURE);
   89 }
   90 
   91 static void
   92 rereadactive(void)
   93 {
   94     struct stat st;
   95     char s[SIZE_s+1];
   96 
   97     (void)xsnprintf(s, SIZE_s, "%s/leaf.node/groupinfo", spooldir);
   98 
   99     if ((!stat(s, &st) && (st.st_mtime > activetime)) || (active == NULL)) {
  100     char *grouptmp = NULL;
  101     if (debugmode)
  102         syslog(LOG_DEBUG, "rereading %s", s);
  103     if (group) {
  104         grouptmp = critstrdup(group->name, "rereadactive");
  105     }
  106     readactive();
  107     if (activesize == 0) 
  108         fakeactive();
  109     else
  110         activetime = st.st_mtime;
  111     if (grouptmp) {
  112         group = findgroup(grouptmp);
  113         xovergroup = NULL;
  114         free(grouptmp);
  115     }
  116     }
  117 }
  118 
  119 /*
  120  * pseudo article stuff
  121  */
  122 
  123 /* build and return an open fd to pseudoart in group */
  124 static FILE *
  125 buildpseudoart(const char *grp)
  126 {
  127     FILE *f;
  128     int fd;
  129 
  130     mastr *n = mastr_new(PATH_MAX);
  131     (void)mastr_vcat(n, spooldir, "/temp.files/pseudo.XXXXXXXXXX", NULL);
  132     fd = safe_mkstemp(mastr_modifyable_str(n));
  133     if (fd < 0) {
  134     syslog(LOG_ERR, "Could not create pseudoarticle: mkstemp failed: %m");
  135     mastr_delete(n);
  136     return NULL;
  137     }
  138 
  139     (void)unlink(mastr_str(n));
  140 
  141     f = fdopen(fd, "w+b");
  142     if (f == NULL) {
  143     syslog(LOG_ERR, "Could not create pseudoarticle: fdopen failed: %m");
  144     (void)close(fd);
  145     mastr_delete(n);
  146     return NULL;
  147     }
  148 
  149     fprintf(f, "Path: %s\n", fqdn);
  150     fprintf(f, "Newsgroups: %s\n", grp);
  151     fprintf(f, "From: Leafnode <%s>\n", newsadmin);
  152     fprintf(f, "Subject: Leafnode placeholder for group %s\n", grp);
  153     fprintf(f, "Date: %s\n", rfctime());
  154     fprintf(f, "Message-ID: <leafnode:placeholder:%s@%s>\n", grp, fqdn);
  155     fprintf(f, "\n");
  156     fprintf(f,
  157         "This server is running leafnode, which is a dynamic NNTP proxy.\n"
  158         "This means that it does not retrieve newsgroups unless someone is\n"
  159         "actively reading them.\n"
  160         "\n"
  161         "If you do an operation on a group - such as reading an article,\n"
  162         "looking at the group table of contents or similar, then leafnode\n"
  163         "will go and fetch articles from that group when it next updates.\n\n");
  164     fprintf(f,
  165         "Since you have read this dummy article, leafnode will retrieve\n"
  166         "the newsgroup %s when fetchnews is run\n"
  167         "the next time. If you'll look into this group a little later, you\n"
  168         "will see real articles.\n\n", grp);
  169     fprintf(f,
  170         "If you see articles in groups you do not read, that is almost\n"
  171         "always because of cross-posting.  These articles do not occupy any\n"
  172         "more space - they are hard-linked into each newsgroup directory.\n"
  173         "\n"
  174         "If you do not understand this, please talk to your newsmaster.\n"
  175         "\n"
  176         "Leafnode can be found at\n" "\thttp://www.leafnode.org/\n\n");
  177 
  178     if (ferror(f)) {
  179     (void)fclose(f);
  180     f = NULL;
  181     } else {
  182     rewind(f);
  183     }
  184     mastr_delete(n);
  185     return f;
  186 }
  187 
  188 static int
  189 parserange(char *arg, unsigned long *a, unsigned long *b)
  190 {
  191     char *l = arg;
  192     /* no argument */
  193     if (!*l) return 0;
  194 
  195     /* parse */
  196     if (*l != '-')
  197     *a = strtoul(arg, &l, 10);
  198     SKIPLWS(l);
  199     if (*l == '-') {
  200     ++l;
  201     SKIPLWS(l);
  202     if (isdigit((unsigned char)*l))
  203         *b = strtoul(l, &l, 10);
  204     } else {
  205     *b = *a;
  206     }
  207     SKIPLWS(l);
  208 
  209     /* trailing garbage */
  210     if (l && *l) {
  211     return 0;
  212     }
  213 
  214     return 1;
  215 }
  216 
  217 static int
  218 is_pseudogroup(/*@null@*/ const struct newsgroup *g)
  219 {
  220     if (!g) return 0;
  221     if (!chdirgroup(g->name, FALSE)) return 1;
  222 /*    if (isinteresting(g->name)) return 0; */
  223     if (g->last < g->first) return 1;
  224     if (g->last <= 1) return 1;
  225     return 0;
  226 }
  227 
  228 /*
  229  * XXX FIXME: an option is needed if the administrator
  230  * locks the interesting.group permissions in order to have a fixed set
  231  * of subscriptions that are always available, or if the subscription
  232  * is handled outside leafnode.
  233  */
  234 static void
  235 markinterest(const char *ng)
  236 {
  237     struct stat st;
  238     struct utimbuf buf;
  239     int err;
  240     time_t now;
  241     FILE *f;
  242     char s[SIZE_s+1];
  243 
  244     /* OK, the user may be setting the file to root owned with group
  245      * write permission. This is a problem when trying to set the ctime
  246      * without setting the mtime, because we would need privileges to do
  247      * that. Instead, we'll bump both times and warn the user. */
  248 
  249     err = 0;
  250     (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng);
  251     if (stat(s, &st) == 0) {
  252     const char *touched = "ctime";
  253     /* group was read before: keep mtime, but bump up ctime */
  254     now = time(NULL);
  255     buf.actime = (now < st.st_atime) ? st.st_atime : now;
  256     /* now < update may happen through HW failures */
  257     buf.modtime = st.st_mtime;
  258     if (utime(s, &buf)) {
  259         int oe = errno;
  260         /* cannot set time - the file's user-id must be the NEWS_USER's
  261          * 2nd best - we'll try to bump both times, but this means
  262          * timeout_short applies, so warn the user. */
  263         if (utime(s, NULL)) {
  264         /* ok, didn't work either. complain and give up.
  265          * We used to fall through to the code below, but that
  266          * may truncate the file and thus lose delaybody article
  267          * numbers. */
  268         syslog(LOG_ERR, "Error: cannot set ctime/mtime timestamp of %s: %s.",
  269             s, strerror(errno));
  270         return;
  271         } else {
  272         /* complain that timeout_short applies, so that the user
  273          * is not surprised by premature group unsubscription. */
  274         touched = "ctime and mtime";
  275         if (timeout_short < timeout_long) {
  276             syslog(LOG_WARNING, "Warning: cannot set the ctime timestamp of %s without resetting mtime (%s). "
  277                 "This means that timeout_short will apply rather than timeout_long. "
  278                 "Either reset file ownership to " NEWS_USER " or set timeout_short and timeout_long to the same value to suppress this warning.",
  279                 s, strerror(oe));
  280         }
  281         }
  282     }
  283     if (debugmode && !err)
  284         syslog(LOG_DEBUG, "markinterest: %s touched %s", ng, touched);
  285     } else {
  286     err = 1;
  287     }
  288     if (err) {
  289     f = fopen(s, "w"); /* truncating sets ctime and mtime */
  290     if (f == NULL || fclose(f) == EOF) {
  291         syslog(LOG_ERR, "Could not create %s: %m", s);
  292     } else {
  293         if (debugmode)
  294         syslog(LOG_DEBUG, "markinterest: %s new", ng);
  295     }
  296     } else {
  297     int fd = open(".", O_RDONLY);
  298     (void)chdirgroup(ng, FALSE);
  299     if (fd >= 0) {
  300         (void)fchdir(fd);
  301         (void)close(fd);
  302     }
  303     }
  304 }
  305 
  306 /* open a pseudo art */
  307 /* WARNING: article_num MUST be 0 for selection based on Message-ID */
  308 static FILE *
  309 fopenpseudoart(const char *arg, const unsigned long article_num)
  310 {
  311     FILE *f = NULL;
  312     char *c;
  313     struct newsgroup *g;
  314 
  315     if (group && (article_num && ((article_num == group->first &&
  316         group->first >= group->last) || is_pseudogroup(group)))) {
  317     f = buildpseudoart(group->name);
  318     } else if (!article_num) {
  319     if (!strncmp(arg, "<leafnode:placeholder:", 22)) {
  320         mastr *msgidbuf = mastr_new(1024);
  321         (void)mastr_cpy(msgidbuf, arg + 22);
  322         if ((c = strchr(mastr_modifyable_str(msgidbuf), '@')) != NULL) {
  323         *c++ = '\0';
  324         (void)strtok(c, ">");
  325         if (0 == strcasecmp(c, fqdn)) {
  326             g = findgroup(mastr_str(msgidbuf));
  327             if (g && (g->last <= 1 || g->first >= g->last)) {
  328             markinterest(g->name);
  329             f = buildpseudoart(g->name);
  330             }
  331         }
  332         }
  333         mastr_delete(msgidbuf);
  334     }
  335     }
  336     return f;
  337 }
  338 
  339 /* open an article by number or message-id */
  340 static FILE *
  341 fopenart(const char *arg)
  342 {
  343     unsigned long int a;
  344     FILE *f;
  345     char *t;
  346     struct stat st;
  347     /*@temp@*/ char buf[32];
  348 
  349     t = NULL;
  350     a = strtoul(arg, &t, 10);
  351     if (arg && *arg == '<') {
  352     /* message ID given */
  353     f = fopen(lookup(arg), "r");
  354     if (!f) f = fopenpseudoart(arg, 0);
  355     } else if (t && !*t && group != NULL) {
  356     const char *ptr = arg;
  357     /* number not given -> take current article pointer */
  358     if (!a) {
  359         a = artno;
  360         sprintf(buf, "%lu", artno); /* RATS: ignore */
  361         ptr = buf;
  362     }
  363     if (is_pseudogroup(group)) {
  364         f = fopenpseudoart(ptr, a);
  365     } else {
  366         f = fopen(ptr, "r");
  367     }
  368     if (f != NULL)
  369         artno = a;
  370     markinterest(group->name);
  371     } else {
  372     f = NULL;
  373     }
  374     if (f != NULL && (fstat(fileno(f), &st) || st.st_size == 0)) {
  375     (void)fclose(f);
  376     f = NULL;
  377     }
  378     return f;
  379 }
  380 
  381 
  382 /*
  383  * Mark an article for download by appending its number to the
  384  * corresponding file in interesting.groups
  385  */
  386 static int
  387 markdownload(const char *ng, unsigned long id)
  388 {
  389     int i, e = 0;
  390     unsigned long n;
  391     FILE *f;
  392     char *t;
  393     char s[SIZE_s+1];
  394 
  395     (void)xsnprintf(s, SIZE_s, "%s/interesting.groups/%s", spooldir, ng);
  396     if ((f = fopen(s, "r+"))) {
  397     i = 0;
  398     while ((t = getaline(f))) {
  399         if (sscanf(t, "%lu", &n) == 1 && n == id) {
  400         (void)fclose(f);    /* we only read from the file */
  401         return 0;   /* already marked */
  402         }
  403         if (ferror(f))
  404         e = errno;
  405         ++i;
  406     }
  407     if (i < BODY_DOWNLOAD_LIMIT) {
  408         (void)fprintf(f, "%lu\n", id);
  409         if (ferror(f))
  410         e = errno;
  411         if (debugmode)
  412         syslog(LOG_DEBUG, "Marking %s %lu for download", ng, id);
  413     } else {
  414         syslog(LOG_ERR, "Too many bodies marked in %s", ng);
  415     }
  416     if (fclose(f))
  417         e = errno;
  418     }
  419     if (e) {
  420     syslog(LOG_ERR, "I/O error handling \"%s\": %s", s, strerror(e));
  421     return -1;
  422     }
  423     return 1;
  424 }
  425 
  426 static void
  427 nogroup(void)
  428 {
  429     printf("412 Use the GROUP command first\r\n");
  430     if (debugmode)
  431     syslog(LOG_DEBUG, ">412 Use the GROUP command first");
  432     return;
  433 }
  434 
  435 /* display an article or somesuch */
  436 /* DOARTICLE */
  437 static void
  438 doarticle(const char *arg, int what)
  439 {
  440     FILE *f;
  441     char *p = NULL;
  442     char *q = NULL;
  443     char *t;
  444     unsigned long localartno;
  445     unsigned long replyartno;
  446     char *localmsgid, *xref = NULL;
  447     char *markgroup = NULL;
  448     char s[SIZE_s+1];
  449 
  450     f = fopenart(arg);
  451     if (!f) {
  452     if (arg && *arg != '<' && !group) {
  453         nogroup();
  454     } else if (strlen(arg)) {
  455         printf("430 No such article: %s\r\n", arg);
  456         if (debugmode)
  457         syslog(LOG_DEBUG, ">430 No such article: %s", arg);
  458     } else {
  459         printf("423 No such article: %lu\r\n", artno);
  460         if (debugmode)
  461         syslog(LOG_DEBUG, ">423 No such article: %lu", artno);
  462     }
  463     return;
  464     }
  465 
  466     if (!*arg) {
  467         /* no. implicit */
  468     localartno = artno;
  469     localmsgid = fgetheader(f, "Message-ID:");
  470     } else if (*arg == '<') {
  471         /* message-id -- do not modify artno */
  472     localartno = 0;
  473     localmsgid = critstrdup(arg, "doarticle");
  474     } else {
  475         /* no. explicit */
  476     localartno = strtoul(arg, NULL, 10);
  477     localmsgid = fgetheader(f, "Message-ID:");
  478     }
  479 
  480     replyartno = localartno;
  481     if (!localartno) {
  482     /* we have to extract the article number and the newsgroup from
  483      * the Xref: header */
  484     xref = fgetheader(f, "Xref:");
  485     p = xref;
  486     if (p) {
  487         /* skip host name */
  488         while (*p && !isspace((unsigned char)*p)) {
  489         p++;
  490         }
  491         while (isspace((unsigned char)*p)) {
  492         p++;
  493         }
  494     }
  495     if (p) {
  496         /* search article number of current group in Xref: */
  497         if (group) {
  498         while ((q = strstr(p, group->name)) != NULL) {
  499             q += strlen(group->name);
  500             if (*q++ == ':') {
  501             localartno = strtoul(q, NULL, 10);
  502             markgroup = group->name;
  503             break;
  504             }
  505             p = q + strcspn(q, " \t");
  506         }
  507         }
  508         /* if we don't have a localartno, then we need to mark this
  509          * article in a different news group */
  510         if (!localartno) {
  511         char *r = p;
  512         while ((q = strchr(r, ':'))) {
  513             *q++ = '\0';
  514             if (isinteresting(p)) {
  515             /* got one we can mark */
  516             markgroup = r;
  517             localartno = strtoul(q, NULL, 10);
  518             break;
  519             }
  520             while (isdigit((unsigned char)*q)) {
  521             q++;
  522             }
  523             while (isspace((unsigned char)*q)) {
  524             q++;
  525             }
  526             r = q;
  527         }
  528         }
  529     }
  530     } else if (group) {
  531     markgroup = group->name;
  532     }
  533 
  534     if (!localmsgid) {
  535     const char *tmp = "423 Corrupt article.";
  536     printf("%s\r\n", tmp);
  537     syslog(LOG_WARNING, ">%s", tmp);
  538     if (replyartno) {
  539         (void)xsnprintf(s, SIZE_s, "%lu", replyartno);
  540         (void)log_unlink(s, 0);
  541     }
  542     (void)fclose(f);
  543     if (xref)
  544         free(xref);
  545     return;
  546     }
  547     (void)xsnprintf(s, SIZE_s - 24, "%3d %lu %s article retrieved - ",
  548             223 - what, replyartno, localmsgid);
  549     free(localmsgid);
  550 
  551     if (what == 0)
  552     strcat(s, "request text separately");
  553     else if (what == 1)
  554     strcat(s, "body follows");
  555     else if (what == 2)
  556     strcat(s, "head follows");
  557     else
  558     strcat(s, "text follows");
  559     printf("%s\r\n", s);
  560     if (debugmode)
  561     syslog(LOG_DEBUG, ">%s", s);
  562 
  563     while ((t = getaline(f)) && *t) {
  564     if (what & 2) {
  565         if (*t == '.')
  566         (void)fputc('.', stdout);
  567         (void)fputs(t, stdout);
  568         (void)fputs("\r\n", stdout);
  569         if (ferror(stdout))
  570         fatal_write();
  571     }
  572     }
  573     /* Matthias Andree, 2002-03-05:
  574      * t == NULL or *t == '\0' makes a difference here.
  575      * -  t == NULL means we ran into EOF and don't have a body in delaybody mode.
  576      * -  t != NULL but *t == '\0' means we just read the empty separator line between header and body.
  577      */
  578 
  579     if (what == 3)
  580     printf("\r\n");     /* empty separator line */
  581 
  582     if (what & 1) {
  583     /* delaybody:
  584        t == NULL: body is missing, mark for download
  585        t != NULL: body is present */
  586     if (t == NULL) {
  587         if (localartno && markgroup != NULL) {
  588         switch (markdownload(markgroup, localartno)) {
  589         case 0:
  590             printf("\r\n\r\n"
  591                "\t[ Leafnode: ]\r\n"
  592                "\t[ This message has already been "
  593                "marked for download. ]\r\n");
  594             break;
  595         case 1:
  596             printf("\r\n\r\n"
  597                "\t[ Leafnode: ]\r\n"
  598                "\t[ Message %lu of %s ]\r\n"
  599                "\t[ has been marked for download. ]\r\n",
  600                localartno, markgroup);
  601             break;
  602         default:
  603             printf("\r\n\r\n"
  604                "\t[ Leafnode: ]\r\n"
  605                "\t[ Message %lu of %s ]\r\n"
  606                "\t[ cannot be marked for download. ]\r\n"
  607                "\t[ (Check the server's syslog "
  608                "for information). ]\r\n", localartno, markgroup);
  609             break;
  610         }
  611         } else {
  612         /* did not figure a group for which to mark this article */
  613         syslog(LOG_ERR,
  614                "cannot mark for body download: arg=\"%s\" "
  615                "localartno=%lu markgroup=\"%s\" group=\"%s\"",
  616                arg, localartno, markgroup ? markgroup : "(null)",
  617                group ? group->name : "(null)");
  618         printf("\r\n\r\n"
  619                "\t[ Leafnode: ]\r\n"
  620                "\t[ I cannot figure out a newsgroup for which to download ]\r\n"
  621                "\t[ this article. Please report this condition to the ]\r\n"
  622                "\t[ leafnode mailing list, with leafnode version, the name ]\r\n"
  623                "\t[ and the version of your news reader and a log excerpt. ]\r\n");
  624         }
  625     } else {        /* immediate body */
  626         while ((t = getaline(f))) {
  627         if (*t == '.')
  628             (void)fputc('.', stdout);
  629         (void)fputs(t, stdout);
  630         (void)fputs("\r\n", stdout);
  631         if (ferror(stdout))
  632             fatal_write();
  633         }
  634     }
  635     }
  636     if (what)
  637     printf(".\r\n");
  638     (void)fclose(f);
  639 
  640     if (xref)
  641     free(xref);
  642 
  643     return;         /* OF COURSE there were no errors */
  644 }
  645 
  646 
  647 /* change to group - note no checks on group name */
  648 static int
  649 dogroup(const char *arg)
  650 {
  651     struct newsgroup *g;
  652 
  653     g = findgroup(arg);
  654     if (g) {
  655     group = g; /* global */
  656     if (isinteresting(arg)) {
  657         if (debugmode)
  658         syslog(LOG_DEBUG, "marked group %s interesting", arg);
  659         markinterest(arg);
  660     }
  661     if (!is_pseudogroup(g)) {
  662         /* regular news group */
  663         if (debugmode)
  664         syslog(LOG_DEBUG, ">211 %lu %lu %lu %s group selected",
  665                g->last >= g->first ? g->last - g->first + 1 : 0,
  666                g->first, g->last, g->name);
  667         printf("211 %lu %lu %lu %s group selected\r\n",
  668            g->last >= g->first ? g->last - g->first + 1 : 0,
  669            g->first, g->last, g->name);
  670     } else {
  671         /* pseudo news group */
  672         if (debugmode)
  673         syslog(LOG_DEBUG,
  674                ">211 %lu %lu %lu %s group selected (pseudo article)",
  675                1lu, g->first, g->first, g->name);
  676         printf("211 %lu %lu %lu %s group selected (pseudo article)\r\n",
  677                1lu, g->first, g->first, g->name);
  678     }
  679     artno = g->first;
  680     } else {
  681     if (debugmode)
  682         syslog(LOG_DEBUG, ">411 No such group");
  683     printf("411 No such group\r\n");
  684     }
  685     if (fflush(stdout)) return -1;
  686     return 0;
  687 }
  688 
  689 static void
  690 dohelp(void)
  691 {
  692     fputs("100 Legal commands on THIS server:\r\n"
  693         " ARTICLE [<Message-ID>|<Number>]\r\n"
  694         " BODY [<Message-ID>|<Number>]\r\n"
  695         " DATE\r\n"
  696         " GROUP <Newsgroup>\r\n"
  697         " HDR <Header> <Message-ID>|<Range>\r\n"
  698         " HEAD [<Message-ID>|<Number>]\r\n"
  699         " HELP\r\n"
  700         " LAST\r\n"
  701         " LIST [ACTIVE|NEWSGROUPS] [<Wildmat>]]\r\n"
  702         " LIST [ACTIVE.TIMES|EXTENSIONS|OVERVIEW.FMT]\r\n"
  703         " LISTGROUP <Newsgroup>\r\n"
  704         " MODE READER\r\n"
  705         " NEWGROUPS <yymmdd> <hhmmss> [GMT]\r\n"
  706         " NEXT\r\n"
  707         " POST\r\n"
  708         " OVER <Range>\r\n"
  709         " SLAVE\r\n"
  710         " STAT [<Message-ID>|<Number>]\r\n"
  711         " XHDR <Header> <Message-ID>|<Range>\r\n"
  712         " XOVER <Range>\r\n"
  713         ".\r\n", stdout);
  714 }
  715 
  716 static void
  717 domove(int by)
  718 {
  719     char *msgid;
  720     char s[SIZE_s+1];
  721 
  722     by = (by < 0) ? -1 : 1;
  723     if (group) {
  724     if (artno) {
  725         artno += by;
  726         do {
  727         sprintf(s, "%lu", artno);
  728         msgid = getheader(s, "Message-ID:");
  729         if (!msgid)
  730             artno += by;
  731         } while (msgid == NULL && artno >= group->first && artno <= group->last);
  732         if (msgid && (artno > group->last || artno < group->first)) {
  733         free(msgid);
  734         msgid = NULL;
  735         }
  736         if (msgid == NULL) {
  737         if (by > 0) {
  738             artno = group->last;
  739             printf("421 There is no next article\r\n");
  740             if (debugmode)
  741             syslog(LOG_DEBUG, ">421 There is no next article");
  742         } else {
  743             artno = group->first;
  744             printf("422 There is no previous article\r\n");
  745             if (debugmode)
  746             syslog(LOG_DEBUG, ">422 There is no previous article");
  747         }
  748         } else {
  749         printf("223 %lu %s article retrieved\r\n", artno, msgid);
  750         if (debugmode)
  751             syslog(LOG_DEBUG, ">223 %lu %s article retrieved",
  752                artno, msgid);
  753         free(msgid);
  754         }
  755     } else {
  756         printf("420 There is no current article\r\n");
  757         if (debugmode)
  758         syslog(LOG_DEBUG, ">420 There is no current article");
  759     }
  760     } else {
  761     nogroup();
  762     }
  763 }
  764 
  765 static int is_pattern(const char *s) {
  766     return s ? strcspn(s, "?*[") < strlen(s) : 1;
  767 }
  768 
  769 /* LIST ACTIVE if what==0,
  770  * LIST NEWSGROUPS if what==1
  771  * LIST ACTIVE.TIMES if what==2
  772  */
  773 static void
  774 printlist(const struct newsgroup *g, const int what)
  775 {
  776     switch(what) {
  777     case 0:
  778         if (is_pseudogroup(g))
  779         printf("%s %lu %lu y\r\n", g->name, g->first, g->first);
  780         else
  781         printf("%s %lu %lu y\r\n", g->name, g->last, g->first);
  782         break;
  783     case 1:
  784         printf("%s\t%s\r\n", g->name, g->desc ? g->desc : "-x-");
  785         break;
  786     case 2:
  787         printf("%s %lu %s\r\n", g->name, (unsigned long)g->age, newsadmin);
  788         break;
  789     default:
  790         abort();
  791         break;
  792     }
  793 }
  794 
  795 /* LIST ACTIVE if what==0,
  796  * LIST NEWSGROUPS if what==1
  797  * LIST ACTIVE.TIMES if what==2
  798  */
  799 static void
  800 list(struct newsgroup *g, const int what, const char *pattern)
  801 {
  802     if (is_pattern(pattern)) {
  803     while(g->name) {
  804         if (!pattern || !ngmatch(pattern, g->name)) {
  805         printlist(g, what);
  806         }
  807         g++;
  808     }
  809     } else {
  810     /* single group */
  811     g = findgroup(pattern);
  812     if (g) {
  813         printlist(g, what);
  814         if (what == 0 && isinteresting(pattern))
  815         markinterest(pattern);
  816     }
  817     }
  818 }
  819 
  820 static void
  821 dolist(char *arg)
  822 {
  823     if (!strcasecmp(arg, "extensions")) {
  824     printf("202 extensions supported follow\r\n"
  825         "HDR\r\n"
  826         "LISTGROUP\r\n"
  827         "OVER\r\n"
  828         "XHDR\r\n"
  829         "XOVER\r\n"
  830         ".\r\n");
  831     if (debugmode)
  832         syslog(LOG_DEBUG, ">202 extensions supported follow");
  833     } else if (!strcasecmp(arg, "overview.fmt")) {
  834     printf("215 information follows\r\n"
  835         "Subject:\r\n"
  836         "From:\r\n"
  837         "Date:\r\n"
  838         "Message-ID:\r\n"
  839         "References:\r\n"
  840         "Bytes:\r\n"
  841         "Lines:\r\n"
  842         "Xref:full\r\n"
  843         ".\r\n");
  844     if (debugmode)
  845         syslog(LOG_DEBUG, ">215 information follows");
  846     } else if (!strncasecmp(arg, "active.times", 12)) {
  847     if (active) {
  848         const char m[] = "215 Note that leafnode will fetch groups on demand.";
  849         printf("%s\r\n", m);
  850         if (debugmode)
  851         syslog(LOG_DEBUG, ">%s", m);
  852         list(active, 2, NULL);
  853         printf(".\r\n");
  854     } else {
  855         const char e[] = "503 Active file has not been read.";
  856         printf("%s\r\n", e);
  857         if (debugmode)
  858         syslog(LOG_DEBUG, ">%s", e);
  859     }
  860     } else {
  861     if (!active) {
  862         printf("503 Group information file does not exist!\r\n");
  863         syslog(LOG_ERR, ">503 Group information file does not exist!");
  864     } else if (!*arg || !strncasecmp(arg, "active", 6)) {
  865         printf("215 Newsgroups in form \"group high low flags\".\r\n");
  866         if (debugmode)
  867         syslog(LOG_DEBUG,
  868             ">215 Newsgroups in form \"group high low flags\".");
  869         if (active) {
  870         if (!*arg || strlen(arg) == 6)
  871             list(active, 0, NULL);
  872         else {
  873             while (*arg && (!isspace((unsigned char)*arg)))
  874             arg++;
  875             while (*arg && isspace((unsigned char)*arg))
  876             arg++;
  877             list(active, 0, arg);
  878         }
  879         }
  880         printf(".\r\n");
  881     } else if (!strncasecmp(arg, "newsgroups", 10)) {
  882         printf("215 Descriptions in form \"group description\".\r\n");
  883         if (debugmode)
  884         syslog(LOG_DEBUG,
  885             ">215 Descriptions in form \"group description\".");
  886         if (active) {
  887         if (strlen(arg) == 10)
  888             list(active, 1, NULL);
  889         else {
  890             while (*arg && (!isspace((unsigned char)*arg)))
  891             arg++;
  892             while (*arg && isspace((unsigned char)*arg))
  893             arg++;
  894             list(active, 1, arg);
  895         }
  896         }
  897         printf(".\r\n");
  898     } else {
  899         printf("503 Syntax error\r\n");
  900         if (debugmode)
  901         syslog(LOG_DEBUG, ">503 Syntax error");
  902     }
  903     }
  904 }
  905 
  906 static void
  907 donewgroups(const char *arg)
  908 {
  909     struct tm timearray;
  910     struct tm *ltime;
  911     time_t age;
  912     time_t now;
  913     int year, century;
  914     char *l;
  915     long a;
  916     long b;
  917     struct newsgroup *ng;
  918 
  919     now = time(NULL);
  920     ltime = localtime(&now);
  921     if (ltime == NULL) {
  922     syslog(LOG_CRIT, "fatal: localtime returned NULL. abort.");
  923     abort();
  924     }
  925     year = ltime->tm_year % 100;
  926     century = ltime->tm_year / 100; /* 0 for 1900-1999, 1 for 2000-2099 etc */
  927 
  928     memset(&timearray, 0, sizeof(timearray));
  929     l = NULL;
  930     a = (int)strtol(arg, &l, 10);
  931     /* NEWGROUPS may have the form YYMMDD or YYYYMMDD.
  932        Distinguish between the two */
  933     b = a / 10000;
  934     if (b < 100) {
  935     /* YYMMDD */
  936     if (b <= year)
  937         timearray.tm_year = (int)(b + (century * 100));
  938     else
  939         timearray.tm_year = (int)(b + (century - 1) * 100);
  940     } else if (b < 1000) {
  941     /* YYYMMDD - happens with buggy newsreaders */
  942     /* In these readers, YYY=100 is equivalent to YY=00 or YYYY=2000 */
  943     syslog(LOG_NOTICE,
  944            "NEWGROUPS year is %ld: please update your newsreader", b);
  945     timearray.tm_year = (int)b;
  946     } else {
  947     /* YYYYMMDD */
  948     timearray.tm_year = (int)b - 1900;
  949     }
  950     timearray.tm_mon = (int)(a % 10000 / 100) - 1;
  951     timearray.tm_mday = (int)(a % 100);
  952     while (*l && isspace((unsigned char)*l))
  953     l++;
  954     a = strtol(l, &l, 10);  /* we don't care about the rest of the line */
  955     while (*l && isspace((unsigned char)*l))
  956     l++;
  957     timearray.tm_hour = (int)(a / 10000);
  958     timearray.tm_min = (int)(a % 10000 / 100);
  959     timearray.tm_sec = (int)(a % 100);
  960     /* mktime() shall guess correct value of tm_isdst (0 or 1) */
  961     timearray.tm_isdst = -1;
  962     if (0 == strncasecmp(l, "gmt", 3))
  963     age = timegm(&timearray);
  964     else
  965     age = mktime(&timearray);
  966 
  967     printf("231 List of new newsgroups since %ld follows\r\n", (long)age);
  968     if (debugmode)
  969     syslog(LOG_DEBUG, "231 List of new newsgroups since %ld follows",
  970            (long)age);
  971 
  972     ng = active;
  973     if (ng != NULL) 
  974     while (ng->name) {
  975         if (ng->age >= age)
  976         printf("%s %lu %lu y\r\n", ng->name, ng->last, ng->first);
  977         ng++;
  978     }
  979     printf(".\r\n");
  980 }
  981 
  982 /* next bit is copied from INN 1.4 and modified ("broken") by agulbra
  983 
  984    mail to Rich $alz <rsalz@uunet.uu.net> bounced */
  985 
  986 /* Scale time back a bit, for shorter Message-ID's. */
  987 #define OFFSET  (time_t)1026380000L
  988 
  989 /*@observer@*/ static char *
  990 generateMessageID(void)
  991 {
  992     static char ALPHABET[] = "0123456789abcdefghijklmnopqrstuv";
  993 
  994     static char buff[1000];
  995     static time_t then;
  996     static unsigned int fudge;
  997     time_t now;
  998     char *p;
  999     unsigned long n;
 1000 
 1001     now = time(NULL);       /* might be 0, in which case fudge
 1002                    will almost fix it */
 1003     if (now < OFFSET) {
 1004     ln_log(LNLOG_SCRIT, LNLOG_CTOP,
 1005         "your system clock cannot be right. abort.");
 1006     abort();
 1007     }
 1008     if (now != then)
 1009     fudge = 0;
 1010     else
 1011     fudge++;
 1012     then = now;
 1013 
 1014     p = buff;
 1015     *p++ = '<';
 1016     n = (unsigned long)now - OFFSET;
 1017     while (n) {
 1018     *p++ = ALPHABET[(int)(n & 31)];
 1019     n >>= 5;
 1020     }
 1021     *p++ = '-';
 1022     n = fudge * 32768 + (int)getpid();
 1023     while (n) {
 1024     *p++ = ALPHABET[(int)(n & 31)];
 1025     n >>= 5;
 1026     }
 1027     sprintf(p, ".ln1@%-.256s>", fqdn);
 1028     return buff;
 1029 }
 1030 /* the end of what I stole from rsalz and then mangled */
 1031 
 1032 
 1033 static int
 1034 dopost(void)
 1035 {
 1036     char *line;
 1037     int havefrom = 0;
 1038     int havepath = 0;
 1039     int havedate = 0;
 1040     int havenewsgroups = 0;
 1041     int havemessageid = 0;
 1042     int havesubject = 0;
 1043     int err = 0, ferr = 0;
 1044     /*@observer@*/ const char *ferrstr = NULL;
 1045     int o;
 1046     size_t len;
 1047     FILE *out;
 1048     char outname[1000];
 1049     static int postingno;   /* starts as 0 */
 1050     char *suggmid;
 1051     /*@observer@*/ const char *appendheader = NULL;
 1052 
 1053     if (getenv("LN_REJECT_POST_PRE")) {
 1054     printf("400 Posting rejected - debug variable LN_REJECT_POST_PRE exists\r\n");
 1055     return 0;
 1056     }
 1057 
 1058     do {
 1059     (void)xsnprintf(outname, sizeof(outname), "%s/out.going/%d-%d-%d",
 1060         spooldir, (int)getpid(), (int)time(NULL), ++postingno);
 1061 
 1062     o = open(outname, O_WRONLY | O_EXCL | O_CREAT, 0244);
 1063     if (o < 0 && errno != EEXIST) {
 1064         char *errmsg = strerror(errno);
 1065         printf("441 Unable to open spool file %s: %s\r\n", outname, errmsg);
 1066         syslog(LOG_ERR, ">441 Unable to open spool file %s: %s", outname,
 1067             errmsg);
 1068         return 0;
 1069     }
 1070     } while (o < 0);
 1071 
 1072     out = fdopen(o, "w");
 1073     if (out == NULL) {
 1074     char *errmsg = strerror(errno);
 1075     printf("441 Unable to fdopen(%d): %s\r\n", o, errmsg);
 1076     syslog(LOG_ERR, ">441 Unable to fdopen(%d): %s", o, errmsg);
 1077     return 0;
 1078     }
 1079 
 1080     suggmid = generateMessageID();
 1081     printf("340 Ok, recommended ID %s\r\n", suggmid);
 1082     if (debugmode)
 1083     syslog(LOG_DEBUG, ">340 Go ahead.");
 1084     if (fflush(stdout)) return -1;
 1085 
 1086     /* get headers */
 1087     do {
 1088     debug = 0;
 1089     line = getaline(stdin);
 1090     if (!line) { /* timeout */
 1091             unlink(outname);
 1092         exit(0);
 1093     }
 1094 
 1095     if (0 == strcmp(line, ".")) {
 1096         ferr = TRUE;
 1097         ferrstr = "No body found.";
 1098         break;
 1099     }
 1100     debug = debugmode;
 1101 
 1102     if (!strncasecmp(line, "From: ", 6)) {
 1103         if (havefrom)
 1104         ferr = TRUE, ferrstr = "Duplicate From: header";
 1105         else
 1106         havefrom = TRUE;
 1107     }
 1108     if (!strncasecmp(line, "Path: ", 6)) {
 1109         if (havepath)
 1110         ferr = TRUE, ferrstr = "Duplicate Path: header";
 1111         else
 1112         havepath = TRUE;
 1113     }
 1114     if (!strncasecmp(line, "Message-ID: ", 12)) {
 1115         if (havemessageid)
 1116         ferr = TRUE, ferrstr = "Duplicate Message-ID: header";
 1117         else {
 1118         char *vec[2]; /* RATS: ignore */
 1119         int rc;
 1120         
 1121         havemessageid = TRUE;
 1122         if (debugmode)
 1123             syslog(LOG_DEBUG, "debug header: %s", line);
 1124         if (2 != (rc = ln_pcre_extract((unsigned char *)line,
 1125                            (unsigned char *)"Message-ID:\\s+<(?:[^>]+)@([^@>]+)>\\s*$",
 1126                            vec, 2))
 1127             || vec[1] == NULL) {
 1128             ferr = TRUE, ferrstr = "Malformatted Message-ID: header.";
 1129         } else if (!strchr(vec[1], '.')) {
 1130             ferr = TRUE, ferrstr = "Message-ID: header does not have domain name part.";
 1131         } else if (!is_validfqdn(vec[1])) {
 1132             ferr = TRUE, ferrstr = "Message-ID: header contains invalid domain name part.";
 1133         }
 1134         ln_pcre_extract_free(vec, rc);
 1135         }
 1136     }
 1137     if (!strncasecmp(line, "Subject: ", 9)) {
 1138         if (havesubject)
 1139         ferr = TRUE, ferrstr = "Duplicate Subject: header";
 1140         else
 1141         havesubject = TRUE;
 1142     }
 1143     if (!strncasecmp(line, "Newsgroups: ", 12)) {
 1144         if (havenewsgroups)
 1145         ferr = TRUE, ferrstr = "Duplicate Newsgroups: header";
 1146         else
 1147         havenewsgroups = TRUE;
 1148     }
 1149     if (!strncasecmp(line, "Date: ", 6)) {
 1150         if (havedate)
 1151         ferr = TRUE, ferrstr = "Duplicate Date: header";
 1152         else
 1153         havedate = TRUE;
 1154     }
 1155 
 1156     len = strlen(line);
 1157 
 1158     /* check for illegal 8bit/control stuff in header */
 1159     {
 1160         char *t;
 1161         for (t = line; *t; t++) {
 1162         if (*t & 0x80) {
 1163             if (allow_8bit_headers) {
 1164             appendheader = "X-Leafnode-Warning: administrator "
 1165                 "allowed illegal use of 8-bit data in header.\r\n";
 1166             } else {
 1167             ferr = TRUE;
 1168             ferrstr = "Illegal use of 8-bit data in header.";
 1169             break;
 1170             }
 1171         }
 1172         if ((unsigned char)*t < (unsigned char)0x20u && *t != '\t') {
 1173             ferr = TRUE;
 1174             ferrstr = "Illegal use of control data in header.";
 1175             break;
 1176         }
 1177         }
 1178     }
 1179 
 1180     /* checks for non-folded lines */
 1181     if (*line && *line != ' ' && *line != '\t') {
 1182         if (strchr(line, ':') == NULL) {
 1183         /* must have a colon */
 1184         ferr = TRUE;
 1185         ferrstr = "Header tag not found.";
 1186         } else if (strcspn(line, " \t") < strcspn(line, ":")) {
 1187         /* must not have space before colon */
 1188         ferr = TRUE;
 1189         ferrstr = "Whitespace in header tag is not allowed.";
 1190         }
 1191     }
 1192 
 1193     if (len) {
 1194         if (fwrite(line, 1, len, out) != (size_t) len)
 1195         err = 1;
 1196     } else {
 1197         if (!havepath) {
 1198         if (fputs("Path: ", out) == EOF)
 1199             err = 1;
 1200         if (fputs(fqdn, out) == EOF)
 1201             err = 1;
 1202         if (fputs("!not-for-mail\r\n", out) == EOF)
 1203             err = 1;
 1204         }
 1205         if (!havedate) {
 1206         const char *l = rfctime();
 1207         if (fputs("Date: ", out) == EOF)
 1208             err = 1;
 1209         if (fputs(l, out) == EOF)
 1210             err = 1;
 1211         if (fputs("\r\n", out) == EOF)
 1212             err = 1;
 1213         }
 1214         if (!havemessageid) {
 1215         if (fputs("Message-ID: ", out) == EOF)
 1216             err = 1;
 1217         if (fputs(suggmid, out) == EOF)
 1218             err = 1;
 1219         if (fputs("\r\n", out) == EOF)
 1220             err = 1;
 1221         }
 1222         if (appendheader) {
 1223         if (fputs(appendheader, out) == EOF)
 1224             err = 1;
 1225         }
 1226     }
 1227     if (fputs("\r\n", out) == EOF)
 1228         err = 1;
 1229     } while (*line);
 1230 
 1231     /* get bodies */
 1232     if (strcmp(line, "."))
 1233     do {
 1234         debug = 0;
 1235         line = getaline(stdin);
 1236         debug = debugmode;
 1237         if (!line) {
 1238         (void)unlink(outname);
 1239         exit(1);
 1240         }
 1241 
 1242         len = strlen(line);
 1243         if (line[0] == '.') {
 1244         if (len > 1) {
 1245             if (fputs(line + 1, out) == EOF)
 1246             err = 1;
 1247             if (fputs("\r\n", out) == EOF)
 1248             err = 1;
 1249         }
 1250         } else {
 1251         if (fputs(line, out) == EOF)
 1252             err = 1;
 1253         if (fputs("\r\n", out) == EOF)
 1254             err = 1;
 1255         }
 1256     } while (line[0] != '.' || line[1] != '\0');
 1257 
 1258     if (fflush(out))
 1259     err = 1;
 1260     
 1261     if (fsync(fileno(out)))
 1262     err = 1;
 1263 
 1264     if (fclose(out))
 1265     err = 1;
 1266 
 1267     if (!havenewsgroups)
 1268     ferrstr = "Missing Newsgroups: header";
 1269     if (!havesubject)
 1270     ferrstr = "Missing Subject: header";
 1271     if (!havefrom)
 1272     ferrstr = "Missing From: header";
 1273 
 1274     if (getenv("LN_REJECT_POST_POST"))
 1275     ferr = 1;
 1276 
 1277     if (havefrom && havesubject && havenewsgroups && !ferr) {
 1278     if (!err && 0 == chmod(outname, 0644)) {
 1279         printf("240 Article posted, now be patient\r\n");
 1280         if (debugmode)
 1281         syslog(LOG_DEBUG, ">240 Article posted, now be patient");
 1282         return 0;
 1283     } else {
 1284         (void)unlink(outname);
 1285         printf("441 I/O error, article not posted\r\n");
 1286         syslog(LOG_INFO, ">441 I/O error, article not posted");
 1287         return 0;
 1288     }
 1289     }
 1290 
 1291     (void)unlink(outname);
 1292 
 1293     if (getenv("LN_REJECT_POST_POST")) {
 1294     printf("400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n");
 1295     syslog(LOG_INFO, ">400 Posting rejected - debug variable LN_REJECT_POST_POST exists\r\n");
 1296     return 0;
 1297     }
 1298 
 1299     if (ferrstr) {
 1300     printf("441 Post rejected, formatting error: %s\r\n", ferrstr);
 1301     syslog(LOG_INFO, ">441 Post rejected, formatting error: %s", ferrstr);
 1302     } else {
 1303     printf("441 Post rejected, formatting error\r\n");
 1304     syslog(LOG_INFO, ">441 Post rejected, formatting error");
 1305     }
 1306 
 1307     return 0;
 1308 }
 1309 
 1310 static void invalidrange(void)
 1311 {
 1312     printf("420 No articles in specified range.\r\n");
 1313     if (debugmode)
 1314     syslog(LOG_DEBUG, ">420 No articles in specified range.");
 1315 }
 1316 
 1317 /* check if a - b is a valid range for the current group.
 1318  * If it's not, print a 420 error and return 0.
 1319  * If it is, do not print anything and return 1.
 1320  * group must not be NULL!
 1321  */
 1322 static int checkrange(const struct newsgroup *g,
 1323     unsigned long a, unsigned long b)
 1324 {
 1325     if ((a > b) || (g->first <= g->last
 1326         ? (a > g->last) || (b < g->first)
 1327         : (a > g->first) || (b < g->first))) {
 1328     invalidrange();
 1329     return 0;
 1330     }
 1331     return 1;
 1332 }
 1333 
 1334 
 1335 
 1336 static void
 1337 doxhdr(char *arg)
 1338 {
 1339     static const char *h[] = { "Subject", "From", "Date", "Message-ID",
 1340     "References", "Bytes", "Lines"
 1341     };
 1342 
 1343     int n = 7;
 1344     size_t i;
 1345     char *l;
 1346     char *buf;
 1347     unsigned long a, b = 0, c;
 1348     char s[SIZE_s+1];
 1349 
 1350     if (!arg || !*arg) {
 1351     if (debugmode)
 1352         syslog(LOG_DEBUG,
 1353            ">502 Usage: HDR header first[-last] or "
 1354            "HDR header message-id");
 1355     printf("502 Usage: HDR header first[-last] or "
 1356            "HDR header message-id\r\n");
 1357     return;
 1358     }
 1359 
 1360     /* go figure header */
 1361     l = arg;
 1362     while (l && *l && !isspace((unsigned char)*l))
 1363     l++;
 1364     if (l && *l)
 1365     *l++ = '\0';
 1366     SKIPLWS(l);
 1367 
 1368     buf = critmalloc((i = strlen(arg)) + 2, "doxhdr");
 1369     strcpy(buf, arg); /* RATS: ignore */
 1370     if (buf[i - 1] != ':')
 1371     strcpy(buf + i, ":");
 1372 
 1373     if (l && *l == '<') {   /* handle message-id form (well) */
 1374     FILE *f;
 1375     char *m = critstrdup(l, "doxhdr");
 1376     f = fopenart(l);
 1377     if (!f) {
 1378         printf("430 No such article\r\n");
 1379         if (debugmode)
 1380         syslog(LOG_DEBUG, ">430 No such article");
 1381         free(buf);
 1382         free(m);
 1383         return;
 1384     }
 1385     l = fgetheader(f, buf);
 1386     if (debugmode) {
 1387         syslog(LOG_DEBUG, ">221 %s header of %s follows:", buf, m);
 1388         if (l) syslog(LOG_DEBUG, ">%s %s", m, l);
 1389         syslog(LOG_DEBUG, ">.");
 1390     }
 1391     printf("221 %s header of %s follows:\r\n", buf, m);
 1392     if (l) printf("%s %s\r\n", m, l);
 1393     printf(".\r\n");
 1394     free(m);
 1395     (void)fclose(f);
 1396     free(buf);
 1397     if (l) free(l);
 1398     return;
 1399     }
 1400 
 1401     if (!group) {
 1402     nogroup();
 1403     free(buf);
 1404     return;
 1405     }
 1406 
 1407     markinterest(group->name);
 1408 
 1409     a = group->first;
 1410     b = group->last;
 1411     if (b < a) b = a;
 1412     if (!parserange(l, &a, &b)) {
 1413     if (debugmode)
 1414         syslog(LOG_DEBUG, ">502 Usage: XHDR header first[-last] "
 1415             "or XHDR header message-id");
 1416     printf("502 Usage: XHDR header first[-last] "
 1417         "or XHDR header message-id\r\n");
 1418     free(buf);
 1419     return;
 1420     }
 1421 
 1422     if (!checkrange(group, a, b)) {
 1423     free(buf);
 1424     return;
 1425     }
 1426 
 1427     if (!is_pseudogroup(group)) {
 1428         if (xovergroup != group && chdirgroup(group->name, FALSE))
 1429             if (getxover())
 1430                 xovergroup = group;
 1431     }
 1432 
 1433     if (is_pseudogroup(group)) {
 1434     do {
 1435         n--;
 1436     } while (n >= 0 && strncasecmp(h[n], buf, strlen(h[n])) != 0);
 1437     if ((n < 0) && strncasecmp("Newsgroups", buf, 10)) {
 1438         printf("430 No such header: %s\r\n", buf);
 1439         if (debugmode)
 1440         syslog(LOG_DEBUG, ">430 No such header: %s", buf);
 1441         free(buf);
 1442         return;
 1443     }
 1444     if (debugmode)
 1445         syslog(LOG_DEBUG,
 1446             ">221 First line of %s pseudo-header follows:", buf);
 1447     printf("221 First line of %s pseudo-header follows:\r\n", buf);
 1448     if (a <= b && a <= group->first && b >= group->last) {
 1449         printf("%lu ", group->first);
 1450         if (n == 0)     /* Subject: */
 1451         printf("Leafnode placeholder for group %s\r\n", group->name);
 1452         else if (n == 1)    /* From: */
 1453         printf("Leafnode <%s>\r\n", newsadmin);
 1454         else if (n == 2)    /* Date: */
 1455         printf("%s\r\n", rfctime());
 1456         else if (n == 3)    /* Message-ID: */
 1457         printf("<leafnode:placeholder:%s@%s>\r\n", group->name, fqdn);
 1458         else if (n == 4)    /* References */
 1459         printf("(none)\r\n");
 1460         else if (n == 5)    /* Bytes */
 1461         printf("%d\r\n", 1024); /* FIXME: just a guess */
 1462         else if (n == 6)    /* Lines */
 1463         printf("%d\r\n", 22);   /* FIXME: from buildpseudoart() */
 1464         else            /* Newsgroups */
 1465         printf("%s\r\n", group->name);
 1466     }
 1467     printf(".\r\n");
 1468     free(buf);
 1469     return;
 1470     }
 1471 
 1472     do {
 1473     n--;
 1474     } while (n > -1 && strncasecmp(buf, h[n], strlen(h[n])));
 1475 
 1476     if (a < group->first)
 1477     a = group->first;
 1478 
 1479     if (b > group->last)
 1480     b = group->last;
 1481 
 1482     if (n >= 0) {
 1483     if (debugmode)
 1484         syslog(LOG_DEBUG, "221 %s header (from overview) "
 1485            "for postings %lu-%lu:", h[n], a, b);
 1486     printf("221 %s header (from overview) for postings %lu-%lu:\r\n",
 1487            h[n], a, b);
 1488 
 1489     s[sizeof(s)-1] = '\0';
 1490     for (c = a; c <= b; c++) {
 1491         if (xoverinfo &&
 1492         c >= xfirst && c <= xlast && xoverinfo[c - xfirst].text) {
 1493         char *l2 = xoverinfo[c - xfirst].text;
 1494         int d;
 1495         for (d = 0; l2 && d <= n; d++)
 1496             l2 = strchr(l2 + 1, '\t');
 1497         if (l2) {
 1498             char *p;
 1499             (void)strlcpy(s, ++l2, sizeof(s));
 1500             p = strchr(s, '\t');
 1501             if (p)
 1502             *p = '\0';
 1503         }
 1504         if (l2 && *l2) printf("%lu %s\r\n", c, s);
 1505         }
 1506     }
 1507     } else {
 1508     if (debugmode)
 1509         syslog(LOG_DEBUG, ">221 %s header (from article files) "
 1510            "for postings %lu-%lu:", buf, a, b);
 1511     printf("221 %s header (from article files) for postings %lu-%lu:\r\n",
 1512            buf, a, b);
 1513     for (c = a; c <= b; c++) {
 1514         sprintf(s, "%lu", c);
 1515         l = getheader(s, buf);
 1516         if (l) {
 1517         printf("%lu %s\r\n", c, l); /* (l && *l) ? l : "(none)" ); */
 1518         free(l);
 1519         }
 1520     }
 1521     }
 1522 
 1523     free(buf);
 1524     printf(".\r\n");
 1525     return;
 1526 }
 1527 
 1528 static void
 1529 doxover(char *arg)
 1530 {
 1531     unsigned long a, b, art;
 1532 
 1533     if (!group) {
 1534     nogroup();
 1535     return;
 1536     }
 1537 
 1538     markinterest(group->name);
 1539     a = group->first;
 1540     b = group->last;
 1541     if (b < a) b = a;
 1542 
 1543     if (!arg || !*arg)
 1544     a = b = artno;
 1545     else if (!parserange(arg, &a, &b)) {
 1546     printf("502 Usage: OVER first[-[last]]\r\n");
 1547     if (debugmode)
 1548         syslog(LOG_DEBUG, ">502 Usage: OVER first[-[last]]");
 1549     return;
 1550     }
 1551 
 1552     if (!checkrange(group, a, b))
 1553     return;
 1554 
 1555     if (!is_pseudogroup(group)) {
 1556     if (xovergroup != group && chdirgroup(group->name, FALSE))
 1557         if (getxover()) xovergroup = group;
 1558 
 1559     if (NULL == xoverinfo) {
 1560         invalidrange();
 1561         return;
 1562     }
 1563     if (b > xlast)
 1564         b = xlast;
 1565     if (a < xfirst)
 1566         a = xfirst;
 1567 
 1568     printf("224 Overview information for postings %lu-%lu:\r\n", a, b);
 1569     if (debugmode)
 1570         syslog(LOG_DEBUG, ">224 Overview information for postings %lu-%lu:",
 1571            a, b);
 1572     for (art = a; art <= b; art++) {
 1573         if (xoverinfo[art - xfirst].text)
 1574         printf("%s\r\n", xoverinfo[art - xfirst].text);
 1575     }
 1576     printf(".\r\n");
 1577     } else {
 1578     if ((a > b) || (group->first <= group->last
 1579         ? (a > group->last) || (b < group->first)
 1580         : (a > group->first) || (b < group->first))) {
 1581         printf("420 No articles in specified range.\r\n");
 1582         if (debugmode)
 1583         syslog(LOG_DEBUG, ">420 No articles in specified range.");
 1584         return;
 1585     }
 1586 
 1587     printf("224 Overview information (pseudo) for postings %lu-%lu:\r\n", 
 1588         group->first, group->first);
 1589     if (debugmode)
 1590         syslog(LOG_DEBUG, ">224 Overview information (pseudo) for "
 1591            "postings %lu-%lu:", group->first, group->first);
 1592     printf("%lu\t"
 1593            "Leafnode placeholder for group %s\t"
 1594            "%s (Leafnode)\t%s\t"
 1595            "<leafnode:placeholder:%s@%s>\t\t1000\t40\r\n", group->first,
 1596            group->name, newsadmin, rfctime(), group->name, fqdn);
 1597     printf(".\r\n");
 1598     if (debugmode)
 1599         syslog(LOG_DEBUG, ">%lu\tLeafnode placeholder for group %s\t"
 1600            "%s (Leafnode)\t%s\t<leafnode:placeholder:%s@%s>\t\t1000\t40",
 1601            group->first, group->name, newsadmin, rfctime(), group->name, fqdn);
 1602     }
 1603 }
 1604 
 1605 static void
 1606 dolistgroup(const char *arg)
 1607 {
 1608     unsigned long art;
 1609 
 1610     if (arg && *(arg)) {
 1611     struct newsgroup *g;
 1612     g = findgroup(arg);
 1613     if (g) {
 1614         group = g;
 1615         artno = g->first;
 1616     } else  {
 1617         printf("411 No such group: %s\r\n", arg);
 1618         if (debugmode)
 1619         syslog(LOG_DEBUG, ">411 No such group: %s", arg);
 1620         return;
 1621     }
 1622     }
 1623 
 1624     if (!group) {
 1625     nogroup();
 1626     return;
 1627     }
 1628 
 1629     /* group = g; */
 1630     markinterest(group->name);
 1631     if ((NULL == xovergroup || xovergroup != group)
 1632         && chdirgroup(group->name, FALSE))
 1633         if (getxover()) xovergroup = group;
 1634 
 1635     if (is_pseudogroup(group)) {
 1636     printf("211 Article list for %s follows (pseudo)\r\n", group->name);
 1637     if (debugmode)
 1638         syslog(LOG_DEBUG,
 1639            ">211 Article list for %s follows (pseudo)", group->name);
 1640     printf("%lu\r\n", group->first ? group->first : 1);
 1641     } else {
 1642     printf("211 Article list for %s follows\r\n", group->name);
 1643     if (debugmode)
 1644         syslog(LOG_DEBUG, ">211 Article list for %s follows", group->name);
 1645     if (xoverinfo)
 1646         for (art = xfirst; art <= xlast; art++) {
 1647         if (xoverinfo[art - xfirst].text)
 1648             printf("%lu\r\n", art);
 1649         }
 1650     }
 1651     printf(".\r\n");
 1652 }
 1653 
 1654 static void
 1655 parser(void)
 1656 {
 1657     char *arg;
 1658     int n;
 1659     size_t size;
 1660 
 1661     mgetaline_settimeout(timeout_client);
 1662 
 1663     while ((cmd = mgetaline(stdin))) {
 1664     if (debug == 1)
 1665         syslog(LOG_DEBUG, "<%s", cmd);
 1666 
 1667     size = strlen(cmd);
 1668     if (size == 0)
 1669         continue;       /* ignore */
 1670     if (size > MAXLINELENGTH || (long)size > (long)INT_MAX) {
 1671         /* ignore attempts at buffer overflow */
 1672         if (debugmode)
 1673         syslog(LOG_DEBUG, ">500 Dazed and confused");
 1674         printf("500 Dazed and confused\r\n");
 1675         continue;
 1676     }
 1677 
 1678     /* parse command line */
 1679     n = 0;
 1680     while (isalpha((unsigned char)cmd[n]))
 1681         n++;
 1682     while (isspace((unsigned char)cmd[n]))
 1683         cmd[n++] = '\0';
 1684 
 1685     arg = cmd + n;
 1686 
 1687     while (cmd[n])
 1688         n++;
 1689     n--;
 1690     while (n >= 0 && isspace((unsigned char)cmd[n]))
 1691         cmd[n--] = '\0';
 1692 
 1693     if (!strcasecmp(cmd, "quit")) {
 1694         if (debugmode)
 1695         syslog(LOG_DEBUG, ">205 Always happy to serve!");
 1696         printf("205 Always happy to serve!\r\n");
 1697         return;
 1698     }
 1699     rereadactive();
 1700     if (!strcasecmp(cmd, "article")) {
 1701         doarticle(arg, 3);
 1702     } else if (!strcasecmp(cmd, "head")) {
 1703         doarticle(arg, 2);
 1704     } else if (!strcasecmp(cmd, "body")) {
 1705         doarticle(arg, 1);
 1706     } else if (!strcasecmp(cmd, "stat")) {
 1707         doarticle(arg, 0);
 1708     } else if (!strcasecmp(cmd, "help")) {
 1709         dohelp();
 1710     } else if (!strcasecmp(cmd, "last")) {
 1711         domove(-1);
 1712     } else if (!strcasecmp(cmd, "next")) {
 1713         domove(1);
 1714     } else if (!strcasecmp(cmd, "list")) {
 1715         dolist(arg);
 1716     } else if (!strcasecmp(cmd, "date")) {
 1717         dodate();
 1718     } else if (!strcasecmp(cmd, "mode")) {
 1719         if (debugmode)
 1720         syslog(LOG_DEBUG, ">200 Leafnode %s, pleased to meet you!",
 1721                version);
 1722         printf("200 Leafnode %s, pleased to meet you!\r\n", version);
 1723     } else if (!strcasecmp(cmd, "newgroups")) {
 1724         donewgroups(arg);
 1725     } else if (!strcasecmp(cmd, "newnews")) {
 1726         if (debugmode)
 1727         syslog(LOG_DEBUG,
 1728                ">500 NEWNEWS is meaningless for this server");
 1729         printf("500 NEWNEWS is meaningless for this server\r\n");
 1730     } else if (!strcasecmp(cmd, "post")) {
 1731         if (dopost()) break;
 1732     } else if (!strcasecmp(cmd, "slave")) {
 1733         if (debugmode)
 1734         syslog(LOG_DEBUG, ">202 Cool - I always wanted a slave");
 1735         printf("202 Cool - I always wanted a slave\r\n");
 1736     } else if (!strcasecmp(cmd, "xhdr")) {
 1737         doxhdr(arg);
 1738     } else if (!strcasecmp(cmd, "hdr")) {
 1739         doxhdr(arg);
 1740     } else if (!strcasecmp(cmd, "xover")) {
 1741         doxover(arg);
 1742     } else if (!strcasecmp(cmd, "over")) {
 1743         doxover(arg);
 1744     } else if (!strcasecmp(cmd, "listgroup")) {
 1745         dolistgroup(arg);
 1746     } else if (!strcasecmp(cmd, "group")) {
 1747         if (dogroup(arg)) break;
 1748     } else {
 1749         if (debugmode)
 1750         syslog(LOG_DEBUG, ">500 Unknown command");
 1751         printf("500 Unknown command\r\n");
 1752     }
 1753     if (ferror(stdout) || fflush(stdout)) {
 1754         syslog(LOG_ERR, "Cannot write to client: %s", strerror(errno));
 1755         break;
 1756     }
 1757     }
 1758     if (debugmode)
 1759     syslog(LOG_DEBUG, "Client timeout, disconnecting.");
 1760 
 1761     /* There was once a 400 error message here. It confused broken
 1762      * clients, most notably, tin.
 1763      * Future NNTP drafts command that we don't send stuff back on
 1764      * timeout, so we anticipate these. */
 1765 }
 1766 
 1767 int
 1768 main(int argc, char **argv)
 1769 {
 1770     socklen_t fodder;
 1771     char peername[256]; /* RATS: ignore */
 1772 #ifdef HAVE_IPV6
 1773     char *st;
 1774     int h_err;
 1775 #define ADDRLEN INET6_ADDRSTRLEN
 1776     union sockaddr_union su;
 1777 #else
 1778     struct hostent *he;
 1779 #ifdef INET_ADDRSTRLEN
 1780 #define ADDRLEN INET_ADDRSTRLEN
 1781 #else
 1782 #define ADDRLEN 16
 1783 #endif
 1784     struct sockaddr_in sa;
 1785 #endif
 1786     char peerip[ADDRLEN]; /* RATS: ignore */
 1787     char ownip[ADDRLEN]; /* RATS: ignore */
 1788     char origfqdn[FQDNLEN + 1]; /* RATS: ignore */
 1789 
 1790     ln_log_use_console(0); /* disable console logging */
 1791     (void)argc; /* quiet compiler warning */
 1792     myopenlog("leafnode");
 1793 
 1794     /* this gets the actual hostname */
 1795     if (!initvars(argv[0]))
 1796     exit(1);
 1797 
 1798     artno = 0;
 1799     verbose = 0;
 1800     (void)umask(2);
 1801 
 1802     /* this reads the host name from the config file */
 1803     if (!readconfig(1)) {
 1804     const char *m = "503 Unable to read configuration file, exiting; the server's syslog should have more information.";
 1805     printf("%s\r\n", m);
 1806     syslog(LOG_ERR, "%s", m);
 1807     exit(1);
 1808     }
 1809     freeservers();
 1810 
 1811     strcpy(origfqdn, fqdn); /* same size buffer */ /* RATS: ignore */
 1812 
 1813     /* get own name */
 1814 #ifdef HAVE_IPV6
 1815     fodder = sizeof(union sockaddr_union);
 1816     if (0 == getsockname(0, (struct sockaddr *)&su, &fodder)) {
 1817     if (su.sin.sin_family == AF_INET6)
 1818         inet_ntop(AF_INET6, &su.sin6.sin6_addr, ownip, sizeof(ownip));
 1819     else
 1820         inet_ntop(AF_INET, &su.sin.sin_addr, ownip, sizeof(ownip));
 1821 
 1822     if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) {
 1823         xstrlcpy(fqdn, st, sizeof(fqdn));
 1824         free(st);
 1825     }
 1826     }
 1827 #else
 1828     fodder = sizeof(struct sockaddr_in);
 1829     if (0 == getsockname(0, (struct sockaddr *)&sa, &fodder)) {
 1830     he = gethostbyaddr((char *)&sa.sin_addr.s_addr,
 1831                sizeof(sa.sin_addr.s_addr), AF_INET);
 1832     *fqdn = '\0';
 1833     (void)xstrlcpy(fqdn,
 1834         he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr),
 1835         sizeof(fqdn));
 1836     strcpy(ownip, inet_ntoa(sa.sin_addr));
 1837     }
 1838 #endif
 1839     else {
 1840     strcpy(ownip, "no IP");
 1841     }
 1842 
 1843     /* get remote name */
 1844 #ifdef HAVE_IPV6
 1845     fodder = sizeof(union sockaddr_union);
 1846     if (0 == getpeername(0, (struct sockaddr *)&su, &fodder)) {
 1847     if (su.sa.sa_family == AF_INET6)
 1848         inet_ntop(AF_INET6, &su.sin6.sin6_addr, peername, sizeof(peername));
 1849     else
 1850         inet_ntop(AF_INET, &su.sin.sin_addr, peername, sizeof(peername));
 1851 
 1852     strcpy(peerip, peername);
 1853 
 1854     if ((st = masock_sa2name((struct sockaddr *)&su, &h_err))) {
 1855         xstrlcpy(peername, st, sizeof(peername));
 1856         free(st);
 1857     }
 1858 
 1859     }
 1860 #else
 1861     fodder = sizeof(struct sockaddr_in);
 1862     if (0 == getpeername(0, (struct sockaddr *)&sa, &fodder)) {
 1863     he = gethostbyaddr((char *)&sa.sin_addr.s_addr,
 1864                sizeof(sa.sin_addr.s_addr), AF_INET);
 1865     (void)xstrlcpy(peername,
 1866         he && he->h_name ? he->h_name : inet_ntoa(sa.sin_addr),
 1867         sizeof(peername));
 1868     strcpy(peerip, inet_ntoa(sa.sin_addr));
 1869     }
 1870 #endif
 1871     else {
 1872     if (errno == ENOTSOCK) {
 1873         strcpy(peername, "(local file)");
 1874         strcpy(peerip, "no IP");
 1875     } else {
 1876         strcpy(peerip, "unknown");
 1877         strcpy(peername, "(unknown)");
 1878     }
 1879     }
 1880 
 1881     if (allowstrangers == 0 && checkpeerlocal(0) != 1) {
 1882     unsigned int i = 5;
 1883 
 1884     syslog(LOG_NOTICE, "Refusing connect from %s (%s) to %s (%s) (my fqdn: %s), outside the local networks. (Check config.example.)",
 1885         peername, peerip, fqdn, ownip, origfqdn);
 1886     while (i)
 1887         i = sleep(i);
 1888     printf("502 Remote access denied.\n");
 1889     exit(0);
 1890     }
 1891 
 1892     syslog(LOG_INFO, "connect from %s (%s) to %s (%s) (my fqdn: %s)",
 1893         peername, peerip, fqdn, ownip, origfqdn);
 1894 
 1895     printf("200 Leafnode NNTP Daemon, version %s "
 1896         "running at %s (my fqdn: %s)\r\n",
 1897         version, fqdn, origfqdn);
 1898     if (fflush(stdout)) exit(0);
 1899 
 1900     strcpy(fqdn, origfqdn);
 1901 
 1902     rereadactive();
 1903 
 1904     parser();
 1905 
 1906     (void)fflush(stdout);
 1907     freeactive(active);
 1908     freexover();
 1909     freeconfig();
 1910     sleep(1); /* protect against process ID induced file name collisions */
 1911     exit(0);
 1912 }