1 /*
2 **  Routines to implement site-feeding.  Mainly working with channels to
3 **  do buffering and determine what to send.
4 */
5 
6 #include "portable/system.h"
7 
8 #include "inn/fdflag.h"
9 #include "inn/innconf.h"
10 #include "innd.h"
11 
12 
13 static int SITEcount;
14 static int SITEhead = NOSITE;
15 static int SITEtail = NOSITE;
16 static char SITEshell[] = "/bin/sh";
17 
18 
19 /*
20 **  Called when input is ready to read.  Shouldn't happen.
21 */
22 static void
SITEreader(CHANNEL * cp)23 SITEreader(CHANNEL *cp)
24 {
25     syslog(L_ERROR, "%s internal SITEreader: %s", LogName, CHANname(cp));
26 }
27 
28 
29 /*
30 **  Called when write is done.  No-op.
31 */
32 static void
SITEwritedone(CHANNEL * cp UNUSED)33 SITEwritedone(CHANNEL *cp UNUSED)
34 {
35 }
36 
37 
38 /*
39 **  Make a site start spooling.
40 */
41 static bool
SITEspool(SITE * sp,CHANNEL * cp)42 SITEspool(SITE *sp, CHANNEL *cp)
43 {
44     int i;
45     char *name;
46     char *togo = NULL;
47 
48     name = sp->SpoolName;
49     i = open(name, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
50     if (i < 0 && errno == EISDIR) {
51         togo = concatpath(sp->SpoolName, "togo");
52         name = togo;
53         i = open(name, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
54     }
55     if (i < 0) {
56         i = errno;
57         syslog(L_ERROR, "%s cant open %s %m", sp->Name, name);
58         IOError("site batch file", i);
59         sp->Channel = NULL;
60         if (togo != NULL)
61             free(togo);
62         return false;
63     }
64     if (togo != NULL)
65         free(togo);
66     if (cp != NULL) {
67         /* Don't log a message here; it spewed into syslog. */
68         if (cp->fd >= 0) {
69             WCHANremove(cp);
70             RCHANremove(cp);
71             SCHANremove(cp);
72             close(cp->fd);
73         }
74         cp->fd = i;
75         return true;
76     }
77     sp->Channel = CHANcreate(i, CTfile, CSwriting, SITEreader, SITEwritedone);
78     if (sp->Channel == NULL) {
79         syslog(L_ERROR, "%s cant channel %m", sp->Name);
80         close(i);
81         return false;
82     }
83     WCHANset(sp->Channel, "", 0);
84     sp->Spooling = true;
85     return true;
86 }
87 
88 
89 /*
90 **  Delete a site from the file writing list.  Can be called even if
91 **  site is not on the list.
92 */
93 static void
SITEunlink(SITE * sp)94 SITEunlink(SITE *sp)
95 {
96     if (sp->Next != NOSITE || sp->Prev != NOSITE
97         || (SITEhead != NOSITE && sp == &Sites[SITEhead]))
98         SITEcount--;
99 
100     if (sp->Next != NOSITE)
101         Sites[sp->Next].Prev = sp->Prev;
102     else if (SITEtail != NOSITE && sp == &Sites[SITEtail])
103         SITEtail = sp->Prev;
104 
105     if (sp->Prev != NOSITE)
106         Sites[sp->Prev].Next = sp->Next;
107     else if (SITEhead != NOSITE && sp == &Sites[SITEhead])
108         SITEhead = sp->Next;
109 
110     sp->Next = sp->Prev = NOSITE;
111 }
112 
113 
114 /*
115 **  Find the oldest "file feed" site and buffer it.
116 */
117 static void
SITEbufferoldest(void)118 SITEbufferoldest(void)
119 {
120     SITE *sp;
121     struct buffer *bp;
122     struct buffer *out;
123 
124     /* Get the oldest user of a file. */
125     if (SITEtail == NOSITE) {
126         syslog(L_ERROR, "%s internal no oldest site found", LogName);
127         SITEcount = 0;
128         return;
129     }
130 
131     sp = &Sites[SITEtail];
132     SITEunlink(sp);
133 
134     if (sp->Buffered) {
135         syslog(L_ERROR, "%s internal oldest (%s) was buffered", LogName,
136                sp->Name);
137         return;
138     }
139 
140     if (sp->Type != FTfile) {
141         syslog(L_ERROR, "%s internal oldest (%s) not FTfile", LogName,
142                sp->Name);
143         return;
144     }
145 
146     /* Write out what we can. */
147     WCHANflush(sp->Channel);
148 
149     /* Get a buffer for the site. */
150     sp->Buffered = true;
151     bp = &sp->Buffer;
152     bp->used = 0;
153     bp->left = 0;
154     if (bp->size == 0) {
155         bp->size = sp->Flushpoint;
156         bp->data = xmalloc(bp->size);
157     } else {
158         bp->size = sp->Flushpoint;
159         bp->data = xrealloc(bp->data, bp->size);
160     }
161 
162     /* If there's any unwritten data, copy it. */
163     out = &sp->Channel->Out;
164     if (out->left) {
165         buffer_set(bp, &out->data[out->used], out->left);
166         out->left = 0;
167     }
168 
169     /* Now close the original channel. */
170     CHANclose(sp->Channel, sp->Name);
171     sp->Channel = NULL;
172 }
173 
174 /*
175  * *  Bilge Site's Channel out buffer.
176  */
177 static bool
SITECHANbilge(SITE * sp)178 SITECHANbilge(SITE *sp)
179 {
180     int fd;
181     int i;
182     char *name;
183     char *togo = NULL;
184 
185     name = sp->SpoolName;
186     fd = open(name, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
187     if (fd < 0 && errno == EISDIR) {
188         togo = concatpath(sp->SpoolName, "togo");
189         name = togo;
190         fd = open(togo, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
191     }
192     if (fd < 0) {
193         int oerrno = errno;
194         syslog(L_ERROR, "%s cant open %s %m", sp->Name, name);
195         IOError("site batch file", oerrno);
196         sp->Channel = NULL;
197         if (togo != NULL)
198             free(togo);
199         return false;
200     }
201     if (togo != NULL)
202         free(togo);
203     while (sp->Channel->Out.left > 0) {
204         i = write(fd, &sp->Channel->Out.data[sp->Channel->Out.used],
205                   sp->Channel->Out.left);
206         if (i <= 0) {
207             syslog(L_ERROR, "%s cant spool count %lu", CHANname(sp->Channel),
208                    (unsigned long) sp->Channel->Out.left);
209             close(fd);
210             return false;
211         }
212         sp->Channel->Out.left -= i;
213         sp->Channel->Out.used += i;
214     }
215     close(fd);
216     free(sp->Channel->Out.data);
217     sp->Channel->Out.data = xmalloc(SMBUF);
218     sp->Channel->Out.size = SMBUF;
219     sp->Channel->Out.left = 0;
220     sp->Channel->Out.used = 0;
221     return true;
222 }
223 
224 /*
225 **  Check if we need to write out the site's buffer.  If we're buffered
226 **  or the feed is backed up, this gets a bit complicated.
227 */
228 static void
SITEflushcheck(SITE * sp,struct buffer * bp)229 SITEflushcheck(SITE *sp, struct buffer *bp)
230 {
231     int i;
232     CHANNEL *cp;
233 
234     /* If we're buffered, and we hit the flushpoint, do an LRU. */
235     if (sp->Buffered) {
236         if (bp->left < sp->Flushpoint)
237             return;
238         while (SITEcount >= MaxOutgoing)
239             SITEbufferoldest();
240         if (!SITEsetup(sp) || sp->Buffered) {
241             syslog(L_ERROR, "%s cant unbuffer %m", sp->Name);
242             return;
243         }
244         WCHANsetfrombuffer(sp->Channel, bp);
245         WCHANadd(sp->Channel);
246         /* Reset buffer; data has been moved. */
247         buffer_set(bp, "", 0);
248     }
249 
250     if (PROCneedscan)
251         PROCscan();
252 
253     /* Handle buffering. */
254     cp = sp->Channel;
255     i = cp->Out.left;
256     if (i < sp->StopWriting)
257         WCHANremove(cp);
258     if ((sp->StartWriting == 0 || i > sp->StartWriting) && !CHANsleeping(cp)) {
259         if (sp->Type == FTchannel) { /* channel feed, try the write */
260             int j;
261             if (bp->left == 0)
262                 return;
263             j = write(cp->fd, &bp->data[bp->used], bp->left);
264             if (j > 0) {
265                 bp->left -= j;
266                 bp->used += j;
267                 i = cp->Out.left;
268                 /* Since we just had a successful write, we need to clear the
269                  * channel's error counts. - dave@jetcafe.org */
270                 cp->BadWrites = 0;
271                 cp->BlockedWrites = 0;
272             }
273             if (bp->left <= 0) {
274                 /* reset Used, Left on bp, keep channel buffer size from
275                    exploding. */
276                 bp->used = bp->left = 0;
277                 WCHANremove(cp);
278             } else
279                 WCHANadd(cp);
280         } else
281             WCHANadd(cp);
282     }
283 
284     cp->LastActive = Now.tv_sec;
285 
286     /* If we're a channel that's getting big, see if we need to spool. */
287     if (sp->Type == FTfile || sp->StartSpooling == 0 || i < sp->StartSpooling)
288         return;
289 
290     syslog(L_ERROR, "%s spooling %d bytes", sp->Name, i);
291     if (!SITECHANbilge(sp)) {
292         syslog(L_ERROR, "%s overflow %d bytes", sp->Name, i);
293         return;
294     }
295 }
296 
297 
298 /*
299 **  Send a control line to an exploder.
300 */
301 void
SITEwrite(SITE * sp,const char * text)302 SITEwrite(SITE *sp, const char *text)
303 {
304     static char PREFIX[] = {EXP_CONTROL, '\0'};
305     struct buffer *bp;
306 
307     if (sp->Buffered)
308         bp = &sp->Buffer;
309     else {
310         if (sp->Channel == NULL)
311             return;
312         sp->Channel->LastActive = Now.tv_sec;
313         bp = &sp->Channel->Out;
314     }
315     buffer_append(bp, PREFIX, strlen(PREFIX));
316     buffer_append(bp, text, strlen(text));
317     buffer_append(bp, "\n", 1);
318     if (sp->Channel != NULL)
319         WCHANadd(sp->Channel);
320 }
321 
322 
323 /*
324 **  Send the desired data about an article down a channel.
325 */
326 static void
SITEwritefromflags(SITE * sp,ARTDATA * Data)327 SITEwritefromflags(SITE *sp, ARTDATA *Data)
328 {
329     HDRCONTENT *hc = Data->HdrContent;
330     static char ITEMSEP[] = " ";
331     static char NL[] = "\n";
332     char pbuff[32];
333     char *p;
334     bool Dirty;
335     struct buffer *bp;
336     SITE *spx;
337     int i;
338 
339     if (sp->Buffered)
340         bp = &sp->Buffer;
341     else {
342         /* This should not happen, but if we tried to spool and failed,
343          * e.g., because of a bad F param for this site, we can get
344          * into this state.  We already logged a message so give up. */
345         if (sp->Channel == NULL)
346             return;
347         bp = &sp->Channel->Out;
348     }
349     for (Dirty = false, p = sp->FileFlags; *p; p++) {
350         switch (*p) {
351         default:
352             syslog(L_ERROR, "%s internal SITEwritefromflags %c", sp->Name, *p);
353             continue;
354         case FEED_BYTESIZE:
355             if (Dirty)
356                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
357             buffer_append(bp, Data->Bytes + sizeof("Bytes: ") - 1,
358                           Data->BytesLength);
359             break;
360         case FEED_FULLNAME:
361         case FEED_NAME:
362             if (Dirty)
363                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
364             buffer_append(bp, Data->TokenText, sizeof(TOKEN) * 2 + 2);
365             break;
366         case FEED_HASH:
367             if (Dirty)
368                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
369             buffer_append(bp, "[", 1);
370             buffer_append(bp, HashToText(*(Data->Hash)), sizeof(HASH) * 2);
371             buffer_append(bp, "]", 1);
372             break;
373         case FEED_HDR_DISTRIB:
374             if (Dirty)
375                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
376             buffer_append(bp, HDR(HDR__DISTRIBUTION),
377                           HDR_LEN(HDR__DISTRIBUTION));
378             break;
379         case FEED_HDR_NEWSGROUP:
380             if (Dirty)
381                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
382             buffer_append(bp, HDR(HDR__NEWSGROUPS), HDR_LEN(HDR__NEWSGROUPS));
383             break;
384         case FEED_HEADERS:
385             if (Dirty)
386                 buffer_append(bp, NL, strlen(NL));
387             buffer_append(bp, Data->Headers.data, Data->Headers.left);
388             break;
389         case FEED_OVERVIEW:
390             if (Dirty)
391                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
392             buffer_append(bp, Data->Overview.data, Data->Overview.left);
393             break;
394         case FEED_PATH:
395             if (Dirty)
396                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
397             if (!Data->Hassamepath || Data->AddAlias || Pathcluster.used) {
398                 if (Pathcluster.used)
399                     buffer_append(bp, Pathcluster.data, Pathcluster.used);
400                 buffer_append(bp, Path.data, Path.used);
401                 if (Data->AddAlias)
402                     buffer_append(bp, Pathalias.data, Pathalias.used);
403             }
404             if (Data->Hassamecluster)
405                 buffer_append(bp, HDR(HDR__PATH) + Pathcluster.used,
406                               HDR_LEN(HDR__PATH) - Pathcluster.used);
407             else
408                 buffer_append(bp, HDR(HDR__PATH), HDR_LEN(HDR__PATH));
409             break;
410         case FEED_REPLIC:
411             if (Dirty)
412                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
413             buffer_append(bp, Data->Replic, Data->ReplicLength);
414             break;
415         case FEED_STOREDGROUP:
416             if (Dirty)
417                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
418             buffer_append(bp, Data->Newsgroups.List[0],
419                           Data->StoredGroupLength);
420             break;
421         case FEED_TIMERECEIVED:
422             if (Dirty)
423                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
424             snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Arrived);
425             buffer_append(bp, pbuff, strlen(pbuff));
426             break;
427         case FEED_TIMEPOSTED:
428             if (Dirty)
429                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
430             snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Posted);
431             buffer_append(bp, pbuff, strlen(pbuff));
432             break;
433         case FEED_TIMEEXPIRED:
434             if (Dirty)
435                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
436             snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Expires);
437             buffer_append(bp, pbuff, strlen(pbuff));
438             break;
439         case FEED_MESSAGEID:
440             if (Dirty)
441                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
442             buffer_append(bp, HDR(HDR__MESSAGE_ID), HDR_LEN(HDR__MESSAGE_ID));
443             break;
444         case FEED_FNLNAMES:
445             if (sp->FNLnames.left != 0) {
446                 /* Funnel; write names of our sites that got it. */
447                 if (Dirty)
448                     buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
449                 buffer_append(bp, sp->FNLnames.data, sp->FNLnames.left);
450             } else {
451                 /* Not funnel; write names of all sites that got it. */
452                 for (spx = Sites, i = nSites; --i >= 0; spx++)
453                     if (spx->Sendit) {
454                         if (Dirty)
455                             buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
456                         buffer_append(bp, spx->Name, spx->NameLength);
457                         Dirty = true;
458                     }
459             }
460             break;
461         case FEED_NEWSGROUP:
462             if (Dirty)
463                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
464             if (sp->ng)
465                 buffer_append(bp, sp->ng->Name, sp->ng->NameLength);
466             else
467                 buffer_append(bp, "?", 1);
468             break;
469         case FEED_SITE:
470             if (Dirty)
471                 buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
472             buffer_append(bp, Data->Feedsite, Data->FeedsiteLength);
473             break;
474         }
475         Dirty = true;
476     }
477     if (Dirty) {
478         buffer_append(bp, "\n", 1);
479         SITEflushcheck(sp, bp);
480     }
481 }
482 
483 
484 /*
485 **  Send one article to a site.
486 */
487 void
SITEsend(SITE * sp,ARTDATA * Data)488 SITEsend(SITE *sp, ARTDATA *Data)
489 {
490     int i;
491     char *p;
492     char *temp;
493     char buff[BUFSIZ];
494     char *argv[MAX_BUILTIN_ARGV];
495 
496     switch (sp->Type) {
497     default:
498         syslog(L_ERROR, "%s internal SITEsend type %d", sp->Name, sp->Type);
499         break;
500     case FTlogonly:
501         break;
502     case FTfunnel:
503         syslog(L_ERROR, "%s funnel_send", sp->Name);
504         break;
505     case FTfile:
506     case FTchannel:
507     case FTexploder:
508         SITEwritefromflags(sp, Data);
509         break;
510     case FTprogram:
511         /* Set up the argument vector. */
512         if (sp->FNLwantsnames) {
513             i = strlen(sp->Param) + sp->FNLnames.left;
514             if (i + (sizeof(TOKEN) * 2) + 3 >= sizeof buff) {
515                 syslog(L_ERROR, "%s toolong need %lu for %s", sp->Name,
516                        (unsigned long) (i + (sizeof(TOKEN) * 2) + 3),
517                        sp->Name);
518                 break;
519             }
520             p = strchr(sp->Param, '*');
521             *p = '\0';
522             xasprintf(&temp, "%s%.*s%s", sp->Param, (int) sp->FNLnames.left,
523                       sp->FNLnames.data, &p[1]);
524             *p = '*';
525 #if __GNUC__ > 4 || defined(__llvm__) || defined(__clang__)
526 #    pragma GCC diagnostic ignored "-Wformat-nonliteral"
527 #endif
528             snprintf(buff, sizeof(buff), temp, Data->TokenText);
529 #if __GNUC__ > 4 || defined(__llvm__) || defined(__clang__)
530 #    pragma GCC diagnostic warning "-Wformat-nonliteral"
531 #endif
532             free(temp);
533         } else {
534 #if __GNUC__ > 4 || defined(__llvm__) || defined(__clang__)
535 #    pragma GCC diagnostic ignored "-Wformat-nonliteral"
536 #endif
537             snprintf(buff, sizeof(buff), sp->Param, Data->TokenText);
538 #if __GNUC__ > 4 || defined(__llvm__) || defined(__clang__)
539 #    pragma GCC diagnostic warning "-Wformat-nonliteral"
540 #endif
541         }
542 
543         if (NeedShell(buff, (const char **) argv,
544                       (const char **) ARRAY_END(argv))) {
545             argv[0] = SITEshell;
546             argv[1] = (char *) "-c";
547             argv[2] = buff;
548             argv[3] = NULL;
549         }
550 
551         /* Start the process. */
552         i = Spawn(sp->Nice, 0, fileno(Errlog), fileno(Errlog), argv);
553         if (i >= 0)
554             PROCwatch(i, -1);
555         break;
556     }
557 }
558 
559 
560 /*
561 **  The channel was sleeping because we had to spool our output to
562 **  a file.  Flush and restart.
563 */
564 static void
SITEspoolwake(CHANNEL * cp)565 SITEspoolwake(CHANNEL *cp)
566 {
567     SITE *sp;
568     int *ip;
569 
570     ip = (int *) cp->Argument;
571     sp = &Sites[*ip];
572     free(cp->Argument);
573     cp->Argument = NULL;
574     if (sp->Channel != cp) {
575         syslog(L_ERROR, "%s internal SITEspoolwake %s got %d, not %d", LogName,
576                sp->Name, cp->fd, sp->Channel->fd);
577         return;
578     }
579     syslog(L_NOTICE, "%s spoolwake", sp->Name);
580     SITEflush(sp, true);
581 }
582 
583 
584 /*
585 **  Start up a process for a channel, or a spool to a file if we can't.
586 **  Create a channel for the site to talk to.
587 */
588 static bool
SITEstartprocess(SITE * sp)589 SITEstartprocess(SITE *sp)
590 {
591     pid_t i;
592     char *argv[MAX_BUILTIN_ARGV];
593     char *process;
594     int *ip;
595     int pan[2];
596 
597 #if HAVE_SOCKETPAIR
598     /* Create a socketpair. */
599     if (socketpair(PF_UNIX, SOCK_STREAM, 0, pan) < 0) {
600         syslog(L_ERROR, "%s cant socketpair %m", sp->Name);
601         return false;
602     }
603 #else
604     /* Create a pipe. */
605     if (pipe(pan) < 0) {
606         syslog(L_ERROR, "%s cant pipe %m", sp->Name);
607         return false;
608     }
609 #endif
610     fdflag_close_exec(pan[PIPE_WRITE], true);
611 
612     /* Set up the argument vector. */
613     process = xstrdup(sp->Param);
614     if (NeedShell(process, (const char **) argv,
615                   (const char **) ARRAY_END(argv))) {
616         argv[0] = SITEshell;
617         argv[1] = (char *) "-c";
618         argv[2] = process;
619         argv[3] = NULL;
620     }
621 
622     /* Fork a child. */
623     i = Spawn(sp->Nice, pan[PIPE_READ], (int) fileno(Errlog),
624               (int) fileno(Errlog), argv);
625     if (i > 0) {
626         sp->pid = i;
627         sp->Spooling = false;
628         sp->Process = PROCwatch(i, sp - Sites);
629         close(pan[PIPE_READ]);
630         sp->Channel = CHANcreate(
631             pan[PIPE_WRITE], sp->Type == FTchannel ? CTprocess : CTexploder,
632             CSwriting, SITEreader, SITEwritedone);
633         free(process);
634         return true;
635     }
636 
637     /* Error.  Switch to spooling. */
638     syslog(L_ERROR, "%s cant spawn spooling %m", sp->Name);
639     close(pan[PIPE_WRITE]);
640     close(pan[PIPE_READ]);
641     free(process);
642     if (!SITEspool(sp, (CHANNEL *) NULL))
643         return false;
644 
645     /* We'll try to restart the channel later. */
646     syslog(L_ERROR, "%s cant spawn spooling %m", sp->Name);
647     ip = xmalloc(sizeof(int));
648     *ip = sp - Sites;
649     SCHANadd(sp->Channel, Now.tv_sec + innconf->chanretrytime, NULL,
650              SITEspoolwake, ip);
651     return true;
652 }
653 
654 
655 /*
656 **  Set up a site for internal buffering.
657 */
658 static void
SITEbuffer(SITE * sp)659 SITEbuffer(SITE *sp)
660 {
661     struct buffer *bp;
662 
663     SITEunlink(sp);
664     sp->Buffered = true;
665     sp->Channel = NULL;
666     bp = &sp->Buffer;
667     buffer_resize(bp, sp->Flushpoint);
668     buffer_set(bp, "", 0);
669     syslog(L_NOTICE, "%s buffered", sp->Name);
670 }
671 
672 
673 /*
674 **  Link a site at the head of the "currently writing to a file" list
675 */
676 static void
SITEmovetohead(SITE * sp)677 SITEmovetohead(SITE *sp)
678 {
679     if ((SITEhead == NOSITE) && ((sp->Next != NOSITE) || (sp->Prev != NOSITE)))
680         SITEunlink(sp);
681 
682     if ((sp->Next = SITEhead) != NOSITE)
683         Sites[SITEhead].Prev = sp - Sites;
684     sp->Prev = NOSITE;
685 
686     SITEhead = sp - Sites;
687     if (SITEtail == NOSITE)
688         SITEtail = sp - Sites;
689 
690     SITEcount++;
691 }
692 
693 
694 /*
695 **  Set up a site's feed.  This means opening a file or channel if needed.
696 */
697 bool
SITEsetup(SITE * sp)698 SITEsetup(SITE *sp)
699 {
700     int fd;
701     int oerrno;
702 
703     switch (sp->Type) {
704     default:
705         syslog(L_ERROR, "%s internal SITEsetup %d", sp->Name, sp->Type);
706         return false;
707     case FTfunnel:
708     case FTlogonly:
709     case FTprogram:
710         /* Nothing to do here. */
711         break;
712     case FTfile:
713         if (SITEcount >= MaxOutgoing)
714             SITEbuffer(sp);
715         else {
716             sp->Buffered = false;
717             fd =
718                 open(sp->Param, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
719             if (fd < 0) {
720                 if (errno == EMFILE) {
721                     syslog(L_ERROR, "%s cant open %s %m", sp->Name, sp->Param);
722                     SITEbuffer(sp);
723                     break;
724                 }
725                 oerrno = errno;
726                 syslog(L_NOTICE, "%s cant open %s %m", sp->Name, sp->Param);
727                 IOError("site file", oerrno);
728                 return false;
729             }
730             SITEmovetohead(sp);
731             sp->Channel =
732                 CHANcreate(fd, CTfile, CSwriting, SITEreader, SITEwritedone);
733             syslog(L_NOTICE, "%s opened %s", sp->Name, CHANname(sp->Channel));
734             WCHANset(sp->Channel, "", 0);
735         }
736         break;
737     case FTchannel:
738     case FTexploder:
739         if (!SITEstartprocess(sp))
740             return false;
741         syslog(L_NOTICE, "%s spawned %s", sp->Name, CHANname(sp->Channel));
742         WCHANset(sp->Channel, "", 0);
743         WCHANadd(sp->Channel);
744         break;
745     }
746     return true;
747 }
748 
749 
750 /*
751 **  A site's channel process died; restart it.
752 */
753 void
SITEprocdied(SITE * sp,int process,PROCESS * pp)754 SITEprocdied(SITE *sp, int process, PROCESS *pp)
755 {
756     syslog(pp->Status ? L_ERROR : L_NOTICE, "%s exit %d elapsed %ld pid %ld",
757            sp->Name ? sp->Name : "?", pp->Status,
758            (long) (pp->Collected - pp->Started), (long) pp->Pid);
759     if (sp->Process != process || sp->Name == NULL)
760         /* We already started a new process for this channel
761          * or this site has been dropped. */
762         return;
763     if (sp->Channel != NULL)
764         CHANclose(sp->Channel, CHANname(sp->Channel));
765     sp->Working = SITEsetup(sp);
766     if (!sp->Working) {
767         syslog(L_ERROR, "%s cant restart %m", sp->Name);
768         return;
769     }
770     syslog(L_NOTICE, "%s restarted", sp->Name);
771 }
772 
773 /*
774 **  A channel is about to be closed; see if any site cares.
775 */
776 void
SITEchanclose(CHANNEL * cp)777 SITEchanclose(CHANNEL *cp)
778 {
779     int i;
780     SITE *sp;
781     int *ip;
782 
783     for (i = nSites, sp = Sites; --i >= 0; sp++)
784         if (sp->Channel == cp) {
785             /* Found the site that has this channel.  Start that
786              * site spooling, copy any data that might be pending,
787              * and arrange to retry later. */
788             if (!SITEspool(sp, (CHANNEL *) NULL)) {
789                 syslog(L_ERROR, "%s loss %lu bytes", sp->Name,
790                        (unsigned long) cp->Out.left);
791                 return;
792             }
793             WCHANsetfrombuffer(sp->Channel, &cp->Out);
794             WCHANadd(sp->Channel);
795             ip = xmalloc(sizeof(int));
796             *ip = sp - Sites;
797             SCHANadd(sp->Channel, Now.tv_sec + innconf->chanretrytime, NULL,
798                      SITEspoolwake, ip);
799             break;
800         }
801 }
802 
803 
804 /*
805 **  Flush any pending data waiting to be sent.
806 */
807 void
SITEflush(SITE * sp,const bool Restart)808 SITEflush(SITE *sp, const bool Restart)
809 {
810     CHANNEL *cp;
811     struct buffer *out;
812     int count;
813 
814     if (sp->Name == NULL)
815         return;
816 
817     if (Restart)
818         SITEforward(sp, "flush");
819 
820     switch (sp->Type) {
821     default:
822         syslog(L_ERROR, "%s internal SITEflush %d", sp->Name, sp->Type);
823         return;
824 
825     case FTlogonly:
826     case FTprogram:
827     case FTfunnel:
828         /* Nothing to do here. */
829         return;
830 
831     case FTchannel:
832     case FTexploder:
833         /* If spooling, close the file right now -- documented behavior. */
834         if (sp->Spooling && (cp = sp->Channel) != NULL) {
835             WCHANflush(cp);
836             CHANclose(cp, CHANname(cp));
837             sp->Channel = NULL;
838         }
839         break;
840 
841     case FTfile:
842         /* We must ensure we have a file open for this site, so if
843          * we're buffered we HACK and pretend we have no sites
844          * for a moment. */
845         if (sp->Buffered) {
846             count = SITEcount;
847             SITEcount = 0;
848             if (!SITEsetup(sp) || sp->Buffered)
849                 syslog(L_ERROR, "%s cant unbuffer to flush", sp->Name);
850             else
851                 buffer_swap(&sp->Buffer, &sp->Channel->Out);
852             SITEcount += count;
853         }
854         break;
855     }
856 
857     /* We're only dealing with files and channels now. */
858     if ((cp = sp->Channel) != NULL)
859         WCHANflush(cp);
860 
861     /* Restart the site, copy any pending data. */
862     if (Restart) {
863         if (!SITEsetup(sp))
864             syslog(L_ERROR, "%s cant restart %m", sp->Name);
865         else if (cp != NULL) {
866             if (sp->Buffered) {
867                 /* SITEsetup had to buffer us; save any residue. */
868                 out = &cp->Out;
869                 if (out->left)
870                     buffer_set(&sp->Buffer, &out->data[out->used], out->left);
871             } else
872                 WCHANsetfrombuffer(sp->Channel, &cp->Out);
873         }
874     } else if (cp != NULL && cp->Out.left) {
875         if (sp->Type == FTfile || sp->Spooling) {
876             /* Can't flush a file?  Hopeless. */
877             syslog(L_ERROR, "%s dataloss %lu", sp->Name,
878                    (unsigned long) cp->Out.left);
879             return;
880         }
881         /* Must be a working channel; spool and retry. */
882         syslog(L_ERROR, "%s spooling %lu bytes", sp->Name,
883                (unsigned long) cp->Out.left);
884         if (SITEspool(sp, cp))
885             SITEflush(sp, false);
886         return;
887     }
888 
889     /* Close the old channel if it was open. */
890     if (cp != NULL) {
891         /* Make sure we have no dangling pointers to it. */
892         if (!Restart)
893             sp->Channel = NULL;
894         CHANclose(cp, sp->Name);
895         if (sp->Type == FTfile)
896             SITEunlink(sp);
897     }
898 }
899 
900 
901 /*
902 **  Flush all sites.
903 */
904 void
SITEflushall(const bool Restart)905 SITEflushall(const bool Restart)
906 {
907     int i;
908     SITE *sp;
909 
910     for (i = nSites, sp = Sites; --i >= 0; sp++)
911         if (sp->Name)
912             SITEflush(sp, Restart);
913 }
914 
915 
916 /*
917 **  Run down the site's pattern list and see if it wants the specified
918 **  newsgroup.
919 */
920 bool
SITEwantsgroup(SITE * sp,char * name)921 SITEwantsgroup(SITE *sp, char *name)
922 {
923     bool match;
924     bool subvalue;
925     char *pat;
926     char **argv;
927 
928     match = SUB_DEFAULT;
929     if (ME.Patterns) {
930         for (argv = ME.Patterns; (pat = *argv++) != NULL;) {
931             subvalue = *pat != SUB_NEGATE && *pat != SUB_POISON;
932             if (!subvalue)
933                 pat++;
934             if ((match != subvalue) && uwildmat(name, pat))
935                 match = subvalue;
936         }
937     }
938     for (argv = sp->Patterns; (pat = *argv++) != NULL;) {
939         subvalue = *pat != SUB_NEGATE && *pat != SUB_POISON;
940         if (!subvalue)
941             pat++;
942         if ((match != subvalue) && uwildmat(name, pat))
943             match = subvalue;
944     }
945     return match;
946 }
947 
948 
949 /*
950 **  Run down the site's pattern list and see if specified newsgroup is
951 **  considered poison.
952 */
953 bool
SITEpoisongroup(SITE * sp,char * name)954 SITEpoisongroup(SITE *sp, char *name)
955 {
956     bool match;
957     bool poisonvalue;
958     char *pat;
959     char **argv;
960 
961     match = SUB_DEFAULT;
962     if (ME.Patterns) {
963         for (argv = ME.Patterns; (pat = *argv++) != NULL;) {
964             poisonvalue = *pat == SUB_POISON;
965             if (*pat == SUB_NEGATE || *pat == SUB_POISON)
966                 pat++;
967             if (uwildmat(name, pat))
968                 match = poisonvalue;
969         }
970     }
971     for (argv = sp->Patterns; (pat = *argv++) != NULL;) {
972         poisonvalue = *pat == SUB_POISON;
973         if (*pat == SUB_NEGATE || *pat == SUB_POISON)
974             pat++;
975         if (uwildmat(name, pat))
976             match = poisonvalue;
977     }
978     return match;
979 }
980 
981 
982 /*
983 **  Find a site.
984 */
985 SITE *
SITEfind(const char * p)986 SITEfind(const char *p)
987 {
988     int i;
989     SITE *sp;
990 
991     for (i = nSites, sp = Sites; --i >= 0; sp++)
992         if (sp->Name && strcasecmp(p, sp->Name) == 0)
993             return sp;
994     return NULL;
995 }
996 
997 
998 /*
999 **  Find the next site that matches this site.
1000 */
1001 SITE *
SITEfindnext(const char * p,SITE * sp)1002 SITEfindnext(const char *p, SITE *sp)
1003 {
1004     SITE *end;
1005 
1006     for (sp++, end = &Sites[nSites]; sp < end; sp++)
1007         if (sp->Name && strcasecmp(p, sp->Name) == 0)
1008             return sp;
1009     return NULL;
1010 }
1011 
1012 
1013 /*
1014 **  Close a site down.
1015 */
1016 void
SITEfree(SITE * sp)1017 SITEfree(SITE *sp)
1018 {
1019     SITE *s;
1020     HASHFEEDLIST *hf, *hn;
1021     int new;
1022     int i;
1023 
1024     if (sp->Channel) {
1025         CHANclose(sp->Channel, CHANname(sp->Channel));
1026         sp->Channel = NULL;
1027     }
1028 
1029     SITEunlink(sp);
1030 
1031     sp->Name = NULL;
1032     if (sp->Process > 0) {
1033         /* Kill the backpointer so PROCdied won't call us. */
1034         PROCunwatch(sp->Process);
1035         sp->Process = -1;
1036     }
1037     if (sp->Entry) {
1038         free(sp->Entry);
1039         sp->Entry = NULL;
1040     }
1041     if (sp->Originator) {
1042         free(sp->Originator);
1043         sp->Originator = NULL;
1044     }
1045     if (sp->Param) {
1046         free(sp->Param);
1047         sp->Param = NULL;
1048     }
1049     if (sp->SpoolName) {
1050         free(sp->SpoolName);
1051         sp->SpoolName = NULL;
1052     }
1053     if (sp->Patterns) {
1054         free(sp->Patterns);
1055         sp->Patterns = NULL;
1056     }
1057     if (sp->Exclusions) {
1058         free(sp->Exclusions);
1059         sp->Exclusions = NULL;
1060     }
1061     if (sp->Distributions) {
1062         free(sp->Distributions);
1063         sp->Distributions = NULL;
1064     }
1065     if (sp->Buffer.data) {
1066         free(sp->Buffer.data);
1067         sp->Buffer.data = NULL;
1068         sp->Buffer.size = 0;
1069     }
1070     if (sp->FNLnames.data) {
1071         free(sp->FNLnames.data);
1072         sp->FNLnames.data = NULL;
1073         sp->FNLnames.size = 0;
1074         sp->FNLnames.left = 0;
1075         sp->FNLnames.used = 0;
1076     }
1077     if (sp->HashFeedList) {
1078         for (hf = sp->HashFeedList; hf; hf = hn) {
1079             hn = hf->next;
1080             free(hf);
1081         }
1082         sp->HashFeedList = NULL;
1083     }
1084 
1085     /* If this site was a master, find a new one. */
1086     if (sp->IsMaster) {
1087         for (new = NOSITE, s = Sites, i = nSites; --i >= 0; s++)
1088             if (&Sites[s->Master] == sp) {
1089                 if (new == NOSITE) {
1090                     s->Master = NOSITE;
1091                     s->IsMaster = true;
1092                     new = s - Sites;
1093                 } else
1094                     s->Master = new;
1095             }
1096         sp->IsMaster = false;
1097     }
1098 }
1099 
1100 
1101 /*
1102 **  If a site is an exploder or funnels into one, forward a command
1103 **  to it.
1104 */
1105 void
SITEforward(SITE * sp,const char * text)1106 SITEforward(SITE *sp, const char *text)
1107 {
1108     SITE *fsp;
1109     char buff[SMBUF];
1110 
1111     fsp = sp;
1112     if (fsp->Funnel != NOSITE)
1113         fsp = &Sites[fsp->Funnel];
1114     if (sp->Name == NULL || fsp->Name == NULL)
1115         return;
1116     if (fsp->Type == FTexploder) {
1117         strlcpy(buff, text, sizeof(buff));
1118         if (fsp != sp && fsp->FNLwantsnames) {
1119             strlcat(buff, " ", sizeof(buff));
1120             strlcat(buff, sp->Name, sizeof(buff));
1121         }
1122         SITEwrite(fsp, buff);
1123     }
1124 }
1125 
1126 
1127 /*
1128 **  Drop a site.
1129 */
1130 void
SITEdrop(SITE * sp)1131 SITEdrop(SITE *sp)
1132 {
1133     SITEforward(sp, "drop");
1134     SITEflush(sp, false);
1135     SITEfree(sp);
1136 }
1137 
1138 
1139 /*
1140 **  Append info about the current state of the site to the buffer
1141 */
1142 void
SITEinfo(struct buffer * bp,SITE * sp,const bool Verbose)1143 SITEinfo(struct buffer *bp, SITE *sp, const bool Verbose)
1144 {
1145     static char FREESITE[] = "<<No name>>\n\n";
1146     CHANNEL *cp;
1147     const char *sep;
1148 
1149     if (sp->Name == NULL) {
1150         buffer_append(bp, FREESITE, strlen(FREESITE));
1151         return;
1152     }
1153 
1154     buffer_append_sprintf(bp, "%s%s:\t", sp->Name, sp->IsMaster ? "(*)" : "");
1155 
1156     if (sp->Type == FTfunnel) {
1157         sp = &Sites[sp->Funnel];
1158         buffer_append_sprintf(bp, "funnel -> %s: ", sp->Name);
1159     }
1160 
1161     switch (sp->Type) {
1162     default:
1163         buffer_append_sprintf(bp, "unknown feed type %d", sp->Type);
1164         break;
1165     case FTerror:
1166     case FTfile:
1167         buffer_append_sprintf(bp, "file");
1168         if (sp->Buffered)
1169             buffer_append_sprintf(bp, " buffered(%lu)",
1170                                   (unsigned long) sp->Buffer.left);
1171         else if ((cp = sp->Channel) == NULL)
1172             buffer_append_sprintf(bp, " no channel?");
1173         else
1174             buffer_append_sprintf(bp, " open fd=%d, in mem %lu", cp->fd,
1175                                   (unsigned long) cp->Out.left);
1176         break;
1177     case FTchannel:
1178         buffer_append_sprintf(bp, "channel");
1179         goto common;
1180     case FTexploder:
1181         buffer_append_sprintf(bp, "exploder");
1182     common:
1183         if (sp->Process >= 0)
1184             buffer_append_sprintf(bp, " pid=%ld", (long) sp->pid);
1185         if (sp->Spooling)
1186             buffer_append_sprintf(bp, " spooling");
1187         cp = sp->Channel;
1188         if (cp == NULL)
1189             buffer_append_sprintf(bp, " no channel?");
1190         else
1191             buffer_append_sprintf(bp, " fd=%d, in mem %lu", cp->fd,
1192                                   (unsigned long) cp->Out.left);
1193         break;
1194     case FTfunnel:
1195         buffer_append_sprintf(bp, "recursive funnel");
1196         break;
1197     case FTlogonly:
1198         buffer_append_sprintf(bp, "log only");
1199         break;
1200     case FTprogram:
1201         buffer_append_sprintf(bp, "program");
1202         if (sp->FNLwantsnames)
1203             buffer_append_sprintf(bp, " with names");
1204         break;
1205     }
1206     buffer_append(bp, "\n", 1);
1207     if (Verbose) {
1208         sep = "\t";
1209         if (sp->Buffered && sp->Flushpoint) {
1210             buffer_append_sprintf(bp, "%sFlush @ %lu", sep,
1211                                   (unsigned long) sp->Flushpoint);
1212             sep = "; ";
1213         }
1214         if (sp->StartWriting || sp->StopWriting) {
1215             buffer_append_sprintf(bp, "%sWrite [%ld..%ld]", sep,
1216                                   sp->StopWriting, sp->StartWriting);
1217             sep = "; ";
1218         }
1219         if (sp->StartSpooling) {
1220             buffer_append_sprintf(bp, "%sSpool @ %ld", sep, sp->StartSpooling);
1221             sep = "; ";
1222         }
1223         if (sep[0] != '\t')
1224             buffer_append(bp, "\n", 1);
1225         if (sp->Spooling && sp->SpoolName)
1226             buffer_append_sprintf(bp, "\tSpooling to \"%s\"\n", sp->SpoolName);
1227         cp = sp->Channel;
1228         if (cp != NULL) {
1229             buffer_append_sprintf(bp, "\tChannel created %.12s",
1230                                   ctime(&cp->Started) + 4);
1231             buffer_append_sprintf(bp, ", last active %.12s\n",
1232                                   ctime(&cp->LastActive) + 4);
1233             if (cp->Waketime > Now.tv_sec)
1234                 buffer_append_sprintf(bp, "\tSleeping until %.12s\n",
1235                                       ctime(&cp->Waketime) + 4);
1236         }
1237     }
1238     buffer_append(bp, "", 1);
1239 }
1240