1 /* $Id: inews.c 7769 2008-04-13 08:11:41Z iulius $
3 ** Send an article (prepared by someone on the local site) to the
9 #include "portable/time.h"
17 #include "inn/innconf.h"
18 #include "inn/messages.h"
23 /* Signature handling. The separator will be appended before the signature,
24 and at most SIG_MAXLINES will be appended. */
25 #define SIG_MAXLINES 4
26 #define SIG_SEPARATOR "-- \n"
28 #define FLUSH_ERROR(F) (fflush((F)) == EOF || ferror((F)))
29 #define LPAREN '(' /* For vi :-) */
30 #define HEADER_DELTA 20
31 #define GECOSTERM(c) \
32 ((c) == ',' || (c) == ';' || (c) == ':' || (c) == LPAREN)
33 #define HEADER_STRLEN 998
35 typedef enum _HEADERTYPE {
41 typedef struct _HEADER {
52 static char **OtherHeaders;
53 static char SIGSEP[] = SIG_SEPARATOR;
54 static FILE *FromServer;
55 static FILE *ToServer;
56 static int OtherCount;
58 static const char *Exclusions = "";
59 static const char * const BadDistribs[] = {
63 static HEADER Table[] = {
64 /* Name Canset Type */
65 { "Path", true, HTstd, 0, NULL },
67 { "From", true, HTstd, 0, NULL },
69 { "Newsgroups", true, HTreq, 0, NULL },
71 { "Subject", true, HTreq, 0, NULL },
73 { "Control", true, HTstd, 0, NULL },
75 { "Supersedes", true, HTstd, 0, NULL },
77 { "Followup-To", true, HTstd, 0, NULL },
79 { "Date", true, HTstd, 0, NULL },
81 { "Organization", true, HTstd, 0, NULL },
82 #define _organization 8
83 { "Lines", true, HTstd, 0, NULL },
85 { "Sender", true, HTstd, 0, NULL },
87 { "Approved", true, HTstd, 0, NULL },
89 { "Distribution", true, HTstd, 0, NULL },
90 #define _distribution 12
91 { "Expires", true, HTstd, 0, NULL },
93 { "Message-ID", true, HTstd, 0, NULL },
95 { "References", true, HTstd, 0, NULL },
96 #define _references 15
97 { "Reply-To", true, HTstd, 0, NULL },
99 { "Also-Control", true, HTstd, 0, NULL },
100 #define _alsocontrol 17
101 { "Xref", false, HTstd, 0, NULL },
102 { "Summary", true, HTstd, 0, NULL },
103 { "Keywords", true, HTstd, 0, NULL },
104 { "Date-Received", false, HTobs, 0, NULL },
105 { "Received", false, HTobs, 0, NULL },
106 { "Posted", false, HTobs, 0, NULL },
107 { "Posting-Version", false, HTobs, 0, NULL },
108 { "Relay-Version", false, HTobs, 0, NULL },
111 #define HDR(_x) (Table[(_x)].Value)
116 ** Send the server a quit message, wait for a reply.
121 char buff[HEADER_STRLEN];
127 warn("article not posted");
128 fprintf(ToServer, "quit\r\n");
129 if (FLUSH_ERROR(ToServer))
130 sysdie("cannot send quit to server");
131 if (fgets(buff, sizeof buff, FromServer) == NULL)
132 sysdie("warning: server did not reply to quit");
133 if ((p = strchr(buff, '\r')) != NULL)
135 if ((p = strchr(buff, '\n')) != NULL)
137 if (atoi(buff) != NNTP_GOODBYE_ACK_VAL)
138 die("server did not reply to quit properly: %s", buff);
146 ** Failure handler, called by die. Calls QuitServer to cleanly shut down the
147 ** connection with the remote server before exiting.
153 message_fatal_cleanup = NULL;
155 /* QuitServer does all the work. */
162 ** Flush a stdio FILE; exit if there are any errors.
168 sysdie("cannot send text to server");
173 ** Trim trailing spaces, return pointer to first non-space char.
180 for (start = p; ISWHITE(*start); start++)
182 for (p = start + strlen(start); p > start && CTYPE(isspace, p[-1]); )
189 ** Mark the end of the header starting at p, and return a pointer
190 ** to the start of the next one. Handles continuations.
196 if ((p = strchr(p, '\n')) == NULL)
197 die("article is all headers");
198 if (!ISWHITE(p[1])) {
207 ** Strip any headers off the article and dump them into the table.
210 StripOffHeaders(char *article)
218 /* Set up the other headers list. */
219 OtherSize = HEADER_DELTA;
220 OtherHeaders = xmalloc(OtherSize * sizeof(char *));
223 /* Scan through buffer, a header at a time. */
224 for (i = 0, p = article; ; i++) {
226 if ((q = strchr(p, ':')) == NULL)
227 die("no colon in header line \"%.30s...\"", p);
228 if (q[1] == '\n' && !ISWHITE(q[2])) {
229 /* Empty header; ignore this one, get next line. */
235 if (q[1] != '\0' && !ISWHITE(q[1])) {
236 if ((q = strchr(q, '\n')) != NULL)
238 die("no space after colon in \"%.30s...\"", p);
241 /* See if it's a known header. */
242 c = CTYPE(islower, *p) ? toupper(*p) : *p;
243 for (hp = Table; hp < ARRAY_END(Table); hp++)
245 && p[hp->Size] == ':'
246 && ISWHITE(p[hp->Size + 1])
247 && strncasecmp(p, hp->Name, hp->Size) == 0) {
248 if (hp->Type == HTobs)
249 die("obsolete header: %s", hp->Name);
251 die("duplicate header: %s", hp->Name);
252 for (q = &p[hp->Size + 1]; ISWHITE(*q); q++)
258 /* Too many headers? */
259 if (++i > 5 * HEADER_DELTA)
260 die("more than %d lines of header", i);
262 /* No; add it to the set of other headers. */
263 if (hp == ARRAY_END(Table)) {
264 if (OtherCount >= OtherSize - 1) {
265 OtherSize += HEADER_DELTA;
266 OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
268 OtherHeaders[OtherCount++] = p;
271 /* Get start of next header; if it's a blank line, we hit the end. */
283 ** See if the user is allowed to cancel the indicated message. Assumes
284 ** that the Sender or From line has already been filled in.
287 CheckCancel(char *msgid, bool JustReturn)
289 char localfrom[SMBUF];
292 char remotefrom[SMBUF];
294 /* Ask the server for the article. */
295 fprintf(ToServer, "head %s\r\n", msgid);
297 if (fgets(buff, sizeof buff, FromServer) == NULL
298 || atoi(buff) != NNTP_HEAD_FOLLOWS_VAL) {
301 die("server has no such article");
304 /* Read the headers, looking for the From or Sender. */
305 remotefrom[0] = '\0';
306 while (fgets(buff, sizeof buff, FromServer) != NULL) {
307 if ((p = strchr(buff, '\r')) != NULL)
309 if ((p = strchr(buff, '\n')) != NULL)
311 if (buff[0] == '.' && buff[1] == '\0')
313 if (strncmp(buff, "Sender:", 7) == 0)
314 strlcpy(remotefrom, TrimSpaces(&buff[7]), SMBUF);
315 else if (remotefrom[0] == '\0' && strncmp(buff, "From:", 5) == 0)
316 strlcpy(remotefrom, TrimSpaces(&buff[5]), SMBUF);
318 if (remotefrom[0] == '\0') {
321 die("article is garbled");
323 HeaderCleanFrom(remotefrom);
325 /* Get the local user. */
326 strlcpy(localfrom, HDR(_sender) ? HDR(_sender) : HDR(_from), SMBUF);
327 HeaderCleanFrom(localfrom);
329 /* Is the right person cancelling? */
330 if (strcasecmp(localfrom, remotefrom) != 0)
331 die("article was posted by \"%s\" and you are \"%s\"", remotefrom,
337 ** See if the user is the news administrator.
340 AnAdministrator(char *name, gid_t group)
350 /* Find out who we are. */
351 if ((pwp = getpwnam(NEWSUSER)) == NULL)
352 /* Silent falure; clients might not have the group. */
354 if (getuid() == pwp->pw_uid)
357 /* See if the we're in the right group. */
358 if ((grp = getgrnam(NEWSGRP)) == NULL || (mem = grp->gr_mem) == NULL)
359 /* Silent falure; clients might not have the group. */
361 if (group == grp->gr_gid)
363 while ((p = *mem++) != NULL)
364 if (strcmp(name, p) == 0)
371 ** Check the control message, and see if it's legit.
374 CheckControl(char *ctrl, struct passwd *pwp)
381 /* Snip off the first word. */
382 for (p = ctrl; ISWHITE(*p); p++)
384 for (ctrl = p; *p && !ISWHITE(*p); p++)
387 die("emtpy control message");
391 if (strcmp(ctrl, "cancel") == 0) {
392 for (q = p + 1; ISWHITE(*q); q++)
395 die("message ID missing in cancel");
397 CheckCancel(q, false);
399 else if (strcmp(ctrl, "checkgroups") == 0
400 || strcmp(ctrl, "ihave") == 0
401 || strcmp(ctrl, "sendme") == 0
402 || strcmp(ctrl, "newgroup") == 0
403 || strcmp(ctrl, "rmgroup") == 0
404 || strcmp(ctrl, "sendsys") == 0
405 || strcmp(ctrl, "senduuname") == 0
406 || strcmp(ctrl, "version") == 0) {
407 strlcpy(name, pwp->pw_name, SMBUF);
408 if (!AnAdministrator(name, pwp->pw_gid))
409 die("ask your news administrator to do the %s for you", ctrl);
412 die("%s is not a valid control message", ctrl);
420 ** Parse the GECOS field to get the user's full name. This comes Sendmail's
421 ** buildfname routine. Ignore leading stuff like "23-" "stuff]-" or
422 ** "stuff -" as well as trailing whitespace, or anything that comes after
423 ** a comma, semicolon, or in parentheses. This seems to strip off most of
424 ** the UCB or ATT stuff people fill out the entries with. Also, turn &
425 ** into the login name, with perhaps an initial capital. (Everyone seems
426 ** to hate that, but everyone also supports it.)
429 FormatUserName(struct passwd *pwp, char *node)
437 #if !defined(DONT_MUNGE_GETENV)
438 memset(outbuff, 0, SMBUF);
439 if ((p = getenv("NAME")) != NULL)
440 strlcpy(outbuff, p, SMBUF);
441 if (strlen(outbuff) == 0) {
442 #endif /* !defined(DONT_MUNGE_GETENV) */
445 #ifndef DO_MUNGE_GECOS
446 strlcpy(outbuff, pwp->pw_gecos, SMBUF);
448 /* Be very careful here. If we're not, we can potentially overflow our
449 * buffer. Remember that on some Unix systems, the content of the GECOS
450 * field is under (untrusted) user control and we could be setgid. */
455 for (out = outbuff; *p && !GECOSTERM(*p) && left; p++) {
457 strncpy(out, pwp->pw_name, left);
458 if (CTYPE(islower, *out)
459 && (out == outbuff || !CTYPE(isalpha, out[-1])))
460 *out = toupper(*out);
468 && (CTYPE(isdigit, p[-1]) || CTYPE(isspace, p[-1])
479 #endif /* DO_MUNGE_GECOS */
481 #if !defined(DONT_MUNGE_GETENV)
483 #endif /* !defined(DONT_MUNGE_GETENV) */
485 out = TrimSpaces(outbuff);
487 buff = concat(pwp->pw_name, "@", node, " (", out, ")", (char *) 0);
489 buff = concat(pwp->pw_name, "@", node, (char *) 0);
495 ** Check the Distribution header, and exit on error.
497 static void CheckDistribution(char *p)
499 static char SEPS[] = " \t,";
500 const char * const *dp;
502 if ((p = strtok(p, SEPS)) == NULL)
503 die("cannot parse Distribution header");
505 for (dp = BadDistribs; *dp; dp++)
506 if (uwildmat(p, *dp))
507 die("illegal distribution %s", p);
508 } while ((p = strtok((char *)NULL, SEPS)) != NULL);
513 ** Process all the headers. FYI, they're done in RFC-order.
516 ProcessHeaders(bool AddOrg, int linecount, struct passwd *pwp)
518 static char PATHFLUFF[] = PATHMASTER;
525 /* Do some preliminary fix-ups. */
526 for (hp = Table; hp < ARRAY_END(Table); hp++) {
527 if (!hp->CanSet && hp->Value)
528 die("cannot set system header %s", hp->Name);
530 hp->Value = TrimSpaces(hp->Value);
531 if (*hp->Value == '\0')
536 /* Set From or Sender. */
537 if ((p = innconf->fromhost) == NULL)
538 sysdie("cannot get hostname");
539 if (HDR(_from) == NULL)
540 HDR(_from) = FormatUserName(pwp, p);
542 if (strlen(pwp->pw_name) + strlen(p) + 2 > sizeof(buff))
543 die("username and host are too long");
544 sprintf(buff, "%s@%s", pwp->pw_name, p);
545 strlcpy(from, HDR(_from), SMBUF);
546 HeaderCleanFrom(from);
547 if (strcmp(from, buff) != 0)
548 HDR(_sender) = xstrdup(buff);
551 if (HDR(_date) == NULL) {
553 if (!makedate(-1, true, buff, sizeof(buff)))
554 die("cannot generate Date header");
555 HDR(_date) = xstrdup(buff);
558 /* Newsgroups are checked later. */
560 /* Set Subject; Control overrides the subject. */
562 CheckControl(HDR(_control), pwp);
567 die("required Subject header is missing or empty");
568 else if (HDR(_alsocontrol))
569 CheckControl(HDR(_alsocontrol), pwp);
571 if (strncmp(p, "Re: ", 4) == 0 && HDR(_references) == NULL)
572 die("article subject begins with \"Re: \" but has no references");
577 if (HDR(_messageid) == NULL) {
578 if ((p = GenerateMessageID(innconf->domain)) == NULL)
579 die("cannot generate Message-ID header");
580 HDR(_messageid) = xstrdup(p);
582 else if ((p = strchr(HDR(_messageid), '@')) == NULL
583 || strchr(++p, '@') != NULL) {
584 die("message ID must have exactly one @");
588 if (HDR(_path) == NULL) {
589 #if defined(DO_INEWS_PATH)
590 if ((p = innconf->pathhost) != NULL) {
592 HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
594 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
596 else if (innconf->server != NULL) {
597 if ((p = GetFQDN(innconf->domain)) == NULL)
598 sysdie("cannot get hostname");
599 HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
602 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
605 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
606 #endif /* defined(DO_INEWS_PATH) */
609 /* Reply-To; left alone. */
610 /* Sender; set above. */
611 /* Followup-To; checked with Newsgroups. */
614 if (GetTimeInfo(&Now) < 0)
615 sysdie("cannot get the time");
616 if (HDR(_expires) && parsedate(HDR(_expires), &Now) == -1)
617 die("cannot parse \"%s\" as an expiration date", HDR(_expires));
619 /* References; left alone. */
620 /* Control; checked above. */
623 if ((p = HDR(_distribution)) != NULL) {
625 CheckDistribution(p);
629 /* Set Organization. */
631 && HDR(_organization) == NULL
632 && (p = innconf->organization) != NULL) {
633 HDR(_organization) = xstrdup(p);
636 /* Keywords; left alone. */
637 /* Summary; left alone. */
638 /* Approved; left alone. */
641 sprintf(buff, "%d", linecount);
642 HDR(_lines) = xstrdup(buff);
644 /* Check Supersedes. */
645 if (HDR(_supersedes))
646 CheckCancel(HDR(_supersedes), true);
648 /* Now make sure everything is there. */
649 for (hp = Table; hp < ARRAY_END(Table); hp++)
650 if (hp->Type == HTreq && hp->Value == NULL)
651 die("required header %s is missing or empty", hp->Name);
656 ** Try to append $HOME/.signature to the article. When in doubt, exit
657 ** out in order to avoid postings like "Sorry, I forgot my .signature
658 ** -- here's the article again."
661 AppendSignature(bool UseMalloc, char *article, char *homedir, int *linesp)
663 static char NOSIG[] = "Can't add your .signature (%s), article not posted";
673 if (strlen(homedir) > sizeof(buff) - 14)
674 die("home directory path too long");
675 sprintf(buff, "%s/.signature", homedir);
676 if ((F = fopen(buff, "r")) == NULL) {
679 fprintf(stderr, NOSIG, strerror(errno));
684 length = fread(buff, 1, sizeof buff - 2, F);
688 die("signature file is empty");
690 sysdie("cannot read signature file");
691 if (length == sizeof buff - 2 && !i)
692 die("signature is too large");
694 /* Make sure the buffer ends with \n\0. */
695 if (buff[length - 1] != '\n')
696 buff[length++] = '\n';
699 /* Count the lines. */
700 for (i = 0, p = buff; (p = strchr(p, '\n')) != NULL; p++)
701 if (++i > SIG_MAXLINES)
702 die("signature has too many lines");
705 /* Grow the article to have the signature. */
707 artsize = i + sizeof(SIGSEP) - 1 + length + 1;
709 p = xmalloc(artsize);
710 strlcpy(p, article, artsize);
714 article = xrealloc(article, artsize);
715 strlcat(article, SIGSEP, artsize);
716 strlcat(article, buff, artsize);
722 ** See if the user has more included text than new text. Simple-minded, but
723 ** reasonably effective for catching neophyte's mistakes. A line starting
724 ** with > is included text. Decrement the count on lines starting with <
725 ** so that we don't reject diff(1) output.
728 CheckIncludedText(char *p, int lines)
747 if ((p = strchr(p, '\n')) == NULL)
750 if ((i * 2 > lines) && (lines > 40))
751 die("more included text than new text");
757 ** Read stdin into a string and return it. Can't use ReadInDescriptor
758 ** since that will fail if stdin is a tty.
770 article = xmalloc(size);
771 end = &article[size - 3];
772 for (p = article; (i = getchar()) != EOF; *p++ = (char)i)
774 article = xrealloc(article, size + BUFSIZ);
775 p = &article[size - 3];
777 end = &article[size - 3];
780 /* Force a \n terminator. */
781 if (p > article && p[-1] != '\n')
790 ** Offer the article to the server, return its reply.
793 OfferArticle(char *buff, bool Authorized)
795 fprintf(ToServer, "post\r\n");
797 if (fgets(buff, HEADER_STRLEN, FromServer) == NULL)
798 sysdie(Authorized ? "Can't offer article to server (authorized)"
799 : "Can't offer article to server");
805 ** Spool article to temp file.
808 Spoolit(char *article, size_t Length, char *deadfile)
814 /* Try to write to the deadfile. */
815 if (deadfile == NULL)
817 F = xfopena(deadfile);
819 sysdie("cannot create spool file");
821 /* Write the headers and a blank line. */
822 for (hp = Table; hp < ARRAY_END(Table); hp++)
824 fprintf(F, "%s: %s\n", hp->Name, hp->Value);
825 for (i = 0; i < OtherCount; i++)
826 fprintf(F, "%s\n", OtherHeaders[i]);
829 sysdie("cannot write headers");
831 /* Write the article and exit. */
832 if (fwrite(article, 1, Length, F) != Length)
833 sysdie("cannot write article");
835 sysdie("cannot write article");
836 if (fclose(F) == EOF)
837 sysdie("cannot close spool file");
842 ** Print usage message and exit.
847 fprintf(stderr, "Usage: inews [-D] [-h] [header_flags] [article]\n");
848 /* Don't call QuitServer here -- connection isn't open yet. */
854 main(int ac, char *av[])
856 static char NOCONNECT[] = "cannot connect to server";
867 char buff[HEADER_STRLEN];
868 char SpoolMessage[HEADER_STRLEN];
874 /* First thing, set up logging and our identity. */
875 message_program_name = "inews";
877 /* Find out who we are. */
879 if (uid == (uid_t) -1)
880 sysdie("cannot get your user ID");
881 if ((pwp = getpwuid(uid)) == NULL)
882 sysdie("cannot get your passwd entry");
891 if (!innconf_read(NULL))
897 while ((i = getopt(ac, av, "DNAVWORShx:a:c:d:e:f:n:p:r:t:F:o:w:")) != EOF)
909 /* Ignore C News options. */
924 Exclusions = concat(optarg, "!", (char *) 0);
929 /* Header lines that can be specified on the command line. */
930 case 'a': HDR(_approved) = optarg; break;
931 case 'c': HDR(_control) = optarg; break;
932 case 'd': HDR(_distribution) = optarg; break;
933 case 'e': HDR(_expires) = optarg; break;
934 case 'f': HDR(_from) = optarg; break;
935 case 'n': HDR(_newsgroups) = optarg; break;
936 case 'r': HDR(_replyto) = optarg; break;
937 case 't': HDR(_subject) = optarg; break;
938 case 'F': HDR(_references) = optarg; break;
939 case 'o': HDR(_organization) = optarg; break;
940 case 'w': HDR(_followupto) = optarg; break;
945 /* Parse positional arguments; at most one, the input file. */
952 article = ReadStdin();
955 /* Read named file. */
956 article = ReadInFile(av[0], (struct stat *)NULL);
958 sysdie("cannot read input file");
965 /* Try to open a connection to the server. */
966 if (NNTPremoteopen(port, &FromServer, &ToServer, buff) < 0) {
968 if ((p = strchr(buff, '\n')) != NULL)
970 if ((p = strchr(buff, '\r')) != NULL)
972 strcpy(SpoolMessage, buff[0] ? buff : NOCONNECT);
973 deadfile = concatpath(pwp->pw_dir, "dead.article");
976 /* We now have an open server connection, so close it on failure. */
977 message_fatal_cleanup = fatal_cleanup;
979 /* See if we can post. */
982 /* Tell the server we're posting. */
983 setbuf(FromServer, xmalloc(BUFSIZ));
984 setbuf(ToServer, xmalloc(BUFSIZ));
985 fprintf(ToServer, "mode reader\r\n");
987 if (fgets(buff, HEADER_STRLEN, FromServer) == NULL)
988 sysdie("cannot tell server we're reading");
989 if ((j = atoi(buff)) != NNTP_BAD_COMMAND_VAL)
992 if (i != NNTP_POSTOK_VAL) {
993 /* We try to authenticate in case it is all the same possible
995 if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
996 die("you do not have permission to post");
1001 /* Basic processing. */
1002 for (hp = Table; hp < ARRAY_END(Table); hp++)
1003 hp->Size = strlen(hp->Name);
1005 article = StripOffHeaders(article);
1006 for (i = 0, p = article; (p = strchr(p, '\n')) != NULL; i++, p++)
1008 if (innconf->checkincludedtext)
1009 CheckIncludedText(article, i);
1011 article = AppendSignature(Mode == 'h', article, pwp->pw_dir, &SigLines);
1014 ProcessHeaders(AddOrg, i + SigLines, pwp);
1015 Length = strlen(article);
1016 if ((innconf->localmaxartsize > 0)
1017 && (Length > (size_t)innconf->localmaxartsize))
1018 die("article is larger than local limit of %ld bytes",
1019 innconf->localmaxartsize);
1021 /* Do final checks. */
1022 if (i == 0 && HDR(_control) == NULL)
1023 die("article is empty");
1024 for (hp = Table; hp < ARRAY_END(Table); hp++)
1025 if (hp->Value && (int)strlen(hp->Value) + hp->Size > HEADER_STRLEN)
1026 die("%s header is too long", hp->Name);
1027 for (i = 0; i < OtherCount; i++)
1028 if ((int)strlen(OtherHeaders[i]) > HEADER_STRLEN)
1029 die("header too long (maximum length is %d): %.40s...",
1030 HEADER_STRLEN, OtherHeaders[i]);
1033 /* Write the headers and a blank line. */
1034 for (hp = Table; hp < ARRAY_END(Table); hp++)
1036 printf("%s: %s\n", hp->Name, hp->Value);
1037 for (i = 0; i < OtherCount; i++)
1038 printf("%s\n", OtherHeaders[i]);
1040 if (FLUSH_ERROR(stdout))
1041 sysdie("cannot write headers");
1043 /* Write the article and exit. */
1044 if (fwrite(article, 1, Length, stdout) != Length)
1045 sysdie("cannot write article");
1051 warn("warning: %s", SpoolMessage);
1052 warn("article will be spooled");
1053 Spoolit(article, Length, deadfile);
1057 /* Article is prepared, offer it to the server. */
1058 i = OfferArticle(buff, false);
1059 if (i == NNTP_AUTH_NEEDED_VAL) {
1060 /* Posting not allowed, try to authorize. */
1061 if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
1062 sysdie("authorization error");
1063 i = OfferArticle(buff, true);
1065 if (i != NNTP_START_POST_VAL)
1066 die("server doesn't want the article: %s", buff);
1068 /* Write the headers, a blank line, then the article. */
1069 for (hp = Table; hp < ARRAY_END(Table); hp++)
1071 fprintf(ToServer, "%s: %s\r\n", hp->Name, hp->Value);
1072 for (i = 0; i < OtherCount; i++)
1073 fprintf(ToServer, "%s\r\n", OtherHeaders[i]);
1074 fprintf(ToServer, "\r\n");
1075 if (NNTPsendarticle(article, ToServer, true) < 0)
1076 sysdie("cannot send article to server");
1077 SafeFlush(ToServer);
1079 if (fgets(buff, sizeof buff, FromServer) == NULL)
1080 sysdie("no reply from server after sending the article");
1081 if ((p = strchr(buff, '\r')) != NULL)
1083 if ((p = strchr(buff, '\n')) != NULL)
1085 if (atoi(buff) != NNTP_POSTEDOK_VAL)
1086 die("cannot send article to server: %s", buff);