1 /*
2 **  NNTP server for readers (NNRP) for InterNetNews.
3 **
4 **  This server doesn't do any real load-limiting, except for what has
5 **  proven empirically necessary (i.e. look at GRPscandir).
6 */
7 
8 #include "portable/system.h"
9 
10 #include "portable/setproctitle.h"
11 #include "portable/socket.h"
12 #include <netdb.h>
13 #include <signal.h>
14 #if defined(INN_BSDI_HOST)
15 #    include <netinet/tcp.h>
16 #endif
17 
18 #if HAVE_GETSPNAM
19 #    include <shadow.h>
20 #endif
21 #include <sys/wait.h>
22 
23 #include "inn/innconf.h"
24 #include "inn/libinn.h"
25 #include "inn/messages.h"
26 #include "inn/network-innbind.h"
27 #include "inn/network.h"
28 #include "inn/newsuser.h"
29 #include "inn/ov.h"
30 #include "inn/overview.h"
31 #include "inn/version.h"
32 
33 /* Silent this warning because of the way we deal with EXTERN. */
34 #if defined(__llvm__) || defined(__clang__)
35 #    pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
36 #endif
37 
38 #define MAINLINE
39 #include "nnrpd.h"
40 
41 #include "tls.h"
42 
43 #if defined(HAVE_OPENSSL) || defined(HAVE_SASL)
44 bool encryption_layer_on = false;
45 #endif
46 
47 static void Usage(void) __attribute__((__noreturn__));
48 
49 /*
50 **  If we have getloadavg, include the appropriate header file.  Otherwise,
51 **  just assume that we always have a load of 0.
52 */
53 #if HAVE_GETLOADAVG
54 #    if HAVE_SYS_LOADAVG_H
55 #        include <sys/loadavg.h>
56 #    endif
57 #else
58 static int
getloadavg(double loadavg[],int nelem)59 getloadavg(double loadavg[], int nelem)
60 {
61     int i;
62 
63     for (i = 0; i < nelem && i < 3; i++)
64         loadavg[i] = 0;
65     return i;
66 }
67 #endif
68 
69 
70 #define CMDany -1
71 
72 
73 typedef struct _CMDENT {
74     const char *Name;
75     void (*Function)(int, char **);
76     bool Needauth;
77     int Minac;
78     int Maxac;
79     bool Stripspaces;
80     const char *Help;
81 } CMDENT;
82 
83 
84 char *ACTIVE = NULL;
85 char *ACTIVETIMES = NULL;
86 char *HISTORY = NULL;
87 char *NEWSGROUPS = NULL;
88 char *NNRPACCESS = NULL;
89 
90 static char *LocalLogFileName = NULL;
91 static char *LocalLogDirName;
92 
93 static double STATstart;
94 static double STATfinish;
95 static char *PushedBack;
96 static sig_atomic_t ChangeTrace;
97 static bool DaemonMode = false;
98 static bool ForeGroundMode = false;
99 static const char *HostErrorStr;
100 static bool GetHostByAddr = true; /* Formerly DO_NNRP_GETHOSTBYADDR. */
101 const char *NNRPinstance = "";
102 
103 /* Default values for the syntaxchecks parameter in inn.conf. */
104 bool laxmid = false;
105 
106 /* Other default values. */
107 bool Tracing = false;
108 
109 #ifdef DO_PERL
110 bool PerlLoaded = false;
111 #endif
112 
113 #ifdef DO_PYTHON
114 bool PY_use_dynamic = false;
115 #endif
116 
117 static char CMDfetchhelp[] = "[message-ID|number]";
118 
119 
120 /*
121 **  { command base name, function to call, need authentication,
122 **    min args, max args, strip spaces, help string }
123 */
124 /* clang-format off */
125 static CMDENT CMDtable[] = {
126     {   "ARTICLE",      CMDfetch,        true,   1,  2,      true,
127         CMDfetchhelp },
128     /* Parse AUTHINFO in a special way so as to keep white spaces
129      * in usernames and passwords. */
130     {   "AUTHINFO",     CMDauthinfo,     false,  3,  CMDany, false,
131         "USER name|PASS password"
132 #ifdef HAVE_SASL
133         "|SASL mechanism [initial-response]"
134 #endif
135         "|GENERIC program [argument ...]" },
136     {   "BODY",         CMDfetch,        true,   1,  2,      true,
137         CMDfetchhelp },
138     {   "CAPABILITIES", CMDcapabilities, false,  1,  2,      true,
139         "[keyword]" },
140 #if defined(HAVE_ZLIB)
141     {   "COMPRESS",     CMDcompress,     false,  2,  2,      true,
142         "DEFLATE" },
143 #endif
144     {   "DATE",         CMDdate,         false,  1,  1,      true,
145         NULL },
146     {   "GROUP",        CMDgroup,        true,   2,  2,      true,
147         "newsgroup" },
148     {   "HDR",          CMDpat  ,        true,   2,  3,      true,
149         "header [message-ID|range]" },
150     {   "HEAD",         CMDfetch,        true,   1,  2,      true,
151         CMDfetchhelp },
152     {   "HELP",         CMDhelp,         false,  1,  1,      true,
153         NULL },
154     {   "IHAVE",        CMDpost,         true,   2,  2,      true,
155         "message-ID" },
156     {   "LAST",         CMDnextlast,     true,   1,  1,      true,
157         NULL },
158     {   "LIST",         CMDlist,         true,   1,  3,      true,
159         "[ACTIVE [wildmat]|ACTIVE.TIMES [wildmat]|COUNTS [wildmat]|DISTRIB.PATS"
160         "|DISTRIBUTIONS|HEADERS [MSGID|RANGE]|MODERATORS|MOTD|NEWSGROUPS [wildmat]"
161         "|OVERVIEW.FMT|SUBSCRIPTIONS [wildmat]]" },
162     {   "LISTGROUP",    CMDgroup,        true,   1,  3,      true,
163         "[newsgroup [range]]" },
164     {   "MODE",         CMDmode,         false,  2,  2,      true,
165         "READER" },
166     {   "NEWGROUPS",    CMDnewgroups,    true,   3,  4,      true,
167         "[yy]yymmdd hhmmss [GMT]" },
168     {   "NEWNEWS",      CMDnewnews,      true,   4,  5,      true,
169         "wildmat [yy]yymmdd hhmmss [GMT]" },
170     {   "NEXT",         CMDnextlast,     true,   1,  1,      true,
171         NULL },
172     {   "OVER",         CMDover,         true,   1,  2,      true,
173         "[range]" },
174     {   "POST",         CMDpost,         true,   1,  1,      true,
175         NULL },
176     {   "QUIT",         CMDquit,         false,  1,  1,      true,
177         NULL },
178     /* SLAVE (which was ill-defined in RFC 977) was removed from the NNTP
179      * protocol in RFC 3977. */
180     {   "SLAVE",        CMD_unimp,       false,  1,  1,      true,
181         NULL },
182 #ifdef HAVE_OPENSSL
183     {   "STARTTLS",     CMDstarttls,     false,  1,  1,      true,
184         NULL },
185 #endif
186     {   "STAT",         CMDfetch,        true,   1,  2,      true,
187         CMDfetchhelp },
188     {   "XGTITLE",      CMDxgtitle,      true,   1,  2,      true,
189         "[wildmat]" },
190     {   "XHDR",         CMDpat,          true,   2,  3,      true,
191         "header [message-ID|range]" },
192     {   "XOVER",        CMDover,         true,   1,  2,      true,
193         "[range]" },
194     {   "XPAT",         CMDpat,          true,   4,  CMDany, true,
195         "header message-ID|range pattern [pattern ...]" },
196     {   NULL,           CMD_unimp,       false,  0,  0,      true,
197         NULL }
198 };
199 /* clang-format on */
200 
201 static const char *const timer_name[] = {
202     "idle", "newnews", "readart", "checkart", "nntpread", "nntpwrite",
203 };
204 
205 
206 /*
207 **  Log a summary status message and exit.
208 */
209 void
ExitWithStats(int x,bool readconf)210 ExitWithStats(int x, bool readconf)
211 {
212     double usertime;
213     double systime;
214 
215     line_free(&NNTPline);
216     fflush(stdout);
217     STATfinish = TMRnow_double();
218     if (GetResourceUsage(&usertime, &systime) < 0) {
219         usertime = 0;
220         systime = 0;
221     }
222 
223     GRPreport();
224     if (ARTcount)
225         syslog(L_NOTICE, "%s exit articles %ld groups %ld", Client.host,
226                ARTcount, GRPcount);
227     if (POSTreceived || POSTrejected)
228         syslog(L_NOTICE, "%s posts received %ld rejected %ld", Client.host,
229                POSTreceived, POSTrejected);
230     syslog(L_NOTICE, "%s times user %.3f system %.3f idle %.3f elapsed %.3f",
231            Client.host, usertime, systime, IDLEtime, STATfinish - STATstart);
232     /* Tracking code - Make entries in the logfile(s) to show that we have
233      * finished with this session. */
234     if (!readconf && PERMaccessconf && PERMaccessconf->readertrack) {
235         syslog(L_NOTICE, "%s Tracking Disabled (%s)", Client.host, Username);
236         if (LLOGenable) {
237             fprintf(locallog, "%s Tracking Disabled (%s)\n", Client.host,
238                     Username);
239             fclose(locallog);
240             syslog(L_NOTICE, "%s Local Logging ends (%s) %s", Client.host,
241                    Username, LocalLogFileName);
242         }
243     }
244     if (ARTget)
245         syslog(L_NOTICE, "%s artstats get %ld time %ld size %ld", Client.host,
246                ARTget, ARTgettime, ARTgetsize);
247     if (!readconf && PERMaccessconf && PERMaccessconf->nnrpdoverstats
248         && OVERcount)
249         syslog(L_NOTICE,
250                "%s overstats count %ld hit %ld miss %ld time %ld size %ld dbz "
251                "%ld seek %ld get %ld artcheck %ld",
252                Client.host, OVERcount, OVERhit, OVERmiss, OVERtime, OVERsize,
253                OVERdbz, OVERseek, OVERget, OVERartcheck);
254 
255 #ifdef HAVE_OPENSSL
256     if (tls_conn) {
257         SSL_shutdown(tls_conn);
258         SSL_free(tls_conn);
259         tls_conn = NULL;
260     }
261 #endif
262 
263 #ifdef HAVE_SASL
264     if (sasl_conn) {
265         sasl_dispose(&sasl_conn);
266         sasl_conn = NULL;
267         sasl_ssf = 0;
268         sasl_maxout = NNTP_MAXLEN_COMMAND;
269     }
270     sasl_done();
271 #endif /* HAVE_SASL */
272 
273 #if defined(HAVE_ZLIB)
274     if (compression_layer_on) {
275         inflateEnd(zstream_in);
276         free(zstream_in);
277         free(zbuf_in);
278         deflateEnd(zstream_out);
279         free(zstream_out);
280         free(zbuf_out);
281     }
282 #endif /* HAVE_ZLIB */
283 
284     if (DaemonMode) {
285         shutdown(STDIN_FILENO, 2);
286         shutdown(STDOUT_FILENO, 2);
287         shutdown(STDERR_FILENO, 2);
288         close(STDIN_FILENO);
289         close(STDOUT_FILENO);
290         close(STDERR_FILENO);
291     }
292 
293     OVclose();
294     SMshutdown();
295 
296 #ifdef DO_PYTHON
297     PY_close_python();
298 #endif /* DO_PYTHON */
299 
300     if (History)
301         HISclose(History);
302 
303     if (innconf->timer != 0) {
304         TMRsummary(Client.host, timer_name);
305         TMRfree();
306     }
307 
308     if (LocalLogFileName != NULL)
309         free(LocalLogFileName);
310     closelog();
311     exit(x);
312 }
313 
314 
315 /*
316 **  The HELP command.
317 */
318 void
CMDhelp(int ac UNUSED,char * av[]UNUSED)319 CMDhelp(int ac UNUSED, char *av[] UNUSED)
320 {
321     CMDENT *cp;
322     char *p, *q;
323     static const char *newsmaster = NEWSMASTER;
324 
325     Reply("%d Legal commands\r\n", NNTP_INFO_HELP);
326     for (cp = CMDtable; cp->Name; cp++) {
327         if (cp->Function == CMD_unimp)
328             continue;
329         if (cp->Help == NULL)
330             Printf("  %s\r\n", cp->Name);
331         else
332             Printf("  %s %s\r\n", cp->Name, cp->Help);
333     }
334     if (PERMaccessconf && (VirtualPathlen > 0)) {
335         if (PERMaccessconf->newsmaster) {
336             if (strchr(PERMaccessconf->newsmaster, '@') == NULL) {
337                 Printf("Report problems to <%s@%s>.\r\n",
338                        PERMaccessconf->newsmaster, PERMaccessconf->domain);
339             } else {
340                 Printf("Report problems to <%s>.\r\n",
341                        PERMaccessconf->newsmaster);
342             }
343         } else {
344             /* Sigh, pickup from newsmaster anyway. */
345             if ((p = strchr(newsmaster, '@')) == NULL)
346                 Printf("Report problems to <%s@%s>.\r\n", newsmaster,
347                        PERMaccessconf->domain);
348             else {
349                 q = xstrndup(newsmaster, p - newsmaster);
350                 Printf("Report problems to <%s@%s>.\r\n", q,
351                        PERMaccessconf->domain);
352                 free(q);
353             }
354         }
355     } else {
356         if (strchr(newsmaster, '@') == NULL)
357             Printf("Report problems to <%s@%s>.\r\n", newsmaster,
358                    innconf->fromhost);
359         else
360             Printf("Report problems to <%s>.\r\n", newsmaster);
361     }
362     Printf(".\r\n");
363 }
364 
365 
366 /*
367 **  The CAPABILITIES command.
368 **
369 **  nnrpd does not advertise the MODE-READER capability; only innd may
370 **  advertise it.
371 */
372 void
CMDcapabilities(int ac,char * av[])373 CMDcapabilities(int ac, char *av[])
374 {
375 #ifdef HAVE_SASL
376     const char *mechlist = NULL;
377 #endif
378 
379     if (ac == 2 && !IsValidKeyword(av[1])) {
380         Reply("%d Syntax error in keyword\r\n", NNTP_ERR_SYNTAX);
381         return;
382     }
383 
384     Reply("%d Capability list:\r\n", NNTP_INFO_CAPABILITIES);
385 
386     Printf("VERSION 2\r\n");
387 
388     Printf("IMPLEMENTATION %s\r\n", INN_VERSION_STRING);
389 
390 #ifdef HAVE_SASL
391     /* Check for available SASL mechanisms.
392      * Start the string with a space for the strstr() calls afterwards. */
393     sasl_listmech(sasl_conn, NULL, " ", " ", "", &mechlist, NULL, NULL);
394 #endif
395 
396     /* The client is not already authenticated. */
397     if ((!PERMauthorized || PERMneedauth || PERMcanauthenticate)) {
398         Printf("AUTHINFO");
399 
400         /* No arguments if the server does not permit any authentication
401          * commands in its current state (either a compression layer other
402          * than the one negotiated along with TLS is active, or the user
403          * has no way to authenticate successfully). */
404         if (
405 #if defined(HAVE_ZLIB)
406             (!compression_layer_on || tls_compression_on) &&
407 #endif /* HAVE_ZLIB */
408             PERMcanauthenticate) {
409 #ifdef HAVE_OPENSSL
410             if (PERMcanauthenticatewithoutSSL || encryption_layer_on) {
411 #endif
412                 /* AUTHINFO USER is advertised only if a TLS layer is active,
413                  * if compiled with TLS support. */
414                 Printf(" USER");
415 #ifdef HAVE_OPENSSL
416             } else {
417 #    ifdef HAVE_SASL
418                 /* Remove unsecure PLAIN, LOGIN and EXTERNAL SASL mechanisms,
419                  * if compiled with TLS support and a TLS layer is not active.
420                  */
421                 if (mechlist != NULL) {
422                     char *p;
423 
424                     if ((p = strstr(mechlist, " PLAIN")) != NULL
425                         && (p[6] == '\0' || p[6] == ' ')) {
426                         memmove(p, p + 6, strlen(p) - 5);
427                     }
428                     if ((p = strstr(mechlist, " LOGIN")) != NULL
429                         && (p[6] == '\0' || p[6] == ' ')) {
430                         memmove(p, p + 6, strlen(p) - 5);
431                     }
432                     if ((p = strstr(mechlist, " EXTERNAL")) != NULL
433                         && (p[9] == '\0' || p[9] == ' ')) {
434                         memmove(p, p + 9, strlen(p) - 8);
435                     }
436                 }
437 #    endif /* HAVE_SASL */
438             }
439 #endif /* HAVE_OPENSSL */
440 #ifdef HAVE_SASL
441             /* Check whether at least one SASL mechanism is available. */
442             if (mechlist != NULL && strlen(mechlist) > 2) {
443                 Printf(" SASL");
444             }
445 #endif
446         }
447         Printf("\r\n");
448     }
449 
450 #if defined(HAVE_ZLIB)
451     /* A compression layer is not active. */
452     if (!compression_layer_on) {
453         Printf("COMPRESS DEFLATE\r\n");
454     }
455 #endif /* HAVE_ZLIB */
456 
457     if (PERMcanread) {
458         Printf("HDR\r\n");
459     }
460 
461     if (PERMaccessconf->allowihave && PERMcanpost) {
462         Printf("IHAVE\r\n");
463     }
464 
465     Printf(
466         "LIST ACTIVE ACTIVE.TIMES COUNTS DISTRIB.PATS DISTRIBUTIONS"
467         " HEADERS MODERATORS MOTD NEWSGROUPS OVERVIEW.FMT SUBSCRIPTIONS\r\n");
468 
469     if (PERMaccessconf->allownewnews && PERMcanread) {
470         Printf("NEWNEWS\r\n");
471     }
472 
473     if (PERMcanread) {
474         Printf("OVER\r\n");
475     }
476 
477     if (PERMcanpost) {
478         Printf("POST\r\n");
479     }
480 
481     Printf("READER\r\n");
482 
483 #if defined(HAVE_SASL)
484     /* Check whether at least one SASL mechanism is available.
485      * The SASL capability has to be advertised, even after authentication,
486      * so that the client can detect a possible active down-negotiation
487      * attack. */
488     if (mechlist != NULL && strlen(mechlist) > 2) {
489         Printf("SASL%s\r\n", mechlist);
490     }
491 #endif /* HAVE_SASL */
492 
493 #if defined(HAVE_OPENSSL)
494     /* A TLS layer is not active, a compression layer is not active,
495      * and the client is not already authenticated. */
496     if (!encryption_layer_on
497 #    if defined(HAVE_ZLIB)
498         && !compression_layer_on
499 #    endif /* HAVE_ZLIB */
500         && (!PERMauthorized || PERMneedauth || PERMcanauthenticate)) {
501         Printf("STARTTLS\r\n");
502     }
503 #endif /* HAVE_OPENSSL */
504 
505     if (PERMcanread) {
506         Printf("XPAT\r\n");
507     }
508 
509     Printf(".\r\n");
510 }
511 
512 
513 /*
514 **  Catch-all for unimplemented functions.
515 */
516 void
CMD_unimp(int ac UNUSED,char * av[])517 CMD_unimp(int ac UNUSED, char *av[])
518 {
519     Reply("%d \"%s\" not implemented; try \"HELP\"\r\n", NNTP_ERR_COMMAND,
520           av[0]);
521 }
522 
523 
524 /*
525 **  The QUIT command.
526 */
527 void
CMDquit(int ac UNUSED,char * av[]UNUSED)528 CMDquit(int ac UNUSED, char *av[] UNUSED)
529 {
530     Reply("%d Bye!\r\n", NNTP_OK_QUIT);
531     ExitWithStats(0, false);
532 }
533 
534 
535 /*
536 **  Convert an address to a hostname.  Don't trust the reverse lookup
537 **  since anyone can fake reverse DNS entries.
538 */
539 static bool
Address2Name(struct sockaddr * sa,socklen_t len,char * hostname,size_t size)540 Address2Name(struct sockaddr *sa, socklen_t len, char *hostname, size_t size)
541 {
542     static const char MISMATCH[] = "reverse lookup validation failed";
543     struct addrinfo hints, *ai, *host;
544     char *p;
545     int ret;
546     bool valid = false;
547 
548     /* Get the official hostname, store it away. */
549     ret = getnameinfo(sa, len, hostname, size, NULL, 0, NI_NAMEREQD);
550     if (ret != 0) {
551         HostErrorStr = gai_strerror(ret);
552         return false;
553     }
554 
555     /* Get addresses for this host. */
556     memset(&hints, 0, sizeof(hints));
557     hints.ai_family = AF_UNSPEC;
558     hints.ai_socktype = SOCK_STREAM;
559     ret = getaddrinfo(hostname, NULL, &hints, &ai);
560     if (ret != 0) {
561         HostErrorStr = gai_strerror(ret);
562         return false;
563     }
564 
565     /* Make sure one of those addresses is the address we got. */
566     for (host = ai; host != NULL; host = host->ai_next)
567         if (network_sockaddr_equal(sa, host->ai_addr)) {
568             valid = true;
569             break;
570         }
571     freeaddrinfo(ai);
572 
573     /* Lowercase the returned name since wildmats are case-sensitive. */
574     if (valid) {
575         for (p = hostname; *p != '\0'; p++)
576             if (isupper((unsigned char) *p))
577                 *p = tolower((unsigned char) *p);
578         return true;
579     } else {
580         HostErrorStr = MISMATCH;
581         return false;
582     }
583 }
584 
585 
586 /*
587 **  Determine hostname and IP of the client, amongst other information.
588 */
589 static void
GetClientInfo(unsigned short port)590 GetClientInfo(unsigned short port)
591 {
592     static const char *default_host_error = "unknown error";
593     struct sockaddr_storage ssc, sss;
594     struct sockaddr *sac = (struct sockaddr *) &ssc;
595     struct sockaddr *sas = (struct sockaddr *) &sss;
596     socklen_t length;
597     size_t size;
598 
599     memset(&Client, 0, sizeof(Client));
600     strlcpy(Client.host, "?", sizeof(Client.host));
601 
602     /* Get the peer's name. */
603     length = sizeof(ssc);
604     if (getpeername(STDIN_FILENO, sac, &length) < 0) {
605         if (!isatty(STDIN_FILENO)) {
606             sysnotice("? can't getpeername");
607             Reply("%d Can't get your name.  Goodbye!\r\n", NNTP_ERR_ACCESS);
608             ExitWithStats(1, true);
609         }
610         strlcpy(Client.host, "localhost", sizeof(Client.host));
611     } else {
612         /* Figure out client's IP address/hostname. */
613         HostErrorStr = default_host_error;
614         if (!network_sockaddr_sprint(Client.ip, sizeof(Client.ip), sac)) {
615             notice("? can't get client numeric address: %s", HostErrorStr);
616             Reply("%d Can't get your numeric address.  Goodbye!\r\n",
617                   NNTP_ERR_ACCESS);
618             ExitWithStats(1, true);
619         }
620         if (GetHostByAddr) {
621             HostErrorStr = default_host_error;
622             if (!Address2Name(sac, length, Client.host, sizeof(Client.host))) {
623                 notice("? reverse lookup for %s failed: %s -- using IP"
624                        " address for access",
625                        Client.ip, HostErrorStr);
626                 strlcpy(Client.host, Client.ip, sizeof(Client.host));
627             }
628         } else {
629             strlcpy(Client.host, Client.ip, sizeof(Client.host));
630         }
631 
632         /* Figure out server's IP address/hostname. */
633         length = sizeof(sss);
634         if (getsockname(STDIN_FILENO, sas, &length) < 0) {
635             sysnotice("%s can't getsockname", Client.host);
636             Reply("%d Can't figure out where you connected to.  Goodbye!\r\n",
637                   NNTP_ERR_ACCESS);
638             ExitWithStats(1, true);
639         }
640         HostErrorStr = default_host_error;
641         size = sizeof(Client.serverip);
642         if (!network_sockaddr_sprint(Client.serverip, size, sas)) {
643             notice("? can't get server numeric address: %s", HostErrorStr);
644             Reply("%d Can't get my own numeric address.  Goodbye!\r\n",
645                   NNTP_ERR_ACCESS);
646             ExitWithStats(1, true);
647         }
648         if (GetHostByAddr) {
649             HostErrorStr = default_host_error;
650             size = sizeof(Client.serverhost);
651             if (!Address2Name(sas, length, Client.serverhost, size)) {
652                 notice("? reverse lookup for %s failed: %s -- using IP"
653                        " address for access",
654                        Client.serverip, HostErrorStr);
655                 strlcpy(Client.serverhost, Client.serverip,
656                         sizeof(Client.serverhost));
657             }
658         } else {
659             strlcpy(Client.serverhost, Client.serverip,
660                     sizeof(Client.serverhost));
661         }
662 
663         /* Get port numbers. */
664         Client.port = network_sockaddr_port(sac);
665         Client.serverport = network_sockaddr_port(sas);
666     }
667 
668 #if defined(INN_BSDI_HOST)
669     /* Setting TCP_NODELAY to nnrpd fixes a problem of slow downloading
670      * of overviews and slow answers on some architectures (like BSD/OS
671      * where TCP delayed acknowledgements are enabled). */
672     int nodelay = 1;
673     setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &nodelay,
674                sizeof(nodelay));
675 #endif
676 
677     notice("%s (%s) connect - port %u", Client.host, Client.ip, port);
678 }
679 
680 
681 /*
682 **  Write a buffer, via compression layer and/or SASL security layer
683 **  and/or TLS if necessary.
684 */
685 void
write_buffer(const char * buff,ssize_t len)686 write_buffer(const char *buff, ssize_t len)
687 {
688     const char *p;
689     ssize_t n;
690 
691     TMRstart(TMR_NNTPWRITE);
692     p = buff;
693 
694 #if defined(HAVE_ZLIB)
695     if (compression_layer_on) {
696         int r;
697 
698         zstream_out->next_in = (unsigned char *) p;
699         zstream_out->avail_in = len;
700         zstream_out->next_out = zbuf_out;
701         zstream_out->avail_out = zbuf_out_size;
702 
703         do {
704             /* Grow the output buffer if needed. */
705             if (zstream_out->avail_out == 0) {
706                 size_t newsize = zbuf_out_size * 2;
707                 zbuf_out = xrealloc(zbuf_out, newsize);
708                 zstream_out->next_out = zbuf_out + zbuf_out_size;
709                 zstream_out->avail_out = zbuf_out_size;
710                 zbuf_out_size = newsize;
711             }
712 
713             r = deflate(zstream_out,
714                         zstream_flush_needed ? Z_PARTIAL_FLUSH : Z_NO_FLUSH);
715 
716             if (!(r == Z_OK || r == Z_BUF_ERROR || r == Z_STREAM_END)) {
717                 sysnotice("deflate() failed: %d; %s", r,
718                           zstream_out->msg != NULL ? zstream_out->msg
719                                                    : "no detail");
720                 TMRstop(TMR_NNTPWRITE);
721                 return;
722             }
723         } while (r == Z_OK && zstream_out->avail_out == 0);
724 
725         p = (char *) zbuf_out;
726         len = zbuf_out_size - zstream_out->avail_out;
727         zstream_flush_needed = false;
728     }
729 #endif /* HAVE_ZLIB */
730 
731     while (len > 0) {
732         const char *out;
733         unsigned outlen;
734 
735 #if defined(HAVE_SASL)
736         if (sasl_conn != NULL && sasl_ssf > 0) {
737             int r;
738 
739             /* Can only encode as much as the client can handle at one time. */
740             n = (len > sasl_maxout) ? sasl_maxout : len;
741             if ((r = sasl_encode(sasl_conn, p, n, &out, &outlen)) != SASL_OK) {
742                 const char *ed = sasl_errdetail(sasl_conn);
743 
744                 sysnotice("sasl_encode() failed: %s; %s",
745                           sasl_errstring(r, NULL, NULL),
746                           ed != NULL ? ed : "no detail");
747                 TMRstop(TMR_NNTPWRITE);
748                 return;
749             }
750         } else
751 #endif /* HAVE_SASL */
752         {
753             /* Output the entire unencoded string. */
754             n = len;
755             out = p;
756             outlen = len;
757         }
758 
759         len -= n;
760         p += n;
761 
762 #ifdef HAVE_OPENSSL
763         if (tls_conn) {
764             int r;
765 
766         Again:
767             r = SSL_write(tls_conn, out, outlen);
768             switch (SSL_get_error(tls_conn, r)) {
769             case SSL_ERROR_NONE:
770                 break;
771             case SSL_ERROR_WANT_WRITE:
772                 goto Again;
773                 /* NOTREACHED */
774             case SSL_ERROR_ZERO_RETURN:
775                 SSL_shutdown(tls_conn);
776                 goto fallthrough;
777             case SSL_ERROR_SSL:
778             case SSL_ERROR_SYSCALL:
779             fallthrough:
780                 /* SSL_shutdown() must not be called. */
781                 tls_conn = NULL;
782                 errno = ECONNRESET;
783                 break;
784             }
785         } else
786 #endif /* HAVE_OPENSSL */
787             do {
788                 n = write(STDIN_FILENO, out, outlen);
789             } while (n == -1 && errno == EINTR);
790     }
791 
792     TMRstop(TMR_NNTPWRITE);
793 }
794 
795 
796 /*
797 **  Send formatted output, possibly with debugging output.
798 */
799 static void __attribute__((__format__(printf, 1, 0)))
VPrintf(const char * fmt,va_list args,int dotrace)800 VPrintf(const char *fmt, va_list args, int dotrace)
801 {
802     char buff[2048], *p;
803     ssize_t len;
804 
805     len = vsnprintf(buff, sizeof(buff), fmt, args);
806     if (len < 0)
807         len = 0;
808     else if ((size_t) len >= sizeof(buff))
809         len = sizeof(buff) - 1;
810     write_buffer(buff, len);
811 
812     if (dotrace && Tracing) {
813         int oerrno = errno;
814 
815         /* Copy output, but strip trailing CR-LF.  Note we're assuming here
816          * that no output line can ever be longer than 2045 characters. */
817         p = buff + strlen(buff) - 1;
818         while (p >= buff && (*p == '\n' || *p == '\r'))
819             *p-- = '\0';
820         syslog(L_TRACE, "%s > %s", Client.host, buff);
821 
822         errno = oerrno;
823     }
824 }
825 
826 
827 /*
828 **  Send a reply, possibly with debugging output.
829 **  Reply() is used for answers which can possibly be traced (response codes).
830 **  Printf() is used in other cases.
831 */
832 void
Reply(const char * fmt,...)833 Reply(const char *fmt, ...)
834 {
835     va_list args;
836 
837 #if defined(HAVE_ZLIB)
838     /* For single-line responses, immediately flush the output stream. */
839     zstream_flush_needed = true;
840 #endif /* HAVE_ZLIB */
841 
842     va_start(args, fmt);
843     VPrintf(fmt, args, 1);
844     va_end(args);
845 }
846 
847 void
Printf(const char * fmt,...)848 Printf(const char *fmt, ...)
849 {
850     va_list args;
851 
852 #if defined(HAVE_ZLIB)
853     /* Last line of a multi-line data block response.
854      * Time to flush the compressed output stream.
855      * Check that only when the compression layer is active. */
856     if (compression_layer_on && strlen(fmt) == 3
857         && strcasecmp(fmt, ".\r\n") == 0) {
858         zstream_flush_needed = true;
859     }
860 #endif /* HAVE_ZLIB */
861 
862     va_start(args, fmt);
863     VPrintf(fmt, args, 0);
864     va_end(args);
865 }
866 
867 
868 #ifdef HAVE_SIGACTION
869 #    define NO_SIGACTION_UNUSED UNUSED
870 #else
871 #    define NO_SIGACTION_UNUSED
872 #endif
873 
874 
875 /*
876 **  Got a signal; toggle tracing.
877 */
878 static void
ToggleTrace(int s NO_SIGACTION_UNUSED)879 ToggleTrace(int s NO_SIGACTION_UNUSED)
880 {
881     ChangeTrace = true;
882 #ifndef HAVE_SIGACTION
883     xsignal(s, ToggleTrace);
884 #endif
885 }
886 
887 /*
888 **  Got a SIGPIPE; exit cleanly.
889 */
890 __attribute__((__noreturn__)) static void
CatchPipe(int s UNUSED)891 CatchPipe(int s UNUSED)
892 {
893     ExitWithStats(0, false);
894 }
895 
896 
897 /*
898 **  Got a signal; wait for children.
899 */
900 static void
WaitChild(int s NO_SIGACTION_UNUSED)901 WaitChild(int s NO_SIGACTION_UNUSED)
902 {
903     int pid;
904 
905     for (;;) {
906         pid = waitpid(-1, NULL, WNOHANG);
907         if (pid <= 0)
908             break;
909     }
910 #ifndef HAVE_SIGACTION
911     xsignal(s, WaitChild);
912 #endif
913 }
914 
915 
916 static void
SetupDaemon(void)917 SetupDaemon(void)
918 {
919     bool val;
920 
921     val = true;
922     if (SMsetup(SM_PREOPEN, (void *) &val) && !SMinit()) {
923         syslog(L_NOTICE, "can't initialize storage method, %s", SMerrorstr);
924         Reply("%d NNTP server unavailable.  Try later!\r\n",
925               NNTP_FAIL_TERMINATING);
926         ExitWithStats(1, true);
927     }
928     OVextra = overview_extra_fields(false);
929     if (OVextra == NULL) {
930         /* overview_extra_fields() should already have logged something
931          * useful. */
932         Reply("%d NNTP server unavailable.  Try later!\r\n",
933               NNTP_FAIL_TERMINATING);
934         ExitWithStats(1, true);
935     }
936     overhdr_xref = overview_index("Xref", OVextra);
937     if (!OVopen(OV_READ)) {
938         /* This shouldn't really happen. */
939         syslog(L_NOTICE, "can't open overview %m");
940         Reply("%d NNTP server unavailable.  Try later!\r\n",
941               NNTP_FAIL_TERMINATING);
942         ExitWithStats(1, true);
943     }
944     if (!OVctl(OVCACHEKEEP, &val)) {
945         syslog(L_NOTICE, "can't enable overview cache %m");
946         Reply("%d NNTP server unavailable.  Try later!\r\n",
947               NNTP_FAIL_TERMINATING);
948         ExitWithStats(1, true);
949     }
950 }
951 
952 
953 /*
954 **  Print a usage message and exit.
955 */
956 static void
Usage(void)957 Usage(void)
958 {
959     fprintf(stderr, "Usage error.\n");
960     exit(1);
961 }
962 
963 
964 int
main(int argc,char * argv[])965 main(int argc, char *argv[])
966 {
967     const char *name;
968     CMDENT *cp;
969     char buff[NNTP_MAXLEN_COMMAND];
970     char **av;
971     int ac;
972     READTYPE r;
973     int i;
974     unsigned int j;
975     char **v;
976     char *Reject;
977     int timeout;
978     unsigned int vid = 0;
979     int count = 123456789;
980     struct timeval tv;
981     unsigned short ListenPort = NNTP_PORT;
982     char *ListenAddr = NULL;
983     char *ListenAddr6 = NULL;
984     int *lfds;
985     fd_set lfdset, lfdsetread;
986     unsigned int lfdcount;
987     int lfdreadcount;
988     bool lfdokay;
989     int lfdmax = 0;
990     int fd = -1;
991     pid_t pid = -1;
992     FILE *pidfile;
993     int clienttimeout;
994     char *ConfFile = NULL;
995     char *path;
996     bool validcommandtoolong;
997 
998     int respawn = 0;
999 
1000     setproctitle_init(argc, argv);
1001 
1002     /* Parse arguments.  Must xstrdup() optarg if used because setproctitle
1003      * may clobber it! */
1004     Reject = NULL;
1005     LLOGenable = false;
1006     GRPcur = NULL;
1007     MaxBytesPerSecond = 0;
1008     strlcpy(Username, "unknown", sizeof(Username));
1009 
1010     /* Set up the pathname, first thing, and teach our error handlers about
1011      * the name of the program. */
1012     name = argv[0];
1013     if (name == NULL || *name == '\0')
1014         name = "nnrpd";
1015     else {
1016         const char *p;
1017 
1018         /* We take the last "/" in the path. */
1019         p = strrchr(name, '/');
1020         if (p != NULL)
1021             name = p + 1;
1022     }
1023     message_program_name = xstrdup(name);
1024     openlog(message_program_name, L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
1025     message_handlers_die(1, message_log_syslog_crit);
1026     message_handlers_warn(1, message_log_syslog_warning);
1027     message_handlers_notice(1, message_log_syslog_notice);
1028 
1029     /* Initialize the character class tables for message-IDs. */
1030     InitializeMessageIDcclass();
1031 
1032     if (!innconf_read(NULL))
1033         exit(1);
1034 
1035 #ifdef HAVE_SASL
1036     if (sasl_server_init(sasl_callbacks, "INN") != SASL_OK) {
1037         syslog(L_FATAL, "sasl_server_init() failed");
1038         exit(1);
1039     }
1040 #endif /* HAVE_SASL */
1041 
1042 #ifdef HAVE_OPENSSL
1043     while ((i = getopt(argc, argv, "4:6:b:c:Dfi:I:nop:P:r:s:St")) != EOF)
1044 #else
1045     while ((i = getopt(argc, argv, "4:6:b:c:Dfi:I:nop:P:r:s:t")) != EOF)
1046 #endif /* HAVE_OPENSSL */
1047         switch (i) {
1048         default:
1049             Usage();
1050             /* NOTREACHED */
1051         case '4': /* Bind to a certain IPv4 address. */
1052         case 'b':
1053             if (ListenAddr != NULL)
1054                 die("-b and -4 are the same and both may not be given");
1055             ListenAddr = xstrdup(optarg);
1056             break;
1057         case '6': /* Bind to a certain IPv6 address. */
1058             ListenAddr6 = xstrdup(optarg);
1059             break;
1060         case 'c': /* Use alternate readers.conf. */
1061             ConfFile = concatpath(innconf->pathetc, optarg);
1062             break;
1063         case 'D': /* Standalone daemon mode. */
1064             DaemonMode = true;
1065             break;
1066         case 'P': /* Prespawn count in daemon mode. */
1067             respawn = atoi(optarg);
1068             break;
1069         case 'f': /* Don't fork on daemon mode. */
1070             ForeGroundMode = true;
1071             break;
1072         case 'i': /* Initial command. */
1073             PushedBack = xstrdup(optarg);
1074             break;
1075         case 'I': /* Instance. */
1076             NNRPinstance = xstrdup(optarg);
1077             break;
1078         case 'n': /* No DNS lookups. */
1079             GetHostByAddr = false;
1080             break;
1081         case 'o': /* Offline posting only. */
1082             Offlinepost = true;
1083             break;
1084         case 'p': /* TCP port for daemon mode. */
1085             ListenPort = atoi(optarg);
1086             break;
1087         case 'r': /* Reject connection message. */
1088             Reject = xstrdup(optarg);
1089             break;
1090         case 's': /* Unused title string. */
1091             break;
1092         case 't': /* Tracing. */
1093             Tracing = true;
1094             break;
1095 #ifdef HAVE_OPENSSL
1096         case 'S': /* Force the negotiation of an encryption layer. */
1097             initialSSL = true;
1098             break;
1099 #endif /* HAVE_OPENSSL */
1100         }
1101     argc -= optind;
1102     if (argc)
1103         Usage();
1104 
1105     /* Make other processes happier if someone is reading.  This allows other
1106      * processes like overchan to keep up when there are lots of readers.
1107      * Note that this is cumulative with nicekids. */
1108     if (innconf->nicennrpd != 0) {
1109         errno = 0;
1110         if (nice(innconf->nicennrpd) < 0 && errno != 0)
1111             syswarn("could not nice to %ld", innconf->nicennrpd);
1112     }
1113 
1114     HISTORY = concatpath(innconf->pathdb, INN_PATH_HISTORY);
1115     ACTIVE = concatpath(innconf->pathdb, INN_PATH_ACTIVE);
1116     ACTIVETIMES = concatpath(innconf->pathdb, INN_PATH_ACTIVETIMES);
1117     NEWSGROUPS = concatpath(innconf->pathdb, INN_PATH_NEWSGROUPS);
1118     if (ConfFile)
1119         NNRPACCESS = ConfFile;
1120     else
1121         NNRPACCESS = concatpath(innconf->pathetc, INN_PATH_NNRPACCESS);
1122 
1123     /* Initialize the checks to perform or not on article syntax. */
1124     if ((innconf->syntaxchecks != NULL)
1125         && (innconf->syntaxchecks->count > 0)) {
1126         for (j = 0; j < innconf->syntaxchecks->count; j++) {
1127             if (innconf->syntaxchecks->strings[j] != NULL) {
1128                 if (strcmp(innconf->syntaxchecks->strings[j], "laxmid") == 0) {
1129                     laxmid = true;
1130                 } else if (strcmp(innconf->syntaxchecks->strings[j],
1131                                   "no-laxmid")
1132                            == 0) {
1133                     laxmid = false;
1134                 } else {
1135                     syslog(L_NOTICE,
1136                            "Unknown \"%s\" value in syntaxchecks "
1137                            "parameter in inn.conf",
1138                            innconf->syntaxchecks->strings[j]);
1139                 }
1140             }
1141         }
1142     }
1143 
1144     if (DaemonMode) {
1145         /* Allocate an lfds array to hold the file descriptors
1146          * for IPv4 and/or IPv6 connections. */
1147         if (ListenAddr == NULL && ListenAddr6 == NULL) {
1148             network_innbind_all(SOCK_STREAM, ListenPort, &lfds, &lfdcount);
1149         } else {
1150             if (ListenAddr != NULL && ListenAddr6 != NULL)
1151                 lfdcount = 2;
1152             else
1153                 lfdcount = 1;
1154             lfds = xmalloc(lfdcount * sizeof(int));
1155             i = 0;
1156             if (ListenAddr6 != NULL) {
1157                 lfds[i++] =
1158                     network_innbind_ipv6(SOCK_STREAM, ListenAddr6, ListenPort);
1159             }
1160             if (ListenAddr != NULL) {
1161                 lfds[i] =
1162                     network_innbind_ipv4(SOCK_STREAM, ListenAddr, ListenPort);
1163             }
1164         }
1165 
1166         /* Bail if we couldn't listen on any sockets. */
1167         lfdokay = false;
1168         for (j = 0; j < lfdcount; j++) {
1169             if (lfds[j] < 0)
1170                 continue;
1171             lfdokay = true;
1172         }
1173         if (!lfdokay)
1174             die("can't bind to any addresses");
1175 
1176         /* If started as root, switch to news uid.  Unlike other parts of INN,
1177          * we don't die if we can't become the news user.  As long as we're not
1178          * running as root, everything's fine; it's okay to write the things we
1179          * write as a member of the news group. */
1180         if (getuid() == 0) {
1181             ensure_news_user_grp(true, true);
1182         }
1183 
1184         /* Detach. */
1185         if (!ForeGroundMode) {
1186             daemonize("/");
1187         }
1188 
1189         if (ListenPort == NNTP_PORT)
1190             strlcpy(buff, "nnrpd.pid", sizeof(buff));
1191         else
1192             snprintf(buff, sizeof(buff), "nnrpd-%d.pid", ListenPort);
1193         path = concatpath(innconf->pathrun, buff);
1194         pidfile = fopen(path, "w");
1195         free(path);
1196         if (pidfile == NULL) {
1197             syslog(L_ERROR, "cannot write %s %m", buff);
1198             exit(1);
1199         }
1200         fprintf(pidfile, "%lu\n", (unsigned long) getpid());
1201         fclose(pidfile);
1202 
1203         /* Set signal handle to care for dead children. */
1204         if (!respawn)
1205             xsignal(SIGCHLD, WaitChild);
1206 
1207         /* Arrange to toggle tracing. */
1208         xsignal(SIGHUP, ToggleTrace);
1209 
1210         setproctitle("accepting connections");
1211 
1212         /* Initialize the listener file descriptors set. */
1213         FD_ZERO(&lfdset);
1214 
1215         for (j = 0; j < lfdcount; j++) {
1216             if (listen(lfds[j], innconf->maxlisten) < 0) {
1217                 if (j != 0 && errno == EADDRINUSE)
1218                     continue;
1219                 syslog(L_ERROR, "can't listen to socket");
1220             } else {
1221                 /* Remember the largest descriptor number
1222                  * that is to be tested by select(). */
1223                 FD_SET(lfds[j], &lfdset);
1224                 if (lfdmax < lfds[j])
1225                     lfdmax = lfds[j];
1226             }
1227         }
1228 
1229         if (respawn) {
1230             /* Pre-forked mode. */
1231             for (;;) {
1232                 if (respawn > 0) {
1233                     --respawn;
1234                     pid = fork();
1235                     if (pid == 0) {
1236                         do {
1237                             fd = -1;
1238 
1239                             /* Copy the master set because lfdsetread
1240                              * will be modified. */
1241                             lfdsetread = lfdset;
1242                             lfdreadcount = select(lfdmax + 1, &lfdsetread,
1243                                                   NULL, NULL, NULL);
1244 
1245                             if (lfdreadcount > 0) {
1246                                 for (j = 0; j < lfdcount; j++) {
1247                                     if (FD_ISSET(lfds[j], &lfdsetread)) {
1248                                         fd = accept(lfds[j], NULL, NULL);
1249                                         /* Only handle the first match.  Future
1250                                          * calls to select() will handle
1251                                          * possible other matches. */
1252                                         if (fd >= 0)
1253                                             break;
1254                                     }
1255                                 }
1256                             }
1257                         } while (fd < 0);
1258                         break;
1259                     }
1260                 }
1261                 for (;;) {
1262                     if (respawn == 0)
1263                         pid = wait(NULL);
1264                     else
1265                         pid = waitpid(-1, NULL, WNOHANG);
1266                     if (pid <= 0)
1267                         break;
1268                     ++respawn;
1269                 }
1270             }
1271         } else {
1272             /* Fork on demand. */
1273             do {
1274                 fd = -1;
1275 
1276                 /* Copy the master set because lfdsetread will be modified. */
1277                 lfdsetread = lfdset;
1278                 lfdreadcount =
1279                     select(lfdmax + 1, &lfdsetread, NULL, NULL, NULL);
1280 
1281                 if (lfdreadcount > 0) {
1282                     for (j = 0; j < lfdcount; j++) {
1283                         if (FD_ISSET(lfds[j], &lfdsetread)) {
1284                             fd = accept(lfds[j], NULL, NULL);
1285                             /* Only handle the first match.  Future calls
1286                              * to select() will handle possible other matches.
1287                              */
1288                             if (fd >= 0)
1289                                 break;
1290                         }
1291                     }
1292                 }
1293                 if (fd < 0)
1294                     continue;
1295 
1296                 for (j = 0; j <= innconf->maxforks && (pid = fork()) < 0;
1297                      j++) {
1298                     if (j == innconf->maxforks) {
1299                         syslog(L_FATAL,
1300                                "can't fork (dropping connection): %m");
1301                         continue;
1302                     }
1303                     syslog(L_NOTICE, "can't fork (waiting): %m");
1304                     sleep(1);
1305                 }
1306                 if (ChangeTrace) {
1307                     Tracing = Tracing ? false : true;
1308                     syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
1309                     ChangeTrace = false;
1310                 }
1311                 if (pid != 0)
1312                     close(fd);
1313             } while (pid != 0);
1314         }
1315 
1316         /* Child process starts here. */
1317         setproctitle("connected");
1318         for (j = 0; j < lfdcount; j++) {
1319             close(lfds[j]);
1320         }
1321         dup2(fd, 0);
1322         close(fd);
1323         dup2(0, 1);
1324         dup2(0, 2);
1325         if (innconf->timer != 0)
1326             TMRinit(TMR_MAX);
1327         STATstart = TMRnow_double();
1328         SetupDaemon();
1329 
1330         /* If we are a daemon, innd didn't make us nice, so be nice kids. */
1331         if (innconf->nicekids) {
1332             if (nice(innconf->nicekids) < 0)
1333                 syslog(L_ERROR, "Could not nice child to %ld: %m",
1334                        innconf->nicekids);
1335         }
1336 
1337         /* Only automatically reap children in the listening process. */
1338         xsignal(SIGCHLD, SIG_DFL);
1339 
1340     } else {
1341         if (innconf->timer != 0)
1342             TMRinit(TMR_MAX);
1343         STATstart = TMRnow_double();
1344         SetupDaemon();
1345         /* Arrange to toggle tracing. */
1346         xsignal(SIGHUP, ToggleTrace);
1347     } /* DaemonMode */
1348 
1349 #ifdef HAVE_OPENSSL
1350     if (initialSSL) {
1351         tls_init();
1352         if (tls_start_servertls(0, 1) == -1) {
1353             GetClientInfo(ListenPort);
1354             notice("%s failure to negotiate TLS session", Client.host);
1355             Reply("%d Encrypted TLS connection failed\r\n",
1356                   NNTP_FAIL_TERMINATING);
1357             ExitWithStats(1, false);
1358         }
1359         encryption_layer_on = true;
1360 
1361 #    if defined(HAVE_ZLIB) && OPENSSL_VERSION_NUMBER >= 0x00090800fL
1362         /* Check whether a compression layer has just been added.
1363          * SSL_get_current_compression() is defined in OpenSSL versions >=
1364          * 0.9.8 final release, as well as LibreSSL. */
1365         tls_compression_on = (SSL_get_current_compression(tls_conn) != NULL);
1366         compression_layer_on = tls_compression_on;
1367 #    endif /* HAVE_ZLIB && OPENSSL >= v0.9.8 */
1368     }
1369 #endif /* HAVE_OPENSSL */
1370 
1371     /* If requested, check the load average. */
1372     if (innconf->nnrpdloadlimit != 0) {
1373         double load[1];
1374 
1375         if (getloadavg(load, 1) < 0)
1376             warn("cannot obtain system load");
1377         else {
1378             if ((unsigned long) (load[0] + 0.5) > innconf->nnrpdloadlimit) {
1379                 GetClientInfo(ListenPort);
1380                 notice("%s load %.2f > %lu", Client.host, load[0],
1381                        innconf->nnrpdloadlimit);
1382                 Reply("%d load at %.2f, try later\r\n", NNTP_FAIL_TERMINATING,
1383                       load[0]);
1384                 ExitWithStats(1, true);
1385             }
1386         }
1387     }
1388 
1389     /* Catch SIGPIPE so that we can exit out of long write loops. */
1390     xsignal(SIGPIPE, CatchPipe);
1391 
1392     /* Get permissions and see if we can talk to this client. */
1393     GetClientInfo(ListenPort);
1394     PERMgetinitialaccess(NNRPACCESS);
1395     PERMgetaccess(true);
1396     PERMgetpermissions();
1397 
1398     if (!PERMcanread && !PERMcanpost && !PERMneedauth) {
1399         syslog(L_NOTICE, "%s no_permission", Client.host);
1400         Reply("%d You have no permission to talk.  Goodbye!\r\n",
1401               NNTP_ERR_ACCESS);
1402         ExitWithStats(1, false);
1403     }
1404 
1405     /* Proceed with initialization. */
1406     setproctitle("%s connect", Client.host);
1407 
1408     /* Were we told to reject connections? */
1409     if (Reject) {
1410         syslog(L_NOTICE, "%s rejected %s", Client.host, Reject);
1411         Reply("%d %s\r\n", NNTP_FAIL_TERMINATING,
1412               is_valid_utf8(Reject) ? Reject : "Connection rejected");
1413         ExitWithStats(0, false);
1414     }
1415 
1416     if (PERMaccessconf) {
1417         if (PERMaccessconf->readertrack)
1418             PERMaccessconf->readertrack =
1419                 TrackClient(Client.host, Username, sizeof(Username));
1420     } else {
1421         if (innconf->readertrack)
1422             innconf->readertrack =
1423                 TrackClient(Client.host, Username, sizeof(Username));
1424     }
1425 
1426     if ((PERMaccessconf && PERMaccessconf->readertrack)
1427         || (!PERMaccessconf && innconf->readertrack)) {
1428         int len;
1429         syslog(L_NOTICE, "%s Tracking Enabled (%s)", Client.host, Username);
1430         pid = getpid();
1431         gettimeofday(&tv, NULL);
1432         count += pid;
1433         vid = tv.tv_sec ^ tv.tv_usec ^ pid ^ count;
1434         len = strlen("innconf->pathlog") + strlen("/tracklogs/log-") + BUFSIZ;
1435         LocalLogFileName = xmalloc(len);
1436         sprintf(LocalLogFileName, "%s/tracklogs/log-%u", innconf->pathlog,
1437                 vid);
1438         if ((locallog = fopen(LocalLogFileName, "w")) == NULL) {
1439             LocalLogDirName = concatpath(innconf->pathlog, "tracklogs");
1440             MakeDirectory(LocalLogDirName, false);
1441             free(LocalLogDirName);
1442         }
1443         if (locallog == NULL
1444             && (locallog = fopen(LocalLogFileName, "w")) == NULL) {
1445             syslog(L_ERROR, "%s Local Logging failed (%s) %s: %m", Client.host,
1446                    Username, LocalLogFileName);
1447         } else {
1448             syslog(L_NOTICE, "%s Local Logging begins (%s) %s", Client.host,
1449                    Username, LocalLogFileName);
1450             fprintf(locallog, "%s Tracking Enabled (%s)\n", Client.host,
1451                     Username);
1452             fflush(locallog);
1453             LLOGenable = true;
1454         }
1455     }
1456 
1457 #ifdef HAVE_SASL
1458     SASLnewserver();
1459 #endif /* HAVE_SASL */
1460 
1461     if (PERMaccessconf) {
1462         Reply("%d %s InterNetNews NNRP server %s ready (%s)\r\n",
1463               (PERMcanpost || (PERMcanauthenticate && PERMcanpostgreeting))
1464                   ? NNTP_OK_BANNER_POST
1465                   : NNTP_OK_BANNER_NOPOST,
1466               PERMaccessconf->pathhost, INN_VERSION_STRING,
1467               (!PERMneedauth && PERMcanpost) ? "posting ok" : "no posting");
1468         clienttimeout = PERMaccessconf->clienttimeout;
1469     } else {
1470         Reply("%d %s InterNetNews NNRP server %s ready (%s)\r\n",
1471               (PERMcanpost || (PERMcanauthenticate && PERMcanpostgreeting))
1472                   ? NNTP_OK_BANNER_POST
1473                   : NNTP_OK_BANNER_NOPOST,
1474               innconf->pathhost, INN_VERSION_STRING,
1475               (!PERMneedauth && PERMcanpost) ? "posting ok" : "no posting");
1476         clienttimeout = innconf->clienttimeout;
1477     }
1478 
1479     line_init(&NNTPline);
1480 
1481     /* Main dispatch loop. */
1482     for (timeout = innconf->initialtimeout, av = NULL, ac = 0;;
1483          timeout = clienttimeout) {
1484         TMRstart(TMR_NNTPWRITE);
1485         fflush(stdout);
1486         TMRstop(TMR_NNTPWRITE);
1487         if (ChangeTrace) {
1488             Tracing = Tracing ? false : true;
1489             syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
1490             ChangeTrace = false;
1491         }
1492         if (PushedBack) {
1493             if (PushedBack[0] == '\0')
1494                 continue;
1495             if (Tracing)
1496                 syslog(L_TRACE, "%s < %s", Client.host, PushedBack);
1497             ac = nArgify(PushedBack, &av, 1);
1498             r = RTok;
1499         } else {
1500             size_t len;
1501             size_t lenstripped = 0;
1502             const char *p;
1503             char *q;
1504 
1505             r = line_read(&NNTPline, timeout, &p, &len, &lenstripped);
1506             switch (r) {
1507             default:
1508                 syslog(L_ERROR, "%s internal %d in main", Client.host, r);
1509                 goto fallthroughRTtimeout;
1510             case RTtimeout:
1511             fallthroughRTtimeout:
1512                 if (timeout < clienttimeout)
1513                     syslog(L_NOTICE, "%s timeout short", Client.host);
1514                 else
1515                     syslog(L_NOTICE, "%s timeout", Client.host);
1516                 ExitWithStats(1, false);
1517                 /* NOTREACHED */
1518             case RTok:
1519                 /* len does not count CRLF. */
1520                 if (len + lenstripped <= sizeof(buff)) {
1521                     /* line_read guarantees null termination. */
1522                     memcpy(buff, p, len + 1);
1523                     /* Do some input processing, check for blank line. */
1524                     if (buff[0] != '\0')
1525                         ac = nArgify(buff, &av, 1);
1526                     if (Tracing) {
1527                         /* Do not log passwords if AUTHINFO PASS,
1528                          * AUTHINFO SASL PLAIN or AUTHINFO SASL EXTERNAL
1529                          * are used.  (Only one space between SASL and
1530                          * PLAIN/EXTERNAL should be put; otherwise, the
1531                          * whole command will be logged).
1532                          * AUTHINFO SASL LOGIN does not use an initial
1533                          * response;
1534                          * therefore, there is nothing to hide here. */
1535                         if (ac > 1 && strcasecmp(av[0], "AUTHINFO") == 0) {
1536                             if (strncasecmp(av[1], "PASS", 4) == 0)
1537                                 syslog(L_TRACE, "%s < AUTHINFO PASS ********",
1538                                        Client.host);
1539                             else if (strncasecmp(av[1], "SASL PLAIN", 10) == 0)
1540                                 syslog(L_TRACE,
1541                                        "%s < AUTHINFO SASL PLAIN ********",
1542                                        Client.host);
1543                             else if (strncasecmp(av[1], "SASL EXTERNAL", 13)
1544                                      == 0)
1545                                 syslog(L_TRACE,
1546                                        "%s < AUTHINFO SASL EXTERNAL ********",
1547                                        Client.host);
1548                             else
1549                                 syslog(L_TRACE, "%s < %s", Client.host, buff);
1550                         } else {
1551                             syslog(L_TRACE, "%s < %s", Client.host, buff);
1552                         }
1553                     }
1554                     if (buff[0] == '\0')
1555                         continue;
1556                     break;
1557                 }
1558                 goto fallthroughRTlong;
1559             case RTlong:
1560             fallthroughRTlong:
1561                 /* The line is too long but we have to make sure that
1562                  * no recognized command has been sent. */
1563                 q = (char *) p;
1564                 ac = nArgify(q, &av, 1);
1565                 validcommandtoolong = false;
1566                 for (cp = CMDtable; cp->Name; cp++) {
1567                     if ((cp->Function != CMD_unimp)
1568                         && (ac > 0 && strcasecmp(cp->Name, av[0]) == 0)) {
1569                         validcommandtoolong = true;
1570                         break;
1571                     }
1572                 }
1573                 Reply("%d Line too long\r\n", validcommandtoolong
1574                                                   ? NNTP_ERR_SYNTAX
1575                                                   : NNTP_ERR_COMMAND);
1576                 continue;
1577             case RTeof:
1578                 /* Handled below. */
1579                 break;
1580             }
1581         }
1582         /* Client gone? */
1583         if (r == RTeof)
1584             break;
1585         if (ac == 0)
1586             continue;
1587 
1588         /* Find command. */
1589         for (cp = CMDtable; cp->Name; cp++)
1590             if (strcasecmp(cp->Name, av[0]) == 0)
1591                 break;
1592 
1593         /* If no command has been recognized. */
1594         if (cp->Name == NULL) {
1595             if (strcasecmp(av[0], "XYZZY") == 0) {
1596                 /* Acknowledge the magic word from the Colossal Cave Adventure
1597                  * computer game. */
1598                 Reply("%d Nothing happens\r\n", NNTP_ERR_COMMAND);
1599             } else {
1600                 Reply("%d What?\r\n", NNTP_ERR_COMMAND);
1601                 if ((int) strlen(buff) > 40)
1602                     syslog(L_NOTICE, "%s unrecognized %.40s...", Client.host,
1603                            buff);
1604                 else
1605                     syslog(L_NOTICE, "%s unrecognized %s", Client.host, buff);
1606             }
1607             continue;
1608         }
1609 
1610         /* Go on parsing the command. */
1611         ac--;
1612         ac += reArgify(av[ac], &av[ac],
1613                        cp->Stripspaces ? -1 : cp->Minac - ac - 1,
1614                        cp->Stripspaces);
1615 
1616         /* Check whether all arguments do not exceed their allowed size. */
1617         if (ac > 1) {
1618             validcommandtoolong = false;
1619             for (v = av; *v; v++)
1620                 if (strlen(*v) > NNTP_MAXLEN_ARG) {
1621                     validcommandtoolong = true;
1622                     Reply("%d Argument too long\r\n", NNTP_ERR_SYNTAX);
1623                     break;
1624                 }
1625             if (validcommandtoolong)
1626                 continue;
1627         }
1628 
1629         /* Check usage. */
1630         if ((cp->Minac != CMDany && ac < cp->Minac)
1631             || (cp->Maxac != CMDany && ac > cp->Maxac)) {
1632             Reply("%d Syntax is: %s %s\r\n", NNTP_ERR_SYNTAX, cp->Name,
1633                   cp->Help ? cp->Help : "(no argument allowed)");
1634             continue;
1635         }
1636 
1637         /* Check permissions and dispatch. */
1638         if (cp->Needauth && PERMneedauth) {
1639             Reply("%d Authentication required for command\r\n",
1640                   NNTP_FAIL_AUTH_NEEDED);
1641             continue;
1642         }
1643         setproctitle("%s %s", Client.host, av[0]);
1644 
1645         (*cp->Function)(ac, av);
1646 
1647         if (PushedBack)
1648             break;
1649         if (PERMaccessconf)
1650             clienttimeout = PERMaccessconf->clienttimeout;
1651         else
1652             clienttimeout = innconf->clienttimeout;
1653     }
1654 
1655     ExitWithStats(0, false);
1656 
1657     /* NOTREACHED */
1658     return 1;
1659 }
1660