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