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