3 ** Check article, send it to the local server.
8 #include "inn/innconf.h"
13 #define FLUSH_ERROR(F) (fflush((F)) == EOF || ferror((F)))
14 #define HEADER_DELTA 20
17 static char Error[SMBUF];
18 static char NGSEPS[] = NG_SEPARATOR;
23 static const char * const BadDistribs[] = {
28 /* Name Canset Type Size Value */
29 { "Path", true, HTstd, 0, NULL, NULL, 0 },
30 { "From", true, HTreq, 0, NULL, NULL, 0 },
31 { "Newsgroups", true, HTreq, 0, NULL, NULL, 0 },
32 { "Subject", true, HTreq, 0, NULL, NULL, 0 },
33 { "Control", true, HTstd, 0, NULL, NULL, 0 },
34 { "Supersedes", true, HTstd, 0, NULL, NULL, 0 },
35 { "Followup-To", true, HTstd, 0, NULL, NULL, 0 },
36 { "Date", true, HTstd, 0, NULL, NULL, 0 },
37 { "Organization", true, HTstd, 0, NULL, NULL, 0 },
38 { "Lines", true, HTstd, 0, NULL, NULL, 0 },
39 { "Sender", true, HTstd, 0, NULL, NULL, 0 },
40 { "Approved", true, HTstd, 0, NULL, NULL, 0 },
41 { "Distribution", true, HTstd, 0, NULL, NULL, 0 },
42 { "Expires", true, HTstd, 0, NULL, NULL, 0 },
43 { "Message-ID", true, HTstd, 0, NULL, NULL, 0 },
44 { "References", true, HTstd, 0, NULL, NULL, 0 },
45 { "Reply-To", true, HTstd, 0, NULL, NULL, 0 },
46 { "NNTP-Posting-Host", false, HTstd, 0, NULL, NULL, 0 },
47 { "Mime-Version", true, HTstd, 0, NULL, NULL, 0 },
48 { "Content-Type", true, HTstd, 0, NULL, NULL, 0 },
49 { "Content-Transfer-Encoding", true, HTstd, 0, NULL, NULL, 0 },
50 { "X-Trace", false, HTstd, 0, NULL, NULL, 0 },
51 { "X-Complaints-To", false, HTstd, 0, NULL, NULL, 0 },
52 { "NNTP-Posting-Date", false, HTstd, 0, NULL, NULL, 0 },
53 { "Xref", false, HTstd, 0, NULL, NULL, 0 },
54 { "Injector-Info", false, HTstd, 0, NULL, NULL, 0 },
55 { "Summary", true, HTstd, 0, NULL, NULL, 0 },
56 { "Keywords", true, HTstd, 0, NULL, NULL, 0 },
57 { "Date-Received", false, HTobs, 0, NULL, NULL, 0 },
58 { "Received", false, HTobs, 0, NULL, NULL, 0 },
59 { "Posted", false, HTobs, 0, NULL, NULL, 0 },
60 { "Posting-Version", false, HTobs, 0, NULL, NULL, 0 },
61 { "Relay-Version", false, HTobs, 0, NULL, NULL, 0 },
62 { "Cc", true, HTstd, 0, NULL, NULL, 0 },
63 { "Bcc", true, HTstd, 0, NULL, NULL, 0 },
64 { "To", true, HTstd, 0, NULL, NULL, 0 },
67 HEADER *EndOfTable = ARRAY_END(Table);
71 /* Join() and MaxLength() are taken from innd.c */
73 ** Turn any \r or \n in text into spaces. Used to splice back multi-line
74 ** headers into a single line.
81 for (p = text; *p; p++)
82 if (*p == '\n' || *p == '\r')
88 ** Return a short name that won't overrun our bufer or syslog's buffer.
89 ** q should either be p, or point into p where the "interesting" part is.
92 MaxLength(char *p, char *q)
97 /* Already short enough? */
99 if (i < sizeof buff - 1)
102 /* Don't want casts to unsigned to go horribly wrong. */
103 if (q < p || q > p + i)
106 /* Simple case of just want the begining? */
107 if ((size_t)(q - p) < sizeof(buff) - 4) {
108 strlcpy(buff, p, sizeof(buff) - 3);
109 strlcat(buff, "...", sizeof(buff));
110 } else if ((p + i) - q < 10) {
111 /* Is getting last 10 characters good enough? */
112 strlcpy(buff, p, sizeof(buff) - 13);
113 strlcat(buff, "...", sizeof(buff) - 10);
114 strlcat(buff, &p[i - 10], sizeof(buff));
116 /* Not in last 10 bytes, so use double elipses. */
117 strlcpy(buff, p, sizeof(buff) - 16);
118 strlcat(buff, "...", sizeof(buff) - 13);
119 strlcat(buff, &q[-5], sizeof(buff) - 3);
120 strlcat(buff, "...", sizeof(buff));
125 ** Trim trailing spaces, return pointer to first non-space char.
132 for (start = p; ISWHITE(*start) || *start == '\n'; start++)
134 for (p = start + strlen(start); p > start && CTYPE(isspace, (int)p[-1]); p--)
136 return (int)(p - start);
141 ** Mark the end of the header starting at p, and return a pointer
142 ** to the start of the next one or NULL. Handles continuations.
147 for ( ; (p = strchr(p, '\n')) != NULL; p++) {
158 ** Strip any headers off the article and dump them into the table.
159 ** On error, return NULL and fill in Error.
162 StripOffHeaders(char *article)
169 /* Scan through buffer, a header at a time. */
170 for (p = article; ; ) {
172 /* See if it's a known header. */
173 c = CTYPE(islower, (int)*p) ? toupper(*p) : *p;
174 for (hp = Table; hp < ARRAY_END(Table); hp++) {
176 && p[hp->Size] == ':'
177 && strncasecmp(p, hp->Name, hp->Size) == 0) {
178 if (hp->Type == HTobs) {
179 snprintf(Error, sizeof(Error), "Obsolete \"%s\" header",
184 snprintf(Error, sizeof(Error), "Duplicate \"%s\" header",
188 hp->Value = &p[hp->Size + 1];
189 /* '\r\n' is replaced with '\n', and unnecessary to consider
191 for (q = &p[hp->Size + 1]; ISWHITE(*q) || *q == '\n'; q++)
198 /* No; add it to the set of other headers. */
199 if (hp == ARRAY_END(Table)) {
200 if (OtherCount >= OtherSize - 1) {
201 OtherSize += HEADER_DELTA;
202 OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
204 OtherHeaders[OtherCount++] = p;
207 /* Get start of next header; if it's a blank line, we hit the end. */
208 if ((p = NextHeader(p)) == NULL) {
209 strlcpy(Error, "Article has no body -- just headers",
223 ** Check the control message, and see if it's legit. Return pointer to
224 ** error message if not.
227 CheckControl(char *ctrl)
233 /* Snip off the first word. */
234 for (p = ctrl; ISWHITE(*p); p++)
236 for (ctrl = p; *p && !ISWHITE(*p); p++)
239 return "Empty control message";
243 if (strcmp(ctrl, "cancel") == 0) {
244 for (q = p + 1; ISWHITE(*q); q++)
247 return "Message-ID missing in cancel";
249 else if (strcmp(ctrl, "sendsys") == 0
250 || strcmp(ctrl, "senduuname") == 0
251 || strcmp(ctrl, "version") == 0
252 || strcmp(ctrl, "checkgroups") == 0
253 || strcmp(ctrl, "ihave") == 0
254 || strcmp(ctrl, "sendme") == 0
255 || strcmp(ctrl, "newgroup") == 0
256 || strcmp(ctrl, "rmgroup") == 0)
259 snprintf(Error, sizeof(Error),
260 "\"%s\" is not a valid control message",
261 MaxLength(ctrl,ctrl));
270 ** Check the Distribution header, and exit on error.
273 CheckDistribution(char *p)
275 static char SEPS[] = " \t,";
276 const char * const *dp;
278 if ((p = strtok(p, SEPS)) == NULL)
279 return "Can't parse Distribution line.";
281 for (dp = BadDistribs; *dp; dp++)
282 if (uwildmat(p, *dp)) {
283 snprintf(Error, sizeof(Error), "Illegal distribution \"%s\"",
287 } while ((p = strtok((char *)NULL, SEPS)) != NULL);
293 ** Process all the headers. FYI, they're done in RFC-order.
294 ** Return NULL if okay, or an error message.
297 ProcessHeaders(int linecount, char *idbuff, bool ihave)
299 static char MONTHS[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
300 static char datebuff[40];
301 static char localdatebuff[40];
302 static char orgbuff[SMBUF];
303 static char linebuff[40];
304 static char tracebuff[SMBUF];
305 static char complaintsbuff[SMBUF];
306 static char sendbuff[SMBUF];
307 static char *newpath = NULL;
315 bool addvirtual = false;
317 /* Various things need Now to be set. */
318 if (GetTimeInfo(&Now) < 0) {
319 snprintf(Error, sizeof(Error), "Can't get the time, %s",
324 /* Do some preliminary fix-ups. */
325 for (hp = Table; hp < ARRAY_END(Table); hp++) {
326 if (!ihave && !hp->CanSet && hp->Value) {
327 snprintf(Error, sizeof(Error),
328 "Can't set system \"%s\" header", hp->Name);
332 hp->Len = TrimSpaces(hp->Value);
334 hp->Value = hp->Body = NULL;
338 /* If authorized, add the header based on our info. If not authorized,
339 zap the Sender so we don't put out unauthenticated data. */
340 if (PERMaccessconf->nnrpdauthsender) {
341 if (PERMauthorized && PERMuser[0] != '\0') {
342 p = strchr(PERMuser, '@');
344 snprintf(sendbuff, sizeof(sendbuff), "%s@%s", PERMuser,
347 snprintf(sendbuff, sizeof(sendbuff), "%s", PERMuser);
349 HDR_SET(HDR__SENDER, sendbuff);
351 HDR_SET(HDR__SENDER, NULL);
355 /* Set Date. datebuff is used later for NNTP-Posting-Date, so we have
356 to set it and it has to be the UTC date. */
357 if (!makedate(-1, false, datebuff, sizeof(datebuff)))
358 return "Can't generate date header";
359 if (HDR(HDR__DATE) == NULL) {
361 return "Missing \"Date\" header";
362 if (PERMaccessconf->localtime) {
363 if (!makedate(-1, true, localdatebuff, sizeof(localdatebuff)))
364 return "Can't generate local date header";
365 HDR_SET(HDR__DATE, localdatebuff);
367 HDR_SET(HDR__DATE, datebuff);
370 if ((t = parsedate(HDR(HDR__DATE), &Now)) == -1)
371 return "Can't parse \"Date\" header";
372 if (t > Now.time + DATE_FUZZ)
373 return "Article posted in the future";
376 /* Newsgroups are checked later. */
378 if (HDR(HDR__CONTROL)) {
379 if ((error = CheckControl(HDR(HDR__CONTROL))) != NULL)
382 p = HDR(HDR__SUBJECT);
384 return "Required \"Subject\" header is missing";
385 if (strncmp(p, "cmsg ", 5) == 0) {
386 HDR_SET(HDR__CONTROL, p + 5);
387 if ((error = CheckControl(HDR(HDR__CONTROL))) != NULL)
393 if (HDR(HDR__MESSAGEID) == NULL) {
395 return "Missing \"Message-ID\" header";
396 HDR_SET(HDR__MESSAGEID, idbuff);
400 if (HDR(HDR__PATH) == NULL) {
402 return "Missing \"Path\" header";
403 /* Note that innd will put host name here for us. */
404 HDR_SET(HDR__PATH, PATHMASTER);
405 if (VirtualPathlen > 0)
407 } else if (PERMaccessconf->strippath) {
408 /* Here's where to do Path changes for new Posts. */
409 if ((p = strrchr(HDR(HDR__PATH), '!')) != NULL) {
412 HDR_SET(HDR__PATH, PATHMASTER);
413 if (VirtualPathlen > 0)
416 HDR_SET(HDR__PATH, p);
417 if ((VirtualPathlen > 0) &&
418 strcmp(p, PERMaccessconf->pathhost) != 0)
421 } else if (VirtualPathlen > 0)
424 if ((VirtualPathlen > 0) &&
425 (p = strchr(HDR(HDR__PATH), '!')) != NULL) {
427 if (strcmp(HDR(HDR__PATH), PERMaccessconf->pathhost) != 0)
430 } else if (VirtualPathlen > 0)
436 newpath = concat(VirtualPath, HDR(HDR__PATH), (char *) 0);
437 HDR_SET(HDR__PATH, newpath);
441 /* Reply-To; left alone. */
442 /* Sender; set above. */
445 if (HDR(HDR__EXPIRES) && parsedate(HDR(HDR__EXPIRES), &Now) == -1)
446 return "Can't parse \"Expires\" header";
448 /* References; left alone. */
449 /* Control; checked above. */
452 if ((p = HDR(HDR__DISTRIBUTION)) != NULL) {
454 error = CheckDistribution(p);
460 /* Set Organization */
461 if (!ihave && HDR(HDR__ORGANIZATION) == NULL
462 && (p = PERMaccessconf->organization) != NULL) {
463 strlcpy(orgbuff, p, sizeof(orgbuff));
464 HDR_SET(HDR__ORGANIZATION, orgbuff);
467 /* Keywords; left alone. */
468 /* Summary; left alone. */
469 /* Approved; left alone. */
473 snprintf(linebuff, sizeof(linebuff), "%d", linecount);
474 HDR_SET(HDR__LINES, linebuff);
477 /* Supersedes; left alone. */
479 /* NNTP-Posting host; set. */
480 if (!ihave && PERMaccessconf->addnntppostinghost)
481 HDR_SET(HDR__NNTPPOSTINGHOST, ClientHost);
482 /* NNTP-Posting-Date - not in RFC (yet) */
483 if (!ihave && PERMaccessconf->addnntppostingdate)
484 HDR_SET(HDR__NNTPPOSTINGDATE, datebuff);
487 t = time((time_t *)NULL) ;
488 pid = (long) getpid() ;
489 if ((gmt = gmtime(&Now.time)) == NULL)
490 return "Can't get the time";
491 if (VirtualPathlen > 0)
492 p = PERMaccessconf->domain;
494 if ((p = GetFQDN(PERMaccessconf->domain)) == NULL)
496 snprintf(tracebuff, sizeof(tracebuff),
497 "%s %ld %ld %s (%d %3.3s %d %02d:%02d:%02d GMT)",
498 p, (long) t, (long) pid, ClientIpString,
499 gmt->tm_mday, &MONTHS[3 * gmt->tm_mon], 1900 + gmt->tm_year,
500 gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
501 HDR_SET(HDR__XTRACE, tracebuff);
503 /* X-Complaints-To; set */
504 if ((p = PERMaccessconf->complaints) != NULL)
505 snprintf (complaintsbuff, sizeof(complaintsbuff), "%s", p);
507 static const char newsmaster[] = NEWSMASTER;
509 if ((p = PERMaccessconf->fromhost) != NULL && strchr(newsmaster, '@') == NULL)
510 snprintf (complaintsbuff, sizeof(complaintsbuff), "%s@%s",
513 snprintf (complaintsbuff, sizeof(complaintsbuff), "%s",
516 HDR_SET(HDR__XCOMPLAINTSTO, complaintsbuff);
518 /* Clear out some headers that should not be here */
519 if (!ihave && PERMaccessconf->strippostcc) {
520 HDR_SET(HDR__CC, NULL);
521 HDR_SET(HDR__BCC, NULL);
522 HDR_SET(HDR__TO, NULL);
524 /* Now make sure everything is there. */
525 for (hp = Table; hp < ARRAY_END(Table); hp++)
526 if (hp->Type == HTreq && hp->Value == NULL) {
527 snprintf(Error, sizeof(Error),
528 "Required \"%s\" header is missing", hp->Name);
537 ** See if the user has more included text than new text. Simple-minded,
538 ** but reasonably effective for catching neophyte's mistakes. Son-of-1036
541 ** NOTE: While encouraging trimming is desirable, the 50% rule imposed
542 ** by some old posting agents is both inadequate and counterproductive.
543 ** Posters do not respond to it by being more selective about quoting;
544 ** they respond by padding short responses, or by using different
545 ** quoting styles to defeat automatic analysis. The former adds
546 ** unnecessary noise and volume, while the latter also defeats more
547 ** useful forms of automatic analysis that reading agents might wish to
550 ** NOTE: At the very least, if a minimum-unquoted quota is being set,
551 ** article bodies shorter than (say) 20 lines, or perhaps articles
552 ** which exceed the quota by only a few lines, should be exempt. This
553 ** avoids the ridiculous situation of complaining about a 5-line
554 ** response to a 6-line quote.
556 ** Accordingly, bodies shorter than 20 lines are exempt. A line starting
557 ** with >, |, or : is included text. Decrement the count on lines starting
558 ** with < so that we don't reject diff(1) output.
561 CheckIncludedText(const char *p, int lines)
569 case '>': i++; break;
570 case '|': i++; break;
571 case ':': i++; break;
572 case '<': i--; break;
580 return "Article not posted -- more included text than new text";
587 ** Try to mail an article to the moderator of the group.
590 MailArticle(char *group, char *article)
592 static char CANTSEND[] = "Can't send text to mailer";
600 /* Try to get the address first. */
601 if ((address = GetModeratorAddress(NULL, NULL, group, PERMaccessconf->moderatormailer)) == NULL) {
602 snprintf(Error, sizeof(Error), "No mailing address for \"%s\" -- %s",
603 group, "ask your news administrator to fix this");
609 /* Now build up the command (ignore format/argument mismatch errors,
610 * in case %s isn't in inconf->mta) and send the headers. */
611 if ((mta = innconf->mta) == NULL)
612 return "Can't start mailer - mta not set";
613 snprintf(buff, sizeof(buff), innconf->mta, address);
614 if ((F = popen(buff, "w")) == NULL)
615 return "Can't start mailer";
616 fprintf(F, "To: %s\n", address);
617 if (FLUSH_ERROR(F)) {
622 /* Write the headers, a blank line, then the article. */
623 for (hp = Table; hp < ARRAY_END(Table); hp++)
625 if (*hp->Value == ' ' || *hp->Value == '\t')
626 fprintf(F, "%s:%s\n", hp->Name, hp->Value);
628 fprintf(F, "%s: %s\n", hp->Name, hp->Value);
629 if (FLUSH_ERROR(F)) {
634 for (i = 0; i < OtherCount; i++) {
635 fprintf(F, "%s\n", OtherHeaders[i]);
636 if (FLUSH_ERROR(F)) {
643 if (fwrite(article, 1, i, F) != (size_t)i)
644 return "Can't send article";
645 if (FLUSH_ERROR(F)) {
651 snprintf(Error, sizeof(Error), "Mailer exited with status %d -- %s",
652 i, "Article might not have been mailed");
660 ** Check the newsgroups and make sure they're all valid, that none are
664 ValidNewsgroups(char *hdr, char **modgroup)
666 static char distbuff[SMBUF];
675 bool hookpresent = false;
678 hookpresent = PY_use_dynamic;
679 #endif /* DO_PYTHON */
681 p = HDR(HDR__CONTROL);
682 IsNewgroup = (p && strncmp(p, "newgroup", 8) == 0);
683 groups = xstrdup(hdr);
684 if ((p = strtok(groups, NGSEPS)) == NULL)
685 return "Can't parse newsgroups line";
688 /* Reject all articles with Approved headers unless the user is allowed to
689 add them, even to unmoderated or local groups. We want to reject them
690 to unmoderated groups in case there's a disagreement of opinion
691 between various sites as to the moderation status. */
692 approved = HDR(HDR__APPROVED) != NULL;
693 if (approved && !PERMaccessconf->allowapproved) {
694 snprintf(Error, sizeof(Error),
695 "You are not allowed to approve postings");
699 h = DDstart((FILE *)NULL, (FILE *)NULL);
701 if (innconf->mergetogroups && p[0] == 't' && p[1] == 'o' && p[2] == '.')
703 if (!hookpresent && PERMspecified) {
706 if (!PERMmatch(PERMpostlist, grplist)) {
707 snprintf(Error, sizeof(Error),
708 "You are not allowed to post to %s\r\n", p);
711 if (!OVgroupstats(p, NULL, NULL, NULL, &flag))
718 if (PY_use_dynamic) {
721 /* Authorize user using Python module method dynamic */
722 if (PY_dynamic(PERMuser, p, true, &reply) < 0) {
723 syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
726 syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
727 snprintf(Error, sizeof(Error), "%s\r\n", reply);
733 #endif /* DO_PYTHON */
735 case NF_FLAG_MODERATED:
736 if (!approved && modgroup != NULL && !*modgroup)
737 *modgroup = xstrdup(p);
740 case NF_FLAG_NOLOCAL:
741 if (!PERMaccessconf->locpost)
742 snprintf(Error, sizeof(Error),
743 "Postings to \"%s\" are not allowed here.", p);
745 case NF_FLAG_EXCLUDED:
746 /* Do NOT return an error. */
749 snprintf(Error, sizeof(Error),
750 "The newsgroup \"%s\" has been renamed.\n", p);
753 } while ((p = strtok((char *)NULL, NGSEPS)) != NULL);
756 if (!FoundOne && !IsNewgroup)
757 snprintf(Error, sizeof(Error), "No valid newsgroups in \"%s\"",
762 if (modgroup != NULL && *modgroup != NULL) {
770 if (HDR(HDR__DISTRIBUTION) == NULL && *p) {
771 strlcpy(distbuff, p, sizeof(distbuff));
772 HDR_SET(HDR__DISTRIBUTION, distbuff);
780 ** Send a quit message to the server, eat its reply.
783 SendQuit(FILE *FromServer, FILE *ToServer)
785 char buff[NNTP_STRLEN];
787 fprintf(ToServer, "quit\r\n");
790 fgets(buff, sizeof buff, FromServer);
796 ** Offer the article to the server, return its reply.
799 OfferArticle(char *buff, int buffsize, FILE *FromServer, FILE *ToServer)
801 static char CANTSEND[] = "Can't send %s to server, %s";
803 fprintf(ToServer, "ihave %s\r\n", HDR(HDR__MESSAGEID));
804 if (FLUSH_ERROR(ToServer)
805 || fgets(buff, buffsize, FromServer) == NULL) {
806 snprintf(buff, sizeof(buff), CANTSEND, "IHAVE", strerror(errno));
814 ** Spool article to temp file.
817 SpoolitTo(char *article, char *err, char *SpoolDir)
819 static char CANTSPOOL[NNTP_STRLEN+2];
823 char *tmpspool = NULL;
824 char *spoolfile = NULL;
827 /* Initialize the returned error message */
828 snprintf(CANTSPOOL, sizeof(CANTSPOOL),
829 "%s and can't write text to local spool file", err);
831 /* Try to write it to the spool dir. */
832 tmpspool = concatpath(SpoolDir, ".XXXXXX");
833 fd = mkstemp(tmpspool);
835 syslog(L_FATAL, "cant create temporary spool file %s %m", tmpspool);
840 syslog(L_FATAL, "cant open %s %m", tmpspool);
843 fchmod(fileno(F), BATCHFILE_MODE);
845 /* Write the headers and a blank line. */
846 for (hp = Table; hp < ARRAY_END(Table); hp++)
848 q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
849 if (*hp->Value == ' ' || *hp->Value == '\t')
850 fprintf(F, "%s:%s\n", hp->Name, q);
852 fprintf(F, "%s: %s\n", hp->Name, q);
853 if (FLUSH_ERROR(F)) {
860 for (i = 0; i < OtherCount; i++) {
861 fprintf(F, "%s\n", OtherHeaders[i]);
862 if (FLUSH_ERROR(F)) {
869 /* Write the article body */
871 if (fwrite(article, 1, i, F) != (size_t)i) {
876 /* Flush and catch any errors */
880 /* Rename the spool file to something rnews will pick up. */
881 spoolfile = concatpath(SpoolDir, "XXXXXX");
882 fd = mkstemp(spoolfile);
884 syslog(L_FATAL, "cant create spool file %s %m", spoolfile);
888 if (rename(tmpspool, spoolfile) < 0) {
889 syslog(L_FATAL, "cant rename %s %s %m", tmpspool, spoolfile);
893 /* Article has been spooled */
899 if (tmpspool != NULL)
901 if (spoolfile != NULL)
907 ** Spool article to temp file.
910 Spoolit(char *article, char *err)
912 return SpoolitTo(article, err, innconf->pathincoming);
915 static char *Towire(char *p) {
917 int curlen, len = BIG_BUFFER;
919 for (r = p, q = s = xmalloc(len); *r != '\0' ;) {
921 if (curlen + 3 > len) {
923 s = xrealloc(s, len);
928 if (*(r - 1) != '\r')
931 /* this should not happen */
939 if (curlen + 1 > len) {
941 s = xrealloc(s, len);
949 ARTpost(char *article,
954 static char CANTSEND[] = "Can't send %s to server, %s";
961 char buff[NNTP_STRLEN + 2], frombuf[SMBUF];
962 char *modgroup = NULL;
969 /* Assume errors are permanent, until we discover otherwise */
972 /* Set up the other headers list. */
973 if (OtherHeaders == NULL) {
974 OtherSize = HEADER_DELTA;
975 OtherHeaders = xmalloc(OtherSize * sizeof(char *));
978 /* Basic processing. */
980 for (hp = Table; hp < ARRAY_END(Table); hp++) {
981 hp->Size = strlen(hp->Name);
982 hp->Value = hp->Body = NULL;
984 if ((article = StripOffHeaders(article)) == NULL)
986 for (i = 0, p = article; p; i++, p = next + 1)
987 if ((next = strchr(p, '\n')) == NULL)
989 if (PERMaccessconf->checkincludedtext) {
990 if ((error = CheckIncludedText(article, i)) != NULL)
993 if ((error = ProcessHeaders(i, idbuff, ihave)) != NULL)
995 if (i == 0 && HDR(HDR__CONTROL) == NULL)
996 return "Article is empty";
998 if ((error = ValidNewsgroups(HDR(HDR__NEWSGROUPS), &modgroup)) != NULL)
1001 strlcpy(frombuf, HDR(HDR__FROM), sizeof(frombuf));
1002 for (i = 0, p = frombuf;p < frombuf + sizeof(frombuf);)
1003 if ((p = strchr(p, '\n')) == NULL)
1007 HeaderCleanFrom(frombuf);
1008 p = strchr(frombuf, '@');
1010 strlcpy(frombuf, p+1, sizeof(frombuf));
1011 p = strrchr(frombuf, '.');
1015 return "From: address not in Internet syntax";
1021 return "From: address not in Internet syntax";
1023 if ((p = HDR(HDR__FOLLOWUPTO)) != NULL
1024 && strcmp(p, "poster") != 0
1025 && (error = ValidNewsgroups(p, (char **)NULL)) != NULL) {
1030 if ((PERMaccessconf->localmaxartsize > 0) &&
1031 (strlen(article) > (unsigned)PERMaccessconf->localmaxartsize)) {
1032 snprintf(Error, sizeof(Error),
1033 "Article is bigger then local limit of %ld bytes\n",
1034 PERMaccessconf->localmaxartsize);
1040 #if defined(DO_PERL)
1041 /* Calls the Perl subroutine for headers management */
1042 p = PERMaccessconf->nnrpdperlfilter ? HandleHeaders(article) : NULL;
1046 sprintf(idbuff, "(mailed to moderator for %s)", modgroup);
1048 strlcpy(idbuff, HDR(HDR__MESSAGEID), SMBUF);
1050 if (strncmp(p, "DROP", 4) == 0) {
1051 syslog(L_NOTICE, "%s post %s", ClientHost, p);
1056 else if (strncmp(p, "SPOOL", 5) == 0) {
1057 syslog(L_NOTICE, "%s post %s", ClientHost, p);
1058 strlcpy(SDir, innconf->pathincoming, sizeof(SDir));
1061 strlcat(SDir, "/spam/mod", sizeof(SDir));
1062 return SpoolitTo(article, p, SDir);
1065 strlcat(SDir, "/spam", sizeof(SDir));
1066 return SpoolitTo(article, p, SDir);
1076 #endif /* defined(DO_PERL) */
1078 /* handle mailing to moderated groups */
1081 if (idbuff != NULL) {
1083 retstr = MailArticle(modgroup, article);
1084 strcpy (idbuff,"(mailed to moderator)") ;
1087 return MailArticle(modgroup, article);
1091 strlcpy(idbuff, HDR(HDR__MESSAGEID), SMBUF);
1093 if (PERMaccessconf->spoolfirst)
1094 return Spoolit(article, Error);
1097 return Spoolit(article,Error);
1099 /* Open a local connection to the server. */
1100 if (PERMaccessconf->nnrpdposthost != NULL)
1101 i = NNTPconnect(PERMaccessconf->nnrpdposthost, PERMaccessconf->nnrpdpostport,
1102 &FromServer, &ToServer, buff);
1104 #if defined(HAVE_UNIX_DOMAIN_SOCKETS)
1105 i = NNTPlocalopen(&FromServer, &ToServer, buff);
1107 i = NNTPremoteopen(innconf->port, &FromServer,
1109 #endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
1112 /* If we cannot open the connection, initialize the error message and
1113 * attempt to recover from this by spooling it locally */
1116 strlcpy(Error, buff, sizeof(Error));
1118 snprintf(Error, sizeof(Error), CANTSEND, "connect request",
1120 return Spoolit(article,Error);
1123 syslog(L_TRACE, "%s post_connect %s",
1124 ClientHost, PERMaccessconf->nnrpdposthost ? PERMaccessconf->nnrpdposthost : "localhost");
1126 /* The code below ignores too many return values for my tastes. At least
1127 * they are all inside cases that are most likely never going to happen --
1128 * for example, if the server crashes. */
1130 /* Offer article to server. */
1131 i = OfferArticle(buff, (int)sizeof buff, FromServer, ToServer);
1132 if (i == NNTP_AUTH_NEEDED_VAL) {
1133 /* Send authorization. */
1134 if (NNTPsendpassword(PERMaccessconf->nnrpdposthost, FromServer, ToServer) < 0) {
1135 snprintf(Error, sizeof(Error), "Can't authorize with %s",
1136 PERMaccessconf->nnrpdposthost ? PERMaccessconf->nnrpdposthost : "innd");
1137 return Spoolit(article,Error);
1139 i = OfferArticle(buff, (int)sizeof buff, FromServer, ToServer);
1141 if (i != NNTP_SENDIT_VAL) {
1142 strlcpy(Error, buff, sizeof(Error));
1143 SendQuit(FromServer, ToServer);
1144 if (i != NNTP_HAVEIT_VAL)
1145 return Spoolit(article, Error);
1146 if (i == NNTP_REJECTIT_VAL || i == NNTP_RESENDIT_VAL) {
1152 syslog(L_TRACE, "%s post starting", ClientHost);
1154 /* Write the headers and a blank line. */
1155 for (hp = Table; hp < ARRAY_END(Table); hp++)
1157 q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
1158 if (strchr(q, '\n') != NULL) {
1159 if ((p = Towire(q)) != NULL) {
1160 /* there is no white space, if hp->Value and hp->Body is the same */
1161 if (*hp->Value == ' ' || *hp->Value == '\t')
1162 fprintf(ToServer, "%s:%s\r\n", hp->Name, p);
1164 fprintf(ToServer, "%s: %s\r\n", hp->Name, p);
1168 /* there is no white space, if hp->Value and hp->Body is the same */
1169 if (*hp->Value == ' ' || *hp->Value == '\t')
1170 fprintf(ToServer, "%s:%s\r\n", hp->Name, q);
1172 fprintf(ToServer, "%s: %s\r\n", hp->Name, q);
1176 for (i = 0; i < OtherCount; i++) {
1177 if (strchr(OtherHeaders[i], '\n') != NULL) {
1178 if ((p = Towire(OtherHeaders[i])) != NULL) {
1179 fprintf(ToServer, "%s\r\n", p);
1183 fprintf(ToServer, "%s\r\n", OtherHeaders[i]);
1186 fprintf(ToServer, "\r\n");
1187 if (FLUSH_ERROR(ToServer)) {
1188 snprintf(Error, sizeof(Error), CANTSEND, "headers", strerror(errno));
1191 return Spoolit(article, Error);
1194 /* Send the article, get the server's reply. */
1195 if (NNTPsendarticle(article, ToServer, true) < 0
1196 || fgets(buff, sizeof buff, FromServer) == NULL) {
1197 snprintf(Error, sizeof(Error), CANTSEND, "article", strerror(errno));
1200 return Spoolit(article, Error);
1203 /* Did the server want the article? */
1204 if ((i = atoi(buff)) != NNTP_TOOKIT_VAL) {
1205 strlcpy(Error, buff, sizeof(Error));
1206 SendQuit(FromServer, ToServer);
1207 syslog(L_TRACE, "%s server rejects %s from %s", ClientHost, HDR(HDR__MESSAGEID), HDR(HDR__PATH));
1208 if (i != NNTP_REJECTIT_VAL && i != NNTP_HAVEIT_VAL)
1209 return Spoolit(article, Error);
1210 if (i == NNTP_REJECTIT_VAL || i == NNTP_RESENDIT_VAL) {
1216 /* Send a quit and close down */
1217 SendQuit(FromServer, ToServer);
1220 if (PERMaccessconf->readertrack) {
1221 TrackID = concat(innconf->pathlog, "/trackposts/track.",
1222 HDR(HDR__MESSAGEID), (char *) 0);
1223 if ((ftd = fopen(TrackID,"w")) == NULL) {
1224 DirTrackID = concatpath(innconf->pathlog, "trackposts");
1225 MakeDirectory(DirTrackID, false);
1228 if (ftd == NULL && (ftd = fopen(TrackID,"w")) == NULL) {
1229 syslog(L_ERROR, "%s (%s) open %s: %m",
1230 ClientHost, Username, TrackID);
1234 for (hp = Table; hp < ARRAY_END(Table); hp++)
1236 q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
1237 if (strchr(q, '\n') != NULL) {
1238 if ((p = Towire(q)) != NULL) {
1239 /* there is no white space, if hp->Value and hp->Body is the same */
1240 if (*hp->Value == ' ' || *hp->Value == '\t')
1241 fprintf(ftd, "%s:%s\r\n", hp->Name, p);
1243 fprintf(ftd, "%s: %s\r\n", hp->Name, p);
1247 /* there is no white space, if hp->Value and hp->Body is the same */
1248 if (*hp->Value == ' ' || *hp->Value == '\t')
1249 fprintf(ftd, "%s:%s\r\n", hp->Name, q);
1251 fprintf(ftd, "%s: %s\r\n", hp->Name, q);
1255 for (i = 0 ; i < OtherCount ; i++) {
1256 if (strchr(OtherHeaders[i], '\n') != NULL) {
1257 if ((p = Towire(OtherHeaders[i])) != NULL) {
1258 fprintf(ftd, "%s\r\n", p);
1262 fprintf(ftd, "%s\r\n", OtherHeaders[i]);
1265 fprintf(ftd,"\r\n");
1266 NNTPsendarticle(article, ftd, true);
1267 if (fclose(ftd) != EOF) {
1268 syslog(L_NOTICE, "%s (%s) posttrack ok %s",
1269 ClientHost, Username, TrackID);
1271 fprintf(locallog, "%s (%s) posttrack ok %s\n",
1272 ClientHost, Username, TrackID);
1274 syslog(L_ERROR, "%s (%s) posttrack error 2 %s",
1275 ClientHost, Username, TrackID);