chiark / gitweb /
debugging for thing that crashed
[innduct.git] / innd / cc.c
1 /*  $Id: cc.c 7748 2008-04-06 13:49:56Z iulius $
2 **
3 **  Routines for the control channel.
4 **
5 **  Create a Unix-domain datagram socket that processes on the local server
6 **  send messages to.  The control channel is used only by ctlinnd to tell
7 **  the server to perform special functions.  We use datagrams so that we
8 **  don't need to do an accept() and tie up another descriptor.  recvfrom
9 **  seems to be broken on several systems, so the client passes in the
10 **  socket name.
11 **
12 **  This module completely rips away all pretense of software layering.
13 */
14
15 #include "config.h"
16 #include "clibrary.h"
17
18 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
19 # include <sys/un.h>
20 #endif
21
22 #include "inn/innconf.h"
23 #include "inn/qio.h"
24 #include "innd.h"
25 #include "inndcomm.h"
26 #include "innperl.h"
27
28 /*
29 **  An entry in the dispatch table.  The name, and implementing function,
30 **  of every command we support.
31 */
32 typedef struct _CCDISPATCH {
33     char Name;
34     int argc;
35     const char * (*Function)(char *av[]);
36 } CCDISPATCH;
37
38
39 static const char *     CCallow(char *av[]);
40 static const char *     CCbegin(char *av[]);
41 static const char *     CCchgroup(char *av[]);
42 static const char *     CCdrop(char *av[]);
43 static const char *     CCfeedinfo(char *av[]);
44 static const char *     CCflush(char *av[]);
45 static const char *     CCflushlogs(char *unused[]);
46 static const char *     CCgo(char *av[]);
47 static const char *     CChangup(char *av[]);
48 static const char *     CCreserve(char *av[]);
49 static const char *     CClogmode(char *unused[]);
50 static const char *     CCmode(char *unused[]);
51 static const char *     CCname(char *av[]);
52 static const char *     CCnewgroup(char *av[]);
53 static const char *     CCparam(char *av[]);
54 static const char *     CCpause(char *av[]);
55 static const char *     CCreaders(char *av[]);
56 static const char *     CCreject(char *av[]);
57 static const char *     CCreload(char *av[]);
58 static const char *     CCrenumber(char *av[]);
59 static const char *     CCrmgroup(char *av[]);
60 static const char *     CCsend(char *av[]);
61 static const char *     CCshutdown(char *av[]);
62 static const char *     CCsignal(char *av[]);
63 static const char *     CCstathist(char *av[]);
64 static const char *     CCstatus(char *av[]);
65 static const char *     CCthrottle(char *av[]);
66 static const char *     CCtimer(char *av[]);
67 static const char *     CCtrace(char *av[]);
68 static const char *     CCxabort(char *av[]);
69 static const char *     CCxexec(char *av[]);
70 static const char *     CCfilter(char *av[]);
71 static const char *     CCperl(char *av[]);
72 static const char *     CCpython(char *av[]);
73 static const char *     CClowmark(char *av[]);
74
75
76 static char             *CCpath = NULL;
77 static char             **CCargv;
78 static char             CCnosite[] = "1 No such site";
79 static char             CCwrongtype[] = "1 Wrong site type";
80 static char             CCnogroup[] = "1 No such group";
81 static char             CCnochannel[] = "1 No such channel";
82 static char             CCnoreason[] = "1 Empty reason";
83 static char             CCbigreason[] = "1 Reason too long";
84 static char             CCnotrunning[] = "1 Must be running";
85 static struct buffer    CCreply;
86 static CHANNEL          *CCchan;
87 static int              CCwriter;
88 static CCDISPATCH       CCcommands[] = {
89     {   SC_ADDHIST,     5, CCaddhist    },
90     {   SC_ALLOW,       1, CCallow      },
91     {   SC_BEGIN,       1, CCbegin      },
92     {   SC_CANCEL,      1, CCcancel     },
93     {   SC_CHANGEGROUP, 2, CCchgroup    },
94     {   SC_CHECKFILE,   0, CCcheckfile  },
95     {   SC_DROP,        1, CCdrop       },
96     {   SC_FEEDINFO,    1, CCfeedinfo   },
97     {   SC_FILTER,      1, CCfilter     },
98     {   SC_PERL,        1, CCperl       },
99     {   SC_PYTHON,      1, CCpython     },
100     {   SC_FLUSH,       1, CCflush      },
101     {   SC_FLUSHLOGS,   0, CCflushlogs  },
102     {   SC_GO,          1, CCgo         },
103     {   SC_HANGUP,      1, CChangup     },
104     {   SC_LOGMODE,     0, CClogmode    },
105     {   SC_MODE,        0, CCmode       },
106     {   SC_NAME,        1, CCname       },
107     {   SC_NEWGROUP,    3, CCnewgroup   },
108     {   SC_PARAM,       2, CCparam      },
109     {   SC_PAUSE,       1, CCpause      },
110     {   SC_READERS,     2, CCreaders    },
111     {   SC_REJECT,      1, CCreject     },
112     {   SC_RENUMBER,    1, CCrenumber   },
113     {   SC_RELOAD,      2, CCreload     },
114     {   SC_RESERVE,     1, CCreserve    },
115     {   SC_RMGROUP,     1, CCrmgroup    },
116     {   SC_SEND,        2, CCsend       },
117     {   SC_SHUTDOWN,    1, CCshutdown   },
118     {   SC_SIGNAL,      2, CCsignal     },
119     {   SC_STATHIST,    1, CCstathist   },
120     {   SC_STATUS,      1, CCstatus     },
121     {   SC_THROTTLE,    1, CCthrottle   },
122     {   SC_TIMER,       1, CCtimer      },
123     {   SC_TRACE,       2, CCtrace      },
124     {   SC_XABORT,      1, CCxabort     },
125     {   SC_LOWMARK,     1, CClowmark    },
126     {   SC_XEXEC,       1, CCxexec      }
127 };
128
129 static RETSIGTYPE CCresetup(int unused);
130 \f
131
132 void
133 CCcopyargv(char *av[])
134 {
135     char        **v;
136     int         i;
137
138     /* Get the vector size. */
139     for (i = 0; av[i]; i++)
140         continue;
141
142     /* Get the vector, copy each element. */
143     for (v = CCargv = xmalloc((i + 1) * sizeof(char *)); *av; av++) {
144         /* not to renumber */
145         if (strncmp(*av, "-r", 2) == 0)
146             continue;
147         *v++ = xstrdup(*av);
148     }
149     *v = NULL;
150 }
151
152
153 /*
154 **  Return a string representing our operating mode.
155 */
156 static const char *
157 CCcurrmode(void)
158 {
159     static char         buff[32];
160
161     /* Server's mode. */
162     switch (Mode) {
163     default:
164         snprintf(buff, sizeof(buff), "Unknown %d", Mode);
165         return buff;
166     case OMrunning:
167         return "running";
168     case OMpaused:
169         return "paused";
170     case OMthrottled:
171         return "throttled";
172     }
173 }
174
175
176 /*
177 **  Add <> around Message-ID if needed.
178 */
179 static const char *
180 CCgetid(char *p, const char **store)
181 {
182     static char NULLMESGID[] = "1 Empty Message-ID";
183     static struct buffer Save = { 0, 0, 0, NULL };
184     int i;
185
186     if (*p == '\0')
187         return NULLMESGID;
188     if (*p == '<') {
189         if (p[1] == '\0' || p[1] == '>')
190             return NULLMESGID;
191         *store = p;
192         return NULL;
193     }
194
195     /* Make sure the Message-ID buffer has room. */
196     i = 1 + strlen(p) + 1 + 1;
197     buffer_resize(&Save, i);
198     *store = Save.data;
199     snprintf(Save.data, Save.size, "<%s>", p);
200     return NULL;
201 }
202
203
204 /*
205 **  Abort and dump core.
206 */
207 static const char *
208 CCxabort(char *av[])
209 {
210     syslog(L_FATAL, "%s abort %s", LogName, av[0]);
211     abort();
212     syslog(L_FATAL, "%s cant abort %m", LogName);
213     CleanupAndExit(1, av[0]);
214     /* NOTREACHED */
215 }
216
217
218 /*
219 **  Do the work needed to add a history entry.
220 */
221 const char *
222 CCaddhist(char *av[])
223 {
224     static char         DIGITS[] = "0123456789";
225     ARTDATA             Data;
226     const char *        p, *msgid;
227     bool                ok;
228     TOKEN               token;
229
230     /* You must pass a <message-id> ID, the history API will hash it as it
231      * wants */
232     if ((p = CCgetid(av[0], &msgid)) != NULL)
233         return p;
234
235     /* If paused, don't try to use the history database since expire may be
236        running */
237     if (Mode == OMpaused)
238         return "1 Server paused";
239
240     /* If throttled by admin, briefly open the history database. */
241     if (Mode != OMrunning) {
242         if (ThrottledbyIOError)
243             return "1 Server throttled";
244         InndHisOpen();
245     }
246
247     if (HIScheck(History, msgid)) {
248         if (Mode != OMrunning) InndHisClose();
249         return "1 Duplicate";
250     }
251     if (Mode != OMrunning) InndHisClose();
252     if (strspn(av[1], DIGITS) != strlen(av[1]))
253         return "1 Bad arrival date";
254     Data.Arrived = atol(av[1]);
255     if (strspn(av[2], DIGITS) != strlen(av[2]))
256         return "1 Bad expiration date";
257     Data.Expires = atol(av[2]);
258     if (strspn(av[3], DIGITS) != strlen(av[3]))
259         return "1 Bad posted date";
260     Data.Posted = atol(av[3]);
261
262     token = TextToToken(av[4]);
263     if (Mode == OMrunning)
264         ok = InndHisWrite(msgid, Data.Arrived, Data.Posted,
265                           Data.Expires, &token);
266     else {
267         /* Possible race condition, but documented in ctlinnd manpage. */
268         InndHisOpen();
269         ok = InndHisWrite(msgid, Data.Arrived, Data.Posted,
270                           Data.Expires, &token);
271         InndHisClose();
272     }
273     return ok ? NULL : "1 Write failed";
274 }
275
276
277 /*
278 **  Do the work to allow foreign connectiosn.
279 */
280 static const char *
281 CCallow(char *av[])
282 {
283     char        *p;
284
285     if (RejectReason == NULL)
286         return "1 Already allowed";
287     p = av[0];
288     if (*p && strcmp(p, RejectReason) != 0)
289         return "1 Wrong reason";
290     free(RejectReason);
291     RejectReason = NULL;
292     return NULL;
293 }
294
295
296 /*
297 **  Do the work needed to start feeding a (new) site.
298 */
299 static const char *
300 CCbegin(char *av[])
301 {
302     SITE        *sp;
303     int         i;
304     int         length;
305     char        *p;
306     const char  *p1;
307     char        **strings;
308     NEWSGROUP   *ngp;
309     const char  *error;
310     char        *subbed;
311     char        *poison;
312
313     /* If site already exists, drop it. */
314     if (SITEfind(av[0]) != NULL && (p1 = CCdrop(av)) != NULL)
315         return p1;
316
317     /* Find the named site. */
318     length = strlen(av[0]);
319     for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
320         if ((p[length] == NF_FIELD_SEP || p[length] == NF_SUBFIELD_SEP)
321          && strncasecmp(p, av[0], length) == 0) {
322             p = xstrdup(p);
323             break;
324         }
325     if (p == NULL)
326         return CCnosite;
327
328     if (p[0] == 'M' && p[1] == 'E' && p[2] == NF_FIELD_SEP)
329         sp = &ME;
330     else {
331         /* Get space for the new site entry, and space for it in all
332          * the groups. */
333         for (i = nSites, sp = Sites; --i >= 0; sp++)
334             if (sp->Name == NULL)
335                 break;
336         if (i < 0) {
337             nSites++;
338             Sites = xrealloc(Sites, nSites * sizeof(SITE));
339             sp = &Sites[nSites - 1];
340             sp->Next = sp->Prev = NOSITE;
341             for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
342                 ngp->Sites = xrealloc(ngp->Sites, nSites * sizeof(int));
343                 ngp->Poison = xrealloc(ngp->Poison, nSites * sizeof(int));
344             }
345         }
346     }
347
348     /* Parse. */
349     subbed = xmalloc(nGroups);
350     poison = xmalloc(nGroups);
351     error = SITEparseone(p, sp, subbed, poison);
352     free(subbed);
353     free(poison);
354     if (error != NULL) {
355         free((void *)p);
356         syslog(L_ERROR, "%s bad_newsfeeds %s", av[0], error);
357         return "1 Parse error";
358     }
359
360     if (sp != &ME && (!SITEsetup(sp) || !SITEfunnelpatch()))
361         return "1 Startup error";
362     SITEforward(sp, "begin");
363     return NULL;
364 }
365
366
367 /*
368 **  Common code to change a group's flags.
369 */
370 static const char *
371 CCdochange(NEWSGROUP *ngp, char *Rest)
372 {
373     int                 length;
374     char                *p;
375
376     if (ngp->Rest[0] == Rest[0]) {
377         length = strlen(Rest);
378         if (ngp->Rest[length] == '\n' && strncmp(ngp->Rest, Rest, length) == 0)
379             return "0 Group status unchanged";
380     }
381     if (Mode != OMrunning)
382         return CCnotrunning;
383
384     p = xstrdup(ngp->Name);
385     if (!ICDchangegroup(ngp, Rest)) {
386         syslog(L_NOTICE, "%s cant change_group %s to %s", LogName, p, Rest);
387         free(p);
388         return "1 Change failed (probably can't write active?)";
389     }
390     syslog(L_NOTICE, "%s change_group %s to %s", LogName, p, Rest);
391     free(p);
392     return NULL;
393 }
394
395
396 /*
397 **  Change the mode of a newsgroup.
398 */
399 static const char *
400 CCchgroup(char *av[])
401 {
402     NEWSGROUP   *ngp;
403     char        *Rest;
404
405     if ((ngp = NGfind(av[0])) == NULL)
406         return CCnogroup;
407     Rest = av[1];
408     if (Rest[0] != NF_FLAG_ALIAS) {
409         Rest[1] = '\0';
410         if (CTYPE(isupper, Rest[0]))
411             Rest[0] = tolower(Rest[0]);
412     }
413     return CCdochange(ngp, Rest);
414 }
415
416
417 /*
418 **  Cancel a message.
419 */
420 const char *
421 CCcancel(char *av[])
422 {
423     ARTDATA     Data;
424     const char *        p, *msgid;
425
426     Data.Posted = Data.Arrived = Now.time;
427     Data.Expires = 0;
428     Data.Feedsite = "?";
429     if ((p = CCgetid(av[0], &msgid)) != NULL)
430         return p;
431
432     Data.HdrContent[HDR__MESSAGE_ID].Value = (char *)msgid;
433     Data.HdrContent[HDR__MESSAGE_ID].Length = strlen(msgid);
434     if (Mode == OMrunning)
435         ARTcancel(&Data, msgid, true);
436     else {
437         /* If paused, don't try to use the history database since expire may be
438            running */
439         if (Mode == OMpaused)
440             return "1 Server paused";
441         if (ThrottledbyIOError)
442             return "1 Server throttled";
443         /* Possible race condition, but documented in ctlinnd manpage. */
444         InndHisOpen();
445         ARTcancel(&Data, msgid, true);
446         InndHisClose();
447     }
448     if (innconf->logcancelcomm)
449         syslog(L_NOTICE, "%s cancelled %s", LogName, msgid);
450     return NULL;
451 }
452
453
454 /*
455 **  Syntax-check the newsfeeds file.
456 */
457 const char *
458 CCcheckfile(char *unused[])
459 {
460   char          **strings;
461   char          *p;
462   int           i;
463   int           errors;
464   const char *  error;
465   SITE          fake;
466   bool          needheaders, needoverview, needpath, needstoredgroup;
467   bool          needreplicdata;
468
469   unused = unused;              /* ARGSUSED */
470   /* Parse all site entries. */
471   strings = SITEreadfile(false);
472   fake.Buffer.size = 0;
473   fake.Buffer.data = NULL;
474   /* save global variables not to be changed */
475   needheaders = NeedHeaders;
476   needoverview = NeedOverview;
477   needpath = NeedPath;
478   needstoredgroup = NeedStoredGroup;
479   needreplicdata = NeedReplicdata;
480   for (errors = 0, i = 0; (p = strings[i]) != NULL; i++) {
481     if ((error = SITEparseone(p, &fake, (char *)NULL, (char *)NULL)) != NULL) {
482       syslog(L_ERROR, "%s bad_newsfeeds %s", MaxLength(p, p), error);
483       errors++;
484     }
485     SITEfree(&fake);
486   }
487   free(strings);
488   /* restore global variables not to be changed */
489   NeedHeaders = needheaders;
490   NeedOverview = needoverview;
491   NeedPath = needpath;
492   NeedStoredGroup = needstoredgroup;
493   NeedReplicdata = needreplicdata;
494
495   if (errors == 0)
496     return NULL;
497
498   buffer_resize(&CCreply, SMBUF);
499   snprintf(CCreply.data, CCreply.size, "1 Found %d errors -- see syslog",
500            errors);
501   return CCreply.data;
502 }
503
504
505 /*
506 **  Drop a site.
507 */
508 static const char *
509 CCdrop(char *av[])
510 {
511     SITE        *sp;
512     NEWSGROUP   *ngp;
513     int         *ip;
514     int         idx;
515     int         i;
516     int         j;
517
518     if ((sp = SITEfind(av[0])) == NULL)
519         return CCnosite;
520
521     SITEdrop(sp);
522
523     /* Loop over all groups, and if the site is in a group, clobber it. */
524     for (idx = sp - Sites, i = nGroups, ngp = Groups; --i >= 0; ngp++) {
525         for (j = ngp->nSites, ip = ngp->Sites; --j >= 0; ip++)
526             if (*ip == idx)
527                 *ip = NOSITE;
528         for (j = ngp->nPoison, ip = ngp->Poison; --j >= 0; ip++)
529             if (*ip == idx)
530                 *ip = NOSITE;
531     }
532
533         return NULL;
534 }
535
536 /*
537 **  Return info on the feeds for one, or all, sites
538 */
539 static const char *
540 CCfeedinfo(char *av[])
541 {
542     SITE        *sp;
543     char        *p;
544     int         i;
545
546     buffer_set(&CCreply, "0 ", 2);
547     p = av[0];
548     if (*p != '\0') {
549         if ((sp = SITEfind(p)) == NULL)
550             return "1 No such site";
551
552         SITEinfo(&CCreply, sp, true);
553         while ((sp = SITEfindnext(p, sp)) != NULL)
554             SITEinfo(&CCreply, sp, true);
555     }
556     else
557         for (i = nSites, sp = Sites; --i >= 0; sp++)
558             if (sp->Name)
559                 SITEinfo(&CCreply, sp, false);
560
561     buffer_append(&CCreply, "", 1);
562     return CCreply.data;
563 }
564
565
566 static const char *
567 CCfilter(char *av[] UNUSED)
568 {
569 #if defined(DO_TCL)
570     char        *p;
571
572     switch (av[0][0]) {
573     default:
574         return "1 Bad flag";
575     case 'y':
576         if (TCLFilterActive)
577             return "1 tcl filter already enabled";
578         TCLfilter(true);
579         break;
580     case 'n':
581         if (!TCLFilterActive)
582             return "1 tcl filter already disabled";
583         TCLfilter(false);
584         break;
585     }
586     return NULL;
587 #else /* defined(DO_TCL) */
588     return "1 TCL filtering support not compiled in";
589 #endif /* defined(DO_TCL) */
590 }
591
592
593 static const char *
594 CCperl(char *av[])
595 {
596 #if defined(DO_PERL)
597     switch (av[0][0]) {
598     default:
599         return "1 Bad flag";
600     case 'y':
601         if (PerlFilterActive)
602             return "1 Perl filter already enabled";
603         else if (!PerlFilter(true))
604             return "1 Perl filter not defined";
605         break;
606     case 'n':
607         if (!PerlFilterActive)
608             return "1 Perl filter already disabled";
609         PerlFilter(false);
610         break;
611     }
612     return NULL;
613 #else /* defined(DO_PERL) */
614     return "1 Perl filtering support not compiled in";
615 #endif /* defined(DO_PERL) */
616 }
617
618
619 static const char *
620 CCpython(char *av[] UNUSED)
621 {
622 #if defined(DO_PYTHON)
623     return PYcontrol(av);
624 #else /* defined(DO_PYTHON) */
625     return "1 Python filtering support not compiled in";
626 #endif /* defined(DO_PYTHON) */
627 }
628
629
630 /*
631 **  Flush all sites or one site.
632 */
633 static const char *
634 CCflush(char *av[])
635 {
636     SITE        *sp;
637     int         i;
638     char        *p;
639
640     p = av[0];
641     if (*p == '\0') {
642         ICDwrite();
643         for (sp = Sites, i = nSites; --i >= 0; sp++)
644             SITEflush(sp, true);
645         syslog(L_NOTICE, "%s flush_all", LogName);
646     }
647     else  {
648         if ((sp = SITEfind(p)) == NULL)
649             return CCnosite;
650         syslog(L_NOTICE, "%s flush", sp->Name);
651         SITEflush(sp, true);
652     }
653     return NULL;
654 }
655
656
657 /*
658 **  Flush the log files.
659 */
660 static const char *
661 CCflushlogs(char *unused[])
662 {
663     unused = unused;            /* ARGSUSED */
664
665     if (Debug)
666         return "1 In debug mode";
667
668     ICDwrite();
669     syslog(L_NOTICE, "%s flushlogs %s", LogName, CCcurrmode());
670     ReopenLog(Log);
671     ReopenLog(Errlog);
672     return NULL;
673 }
674
675
676 /*
677 **  Leave paused or throttled mode.
678 */
679 static const char *
680 CCgo(char *av[])
681 {
682     static char YES[] = "y";
683     char        *p;
684
685     p = av[0];
686     if (Reservation && strcmp(p, Reservation) == 0) {
687         free(Reservation);
688         Reservation = NULL;
689     }
690     if (RejectReason && strcmp(p, RejectReason) == 0) {
691         free(RejectReason);
692         RejectReason = NULL;
693     }
694
695     if (Mode == OMrunning)
696         return "1 Already running";
697     if (*p && strcmp(p, ModeReason) != 0)
698         return "1 Wrong reason";
699
700 #if defined(DO_PERL)
701     PLmode(Mode, OMrunning, p);
702 #endif /* defined(DO_PERL) */
703 #if defined(DO_PYTHON)
704     PYmode(Mode, OMrunning, p);
705 #endif /* defined(DO_PYTHON) */
706     
707     free(ModeReason);
708     ModeReason = NULL;
709     Mode = OMrunning;
710     ThrottledbyIOError = false;
711
712     if (NNRPReason && !innconf->readerswhenstopped) {
713         av[0] = YES;
714         av[1] = p;
715         CCreaders(av);
716     }
717     if (ErrorCount < 0)
718         ErrorCount = IO_ERROR_COUNT;
719     InndHisOpen();
720     syslog(L_NOTICE, "%s running", LogName);
721     if (ICDneedsetup)
722         ICDsetup(true);
723     SCHANwakeup(&Mode);
724     return NULL;
725 }
726
727
728 /*
729 **  Hangup a channel.
730 */
731 static const char *
732 CChangup(char *av[])
733 {
734     CHANNEL     *cp;
735     int         fd;
736     char        *p;
737     int         i;
738
739     /* Parse the argument, a channel number. */
740     for (p = av[0], fd = 0; *p; p++) {
741         if (!CTYPE(isdigit, *p))
742             return "1 Bad channel number";
743         fd = fd * 10 + *p - '0';
744     }
745
746     /* Loop over all channels for the desired one. */
747     for (i = 0; (cp = CHANiter(&i, CTany)) != NULL; )
748         if (cp->fd == fd) {
749             p = CHANname(cp);
750             switch (cp->Type) {
751             default:
752                 snprintf(CCreply.data, CCreply.size, "1 Can't close %s", p);
753                 return CCreply.data;
754             case CTexploder:
755             case CTprocess:
756             case CTfile:
757             case CTnntp:
758             case CTreject:
759                 syslog(L_NOTICE, "%s hangup", p);
760                 CHANclose(cp, p);
761                 return NULL;
762             }
763         }
764     return "1 Not active";
765 }
766
767
768 /*
769 **  Return our operating mode.
770 */
771 static const char *
772 CCmode(char *unused[] UNUSED)
773 {
774     char        *p;
775     int         i;
776     int         h;
777     char        buff[BUFSIZ];
778 #if defined(DO_PERL)
779     char        *stats;
780 #endif /* defined(DO_PERL) */
781
782     /* FIXME: We assume here that BUFSIZ is >= 512, and that none of
783      * ModeReason, RejectReason, Reservation, NNRPReason, or the Perl filter
784      * statistics are longer than MAX_REASON_LEN bytes (or actually, the
785      * average of their lengths is <= MAX_REASON_LEN).  If this is not true,
786      * the sprintf's/strcpy's below are likely to overflow buff with somewhat
787      * nasty consequences...
788      */
789
790     p = buff;
791     p += strlen(strcpy(buff, "0 Server "));
792
793     /* Server's mode. */
794     switch (Mode) {
795     default:
796         sprintf(p, "Unknown %d", Mode);
797         p += strlen(p);
798         break;
799     case OMrunning:
800         p += strlen(strcpy(p, "running"));
801         break;
802     case OMpaused:
803         p += strlen(strcpy(p, "paused "));
804         p += strlen(strcpy(p, ModeReason));
805         break;
806     case OMthrottled:
807         p += strlen(strcpy(p, "throttled "));
808         p += strlen(strcpy(p, ModeReason));
809         break;
810     }
811     *p++ = '\n';
812     if (RejectReason) {
813         p += strlen(strcpy(p, "Rejecting "));
814         p += strlen(strcpy(p, RejectReason));
815     }
816     else
817         p += strlen(strcpy(p, "Allowing remote connections"));
818
819     /* Server parameters. */
820     for (i = 0, h = 0; CHANiter(&h, CTnntp) != NULL; )
821         i++;
822     *p++ = '\n';
823     sprintf(p, "Parameters c %ld i %ld (%d) l %ld o %d t %ld H %d T %d X %d %s %s",
824                   innconf->artcutoff, innconf->maxconnections, i,
825                   innconf->maxartsize, MaxOutgoing, (long)TimeOut.tv_sec,
826                   RemoteLimit, RemoteTotal, (int) RemoteTimer,
827                   innconf->xrefslave ? "slave" : "normal",
828                   AnyIncoming ? "any" : "specified");
829     p += strlen(p);
830
831     /* Reservation. */
832     *p++ = '\n';
833     if (Reservation) {
834         sprintf(p, "Reserved %s", Reservation);
835         p += strlen(p);
836     }
837     else
838         p += strlen(strcpy(p, "Not reserved"));
839
840     /* Newsreaders. */
841     *p++ = '\n';
842     p += strlen(strcpy(p, "Readers "));
843     if (innconf->readerswhenstopped)
844         p += strlen(strcpy(p, "independent "));
845     else
846         p += strlen(strcpy(p, "follow "));
847     if (NNRPReason == NULL)
848         p += strlen(strcpy(p, "enabled"));
849     else {
850         sprintf(p, "disabled %s", NNRPReason);
851         p += strlen(p);
852     }
853
854 #if defined(DO_TCL)
855     *p++ = '\n';
856     p += strlen(strcpy(p, "Tcl filtering "));
857     if (TCLFilterActive)
858         p += strlen(strcpy(p, "enabled"));
859     else
860         p += strlen(strcpy(p, "disabled"));
861 #endif /* defined(DO_TCL) */
862
863 #if defined(DO_PERL)
864     *p++ = '\n';
865     p += strlen(strcpy(p, "Perl filtering "));
866     if (PerlFilterActive)
867         p += strlen(strcpy(p, "enabled"));
868     else
869         p += strlen(strcpy(p, "disabled"));
870
871     /* Perl filter status. */
872     stats = PLstats();
873     if (stats != NULL) {
874         *p++ = '\n';
875         p += strlen(strcpy(p, "Perl filter stats: "));
876         p += strlen(strcpy(p, stats));
877         free(stats);
878     }    
879 #endif /* defined(DO_PERL) */
880
881 #if defined(DO_PYTHON)
882     *p++ = '\n';
883     p += strlen(strcpy(p, "Python filtering "));
884     if (PythonFilterActive)
885         p += strlen(strcpy(p, "enabled"));
886     else
887         p += strlen(strcpy(p, "disabled"));
888 #endif /* defined(DO_PYTHON) */
889
890     buffer_set(&CCreply, buff, strlen(buff) + 1);
891     return CCreply.data;
892 }
893
894
895 /*
896 **  Log our operating mode (via syslog).
897 */
898 static const char *
899 CClogmode(char *unused[])
900 {
901     unused = unused;            /* ARGSUSED */
902     syslog(L_NOTICE, "%s servermode %s", LogName, CCcurrmode());
903     return NULL;
904 }
905
906
907 /*
908 **  Name the channels.  ("Name the bats -- simple names.")
909 */
910 static const char *
911 CCname(char *av[])
912 {
913     static char         NL[] = "\n";
914     static char         NIL[] = "\0";
915     char                buff[SMBUF];
916     CHANNEL             *cp;
917     char                *p;
918     int                 count;
919     int                 i;
920
921     p = av[0];
922     if (*p != '\0') {
923         if ((cp = CHANfromdescriptor(atoi(p))) == NULL)
924             return xstrdup(CCnochannel);
925         snprintf(CCreply.data, CCreply.size, "0 %s", CHANname(cp));
926         return CCreply.data;
927     }
928     buffer_set(&CCreply, "0 ", 2);
929     for (count = 0, i = 0; (cp = CHANiter(&i, CTany)) != NULL; ) {
930         if (cp->Type == CTfree)
931             continue;
932         if (++count > 1)
933             buffer_append(&CCreply, NL, 1);
934         p = CHANname(cp);
935         buffer_append(&CCreply, p, strlen(p));
936         switch (cp->Type) {
937         case CTremconn:
938             sprintf(buff, ":remconn::");
939             break;
940         case CTreject:
941             sprintf(buff, ":reject::");
942             break;
943         case CTnntp:
944            sprintf(buff, ":%s:%ld:%s",
945                     cp->State == CScancel ? "cancel" : "nntp",
946                     (long) Now.time - cp->LastActive,
947                     (cp->MaxCnx > 0 && cp->ActiveCnx == 0) ? "paused" : "");
948             break;
949         case CTlocalconn:
950             sprintf(buff, ":localconn::");
951             break;
952         case CTcontrol:
953             sprintf(buff, ":control::");
954             break;
955         case CTfile:
956             sprintf(buff, "::");
957             break;
958         case CTexploder:
959             sprintf(buff, ":exploder::");
960             break;
961         case CTprocess:
962             sprintf(buff, ":");
963             break;
964         default:
965             sprintf(buff, ":unknown::");
966             break;
967         }
968         p = buff;
969         buffer_append(&CCreply, p, strlen(p));
970     }
971     buffer_append(&CCreply, NIL, 1);
972     return CCreply.data;
973 }
974
975
976 /*
977 **  Create a newsgroup.
978 */
979 static const char *
980 CCnewgroup(char *av[])
981 {
982     static char         *TIMES = NULL;
983     static char         WHEN[] = "updating active.times";
984     int                 fd;
985     char                *p;
986     NEWSGROUP           *ngp;
987     char                *Name;
988     char                *Rest;
989     const char *                who;
990     char                *buff;
991     int                 oerrno;
992     size_t              length;
993
994     if (TIMES == NULL)
995         TIMES = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
996
997     Name = av[0];
998     if (Name[0] == '.' || strspn(Name, "0123456789") == strlen(Name))
999         return "1 Illegal newsgroup name";
1000     for (p = Name; *p; p++)
1001         if (*p == '.') {
1002             if (p[1] == '.' || p[1] == '\0')
1003                 return "1 Double or trailing period in newsgroup name";
1004         }
1005         else if (ISWHITE(*p) || *p == ':' || *p == '!' || *p == '/')
1006             return "1 Illegal character in newsgroup name";
1007
1008     Rest = av[1];
1009     if (Rest[0] != NF_FLAG_ALIAS) {
1010         Rest[1] = '\0';
1011         if (CTYPE(isupper, Rest[0]))
1012             Rest[0] = tolower(Rest[0]);
1013     }
1014     if (strlen(Name) + strlen(Rest) > SMBUF - 24)
1015         return "1 Name too long";
1016
1017     if ((ngp = NGfind(Name)) != NULL)
1018         return CCdochange(ngp, Rest);
1019
1020     if (Mode == OMthrottled && ThrottledbyIOError)
1021         return "1 server throttled";
1022
1023     /* Update the log of groups created.  Don't use stdio because SunOS
1024      * 4.1 has broken libc which can't handle fd's greater than 127. */
1025     if ((fd = open(TIMES, O_WRONLY | O_APPEND | O_CREAT, 0664)) < 0) {
1026         oerrno = errno;
1027         syslog(L_ERROR, "%s cant open %s %m", LogName, TIMES);
1028         IOError(WHEN, oerrno);
1029     }
1030     else {
1031         who = av[2];
1032         if (*who == '\0')
1033             who = NEWSMASTER;
1034
1035         length = snprintf(NULL, 0, "%s %ld %s\n", Name, (long) Now.time, who) + 1;
1036         buff = xmalloc(length);
1037         snprintf(buff, length, "%s %ld %s\n", Name, (long) Now.time, who);
1038         if (xwrite(fd, buff, strlen(buff)) < 0) {
1039             oerrno = errno;
1040             syslog(L_ERROR, "%s cant write %s %m", LogName, TIMES);
1041             IOError(WHEN, oerrno);
1042         }
1043
1044         free(buff);
1045         
1046         if (close(fd) < 0) {
1047             oerrno = errno;
1048             syslog(L_ERROR, "%s cant close %s %m", LogName, TIMES);
1049             IOError(WHEN, oerrno);
1050         }
1051     }
1052
1053     /* Update the in-core data. */
1054     if (!ICDnewgroup(Name, Rest))
1055         return "1 Failed";
1056     syslog(L_NOTICE, "%s newgroup %s as %s", LogName, Name, Rest);
1057
1058     return NULL;
1059 }
1060
1061
1062 /*
1063 **  Parse and set a boolean flag.
1064 */
1065 static bool
1066 CCparsebool(char name, bool *bp, char value)
1067 {
1068     switch (value) {
1069     default:
1070         return false;
1071     case 'y':
1072         *bp = false;
1073         break;
1074     case 'n':
1075         *bp = true;
1076         break;
1077     }
1078     syslog(L_NOTICE, "%s changed -%c %c", LogName, name, value);
1079     return true;
1080 }
1081
1082
1083 /*
1084 **  Change a running parameter.
1085 */
1086 static const char *
1087 CCparam(char *av[])
1088 {
1089     static char BADVAL[] = "1 Bad value";
1090     char        *p;
1091     int         temp;
1092
1093     p = av[1];
1094     switch (av[0][0]) {
1095     default:
1096         return "1 Unknown parameter";
1097     case 'a':
1098         if (!CCparsebool('a', (bool *)&AnyIncoming, *p))
1099             return BADVAL;
1100         break;
1101     case 'c':
1102         innconf->artcutoff = atoi(p);
1103         syslog(L_NOTICE, "%s changed -c %ld", LogName, innconf->artcutoff);
1104         break;
1105     case 'i':
1106         innconf->maxconnections = atoi(p);
1107         syslog(L_NOTICE, "%s changed -i %ld", LogName, innconf->maxconnections);
1108         break;
1109     case 'l':
1110         innconf->maxartsize = atol(p);
1111         syslog(L_NOTICE, "%s changed -l %ld", LogName, innconf->maxartsize);
1112         break;
1113     case 'n':
1114         if (!CCparsebool('n', (bool *)&innconf->readerswhenstopped, *p))
1115             return BADVAL;
1116         break;
1117     case 'o':
1118         MaxOutgoing = atoi(p);
1119         syslog(L_NOTICE, "%s changed -o %d", LogName, MaxOutgoing);
1120         break;
1121     case 't':
1122         TimeOut.tv_sec = atol(p);
1123         syslog(L_NOTICE, "%s changed -t %ld", LogName, (long)TimeOut.tv_sec);
1124         break;
1125     case 'H':
1126         RemoteLimit = atoi(p);
1127         syslog(L_NOTICE, "%s changed -H %d", LogName, RemoteLimit);
1128         break;
1129     case 'T':
1130         temp = atoi(p);
1131         if (temp > REMOTETABLESIZE) {
1132             syslog(L_NOTICE, "%s -T must be lower than %d",
1133                    LogName, REMOTETABLESIZE+1);
1134             temp = REMOTETABLESIZE;
1135         }
1136         syslog(L_NOTICE, "%s changed -T from %d to %d",
1137                LogName, RemoteTotal, temp);
1138         RemoteTotal = temp;
1139         break;
1140     case 'X':
1141         RemoteTimer = (time_t) atoi(p);
1142         syslog(L_NOTICE, "%s changed -X %d", LogName, (int) RemoteTimer);
1143         break;
1144     }
1145     return NULL;
1146 }
1147
1148
1149 /*
1150 **  Common code to implement a pause or throttle.
1151 */
1152 const char *
1153 CCblock(OPERATINGMODE NewMode, char *reason)
1154 {
1155     static char         NO[] = "n";
1156     char *              av[2];
1157
1158     if (*reason == '\0')
1159         return CCnoreason;
1160
1161     if (strlen(reason) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
1162         return CCbigreason;
1163
1164     if (Reservation) {
1165         if (strcmp(reason, Reservation) != 0) {
1166             snprintf(CCreply.data, CCreply.size, "1 Reserved \"%s\"",
1167                      Reservation);
1168             return CCreply.data;
1169         }
1170         free(Reservation);
1171         Reservation = NULL;
1172     }
1173
1174 #if defined(DO_PERL)
1175     PLmode(Mode, NewMode, reason);
1176 #endif /* defined(DO_PERL) */
1177 #if defined(DO_PYTHON)
1178     PYmode(Mode, NewMode, reason);
1179 #endif /* defined(DO_PYTHON) */
1180
1181     ICDwrite();
1182     InndHisClose();
1183     Mode = NewMode;
1184     if (ModeReason)
1185         free(ModeReason);
1186     ModeReason = xstrdup(reason);
1187     if (NNRPReason == NULL && !innconf->readerswhenstopped) {
1188         av[0] = NO;
1189         av[1] = ModeReason;
1190         CCreaders(av);
1191     }
1192     syslog(L_NOTICE, "%s %s %s",
1193         LogName, NewMode == OMpaused ? "paused" : "throttled", reason);
1194     return NULL;
1195 }
1196
1197
1198 /*
1199 **  Enter paused mode.
1200 */
1201 static const char *
1202 CCpause(char *av[])
1203 {
1204     switch (Mode) {
1205     case OMrunning:
1206         return CCblock(OMpaused, av[0]);
1207     case OMpaused:
1208         return "1 Already paused";
1209     case OMthrottled:
1210         return "1 Already throttled";
1211     }
1212     return "1 Unknown mode";
1213 }
1214
1215
1216 /*
1217 **  Allow or disallow newsreaders.
1218 */
1219 static const char *
1220 CCreaders(char *av[])
1221 {
1222     const char  *p;
1223
1224     switch (av[0][0]) {
1225     default:
1226         return "1 Bad flag";
1227     case 'y':
1228         if (NNRPReason == NULL)
1229             return "1 Already allowing readers";
1230         p = av[1];
1231         if (*p && strcmp(p, NNRPReason) != 0)
1232             return "1 Wrong reason";
1233         free(NNRPReason);
1234         NNRPReason = NULL;
1235         break;
1236     case 'n':
1237         if (NNRPReason)
1238             return "1 Already not allowing readers";
1239         p = av[1];
1240         if (*p == '\0')
1241             return CCnoreason;
1242         if (strlen(p) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
1243             return CCbigreason;
1244         NNRPReason = xstrdup(p);
1245         break;
1246     }
1247     return NULL;
1248 }
1249
1250
1251 /*
1252 **  Re-exec ourselves.
1253 */
1254 static const char *
1255 CCxexec(char *av[])
1256 {
1257     char        *inndstart;
1258     char        *p;
1259     int         i;
1260
1261     if (CCargv == NULL)
1262         return "1 no argv!";
1263     
1264     inndstart = concatpath(innconf->pathbin, "inndstart");
1265     /* Get the pathname. */
1266     p = av[0];
1267     if (*p == '\0' || strcmp(p, "innd") == 0 || strcmp(p, "inndstart") == 0)
1268         CCargv[0] = inndstart;
1269     else
1270         return "1 Bad value";
1271
1272     JustCleanup();
1273     syslog(L_NOTICE, "%s execv %s", LogName, CCargv[0]);
1274
1275     /* Close all fds to protect possible fd leaking accross successive innds. */
1276     for (i=3; i<30; i++)
1277         close(i);
1278
1279     execv(CCargv[0], CCargv);
1280     syslog(L_FATAL, "%s cant execv %s %m", LogName, CCargv[0]);
1281     _exit(1);
1282     /* NOTREACHED */
1283     return "1 Exit failed";
1284 }
1285
1286 /*
1287 **  Reject remote readers.
1288 */
1289 static const char *
1290 CCreject(char *av[])
1291 {
1292     if (RejectReason)
1293         return "1 Already rejecting";
1294     if (strlen(av[0]) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
1295         return CCbigreason;
1296     RejectReason = xstrdup(av[0]);
1297     return NULL;
1298 }
1299
1300
1301 /*
1302 **  Re-read all in-core data.
1303 */
1304 static const char *
1305 CCreload(char *av[])
1306 {
1307     static char BADSCHEMA[] = "1 Can't read schema";
1308 #if defined(DO_PERL)
1309     static char BADPERLRELOAD[] = "1 Failed to define filter_art" ;
1310 #endif /* defined(DO_PERL) */
1311 #if defined(DO_PYTHON)
1312     static char BADPYRELOAD[] = "1 Failed to reload filter_innd.py" ;
1313 #endif /* defined(DO_PYTHON) */
1314     const char *p;
1315     char *path;
1316
1317     p = av[0];
1318     if (*p == '\0' || strcmp(p, "all") == 0) {
1319         SITEflushall(false);
1320         if (Mode == OMrunning)
1321             InndHisClose();
1322         RCreadlist();
1323         if (Mode == OMrunning)
1324             InndHisOpen();
1325         ICDwrite();
1326         ICDsetup(true);
1327         if (!ARTreadschema())
1328             return BADSCHEMA;
1329 #if defined(DO_TCL)
1330         TCLreadfilter();
1331 #endif /* defined(DO_TCL) */
1332 #if defined(DO_PERL)
1333         path = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_INND);
1334         PERLreadfilter(path, "filter_art") ;
1335         free(path);
1336 #endif /* defined(DO_PERL) */
1337 #if defined(DO_PYTHON)
1338         syslog(L_NOTICE, "reloading pyfilter");
1339          PYreadfilter();
1340         syslog(L_NOTICE, "reloaded pyfilter OK");
1341 #endif /* DO_PYTHON */
1342         p = "all";
1343     }
1344     else if (strcmp(p, "active") == 0 || strcmp(p, "newsfeeds") == 0) {
1345         SITEflushall(false);
1346         ICDwrite();
1347         ICDsetup(true);
1348     }
1349     else if (strcmp(p, "history") == 0) {
1350         if (Mode != OMrunning)
1351             return CCnotrunning;
1352         InndHisClose();
1353         InndHisOpen();
1354     }
1355     else if (strcmp(p, "incoming.conf") == 0)
1356         RCreadlist();
1357     else if (strcmp(p, "overview.fmt") == 0) {
1358         if (!ARTreadschema())
1359             return BADSCHEMA;
1360     }
1361 #if 0 /* we should check almost all innconf parameter, but the code
1362          is still incomplete for innd, so just commented out */
1363     else if (strcmp(p, "inn.conf") == 0) {
1364         struct innconf *saved;
1365
1366         saved = innconf;
1367         innconf = NULL;
1368         if (innconf_read(NULL))
1369             innconf_free(saved);
1370         else {
1371             innconf = saved;
1372             return "1 Reload of inn.conf failed";
1373         }
1374         if (innconf->pathhost == NULL) {
1375             syslog(L_FATAL, "%s No pathhost set", LogName);
1376             exit(1);
1377         }   
1378         free(Path.Data);
1379         Path.Used = strlen(innconf->pathhost) + 1;
1380         Path.Data = xmalloc(Path.Used + 1);
1381         sprintf(Path.Data, "%s!", innconf->pathhost);
1382         if (Pathalias.Used > 0)
1383             free(Pathalias.Data);
1384         if (innconf->pathalias == NULL) {
1385             Pathalias.Used = 0;
1386             Pathalias.Data = NULL;
1387         } else {
1388             Pathalias.Used = strlen(innconf->pathalias) + 1;
1389             Pathalias.Data = xmalloc(Pathalias.Used + 1);
1390             sprintf(Pathalias.Data, "%s!", innconf->pathalias);
1391         }
1392         if (Pathcluster.Used > 0)
1393             free(Pathcluster.Data);
1394         if (innconf->pathcluster == NULL) {
1395             Pathcluster.Used = 0;
1396             Pathcluster.Data = NULL;
1397         } else {
1398             Pathcluster.Used = strlen(innconf->pathcluster) + 1;
1399             Pathcluster.Data = xmalloc(Pathcluster.Used + 1);
1400             sprintf(Pathcluster.Data, "%s!", innconf->pathcluster);
1401         }
1402     }
1403 #endif
1404 #if defined(DO_TCL)
1405     else if (strcmp(p, "filter.tcl") == 0) {
1406         TCLreadfilter();
1407     }
1408 #endif /* defined(DO_TCL) */
1409 #if defined(DO_PERL)
1410     else if (strcmp(p, "filter.perl") == 0) {
1411         path = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_INND);
1412         if (!PERLreadfilter(path, "filter_art"))
1413             return BADPERLRELOAD;
1414     }
1415 #endif /* defined(DO_PERL) */
1416 #if defined(DO_PYTHON)
1417     else if (strcmp(p, "filter.python") == 0) {
1418         if (!PYreadfilter())
1419             return BADPYRELOAD;
1420     }
1421 #endif /* defined(DO_PYTHON) */
1422     else
1423         return "1 Unknown reload type";
1424
1425     syslog(L_NOTICE, "%s reload %s %s", LogName, p, av[1]);
1426     return NULL;
1427 }
1428
1429
1430 /*
1431 **  Renumber the active file.
1432 */
1433 static const char *
1434 CCrenumber(char *av[])
1435 {
1436     static char CANTRENUMBER[] = "1 Failed (see syslog)";
1437     char        *p;
1438     NEWSGROUP   *ngp;
1439
1440     if (Mode != OMrunning)
1441         return CCnotrunning;
1442     if (ICDneedsetup)
1443         return "1 Must first reload newsfeeds";
1444     p = av[0];
1445     if (*p) {
1446         if ((ngp = NGfind(p)) == NULL)
1447             return CCnogroup;
1448         if (!NGrenumber(ngp))
1449             return CANTRENUMBER;
1450     }
1451     else if (!ICDrenumberactive())
1452         return CANTRENUMBER;
1453     return NULL;
1454 }
1455
1456
1457 /*
1458 **  Reserve a lock.
1459 */
1460 static const char *
1461 CCreserve(char *av[])
1462 {
1463     char        *p;
1464
1465     if (Mode != OMrunning)
1466         return CCnotrunning;
1467     p = av[0];
1468     if (*p) {
1469         /* Trying to make a reservation. */
1470         if (Reservation)
1471             return "1 Already reserved";
1472         if (strlen(p) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
1473             return CCbigreason;
1474         Reservation = xstrdup(p);
1475     }
1476     else {
1477         /* Trying to remove a reservation. */
1478         if (Reservation == NULL)
1479             return "1 Not reserved";
1480         free(Reservation);
1481         Reservation = NULL;
1482     }
1483     return NULL;
1484 }
1485
1486
1487 /*
1488 **  Remove a newsgroup.
1489 */
1490 static const char *
1491 CCrmgroup(char *av[])
1492 {
1493     NEWSGROUP   *ngp;
1494
1495     if ((ngp = NGfind(av[0])) == NULL)
1496         return CCnogroup;
1497
1498     if (Mode == OMthrottled && ThrottledbyIOError)
1499         return "1 server throttled";
1500
1501     /* Update the in-core data. */
1502     if (!ICDrmgroup(ngp))
1503         return "1 Failed";
1504     syslog(L_NOTICE, "%s rmgroup %s", LogName, av[0]);
1505     return NULL;
1506 }
1507
1508
1509 /*
1510 **  Send a command line to an exploder.
1511 */
1512 static const char *
1513 CCsend(char *av[])
1514 {
1515     SITE                *sp;
1516
1517     if ((sp = SITEfind(av[0])) == NULL)
1518         return CCnosite;
1519     if (sp->Type != FTexploder)
1520         return CCwrongtype;
1521     SITEwrite(sp, av[1]);
1522     return NULL;
1523 }
1524
1525
1526 /*
1527 **  Shut down the system.
1528 */
1529 static const char *
1530 CCshutdown(char *av[])
1531 {
1532     syslog(L_NOTICE, "%s shutdown %s", LogName, av[0]);
1533     CleanupAndExit(0, av[0]);
1534     /* NOTREACHED */
1535     return "1 Exit failed";
1536 }
1537
1538
1539 /*
1540 **  Send a signal to a site's feed.
1541 */
1542 static const char *
1543 CCsignal(char *av[])
1544 {
1545     SITE        *sp;
1546     char        *p;
1547     int         s;
1548     int         oerrno;
1549
1550     /* Parse the signal. */
1551     p = av[0];
1552     if (*p == '-')
1553         p++;
1554     if (strcasecmp(p, "HUP") == 0)
1555         s = SIGHUP;
1556     else if (strcasecmp(p, "INT") == 0)
1557         s = SIGINT;
1558     else if (strcasecmp(p, "TERM") == 0)
1559         s = SIGTERM;
1560     else if ((s = atoi(p)) <= 0)
1561         return "1 Invalid signal";
1562
1563     /* Parse the site. */
1564     p = av[1];
1565     if ((sp = SITEfind(p)) == NULL)
1566         return CCnosite;
1567     if (sp->Type != FTchannel && sp->Type != FTexploder)
1568         return CCwrongtype;
1569     if (sp->Process < 0)
1570         return "1 Site has no process";
1571
1572     /* Do it. */
1573     if (kill(sp->pid, s) < 0) {
1574         oerrno = errno;
1575         syslog(L_ERROR, "%s cant kill %ld %d site %s, %m", LogName, 
1576                 (long) sp->pid, s, p);
1577         snprintf(CCreply.data, CCreply.size, "1 Can't signal process %ld, %s",
1578                 (long) sp->pid, strerror(oerrno));
1579         return CCreply.data;
1580     }
1581
1582     return NULL;
1583 }
1584
1585
1586 /*
1587 **  Enter throttled mode.
1588 */
1589 static const char *
1590 CCthrottle(char *av[])
1591 {
1592     char        *p;
1593
1594     p = av[0];
1595     switch (Mode) {
1596     case OMpaused:
1597         if (*p && strcmp(p, ModeReason) != 0)
1598             return "1 Already paused";
1599         /* FALLTHROUGH */
1600     case OMrunning:
1601         return CCblock(OMthrottled, p);
1602     case OMthrottled:
1603         return "1 Already throttled";
1604     }
1605     return "1 unknown mode";
1606 }
1607
1608 /*
1609 **  Turn on or off performance monitoring
1610 */
1611 static const char *
1612 CCtimer(char *av[])
1613 {
1614     int                 value;
1615     char                *p;
1616     
1617     if (strcmp(av[0], "off") == 0)
1618         value = 0;
1619     else {
1620         for (p = av[0]; *p; p++) {
1621             if (!CTYPE(isdigit, *p))
1622                 return "1 parameter should be a number or 'off'";
1623         }
1624         value = atoi(av[0]);
1625     }
1626     innconf->timer = value;
1627     if (innconf->timer)
1628         TMRinit(TMR_MAX);
1629     else
1630         TMRinit(0);
1631     return NULL;
1632 }
1633
1634 /*
1635 **  Log into filename some history stats
1636 */
1637 static const char *
1638 CCstathist(char *av[])
1639 {
1640     if (strcmp(av[0], "off") == 0)
1641         HISlogclose();
1642     else
1643         HISlogto(av[0]);
1644     return NULL;
1645 }
1646
1647 /*
1648 **  Turn innd status creation on or off
1649 */
1650 static const char *
1651 CCstatus(char *av[])
1652 {
1653     int                 value;
1654     char                *p;
1655     
1656     if (strcmp(av[0], "off") == 0)
1657         value = 0;
1658     else {
1659         for (p = av[0]; *p; p++) {
1660             if (!CTYPE(isdigit, *p))
1661                 return "1 parameter should be a number or 'off'";
1662         }
1663         value = atoi(av[0]);
1664     }
1665     innconf->status = value;
1666     return NULL;
1667 }
1668
1669 /*
1670 **  Add or remove tracing.
1671 */
1672 static const char *
1673 CCtrace(char *av[])
1674 {
1675     char        *p;
1676     bool        Flag;
1677     const char *        word;
1678     CHANNEL     *cp;
1679
1680     /* Parse the flag. */
1681     p = av[1];
1682     switch (p[0]) {
1683     default:                    return "1 Bad trace flag";
1684     case 'y': case 'Y':         Flag = true;    word = "on";    break;
1685     case 'n': case 'N':         Flag = false;   word = "off";   break;
1686     }
1687
1688     /* Parse what's being traced. */
1689     p = av[0];
1690     switch (*p) {
1691     default:
1692         return "1 Bad trace item";
1693     case 'i': case 'I':
1694         Tracing = Flag;
1695         syslog(L_NOTICE, "%s trace innd %s", LogName, word);
1696         break;
1697     case 'n': case 'N':
1698         NNRPTracing = Flag;
1699         syslog(L_NOTICE, "%s trace nnrpd %s", LogName, word);
1700         break;
1701     case '0': case '1': case '2': case '3': case '4':
1702     case '5': case '6': case '7': case '8': case '9':
1703         if ((cp = CHANfromdescriptor(atoi(p))) == NULL)
1704             return CCnochannel;
1705         CHANtracing(cp, Flag);
1706         break;
1707     }
1708     return NULL;
1709 }
1710
1711 \f
1712
1713 /*
1714 **  Split up the text into fields and stuff them in argv.  Return the
1715 **  number of elements or -1 on error.
1716 */
1717 static int
1718 CCargsplit(char *p, char *end, char **argv, int size)
1719 {
1720     char                **save;
1721
1722     for (save = argv, *argv++ = p, size--; p < end; p++)
1723         if (*p == SC_SEP) {
1724             if (--size <= 0)
1725                 return -1;
1726             *p = '\0';
1727             *argv++ = p + 1;
1728         }
1729     *argv = NULL;
1730     return argv - save;
1731 }
1732
1733
1734 /*
1735 **  Read function.  Read and process the message.
1736 */
1737 static void
1738 CCreader(CHANNEL *cp)
1739 {
1740     static char         TOOLONG[] = "0 Reply too long for server to send";
1741     CCDISPATCH          *dp;
1742     const char *        p;
1743     char                *q;
1744     ICC_MSGLENTYPE      bufflen;
1745     ICC_PROTOCOLTYPE    protocol ;
1746 #if     defined(HAVE_UNIX_DOMAIN_SOCKETS)
1747     struct sockaddr_un  client;
1748 #else
1749     int                 written;
1750 #endif  /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
1751     int                 i;
1752     char                buff[BIG_BUFFER + 2];
1753     char                copy[BIG_BUFFER + 2];
1754     char                *argv[SC_MAXFIELDS + 2];
1755     int                 argc;
1756     int                 len;
1757     char                *tbuff ;
1758
1759     if (cp != CCchan) {
1760         syslog(L_ERROR, "%s internal CCreader wrong channel 0x%p not 0x%p",
1761             LogName, (void *)cp, (void *)CCchan);
1762         return;
1763     }
1764
1765 #if defined (HAVE_UNIX_DOMAIN_SOCKETS)
1766     
1767     i = RECVorREAD(CCchan->fd, buff, BIG_BUFFER) ;
1768     if (i < 0) {
1769         syslog(L_ERROR, "%s cant recv CCreader %m", LogName);
1770         return;
1771     } else if (i == 0) {
1772         syslog(L_ERROR, "%s cant recv CCreader empty", LogName);
1773         return;
1774     } else if (i < (int)HEADER_SIZE) {
1775         syslog(L_ERROR, "%s cant recv CCreader header-length %m", LogName);
1776         return;
1777     }
1778
1779     memcpy (&protocol,buff,sizeof (protocol)) ;
1780     memcpy (&bufflen,buff + sizeof (protocol),sizeof (bufflen)) ;
1781     bufflen = ntohs (bufflen) ;
1782     
1783     if (i != bufflen) {
1784         syslog(L_ERROR, "%s cant recv CCreader short-read %m", LogName);
1785         return;
1786     }
1787
1788     i -= HEADER_SIZE ;
1789     memmove (buff,buff + HEADER_SIZE,i) ;
1790     buff[i] = '\0';
1791
1792     if (protocol != ICC_PROTOCOL_1) {
1793         syslog(L_ERROR, "%s CCreader protocol mismatch", LogName) ;
1794         return ;
1795     }
1796
1797 #else  /* defined (HAVE_UNIX_DOMAIN_SOCKETS) */
1798
1799     i = RECVorREAD(CCchan->fd, buff, HEADER_SIZE) ;
1800     if (i < 0) {
1801         syslog(L_ERROR, "%s cant read CCreader header %m", LogName);
1802         return;
1803     } else if (i == 0) {
1804         syslog(L_ERROR, "%s cant read CCreader header empty", LogName);
1805         return;
1806     } else if (i != HEADER_SIZE) {
1807         syslog(L_ERROR, "%s cant read CCreader header-length %m", LogName);
1808         return;
1809     }
1810
1811     memcpy (&protocol,buff,sizeof (protocol)) ;
1812     memcpy (&bufflen,buff + sizeof (protocol),sizeof (bufflen)) ;
1813     bufflen = ntohs (bufflen);
1814     if (bufflen < HEADER_SIZE || bufflen > BIG_BUFFER) {
1815         syslog(L_ERROR, "%s cant read CCreader bad length", LogName);
1816         return;
1817     }
1818     bufflen -= HEADER_SIZE ;
1819     
1820     i = RECVorREAD(CCchan->fd, buff, bufflen) ;
1821
1822     if (i < 0) {
1823         syslog(L_ERROR, "%s cant read CCreader buffer %m", LogName);
1824         return;
1825     } else if (i == 0) {
1826         syslog(L_ERROR, "%s cant read CCreader buffer empty", LogName);
1827         return;
1828     } else if (i != bufflen) {
1829         syslog(L_ERROR, "%s cant read CCreader buffer-length %m", LogName);
1830         return;
1831     }
1832
1833     buff[i] = '\0';
1834
1835     if (protocol != ICC_PROTOCOL_1) {
1836         syslog(L_ERROR, "%s CCreader protocol mismatch", LogName) ;
1837         return ;
1838     }
1839
1840 #endif /* defined (HAVE_UNIX_DOMAIN_SOCKETS) */
1841     
1842     /* Copy to a printable buffer, and log. */
1843     strcpy(copy, buff);
1844     for (p = NULL, q = copy; *q; q++)
1845         if (*q == SC_SEP) {
1846             *q = ':';
1847             if (p == NULL)
1848                 p = q + 1;
1849         }
1850     syslog(L_CC_CMD, "%s", p ? p : copy);
1851
1852     /* Split up the fields, get the command letter. */
1853     if ((argc = CCargsplit(buff, &buff[i], argv, ARRAY_SIZE(argv))) < 2
1854      || argc == ARRAY_SIZE(argv)) {
1855         syslog(L_ERROR, "%s bad_fields CCreader", LogName);
1856         return;
1857     }
1858     p = argv[1];
1859     i = *p;
1860
1861     /* Dispatch to the command function. */
1862     for (argc -= 2, dp = CCcommands; dp < ARRAY_END(CCcommands); dp++)
1863         if (i == dp->Name) {
1864             if (argc != dp->argc)
1865                 p = "1 Wrong number of parameters";
1866             else
1867                 p = (*dp->Function)(&argv[2]);
1868             break;
1869         }
1870     if (dp == ARRAY_END(CCcommands)) {
1871         syslog(L_NOTICE, "%s bad_message %c", LogName, i);
1872         p = "1 Bad command";
1873     }
1874     else if (p == NULL)
1875         p = "0 Ok";
1876
1877     /* Build the reply address and send the reply. */
1878     len = strlen(p) + HEADER_SIZE ;
1879     tbuff = xmalloc(len + 1);
1880     
1881     protocol = ICC_PROTOCOL_1 ;
1882     memcpy (tbuff,&protocol,sizeof (protocol)) ;
1883     tbuff += sizeof (protocol) ;
1884     
1885     bufflen = htons (len) ;
1886     memcpy (tbuff,&bufflen,sizeof (bufflen)) ;
1887     tbuff += sizeof (bufflen) ;
1888
1889     strcpy (tbuff,p) ;
1890     tbuff -= HEADER_SIZE ;
1891
1892 #if     defined(HAVE_UNIX_DOMAIN_SOCKETS)
1893     memset(&client, 0, sizeof client);
1894     client.sun_family = AF_UNIX;
1895     strcpy(client.sun_path, argv[0]);
1896     if (sendto(CCwriter, tbuff, len, 0,
1897             (struct sockaddr *) &client, SUN_LEN(&client)) < 0) {
1898         i = errno;
1899         syslog(i == ENOENT ? L_NOTICE : L_ERROR,
1900             "%s cant sendto CCreader bytes %d %m", LogName, len);
1901         if (i == EMSGSIZE)
1902             sendto(CCwriter, TOOLONG, strlen(TOOLONG), 0,
1903                 (struct sockaddr *) &client, SUN_LEN(&client));
1904     }
1905 #else
1906     if ((i = open(argv[0], O_WRONLY | O_NDELAY)) < 0)
1907         syslog(L_ERROR, "%s cant open %s %m", LogName, argv[0]);
1908     else {
1909         if ((written = write(i, tbuff, len)) != len)
1910             if (written < 0)
1911                 syslog(L_ERROR, "%s cant write %s %m", LogName, argv[0]);
1912             else
1913                 syslog(L_ERROR, "%s cant write %s", LogName, argv[0]);
1914         if (close(i) < 0)
1915             syslog(L_ERROR, "%s cant close %s %m", LogName, argv[0]);
1916     }
1917 #endif  /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
1918     free (tbuff) ;
1919 }
1920
1921
1922 /*
1923 **  Called when a write-in-progress is done on the channel.  Shouldn't happen.
1924 */
1925 static void
1926 CCwritedone(CHANNEL *unused)
1927 {
1928     unused = unused;            /* ARGSUSED */
1929     syslog(L_ERROR, "%s internal CCwritedone", LogName);
1930 }
1931
1932
1933 /*
1934 **  Create the channel.
1935 */
1936 void
1937 CCsetup(void)
1938 {
1939     int                 i;
1940 #if     defined(HAVE_UNIX_DOMAIN_SOCKETS)
1941     struct sockaddr_un  server;
1942     int size = 65535;
1943 #endif  /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
1944
1945     if (CCpath == NULL)
1946         CCpath = concatpath(innconf->pathrun, _PATH_NEWSCONTROL);
1947     /* Remove old detritus. */
1948     if (unlink(CCpath) < 0 && errno != ENOENT) {
1949         syslog(L_FATAL, "%s cant unlink %s %m", LogName, CCpath);
1950         exit(1);
1951     }
1952
1953 #if     defined(HAVE_UNIX_DOMAIN_SOCKETS)
1954     /* Create a socket and name it. */
1955     if ((i = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
1956         syslog(L_FATAL, "%s cant socket %s %m", LogName, CCpath);
1957         exit(1);
1958     }
1959     memset(&server, 0, sizeof server);
1960     server.sun_family = AF_UNIX;
1961     strcpy(server.sun_path, CCpath);
1962     if (bind(i, (struct sockaddr *) &server, SUN_LEN(&server)) < 0) {
1963         syslog(L_FATAL, "%s cant bind %s %m", LogName, CCpath);
1964         exit(1);
1965     }
1966
1967     /* Create an unbound socket to reply on. */
1968     if ((CCwriter = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
1969         syslog(L_FATAL, "%s cant socket unbound %m", LogName);
1970         exit(1);
1971     }
1972
1973     /* Increase the buffer size for the Unix domain socket */
1974     if (setsockopt(CCwriter, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) < 0)
1975         syslog(L_ERROR, "%s cant setsockopt %m", LogName);
1976 #else
1977     /* Create a named pipe and open it. */
1978     if (mkfifo(CCpath, 0666) < 0) {
1979         syslog(L_FATAL, "%s cant mkfifo %s %m", LogName, CCpath);
1980         exit(1);
1981     }
1982     if ((i = open(CCpath, O_RDWR)) < 0) {
1983         syslog(L_FATAL, "%s cant open %s %m", LogName, CCpath);
1984         exit(1);
1985     }
1986 #endif  /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
1987
1988     CCchan = CHANcreate(i, CTcontrol, CSwaiting, CCreader, CCwritedone);
1989     syslog(L_NOTICE, "%s ccsetup %s", LogName, CHANname(CCchan));
1990     RCHANadd(CCchan);
1991
1992     buffer_resize(&CCreply, SMBUF);
1993
1994     /*
1995      *  Catch SIGUSR1 so that we can recreate the control channel when
1996      *  needed (i.e. something has deleted our named socket.
1997      */
1998 #if     defined(SIGUSR1)
1999     xsignal(SIGUSR1, CCresetup);
2000 #endif  /* defined(SIGUSR1) */
2001 }
2002
2003
2004 /*
2005 **  Cleanly shut down the channel.
2006 */
2007 void
2008 CCclose(void)
2009 {
2010     CHANclose(CCchan, CHANname(CCchan));
2011     CCchan = NULL;
2012     if (unlink(CCpath) < 0)
2013         syslog(L_ERROR, "%s cant unlink %s %m", LogName, CCpath);
2014     free(CCpath);
2015     CCpath = NULL;
2016     free(CCreply.data);
2017     CCreply.data = NULL;
2018     CCreply.size = 0;
2019     CCreply.used = 0;
2020     CCreply.left = 0;
2021 #if     defined(HAVE_UNIX_DOMAIN_SOCKETS)
2022     if (close(CCwriter) < 0)
2023         syslog(L_ERROR, "%s cant close unbound %m", LogName);
2024 #endif  /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
2025 }
2026
2027
2028 /*
2029 **  Restablish the control channel.
2030 */
2031 static RETSIGTYPE
2032 CCresetup(int unused)
2033 {
2034 #ifndef HAVE_SIGACTION
2035     xsignal(s, CCresetup);
2036 #else
2037     unused = unused;            /* ARGSUSED */
2038 #endif
2039     CCclose();
2040     CCsetup();
2041 }
2042
2043
2044 /*
2045  * Read a file containing lines of the form "newsgroup lowmark",
2046  * and reset the low article number for the specified groups.
2047  */
2048 static const char *
2049 CClowmark(char *av[])
2050 {
2051     long lo;
2052     char *line, *cp;
2053     const char  *ret = NULL;
2054     QIOSTATE *qp;
2055     NEWSGROUP *ngp;
2056
2057     if (Mode != OMrunning)
2058         return CCnotrunning;
2059     if (ICDneedsetup)
2060         return "1 Must first reload newsfeeds";
2061     if ((qp = QIOopen(av[0])) == NULL) {
2062         syslog(L_ERROR, "%s cant open %s %m", LogName, av[0]);
2063         return "1 Cannot read input file";
2064     }
2065     while ((line = QIOread(qp)) != NULL) {
2066         if (QIOerror(qp))
2067                 break;
2068         if (QIOtoolong(qp)) {
2069             ret = "1 Malformed input line (too long)";
2070             break;
2071         }
2072         while (ISWHITE(*line))
2073             line++;
2074         for (cp = line; *cp && !ISWHITE(*cp); cp++)
2075             ;
2076         if (*cp == '\0') {
2077             ret = "1 Malformed input line (only one field)";
2078             break;
2079         }
2080         *cp++ = '\0';
2081         while (ISWHITE(*cp))
2082             cp++;
2083         if (strspn(cp, "0123456789") != strlen(cp)) {
2084             ret = "1 Malformed input line (non-digit in low mark)";
2085             break;
2086         }
2087         if ((lo = atol(cp)) == 0 && (cp[0] != '0' || cp[1] != '\0')) {
2088             ret = "1 Malformed input line (bad low mark)";
2089             break;
2090         }
2091         if ((ngp = NGfind(line)) == NULL) {
2092             /* ret = CCnogroup; break; */
2093             continue;
2094         }
2095         if (!NGlowmark(ngp, lo)) {
2096             ret = "1 Cannot set low mark - see syslog";
2097             break;
2098         }
2099     }
2100     if (ret == NULL && QIOerror(qp)) {
2101         syslog(L_ERROR, "%s cant read %s %m", LogName, av[0]);
2102         ret = "1 Error reading input file";
2103     }
2104     QIOclose(qp);
2105     ICDwrite();
2106     return ret;
2107 }