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