1 /*  $Id: nc.c 10305 2018-12-02 14:21:56Z iulius $
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 "inn/qio.h"
12 #include "inn/version.h"
13 #include "innd.h"
14 
15 #define BAD_COMMAND_COUNT	10
16 
17 
18 extern bool laxmid;
19 
20 /*
21 **  An entry in the dispatch table.  The name, and implementing function,
22 **  of every command we support.
23 */
24 typedef struct _NCDISPATCH {
25     const char *             Name;
26     innd_callback_nntp_func  Function;
27     bool                     Needauth;
28     int                      Minac;
29     int                      Maxac;
30     bool                     Stripspaces;
31     const char *             Help;
32 } NCDISPATCH;
33 
34 /* The functions that implement the various commands. */
35 static void NCauthinfo       (CHANNEL *cp);
36 static void NCcancel         (CHANNEL *cp);
37 static void NCcapabilities   (CHANNEL *cp);
38 static void NCcheck          (CHANNEL *cp);
39 static void NChead           (CHANNEL *cp);
40 static void NChelp           (CHANNEL *cp);
41 static void NCihave          (CHANNEL *cp);
42 static void NClist           (CHANNEL *cp);
43 static void NCmode           (CHANNEL *cp);
44 static void NCquit           (CHANNEL *cp);
45 static void NCstat           (CHANNEL *cp);
46 static void NCtakethis       (CHANNEL *cp);
47 static void NCxbatch         (CHANNEL *cp);
48 
49 /* Handlers for unimplemented commands.  We need two handlers so that we can
50    return the right status code; reader commands that are required by the
51    standard must return a 401 response code rather than a 500 error. */
52 static void NC_reader        (CHANNEL *cp);
53 static void NC_unimp         (CHANNEL *cp);
54 
55 /* Supporting functions. */
56 static void NCwritedone      (CHANNEL *cp);
57 
58 /* Set up the dispatch table for all of the commands. */
59 #define NC_any -1
60 #define COMMAND(name, func, auth, min, max, strip, help) { name, func, auth, min, max, strip, help }
61 #define COMMAND_READER(name) { name, NC_reader, false, 1, NC_any, true, NULL }
62 #define COMMAND_UNIMP(name)  { name, NC_unimp,  false, 1, NC_any, true, NULL }
63 static NCDISPATCH NCcommands[] = {
64     COMMAND("AUTHINFO",      NCauthinfo,      false, 3,  3, false,
65             "USER name|PASS password"),
66     COMMAND("CAPABILITIES",  NCcapabilities,  false, 1,  2, true,
67             "[keyword]"),
68     COMMAND("CHECK",         NCcheck,         true,  2,  2, false,
69             "message-ID"),
70     COMMAND("HEAD",          NChead,          true,  1,  2, true,
71             "message-ID"),
72     COMMAND("HELP",          NChelp,          false, 1,  1, true,
73             NULL),
74     COMMAND("IHAVE",         NCihave,         true,  2,  2, true,
75             "message-ID"),
76     COMMAND("LIST",          NClist,          true,  1,  3, true,
77             "[ACTIVE [wildmat]|ACTIVE.TIMES [wildmat]|MOTD|NEWSGROUPS [wildmat]]"),
78     COMMAND("MODE",          NCmode,          false, 2,  2, true,
79             "READER"),
80     COMMAND("QUIT",          NCquit,          false, 1,  1, true,
81             NULL),
82     COMMAND("STAT",          NCstat,          true,  1,  2, true,
83             "message-ID"),
84     COMMAND("TAKETHIS",      NCtakethis,      true,  2,  2, false,
85             "message-ID"),
86     COMMAND("XBATCH",        NCxbatch,        true,  2,  2, true,
87             "size"),
88 
89     /* Unimplemented reader commands which may become available after a MODE
90        READER command. */
91     COMMAND_READER("ARTICLE"),
92     COMMAND_READER("BODY"),
93 #if defined(HAVE_ZLIB)
94     COMMAND_READER("COMPRESS"),
95 #endif /* HAVE_ZLIB */
96     COMMAND_READER("DATE"),
97     COMMAND_READER("GROUP"),
98     COMMAND_READER("HDR"),
99     COMMAND_READER("LAST"),
100     COMMAND_READER("LISTGROUP"),
101     COMMAND_READER("NEWGROUPS"),
102     COMMAND_READER("NEWNEWS"),
103     COMMAND_READER("NEXT"),
104     COMMAND_READER("OVER"),
105     COMMAND_READER("POST"),
106 #ifdef HAVE_OPENSSL
107     COMMAND_READER("STARTTLS"),
108 #endif
109     COMMAND_READER("XGTITLE"),
110     COMMAND_READER("XHDR"),
111     COMMAND_READER("XOVER"),
112     COMMAND_READER("XPAT"),
113 
114     /* Other unimplemented standard commands.
115        SLAVE (which was ill-defined in RFC 977) was removed from the NNTP
116        protocol in RFC 3977. */
117     COMMAND_UNIMP("SLAVE")
118 };
119 #undef COMMAND
120 
121 /* Number of open connections. */
122 static unsigned long NCcount;
123 
124 static char		*NCquietlist[] = { INND_QUIET_BADLIST };
125 static const char	NCterm[] = "\r\n";
126 static const char 	NCdot[] = "." ;
127 
128 /*
129 ** Clear the WIP entry for the given channel.
130 */
131 void
NCclearwip(CHANNEL * cp)132 NCclearwip(CHANNEL *cp)
133 {
134     WIPfree(WIPbyhash(cp->CurrentMessageIDHash));
135     HashClear(&cp->CurrentMessageIDHash);
136     cp->ArtBeg = 0;
137 }
138 
139 /*
140 **  Write an NNTP reply message.
141 **
142 **  Tries to do the actual write immediately if it will not block and if there
143 **  is not already other buffered output.  Then, if the write is successful,
144 **  calls NCwritedone (which does whatever is necessary to accommodate state
145 **  changes).  Else, NCwritedone will be called from the main select loop
146 **  later.
147 **
148 **  If the reply that we are writing now is associated with a state change,
149 **  then cp->State must be set to its new value *before* NCwritereply is
150 **  called.
151 */
152 void
NCwritereply(CHANNEL * cp,const char * text)153 NCwritereply(CHANNEL *cp, const char *text)
154 {
155     struct buffer *bp;
156     int i;
157 
158     /* XXX could do RCHANremove(cp) here, as the old NCwritetext() used to
159      * do, but that would be wrong if the channel is streaming (because it
160      * would zap the channel's input buffer).  There's no harm in
161      * never calling RCHANremove here.  */
162 
163     bp = &cp->Out;
164     i = bp->left;
165     WCHANappend(cp, text, strlen(text));	/* Text in buffer. */
166     WCHANappend(cp, NCterm, strlen(NCterm));	/* Add CR LF to text. */
167 
168     if (i == 0) {	/* If only new data, then try to write directly. */
169 	i = write(cp->fd, &bp->data[bp->used], bp->left);
170 	if (Tracing || cp->Tracing)
171 	    syslog(L_TRACE, "%s NCwritereply %d=write(%d, \"%.15s\", %lu)",
172 		CHANname(cp), i, cp->fd, &bp->data[bp->used],
173 		(unsigned long) bp->left);
174 	if (i > 0)
175             bp->used += i;
176 	if (bp->used == bp->left) {
177 	    /* All the data was written. */
178 	    bp->used = bp->left = 0;
179 	    NCwritedone(cp);
180 	} else {
181             if (i > 0) {
182                 bp->left -= i;
183             }
184             i = 0;
185         }
186     } else {
187         i = 0;
188     }
189     if (i <= 0) {	/* Write failed, queue it for later. */
190 	WCHANadd(cp);
191     }
192     if (Tracing || cp->Tracing)
193 	syslog(L_TRACE, "%s > %s", CHANname(cp), text);
194 }
195 
196 /*
197 **  Tell the NNTP channel to go away.
198 */
199 void
NCwriteshutdown(CHANNEL * cp,const char * text)200 NCwriteshutdown(CHANNEL *cp, const char *text)
201 {
202     char buff[SMBUF];
203 
204     snprintf(buff, sizeof(buff), "%d %s%s", NNTP_FAIL_TERMINATING, text,
205              NCterm);
206 
207     cp->State = CSwritegoodbye;
208     RCHANremove(cp); /* We're not going to read anything more. */
209     WCHANappend(cp, buff, strlen(buff));
210     WCHANadd(cp);
211 }
212 
213 
214 /*
215 **  We have an entire article collected; try to post it.  If we're
216 **  not running, drop the article or just pause and reschedule.
217 */
218 static void
NCpostit(CHANNEL * cp)219 NCpostit(CHANNEL *cp)
220 {
221   const char	*response;
222   char	buff[SMBUF];
223 
224   if (Mode == OMthrottled) {
225     cp->Reported++;
226     NCwriteshutdown(cp, ModeReason);
227     return;
228   } else if (Mode == OMpaused) {
229     cp->Reported++;
230     if (cp->Sendid.size > 3) {
231       /* In streaming mode, there is no NNTP_FAIL_TAKETHIS_DEFER and RFC 4644
232        * mentions that we MUST send 400 here and close the connection so
233        * as not to reject the article.
234        * Yet, we could have sent NNTP_FAIL_ACTION without closing the
235        * connection... */
236       cp->State = CSwritegoodbye;
237       snprintf(buff, sizeof(buff), "%d %s", NNTP_FAIL_TERMINATING, ModeReason);
238     } else {
239       cp->State = CSgetcmd;
240       snprintf(buff, sizeof(buff), "%d %s", NNTP_FAIL_IHAVE_DEFER, ModeReason);
241     }
242     NCwritereply(cp, buff);
243     return;
244   }
245 
246   /* Return an error without trying to post the article if the TAKETHIS
247    * command was not correct in the first place (code which does not start
248    * with a '2'). */
249   if ((cp->Sendid.size > 3) && (cp->Sendid.data[0] != NNTP_CLASS_OK)) {
250     cp->State = CSgetcmd;
251     NCwritereply(cp, cp->Sendid.data);
252     return;
253   }
254 
255   /* Note that some use break, some use return here. */
256   if (ARTpost(cp)) {
257     cp->Received++;
258     if (cp->Sendid.size > 3) { /* We are streaming. */
259       cp->Takethis_Ok++;
260       snprintf(buff, sizeof(buff), "%d", NNTP_OK_TAKETHIS);
261       cp->Sendid.data[0] = buff[0];
262       cp->Sendid.data[1] = buff[1];
263       cp->Sendid.data[2] = buff[2];
264       response = cp->Sendid.data;
265     } else {
266       snprintf(buff, sizeof(buff), "%d Article transferred OK", NNTP_OK_IHAVE);
267       response = buff;
268     }
269   } else {
270     /* The answer to TAKETHIS is a response code followed by a message-ID. */
271     if (cp->Sendid.size > 3) {
272       snprintf(buff, sizeof(buff), "%d", NNTP_FAIL_TAKETHIS_REJECT);
273       cp->Sendid.data[0] = buff[0];
274       cp->Sendid.data[1] = buff[1];
275       cp->Sendid.data[2] = buff[2];
276       response = cp->Sendid.data;
277     } else {
278       response = cp->Error;
279     }
280   }
281   cp->Reported++;
282   if (cp->Reported >= innconf->incominglogfrequency) {
283     syslog(L_NOTICE,
284            "%s checkpoint seconds %ld accepted %ld refused %ld rejected %ld duplicate %ld"
285            " accepted size %.0f duplicate size %.0f rejected size %.0f",
286            CHANname(cp),
287            (long)(Now.tv_sec - cp->Started_checkpoint),
288            cp->Received - cp->Received_checkpoint,
289            cp->Refused - cp->Refused_checkpoint,
290            cp->Rejected - cp->Rejected_checkpoint,
291            cp->Duplicate - cp->Duplicate_checkpoint,
292            (double) (cp->Size - cp->Size_checkpoint),
293            (double) (cp->DuplicateSize - cp->DuplicateSize_checkpoint),
294            (double) (cp->RejectSize - cp->RejectSize_checkpoint));
295     cp->Reported = 0;
296     cp->Started_checkpoint = Now.tv_sec;
297     cp->Received_checkpoint = cp->Received;
298     cp->Refused_checkpoint = cp->Refused;
299     cp->Rejected_checkpoint = cp->Rejected;
300     cp->Duplicate_checkpoint = cp->Duplicate;
301     cp->Size_checkpoint = cp->Size;
302     cp->DuplicateSize_checkpoint = cp->DuplicateSize;
303     cp->RejectSize_checkpoint = cp->RejectSize;
304   }
305 
306   cp->State = CSgetcmd;
307   NCwritereply(cp, response);
308 }
309 
310 
311 /*
312 **  Write-done function.  Close down or set state for what we expect to
313 **  read next.
314 */
315 static void
NCwritedone(CHANNEL * cp)316 NCwritedone(CHANNEL *cp)
317 {
318     switch (cp->State) {
319     default:
320 	syslog(L_ERROR, "%s internal NCwritedone state %d",
321 	    CHANname(cp), cp->State);
322 	break;
323 
324     case CSwritegoodbye:
325 	if (NCcount > 0)
326 	    NCcount--;
327 	CHANclose(cp, CHANname(cp));
328 	break;
329 
330     case CSgetcmd:
331     case CSgetheader:
332     case CSgetbody:
333     case CSgetxbatch:
334     case CSgotlargearticle:
335     case CScancel:
336 	RCHANadd(cp);
337 	break;
338     }
339 }
340 
341 
342 
343 /*
344 **  The HEAD command.
345 */
346 static void
NChead(CHANNEL * cp)347 NChead(CHANNEL *cp)
348 {
349     TOKEN		token;
350     ARTHANDLE		*art;
351     char                *buff = NULL;
352 
353     cp->Start = cp->Next;
354 
355     /* No argument given, or an article number. */
356     if (cp->ac == 1 || IsValidArticleNumber(cp->av[1])) {
357         xasprintf(&buff, "%d Not in a newsgroup", NNTP_FAIL_NO_GROUP);
358         NCwritereply(cp, buff);
359         free(buff);
360         return;
361     }
362 
363     if (!IsValidMessageID(cp->av[1], true, laxmid)) {
364         xasprintf(&buff, "%d Syntax error in message-ID", NNTP_ERR_SYNTAX);
365         NCwritereply(cp, buff);
366         free(buff);
367         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp),
368                MaxLength(cp->av[1], cp->av[1]));
369 	return;
370     }
371 
372     if (Mode == OMthrottled) {
373         NCwriteshutdown(cp, ModeReason);
374         return;
375     } else if (Mode == OMpaused) {
376         xasprintf(&buff, "%d %s", NNTP_FAIL_ACTION, ModeReason);
377         NCwritereply(cp, buff);
378         free(buff);
379         return;
380     }
381 
382     /* Get the article token and retrieve it (to make sure
383      * the article is still here). */
384     if (!HISlookup(History, cp->av[1], NULL, NULL, NULL, &token)) {
385         xasprintf(&buff, "%d No such article", NNTP_FAIL_MSGID_NOTFOUND);
386         NCwritereply(cp, buff);
387         free(buff);
388 	return;
389     }
390     if ((art = SMretrieve(token, RETR_HEAD)) == NULL) {
391         xasprintf(&buff, "%d No such article", NNTP_FAIL_MSGID_NOTFOUND);
392         NCwritereply(cp, buff);
393         free(buff);
394 	return;
395     }
396 
397     /* Write it. */
398     xasprintf(&buff, "%d 0 %s head%s", NNTP_OK_HEAD, cp->av[1], NCterm);
399     WCHANappend(cp, buff, strlen(buff));
400     WCHANappend(cp, art->data, art->len);
401 
402     /* Write the terminator. */
403     NCwritereply(cp, NCdot);
404     free(buff);
405     SMfreearticle(art);
406 }
407 
408 
409 /*
410 **  The STAT command.
411 */
412 static void
NCstat(CHANNEL * cp)413 NCstat(CHANNEL *cp)
414 {
415     TOKEN		token;
416     ARTHANDLE		*art;
417     char		*buff = NULL;
418 
419     cp->Start = cp->Next;
420 
421     /* No argument given, or an article number. */
422     if (cp->ac == 1 || IsValidArticleNumber(cp->av[1])) {
423         xasprintf(&buff, "%d Not in a newsgroup", NNTP_FAIL_NO_GROUP);
424         NCwritereply(cp, buff);
425         free(buff);
426         return;
427     }
428 
429     if (!IsValidMessageID(cp->av[1], true, laxmid)) {
430         xasprintf(&buff, "%d Syntax error in message-ID", NNTP_ERR_SYNTAX);
431         NCwritereply(cp, buff);
432         free(buff);
433         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp),
434                MaxLength(cp->av[1], cp->av[1]));
435 	return;
436     }
437 
438     if (Mode == OMthrottled) {
439         NCwriteshutdown(cp, ModeReason);
440         return;
441     } else if (Mode == OMpaused) {
442         xasprintf(&buff, "%d %s", NNTP_FAIL_ACTION, ModeReason);
443         NCwritereply(cp, buff);
444         free(buff);
445         return;
446     }
447 
448     /* Get the article token and retrieve it (to make sure
449      * the article is still here). */
450     if (!HISlookup(History, cp->av[1], NULL, NULL, NULL, &token)) {
451         xasprintf(&buff, "%d No such article", NNTP_FAIL_MSGID_NOTFOUND);
452         NCwritereply(cp, buff);
453         free(buff);
454 	return;
455     }
456     if ((art = SMretrieve(token, RETR_STAT)) == NULL) {
457         xasprintf(&buff, "%d No such article", NNTP_FAIL_MSGID_NOTFOUND);
458         NCwritereply(cp, buff);
459         free(buff);
460 	return;
461     }
462     SMfreearticle(art);
463 
464     /* Write the message. */
465     xasprintf(&buff, "%d 0 %s status", NNTP_OK_STAT, cp->av[1]);
466     NCwritereply(cp, buff);
467     free(buff);
468 }
469 
470 
471 /*
472 **  The AUTHINFO command.
473 */
474 static void
NCauthinfo(CHANNEL * cp)475 NCauthinfo(CHANNEL *cp)
476 {
477     char *buff = NULL;
478     cp->Start = cp->Next;
479 
480     /* Make sure we're getting only AUTHINFO USER/PASS commands. */
481     if (strcasecmp(cp->av[1], "USER") != 0
482         && strcasecmp(cp->av[1], "PASS") != 0) {
483         xasprintf(&buff, "%d Bad AUTHINFO param", NNTP_ERR_SYNTAX);
484         NCwritereply(cp, buff);
485         free(buff);
486         return;
487     }
488 
489     if (cp->IsAuthenticated) {
490         /* 502 if authentication will fail. */
491         if (cp->CanAuthenticate)
492             xasprintf(&buff, "%d Authentication will fail", NNTP_ERR_ACCESS);
493         else
494             xasprintf(&buff, "%d Already authenticated", NNTP_ERR_ACCESS);
495         NCwritereply(cp, buff);
496         free(buff);
497         return;
498     }
499 
500     /* Ignore AUTHINFO USER commands, since we only care about the
501      * password. */
502     if (strcasecmp(cp->av[1], "USER") == 0) {
503         cp->HasSentUsername = true;
504         xasprintf(&buff, "%d Enter password", NNTP_CONT_AUTHINFO);
505         NCwritereply(cp, buff);
506         free(buff);
507 	return;
508     }
509 
510     /* AUTHINFO PASS cannot be sent before AUTHINFO USER. */
511     if (!cp->HasSentUsername) {
512         xasprintf(&buff, "%d Authentication commands issued out of sequence",
513                   NNTP_FAIL_AUTHINFO_REJECT);
514         NCwritereply(cp, buff);
515         free(buff);
516         return;
517     }
518 
519     /* Got the password -- is it okay? */
520     if (!RCauthorized(cp, cp->av[2])) {
521         xasprintf(&buff, "%d Authentication failed", NNTP_FAIL_AUTHINFO_BAD);
522     } else {
523         xasprintf(&buff, "%d Authentication succeeded", NNTP_OK_AUTHINFO);
524         cp->CanAuthenticate = false;
525         cp->IsAuthenticated = true;
526     }
527     NCwritereply(cp, buff);
528     free(buff);
529 }
530 
531 /*
532 **  The HELP command.
533 **  As MODE STREAM is recognized, we still display MODE when
534 **  noreader is set to true or the server is paused or throttled
535 **  with readerswhenstopped set to false.
536 */
537 static void
NChelp(CHANNEL * cp)538 NChelp(CHANNEL *cp)
539 {
540     static char		LINE1[] = "For more information, contact \"";
541     static char		LINE2[] = "\" at this machine.";
542     char                *buff = NULL;
543     NCDISPATCH		*dp;
544 
545     cp->Start = cp->Next;
546 
547     xasprintf(&buff, "%d Legal commands%s", NNTP_INFO_HELP, NCterm);
548     WCHANappend(cp, buff, strlen(buff));
549     for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++)
550 	if (dp->Function != NC_unimp && dp->Function != NC_reader) {
551             /* Ignore the streaming commands if necessary. */
552             if ((!StreamingOff && cp->Streaming) ||
553                 (dp->Function != NCcheck && dp->Function != NCtakethis)) {
554                 WCHANappend(cp, "  ", 2);
555                 WCHANappend(cp, dp->Name, strlen(dp->Name));
556                 if (dp->Help != NULL) {
557                     WCHANappend(cp, " ", 1);
558                     WCHANappend(cp, dp->Help, strlen(dp->Help));
559                 }
560                 WCHANappend(cp, NCterm, strlen(NCterm));
561             }
562 	}
563     WCHANappend(cp, LINE1, strlen(LINE1));
564     WCHANappend(cp, NEWSMASTER, strlen(NEWSMASTER));
565     WCHANappend(cp, LINE2, strlen(LINE2));
566     WCHANappend(cp, NCterm, strlen(NCterm));
567     NCwritereply(cp, NCdot);
568     free(buff);
569 }
570 
571 /*
572 **  The CAPABILITIES command.
573 */
574 static void
NCcapabilities(CHANNEL * cp)575 NCcapabilities(CHANNEL *cp)
576 {
577     char *buff = NULL;
578 
579     cp->Start = cp->Next;
580 
581     if (cp->ac == 2 && !IsValidKeyword(cp->av[1])) {
582         xasprintf(&buff, "%d Syntax error in keyword", NNTP_ERR_SYNTAX);
583         NCwritereply(cp, buff);
584         free(buff);
585         return;
586     }
587 
588     xasprintf(&buff, "%d Capability list:", NNTP_INFO_CAPABILITIES);
589     NCwritereply(cp, buff);
590     free(buff);
591 
592     WCHANappend(cp, "VERSION 2", 9);
593     WCHANappend(cp, NCterm, strlen(NCterm));
594 
595     xasprintf(&buff, "IMPLEMENTATION %s", INN_VERSION_STRING);
596     NCwritereply(cp, buff);
597     free(buff);
598 
599     if (cp->CanAuthenticate) {
600         WCHANappend(cp, "AUTHINFO", 8);
601         if (!cp->IsAuthenticated)
602             WCHANappend(cp, " USER", 5);
603         WCHANappend(cp, NCterm, strlen(NCterm));
604     }
605 
606     if (cp->IsAuthenticated) {
607         WCHANappend(cp, "IHAVE", 5);
608         WCHANappend(cp, NCterm, strlen(NCterm));
609     }
610 
611     if (cp->IsAuthenticated && !cp->Nolist) {
612         WCHANappend(cp, "LIST ACTIVE ACTIVE.TIMES MOTD NEWSGROUPS", 40);
613         WCHANappend(cp, NCterm, strlen(NCterm));
614     }
615 
616     if (cp->CanAuthenticate && !innconf->noreader
617         && (NNRPReason == NULL || innconf->readerswhenstopped)) {
618         WCHANappend(cp, "MODE-READER", 11);
619         WCHANappend(cp, NCterm, strlen(NCterm));
620     }
621 
622     if (cp->IsAuthenticated && !StreamingOff && cp->Streaming) {
623         WCHANappend(cp, "STREAMING", 9);
624         WCHANappend(cp, NCterm, strlen(NCterm));
625     }
626 
627     if (cp->IsAuthenticated) {
628         WCHANappend(cp, "XBATCH", 6);
629         WCHANappend(cp, NCterm, strlen(NCterm));
630     }
631 
632     NCwritereply(cp, NCdot);
633 }
634 
635 
636 /*
637 **  The IHAVE command.  Check the message-ID, and see if we want the
638 **  article or not.  Set the state appropriately.
639 */
640 static void
NCihave(CHANNEL * cp)641 NCihave(CHANNEL *cp)
642 {
643     char        *buff = NULL;
644 #if defined(DO_PERL) || defined(DO_PYTHON)
645     char	*filterrc = NULL;
646     size_t	msglen;
647 #endif /*defined(DO_PERL) || defined(DO_PYTHON) */
648 
649     cp->Ihave++;
650     cp->Start = cp->Next;
651 
652     if (!IsValidMessageID(cp->av[1], false, laxmid)) {
653         /* Return 435 here instead of 501 for compatibility reasons. */
654         xasprintf(&buff, "%d Syntax error in message-ID", NNTP_FAIL_IHAVE_REFUSE);
655         NCwritereply(cp, buff);
656         free(buff);
657         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp),
658                MaxLength(cp->av[1], cp->av[1]));
659 	return;
660     }
661 
662     if (Mode == OMthrottled) {
663         NCwriteshutdown(cp, ModeReason);
664         return;
665     } else if (Mode == OMpaused) {
666         cp->Ihave_Deferred++;
667         xasprintf(&buff, "%d %s", NNTP_FAIL_IHAVE_DEFER, ModeReason);
668         NCwritereply(cp, buff);
669         free(buff);
670         return;
671     }
672 
673     if ((innconf->refusecybercancels) && (strncmp(cp->av[1], "<cancel.", 8) == 0)) {
674 	cp->Refused++;
675 	cp->Ihave_Cybercan++;
676         xasprintf(&buff, "%d Cyberspam cancel", NNTP_FAIL_IHAVE_REFUSE);
677         NCwritereply(cp, buff);
678         free(buff);
679 	return;
680     }
681 
682 #if defined(DO_PERL)
683     /* Invoke a Perl message filter on the message-ID. */
684     filterrc = PLmidfilter(cp->av[1]);
685     if (filterrc) {
686         cp->Refused++;
687         msglen = strlen(cp->av[1]) + 5; /* 3 digits + space + id + null. */
688         if (cp->Sendid.size < msglen) {
689             if (cp->Sendid.size > 0)
690                 free(cp->Sendid.data);
691             if (msglen > MED_BUFFER)
692                 cp->Sendid.size = msglen;
693             else
694                 cp->Sendid.size = MED_BUFFER;
695             cp->Sendid.data = xmalloc(cp->Sendid.size);
696         }
697         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
698                  NNTP_FAIL_IHAVE_REFUSE, filterrc);
699         NCwritereply(cp, cp->Sendid.data);
700         free(cp->Sendid.data);
701         cp->Sendid.size = 0;
702         return;
703     }
704 #endif
705 
706 #if defined(DO_PYTHON)
707     /* Invoke a Python message filter on the message-ID. */
708     msglen = strlen(cp->av[1]);
709     TMRstart(TMR_PYTHON);
710     filterrc = PYmidfilter(cp->av[1], msglen);
711     TMRstop(TMR_PYTHON);
712     if (filterrc) {
713         cp->Refused++;
714         msglen += 5; /* 3 digits + space + id + null. */
715         if (cp->Sendid.size < msglen) {
716 	    if (cp->Sendid.size > 0)
717 		free(cp->Sendid.data);
718 	    if (msglen > MED_BUFFER)
719 		cp->Sendid.size = msglen;
720 	    else
721 		cp->Sendid.size = MED_BUFFER;
722 	    cp->Sendid.data = xmalloc(cp->Sendid.size);
723 	}
724 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
725                  NNTP_FAIL_IHAVE_REFUSE, filterrc);
726 	NCwritereply(cp, cp->Sendid.data);
727 	free(cp->Sendid.data);
728 	cp->Sendid.size = 0;
729 	return;
730     }
731 #endif
732 
733     if (HIScheck(History, cp->av[1]) || cp->Ignore) {
734 	cp->Refused++;
735 	cp->Ihave_Duplicate++;
736         xasprintf(&buff, "%d Duplicate", NNTP_FAIL_IHAVE_REFUSE);
737         NCwritereply(cp, buff);
738         free(buff);
739     } else if (WIPinprogress(cp->av[1], cp, false)) {
740 	cp->Ihave_Deferred++;
741 	if (cp->NoResendId) {
742 	    cp->Refused++;
743             xasprintf(&buff, "%d Do not resend", NNTP_FAIL_IHAVE_REFUSE);
744             NCwritereply(cp, buff);
745             free(buff);
746 	} else {
747             xasprintf(&buff, "%d Retry later", NNTP_FAIL_IHAVE_DEFER);
748             NCwritereply(cp, buff);
749             free(buff);
750 	}
751     } else {
752 	if (cp->Sendid.size > 0) {
753             free(cp->Sendid.data);
754 	    cp->Sendid.size = 0;
755 	}
756 	cp->Ihave_SendIt++;
757         xasprintf(&buff, "%d Send it", NNTP_CONT_IHAVE);
758         NCwritereply(cp, buff);
759         free(buff);
760 	cp->ArtBeg = Now.tv_sec;
761 	cp->State = CSgetheader;
762 	ARTprepare(cp);
763     }
764 }
765 
766 /*
767 ** The XBATCH command.  Set the state appropriately.
768 */
769 static void
NCxbatch(CHANNEL * cp)770 NCxbatch(CHANNEL *cp)
771 {
772     char *buff = NULL;
773 
774     cp->Start = cp->Next;
775 
776     if (cp->XBatchSize) {
777         syslog(L_FATAL, "NCxbatch(): oops, cp->XBatchSize already set to %d",
778 	       cp->XBatchSize);
779     }
780 
781     cp->XBatchSize = atoi(cp->av[1]);
782     if (Tracing || cp->Tracing)
783         syslog(L_TRACE, "%s will read batch of size %d",
784 	       CHANname(cp), cp->XBatchSize);
785 
786     if (cp->XBatchSize <= 0 || ((innconf->maxartsize != 0) &&
787                                 (innconf->maxartsize < (unsigned long) cp->XBatchSize))) {
788         syslog(L_NOTICE, "%s got bad xbatch size %d",
789                CHANname(cp), cp->XBatchSize);
790         xasprintf(&buff, "%d Invalid size for XBATCH", NNTP_ERR_SYNTAX);
791         NCwritereply(cp, buff);
792         free(buff);
793         return;
794     }
795 
796     /* We prefer not to touch the buffer; NCreader() does enough magic
797      * with it. */
798     cp->State = CSgetxbatch;
799     xasprintf(&buff, "%d Send batch", NNTP_CONT_XBATCH);
800     NCwritereply(cp, buff);
801     free(buff);
802 }
803 
804 /*
805 **  The LIST command.  Send relevant lines of required file.
806 */
807 static void
NClist(CHANNEL * cp)808 NClist(CHANNEL *cp)
809 {
810     QIOSTATE    *qp;
811     char        *p, *path, *save;
812     char        savec;
813     char        *buff = NULL;
814     bool        checkutf8 = false;
815 
816     cp->Start = cp->Next;
817 
818     if (cp->Nolist) {
819         /* Even authenticated, a peer that has nolist: set will not
820          * be able to use the LIST command. */
821         if (!cp->CanAuthenticate || innconf->noreader
822             || (NNRPReason != NULL && !innconf->readerswhenstopped))
823             xasprintf(&buff, "%d Permission denied", NNTP_ERR_ACCESS);
824         else
825             xasprintf(&buff, "%d MODE-READER", NNTP_FAIL_WRONG_MODE);
826         NCwritereply(cp, buff);
827         free(buff);
828 	return;
829     }
830 
831     /* ACTIVE when no argument given. */
832     if (cp->ac == 1 || (strcasecmp(cp->av[1], "ACTIVE") == 0)) {
833         path = concatpath(innconf->pathdb, INN_PATH_ACTIVE);
834         qp = QIOopen(path);
835         free(path);
836         if (qp == NULL) {
837             xasprintf(&buff, "%d No list of active newsgroups available",
838                       NNTP_ERR_UNAVAILABLE);
839             NCwritereply(cp, buff);
840             free(buff);
841             return;
842         } else {
843             xasprintf(&buff, "%d Newsgroups in form \"group high low status\"",
844                       NNTP_OK_LIST);
845             NCwritereply(cp, buff);
846             free(buff);
847         }
848     } else if (strcasecmp(cp->av[1], "NEWSGROUPS") == 0) {
849         path = concatpath(innconf->pathdb, INN_PATH_NEWSGROUPS);
850 	qp = QIOopen(path);
851         free(path);
852 	if (qp == NULL) {
853             xasprintf(&buff, "%d No list of newsgroup descriptions available",
854                       NNTP_ERR_UNAVAILABLE);
855             NCwritereply(cp, buff);
856             free(buff);
857 	    return;
858 	} else {
859             xasprintf(&buff, "%d Newsgroup descriptions in form \"group description\"",
860                       NNTP_OK_LIST);
861             NCwritereply(cp, buff);
862             free(buff);
863         }
864     } else if (strcasecmp(cp->av[1], "ACTIVE.TIMES") == 0) {
865         path = concatpath(innconf->pathdb, INN_PATH_ACTIVETIMES);
866 	qp = QIOopen(path);
867         free(path);
868 	if (qp == NULL) {
869             xasprintf(&buff, "%d No list of newsgroup creation times available",
870                       NNTP_ERR_UNAVAILABLE);
871             NCwritereply(cp, buff);
872             free(buff);
873 	    return;
874 	} else {
875             xasprintf(&buff, "%d Newsgroup creation times in form \"group time who\"",
876                       NNTP_OK_LIST);
877             NCwritereply(cp, buff);
878             free(buff);
879         }
880     } else if (strcasecmp(cp->av[1], "MOTD") == 0) {
881         checkutf8 = true;
882         if (cp->ac > 2) {
883             xasprintf(&buff, "%d Unexpected wildmat or argument",
884                       NNTP_ERR_SYNTAX);
885             NCwritereply(cp, buff);
886             free(buff);
887             return;
888         }
889         path = concatpath(innconf->pathetc, INN_PATH_MOTD_INND);
890         qp = QIOopen(path);
891         free(path);
892         if (qp == NULL) {
893             xasprintf(&buff, "%d No message of the day available",
894                       NNTP_ERR_UNAVAILABLE);
895             NCwritereply(cp, buff);
896             free(buff);
897             return;
898         } else {
899             xasprintf(&buff, "%d Message of the day text in UTF-8",
900                       NNTP_OK_LIST);
901             NCwritereply(cp, buff);
902             free(buff);
903         }
904     } else {
905         xasprintf(&buff, "%d Unknown LIST keyword", NNTP_ERR_SYNTAX);
906         NCwritereply(cp, buff);
907         free(buff);
908 	return;
909     }
910 
911     /* Loop over all lines, sending the text and "\r\n". */
912     while ((p = QIOread(qp)) != NULL) {
913         /* Check that the output does not break the NNTP protocol. */
914         if (p[0] == '.' && p[1] != '.') {
915             syslog(L_ERROR, "%s NClist bad dot-stuffing in file %s",
916                    CHANname(cp), cp->av[1]);
917             continue;
918         }
919 
920         if (checkutf8) {
921             if (!is_valid_utf8(p)) {
922                 syslog(L_ERROR, "%s NClist bad encoding in %s (UTF-8 expected)",
923                        CHANname(cp), cp->av[1]);
924                 continue;
925             }
926         }
927 
928         /* Check whether the newsgroup matches the wildmat pattern,
929          * if given. */
930         if (cp->ac == 3) {
931             savec = '\0';
932             for (save = p; *save != '\0'; save++) {
933                 if (*save == ' ' || *save == '\t') {
934                     savec = *save;
935                     *save = '\0';
936                     break;
937                 }
938             }
939 
940             if (!uwildmat(p, cp->av[2]))
941                 continue;
942 
943             if (savec != '\0')
944                 *save = savec;
945         }
946 
947         /* Write the line. */
948         WCHANappend(cp, p, strlen(p));
949         WCHANappend(cp, NCterm, strlen(NCterm));
950     }
951 
952     QIOclose(qp);
953 
954     /* Write the terminator. */
955     NCwritereply(cp, NCdot);
956 }
957 
958 
959 /*
960 **  The MODE command.  Hand off the channel.
961 */
962 static void
NCmode(CHANNEL * cp)963 NCmode(CHANNEL *cp)
964 {
965     char buff[SMBUF];
966     HANDOFF h;
967 
968     cp->Start = cp->Next;
969 
970     if (strcasecmp(cp->av[1], "READER") == 0 && !innconf->noreader) {
971         /* MODE READER. */
972         syslog(L_NOTICE, "%s NCmode \"MODE READER\" received",
973                CHANname(cp));
974         if (!cp->CanAuthenticate) {
975             /* AUTHINFO has already been successfully used. */
976             snprintf(buff, sizeof(buff), "%d Already authenticated as a feeder",
977                      NNTP_ERR_ACCESS);
978             NCwritereply(cp, buff);
979             return;
980         }
981         if (NNRPReason != NULL && !innconf->readerswhenstopped) {
982             /* Server paused or throttled. */
983             snprintf(buff, sizeof(buff), "%d %s", NNTP_FAIL_ACTION, NNRPReason);
984             NCwritereply(cp, buff);
985             return;
986         } else {
987             /* We will hand off the channel to nnrpd. */
988             h = HOnnrpd;
989         }
990     } else if (strcasecmp(cp->av[1], "STREAM") == 0 &&
991                (!StreamingOff && cp->Streaming)) {
992         /* MODE STREAM. */
993         snprintf(buff, sizeof(buff), "%d Streaming permitted", NNTP_OK_STREAM);
994 	NCwritereply(cp, buff);
995 	syslog(L_NOTICE, "%s NCmode \"MODE STREAM\" received",
996                CHANname(cp));
997         return;
998     } else if (strcasecmp(cp->av[1], "CANCEL") == 0 && cp->privileged) {
999         /* MODE CANCEL */
1000         cp->State = CScancel;
1001         snprintf(buff, sizeof(buff), "%d Cancels permitted", NNTP_OK_MODE_CANCEL);
1002         NCwritereply(cp, buff);
1003         syslog(L_NOTICE, "%s NCmode \"MODE CANCEL\" received",
1004                 CHANname(cp));
1005         return;
1006     } else {
1007         /* Unknown MODE command or readers not allowed. */
1008         snprintf(buff, sizeof(buff), "%d Unknown MODE variant", NNTP_ERR_SYNTAX);
1009         NCwritereply(cp, buff);
1010         syslog(L_NOTICE, "%s bad_command MODE %s", CHANname(cp),
1011                MaxLength(cp->av[1], cp->av[1]));
1012         return;
1013     }
1014 
1015     /* Hand off if reached. */
1016     RChandoff(cp->fd, h);
1017     if (NCcount > 0)
1018         NCcount--;
1019     CHANclose(cp, CHANname(cp));
1020 }
1021 
1022 
1023 /*
1024 **  The QUIT command.  Acknowledge, and set the state to closing down.
1025 */
1026 static void
NCquit(CHANNEL * cp)1027 NCquit(CHANNEL *cp)
1028 {
1029     char buff[SMBUF];
1030 
1031     cp->Start = cp->Next;
1032 
1033     snprintf(buff, sizeof(buff), "%d Bye!", NNTP_OK_QUIT);
1034 
1035     cp->State = CSwritegoodbye;
1036     NCwritereply(cp, buff);
1037 }
1038 
1039 
1040 /*
1041 **  The catch-all for reader commands, which should return a different status
1042 **  than just "unrecognized command" since a change of state may make them
1043 **  available.
1044 */
1045 static void
NC_reader(CHANNEL * cp)1046 NC_reader(CHANNEL *cp)
1047 {
1048     char buff[SMBUF];
1049 
1050     cp->Start = cp->Next;
1051 
1052     if (!cp->CanAuthenticate || innconf->noreader
1053      || (NNRPReason != NULL && !innconf->readerswhenstopped))
1054         snprintf(buff, sizeof(buff), "%d Permission denied",
1055                  NNTP_ERR_ACCESS);
1056     else
1057         snprintf(buff, sizeof(buff), "%d MODE-READER",
1058                  NNTP_FAIL_WRONG_MODE);
1059     NCwritereply(cp, buff);
1060 }
1061 
1062 
1063 /*
1064 **  The catch-all for unimplemented commands.
1065 */
1066 static void
NC_unimp(CHANNEL * cp)1067 NC_unimp(CHANNEL *cp)
1068 {
1069     char buff[SMBUF];
1070 
1071     cp->Start = cp->Next;
1072 
1073     snprintf(buff, sizeof(buff), "%d \"%s\" not implemented; try \"HELP\"",
1074              NNTP_ERR_COMMAND, MaxLength(cp->av[0], cp->av[0]));
1075     NCwritereply(cp, buff);
1076 }
1077 
1078 
1079 
1080 /*
1081 **  Check whatever data is available on the channel.  If we got the
1082 **  full amount (i.e., the command or the whole article) process it.
1083 */
1084 static void
NCproc(CHANNEL * cp)1085 NCproc(CHANNEL *cp)
1086 {
1087   char	        *p, *q;
1088   NCDISPATCH   	*dp;
1089   struct buffer	*bp;
1090   char		buff[NNTP_MAXLEN_COMMAND];  /* For our (long) answers for CHECK/TAKETHIS,
1091                                              * we need at least the length of the command
1092                                              * (512 bytes). */
1093   size_t	i, j;
1094   bool		readmore, movedata;
1095   ARTDATA	*data = &cp->Data;
1096   HDRCONTENT    *hc = data->HdrContent;
1097   char          **v;
1098   bool          validcommandtoolong;
1099   int           syntaxerrorcode = NNTP_ERR_SYNTAX;
1100 
1101   if (Tracing || cp->Tracing)
1102     syslog(L_TRACE, "%s NCproc Used=%lu", CHANname(cp),
1103            (unsigned long) cp->In.used);
1104 
1105   bp = &cp->In;
1106   if (bp->used == 0)
1107     return;
1108 
1109   for ( ; ; ) {
1110     if (Tracing || cp->Tracing) {
1111       syslog(L_TRACE, "%s cp->Start=%lu cp->Next=%lu bp->Used=%lu",
1112         CHANname(cp), (unsigned long) cp->Start, (unsigned long) cp->Next,
1113         (unsigned long) bp->used);
1114       if (bp->used > 15)
1115 	syslog(L_TRACE, "%s NCproc state=%d next \"%.15s\"", CHANname(cp),
1116 	  cp->State, &bp->data[cp->Next]);
1117     }
1118     switch (cp->State) {
1119     default:
1120       syslog(L_ERROR, "%s internal NCproc state %d", CHANname(cp), cp->State);
1121       movedata = false;
1122       readmore = true;
1123       break;
1124 
1125     case CSwritegoodbye:
1126       movedata = false;
1127       readmore = true;
1128       break;
1129 
1130     case CSgetcmd:
1131     case CScancel:
1132       /* Did we get the whole command, terminated with "\r\n"? */
1133       for (i = cp->Next; (i < bp->used) && (bp->data[i] != '\n'); i++) ;
1134       if (i == bp->used) {
1135 	/* Check for too long command. */
1136 	if ((j = bp->used - cp->Start) > NNTP_MAXLEN_COMMAND) {
1137 	  /* Make some room, saving only the last few bytes. */
1138 	  for (p = bp->data, i = 0; i < SAVE_AMT; i++)
1139 	    p[i] = p[bp->used - SAVE_AMT + i];
1140 	  cp->LargeCmdSize += j - SAVE_AMT;
1141 	  bp->used = cp->Next = SAVE_AMT;
1142 	  bp->left = bp->size - SAVE_AMT;
1143 	  cp->Start = 0;
1144 	  cp->State = CSeatcommand;
1145 	  /* Above means moving data already. */
1146 	  movedata = false;
1147 	} else {
1148 	  cp->Next = bp->used;
1149 	  /* Move data to the beginning anyway. */
1150 	  movedata = true;
1151 	}
1152 	readmore = true;
1153 	break;
1154       }
1155       /* i points where "\n" is; go forward. */
1156       cp->Next = ++i;
1157 
1158       /* Never move data so long as "\r\n" is found, since subsequent
1159        * data may also include a command line. */
1160       movedata = false;
1161       readmore = false;
1162 
1163       /* Ignore too small lines. */
1164       if (i - cp->Start < 3) {
1165         cp->Start = cp->Next;
1166 	break;
1167       }
1168 
1169       p = &bp->data[i];
1170       q = &bp->data[cp->Start];
1171 
1172       /* Ignore blank lines. */
1173       if (*q == '\0' || i - cp->Start == 2) {
1174 	cp->Start = cp->Next;
1175 	break;
1176       }
1177 
1178       /* Guarantee null-termination.  Usually, "\r\n" is found at the end
1179        * of the command line.  In case we only have "\n", we also accept
1180        * the command. */
1181       if (p[-2] == '\r')
1182           p[-2] = '\0';
1183       else
1184           p[-1] = '\0';
1185 
1186       p = q;
1187       cp->ac = nArgify(p, &cp->av, 1);
1188 
1189       /* Ignore empty lines. */
1190       if (cp->ac == 0) {
1191         cp->Start = cp->Next;
1192         break;
1193       }
1194 
1195       if (Tracing || cp->Tracing)
1196 	syslog(L_TRACE, "%s < %s", CHANname(cp), q);
1197 
1198       /* We got something -- stop sleeping (in case we were). */
1199       SCHANremove(cp);
1200       if (cp->Argument != NULL) {
1201 	free(cp->Argument);
1202 	cp->Argument = NULL;
1203       }
1204 
1205       /* When MODE CANCEL is used... */
1206       if (cp->State == CScancel) {
1207           NCcancel(cp);
1208           break;
1209       }
1210 
1211       /* If the line is too long, we have to make sure that
1212        * no recognized command has been sent. */
1213       validcommandtoolong = false;
1214       if (i - cp->Start > NNTP_MAXLEN_COMMAND) {
1215         for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++) {
1216           if ((dp->Function != NC_unimp) &&
1217               (strcasecmp(cp->av[0], dp->Name) == 0)) {
1218             if ((!StreamingOff && cp->Streaming) ||
1219                 (dp->Function != NCcheck && dp->Function != NCtakethis)) {
1220                 validcommandtoolong = true;
1221             }
1222             /* Return 435/438 instead of 501 to IHAVE/CHECK commands
1223              * for compatibility reasons. */
1224             if (strcasecmp(cp->av[0], "IHAVE") == 0) {
1225               syntaxerrorcode = NNTP_FAIL_IHAVE_REFUSE;
1226             } else if (strcasecmp(cp->av[0], "CHECK") == 0
1227                        && (!StreamingOff && cp->Streaming)) {
1228               syntaxerrorcode = NNTP_FAIL_CHECK_REFUSE;
1229             }
1230           }
1231         }
1232         /* If TAKETHIS, we have to read the entire multi-line response
1233          * block before answering. */
1234         if (strcasecmp(cp->av[0], "TAKETHIS") != 0
1235             || (StreamingOff || !cp->Streaming)) {
1236           if (syntaxerrorcode == NNTP_FAIL_CHECK_REFUSE) {
1237             snprintf(buff, sizeof(buff), "%d %s", syntaxerrorcode,
1238                      cp->ac > 1 ? cp->av[1] : "");
1239           } else {
1240             snprintf(buff, sizeof(buff), "%d Line too long",
1241                      validcommandtoolong ? syntaxerrorcode : NNTP_ERR_COMMAND);
1242           }
1243           NCwritereply(cp, buff);
1244           cp->Start = cp->Next;
1245 
1246           syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
1247                  MaxLength(q, q));
1248           break;
1249         }
1250       }
1251 
1252       /* Loop through the command table. */
1253       for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++) {
1254 	if (strcasecmp(cp->av[0], dp->Name) == 0) {
1255           /* Return 435/438 instead of 501 to IHAVE/CHECK commands
1256            * for compatibility reasons. */
1257           if (strcasecmp(cp->av[0], "IHAVE") == 0) {
1258               syntaxerrorcode = NNTP_FAIL_IHAVE_REFUSE;
1259           } else if (strcasecmp(cp->av[0], "CHECK") == 0
1260                      && (!StreamingOff && cp->Streaming)) {
1261               syntaxerrorcode = NNTP_FAIL_CHECK_REFUSE;
1262           }
1263 	  /* Ignore the streaming commands if necessary. */
1264 	  if ((!StreamingOff && cp->Streaming) ||
1265 	    (dp->Function != NCcheck && dp->Function != NCtakethis)) {
1266 	    break;
1267 	  }
1268 	}
1269       }
1270 
1271       /* If no command has been recognized. */
1272       if (dp == ARRAY_END(NCcommands)) {
1273 	if (++(cp->BadCommands) >= BAD_COMMAND_COUNT) {
1274           cp->State = CSwritegoodbye;
1275           snprintf(buff, sizeof(buff), "%d Too many unrecognized commands",
1276                    NNTP_FAIL_TERMINATING);
1277           NCwritereply(cp, buff);
1278           break;
1279         }
1280         if (strcasecmp(cp->av[0], "XYZZY") == 0) {
1281             /* Acknowledge the magic word from the Colossal Cave Adventure computer game. */
1282             snprintf(buff, sizeof(buff), "%d Nothing happens", NNTP_ERR_COMMAND);
1283         } else {
1284             snprintf(buff, sizeof(buff), "%d What?", NNTP_ERR_COMMAND);
1285         }
1286 	NCwritereply(cp, buff);
1287 	cp->Start = cp->Next;
1288 
1289 	/* Channel could have been freed by above NCwritereply if
1290 	   we're writing-goodbye */
1291 	if (cp->Type == CTfree)
1292 	  return;
1293 	for (i = 0; (p = NCquietlist[i]) != NULL; i++)
1294 	  if (strcasecmp(p, q) == 0)
1295 	    break;
1296 	if (p == NULL)
1297 	  syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
1298 	    MaxLength(q, q));
1299         break;
1300       }
1301 
1302       /* Go on parsing the command.
1303        * For instance:
1304        * - "CHECK     <bad mid>  " will give the message-ID "<bad mid>  "
1305        *   with only leading whitespaces stripped.
1306        * - "AUTHINFO USER  test " will give the username " test " with
1307        *   no whitespaces stripped. */
1308       cp->ac--;
1309       cp->ac += reArgify(cp->av[cp->ac], &cp->av[cp->ac],
1310                      dp->Stripspaces ? -1 : dp->Minac - cp->ac - 1,
1311                      dp->Stripspaces);
1312 
1313       /* Check whether all arguments do not exceed their allowed size. */
1314       if (cp->ac > 1) {
1315           validcommandtoolong = false;
1316           for (v = cp->av; *v; v++)
1317               if (strlen(*v) > NNTP_MAXLEN_ARG) {
1318                   validcommandtoolong = true;
1319                   if (syntaxerrorcode == NNTP_FAIL_CHECK_REFUSE) {
1320                       snprintf(buff, sizeof(buff), "%d %s", syntaxerrorcode,
1321                                cp->ac > 1 ? cp->av[1] : "");
1322                   } else {
1323                       snprintf(buff, sizeof(buff), "%d Argument too long",
1324                                syntaxerrorcode);
1325                   }
1326                   break;
1327               }
1328           if (validcommandtoolong) {
1329               /* If TAKETHIS, we have to read the entire multi-line response
1330                * block before answering. */
1331               if (strcasecmp(cp->av[0], "TAKETHIS") != 0
1332                   || (StreamingOff || !cp->Streaming)) {
1333                   NCwritereply(cp, buff);
1334                   cp->Start = cp->Next;
1335 
1336                   syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
1337                          MaxLength(q, q));
1338                   break;
1339               }
1340           }
1341       }
1342 
1343       /* Check usage. */
1344       if ((dp->Minac != NC_any && cp->ac < dp->Minac)
1345           || (dp->Maxac != NC_any && cp->ac > dp->Maxac)) {
1346           if (syntaxerrorcode == NNTP_FAIL_CHECK_REFUSE) {
1347               snprintf(buff, sizeof(buff), "%d %s", syntaxerrorcode,
1348                        cp->ac > 1 ? cp->av[1] : "");
1349           } else {
1350               snprintf(buff, sizeof(buff), "%d Syntax is:  %s %s",
1351                        syntaxerrorcode, dp->Name, dp->Help ? dp->Help : "(no argument allowed)");
1352           }
1353           /* If TAKETHIS, we have to read the entire multi-line response
1354            * block before answering. */
1355           if (strcasecmp(cp->av[0], "TAKETHIS") != 0
1356               || (StreamingOff || !cp->Streaming)) {
1357               NCwritereply(cp, buff);
1358               cp->Start = cp->Next;
1359 
1360               syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
1361                      MaxLength(q, q));
1362               break;
1363           }
1364       }
1365 
1366       /* Check permissions and dispatch. */
1367       if (dp->Needauth && !cp->IsAuthenticated) {
1368           if (cp->CanAuthenticate) {
1369               snprintf(buff, sizeof(buff),
1370                        "%d Authentication required for command",
1371                        NNTP_FAIL_AUTH_NEEDED);
1372           } else {
1373               snprintf(buff, sizeof(buff),
1374                        "%d Access denied", NNTP_ERR_ACCESS);
1375           }
1376           /* If TAKETHIS, we have to read the entire multi-line response
1377            * block before answering. */
1378           if (strcasecmp(cp->av[0], "TAKETHIS") != 0
1379               || (StreamingOff || !cp->Streaming)) {
1380               NCwritereply(cp, buff);
1381               cp->Start = cp->Next;
1382               break;
1383           }
1384       }
1385 
1386       (*dp->Function)(cp);
1387       cp->BadCommands = 0;
1388 
1389       break;
1390 
1391     case CSgetheader:
1392     case CSgetbody:
1393     case CSeatarticle:
1394       TMRstart(TMR_ARTPARSE);
1395       ARTparse(cp);
1396       TMRstop(TMR_ARTPARSE);
1397       if (cp->State == CSgetbody || cp->State == CSgetheader ||
1398 	      cp->State == CSeatarticle) {
1399         if (cp->Next - cp->Start > innconf->datamovethreshold
1400             || (innconf->maxartsize != 0 && cp->Size > innconf->maxartsize)) {
1401 	  /* avoid buffer extention for ever */
1402 	  movedata = true;
1403 	} else {
1404 	  movedata = false;
1405 	}
1406 	readmore = true;
1407 	break;
1408       }
1409 
1410       /* If error is set, we're rejecting this article.  There is no need
1411        * to call ARTlog() as it has already been done during ARTparse(). */
1412       if (*cp->Error != '\0') {
1413         if (innconf->remembertrash && (Mode == OMrunning)
1414             && HDR_FOUND(HDR__MESSAGE_ID)) {
1415             HDR_LASTCHAR_SAVE(HDR__MESSAGE_ID);
1416             HDR_PARSE_START(HDR__MESSAGE_ID);
1417             /* The article posting time has not been parsed.  We cannot
1418              * give it to InndHisRemember. */
1419             if (!HIScheck(History, HDR(HDR__MESSAGE_ID))
1420                 && !InndHisRemember(HDR(HDR__MESSAGE_ID), 0))
1421                 syslog(L_ERROR, "%s cant write history %s %m",
1422                        LogName, HDR(HDR__MESSAGE_ID));
1423             HDR_PARSE_END(HDR__MESSAGE_ID);
1424         }
1425 	ARTreject(REJECT_OTHER, cp);
1426 	cp->State = CSgetcmd;
1427 	cp->Start = cp->Next;
1428 	NCclearwip(cp);
1429         /* The answer to TAKETHIS is a response code followed by a
1430          * message-ID. */
1431 	if (cp->Sendid.size > 3) {
1432           /* Return the right response code if the TAKETHIS command
1433            * was not correct in the first place (code which does not
1434            * start with a '2').  Otherwise, change it to reject the
1435            * article. */
1436           if (cp->Sendid.data[0] == NNTP_CLASS_OK) {
1437             snprintf(buff, sizeof(buff), "%d", NNTP_FAIL_TAKETHIS_REJECT);
1438             cp->Sendid.data[0] = buff[0];
1439             cp->Sendid.data[1] = buff[1];
1440             cp->Sendid.data[2] = buff[2];
1441           }
1442 	  NCwritereply(cp, cp->Sendid.data);
1443         } else {
1444 	  NCwritereply(cp, cp->Error);
1445         }
1446 	readmore = false;
1447 	movedata = false;
1448 	break;
1449       }
1450       /* fall thru */
1451     case CSgotarticle: /* in case caming back from pause */
1452       /* never move data so long as "\r\n.\r\n" is found, since subsequent data
1453 	 may also include command line */
1454       readmore = false;
1455       movedata = false;
1456       if (Mode == OMthrottled) {
1457         /* We do not remember the message-ID of this article because it has not
1458          * been stored. */
1459         ARTreject(REJECT_OTHER, cp);
1460         ARTlogreject(cp, ModeReason);
1461 	/* Clear the work-in-progress entry. */
1462 	NCclearwip(cp);
1463 	NCwriteshutdown(cp, ModeReason);
1464 	return;
1465       }
1466 
1467       SCHANremove(cp);
1468       if (cp->Argument != NULL) {
1469 	free(cp->Argument);
1470 	cp->Argument = NULL;
1471       }
1472       NCpostit(cp);
1473       /* Clear the work-in-progress entry. */
1474       NCclearwip(cp);
1475       if (cp->State == CSwritegoodbye)
1476 	break;
1477       cp->State = CSgetcmd;
1478       cp->Start = cp->Next;
1479       break;
1480 
1481     case CSeatcommand:
1482       /* Eat the command line and then complain that it was too large */
1483       /* Reading a line; look for "\r\n" terminator. */
1484       /* cp->Next should be SAVE_AMT(10) */
1485       for (i = cp->Next ; i < bp->used; i++) {
1486 	if ((bp->data[i - 1] == '\r') && (bp->data[i] == '\n')) {
1487 	  cp->Next = i + 1;
1488 	  break;
1489 	}
1490       }
1491       if (i < bp->used) {	/* did find terminator */
1492 	/* Reached the end of the command line. */
1493 	SCHANremove(cp);
1494 	if (cp->Argument != NULL) {
1495 	  free(cp->Argument);
1496 	  cp->Argument = NULL;
1497 	}
1498 	i += cp->LargeCmdSize;
1499 	syslog(L_NOTICE, "%s internal rejecting too long command line (%lu > %d)",
1500 	  CHANname(cp), (unsigned long) i, NNTP_MAXLEN_COMMAND);
1501 	cp->LargeCmdSize = 0;
1502         /* Command eaten; we do not know whether it was valid (500 or 501). */
1503 	snprintf(buff, sizeof(buff), "%d command exceeds limit of %d bytes",
1504                  NNTP_ERR_COMMAND, NNTP_MAXLEN_COMMAND);
1505 	cp->State = CSgetcmd;
1506 	cp->Start = cp->Next;
1507 	NCwritereply(cp, buff);
1508         readmore = false;
1509         movedata = false;
1510       } else {
1511 	cp->LargeCmdSize += bp->used - cp->Next;
1512 	bp->used = cp->Next = SAVE_AMT;
1513 	bp->left = bp->size - SAVE_AMT;
1514 	cp->Start = 0;
1515         readmore = true;
1516         movedata = false;
1517       }
1518       break;
1519 
1520     case CSgetxbatch:
1521       /* if the batch is complete, write it out into the in.coming
1522        * directory with an unique timestamp, and start rnews on it.
1523        */
1524       if (Tracing || cp->Tracing)
1525 	syslog(L_TRACE, "%s CSgetxbatch: now %lu of %d bytes", CHANname(cp),
1526 	  (unsigned long) bp->used, cp->XBatchSize);
1527 
1528       if (cp->Next != 0) {
1529 	/* data must start from the beginning of the buffer */
1530         movedata = true;
1531 	readmore = false;
1532 	break;
1533       }
1534       if (bp->used < (size_t) cp->XBatchSize) {
1535 	movedata = false;
1536 	readmore = true;
1537 	break;	/* give us more data */
1538       }
1539       movedata = false;
1540       readmore = false;
1541 
1542       /* now do something with the batch */
1543       {
1544 	char buff2[SMBUF];
1545 	int fd, oerrno, failed;
1546 	long now;
1547 
1548 	now = time(NULL);
1549 	failed = 0;
1550 	/* time+channel file descriptor should make an unique file name */
1551 	snprintf(buff, sizeof(buff), "%s/%ld%d.tmp", innconf->pathincoming,
1552                  now, cp->fd);
1553 	fd = open(buff, O_WRONLY|O_CREAT|O_EXCL, ARTFILE_MODE);
1554 	if (fd < 0) {
1555 	  oerrno = errno;
1556 	  failed = 1;
1557 	  syslog(L_ERROR, "%s cannot open outfile %s for xbatch: %m",
1558 	    CHANname(cp), buff);
1559           snprintf(buff, sizeof(buff), "%d XBATCH failed -- cant create file: %s",
1560                    NNTP_FAIL_XBATCH, strerror(oerrno));
1561 	  NCwritereply(cp, buff);
1562 	} else {
1563 	  if (write(fd, cp->In.data, cp->XBatchSize) != cp->XBatchSize) {
1564 	    oerrno = errno;
1565 	    syslog(L_ERROR, "%s cant write batch to file %s: %m", CHANname(cp),
1566 	      buff);
1567             snprintf(buff, sizeof(buff), "%d XBATCH failed -- cant write batch to file: %s",
1568                      NNTP_FAIL_XBATCH, strerror(oerrno));
1569 	    NCwritereply(cp, buff);
1570 	    failed = 1;
1571 	  }
1572 	}
1573 	if (fd >= 0 && close(fd) != 0) {
1574 	  oerrno = errno;
1575 	  syslog(L_ERROR, "%s error closing batch file %s: %m", CHANname(cp),
1576 	    failed ? "" : buff);
1577           snprintf(buff, sizeof(buff), "%d XBATCH failed -- error closing batch file: %s",
1578                    NNTP_FAIL_XBATCH, strerror(oerrno));
1579 	  NCwritereply(cp, buff);
1580 	  failed = 1;
1581 	}
1582 	snprintf(buff2, sizeof(buff2), "%s/%ld%d.x", innconf->pathincoming,
1583                  now, cp->fd);
1584 	if (rename(buff, buff2)) {
1585 	  oerrno = errno;
1586 	  syslog(L_ERROR, "%s cant rename %s to %s: %m", CHANname(cp),
1587 	    failed ? "" : buff, buff2);
1588           snprintf(buff, sizeof(buff), "%d XBATCH failed -- cant rename batch to %s: %s",
1589                    NNTP_FAIL_XBATCH, buff2, strerror(oerrno));
1590 	  NCwritereply(cp, buff);
1591 	  failed = 1;
1592 	}
1593 	cp->Reported++;
1594 	if (!failed) {
1595           snprintf(buff, sizeof(buff), "%d Batch transferred OK",
1596                    NNTP_OK_XBATCH);
1597           NCwritereply(cp, buff);
1598 	  cp->Received++;
1599 	} else {
1600           /* Only reject, no call to ARTlog() because it will not be
1601            * useful for XBATCH -- errors were logged to news.err. */
1602           ARTreject(REJECT_OTHER, cp);
1603         }
1604       }
1605       syslog(L_NOTICE, "%s accepted batch size %d", CHANname(cp),
1606 	cp->XBatchSize);
1607       cp->State = CSgetcmd;
1608       cp->Start = cp->Next = cp->XBatchSize;
1609       break;
1610     }
1611 
1612     if (cp->State == CSwritegoodbye || cp->Type == CTfree)
1613       break;
1614     if (Tracing || cp->Tracing)
1615       syslog(L_TRACE, "%s NCproc state=%d Start=%lu Next=%lu Used=%lu",
1616 	CHANname(cp), cp->State, (unsigned long) cp->Start,
1617         (unsigned long) cp->Next, (unsigned long) bp->used);
1618 
1619     if (movedata) { /* move data rather than extend buffer */
1620       TMRstart(TMR_DATAMOVE);
1621       if (cp->Start > 0)
1622 	memmove(bp->data, &bp->data[cp->Start], bp->used - cp->Start);
1623       bp->used -= cp->Start;
1624       bp->left += cp->Start;
1625       cp->Next -= cp->Start;
1626       if (cp->State == CSgetheader || cp->State == CSgetbody ||
1627 	cp->State == CSeatarticle) {
1628 	/* adjust offset only in CSgetheader, CSgetbody or CSeatarticle */
1629 	data->CurHeader -= cp->Start;
1630 	data->Body -= cp->Start;
1631 	if (data->BytesHeader != NULL)
1632 	  data->BytesHeader -= cp->Start;
1633 	for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
1634 	  if (hc->Value != NULL)
1635 	    hc->Value -= cp->Start;
1636 	}
1637       }
1638       cp->Start = 0;
1639       TMRstop(TMR_DATAMOVE);
1640     }
1641     if (readmore) {
1642         /* need to read more */
1643         break;
1644     }
1645   }
1646 }
1647 
1648 
1649 /*
1650 **  Read whatever data is available on the channel.  If we got the
1651 **  full amount (i.e., the command or the whole article) process it.
1652 */
1653 static void
NCreader(CHANNEL * cp)1654 NCreader(CHANNEL *cp)
1655 {
1656     int			i;
1657 
1658     if (Tracing || cp->Tracing)
1659 	syslog(L_TRACE, "%s NCreader Used=%lu",
1660 	    CHANname(cp), (unsigned long) cp->In.used);
1661 
1662     /* Read any data that's there; ignore errors (retry next time it's our
1663      * turn) and if we got nothing, then it's EOF so mark it closed. */
1664     if ((i = CHANreadtext(cp)) <= 0) {
1665         /* Return of -2 indicates we got EAGAIN even though the descriptor
1666            selected true for reading, probably due to the Solaris select
1667            bug.  Drop back out to the main loop as if the descriptor never
1668            selected true. */
1669         if (i == -2) {
1670             return;
1671         }
1672 	if (i == 0 || cp->BadReads++ >= innconf->badiocount) {
1673 	    if (NCcount > 0)
1674 		NCcount--;
1675 	    CHANclose(cp, CHANname(cp));
1676 	}
1677 	return;
1678     }
1679 
1680     NCproc(cp);	    /* check and process data */
1681 }
1682 
1683 
1684 /*
1685 **  Set up the NNTP channel state.
1686 */
1687 void
NCsetup(void)1688 NCsetup(void)
1689 {
1690     char		*p;
1691     char		buff[SMBUF];
1692 
1693     /* Set the greeting message.  We always send 200 because we cannot know
1694      * for sure that the client will not be able to use POST during its
1695      * entire session. */
1696     p = innconf->pathhost;
1697     if (p == NULL)
1698 	/* Worked in main, now it fails?  Curious. */
1699 	p = Path.data;
1700     snprintf(buff, sizeof(buff), "%d %s InterNetNews server %s ready (transit mode)",
1701              NNTP_OK_BANNER_POST, p, INN_VERSION_STRING);
1702     NCgreeting = xstrdup(buff);
1703 }
1704 
1705 
1706 /*
1707 **  Tear down our state.
1708 */
1709 void
NCclose(void)1710 NCclose(void)
1711 {
1712     CHANNEL		*cp;
1713     int			j;
1714 
1715     /* Close all incoming channels. */
1716     for (j = 0; (cp = CHANiter(&j, CTnntp)) != NULL; ) {
1717 	if (NCcount > 0)
1718 	    NCcount--;
1719 	CHANclose(cp, CHANname(cp));
1720     }
1721 }
1722 
1723 
1724 /*
1725 **  Create an NNTP channel and print the greeting message.
1726 */
1727 CHANNEL *
NCcreate(int fd,bool MustAuthorize,bool IsLocal)1728 NCcreate(int fd, bool MustAuthorize, bool IsLocal)
1729 {
1730     CHANNEL		*cp;
1731     int			i;
1732 
1733     /* Create the channel. */
1734     cp = CHANcreate(fd, CTnntp, CSgetcmd, NCreader, NCwritedone);
1735 
1736     cp->IsAuthenticated = !MustAuthorize;
1737     cp->HasSentUsername = false;
1738 
1739     NCclearwip(cp);
1740     cp->privileged = IsLocal;
1741 #if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF)
1742     if (!IsLocal) {
1743 	i = 24 * 1024;
1744 	if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
1745 	    syslog(L_ERROR, "%s cant setsockopt(SNDBUF) %m", CHANname(cp));
1746 	if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
1747 	    syslog(L_ERROR, "%s cant setsockopt(RCVBUF) %m", CHANname(cp));
1748     }
1749 #endif	/* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
1750 
1751 #if	defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
1752     if (!IsLocal) {
1753 	/* Set KEEPALIVE to catch broken socket connections. */
1754 	i = 1;
1755 	if (setsockopt(fd, SOL_SOCKET,  SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
1756 	    syslog(L_ERROR, "%s cant setsockopt(KEEPALIVE) %m", CHANname(cp));
1757     }
1758 #endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */
1759 
1760     /* Now check our operating mode. */
1761     NCcount++;
1762     if (Mode == OMthrottled) {
1763 	NCwriteshutdown(cp, ModeReason);
1764 	return NULL;
1765     }
1766     if (RejectReason) {
1767 	NCwriteshutdown(cp, RejectReason);
1768 	return NULL;
1769     }
1770 
1771     /* See if we have too many channels. */
1772     if (!IsLocal && innconf->maxconnections != 0 &&
1773 			NCcount >= innconf->maxconnections && !RCnolimit(cp)) {
1774 	/* Recount, just in case we got out of sync. */
1775 	for (NCcount = 0, i = 0; CHANiter(&i, CTnntp) != NULL; )
1776 	    NCcount++;
1777 	if (NCcount >= innconf->maxconnections) {
1778 	    NCwriteshutdown(cp, "Too many connections");
1779 	    return NULL;
1780 	}
1781     }
1782     cp->BadReads = 0;
1783     cp->BadCommands = 0;
1784     return cp;
1785 }
1786 
1787 
1788 
1789 /*
1790 **  These modules support the streaming option to tranfer articles
1791 **  faster.
1792 */
1793 
1794 /*
1795 **  The CHECK command.  Check the message-ID, and see if we want the
1796 **  article or not.  Stay in command state.
1797 */
1798 static void
NCcheck(CHANNEL * cp)1799 NCcheck(CHANNEL *cp)
1800 {
1801     char                *buff = NULL;
1802     size_t		idlen, msglen;
1803 #if defined(DO_PERL) || defined(DO_PYTHON)
1804     char		*filterrc = NULL;
1805 #endif /* DO_PERL || DO_PYTHON */
1806 
1807     cp->Check++;
1808     cp->Start = cp->Next;
1809 
1810     idlen = strlen(cp->av[1]);
1811     msglen = idlen + 5; /* 3 digits + space + id + null. */
1812     if (cp->Sendid.size < msglen) {
1813         if (cp->Sendid.size > 0)
1814             free(cp->Sendid.data);
1815         if (msglen > MED_BUFFER)
1816             cp->Sendid.size = msglen;
1817         else
1818             cp->Sendid.size = MED_BUFFER;
1819         cp->Sendid.data = xmalloc(cp->Sendid.size);
1820     }
1821     if (!IsValidMessageID(cp->av[1], false, laxmid)) {
1822 	snprintf(cp->Sendid.data, cp->Sendid.size,
1823                  "%d %s Syntax error in message-ID",
1824                  NNTP_FAIL_CHECK_REFUSE, cp->av[1]);
1825 	NCwritereply(cp, cp->Sendid.data);
1826 	syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp),
1827                MaxLength(cp->av[1], cp->av[1]));
1828 	return;
1829     }
1830 
1831     if ((innconf->refusecybercancels) && (strncmp(cp->av[1], "<cancel.", 8) == 0)) {
1832 	cp->Refused++;
1833 	cp->Check_cybercan++;
1834 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s Cyberspam cancel",
1835                  NNTP_FAIL_CHECK_REFUSE, cp->av[1]);
1836 	NCwritereply(cp, cp->Sendid.data);
1837 	return;
1838     }
1839 
1840     if (Mode == OMthrottled) {
1841         NCwriteshutdown(cp, ModeReason);
1842         return;
1843     } else if (Mode == OMpaused) {
1844         cp->Check_deferred++;
1845         xasprintf(&buff, "%d %s %s", NNTP_FAIL_CHECK_DEFER, cp->av[1],
1846                   ModeReason);
1847         NCwritereply(cp, buff);
1848         free(buff);
1849         return;
1850     }
1851 
1852 #if defined(DO_PERL)
1853     /* Invoke a Perl message filter on the message-ID. */
1854     filterrc = PLmidfilter(cp->av[1]);
1855     if (filterrc) {
1856 	cp->Refused++;
1857 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s %.200s",
1858                  NNTP_FAIL_CHECK_REFUSE, cp->av[1], filterrc);
1859 	NCwritereply(cp, cp->Sendid.data);
1860 	return;
1861     }
1862 #endif /* defined(DO_PERL) */
1863 
1864 #if defined(DO_PYTHON)
1865     /* Invoke a Python message filter on the message-ID. */
1866     filterrc = PYmidfilter(cp->av[1], idlen);
1867     if (filterrc) {
1868 	cp->Refused++;
1869 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s %.200s",
1870                  NNTP_FAIL_CHECK_REFUSE, cp->av[1], filterrc);
1871 	NCwritereply(cp, cp->Sendid.data);
1872 	return;
1873     }
1874 #endif /* defined(DO_PYTHON) */
1875 
1876     if (HIScheck(History, cp->av[1]) || cp->Ignore) {
1877 	cp->Refused++;
1878 	cp->Check_got++;
1879 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s Duplicate",
1880                  NNTP_FAIL_CHECK_REFUSE, cp->av[1]);
1881 	NCwritereply(cp, cp->Sendid.data);
1882     } else if (WIPinprogress(cp->av[1], cp, true)) {
1883 	cp->Check_deferred++;
1884 	if (cp->NoResendId) {
1885 	    cp->Refused++;
1886 	    snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s Do not resend",
1887                      NNTP_FAIL_CHECK_REFUSE, cp->av[1]);
1888 	} else {
1889 	    snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s Retry later",
1890                      NNTP_FAIL_CHECK_DEFER, cp->av[1]);
1891 	}
1892 	NCwritereply(cp, cp->Sendid.data);
1893     } else {
1894 	cp->Check_send++;
1895 	snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s Send it",
1896                  NNTP_OK_CHECK, cp->av[1]);
1897 	NCwritereply(cp, cp->Sendid.data);
1898     }
1899     /* Stay in command mode. */
1900 }
1901 
1902 /*
1903 **  The TAKETHIS command.  Article follows.
1904 **  Remember <id> for later ack.
1905 */
1906 static void
NCtakethis(CHANNEL * cp)1907 NCtakethis(CHANNEL *cp)
1908 {
1909     char    *mid;
1910     static char empty[] = "";
1911     int     returncode; /* Will *not* be changed in NCpostit()
1912                            if it does *not* start with '2'. */
1913     size_t  idlen, msglen;
1914     WIP     *wp;
1915 #if defined(DO_PERL) || defined(DO_PYTHON)
1916     char    *filterrc = NULL;
1917 #endif /* DO_PERL || DO_PYTHON */
1918 
1919     cp->Takethis++;
1920     cp->Start = cp->Next;
1921 
1922     /* Check the syntax and authentication here because
1923      * it is not done before (TAKETHIS has to eat
1924      * the whole multi-line block before responding). */
1925     if (cp->ac == 1) {
1926         mid = empty;
1927         returncode = NNTP_FAIL_TAKETHIS_REJECT;
1928     } else {
1929         mid = cp->av[1];
1930         returncode = NNTP_OK_TAKETHIS; /* Default code. */
1931     }
1932 
1933     idlen = strlen(mid);
1934     msglen = idlen + 5; /* 3 digits + space + id + null. */
1935 
1936     if (!IsValidMessageID(mid, false, laxmid)) {
1937         syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp),
1938                MaxLength(mid, mid));
1939         returncode = NNTP_FAIL_TAKETHIS_REJECT;
1940     } else {
1941 #if defined(DO_PERL)
1942         /* Invoke a Perl message filter on the message-ID. */
1943         filterrc = PLmidfilter(mid);
1944         if (filterrc) {
1945             returncode = NNTP_FAIL_TAKETHIS_REJECT;
1946         }
1947 #endif /* defined(DO_PERL) */
1948 
1949 #if defined(DO_PYTHON)
1950         /* Invoke a Python message filter on the message-ID. */
1951         filterrc = PYmidfilter(mid, idlen);
1952         if (filterrc) {
1953             returncode = NNTP_FAIL_TAKETHIS_REJECT;
1954         }
1955 #endif /* defined(DO_PYTHON) */
1956     }
1957 
1958     /* Check authentication after everything else. */
1959     if (!cp->IsAuthenticated) {
1960         returncode = cp->CanAuthenticate ?
1961             NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS;
1962     }
1963 
1964     if (cp->Sendid.size < msglen) {
1965         if (cp->Sendid.size > 0)
1966             free(cp->Sendid.data);
1967         if (msglen > MED_BUFFER)
1968             cp->Sendid.size = msglen;
1969         else
1970             cp->Sendid.size = MED_BUFFER;
1971         cp->Sendid.data = xmalloc(cp->Sendid.size);
1972     }
1973 
1974     /* Save ID for later NACK or ACK. */
1975 #if defined(DO_PERL) || defined(DO_PYTHON)
1976     if (filterrc != NULL) {
1977         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s %.200s",
1978                  returncode, mid, filterrc);
1979     } else {
1980         snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1981                  returncode, mid);
1982     }
1983 #else
1984     snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1985              returncode, mid);
1986 #endif /* defined(DO_PERL) || defined(DO_PYTHON) */
1987 
1988     cp->ArtBeg = Now.tv_sec;
1989     cp->State = CSgetheader;
1990     ARTprepare(cp);
1991     /* Set WIP for benefit of later code in NCreader. */
1992     if ((wp = WIPbyid(mid)) == (WIP *)NULL)
1993 	wp = WIPnew(mid, cp);
1994     cp->CurrentMessageIDHash = wp->MessageID;
1995 }
1996 
1997 /*
1998 **  Process a cancel ID from a MODE CANCEL channel.
1999 */
2000 static void
NCcancel(CHANNEL * cp)2001 NCcancel(CHANNEL *cp)
2002 {
2003     char *argv[2] = { NULL, NULL };
2004     char buff[SMBUF];
2005     const char *res;
2006 
2007     ++cp->Received;
2008     argv[0] = cp->In.data + cp->Start;
2009     cp->Start = cp->Next;
2010     res = CCcancel(argv);
2011     if (res) {
2012         snprintf(buff, sizeof(buff), "%d %s", NNTP_FAIL_CANCEL,
2013                  MaxLength(res, res));
2014         syslog(L_NOTICE, "%s cant_cancel %s", CHANname(cp),
2015                MaxLength(res, res));
2016         NCwritereply(cp, buff);
2017     } else {
2018         snprintf(buff, sizeof(buff), "%d Article cancelled OK",
2019                  NNTP_OK_CANCEL);
2020         NCwritereply(cp, buff);
2021     }
2022 }
2023