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