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