chiark / gitweb /
fixes
[inn-innduct.git] / innd / nc.c
1 /*  $Id: nc.c 7418 2005-10-09 04:37:05Z eagle $
2 **
3 **  Routines for the NNTP channel.  Other channels get the descriptors which
4 **  we turn into NNTP channels, and over which we speak NNTP.
5 */
6
7 #include "config.h"
8 #include "clibrary.h"
9
10 #include "inn/innconf.h"
11 #include "innd.h"
12
13 #define BAD_COMMAND_COUNT       10
14
15
16 /*
17 **  An entry in the dispatch table.  The name, and implementing function,
18 **  of every command we support.
19 */
20 typedef struct _NCDISPATCH {
21     const char *        Name;
22     innd_callback_t     Function;
23     int                 Size;
24 } NCDISPATCH;
25
26 /* The functions that implement the various commands. */
27 static void NCauthinfo(CHANNEL *cp);
28 static void NCcancel(CHANNEL *cp);
29 static void NCcheck(CHANNEL *cp);
30 static void NChead(CHANNEL *cp);
31 static void NChelp(CHANNEL *cp);
32 static void NCihave(CHANNEL *cp);
33 static void NClist(CHANNEL *cp);
34 static void NCmode(CHANNEL *cp);
35 static void NCquit(CHANNEL *cp);
36 static void NCstat(CHANNEL *cp);
37 static void NCtakethis(CHANNEL *cp);
38 static void NCxbatch(CHANNEL *cp);
39
40 /* Handlers for unimplemented commands.  We need two handlers so that we can
41    return the right status code; reader commands that are required by the
42    standard must return a 502 error rather than a 500 error. */
43 static void NC_reader(CHANNEL *cp);
44 static void NC_unimp(CHANNEL *cp);
45
46 /* Supporting functions. */
47 static void NCwritedone(CHANNEL *cp);
48
49 /* Set up the dispatch table for all of the commands. */
50 #define COMMAND(name, func) { name, func, sizeof(name) - 1 }
51 static NCDISPATCH NCcommands[] = {
52     COMMAND("authinfo",  NCauthinfo),
53     COMMAND("check",     NCcheck),
54     COMMAND("head",      NChead),
55     COMMAND("help",      NChelp),
56     COMMAND("ihave",     NCihave),
57     COMMAND("list",      NClist),
58     COMMAND("mode",      NCmode),
59     COMMAND("quit",      NCquit),
60     COMMAND("stat",      NCstat),
61     COMMAND("takethis",  NCtakethis),
62     COMMAND("xbatch",    NCxbatch),
63
64     /* Unimplemented reader commands which may become available after a MODE
65        READER command. */
66     COMMAND("article",   NC_reader),
67     COMMAND("body",      NC_reader),
68     COMMAND("group",     NC_reader),
69     COMMAND("last",      NC_reader),
70     COMMAND("newgroups", NC_reader),
71     COMMAND("newnews",   NC_reader),
72     COMMAND("next",      NC_reader),
73     COMMAND("post",      NC_reader),
74
75     /* Other unimplemented standard commands. */
76     COMMAND("date",      NC_unimp),
77     COMMAND("slave",     NC_unimp)
78 };
79 #undef COMMAND
80
81 /* Number of open connections. */
82 static int NCcount;
83
84 static char             *NCquietlist[] = { INND_QUIET_BADLIST };
85 static const char       NCterm[] = "\r\n";
86 static const char       NCdot[] = "." ;
87 static const char       NCbadcommand[] = NNTP_BAD_COMMAND;
88 static const char       NCbadsubcommand[] = NNTP_BAD_SUBCMD;
89
90 /*
91 ** Clear the WIP entry for the given channel
92 */
93 void
94 NCclearwip(CHANNEL *cp)
95 {
96     WIPfree(WIPbyhash(cp->CurrentMessageIDHash));
97     HashClear(&cp->CurrentMessageIDHash);
98     cp->ArtBeg = 0;
99 }
100
101 /*
102 **  Write an NNTP reply message.
103 **
104 **  Tries to do the actual write immediately if it will not block and if there
105 **  is not already other buffered output.  Then, if the write is successful,
106 **  calls NCwritedone (which does whatever is necessary to accommodate state
107 **  changes).  Else, NCwritedone will be called from the main select loop
108 **  later.
109 **
110 **  If the reply that we are writing now is associated with a state change,
111 **  then cp->State must be set to its new value *before* NCwritereply is
112 **  called.
113 */
114 void
115 NCwritereply(CHANNEL *cp, const char *text)
116 {
117     struct buffer *bp;
118     int i;
119
120     /* XXX could do RCHANremove(cp) here, as the old NCwritetext() used to
121      * do, but that would be wrong if the channel is sreaming (because it
122      * would zap the channell's input buffer).  There's no harm in
123      * never calling RCHANremove here.  */
124
125     bp = &cp->Out;
126     i = bp->left;
127     WCHANappend(cp, text, strlen(text));        /* text in buffer */
128     WCHANappend(cp, NCterm, strlen(NCterm));    /* add CR NL to text */
129
130     if (i == 0) {       /* if only data then try to write directly */
131         i = write(cp->fd, &bp->data[bp->used], bp->left);
132         if (Tracing || cp->Tracing)
133             syslog(L_TRACE, "%s NCwritereply %d=write(%d, \"%.15s\", %lu)",
134                 CHANname(cp), i, cp->fd, &bp->data[bp->used],
135                 (unsigned long) bp->left);
136         if (i > 0)
137             bp->used += i;
138         if (bp->used == bp->left) {
139             /* all the data was written */
140             bp->used = bp->left = 0;
141             NCwritedone(cp);
142         } else {
143             bp->left -= i;
144             i = 0;
145         }
146     } else i = 0;
147     if (i <= 0) {       /* write failed, queue it for later */
148         WCHANadd(cp);
149     }
150     if (Tracing || cp->Tracing)
151         syslog(L_TRACE, "%s > %s", CHANname(cp), text);
152 }
153
154 /*
155 **  Tell the NNTP channel to go away.
156 */
157 void
158 NCwriteshutdown(CHANNEL *cp, const char *text)
159 {
160     cp->State = CSwritegoodbye;
161     RCHANremove(cp); /* we're not going to read anything more */
162     WCHANappend(cp, NNTP_GOODBYE, strlen(NNTP_GOODBYE));
163     WCHANappend(cp, " ", 1);
164     WCHANappend(cp, text, (int)strlen(text));
165     WCHANappend(cp, NCterm, strlen(NCterm));
166     WCHANadd(cp);
167 }
168
169
170 /*
171 **  If a Message-ID is bad, write a reject message and return true.
172 */
173 static bool
174 NCbadid(CHANNEL *cp, char *p)
175 {
176     if (ARTidok(p))
177         return false;
178
179     NCwritereply(cp, NNTP_HAVEIT_BADID);
180     syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
181     return true;
182 }
183
184
185 /*
186 **  We have an entire article collected; try to post it.  If we're
187 **  not running, drop the article or just pause and reschedule.
188 */
189 static void
190 NCpostit(CHANNEL *cp)
191 {
192   bool  postok;
193   const char    *response;
194   char  buff[SMBUF];
195
196   /* Note that some use break, some use return here. */
197   if ((postok = ARTpost(cp)) != 0) {
198     cp->Received++;
199     if (cp->Sendid.size > 3) { /* We be streaming */
200       cp->Takethis_Ok++;
201       snprintf(buff, sizeof(buff), "%d", NNTP_OK_RECID_VAL);
202       cp->Sendid.data[0] = buff[0];
203       cp->Sendid.data[1] = buff[1];
204       cp->Sendid.data[2] = buff[2];
205       response = cp->Sendid.data;
206     } else
207       response = NNTP_TOOKIT;
208   } else {
209     cp->Rejected++;
210     if (cp->Sendid.size)
211       response = cp->Sendid.data;
212     else
213       response = cp->Error;
214   }
215   cp->Reported++;
216   if (cp->Reported >= innconf->nntpactsync) {
217     snprintf(buff, sizeof(buff), "accepted size %.0f duplicate size %.0f",
218              cp->Size, cp->DuplicateSize);
219     syslog(L_NOTICE,
220       "%s checkpoint seconds %ld accepted %ld refused %ld rejected %ld duplicate %ld %s",
221       CHANname(cp), (long)(Now.time - cp->Started),
222     cp->Received, cp->Refused, cp->Rejected,
223     cp->Duplicate, buff);
224     cp->Reported = 0;
225   }
226   if (Mode == OMthrottled) {
227     NCwriteshutdown(cp, ModeReason);
228     return;
229   }
230   cp->State = CSgetcmd;
231   NCwritereply(cp, response);
232 }
233
234
235 /*
236 **  Write-done function.  Close down or set state for what we expect to
237 **  read next.
238 */
239 static void
240 NCwritedone(CHANNEL *cp)
241 {
242     switch (cp->State) {
243     default:
244         syslog(L_ERROR, "%s internal NCwritedone state %d",
245             CHANname(cp), cp->State);
246         break;
247
248     case CSwritegoodbye:
249         if (NCcount > 0)
250             NCcount--;
251         CHANclose(cp, CHANname(cp));
252         break;
253
254     case CSgetcmd:
255     case CSgetauth:
256     case CSgetheader:
257     case CSgetbody:
258     case CSgetxbatch:
259     case CSgotlargearticle:
260     case CScancel:
261         RCHANadd(cp);
262         break;
263     }
264 }
265
266 \f
267
268 /*
269 **  The "head" command.
270 */
271 static void
272 NChead(CHANNEL *cp)
273 {
274     char                *p;
275     TOKEN               token;
276     ARTHANDLE           *art;
277
278     /* Snip off the Message-ID. */
279     for (p = cp->In.data + cp->Start + strlen("head"); ISWHITE(*p); p++)
280         continue;
281     cp->Start = cp->Next;
282     if (NCbadid(cp, p))
283         return;
284
285     /* Get the article token and retrieve it. */
286     if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
287         NCwritereply(cp, NNTP_DONTHAVEIT);
288         return;
289     }
290     if ((art = SMretrieve(token, RETR_HEAD)) == NULL) {
291         NCwritereply(cp, NNTP_DONTHAVEIT);
292         return;
293     }
294
295     /* Write it. */
296     WCHANappend(cp, NNTP_HEAD_FOLLOWS, strlen(NNTP_HEAD_FOLLOWS));
297     WCHANappend(cp, " 0 ", 3);
298     WCHANappend(cp, p, strlen(p));
299     WCHANappend(cp, NCterm, strlen(NCterm));
300     WCHANappend(cp, art->data, art->len);
301
302     /* Write the terminator. */
303     NCwritereply(cp, NCdot);
304     SMfreearticle(art);
305 }
306
307
308 /*
309 **  The "stat" command.
310 */
311 static void
312 NCstat(CHANNEL *cp)
313 {
314     char                *p;
315     TOKEN               token;
316     ARTHANDLE           *art;
317     char                *buff;
318     size_t              length;
319
320     /* Snip off the Message-ID. */
321     for (p = cp->In.data + cp->Start + strlen("stat"); ISWHITE(*p); p++)
322         continue;
323     cp->Start = cp->Next;
324     if (NCbadid(cp, p))
325         return;
326
327     /* Get the article filenames; open the first file (to make sure
328      * the article is still here). */
329     if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
330         NCwritereply(cp, NNTP_DONTHAVEIT);
331         return;
332     }
333     if ((art = SMretrieve(token, RETR_STAT)) == NULL) {
334         NCwritereply(cp, NNTP_DONTHAVEIT);
335         return;
336     }
337     SMfreearticle(art);
338
339     /* Write the message. */
340     length = snprintf(NULL, 0, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p) + 1;
341     buff = xmalloc(length);
342     snprintf(buff, length, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p);
343     NCwritereply(cp, buff);
344     free(buff);
345 }
346
347
348 /*
349 **  The "authinfo" command.  Actually, we come in here whenever the
350 **  channel is in CSgetauth state and we just got a command.
351 */
352 static void
353 NCauthinfo(CHANNEL *cp)
354 {
355     static char         AUTHINFO[] = "authinfo ";
356     static char         PASS[] = "pass ";
357     static char         USER[] = "user ";
358     char                *p;
359
360     p = cp->In.data + cp->Start;
361     cp->Start = cp->Next;
362
363     /* Allow the poor sucker to quit. */
364     if (strcasecmp(p, "quit") == 0) {
365         NCquit(cp);
366         return;
367     }
368
369     /* Otherwise, make sure we're only getting "authinfo" commands. */
370     if (strncasecmp(p, AUTHINFO, strlen(AUTHINFO)) != 0) {
371         NCwritereply(cp, NNTP_AUTH_NEEDED);
372         return;
373     }
374     for (p += strlen(AUTHINFO); ISWHITE(*p); p++)
375         continue;
376
377     /* Ignore "authinfo user" commands, since we only care about the
378      * password. */
379     if (strncasecmp(p, USER, strlen(USER)) == 0) {
380         NCwritereply(cp, NNTP_AUTH_NEXT);
381         return;
382     }
383
384     /* Now make sure we're getting only "authinfo pass" commands. */
385     if (strncasecmp(p, PASS, strlen(PASS)) != 0) {
386         NCwritereply(cp, NNTP_AUTH_NEEDED);
387         return;
388     }
389     for (p += strlen(PASS); ISWHITE(*p); p++)
390         continue;
391
392     /* Got the password -- is it okay? */
393     if (!RCauthorized(cp, p)) {
394         cp->State = CSwritegoodbye;
395         NCwritereply(cp, NNTP_AUTH_BAD);
396     } else {
397         cp->State = CSgetcmd;
398         NCwritereply(cp, NNTP_AUTH_OK);
399     }
400 }
401
402 /*
403 **  The "help" command.
404 */
405 static void
406 NChelp(CHANNEL *cp)
407 {
408     static char         LINE1[] = "For more information, contact \"";
409     static char         LINE2[] = "\" at this machine.";
410     NCDISPATCH          *dp;
411
412     WCHANappend(cp, NNTP_HELP_FOLLOWS,strlen(NNTP_HELP_FOLLOWS));
413     WCHANappend(cp, NCterm,strlen(NCterm));
414     for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++)
415         if (dp->Function != NC_unimp) {
416             if ((!StreamingOff && cp->Streaming) ||
417                 (dp->Function != NCcheck && dp->Function != NCtakethis)) {
418                 WCHANappend(cp, "\t", 1);
419                 WCHANappend(cp, dp->Name, dp->Size);
420                 WCHANappend(cp, NCterm, strlen(NCterm));
421             }
422         }
423     WCHANappend(cp, LINE1, strlen(LINE1));
424     WCHANappend(cp, NEWSMASTER, strlen(NEWSMASTER));
425     WCHANappend(cp, LINE2, strlen(LINE2));
426     WCHANappend(cp, NCterm, strlen(NCterm));
427     NCwritereply(cp, NCdot) ;
428     cp->Start = cp->Next;
429 }
430
431 /*
432 **  The "ihave" command.  Check the Message-ID, and see if we want the
433 **  article or not.  Set the state appropriately.
434 */
435 static void
436 NCihave(CHANNEL *cp)
437 {
438     char        *p;
439 #if defined(DO_PERL) || defined(DO_PYTHON)
440     char        *filterrc;
441     int         msglen;
442 #endif /*defined(DO_PERL) || defined(DO_PYTHON) */
443
444     cp->Ihave++;
445     /* Snip off the Message-ID. */
446     for (p = cp->In.data + cp->Start + strlen("ihave"); ISWHITE(*p); p++)
447         continue;
448     cp->Start = cp->Next;
449     if (NCbadid(cp, p))
450         return;
451
452     if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
453         cp->Refused++;
454         cp->Ihave_Cybercan++;
455         NCwritereply(cp, NNTP_HAVEIT);
456         return;
457     }
458
459 #if defined(DO_PERL)
460     /*  Invoke a perl message filter on the message ID. */
461     filterrc = PLmidfilter(p);
462     if (filterrc) {
463         cp->Refused++;
464         msglen = strlen(p) + 5; /* 3 digits + space + id + null */
465         if (cp->Sendid.size < msglen) {
466             if (cp->Sendid.size > 0) free(cp->Sendid.data);
467             if (msglen > MAXHEADERSIZE)
468                 cp->Sendid.size = msglen;
469             else
470                 cp->Sendid.size = MAXHEADERSIZE;
471             cp->Sendid.data = xmalloc(cp->Sendid.size);
472         }
473         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
474                  NNTP_HAVEIT_VAL, filterrc);
475         NCwritereply(cp, cp->Sendid.data);
476         free(cp->Sendid.data);
477         cp->Sendid.size = 0;
478         return;
479     }
480 #endif
481
482 #if defined(DO_PYTHON)
483     /*  invoke a Python message filter on the message id */
484     msglen = strlen(p);
485     TMRstart(TMR_PYTHON);
486     filterrc = PYmidfilter(p, msglen);
487     TMRstop(TMR_PYTHON);
488     if (filterrc) {
489         cp->Refused++;
490         msglen += 5; /* 3 digits + space + id + null */
491         if (cp->Sendid.size < msglen) {
492             if (cp->Sendid.size > 0)
493                 free(cp->Sendid.data);
494             if (msglen > MAXHEADERSIZE)
495                 cp->Sendid.size = msglen;
496             else
497                 cp->Sendid.size = MAXHEADERSIZE;
498             cp->Sendid.data = xmalloc(cp->Sendid.size);
499         }
500         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
501                  NNTP_HAVEIT_VAL, filterrc);
502         NCwritereply(cp, cp->Sendid.data);
503         free(cp->Sendid.data);
504         cp->Sendid.size = 0;
505         return;
506     }
507 #endif
508
509     if (HIScheck(History, p)) {
510         cp->Refused++;
511         cp->Ihave_Duplicate++;
512         NCwritereply(cp, NNTP_HAVEIT);
513     }
514     else if (WIPinprogress(p, cp, false)) {
515         cp->Ihave_Deferred++;
516         if (cp->NoResendId) {
517             cp->Refused++;
518             NCwritereply(cp, NNTP_HAVEIT);
519         } else {
520             NCwritereply(cp, NNTP_RESENDIT_LATER);
521         }
522     }
523     else {
524         if (cp->Sendid.size > 0) {
525             free(cp->Sendid.data);
526             cp->Sendid.size = 0;
527         }
528         cp->Ihave_SendIt++;
529         NCwritereply(cp, NNTP_SENDIT);
530         cp->ArtBeg = Now.time;
531         cp->State = CSgetheader;
532         ARTprepare(cp);
533     }
534 }
535
536 /* 
537 ** The "xbatch" command. Set the state appropriately.
538 */
539
540 static void
541 NCxbatch(CHANNEL *cp)
542 {
543     char        *p;
544
545     /* Snip off the batch size */
546     for (p = cp->In.data + cp->Start + strlen("xbatch"); ISWHITE(*p); p++)
547         continue;
548     cp->Start = cp->Next;
549
550     if (cp->XBatchSize) {
551         syslog(L_FATAL, "NCxbatch(): oops, cp->XBatchSize already set to %d",
552                cp->XBatchSize);
553     }
554
555     cp->XBatchSize = atoi(p);
556     if (Tracing || cp->Tracing)
557         syslog(L_TRACE, "%s will read batch of size %d",
558                CHANname(cp), cp->XBatchSize);
559
560     if (cp->XBatchSize <= 0 || ((innconf->maxartsize != 0) && (innconf->maxartsize < cp->XBatchSize))) {
561         syslog(L_NOTICE, "%s got bad xbatch size %d",
562                CHANname(cp), cp->XBatchSize);
563         NCwritereply(cp, NNTP_XBATCH_BADSIZE);
564         return;
565     }
566
567     /* we prefer not to touch the buffer, NCreader() does enough magic
568      * with it
569      */
570     cp->State = CSgetxbatch;
571     NCwritereply(cp, NNTP_CONT_XBATCH);
572 }
573
574 /*
575 **  The "list" command.  Send the active file.
576 */
577 static void
578 NClist(CHANNEL *cp)
579 {
580     char *p, *q, *trash, *end, *path;
581
582     for (p = cp->In.data + cp->Start + strlen("list"); ISWHITE(*p); p++)
583         continue;
584     cp->Start = cp->Next;
585     if (cp->Nolist) {
586         NCwritereply(cp, NCbadcommand);
587         return;
588     }
589     if (strcasecmp(p, "newsgroups") == 0) {
590         path = concatpath(innconf->pathdb, _PATH_NEWSGROUPS);
591         trash = p = ReadInFile(path, NULL);
592         free(path);
593         if (p == NULL) {
594             NCwritereply(cp, NCdot);
595             return;
596         }
597         end = p + strlen(p);
598     }
599     else if (strcasecmp(p, "active.times") == 0) {
600         path = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
601         trash = p = ReadInFile(path, NULL);
602         free(path);
603         if (p == NULL) {
604             NCwritereply(cp, NCdot);
605             return;
606         }
607         end = p + strlen(p);
608     }
609     else if (*p == '\0' || (strcasecmp(p, "active") == 0)) {
610         p = ICDreadactive(&end);
611         trash = NULL;
612     }
613     else {
614         NCwritereply(cp, NCbadsubcommand);
615         return;
616     }
617
618     /* Loop over all lines, sending the text and \r\n. */
619     WCHANappend(cp, NNTP_LIST_FOLLOWS,strlen(NNTP_LIST_FOLLOWS));
620     WCHANappend(cp, NCterm, strlen(NCterm)) ;
621     for (; p < end && (q = strchr(p, '\n')) != NULL; p = q + 1) {
622         WCHANappend(cp, p, q - p);
623         WCHANappend(cp, NCterm, strlen(NCterm));
624     }
625     NCwritereply(cp, NCdot);
626     if (trash)
627         free(trash);
628 }
629
630
631 /*
632 **  The "mode" command.  Hand off the channel.
633 */
634 static void
635 NCmode(CHANNEL *cp)
636 {
637     char                *p;
638     HANDOFF             h;
639
640     /* Skip the first word, get the argument. */
641     for (p = cp->In.data + cp->Start + strlen("mode"); ISWHITE(*p); p++)
642         continue;
643     cp->Start = cp->Next;
644
645     if (strcasecmp(p, "reader") == 0 && !innconf->noreader)
646         h = HOnnrpd;
647     else if (strcasecmp(p, "stream") == 0 &&
648              (!StreamingOff && cp->Streaming)) {
649         char buff[16];
650
651         snprintf(buff, sizeof(buff), "%d StreamOK.", NNTP_OK_STREAM_VAL);
652         NCwritereply(cp, buff);
653         syslog(L_NOTICE, "%s NCmode \"mode stream\" received",
654                 CHANname(cp));
655         return;
656     } else if (strcasecmp(p, "cancel") == 0 && cp->privileged) {
657        char buff[16];
658
659        cp->State = CScancel;
660        snprintf(buff, sizeof(buff), "%d CancelOK.", NNTP_OK_CANCEL_VAL);
661        NCwritereply(cp, buff);
662        syslog(L_NOTICE, "%s NCmode \"mode cancel\" received",
663                CHANname(cp));
664        return;
665     } else {
666         NCwritereply(cp, NCbadsubcommand);
667         return;
668     }
669     RChandoff(cp->fd, h);
670     if (NCcount > 0)
671         NCcount--;
672     CHANclose(cp, CHANname(cp));
673 }
674
675
676 /*
677 **  The "quit" command.  Acknowledge, and set the state to closing down.
678 */
679 static void
680 NCquit(CHANNEL *cp)
681 {
682     cp->State = CSwritegoodbye;
683     NCwritereply(cp, NNTP_GOODBYE_ACK);
684 }
685
686
687 /*
688 **  The catch-all for reader commands, which should return a different status
689 **  than just "unrecognized command" since a change of state may make them
690 **  available.
691 */
692 static void
693 NC_reader(CHANNEL *cp)
694 {
695     cp->Start = cp->Next;
696     NCwritereply(cp, NNTP_ACCESS);
697 }
698
699
700 /*
701 **  The catch-all for inimplemented commands.
702 */
703 static void
704 NC_unimp(CHANNEL *cp)
705 {
706     char                *p, *q;
707     char                buff[SMBUF];
708
709     /* Nip off the first word. */
710     for (p = q = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
711         continue;
712     cp->Start = cp->Next;
713     *p = '\0';
714     snprintf(buff, sizeof(buff), "%d \"%s\" not implemented; try \"help\".",
715              NNTP_BAD_COMMAND_VAL, MaxLength(q, q));
716     NCwritereply(cp, buff);
717 }
718
719 \f
720
721 /*
722 **  Check whatever data is available on the channel.  If we got the
723 **  full amount (i.e., the command or the whole article) process it.
724 */
725 static void
726 NCproc(CHANNEL *cp)
727 {
728   char          *p, *q;
729   NCDISPATCH    *dp;
730   struct buffer *bp;
731   char          buff[SMBUF];
732   int           i, j;
733   bool          readmore, movedata;
734   ARTDATA       *data = &cp->Data;
735   HDRCONTENT    *hc = data->HdrContent;
736
737   readmore = movedata = false;
738   if (Tracing || cp->Tracing)
739     syslog(L_TRACE, "%s NCproc Used=%lu", CHANname(cp),
740            (unsigned long) cp->In.used);
741
742   bp = &cp->In;
743   if (bp->used == 0)
744     return;
745
746   for ( ; ; ) {
747     if (Tracing || cp->Tracing) {
748       syslog(L_TRACE, "%s cp->Start=%lu cp->Next=%lu bp->Used=%lu",
749         CHANname(cp), (unsigned long) cp->Start, (unsigned long) cp->Next,
750         (unsigned long) bp->used);
751       if (bp->used > 15)
752         syslog(L_TRACE, "%s NCproc state=%d next \"%.15s\"", CHANname(cp),
753           cp->State, &bp->data[cp->Next]);
754     }
755     switch (cp->State) {
756     default:
757       syslog(L_ERROR, "%s internal NCproc state %d", CHANname(cp), cp->State);
758       movedata = false;
759       readmore = true;
760       break;
761
762     case CSwritegoodbye:
763       movedata = false;
764       readmore = true;
765       break;
766
767     case CSgetcmd:
768     case CSgetauth:
769     case CScancel:
770       /* Did we get the whole command, terminated with "\r\n"? */
771       for (i = cp->Next; (i < bp->used) && (bp->data[i] != '\n'); i++) ;
772       if (i == bp->used) {
773         /* Check for too long command. */
774         if ((j = bp->used - cp->Start) > NNTP_STRLEN) {
775           /* Make some room, saving only the last few bytes. */
776           for (p = bp->data, i = 0; i < SAVE_AMT; i++)
777             p[i] = p[bp->used - SAVE_AMT + i];
778           cp->LargeCmdSize += j - SAVE_AMT;
779           bp->used = cp->Next = SAVE_AMT;
780           bp->left = bp->size - SAVE_AMT;
781           cp->Start = 0;
782           cp->State = CSeatcommand;
783           /* above means moving data already */
784           movedata = false;
785         } else {
786           cp->Next = bp->used;
787           /* move data to the begining anyway */
788           movedata = true;
789         }
790         readmore = true;
791         break;
792       }
793       /* i points where '\n" and go forward */
794       cp->Next = ++i;
795       /* never move data so long as "\r\n" is found, since subsequent
796          data may also include command line */
797       movedata = false;
798       readmore = false;
799       if (i - cp->Start < 3) {
800         break;
801       }
802       p = &bp->data[i];
803       if (p[-2] != '\r') { /* probably in an article */
804         char *tmpstr;
805
806         tmpstr = xmalloc(i - cp->Start + 1);
807         memcpy(tmpstr, bp->data + cp->Start, i - cp->Start);
808         tmpstr[i - cp->Start] = '\0';
809         
810         syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
811           MaxLength(tmpstr, tmpstr));
812         free(tmpstr);
813
814         if (++(cp->BadCommands) >= BAD_COMMAND_COUNT) {
815           cp->State = CSwritegoodbye;
816           NCwritereply(cp, NCbadcommand);
817           break;
818         }
819         NCwritereply(cp, NCbadcommand);
820         /* still some data left, go for it */
821         cp->Start = cp->Next;
822         break;
823       }
824
825       q = &bp->data[cp->Start];
826       /* Ignore blank lines. */
827       if (*q == '\0' || i - cp->Start == 2) {
828         cp->Start = cp->Next;
829         break;
830       }
831       p[-2] = '\0';
832       if (Tracing || cp->Tracing)
833         syslog(L_TRACE, "%s < %s", CHANname(cp), q);
834
835       /* We got something -- stop sleeping (in case we were). */
836       SCHANremove(cp);
837       if (cp->Argument != NULL) {
838         free(cp->Argument);
839         cp->Argument = NULL;
840       }
841
842       if (cp->State == CSgetauth) {
843         if (strncasecmp(q, "mode", 4) == 0)
844           NCmode(cp);
845         else
846           NCauthinfo(cp);
847         break;
848       } else if (cp->State == CScancel) {
849         NCcancel(cp);
850         break;
851       }
852
853       /* Loop through the command table. */
854       for (p = q, dp = NCcommands; dp < ARRAY_END(NCcommands); dp++) {
855         if (strncasecmp(p, dp->Name, dp->Size) == 0) {
856           /* ignore the streaming commands if necessary. */
857           if (!StreamingOff || cp->Streaming ||
858             (dp->Function != NCcheck && dp->Function != NCtakethis)) {
859             (*dp->Function)(cp);
860             cp->BadCommands = 0;
861             break;
862           }
863         }
864       }
865       if (dp == ARRAY_END(NCcommands)) {
866         if (++(cp->BadCommands) >= BAD_COMMAND_COUNT)
867             cp->State = CSwritegoodbye;
868         NCwritereply(cp, NCbadcommand);
869         cp->Start = cp->Next;
870
871         /* Channel could have been freed by above NCwritereply if 
872            we're writing-goodbye */
873         if (cp->Type == CTfree)
874           return;
875         for (i = 0; (p = NCquietlist[i]) != NULL; i++)
876           if (strcasecmp(p, q) == 0)
877             break;
878         if (p == NULL)
879           syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
880             MaxLength(q, q));
881       }
882       break;
883
884     case CSgetheader:
885     case CSgetbody:
886     case CSeatarticle:
887       TMRstart(TMR_ARTPARSE);
888       ARTparse(cp);
889       TMRstop(TMR_ARTPARSE);
890       if (cp->State == CSgetbody || cp->State == CSgetheader ||
891               cp->State == CSeatarticle) {
892         if (cp->Next - cp->Start > innconf->datamovethreshold ||
893           (innconf->maxartsize > 0 && cp->Size > innconf->maxartsize)) {
894           /* avoid buffer extention for ever */
895           movedata = true;
896         } else {
897           movedata = false;
898         }
899         readmore = true;
900         break;
901       }
902
903       if (cp->State == CSgotlargearticle) {
904         syslog(L_NOTICE, "%s internal rejecting huge article (%d > %ld)",
905           CHANname(cp), cp->Next - cp->Start, innconf->maxartsize);
906         if (cp->Sendid.size)
907           NCwritereply(cp, cp->Sendid.data);
908         else {
909           snprintf(buff, sizeof(buff),
910                    "%d Article exceeds local limit of %ld bytes",
911                    NNTP_REJECTIT_VAL, innconf->maxartsize);
912           NCwritereply(cp, buff);
913         }
914         cp->State = CSgetcmd;
915         cp->Rejected++;
916         cp->Start = cp->Next;
917
918         /* Write a local cancel entry so nobody else gives it to us. */
919         if (HDR_FOUND(HDR__MESSAGE_ID)) {
920           HDR_PARSE_START(HDR__MESSAGE_ID);
921           if (!HIScheck(History, HDR(HDR__MESSAGE_ID)) &&
922               !InndHisRemember(HDR(HDR__MESSAGE_ID)))
923             syslog(L_ERROR, "%s cant write %s", LogName, HDR(HDR__MESSAGE_ID)); 
924         }
925         /* Clear the work-in-progress entry. */
926         NCclearwip(cp);
927         readmore = false;
928         movedata = false;
929         break;
930       }
931
932       if (*cp->Error != '\0') {
933         cp->Rejected++;
934         cp->State = CSgetcmd;
935         cp->Start = cp->Next;
936         NCclearwip(cp);
937         if (cp->Sendid.size > 3)
938           NCwritereply(cp, cp->Sendid.data);
939         else
940           NCwritereply(cp, cp->Error);
941         readmore = false;
942         movedata = false;
943         break;
944       }
945
946       if (cp->State == CSnoarticle) {
947         /* this should happen when parsing header */
948         cp->Rejected++;
949         cp->State = CSgetcmd;
950         cp->Start = cp->Next;
951         /* Clear the work-in-progress entry. */
952         NCclearwip(cp);
953         if (cp->Sendid.size > 3) { /* We be streaming */
954           cp->Takethis_Err++;
955           snprintf(buff, sizeof(buff), "%d", NNTP_ERR_FAILID_VAL);
956           cp->Sendid.data[0] = buff[0];
957           cp->Sendid.data[1] = buff[1];
958           cp->Sendid.data[2] = buff[2];
959           NCwritereply(cp, cp->Sendid.data);
960         } else
961           NCwritereply(cp, NNTP_REJECTIT_EMPTY);
962         readmore = false;
963         movedata = false;
964         break;
965       }
966     case CSgotarticle: /* in case caming back from pause */
967       /* never move data so long as "\r\n.\r\n" is found, since subsequent data
968          may also include command line */
969       readmore = false;
970       movedata = false;
971       if (Mode == OMpaused) { /* defer processing while paused */
972         RCHANremove(cp); /* don't bother trying to read more for now */
973         SCHANadd(cp, Now.time + innconf->pauseretrytime, &Mode, NCproc, NULL);
974         return;
975       } else if (Mode == OMthrottled) {
976         /* Clear the work-in-progress entry. */
977         NCclearwip(cp);
978         NCwriteshutdown(cp, ModeReason);
979         cp->Rejected++;
980         return;
981       }
982
983       SCHANremove(cp);
984       if (cp->Argument != NULL) {
985         free(cp->Argument);
986         cp->Argument = NULL;
987       }
988       NCpostit(cp);
989       /* Clear the work-in-progress entry. */
990       NCclearwip(cp);
991       if (cp->State == CSwritegoodbye)
992         break;
993       cp->State = CSgetcmd;
994       cp->Start = cp->Next;
995       break;
996
997     case CSeatcommand:
998       /* Eat the command line and then complain that it was too large */
999       /* Reading a line; look for "\r\n" terminator. */
1000       /* cp->Next should be SAVE_AMT(10) */
1001       for (i = cp->Next ; i < bp->used; i++) {
1002         if ((bp->data[i - 1] == '\r') && (bp->data[i] == '\n')) {
1003           cp->Next = i + 1;
1004           break;
1005         }
1006       }
1007       if (i < bp->used) {       /* did find terminator */
1008         /* Reached the end of the command line. */
1009         SCHANremove(cp);
1010         if (cp->Argument != NULL) {
1011           free(cp->Argument);
1012           cp->Argument = NULL;
1013         }
1014         i += cp->LargeCmdSize;
1015         syslog(L_NOTICE, "%s internal rejecting too long command line (%d > %d)",
1016           CHANname(cp), i, NNTP_STRLEN);
1017         cp->LargeCmdSize = 0;
1018         snprintf(buff, sizeof(buff), "%d command exceeds limit of %d bytes",
1019                  NNTP_BAD_COMMAND_VAL, NNTP_STRLEN);
1020         cp->State = CSgetcmd;
1021         cp->Start = cp->Next;
1022         NCwritereply(cp, buff);
1023         readmore = false;
1024         movedata = false;
1025       } else {
1026         cp->LargeCmdSize += bp->used - cp->Next;
1027         bp->used = cp->Next = SAVE_AMT;
1028         bp->left = bp->size - SAVE_AMT;
1029         cp->Start = 0;
1030         readmore = true;
1031         movedata = false;
1032       }
1033       break;
1034
1035     case CSgetxbatch:
1036       /* if the batch is complete, write it out into the in.coming
1037        * directory with an unique timestamp, and start rnews on it.
1038        */
1039       if (Tracing || cp->Tracing)
1040         syslog(L_TRACE, "%s CSgetxbatch: now %lu of %d bytes", CHANname(cp),
1041           (unsigned long) bp->used, cp->XBatchSize);
1042
1043       if (cp->Next != 0) {
1044         /* data must start from the begining of the buffer */
1045         movedata = true;
1046         readmore = false;
1047         break;
1048       }
1049       if (bp->used < cp->XBatchSize) {
1050         movedata = false;
1051         readmore = true;
1052         break;  /* give us more data */
1053       }
1054       movedata = false;
1055       readmore = false;
1056
1057       /* now do something with the batch */
1058       {
1059         char buff2[SMBUF];
1060         int fd, oerrno, failed;
1061         long now;
1062
1063         now = time(NULL);
1064         failed = 0;
1065         /* time+channel file descriptor should make an unique file name */
1066         snprintf(buff, sizeof(buff), "%s/%ld%d.tmp", innconf->pathincoming,
1067                  now, cp->fd);
1068         fd = open(buff, O_WRONLY|O_CREAT|O_EXCL, ARTFILE_MODE);
1069         if (fd < 0) {
1070           oerrno = errno;
1071           failed = 1;
1072           syslog(L_ERROR, "%s cannot open outfile %s for xbatch: %m",
1073             CHANname(cp), buff);
1074           snprintf(buff, sizeof(buff), "%s cant create file: %s",
1075                    NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1076           NCwritereply(cp, buff);
1077         } else {
1078           if (write(fd, cp->In.data, cp->XBatchSize) != cp->XBatchSize) {
1079             oerrno = errno;
1080             syslog(L_ERROR, "%s cant write batch to file %s: %m", CHANname(cp),
1081               buff);
1082             snprintf(buff, sizeof(buff), "%s cant write batch to file: %s",
1083                      NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1084             NCwritereply(cp, buff);
1085             failed = 1;
1086           }
1087         }
1088         if (fd >= 0 && close(fd) != 0) {
1089           oerrno = errno;
1090           syslog(L_ERROR, "%s error closing batch file %s: %m", CHANname(cp),
1091             failed ? "" : buff);
1092           snprintf(buff, sizeof(buff), "%s error closing batch file: %s",
1093                    NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1094           NCwritereply(cp, buff);
1095           failed = 1;
1096         }
1097         snprintf(buff2, sizeof(buff2), "%s/%ld%d.x", innconf->pathincoming,
1098                  now, cp->fd);
1099         if (rename(buff, buff2)) {
1100           oerrno = errno;
1101           syslog(L_ERROR, "%s cant rename %s to %s: %m", CHANname(cp),
1102             failed ? "" : buff, buff2);
1103           snprintf(buff, sizeof(buff), "%s cant rename batch to %s: %s",
1104                    NNTP_RESENDIT_XBATCHERR, buff2, strerror(oerrno));
1105           NCwritereply(cp, buff);
1106           failed = 1;
1107         }
1108         cp->Reported++;
1109         if (!failed) {
1110           NCwritereply(cp, NNTP_OK_XBATCHED);
1111           cp->Received++;
1112         } else
1113           cp->Rejected++;
1114       }
1115       syslog(L_NOTICE, "%s accepted batch size %d", CHANname(cp),
1116         cp->XBatchSize);
1117       cp->State = CSgetcmd;
1118       cp->Start = cp->Next = cp->XBatchSize;
1119       break;
1120     }
1121     if (cp->State == CSwritegoodbye || cp->Type == CTfree)
1122       break;
1123     if (Tracing || cp->Tracing)
1124       syslog(L_TRACE, "%s NCproc state=%d Start=%lu Next=%lu Used=%lu",
1125         CHANname(cp), cp->State, (unsigned long) cp->Start,
1126         (unsigned long) cp->Next, (unsigned long) bp->used);
1127
1128     if (movedata) { /* move data rather than extend buffer */
1129       TMRstart(TMR_DATAMOVE);
1130       movedata = false;
1131       if (cp->Start > 0)
1132         memmove(bp->data, &bp->data[cp->Start], bp->used - cp->Start);
1133       bp->used -= cp->Start;
1134       bp->left += cp->Start;
1135       cp->Next -= cp->Start;
1136       if (cp->State == CSgetheader || cp->State == CSgetbody ||
1137         cp->State == CSeatarticle) {
1138         /* adjust offset only in CSgetheader, CSgetbody or CSeatarticle */
1139         data->CurHeader -= cp->Start;
1140         data->LastTerminator -= cp->Start;
1141         data->LastCR -= cp->Start;
1142         data->LastCRLF -= cp->Start;
1143         data->Body -= cp->Start;
1144         if (data->BytesHeader != NULL)
1145           data->BytesHeader -= cp->Start;
1146         for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
1147           if (hc->Value != NULL)
1148             hc->Value -= cp->Start;
1149         }
1150       }
1151       cp->Start = 0;
1152       TMRstop(TMR_DATAMOVE);
1153     }
1154     if (readmore)
1155       /* need to read more */
1156       break;
1157   }
1158 }
1159
1160
1161 /*
1162 **  Read whatever data is available on the channel.  If we got the
1163 **  full amount (i.e., the command or the whole article) process it.
1164 */
1165 static void
1166 NCreader(CHANNEL *cp)
1167 {
1168     int                 i;
1169
1170     if (Tracing || cp->Tracing)
1171         syslog(L_TRACE, "%s NCreader Used=%lu",
1172             CHANname(cp), (unsigned long) cp->In.used);
1173
1174     /* Read any data that's there; ignore errors (retry next time it's our
1175      * turn) and if we got nothing, then it's EOF so mark it closed. */
1176     if ((i = CHANreadtext(cp)) <= 0) {
1177         /* Return of -2 indicates we got EAGAIN even though the descriptor
1178            selected true for reading, probably due to the Solaris select
1179            bug.  Drop back out to the main loop as if the descriptor never
1180            selected true. */
1181         if (i == -2) {
1182             return;
1183         }
1184         if (i == 0 || cp->BadReads++ >= innconf->badiocount) {
1185             if (NCcount > 0)
1186                 NCcount--;
1187             CHANclose(cp, CHANname(cp));
1188         }
1189         return;
1190     }
1191
1192     NCproc(cp);     /* check and process data */
1193 }
1194
1195
1196
1197 /*
1198 **  Set up the NNTP channel state.
1199 */
1200 void
1201 NCsetup(void)
1202 {
1203     char                *p;
1204     char                buff[SMBUF];
1205
1206     /* Set the greeting message. */
1207     p = innconf->pathhost;
1208     if (p == NULL)
1209         /* Worked in main, now it fails?  Curious. */
1210         p = Path.data;
1211     snprintf(buff, sizeof(buff), "%d %s InterNetNews server %s ready",
1212             NNTP_POSTOK_VAL, p, inn_version_string);
1213     NCgreeting = xstrdup(buff);
1214 }
1215
1216
1217 /*
1218 **  Tear down our state.
1219 */
1220 void
1221 NCclose(void)
1222 {
1223     CHANNEL             *cp;
1224     int                 j;
1225
1226     /* Close all incoming channels. */
1227     for (j = 0; (cp = CHANiter(&j, CTnntp)) != NULL; ) {
1228         if (NCcount > 0)
1229             NCcount--;
1230         CHANclose(cp, CHANname(cp));
1231     }
1232 }
1233
1234
1235 /*
1236 **  Create an NNTP channel and print the greeting message.
1237 */
1238 CHANNEL *
1239 NCcreate(int fd, bool MustAuthorize, bool IsLocal)
1240 {
1241     CHANNEL             *cp;
1242     int                 i;
1243
1244     /* Create the channel. */
1245     cp = CHANcreate(fd, CTnntp, MustAuthorize ? CSgetauth : CSgetcmd,
1246             NCreader, NCwritedone);
1247
1248     NCclearwip(cp);
1249     cp->privileged = IsLocal;
1250 #if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) 
1251     if (!IsLocal) {
1252         i = 24 * 1024;
1253         if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
1254             syslog(L_ERROR, "%s cant setsockopt(SNDBUF) %m", CHANname(cp));
1255         if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
1256             syslog(L_ERROR, "%s cant setsockopt(RCVBUF) %m", CHANname(cp));
1257     }
1258 #endif  /* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
1259
1260 #if     defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
1261     if (!IsLocal) {
1262         /* Set KEEPALIVE to catch broken socket connections. */
1263         i = 1;
1264         if (setsockopt(fd, SOL_SOCKET,  SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
1265             syslog(L_ERROR, "%s cant setsockopt(KEEPALIVE) %m", CHANname(cp));
1266     }
1267 #endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */
1268
1269     /* Now check our operating mode. */
1270     NCcount++;
1271     if (Mode == OMthrottled) {
1272         NCwriteshutdown(cp, ModeReason);
1273         return NULL;
1274     }
1275     if (RejectReason) {
1276         NCwriteshutdown(cp, RejectReason);
1277         return NULL;
1278     }
1279
1280     /* See if we have too many channels. */
1281     if (!IsLocal && innconf->maxconnections && 
1282                         NCcount >= innconf->maxconnections && !RCnolimit(cp)) {
1283         /* Recount, just in case we got out of sync. */
1284         for (NCcount = 0, i = 0; CHANiter(&i, CTnntp) != NULL; )
1285             NCcount++;
1286         if (NCcount >= innconf->maxconnections) {
1287             NCwriteshutdown(cp, "Too many connections");
1288             return NULL;
1289         }
1290     }
1291     cp->BadReads = 0;
1292     cp->BadCommands = 0;
1293     return cp;
1294 }
1295
1296
1297
1298 /* These modules support the streaming option to tranfer articles
1299 ** faster.
1300 */
1301
1302 /*
1303 **  The "check" command.  Check the Message-ID, and see if we want the
1304 **  article or not.  Stay in command state.
1305 */
1306 static void
1307 NCcheck(CHANNEL *cp)
1308 {
1309     char                *p;
1310     int                 idlen, msglen;
1311 #if defined(DO_PERL) || defined(DO_PYTHON)
1312     char                *filterrc;
1313 #endif /* DO_PERL || DO_PYTHON */
1314
1315     cp->Check++;
1316     /* Snip off the Message-ID. */
1317     for (p = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
1318         continue;
1319     cp->Start = cp->Next;
1320     for ( ; ISWHITE(*p); p++)
1321         continue;
1322     idlen = strlen(p);
1323     msglen = idlen + 5; /* 3 digits + space + id + null */
1324     if (cp->Sendid.size < msglen) {
1325         if (cp->Sendid.size > 0) free(cp->Sendid.data);
1326         if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
1327         else cp->Sendid.size = MAXHEADERSIZE;
1328         cp->Sendid.data = xmalloc(cp->Sendid.size);
1329     }
1330     if (!ARTidok(p)) {
1331         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1332                  NNTP_ERR_GOTID_VAL, p);
1333         NCwritereply(cp, cp->Sendid.data);
1334         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
1335         return;
1336     }
1337
1338     if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
1339         cp->Refused++;
1340         cp->Check_cybercan++;
1341         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1342                  NNTP_ERR_GOTID_VAL, p);
1343         NCwritereply(cp, cp->Sendid.data);
1344         return;
1345     }
1346
1347 #if defined(DO_PERL)
1348     /*  Invoke a perl message filter on the message ID. */
1349     filterrc = PLmidfilter(p);
1350     if (filterrc) {
1351         cp->Refused++;
1352         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1353                  NNTP_ERR_GOTID_VAL, p);
1354         NCwritereply(cp, cp->Sendid.data);
1355         return;
1356     }
1357 #endif /* defined(DO_PERL) */
1358
1359 #if defined(DO_PYTHON)
1360     /*  invoke a python message filter on the message id */
1361     filterrc = PYmidfilter(p, idlen);
1362     if (filterrc) {
1363         cp->Refused++;
1364         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1365                  NNTP_ERR_GOTID_VAL, p);
1366         NCwritereply(cp, cp->Sendid.data);
1367         return;
1368     }
1369 #endif /* defined(DO_PYTHON) */
1370
1371     if (HIScheck(History, p)) {
1372         cp->Refused++;
1373         cp->Check_got++;
1374         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1375                  NNTP_ERR_GOTID_VAL, p);
1376         NCwritereply(cp, cp->Sendid.data);
1377     } else if (WIPinprogress(p, cp, true)) {
1378         cp->Check_deferred++;
1379         if (cp->NoResendId) {
1380             cp->Refused++;
1381             snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1382                      NNTP_ERR_GOTID_VAL, p);
1383         } else {
1384             snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1385                      NNTP_RESENDID_VAL, p);
1386         }
1387         NCwritereply(cp, cp->Sendid.data);
1388     } else {
1389         cp->Check_send++;
1390         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1391                  NNTP_OK_SENDID_VAL, p);
1392         NCwritereply(cp, cp->Sendid.data);
1393     }
1394     /* stay in command mode */
1395 }
1396
1397 /*
1398 **  The "takethis" command.  Article follows.
1399 **  Remember <id> for later ack.
1400 */
1401 static void
1402 NCtakethis(CHANNEL *cp)
1403 {
1404     char                *p;
1405     int                 msglen;
1406     WIP                 *wp;
1407
1408     cp->Takethis++;
1409     /* Snip off the Message-ID. */
1410     for (p = cp->In.data + cp->Start + strlen("takethis"); ISWHITE(*p); p++)
1411         continue;
1412     cp->Start = cp->Next;
1413     for ( ; ISWHITE(*p); p++)
1414         continue;
1415     if (!ARTidok(p)) {
1416         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
1417     }
1418     msglen = strlen(p) + 5; /* 3 digits + space + id + null */
1419     if (cp->Sendid.size < msglen) {
1420         if (cp->Sendid.size > 0) free(cp->Sendid.data);
1421         if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
1422         else cp->Sendid.size = MAXHEADERSIZE;
1423         cp->Sendid.data = xmalloc(cp->Sendid.size);
1424     }
1425     /* save ID for later NACK or ACK */
1426     snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s", NNTP_ERR_FAILID_VAL,
1427              p);
1428
1429     cp->ArtBeg = Now.time;
1430     cp->State = CSgetheader;
1431     ARTprepare(cp);
1432     /* set WIP for benefit of later code in NCreader */
1433     if ((wp = WIPbyid(p)) == (WIP *)NULL)
1434         wp = WIPnew(p, cp);
1435     cp->CurrentMessageIDHash = wp->MessageID;
1436 }
1437
1438 /*
1439 **  Process a cancel ID from a "mode cancel" channel.
1440 */
1441 static void
1442 NCcancel(CHANNEL *cp)
1443 {
1444     char *av[2] = { NULL, NULL };
1445     const char *res;
1446
1447     ++cp->Received;
1448     av[0] = cp->In.data + cp->Start;
1449     cp->Start = cp->Next;
1450     res = CCcancel(av);
1451     if (res) {
1452         char buff[SMBUF];
1453
1454         snprintf(buff, sizeof(buff), "%d %s", NNTP_ERR_CANCEL_VAL,
1455                  MaxLength(res, res));
1456         syslog(L_NOTICE, "%s cant_cancel %s", CHANname(cp),
1457                MaxLength(res, res));
1458         NCwritereply(cp, buff);
1459     } else {
1460         NCwritereply(cp, NNTP_OK_CANCELLED);
1461     }
1462 }