1 /* $Id: nc.c 7418 2005-10-09 04:37:05Z eagle $
3 ** Routines for the NNTP channel. Other channels get the descriptors which
4 ** we turn into NNTP channels, and over which we speak NNTP.
10 #include "inn/innconf.h"
13 #define BAD_COMMAND_COUNT 10
17 ** An entry in the dispatch table. The name, and implementing function,
18 ** of every command we support.
20 typedef struct _NCDISPATCH {
22 innd_callback_t Function;
26 /* The functions that implement the various commands. */
27 static void NCauthinfo(CHANNEL *cp);
28 static void NCcancel(CHANNEL *cp);
29 static void NCcheck(CHANNEL *cp);
30 static void NChead(CHANNEL *cp);
31 static void NChelp(CHANNEL *cp);
32 static void NCihave(CHANNEL *cp);
33 static void NClist(CHANNEL *cp);
34 static void NCmode(CHANNEL *cp);
35 static void NCquit(CHANNEL *cp);
36 static void NCstat(CHANNEL *cp);
37 static void NCtakethis(CHANNEL *cp);
38 static void NCxbatch(CHANNEL *cp);
40 /* Handlers for unimplemented commands. We need two handlers so that we can
41 return the right status code; reader commands that are required by the
42 standard must return a 502 error rather than a 500 error. */
43 static void NC_reader(CHANNEL *cp);
44 static void NC_unimp(CHANNEL *cp);
46 /* Supporting functions. */
47 static void NCwritedone(CHANNEL *cp);
49 /* Set up the dispatch table for all of the commands. */
50 #define COMMAND(name, func) { name, func, sizeof(name) - 1 }
51 static NCDISPATCH NCcommands[] = {
52 COMMAND("authinfo", NCauthinfo),
53 COMMAND("check", NCcheck),
54 COMMAND("head", NChead),
55 COMMAND("help", NChelp),
56 COMMAND("ihave", NCihave),
57 COMMAND("list", NClist),
58 COMMAND("mode", NCmode),
59 COMMAND("quit", NCquit),
60 COMMAND("stat", NCstat),
61 COMMAND("takethis", NCtakethis),
62 COMMAND("xbatch", NCxbatch),
64 /* Unimplemented reader commands which may become available after a MODE
66 COMMAND("article", NC_reader),
67 COMMAND("body", NC_reader),
68 COMMAND("group", NC_reader),
69 COMMAND("last", NC_reader),
70 COMMAND("newgroups", NC_reader),
71 COMMAND("newnews", NC_reader),
72 COMMAND("next", NC_reader),
73 COMMAND("post", NC_reader),
75 /* Other unimplemented standard commands. */
76 COMMAND("date", NC_unimp),
77 COMMAND("slave", NC_unimp)
81 /* Number of open connections. */
84 static char *NCquietlist[] = { INND_QUIET_BADLIST };
85 static const char NCterm[] = "\r\n";
86 static const char NCdot[] = "." ;
87 static const char NCbadcommand[] = NNTP_BAD_COMMAND;
88 static const char NCbadsubcommand[] = NNTP_BAD_SUBCMD;
91 ** Clear the WIP entry for the given channel
94 NCclearwip(CHANNEL *cp)
96 WIPfree(WIPbyhash(cp->CurrentMessageIDHash));
97 HashClear(&cp->CurrentMessageIDHash);
102 ** Write an NNTP reply message.
104 ** Tries to do the actual write immediately if it will not block and if there
105 ** is not already other buffered output. Then, if the write is successful,
106 ** calls NCwritedone (which does whatever is necessary to accommodate state
107 ** changes). Else, NCwritedone will be called from the main select loop
110 ** If the reply that we are writing now is associated with a state change,
111 ** then cp->State must be set to its new value *before* NCwritereply is
115 NCwritereply(CHANNEL *cp, const char *text)
120 /* XXX could do RCHANremove(cp) here, as the old NCwritetext() used to
121 * do, but that would be wrong if the channel is sreaming (because it
122 * would zap the channell's input buffer). There's no harm in
123 * never calling RCHANremove here. */
127 WCHANappend(cp, text, strlen(text)); /* text in buffer */
128 WCHANappend(cp, NCterm, strlen(NCterm)); /* add CR NL to text */
130 if (i == 0) { /* if only data then try to write directly */
131 i = write(cp->fd, &bp->data[bp->used], bp->left);
132 if (Tracing || cp->Tracing)
133 syslog(L_TRACE, "%s NCwritereply %d=write(%d, \"%.15s\", %lu)",
134 CHANname(cp), i, cp->fd, &bp->data[bp->used],
135 (unsigned long) bp->left);
138 if (bp->used == bp->left) {
139 /* all the data was written */
140 bp->used = bp->left = 0;
147 if (i <= 0) { /* write failed, queue it for later */
150 if (Tracing || cp->Tracing)
151 syslog(L_TRACE, "%s > %s", CHANname(cp), text);
155 ** Tell the NNTP channel to go away.
158 NCwriteshutdown(CHANNEL *cp, const char *text)
160 cp->State = CSwritegoodbye;
161 RCHANremove(cp); /* we're not going to read anything more */
162 WCHANappend(cp, NNTP_GOODBYE, strlen(NNTP_GOODBYE));
163 WCHANappend(cp, " ", 1);
164 WCHANappend(cp, text, (int)strlen(text));
165 WCHANappend(cp, NCterm, strlen(NCterm));
171 ** If a Message-ID is bad, write a reject message and return true.
174 NCbadid(CHANNEL *cp, char *p)
179 NCwritereply(cp, NNTP_HAVEIT_BADID);
180 syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
186 ** We have an entire article collected; try to post it. If we're
187 ** not running, drop the article or just pause and reschedule.
190 NCpostit(CHANNEL *cp)
193 const char *response;
196 /* Note that some use break, some use return here. */
197 if ((postok = ARTpost(cp)) != 0) {
199 if (cp->Sendid.size > 3) { /* We be streaming */
201 snprintf(buff, sizeof(buff), "%d", NNTP_OK_RECID_VAL);
202 cp->Sendid.data[0] = buff[0];
203 cp->Sendid.data[1] = buff[1];
204 cp->Sendid.data[2] = buff[2];
205 response = cp->Sendid.data;
207 response = NNTP_TOOKIT;
211 response = cp->Sendid.data;
213 response = cp->Error;
216 if (cp->Reported >= innconf->nntpactsync) {
217 snprintf(buff, sizeof(buff), "accepted size %.0f duplicate size %.0f",
218 cp->Size, cp->DuplicateSize);
220 "%s checkpoint seconds %ld accepted %ld refused %ld rejected %ld duplicate %ld %s",
221 CHANname(cp), (long)(Now.time - cp->Started),
222 cp->Received, cp->Refused, cp->Rejected,
223 cp->Duplicate, buff);
226 if (Mode == OMthrottled) {
227 NCwriteshutdown(cp, ModeReason);
230 cp->State = CSgetcmd;
231 NCwritereply(cp, response);
236 ** Write-done function. Close down or set state for what we expect to
240 NCwritedone(CHANNEL *cp)
244 syslog(L_ERROR, "%s internal NCwritedone state %d",
245 CHANname(cp), cp->State);
251 CHANclose(cp, CHANname(cp));
259 case CSgotlargearticle:
269 ** The "head" command.
278 /* Snip off the Message-ID. */
279 for (p = cp->In.data + cp->Start + strlen("head"); ISWHITE(*p); p++)
281 cp->Start = cp->Next;
285 /* Get the article token and retrieve it. */
286 if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
287 NCwritereply(cp, NNTP_DONTHAVEIT);
290 if ((art = SMretrieve(token, RETR_HEAD)) == NULL) {
291 NCwritereply(cp, NNTP_DONTHAVEIT);
296 WCHANappend(cp, NNTP_HEAD_FOLLOWS, strlen(NNTP_HEAD_FOLLOWS));
297 WCHANappend(cp, " 0 ", 3);
298 WCHANappend(cp, p, strlen(p));
299 WCHANappend(cp, NCterm, strlen(NCterm));
300 WCHANappend(cp, art->data, art->len);
302 /* Write the terminator. */
303 NCwritereply(cp, NCdot);
309 ** The "stat" command.
320 /* Snip off the Message-ID. */
321 for (p = cp->In.data + cp->Start + strlen("stat"); ISWHITE(*p); p++)
323 cp->Start = cp->Next;
327 /* Get the article filenames; open the first file (to make sure
328 * the article is still here). */
329 if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
330 NCwritereply(cp, NNTP_DONTHAVEIT);
333 if ((art = SMretrieve(token, RETR_STAT)) == NULL) {
334 NCwritereply(cp, NNTP_DONTHAVEIT);
339 /* Write the message. */
340 length = snprintf(NULL, 0, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p) + 1;
341 buff = xmalloc(length);
342 snprintf(buff, length, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p);
343 NCwritereply(cp, buff);
349 ** The "authinfo" command. Actually, we come in here whenever the
350 ** channel is in CSgetauth state and we just got a command.
353 NCauthinfo(CHANNEL *cp)
355 static char AUTHINFO[] = "authinfo ";
356 static char PASS[] = "pass ";
357 static char USER[] = "user ";
360 p = cp->In.data + cp->Start;
361 cp->Start = cp->Next;
363 /* Allow the poor sucker to quit. */
364 if (strcasecmp(p, "quit") == 0) {
369 /* Otherwise, make sure we're only getting "authinfo" commands. */
370 if (strncasecmp(p, AUTHINFO, strlen(AUTHINFO)) != 0) {
371 NCwritereply(cp, NNTP_AUTH_NEEDED);
374 for (p += strlen(AUTHINFO); ISWHITE(*p); p++)
377 /* Ignore "authinfo user" commands, since we only care about the
379 if (strncasecmp(p, USER, strlen(USER)) == 0) {
380 NCwritereply(cp, NNTP_AUTH_NEXT);
384 /* Now make sure we're getting only "authinfo pass" commands. */
385 if (strncasecmp(p, PASS, strlen(PASS)) != 0) {
386 NCwritereply(cp, NNTP_AUTH_NEEDED);
389 for (p += strlen(PASS); ISWHITE(*p); p++)
392 /* Got the password -- is it okay? */
393 if (!RCauthorized(cp, p)) {
394 cp->State = CSwritegoodbye;
395 NCwritereply(cp, NNTP_AUTH_BAD);
397 cp->State = CSgetcmd;
398 NCwritereply(cp, NNTP_AUTH_OK);
403 ** The "help" command.
408 static char LINE1[] = "For more information, contact \"";
409 static char LINE2[] = "\" at this machine.";
412 WCHANappend(cp, NNTP_HELP_FOLLOWS,strlen(NNTP_HELP_FOLLOWS));
413 WCHANappend(cp, NCterm,strlen(NCterm));
414 for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++)
415 if (dp->Function != NC_unimp) {
416 if ((!StreamingOff && cp->Streaming) ||
417 (dp->Function != NCcheck && dp->Function != NCtakethis)) {
418 WCHANappend(cp, "\t", 1);
419 WCHANappend(cp, dp->Name, dp->Size);
420 WCHANappend(cp, NCterm, strlen(NCterm));
423 WCHANappend(cp, LINE1, strlen(LINE1));
424 WCHANappend(cp, NEWSMASTER, strlen(NEWSMASTER));
425 WCHANappend(cp, LINE2, strlen(LINE2));
426 WCHANappend(cp, NCterm, strlen(NCterm));
427 NCwritereply(cp, NCdot) ;
428 cp->Start = cp->Next;
432 ** The "ihave" command. Check the Message-ID, and see if we want the
433 ** article or not. Set the state appropriately.
439 #if defined(DO_PERL) || defined(DO_PYTHON)
442 #endif /*defined(DO_PERL) || defined(DO_PYTHON) */
445 /* Snip off the Message-ID. */
446 for (p = cp->In.data + cp->Start + strlen("ihave"); ISWHITE(*p); p++)
448 cp->Start = cp->Next;
452 if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
454 cp->Ihave_Cybercan++;
455 NCwritereply(cp, NNTP_HAVEIT);
460 /* Invoke a perl message filter on the message ID. */
461 filterrc = PLmidfilter(p);
464 msglen = strlen(p) + 5; /* 3 digits + space + id + null */
465 if (cp->Sendid.size < msglen) {
466 if (cp->Sendid.size > 0) free(cp->Sendid.data);
467 if (msglen > MAXHEADERSIZE)
468 cp->Sendid.size = msglen;
470 cp->Sendid.size = MAXHEADERSIZE;
471 cp->Sendid.data = xmalloc(cp->Sendid.size);
473 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
474 NNTP_HAVEIT_VAL, filterrc);
475 NCwritereply(cp, cp->Sendid.data);
476 free(cp->Sendid.data);
482 #if defined(DO_PYTHON)
483 /* invoke a Python message filter on the message id */
485 TMRstart(TMR_PYTHON);
486 filterrc = PYmidfilter(p, msglen);
490 msglen += 5; /* 3 digits + space + id + null */
491 if (cp->Sendid.size < msglen) {
492 if (cp->Sendid.size > 0)
493 free(cp->Sendid.data);
494 if (msglen > MAXHEADERSIZE)
495 cp->Sendid.size = msglen;
497 cp->Sendid.size = MAXHEADERSIZE;
498 cp->Sendid.data = xmalloc(cp->Sendid.size);
500 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
501 NNTP_HAVEIT_VAL, filterrc);
502 NCwritereply(cp, cp->Sendid.data);
503 free(cp->Sendid.data);
509 if (HIScheck(History, p)) {
511 cp->Ihave_Duplicate++;
512 NCwritereply(cp, NNTP_HAVEIT);
514 else if (WIPinprogress(p, cp, false)) {
515 cp->Ihave_Deferred++;
516 if (cp->NoResendId) {
518 NCwritereply(cp, NNTP_HAVEIT);
520 NCwritereply(cp, NNTP_RESENDIT_LATER);
524 if (cp->Sendid.size > 0) {
525 free(cp->Sendid.data);
529 NCwritereply(cp, NNTP_SENDIT);
530 cp->ArtBeg = Now.time;
531 cp->State = CSgetheader;
537 ** The "xbatch" command. Set the state appropriately.
541 NCxbatch(CHANNEL *cp)
545 /* Snip off the batch size */
546 for (p = cp->In.data + cp->Start + strlen("xbatch"); ISWHITE(*p); p++)
548 cp->Start = cp->Next;
550 if (cp->XBatchSize) {
551 syslog(L_FATAL, "NCxbatch(): oops, cp->XBatchSize already set to %d",
555 cp->XBatchSize = atoi(p);
556 if (Tracing || cp->Tracing)
557 syslog(L_TRACE, "%s will read batch of size %d",
558 CHANname(cp), cp->XBatchSize);
560 if (cp->XBatchSize <= 0 || ((innconf->maxartsize != 0) && (innconf->maxartsize < cp->XBatchSize))) {
561 syslog(L_NOTICE, "%s got bad xbatch size %d",
562 CHANname(cp), cp->XBatchSize);
563 NCwritereply(cp, NNTP_XBATCH_BADSIZE);
567 /* we prefer not to touch the buffer, NCreader() does enough magic
570 cp->State = CSgetxbatch;
571 NCwritereply(cp, NNTP_CONT_XBATCH);
575 ** The "list" command. Send the active file.
580 char *p, *q, *trash, *end, *path;
582 for (p = cp->In.data + cp->Start + strlen("list"); ISWHITE(*p); p++)
584 cp->Start = cp->Next;
586 NCwritereply(cp, NCbadcommand);
589 if (strcasecmp(p, "newsgroups") == 0) {
590 path = concatpath(innconf->pathdb, _PATH_NEWSGROUPS);
591 trash = p = ReadInFile(path, NULL);
594 NCwritereply(cp, NCdot);
599 else if (strcasecmp(p, "active.times") == 0) {
600 path = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
601 trash = p = ReadInFile(path, NULL);
604 NCwritereply(cp, NCdot);
609 else if (*p == '\0' || (strcasecmp(p, "active") == 0)) {
610 p = ICDreadactive(&end);
614 NCwritereply(cp, NCbadsubcommand);
618 /* Loop over all lines, sending the text and \r\n. */
619 WCHANappend(cp, NNTP_LIST_FOLLOWS,strlen(NNTP_LIST_FOLLOWS));
620 WCHANappend(cp, NCterm, strlen(NCterm)) ;
621 for (; p < end && (q = strchr(p, '\n')) != NULL; p = q + 1) {
622 WCHANappend(cp, p, q - p);
623 WCHANappend(cp, NCterm, strlen(NCterm));
625 NCwritereply(cp, NCdot);
632 ** The "mode" command. Hand off the channel.
640 /* Skip the first word, get the argument. */
641 for (p = cp->In.data + cp->Start + strlen("mode"); ISWHITE(*p); p++)
643 cp->Start = cp->Next;
645 if (strcasecmp(p, "reader") == 0 && !innconf->noreader)
647 else if (strcasecmp(p, "stream") == 0 &&
648 (!StreamingOff && cp->Streaming)) {
651 snprintf(buff, sizeof(buff), "%d StreamOK.", NNTP_OK_STREAM_VAL);
652 NCwritereply(cp, buff);
653 syslog(L_NOTICE, "%s NCmode \"mode stream\" received",
656 } else if (strcasecmp(p, "cancel") == 0 && cp->privileged) {
659 cp->State = CScancel;
660 snprintf(buff, sizeof(buff), "%d CancelOK.", NNTP_OK_CANCEL_VAL);
661 NCwritereply(cp, buff);
662 syslog(L_NOTICE, "%s NCmode \"mode cancel\" received",
666 NCwritereply(cp, NCbadsubcommand);
669 RChandoff(cp->fd, h);
672 CHANclose(cp, CHANname(cp));
677 ** The "quit" command. Acknowledge, and set the state to closing down.
682 cp->State = CSwritegoodbye;
683 NCwritereply(cp, NNTP_GOODBYE_ACK);
688 ** The catch-all for reader commands, which should return a different status
689 ** than just "unrecognized command" since a change of state may make them
693 NC_reader(CHANNEL *cp)
695 cp->Start = cp->Next;
696 NCwritereply(cp, NNTP_ACCESS);
701 ** The catch-all for inimplemented commands.
704 NC_unimp(CHANNEL *cp)
709 /* Nip off the first word. */
710 for (p = q = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
712 cp->Start = cp->Next;
714 snprintf(buff, sizeof(buff), "%d \"%s\" not implemented; try \"help\".",
715 NNTP_BAD_COMMAND_VAL, MaxLength(q, q));
716 NCwritereply(cp, buff);
722 ** Check whatever data is available on the channel. If we got the
723 ** full amount (i.e., the command or the whole article) process it.
733 bool readmore, movedata;
734 ARTDATA *data = &cp->Data;
735 HDRCONTENT *hc = data->HdrContent;
737 readmore = movedata = false;
738 if (Tracing || cp->Tracing)
739 syslog(L_TRACE, "%s NCproc Used=%lu", CHANname(cp),
740 (unsigned long) cp->In.used);
747 if (Tracing || cp->Tracing) {
748 syslog(L_TRACE, "%s cp->Start=%lu cp->Next=%lu bp->Used=%lu",
749 CHANname(cp), (unsigned long) cp->Start, (unsigned long) cp->Next,
750 (unsigned long) bp->used);
752 syslog(L_TRACE, "%s NCproc state=%d next \"%.15s\"", CHANname(cp),
753 cp->State, &bp->data[cp->Next]);
757 syslog(L_ERROR, "%s internal NCproc state %d", CHANname(cp), cp->State);
770 /* Did we get the whole command, terminated with "\r\n"? */
771 for (i = cp->Next; (i < bp->used) && (bp->data[i] != '\n'); i++) ;
773 /* Check for too long command. */
774 if ((j = bp->used - cp->Start) > NNTP_STRLEN) {
775 /* Make some room, saving only the last few bytes. */
776 for (p = bp->data, i = 0; i < SAVE_AMT; i++)
777 p[i] = p[bp->used - SAVE_AMT + i];
778 cp->LargeCmdSize += j - SAVE_AMT;
779 bp->used = cp->Next = SAVE_AMT;
780 bp->left = bp->size - SAVE_AMT;
782 cp->State = CSeatcommand;
783 /* above means moving data already */
787 /* move data to the begining anyway */
793 /* i points where '\n" and go forward */
795 /* never move data so long as "\r\n" is found, since subsequent
796 data may also include command line */
799 if (i - cp->Start < 3) {
803 if (p[-2] != '\r') { /* probably in an article */
806 tmpstr = xmalloc(i - cp->Start + 1);
807 memcpy(tmpstr, bp->data + cp->Start, i - cp->Start);
808 tmpstr[i - cp->Start] = '\0';
810 syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
811 MaxLength(tmpstr, tmpstr));
814 if (++(cp->BadCommands) >= BAD_COMMAND_COUNT) {
815 cp->State = CSwritegoodbye;
816 NCwritereply(cp, NCbadcommand);
819 NCwritereply(cp, NCbadcommand);
820 /* still some data left, go for it */
821 cp->Start = cp->Next;
825 q = &bp->data[cp->Start];
826 /* Ignore blank lines. */
827 if (*q == '\0' || i - cp->Start == 2) {
828 cp->Start = cp->Next;
832 if (Tracing || cp->Tracing)
833 syslog(L_TRACE, "%s < %s", CHANname(cp), q);
835 /* We got something -- stop sleeping (in case we were). */
837 if (cp->Argument != NULL) {
842 if (cp->State == CSgetauth) {
843 if (strncasecmp(q, "mode", 4) == 0)
848 } else if (cp->State == CScancel) {
853 /* Loop through the command table. */
854 for (p = q, dp = NCcommands; dp < ARRAY_END(NCcommands); dp++) {
855 if (strncasecmp(p, dp->Name, dp->Size) == 0) {
856 /* ignore the streaming commands if necessary. */
857 if (!StreamingOff || cp->Streaming ||
858 (dp->Function != NCcheck && dp->Function != NCtakethis)) {
865 if (dp == ARRAY_END(NCcommands)) {
866 if (++(cp->BadCommands) >= BAD_COMMAND_COUNT)
867 cp->State = CSwritegoodbye;
868 NCwritereply(cp, NCbadcommand);
869 cp->Start = cp->Next;
871 /* Channel could have been freed by above NCwritereply if
872 we're writing-goodbye */
873 if (cp->Type == CTfree)
875 for (i = 0; (p = NCquietlist[i]) != NULL; i++)
876 if (strcasecmp(p, q) == 0)
879 syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
887 TMRstart(TMR_ARTPARSE);
889 TMRstop(TMR_ARTPARSE);
890 if (cp->State == CSgetbody || cp->State == CSgetheader ||
891 cp->State == CSeatarticle) {
892 if (cp->Next - cp->Start > innconf->datamovethreshold ||
893 (innconf->maxartsize > 0 && cp->Size > innconf->maxartsize)) {
894 /* avoid buffer extention for ever */
903 if (cp->State == CSgotlargearticle) {
904 syslog(L_NOTICE, "%s internal rejecting huge article (%d > %ld)",
905 CHANname(cp), cp->Next - cp->Start, innconf->maxartsize);
907 NCwritereply(cp, cp->Sendid.data);
909 snprintf(buff, sizeof(buff),
910 "%d Article exceeds local limit of %ld bytes",
911 NNTP_REJECTIT_VAL, innconf->maxartsize);
912 NCwritereply(cp, buff);
914 cp->State = CSgetcmd;
916 cp->Start = cp->Next;
918 /* Write a local cancel entry so nobody else gives it to us. */
919 if (HDR_FOUND(HDR__MESSAGE_ID)) {
920 HDR_PARSE_START(HDR__MESSAGE_ID);
921 if (!HIScheck(History, HDR(HDR__MESSAGE_ID)) &&
922 !InndHisRemember(HDR(HDR__MESSAGE_ID)))
923 syslog(L_ERROR, "%s cant write %s", LogName, HDR(HDR__MESSAGE_ID));
925 /* Clear the work-in-progress entry. */
932 if (*cp->Error != '\0') {
934 cp->State = CSgetcmd;
935 cp->Start = cp->Next;
937 if (cp->Sendid.size > 3)
938 NCwritereply(cp, cp->Sendid.data);
940 NCwritereply(cp, cp->Error);
946 if (cp->State == CSnoarticle) {
947 /* this should happen when parsing header */
949 cp->State = CSgetcmd;
950 cp->Start = cp->Next;
951 /* Clear the work-in-progress entry. */
953 if (cp->Sendid.size > 3) { /* We be streaming */
955 snprintf(buff, sizeof(buff), "%d", NNTP_ERR_FAILID_VAL);
956 cp->Sendid.data[0] = buff[0];
957 cp->Sendid.data[1] = buff[1];
958 cp->Sendid.data[2] = buff[2];
959 NCwritereply(cp, cp->Sendid.data);
961 NCwritereply(cp, NNTP_REJECTIT_EMPTY);
966 case CSgotarticle: /* in case caming back from pause */
967 /* never move data so long as "\r\n.\r\n" is found, since subsequent data
968 may also include command line */
971 if (Mode == OMpaused) { /* defer processing while paused */
972 RCHANremove(cp); /* don't bother trying to read more for now */
973 SCHANadd(cp, Now.time + innconf->pauseretrytime, &Mode, NCproc, NULL);
975 } else if (Mode == OMthrottled) {
976 /* Clear the work-in-progress entry. */
978 NCwriteshutdown(cp, ModeReason);
984 if (cp->Argument != NULL) {
989 /* Clear the work-in-progress entry. */
991 if (cp->State == CSwritegoodbye)
993 cp->State = CSgetcmd;
994 cp->Start = cp->Next;
998 /* Eat the command line and then complain that it was too large */
999 /* Reading a line; look for "\r\n" terminator. */
1000 /* cp->Next should be SAVE_AMT(10) */
1001 for (i = cp->Next ; i < bp->used; i++) {
1002 if ((bp->data[i - 1] == '\r') && (bp->data[i] == '\n')) {
1007 if (i < bp->used) { /* did find terminator */
1008 /* Reached the end of the command line. */
1010 if (cp->Argument != NULL) {
1012 cp->Argument = NULL;
1014 i += cp->LargeCmdSize;
1015 syslog(L_NOTICE, "%s internal rejecting too long command line (%d > %d)",
1016 CHANname(cp), i, NNTP_STRLEN);
1017 cp->LargeCmdSize = 0;
1018 snprintf(buff, sizeof(buff), "%d command exceeds limit of %d bytes",
1019 NNTP_BAD_COMMAND_VAL, NNTP_STRLEN);
1020 cp->State = CSgetcmd;
1021 cp->Start = cp->Next;
1022 NCwritereply(cp, buff);
1026 cp->LargeCmdSize += bp->used - cp->Next;
1027 bp->used = cp->Next = SAVE_AMT;
1028 bp->left = bp->size - SAVE_AMT;
1036 /* if the batch is complete, write it out into the in.coming
1037 * directory with an unique timestamp, and start rnews on it.
1039 if (Tracing || cp->Tracing)
1040 syslog(L_TRACE, "%s CSgetxbatch: now %lu of %d bytes", CHANname(cp),
1041 (unsigned long) bp->used, cp->XBatchSize);
1043 if (cp->Next != 0) {
1044 /* data must start from the begining of the buffer */
1049 if (bp->used < cp->XBatchSize) {
1052 break; /* give us more data */
1057 /* now do something with the batch */
1060 int fd, oerrno, failed;
1065 /* time+channel file descriptor should make an unique file name */
1066 snprintf(buff, sizeof(buff), "%s/%ld%d.tmp", innconf->pathincoming,
1068 fd = open(buff, O_WRONLY|O_CREAT|O_EXCL, ARTFILE_MODE);
1072 syslog(L_ERROR, "%s cannot open outfile %s for xbatch: %m",
1073 CHANname(cp), buff);
1074 snprintf(buff, sizeof(buff), "%s cant create file: %s",
1075 NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1076 NCwritereply(cp, buff);
1078 if (write(fd, cp->In.data, cp->XBatchSize) != cp->XBatchSize) {
1080 syslog(L_ERROR, "%s cant write batch to file %s: %m", CHANname(cp),
1082 snprintf(buff, sizeof(buff), "%s cant write batch to file: %s",
1083 NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1084 NCwritereply(cp, buff);
1088 if (fd >= 0 && close(fd) != 0) {
1090 syslog(L_ERROR, "%s error closing batch file %s: %m", CHANname(cp),
1091 failed ? "" : buff);
1092 snprintf(buff, sizeof(buff), "%s error closing batch file: %s",
1093 NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
1094 NCwritereply(cp, buff);
1097 snprintf(buff2, sizeof(buff2), "%s/%ld%d.x", innconf->pathincoming,
1099 if (rename(buff, buff2)) {
1101 syslog(L_ERROR, "%s cant rename %s to %s: %m", CHANname(cp),
1102 failed ? "" : buff, buff2);
1103 snprintf(buff, sizeof(buff), "%s cant rename batch to %s: %s",
1104 NNTP_RESENDIT_XBATCHERR, buff2, strerror(oerrno));
1105 NCwritereply(cp, buff);
1110 NCwritereply(cp, NNTP_OK_XBATCHED);
1115 syslog(L_NOTICE, "%s accepted batch size %d", CHANname(cp),
1117 cp->State = CSgetcmd;
1118 cp->Start = cp->Next = cp->XBatchSize;
1121 if (cp->State == CSwritegoodbye || cp->Type == CTfree)
1123 if (Tracing || cp->Tracing)
1124 syslog(L_TRACE, "%s NCproc state=%d Start=%lu Next=%lu Used=%lu",
1125 CHANname(cp), cp->State, (unsigned long) cp->Start,
1126 (unsigned long) cp->Next, (unsigned long) bp->used);
1128 if (movedata) { /* move data rather than extend buffer */
1129 TMRstart(TMR_DATAMOVE);
1132 memmove(bp->data, &bp->data[cp->Start], bp->used - cp->Start);
1133 bp->used -= cp->Start;
1134 bp->left += cp->Start;
1135 cp->Next -= cp->Start;
1136 if (cp->State == CSgetheader || cp->State == CSgetbody ||
1137 cp->State == CSeatarticle) {
1138 /* adjust offset only in CSgetheader, CSgetbody or CSeatarticle */
1139 data->CurHeader -= cp->Start;
1140 data->LastTerminator -= cp->Start;
1141 data->LastCR -= cp->Start;
1142 data->LastCRLF -= cp->Start;
1143 data->Body -= cp->Start;
1144 if (data->BytesHeader != NULL)
1145 data->BytesHeader -= cp->Start;
1146 for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
1147 if (hc->Value != NULL)
1148 hc->Value -= cp->Start;
1152 TMRstop(TMR_DATAMOVE);
1155 /* need to read more */
1162 ** Read whatever data is available on the channel. If we got the
1163 ** full amount (i.e., the command or the whole article) process it.
1166 NCreader(CHANNEL *cp)
1170 if (Tracing || cp->Tracing)
1171 syslog(L_TRACE, "%s NCreader Used=%lu",
1172 CHANname(cp), (unsigned long) cp->In.used);
1174 /* Read any data that's there; ignore errors (retry next time it's our
1175 * turn) and if we got nothing, then it's EOF so mark it closed. */
1176 if ((i = CHANreadtext(cp)) <= 0) {
1177 /* Return of -2 indicates we got EAGAIN even though the descriptor
1178 selected true for reading, probably due to the Solaris select
1179 bug. Drop back out to the main loop as if the descriptor never
1184 if (i == 0 || cp->BadReads++ >= innconf->badiocount) {
1187 CHANclose(cp, CHANname(cp));
1192 NCproc(cp); /* check and process data */
1198 ** Set up the NNTP channel state.
1206 /* Set the greeting message. */
1207 p = innconf->pathhost;
1209 /* Worked in main, now it fails? Curious. */
1211 snprintf(buff, sizeof(buff), "%d %s InterNetNews server %s ready",
1212 NNTP_POSTOK_VAL, p, inn_version_string);
1213 NCgreeting = xstrdup(buff);
1218 ** Tear down our state.
1226 /* Close all incoming channels. */
1227 for (j = 0; (cp = CHANiter(&j, CTnntp)) != NULL; ) {
1230 CHANclose(cp, CHANname(cp));
1236 ** Create an NNTP channel and print the greeting message.
1239 NCcreate(int fd, bool MustAuthorize, bool IsLocal)
1244 /* Create the channel. */
1245 cp = CHANcreate(fd, CTnntp, MustAuthorize ? CSgetauth : CSgetcmd,
1246 NCreader, NCwritedone);
1249 cp->privileged = IsLocal;
1250 #if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF)
1253 if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
1254 syslog(L_ERROR, "%s cant setsockopt(SNDBUF) %m", CHANname(cp));
1255 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
1256 syslog(L_ERROR, "%s cant setsockopt(RCVBUF) %m", CHANname(cp));
1258 #endif /* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
1260 #if defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
1262 /* Set KEEPALIVE to catch broken socket connections. */
1264 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
1265 syslog(L_ERROR, "%s cant setsockopt(KEEPALIVE) %m", CHANname(cp));
1267 #endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */
1269 /* Now check our operating mode. */
1271 if (Mode == OMthrottled) {
1272 NCwriteshutdown(cp, ModeReason);
1276 NCwriteshutdown(cp, RejectReason);
1280 /* See if we have too many channels. */
1281 if (!IsLocal && innconf->maxconnections &&
1282 NCcount >= innconf->maxconnections && !RCnolimit(cp)) {
1283 /* Recount, just in case we got out of sync. */
1284 for (NCcount = 0, i = 0; CHANiter(&i, CTnntp) != NULL; )
1286 if (NCcount >= innconf->maxconnections) {
1287 NCwriteshutdown(cp, "Too many connections");
1292 cp->BadCommands = 0;
1298 /* These modules support the streaming option to tranfer articles
1303 ** The "check" command. Check the Message-ID, and see if we want the
1304 ** article or not. Stay in command state.
1307 NCcheck(CHANNEL *cp)
1311 #if defined(DO_PERL) || defined(DO_PYTHON)
1313 #endif /* DO_PERL || DO_PYTHON */
1316 /* Snip off the Message-ID. */
1317 for (p = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
1319 cp->Start = cp->Next;
1320 for ( ; ISWHITE(*p); p++)
1323 msglen = idlen + 5; /* 3 digits + space + id + null */
1324 if (cp->Sendid.size < msglen) {
1325 if (cp->Sendid.size > 0) free(cp->Sendid.data);
1326 if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
1327 else cp->Sendid.size = MAXHEADERSIZE;
1328 cp->Sendid.data = xmalloc(cp->Sendid.size);
1331 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1332 NNTP_ERR_GOTID_VAL, p);
1333 NCwritereply(cp, cp->Sendid.data);
1334 syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
1338 if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
1340 cp->Check_cybercan++;
1341 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1342 NNTP_ERR_GOTID_VAL, p);
1343 NCwritereply(cp, cp->Sendid.data);
1347 #if defined(DO_PERL)
1348 /* Invoke a perl message filter on the message ID. */
1349 filterrc = PLmidfilter(p);
1352 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1353 NNTP_ERR_GOTID_VAL, p);
1354 NCwritereply(cp, cp->Sendid.data);
1357 #endif /* defined(DO_PERL) */
1359 #if defined(DO_PYTHON)
1360 /* invoke a python message filter on the message id */
1361 filterrc = PYmidfilter(p, idlen);
1364 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1365 NNTP_ERR_GOTID_VAL, p);
1366 NCwritereply(cp, cp->Sendid.data);
1369 #endif /* defined(DO_PYTHON) */
1371 if (HIScheck(History, p)) {
1374 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1375 NNTP_ERR_GOTID_VAL, p);
1376 NCwritereply(cp, cp->Sendid.data);
1377 } else if (WIPinprogress(p, cp, true)) {
1378 cp->Check_deferred++;
1379 if (cp->NoResendId) {
1381 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1382 NNTP_ERR_GOTID_VAL, p);
1384 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1385 NNTP_RESENDID_VAL, p);
1387 NCwritereply(cp, cp->Sendid.data);
1390 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
1391 NNTP_OK_SENDID_VAL, p);
1392 NCwritereply(cp, cp->Sendid.data);
1394 /* stay in command mode */
1398 ** The "takethis" command. Article follows.
1399 ** Remember <id> for later ack.
1402 NCtakethis(CHANNEL *cp)
1409 /* Snip off the Message-ID. */
1410 for (p = cp->In.data + cp->Start + strlen("takethis"); ISWHITE(*p); p++)
1412 cp->Start = cp->Next;
1413 for ( ; ISWHITE(*p); p++)
1416 syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
1418 msglen = strlen(p) + 5; /* 3 digits + space + id + null */
1419 if (cp->Sendid.size < msglen) {
1420 if (cp->Sendid.size > 0) free(cp->Sendid.data);
1421 if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
1422 else cp->Sendid.size = MAXHEADERSIZE;
1423 cp->Sendid.data = xmalloc(cp->Sendid.size);
1425 /* save ID for later NACK or ACK */
1426 snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s", NNTP_ERR_FAILID_VAL,
1429 cp->ArtBeg = Now.time;
1430 cp->State = CSgetheader;
1432 /* set WIP for benefit of later code in NCreader */
1433 if ((wp = WIPbyid(p)) == (WIP *)NULL)
1435 cp->CurrentMessageIDHash = wp->MessageID;
1439 ** Process a cancel ID from a "mode cancel" channel.
1442 NCcancel(CHANNEL *cp)
1444 char *av[2] = { NULL, NULL };
1448 av[0] = cp->In.data + cp->Start;
1449 cp->Start = cp->Next;
1454 snprintf(buff, sizeof(buff), "%d %s", NNTP_ERR_CANCEL_VAL,
1455 MaxLength(res, res));
1456 syslog(L_NOTICE, "%s cant_cancel %s", CHANname(cp),
1457 MaxLength(res, res));
1458 NCwritereply(cp, buff);
1460 NCwritereply(cp, NNTP_OK_CANCELLED);