chiark / gitweb /
more manpage, remove -h option
[innduct.git] / nnrpd / nnrpd.c
1 /*  $Id: nnrpd.c 7731 2008-04-06 08:40:29Z iulius $
2 **
3 **  NNTP server for readers (NNRP) for InterNetNews.
4 **
5 **  This server doesn't do any real load-limiting, except for what has
6 **  proven empirically necesary (i.e., look at GRPscandir).
7 */
8
9 #include "config.h"
10 #include "clibrary.h"
11 #include "portable/setproctitle.h"
12 #include "portable/wait.h"
13 #include <grp.h>
14 #include <netdb.h>
15 #include <pwd.h>
16 #include <signal.h>
17
18 #if HAVE_GETSPNAM
19 # include <shadow.h>
20 #endif
21
22 #include "inn/innconf.h"
23 #include "inn/messages.h"
24 #include "libinn.h"
25 #include "ov.h"
26 #define MAINLINE
27 #include "nnrpd.h"
28
29 #include "tls.h"
30 #include "sasl_config.h"
31
32 #ifdef HAVE_SSL
33 extern SSL *tls_conn;
34 int nnrpd_starttls_done = 0;
35 #endif 
36
37 #if NEED_HERRNO_DECLARATION
38 extern int h_errno;
39 #endif
40
41 /* If we have getloadavg, include the appropriate header file.  Otherwise,
42    just assume that we always have a load of 0. */
43 #if HAVE_GETLOADAVG
44 # if HAVE_SYS_LOADAVG_H
45 #  include <sys/loadavg.h>
46 # endif
47 #else
48 static int
49 getloadavg(double loadavg[], int nelem)
50 {
51     int i;
52
53     for (i = 0; i < nelem && i < 3; i++)
54         loadavg[i] = 0;
55     return i;
56 }
57 #endif
58
59
60 #define MAXPATTERNDEFINE        10
61
62 #define CMDany          -1
63
64
65 typedef struct _CMDENT {
66     const char *        Name;
67     void                (*Function)(int, char **);
68     bool                Needauth;
69     int                 Minac;
70     int                 Maxac;
71     const char *        Help;
72 } CMDENT;
73
74
75 char    NOACCESS[] = NNTP_ACCESS;
76 char    *ACTIVE = NULL;
77 char    *ACTIVETIMES = NULL;
78 char    *HISTORY = NULL;
79 char    *NEWSGROUPS = NULL;
80 char    *NNRPACCESS = NULL;
81
82 static char     *LocalLogFileName = NULL;
83 static char     *LocalLogDirName;
84
85 struct history *History;
86 static double   STATstart;
87 static double   STATfinish;
88 static char     *PushedBack;
89 static sig_atomic_t     ChangeTrace;
90 bool    DaemonMode = false;
91 bool    ForeGroundMode = false;
92 #if HAVE_GETSPNAM
93 static const char       *ShadowGroup;
94 #endif
95 static const char       *HostErrorStr;
96 bool GetHostByAddr = true;      /* formerly DO_NNRP_GETHOSTBYADDR */
97 const char *NNRPinstance = "";
98
99 #ifdef DO_PERL
100 bool   PerlLoaded = false;
101 #endif /* DO_PERL */
102
103 #ifdef DO_PYTHON
104 bool PY_use_dynamic = false;
105 #endif /* DO_PYTHON */
106
107 static char     CMDfetchhelp[] = "[MessageID|Number]";
108
109 static CMDENT   CMDtable[] = {
110     {   "authinfo",     CMDauthinfo,    false,  3,      CMDany,
111         "user Name|pass Password|generic <prog> <args>" },
112 #ifdef HAVE_SSL
113     {   "starttls",     CMDstarttls,    false,  1,      1,
114         NULL },
115 #endif
116     {   "article",      CMDfetch,       true,   1,      2,
117         CMDfetchhelp },
118     {   "body",         CMDfetch,       true,   1,      2,
119         CMDfetchhelp },
120     {   "date",         CMDdate,        false,  1,      1,
121         NULL },
122     {   "group",        CMDgroup,       true,   2,      2,
123         "newsgroup" },
124     {   "head",         CMDfetch,       true,   1,      2,
125         CMDfetchhelp },
126     {   "help",         CMDhelp,        false,  1,      CMDany,
127         NULL },
128     {   "ihave",        CMDpost,        true,   2,      2,
129         "MessageID" },
130     {   "last",         CMDnextlast,    true,   1,      1,
131         NULL },
132     {   "list",         CMDlist,        true,   1,      3,
133         "[active|active.times|distrib.pats|distributions|extensions|moderators|motd|newsgroups|overview.fmt|subscriptions]" },
134     {   "listgroup",    CMDgroup,       true,   1,      2,
135         "newsgroup" },
136     {   "mode",         CMDmode,        false,  2,      2,
137         "reader" },
138     {   "newgroups",    CMDnewgroups,   true,   3,      5,
139         "[YY]yymmdd hhmmss [\"GMT\"]" },
140     {   "newnews",      CMDnewnews,     true,   4,      5,
141         "newsgroups [YY]yymmdd hhmmss [\"GMT\"]" },
142     {   "next",         CMDnextlast,    true,   1,      1,
143         NULL },
144     {   "post",         CMDpost,        true,   1,      1,
145         NULL },
146     {   "slave",        CMD_unimp,      false,  1,      1,
147         NULL },
148     {   "stat",         CMDfetch,       true,   1,      2,
149         CMDfetchhelp },
150     {   "xgtitle",      CMDxgtitle,     true,   1,      2,
151         "[group_pattern]" },
152     {   "xhdr",         CMDpat,         true,   2,      3,
153         "header [range|MessageID]" },
154     {   "xover",        CMDxover,       true,   1,      2,
155         "[range]" },
156     {   "xpat",         CMDpat,         true,   4,      CMDany,
157         "header range|MessageID pat [morepat...]" },
158     {   "xpath",        CMDxpath,       true,   2,      2,
159         "MessageID" },
160     {   NULL,           CMD_unimp,      false,  0,      0,
161         NULL }
162 };
163
164
165 static const char *const timer_name[] = {
166     "idle",
167     "newnews",
168     "readart",
169     "checkart",
170     "nntpread",
171     "nntpwrite",
172 };
173
174 /*
175 **  Log a summary status message and exit.
176 */
177 void
178 ExitWithStats(int x, bool readconf)
179 {
180     double              usertime;
181     double              systime;
182
183     line_free(&NNTPline);
184     fflush(stdout);
185     STATfinish = TMRnow_double();
186     if (GetResourceUsage(&usertime, &systime) < 0) {
187         usertime = 0;
188         systime = 0;
189     }
190
191     GRPreport();
192     if (ARTcount)
193         syslog(L_NOTICE, "%s exit articles %ld groups %ld", 
194             ClientHost, ARTcount, GRPcount);
195     if (POSTreceived ||  POSTrejected)
196         syslog(L_NOTICE, "%s posts received %ld rejected %ld",
197            ClientHost, POSTreceived, POSTrejected);
198     syslog(L_NOTICE, "%s times user %.3f system %.3f idle %.3f elapsed %.3f",
199         ClientHost, usertime, systime, IDLEtime, STATfinish - STATstart);
200     /* Tracking code - Make entries in the logfile(s) to show that we have
201         finished with this session */
202     if (!readconf && PERMaccessconf &&  PERMaccessconf->readertrack) {
203         syslog(L_NOTICE, "%s Tracking Disabled (%s)", ClientHost, Username);
204         if (LLOGenable) {
205                 fprintf(locallog, "%s Tracking Disabled (%s)\n", ClientHost, Username);
206                 fclose(locallog);
207                 syslog(L_NOTICE,"%s Local Logging ends (%s) %s",ClientHost, Username, LocalLogFileName);
208         }
209     }
210     if (ARTget)
211         syslog(L_NOTICE, "%s artstats get %ld time %ld size %ld", ClientHost,
212             ARTget, ARTgettime, ARTgetsize);
213     if (!readconf && PERMaccessconf && PERMaccessconf->nnrpdoverstats && OVERcount)
214         syslog(L_NOTICE, "%s overstats count %ld hit %ld miss %ld time %ld size %ld dbz %ld seek %ld get %ld artcheck %ld", ClientHost,
215             OVERcount, OVERhit, OVERmiss, OVERtime, OVERsize, OVERdbz, OVERseek, OVERget, OVERartcheck);
216
217 #ifdef HAVE_SSL
218      if (tls_conn) {
219         SSL_shutdown(tls_conn);
220         SSL_free(tls_conn);
221         tls_conn = NULL;
222      } 
223 #endif
224
225      if (DaemonMode) {
226         shutdown(STDIN_FILENO, 2);
227         shutdown(STDOUT_FILENO, 2);
228         shutdown(STDERR_FILENO, 2);
229         close(STDIN_FILENO);
230         close(STDOUT_FILENO);
231         close(STDERR_FILENO);
232      }
233     
234     OVclose();
235     SMshutdown();
236
237 #ifdef DO_PYTHON
238         PY_close_python();
239 #endif /* DO_PYTHON */
240
241     if (History)
242         HISclose(History);
243
244     if (innconf->timer != 0) {
245         TMRsummary(ClientHost, timer_name);
246         TMRfree();
247     }
248
249     if (LocalLogFileName != NULL)
250         free(LocalLogFileName);
251     closelog();
252     exit(x);
253 }
254
255
256 /*
257 **  The "help" command.
258 */
259 /* ARGSUSED0 */
260 void
261 CMDhelp(int ac UNUSED, char *av[] UNUSED)
262 {
263     CMDENT      *cp;
264     char        *p, *q;
265     static const char *newsmaster = NEWSMASTER;
266
267     Reply("%s\r\n", NNTP_HELP_FOLLOWS);
268     for (cp = CMDtable; cp->Name; cp++)
269         if (cp->Help == NULL)
270             Printf("  %s\r\n", cp->Name);
271         else
272             Printf("  %s %s\r\n", cp->Name, cp->Help);
273     if (PERMaccessconf && (VirtualPathlen > 0)) {
274         if (PERMaccessconf->newsmaster) {
275             if (strchr(PERMaccessconf->newsmaster, '@') == NULL) {
276                 Printf("Report problems to <%s@%s>\r\n",
277                     PERMaccessconf->newsmaster, PERMaccessconf->domain);
278             } else {
279                 Printf("Report problems to <%s>\r\n",
280                     PERMaccessconf->newsmaster);
281             }
282         } else {
283             /* sigh, pickup from newsmaster anyway */
284             if ((p = strchr(newsmaster, '@')) == NULL)
285                 Printf("Report problems to <%s@%s>\r\n",
286                     newsmaster, PERMaccessconf->domain);
287             else {
288                 q = xstrndup(newsmaster, p - newsmaster);
289                 Printf("Report problems to <%s@%s>\r\n",
290                     q, PERMaccessconf->domain);
291                 free(q);
292             }
293         }
294     } else {
295         if (strchr(newsmaster, '@') == NULL)
296             Printf("Report problems to <%s@%s>\r\n",
297                 newsmaster, innconf->fromhost);
298         else
299             Printf("Report problems to <%s>\r\n",
300                 newsmaster);
301     }
302     Reply(".\r\n");
303 }
304
305
306 /*
307 **  Unimplemented catch-all.
308 */
309 /* ARGSUSED0 */
310 void
311 CMD_unimp(ac, av)
312     int         ac UNUSED;
313     char        *av[];
314 {
315     if (strcasecmp(av[0], "slave") == 0)
316         /* Somebody sends us this?  I don't believe it! */
317         Reply("%d Unsupported\r\n", NNTP_SLAVEOK_VAL);
318     else
319         Reply("%d %s not implemented; try help\r\n",
320             NNTP_BAD_COMMAND_VAL, av[0]);
321 }
322
323
324 #ifndef INADDR_LOOPBACK
325 #define INADDR_LOOPBACK 0x7f000001
326 #endif  /* INADDR_LOOPBACK */
327 /*
328 **  Convert an IP address to a hostname.  Don't trust the reverse lookup,
329 **  since anyone can fake .in-addr.arpa entries.
330 */
331 static bool
332 Address2Name(INADDR *ap, char *hostname, int i)
333 {
334     char                *p;
335     struct hostent      *hp;
336     static char         mismatch_error[] = "reverse lookup validation failed";
337     char                **pp;
338
339     /* Get the official hostname, store it away. */
340     if ((hp = gethostbyaddr((char *)ap, sizeof *ap, AF_INET)) == NULL) {
341         HostErrorStr = hstrerror(h_errno);
342         return false;
343     }
344     strlcpy(hostname, hp->h_name, i);
345
346     /* Get addresses for this host. */
347     if ((hp = gethostbyname(hostname)) == NULL) {
348         HostErrorStr = hstrerror(h_errno);
349         return false;
350     }
351
352     /* Make sure one of those addresses is the address we got. */
353     for (pp = hp->h_addr_list; *pp; pp++)
354         if (strncmp((const char *)&ap->s_addr, *pp, hp->h_length) == 0)
355             break;
356     if (*pp == NULL)
357     {
358         HostErrorStr = mismatch_error;
359         return false;
360     }
361
362     /* Only needed for misconfigured YP/NIS systems. */
363     if (ap->s_addr != INADDR_LOOPBACK && strchr(hostname, '.') == NULL
364      && (p = innconf->domain) != NULL) {
365         strlcat(hostname, ".", i);
366         strlcat(hostname, p, i);
367     }
368
369     /* Make all lowercase, for wildmat. */
370     for (p = hostname; *p; p++)
371         if (CTYPE(isupper, (int)*p))
372             *p = tolower(*p);
373     return true;
374 }
375
376 /*
377 **  Convert an IPv6 address to a hostname.  Don't trust the reverse lookup,
378 **  since anyone can fake .ip6.arpa entries.
379 */
380 #ifdef HAVE_INET6
381 static bool
382 Address2Name6(struct sockaddr *sa, char *hostname, int i)
383 {
384     static char         mismatch_error[] = "reverse lookup validation failed";
385     int ret;
386     bool valid = 0;
387     struct addrinfo hints, *res, *res0;
388     char *p;
389
390     /* Get the official hostname, store it away. */
391     ret = getnameinfo( sa, SA_LEN( sa ), hostname, i, NULL, 0, NI_NAMEREQD );
392     if( ret != 0 )
393     {
394         HostErrorStr = gai_strerror( ret );
395         return false;
396     }
397
398     /* Get addresses for this host. */
399     memset( &hints, 0, sizeof( hints ) );
400     hints.ai_socktype = SOCK_STREAM;
401     hints.ai_family = AF_INET6;
402     if( ( ret = getaddrinfo( hostname, NULL, &hints, &res0 ) ) != 0 )
403     {
404         HostErrorStr = gai_strerror( ret );
405         return false;
406     }
407
408     /* Make sure one of those addresses is the address we got. */
409     for( res = res0; res; res = res->ai_next )
410     {
411 #ifdef HAVE_BROKEN_IN6_ARE_ADDR_EQUAL
412         if( ! memcmp( &(((struct sockaddr_in6 *)sa)->sin6_addr),
413                     &(((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr),
414                     sizeof( struct in6_addr ) ) )
415 #else
416         if( IN6_ARE_ADDR_EQUAL( &(((struct sockaddr_in6 *)sa)->sin6_addr),
417                     &(((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr) ) )
418 #endif
419         {
420             valid = 1;
421             break;
422         }
423     }
424
425     freeaddrinfo( res0 );
426
427     if (valid) {
428         /* Make all lowercase for matching. */
429         for (p = hostname; *p != '\0'; p++)
430             if (CTYPE(isupper, *p))
431                 *p = tolower(*p);
432         return true;
433     } else {
434         HostErrorStr = mismatch_error;
435         return false;
436     }
437 }
438 #endif
439
440
441 static bool
442 Sock2String( struct sockaddr *sa, char *string, int len, bool lookup )
443 {
444     struct sockaddr_in *sin4 = (struct sockaddr_in *)sa;
445
446 #ifdef HAVE_INET6
447     struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
448     struct sockaddr_in temp;
449
450     if( sa->sa_family == AF_INET6 )
451     {
452         if( ! IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) )
453         {
454             if( lookup )
455             {
456                 return Address2Name6(sa, string, len);
457             } else {
458                 strlcpy( string, sprint_sockaddr( sa ), len );
459                 return true;
460             }
461         } else {
462             temp.sin_family = AF_INET;
463             memcpy( &temp.sin_addr, sin6->sin6_addr.s6_addr + 12, 4 );
464             temp.sin_port = sin6->sin6_port;
465             sin4 = &temp;
466             /* fall through to AF_INET case */
467         }
468     }
469 #endif
470     if( lookup ) {
471         return Address2Name(&sin4->sin_addr, string, len);
472     } else {
473         strlcpy( string, inet_ntoa(sin4->sin_addr), len );
474         return true;
475     }
476 }
477
478 /*
479 **  Determine access rights of the client.
480 */
481 static void StartConnection(void)
482 {
483     struct sockaddr_storage     ssc, sss;
484     socklen_t           length;
485     const char          *default_host_error = "unknown error";
486
487     ClientIpAddr = 0L;
488     ClientHost[0] = '\0';
489     ClientIpString[0] = '\0';
490     ClientPort = 0;
491     ServerHost[0] = '\0';
492     ServerIpString[0] = '\0';
493     ServerPort = 0;
494
495     /* Get the peer's name. */
496     length = sizeof ssc;
497     if (getpeername(STDIN_FILENO, (struct sockaddr *)&ssc, &length) < 0) {
498       if (!isatty(STDIN_FILENO)) {
499             syslog(L_TRACE, "%s cant getpeername %m", "?");
500              /* so stats generation looks correct. */
501             strlcpy(ClientHost, "?", sizeof(ClientHost));
502             Printf("%d I can't get your name.  Goodbye.\r\n", NNTP_ACCESS_VAL);
503             ExitWithStats(1, true);
504         }
505         strlcpy(ClientHost, "stdin", sizeof(ClientHost));
506     }
507
508     else {
509 #ifdef HAVE_INET6
510         if ( ssc.ss_family != AF_INET && ssc.ss_family != AF_INET6) {
511 #else
512         if ( ssc.ss_family != AF_INET ) {
513 #endif
514             syslog(L_ERROR, "%s bad_address_family %ld",
515                 "?", (long)ssc.ss_family);
516             Printf("%d Bad address family.  Goodbye.\r\n", NNTP_ACCESS_VAL);
517             ExitWithStats(1, true);
518         }
519
520         length = sizeof sss;
521         if (getsockname(STDIN_FILENO, (struct sockaddr *)&sss, &length) < 0) {
522             syslog(L_NOTICE, "%s can't getsockname %m", ClientHost);
523             Printf("%d Can't figure out where you connected to.  Goodbye\r\n", NNTP_ACCESS_VAL);
524             ExitWithStats(1, true);
525         }
526
527         /* figure out client's IP address/hostname */
528         HostErrorStr = default_host_error;
529         if( ! Sock2String( (struct sockaddr *)&ssc, ClientIpString,
530                                 sizeof( ClientIpString ), false ) ) {
531             syslog(L_NOTICE, "? cant get client numeric address: %s", HostErrorStr);
532             ExitWithStats(1, true);
533         }
534         if(GetHostByAddr) {
535             HostErrorStr = default_host_error;
536             if( ! Sock2String( (struct sockaddr *)&ssc, ClientHost,
537                                     sizeof( ClientHost ), true ) ) {
538                 syslog(L_NOTICE,
539                        "? reverse lookup for %s failed: %s -- using IP address for access",
540                        ClientIpString, HostErrorStr);
541                 strlcpy(ClientHost, ClientIpString, sizeof(ClientHost));
542             }
543         } else {
544             strlcpy(ClientHost, ClientIpString, sizeof(ClientHost));
545         }
546
547         /* figure out server's IP address/hostname */
548         HostErrorStr = default_host_error;
549         if( ! Sock2String( (struct sockaddr *)&sss, ServerIpString,
550                                 sizeof( ServerIpString ), false ) ) {
551             syslog(L_NOTICE, "? cant get server numeric address: %s", HostErrorStr);
552             ExitWithStats(1, true);
553         }
554         if(GetHostByAddr) {
555             HostErrorStr = default_host_error;
556             if( ! Sock2String( (struct sockaddr *)&sss, ServerHost,
557                                     sizeof( ServerHost ), true ) ) {
558                 syslog(L_NOTICE,
559                        "? reverse lookup for %s failed: %s -- using IP address for access",
560                        ServerIpString, HostErrorStr);
561                 strlcpy(ServerHost, ServerIpString, sizeof(ServerHost));
562             }
563         } else {
564             strlcpy(ServerHost, ServerIpString, sizeof(ServerHost));
565         }
566
567         /* get port numbers */
568         switch( ssc.ss_family ) {
569             case AF_INET:
570                 ClientPort = ntohs( ((struct sockaddr_in *)&ssc)->sin_port );
571                 ServerPort = ntohs( ((struct sockaddr_in *)&sss)->sin_port );
572                 break;
573 #ifdef HAVE_INET6
574             case AF_INET6:
575                 ClientPort = ntohs( ((struct sockaddr_in6 *)&ssc)->sin6_port );
576                 ServerPort = ntohs( ((struct sockaddr_in6 *)&sss)->sin6_port );
577                 break;
578 #endif
579         }
580     }
581
582     strlcpy(LogName, ClientHost, sizeof(LogName));
583
584     syslog(L_NOTICE, "%s (%s) connect", ClientHost, ClientIpString);
585
586     PERMgetaccess(NNRPACCESS);
587     PERMgetpermissions();
588 }
589
590
591 /*
592 **  Send a reply, possibly with debugging output.
593 */
594 void
595 Reply(const char *fmt, ...)
596 {
597     va_list     args;
598     int         oerrno;
599     char *      p;
600     char        buff[2048];
601
602 #ifdef HAVE_SSL
603     if (tls_conn) {
604       int r;
605
606       va_start(args, fmt);
607       vsnprintf(buff, sizeof(buff), fmt, args);
608       va_end(args);
609       TMRstart(TMR_NNTPWRITE);
610 Again:
611       r = SSL_write(tls_conn, buff, strlen(buff));
612       switch (SSL_get_error(tls_conn, r)) {
613       case SSL_ERROR_NONE:
614       case SSL_ERROR_SYSCALL:
615         break;
616       case SSL_ERROR_WANT_WRITE:
617         goto Again;
618         break;
619       case SSL_ERROR_SSL:
620         SSL_shutdown(tls_conn);
621         tls_conn = NULL;
622         errno = ECONNRESET;
623         break;
624       case SSL_ERROR_ZERO_RETURN:
625         break;
626       }
627       TMRstop(TMR_NNTPWRITE);
628     } else {
629       va_start(args, fmt);
630       TMRstart(TMR_NNTPWRITE);
631       vprintf(fmt, args);
632       TMRstop(TMR_NNTPWRITE);
633       va_end(args);
634     }
635 #else
636       va_start(args, fmt);
637       TMRstart(TMR_NNTPWRITE);
638       vprintf(fmt, args);
639       TMRstop(TMR_NNTPWRITE);
640       va_end(args);
641 #endif
642     if (Tracing) {
643         oerrno = errno;
644         va_start(args, fmt);
645
646         /* Copy output, but strip trailing CR-LF.  Note we're assuming here
647            that no output line can ever be longer than 2045 characters. */
648         vsnprintf(buff, sizeof(buff), fmt, args);
649         va_end(args);
650         p = buff + strlen(buff) - 1;
651         while (p >= buff && (*p == '\n' || *p == '\r'))
652             *p-- = '\0';
653         syslog(L_TRACE, "%s > %s", ClientHost, buff);
654
655         errno = oerrno;
656     }
657 }
658
659 void
660 Printf(const char *fmt, ...)
661 {
662     va_list     args;
663
664 #ifdef HAVE_SSL
665     if (tls_conn) {
666       int r;
667       char buff[2048];
668
669       va_start(args, fmt);
670       vsnprintf(buff, sizeof(buff), fmt, args);
671       va_end(args);
672       TMRstart(TMR_NNTPWRITE);
673 Again:
674       r = SSL_write(tls_conn, buff, strlen(buff));
675       switch (SSL_get_error(tls_conn, r)) {
676       case SSL_ERROR_NONE:
677       case SSL_ERROR_SYSCALL:
678         break;
679       case SSL_ERROR_WANT_WRITE:
680         goto Again;
681         break;
682       case SSL_ERROR_SSL:
683         SSL_shutdown(tls_conn);
684         tls_conn = NULL;
685         errno = ECONNRESET;
686         break;
687       case SSL_ERROR_ZERO_RETURN:
688         break;
689       }
690       TMRstop(TMR_NNTPWRITE);
691     } else {
692 #endif /* HAVE_SSL */
693       va_start(args, fmt);
694       TMRstart(TMR_NNTPWRITE);
695       vprintf(fmt, args);
696       TMRstop(TMR_NNTPWRITE);
697       va_end(args);
698 #ifdef HAVE_SSL
699     }
700 #endif /* HAVE_SSL */
701 }
702
703
704 #ifdef HAVE_SIGACTION
705 #define NO_SIGACTION_UNUSED UNUSED
706 #else
707 #define NO_SIGACTION_UNUSED
708 #endif
709 /*
710 **  Got a signal; toggle tracing.
711 */
712 static RETSIGTYPE
713 ToggleTrace(int s NO_SIGACTION_UNUSED)
714 {
715     ChangeTrace = true;
716 #ifndef HAVE_SIGACTION
717     xsignal(s, ToggleTrace);
718 #endif
719 }
720
721 /*
722 ** Got a SIGPIPE; exit cleanly
723 */
724 static RETSIGTYPE
725 CatchPipe(int s UNUSED)
726 {
727     ExitWithStats(0, false);
728 }
729
730 /*
731 **  Got a signal; wait for children.
732 */
733 static RETSIGTYPE
734 WaitChild(int s NO_SIGACTION_UNUSED)
735 {
736     int pid;
737
738     for (;;) {
739        pid = waitpid(-1, NULL, WNOHANG);
740        if (pid <= 0)
741             break;
742     }
743 #ifndef HAVE_SIGACTION
744     xsignal(s, WaitChild);
745 #endif
746 }
747
748 static void SetupDaemon(void) {
749     bool                val;
750
751     val = true;
752     if (SMsetup(SM_PREOPEN, (void *)&val) && !SMinit()) {
753         syslog(L_NOTICE, "cant initialize storage method, %s", SMerrorstr);
754         Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
755         ExitWithStats(1, true);
756     }
757     OVextra = overview_extra_fields();
758     if (OVextra == NULL) {
759         /* overview_extra_fields should already have logged something
760          * useful */
761         Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
762         ExitWithStats(1, true);
763     }
764     overhdr_xref = overview_index("Xref", OVextra);
765     if (!OVopen(OV_READ)) {
766         /* This shouldn't really happen. */
767         syslog(L_NOTICE, "cant open overview %m");
768         Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
769         ExitWithStats(1, true);
770     }
771     if (!OVctl(OVCACHEKEEP, &val)) {
772         syslog(L_NOTICE, "cant enable overview cache %m");
773         Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
774         ExitWithStats(1, true);
775     }
776 }
777
778 /*
779 **  Print a usage message and exit.
780 */
781 static void
782 Usage(void)
783 {
784     fprintf(stderr, "Usage error.\n");
785     exit(1);
786 }
787
788
789 /* ARGSUSED0 */
790 int
791 main(int argc, char *argv[])
792 {
793     const char *name;
794     CMDENT              *cp;
795     char                buff[NNTP_STRLEN];
796     char                **av;
797     int                 ac;
798     READTYPE            r;
799     int                 i;
800     char                *Reject;
801     int                 timeout;
802     unsigned int        vid=0; 
803     int                 count=123456789;
804     struct              timeval tv;
805     unsigned short      ListenPort = NNTP_PORT;
806 #ifdef HAVE_INET6
807     char                ListenAddr[INET6_ADDRSTRLEN];
808 #else
809     char                ListenAddr[16];
810 #endif
811     int                 lfd, fd;
812     socklen_t           clen;
813 #ifdef HAVE_INET6
814     struct sockaddr_storage ssa, csa;
815     struct sockaddr_in6 *ssa6 = (struct sockaddr_in6 *) &ssa;
816 #else
817     struct sockaddr_in  ssa, csa;
818 #endif
819     struct sockaddr_in  *ssa4 = (struct sockaddr_in *) &ssa;
820     struct stat         Sb;
821     pid_t               pid = -1;
822     gid_t               NewsGID;
823     uid_t               NewsUID;
824     int                 one = 1;
825     FILE                *pidfile;
826     struct passwd       *pwd;
827     int                 clienttimeout;
828     char                *ConfFile = NULL;
829     char                *path;
830 #if HAVE_GETSPNAM
831     struct group        *grp;
832     gid_t               shadowgid;
833 #endif /* HAVE_GETSPNAM */
834
835     int respawn = 0;
836
837     setproctitle_init(argc, argv);
838
839     /* Parse arguments.   Must xstrdup() optarg if used because setproctitle may
840        clobber it! */
841     Reject = NULL;
842     LLOGenable = false;
843     GRPcur = NULL;
844     MaxBytesPerSecond = 0;
845     strlcpy(Username, "unknown", sizeof(Username));
846
847     /* Set up the pathname, first thing, and teach our error handlers about
848        the name of the program. */
849     name = argv[0];
850     if (name == NULL || *name == '\0')
851         name = "nnrpd";
852     else {
853         const char *p;
854
855         p = strrchr(name, '/');
856         if (p != NULL)
857             name = p + 1;
858     }
859     message_program_name = xstrdup(name);
860     openlog(message_program_name, L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
861     message_handlers_die(1, message_log_syslog_crit);
862     message_handlers_warn(1, message_log_syslog_warning);
863     message_handlers_notice(1, message_log_syslog_notice);
864
865     if (!innconf_read(NULL))
866         exit(1);
867
868 #ifdef HAVE_SSL
869     while ((i = getopt(argc, argv, "c:b:Dfi:I:g:nop:P:r:s:tS")) != EOF)
870 #else
871     while ((i = getopt(argc, argv, "c:b:Dfi:I:g:nop:P:r:s:t")) != EOF)
872 #endif /* HAVE_SSL */
873         switch (i) {
874         default:
875             Usage();
876             /* NOTREACHED */
877         case 'c':               /* use alternate readers.conf */
878             ConfFile = concatpath(innconf->pathetc, optarg);
879             break;
880         case 'b':                       /* bind to a certain address in
881                                            daemon mode */
882             strlcpy(ListenAddr, optarg, sizeof(ListenAddr));
883             break;
884         case 'D':                       /* standalone daemon mode */
885             DaemonMode = true;
886             break;
887        case 'P':                       /* prespawn count in daemon mode */
888             respawn = atoi(optarg);
889             break;
890         case 'f':                       /* Don't fork on daemon mode */
891             ForeGroundMode = true;
892             break;
893 #if HAVE_GETSPNAM
894         case 'g':
895             ShadowGroup = optarg;
896             break;
897 #endif /* HAVE_GETSPNAM */
898         case 'i':                       /* Initial command */
899             PushedBack = xstrdup(optarg);
900             break;
901         case 'I':                       /* Instance */
902             NNRPinstance = xstrdup(optarg);
903             break;
904         case 'n':                       /* No DNS lookups */
905             GetHostByAddr = false;
906             break;
907         case 'o':
908             Offlinepost = true;         /* Offline posting only */
909             break;
910         case 'p':                       /* tcp port for daemon mode */
911             ListenPort = atoi(optarg);
912             break;
913         case 'r':                       /* Reject connection message */
914             Reject = xstrdup(optarg);
915             break;
916         case 's':                       /* Unused title string */
917             break;
918         case 't':                       /* Tracing */
919             Tracing = true;
920             break;
921 #ifdef HAVE_SSL
922         case 'S':                       /* SSL negotiation as soon as connected */
923             initialSSL = true;
924             break;
925 #endif /* HAVE_SSL */
926         }
927     argc -= optind;
928     if (argc)
929         Usage();
930
931     /*
932      * Make other processes happier if someone is reading
933      * This allows other processes like 'overchan' to keep up when
934      * there are lots of readers. Note that this is cumulative with
935      * 'nicekids'
936     */
937     if (innconf->nicennrpd > 0)
938         nice(innconf->nicennrpd);
939
940     HISTORY = concatpath(innconf->pathdb, _PATH_HISTORY);
941     ACTIVE = concatpath(innconf->pathdb, _PATH_ACTIVE);
942     ACTIVETIMES = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
943     NEWSGROUPS = concatpath(innconf->pathdb, _PATH_NEWSGROUPS);
944     if(ConfFile)
945         NNRPACCESS = ConfFile;
946     else
947         NNRPACCESS = concatpath(innconf->pathetc,_PATH_NNRPACCESS);
948     SPOOLlen = strlen(innconf->patharticles);
949
950     if (DaemonMode) {
951 #ifdef HAVE_INET6
952         memset(&ssa, '\0', sizeof(struct sockaddr_in6));
953         ssa6->sin6_family = AF_INET6;
954         ssa6->sin6_port   = htons(ListenPort);
955         if (inet_pton(AF_INET6, ListenAddr, ssa6->sin6_addr.s6_addr) > 0) {
956             if ( (lfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
957                 syslog(L_FATAL, "can't open socket (%m)");
958                 exit(1);
959             }
960         }
961         else {
962 #endif
963             memset(&ssa, '\0', sizeof(struct sockaddr_in));
964             ssa4->sin_family = AF_INET;
965             ssa4->sin_port   = htons(ListenPort);
966             if (inet_aton(ListenAddr, &ssa4->sin_addr) <= 0 )
967                 ssa4->sin_addr.s_addr = htonl(INADDR_ANY);
968             if ( (lfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
969                 syslog(L_FATAL, "can't open socket (%m)");
970                 exit(1);
971             }
972 #ifdef HAVE_INET6
973         }
974 #endif
975
976         if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
977                        (char *)&one, sizeof(one)) < 0) {
978             syslog(L_FATAL, "can't setsockopt(SO_REUSEADDR) (%m)");
979             exit(1);
980         }
981
982         if (bind(lfd, (struct sockaddr *) &ssa, sizeof(ssa)) < 0) {
983             fprintf(stderr, "%s: can't bind (%s)\n", argv[0], strerror(errno));
984             syslog(L_FATAL, "can't bind local address (%m)");
985             exit(1);
986         }
987
988         /* If started as root, switch to news uid */
989         if (getuid() == 0) {
990             if (stat(innconf->pathrun, &Sb) < 0 || !S_ISDIR(Sb.st_mode)) {
991                 syslog(L_FATAL, "nnrpd cant stat %s %m", innconf->pathrun);
992                 exit(1);
993             }
994             if (Sb.st_uid == 0) {
995                 syslog(L_FATAL, "nnrpd %s must not be owned by root", innconf->pathrun);
996                 exit(1);
997             }
998             pwd = getpwnam(NEWSUSER);
999             if (pwd == (struct passwd *)NULL) {
1000                 syslog(L_FATAL, "nnrpd getpwnam(%s): %s", NEWSUSER, strerror(errno));
1001                 exit(1);
1002             } else if (pwd->pw_gid != Sb.st_gid) {
1003                 syslog(L_FATAL, "nnrpd %s must have group %s", innconf->pathrun, NEWSGRP);
1004                 exit(1);
1005             } else if (pwd->pw_uid != Sb.st_uid) {
1006                 syslog(L_FATAL, "nnrpd %s must be owned by %s", innconf->pathrun, NEWSUSER);
1007                 exit(1);
1008             }
1009
1010 #if HAVE_GETSPNAM
1011             shadowgid = (gid_t) -1;
1012             /* Find shadowgroup gid if needed */
1013             if (ShadowGroup != NULL) {
1014                 if ((grp = getgrnam(ShadowGroup)) == NULL)
1015                     syslog(L_ERROR, "nnrpd cannot find group %s",
1016                                 ShadowGroup);
1017                 else
1018                     shadowgid = grp->gr_gid;
1019             } else if ((grp = getgrnam("shadow")) != NULL) {
1020                 /* found default group "shadow" */
1021                 shadowgid = grp->gr_gid;
1022                 ShadowGroup = "shadow";
1023             }
1024             /* If we have a shadowgid, try to set it as an extra group. */
1025             if (shadowgid != (gid_t) -1) {
1026                 if (setgroups(1, &shadowgid) < 0)
1027                    syslog(L_ERROR, "nnrpd cannot set supplementary group %s %m",
1028                         ShadowGroup);
1029                 else
1030                    syslog(L_NOTICE, "nnrpd added supplementary group %s",
1031                         ShadowGroup);
1032             }
1033 #endif /* HAVE_GETSPNAM */
1034
1035             NewsUID = Sb.st_uid;
1036             NewsGID = Sb.st_gid;
1037             setgid(NewsGID);
1038             if (getgid() != NewsGID)
1039                 syslog(L_ERROR, "nnrpd cant setgid to %d %m", NewsGID);
1040             setuid(NewsUID);
1041             if (getuid() != NewsUID)
1042                 syslog(L_ERROR, "nnrpd cant setuid to %d %m", NewsUID);
1043         }
1044
1045         /* Detach */
1046         if (!ForeGroundMode) {
1047             daemonize("/");
1048         }
1049
1050         if (ListenPort == NNTP_PORT)
1051             strlcpy(buff, "nnrpd.pid", sizeof(buff));
1052         else
1053             snprintf(buff, sizeof(buff), "nnrpd-%d.pid", ListenPort);
1054         path = concatpath(innconf->pathrun, buff);
1055         pidfile = fopen(path, "w");
1056         free(path);
1057         if (pidfile == NULL) {
1058             syslog(L_ERROR, "cannot write %s %m", buff);
1059             exit(1);
1060         }
1061         fprintf(pidfile,"%lu\n", (unsigned long) getpid());
1062         fclose(pidfile);
1063
1064         /* Set signal handle to care for dead children */
1065         if (!respawn)
1066             xsignal(SIGCHLD, WaitChild);
1067
1068         /* Arrange to toggle tracing. */
1069         xsignal(SIGHUP, ToggleTrace);
1070  
1071         setproctitle("accepting connections");
1072         
1073         listen(lfd, 128);       
1074
1075         if (respawn) {
1076             /* pre-forked mode */
1077             for (;;) {
1078                 if (respawn > 0) {
1079                     --respawn;
1080                     pid = fork();
1081                     if (pid == 0) {
1082                         do {
1083                             clen = sizeof(csa);
1084                             fd = accept(lfd, (struct sockaddr *) &csa, &clen);
1085                         } while (fd < 0);
1086                         break;
1087                     }
1088                 }
1089                 for (;;) {
1090                     if (respawn == 0)
1091                         pid = wait(NULL);
1092                     else
1093                         pid = waitpid(-1, NULL, WNOHANG);
1094                     if (pid <= 0)
1095                         break;
1096                     ++respawn;
1097                 }
1098             }
1099         } else {
1100             /* fork on demand */
1101             do {
1102                 clen = sizeof(csa);
1103                 fd = accept(lfd, (struct sockaddr *) &csa, &clen);
1104                 if (fd < 0)
1105                     continue;
1106             
1107                 for (i = 0; i <= innconf->maxforks && (pid = fork()) < 0; i++) {
1108                     if (i == innconf->maxforks) {
1109                         syslog(L_FATAL, "cant fork (dropping connection): %m");
1110                         continue;
1111                     }
1112                     syslog(L_NOTICE, "cant fork (waiting): %m");
1113                     sleep(1);
1114                 }
1115                 if (ChangeTrace) {
1116                     Tracing = Tracing ? false : true;
1117                     syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
1118                     ChangeTrace = false;
1119                 }
1120                 if (pid != 0)
1121                     close(fd);
1122             } while (pid != 0);
1123         }
1124
1125         /* child process starts here */
1126         setproctitle("connected");
1127         close(lfd);
1128         dup2(fd, 0);
1129         close(fd);
1130         dup2(0, 1);
1131         dup2(0, 2);
1132         if (innconf->timer != 0)
1133             TMRinit(TMR_MAX);
1134         STATstart = TMRnow_double();
1135         SetupDaemon();
1136
1137         /* if we are a daemon innd didn't make us nice, so be nice kids */
1138         if (innconf->nicekids) {
1139             if (nice(innconf->nicekids) < 0)
1140                 syslog(L_ERROR, "Could not nice child to %ld: %m", innconf->nicekids);
1141         }
1142
1143         /* Only automatically reap children in the listening process */
1144         xsignal(SIGCHLD, SIG_DFL);
1145  
1146     } else {
1147         if (innconf->timer)
1148             TMRinit(TMR_MAX);
1149         STATstart = TMRnow_double();
1150         SetupDaemon();
1151         /* Arrange to toggle tracing. */
1152         xsignal(SIGHUP, ToggleTrace);
1153     }/* DaemonMode */
1154
1155 #ifdef HAVE_SSL
1156     ClientSSL = false;
1157     if (initialSSL) {
1158         tls_init();
1159         if (tls_start_servertls(0, 1) == -1) {
1160             Reply("%d SSL connection failed\r\n", NNTP_STARTTLS_BAD_VAL);
1161             ExitWithStats(1, false);
1162         }
1163         nnrpd_starttls_done = 1;
1164         ClientSSL = true;
1165     }
1166 #endif /* HAVE_SSL */
1167
1168     /* If requested, check the load average. */
1169     if (innconf->nnrpdloadlimit > 0) {
1170         double load[1];
1171
1172         if (getloadavg(load, 1) < 0)
1173             warn("cannot obtain system load");
1174         else {
1175             if ((int)(load[0] + 0.5) > innconf->nnrpdloadlimit) {
1176                 syslog(L_NOTICE, "load %.2f > %ld", load[0], innconf->nnrpdloadlimit);
1177                 Reply("%d load at %.2f, try later\r\n", NNTP_GOODBYE_VAL,
1178                       load[0]);
1179                 ExitWithStats(1, true);
1180             }
1181         }
1182     }
1183
1184     strlcpy(LogName, "?", sizeof(LogName));
1185
1186     /* Catch SIGPIPE so that we can exit out of long write loops */
1187     xsignal(SIGPIPE, CatchPipe);
1188
1189     /* Get permissions and see if we can talk to this client */
1190     StartConnection();
1191     if (!PERMcanread && !PERMcanpost && !PERMneedauth) {
1192         syslog(L_NOTICE, "%s no_permission", ClientHost);
1193         Printf("%d You have no permission to talk.  Goodbye.\r\n",
1194                NNTP_ACCESS_VAL);
1195         ExitWithStats(1, false);
1196     }
1197
1198     /* Proceed with initialization. */
1199     setproctitle("%s connect", ClientHost);
1200
1201     /* Were we told to reject connections? */
1202     if (Reject) {
1203         syslog(L_NOTICE, "%s rejected %s", ClientHost, Reject);
1204         Reply("%s %s\r\n", NNTP_GOODBYE, Reject);
1205         ExitWithStats(0, false);
1206     }
1207
1208     if (PERMaccessconf) {
1209         if (PERMaccessconf->readertrack)
1210             PERMaccessconf->readertrack=TrackClient(ClientHost,Username);
1211     } else {
1212         if (innconf->readertrack)
1213             innconf->readertrack=TrackClient(ClientHost,Username);
1214     }
1215
1216     if ((PERMaccessconf && PERMaccessconf->readertrack)
1217         || (!PERMaccessconf && innconf->readertrack)) {
1218         int len;
1219         syslog(L_NOTICE, "%s Tracking Enabled (%s)", ClientHost, Username);
1220         pid=getpid();
1221         gettimeofday(&tv,NULL);
1222         count += pid;
1223         vid = tv.tv_sec ^ tv.tv_usec ^ pid ^ count;
1224         len = strlen("innconf->pathlog") + strlen("/tracklogs/log-") + BUFSIZ;
1225         LocalLogFileName = xmalloc(len);
1226         sprintf(LocalLogFileName, "%s/tracklogs/log-%d", innconf->pathlog, vid);
1227         if ((locallog = fopen(LocalLogFileName, "w")) == NULL) {
1228             LocalLogDirName = concatpath(innconf->pathlog, "tracklogs");
1229             MakeDirectory(LocalLogDirName, false);
1230             free(LocalLogDirName);
1231         }
1232         if (locallog == NULL && (locallog = fopen(LocalLogFileName, "w")) == NULL) {
1233             syslog(L_ERROR, "%s Local Logging failed (%s) %s: %m", ClientHost, Username, LocalLogFileName);
1234         } else {
1235             syslog(L_NOTICE, "%s Local Logging begins (%s) %s",ClientHost, Username, LocalLogFileName);
1236             fprintf(locallog, "%s Tracking Enabled (%s)\n", ClientHost, Username);
1237             fflush(locallog);
1238             LLOGenable = true;
1239         }
1240     }
1241
1242     if (PERMaccessconf) {
1243         Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
1244            PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
1245            PERMaccessconf->pathhost, inn_version_string,
1246            PERMcanpost ? "posting ok" : "no posting");
1247         clienttimeout = PERMaccessconf->clienttimeout;
1248     } else {
1249         Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
1250            PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
1251            innconf->pathhost, inn_version_string,
1252            PERMcanpost ? "posting ok" : "no posting");
1253         clienttimeout = innconf->clienttimeout;
1254     }
1255
1256     line_init(&NNTPline);
1257
1258     /* Main dispatch loop. */
1259     for (timeout = innconf->initialtimeout, av = NULL, ac = 0; ;
1260                         timeout = clienttimeout) {
1261         TMRstart(TMR_NNTPWRITE);
1262         fflush(stdout);
1263         TMRstop(TMR_NNTPWRITE);
1264         if (ChangeTrace) {
1265             Tracing = Tracing ? false : true;
1266             syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
1267             ChangeTrace = false;
1268         }
1269         if (PushedBack) {
1270             if (PushedBack[0] == '\0')
1271                 continue;
1272             if (Tracing)
1273                 syslog(L_TRACE, "%s < %s", ClientHost, PushedBack);
1274             ac = Argify(PushedBack, &av);
1275             r = RTok;
1276         }
1277         else {
1278             size_t len;
1279             const char *p;
1280
1281             r = line_read(&NNTPline, timeout, &p, &len);
1282             switch (r) {
1283             default:
1284                 syslog(L_ERROR, "%s internal %d in main", ClientHost, r);
1285                 /* FALLTHROUGH */
1286             case RTtimeout:
1287                 if (timeout < clienttimeout)
1288                     syslog(L_NOTICE, "%s timeout short", ClientHost);
1289                 else
1290                     syslog(L_NOTICE, "%s timeout", ClientHost);
1291                 ExitWithStats(1, false);
1292                 break;
1293             case RTok:
1294                 if (len < sizeof(buff)) {
1295                     /* line_read guarantees null termination */
1296                     memcpy(buff, p, len + 1);
1297                     /* Do some input processing, check for blank line. */
1298                     if (Tracing)
1299                         syslog(L_TRACE, "%s < %s", ClientHost, buff);
1300                     if (buff[0] == '\0')
1301                         continue;
1302                     ac = Argify(buff, &av);
1303                     break;
1304                 }
1305                 /* FALLTHROUGH */               
1306             case RTlong:
1307                 Reply("%d Line too long\r\n", NNTP_BAD_COMMAND_VAL);
1308                 continue;
1309             case RTeof:
1310                 /* Handled below. */
1311                 break;
1312             }
1313         }
1314         /* Client gone? */
1315         if (r == RTeof)
1316             break;
1317         if (ac == 0 || strcasecmp(av[0], "quit") == 0)
1318             break;
1319
1320         /* Find command. */
1321         for (cp = CMDtable; cp->Name; cp++)
1322             if (strcasecmp(cp->Name, av[0]) == 0)
1323                 break;
1324         if (cp->Name == NULL) {
1325             if ((int)strlen(buff) > 40)
1326                 syslog(L_NOTICE, "%s unrecognized %.40s...", ClientHost, buff);
1327             else
1328                 syslog(L_NOTICE, "%s unrecognized %s", ClientHost, buff);
1329             Reply("%d What?\r\n", NNTP_BAD_COMMAND_VAL);
1330             continue;
1331         }
1332
1333         /* Check usage. */
1334         if ((cp->Minac != CMDany && ac < cp->Minac)
1335          || (cp->Maxac != CMDany && ac > cp->Maxac)) {
1336             Reply("%d %s\r\n",
1337                 NNTP_SYNTAX_VAL,  cp->Help ? cp->Help : "Usage error");
1338             continue;
1339         }
1340
1341         /* Check permissions and dispatch. */
1342         if (cp->Needauth && PERMneedauth) {
1343             Reply("%d Authentication required for command\r\n",
1344                 NNTP_AUTH_NEEDED_VAL);
1345             continue;
1346         }
1347         setproctitle("%s %s", ClientHost, av[0]);
1348         (*cp->Function)(ac, av);
1349         if (PushedBack)
1350             break;
1351         if (PERMaccessconf)
1352             clienttimeout = PERMaccessconf->clienttimeout;
1353         else
1354             clienttimeout = innconf->clienttimeout;
1355     }
1356
1357     Reply("%s\r\n", NNTP_GOODBYE_ACK);
1358
1359     ExitWithStats(0, false);
1360     /* NOTREACHED */
1361     return 1;
1362 }