1 /* $Id: inews.c 10365 2020-05-10 12:58:37Z iulius $
2 **
3 ** Send an article (prepared by someone on the local site) to the
4 ** master news server.
5 */
6
7 #include "config.h"
8 #include "clibrary.h"
9 #include <ctype.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <pwd.h>
13 #include <sys/stat.h>
14
15 #ifdef HAVE_SYS_TIME_H
16 # include <sys/time.h>
17 #endif
18 #include <time.h>
19
20 #include "inn/innconf.h"
21 #include "inn/libinn.h"
22 #include "inn/messages.h"
23 #include "inn/newsuser.h"
24 #include "inn/nntp.h"
25 #include "inn/paths.h"
26
27 /* Signature handling. The separator will be appended before the signature,
28 and at most SIG_MAXLINES will be appended. */
29 #define SIG_MAXLINES 4
30 #define SIG_SEPARATOR "-- \n"
31
32 #define FLUSH_ERROR(F) (fflush((F)) == EOF || ferror((F)))
33 #define LPAREN '(' /* For vi :-) */
34 #define HEADER_DELTA 20
35 #define GECOSTERM(c) \
36 ((c) == ',' || (c) == ';' || (c) == ':' || (c) == LPAREN)
37
38 typedef enum _HEADERTYPE {
39 HTobs,
40 HTreq,
41 HTstd
42 } HEADERTYPE;
43
44 typedef struct _HEADER {
45 const char *Name;
46 bool CanSet;
47 HEADERTYPE Type;
48 int Size;
49 char *Value;
50 } HEADER;
51
52 static bool Dump;
53 static bool Revoked;
54 static bool Spooling;
55 static char **OtherHeaders;
56 static char SIGSEP[] = SIG_SEPARATOR;
57 static FILE *FromServer;
58 static FILE *ToServer;
59 static int OtherCount;
60 static int OtherSize;
61 static const char *Exclusions = "";
62 static const char * const BadDistribs[] = {
63 BAD_DISTRIBS
64 };
65
66 static void Usage(void) __attribute__ ((__noreturn__));
67
68 static HEADER Table[] = {
69 /* Name Canset Type */
70 { "Path", true, HTstd, 0, NULL },
71 #define _path 0
72 { "From", true, HTstd, 0, NULL },
73 #define _from 1
74 { "Newsgroups", true, HTreq, 0, NULL },
75 #define _newsgroups 2
76 { "Subject", true, HTreq, 0, NULL },
77 #define _subject 3
78 { "Control", true, HTstd, 0, NULL },
79 #define _control 4
80 { "Supersedes", true, HTstd, 0, NULL },
81 #define _supersedes 5
82 { "Followup-To", true, HTstd, 0, NULL },
83 #define _followupto 6
84 { "Date", true, HTstd, 0, NULL },
85 #define _date 7
86 { "Organization", true, HTstd, 0, NULL },
87 #define _organization 8
88 { "Lines", true, HTstd, 0, NULL },
89 #define _lines 9
90 { "Sender", true, HTstd, 0, NULL },
91 #define _sender 10
92 { "Approved", true, HTstd, 0, NULL },
93 #define _approved 11
94 { "Distribution", true, HTstd, 0, NULL },
95 #define _distribution 12
96 { "Expires", true, HTstd, 0, NULL },
97 #define _expires 13
98 { "Message-ID", true, HTstd, 0, NULL },
99 #define _messageid 14
100 { "References", true, HTstd, 0, NULL },
101 #define _references 15
102 { "Reply-To", true, HTstd, 0, NULL },
103 #define _replyto 16
104 { "Also-Control", true, HTstd, 0, NULL },
105 #define _alsocontrol 17
106 { "Xref", false, HTstd, 0, NULL },
107 { "Summary", true, HTstd, 0, NULL },
108 { "Keywords", true, HTstd, 0, NULL },
109 { "Date-Received", false, HTobs, 0, NULL },
110 { "Received", false, HTobs, 0, NULL },
111 { "Posted", false, HTobs, 0, NULL },
112 { "Posting-Version", false, HTobs, 0, NULL },
113 { "Relay-Version", false, HTobs, 0, NULL },
114 };
115
116 #define HDR(_x) (Table[(_x)].Value)
117
118
119
120 /*
121 ** Send the server a quit message, wait for a reply.
122 */
123 static void
QuitServer(int x)124 QuitServer(int x)
125 {
126 char buff[MED_BUFFER];
127 char *p;
128
129 if (Spooling)
130 exit(x);
131 if (x)
132 warn("article not posted");
133 fprintf(ToServer, "quit\r\n");
134 if (FLUSH_ERROR(ToServer))
135 sysdie("cannot send quit to server");
136 if (fgets(buff, sizeof buff, FromServer) == NULL)
137 sysdie("warning: server did not reply to quit");
138 if ((p = strchr(buff, '\r')) != NULL)
139 *p = '\0';
140 if ((p = strchr(buff, '\n')) != NULL)
141 *p = '\0';
142 if (atoi(buff) != NNTP_OK_QUIT)
143 die("server did not reply to quit properly: %s", buff);
144 fclose(FromServer);
145 fclose(ToServer);
146 exit(x);
147 }
148
149
150 /*
151 ** Failure handler, called by die. Calls QuitServer to cleanly shut down the
152 ** connection with the remote server before exiting.
153 */
154 static int
fatal_cleanup(void)155 fatal_cleanup(void)
156 {
157 /* Don't recurse. */
158 message_fatal_cleanup = NULL;
159
160 /* QuitServer does all the work. */
161 QuitServer(1);
162 return 1;
163 }
164
165
166 /*
167 ** Flush a stdio FILE; exit if there are any errors.
168 */
169 static void
SafeFlush(FILE * F)170 SafeFlush(FILE *F)
171 {
172 if (FLUSH_ERROR(F))
173 sysdie("cannot send text to server");
174 }
175
176
177 /*
178 ** Trim trailing spaces, return pointer to first non-space char.
179 */
180 static char *
TrimSpaces(char * p)181 TrimSpaces(char *p)
182 {
183 char *start;
184
185 for (start = p; ISWHITE(*start); start++)
186 continue;
187 for (p = start + strlen(start); p > start && isspace((unsigned char) p[-1]); )
188 *--p = '\0';
189 return start;
190 }
191
192
193 /*
194 ** Mark the end of the header starting at p, and return a pointer
195 ** to the start of the next one. Handles continuations.
196 */
197 static char *
NextHeader(char * p)198 NextHeader(char *p)
199 {
200 char *q;
201 for (q = p; ; p++) {
202 if ((p = strchr(p, '\n')) == NULL) {
203 die("article is all headers");
204 }
205 /* Check the maximum length of a single line. */
206 if (p - q + 1 > MAXARTLINELENGTH) {
207 die("header line too long");
208 }
209 /* Check if there is a continuation line for the header. */
210 if (ISWHITE(p[1])) {
211 q = p + 1;
212 continue;
213 }
214 *p = '\0';
215 return p + 1;
216 }
217 }
218
219
220 /*
221 ** Strip any headers off the article and dump them into the table.
222 */
223 static char *
StripOffHeaders(char * article)224 StripOffHeaders(char *article)
225 {
226 char *p;
227 char *q;
228 HEADER *hp;
229 char c;
230
231 /* Set up the other headers list. */
232 OtherSize = HEADER_DELTA;
233 OtherHeaders = xmalloc(OtherSize * sizeof(char *));
234 OtherCount = 0;
235
236 /* Scan through buffer, a header at a time. */
237 for (p = article; ; ) {
238
239 if ((q = strchr(p, ':')) == NULL)
240 die("no colon in header line \"%.30s...\"", p);
241 if (q[1] == '\n' && !ISWHITE(q[2])) {
242 /* Empty header; ignore this one, get next line. */
243 p = NextHeader(p);
244 if (*p == '\n')
245 break;
246 }
247
248 if (q[1] != '\0' && !ISWHITE(q[1])) {
249 if ((q = strchr(q, '\n')) != NULL)
250 *q = '\0';
251 die("no space after colon in \"%.30s...\"", p);
252 }
253
254 /* See if it's a known header. */
255 c = islower((unsigned char) *p) ? toupper((unsigned char) *p) : *p;
256 for (hp = Table; hp < ARRAY_END(Table); hp++)
257 if (c == hp->Name[0]
258 && p[hp->Size] == ':'
259 && ISWHITE(p[hp->Size + 1])
260 && strncasecmp(p, hp->Name, hp->Size) == 0) {
261 if (hp->Type == HTobs)
262 die("obsolete header: %s", hp->Name);
263 if (hp->Value)
264 die("duplicate header: %s", hp->Name);
265 for (q = &p[hp->Size + 1]; ISWHITE(*q); q++)
266 continue;
267 hp->Value = q;
268 break;
269 }
270
271 /* No; add it to the set of other headers. */
272 if (hp == ARRAY_END(Table)) {
273 if (OtherCount >= OtherSize - 1) {
274 OtherSize += HEADER_DELTA;
275 OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
276 }
277 OtherHeaders[OtherCount++] = p;
278 }
279
280 /* Get start of next header; if it's a blank line, we hit the end. */
281 p = NextHeader(p);
282 if (*p == '\n')
283 break;
284 }
285
286 return p + 1;
287 }
288
289
290
291 /*
292 ** See if the user is allowed to cancel the indicated message. Assumes
293 ** that the Sender or From line has already been filled in.
294 */
295 static void
CheckCancel(char * msgid,bool JustReturn)296 CheckCancel(char *msgid, bool JustReturn)
297 {
298 char localfrom[SMBUF];
299 char *p;
300 char buff[BUFSIZ];
301 char remotefrom[SMBUF];
302
303 /* Ask the server for the article. */
304 fprintf(ToServer, "head %s\r\n", msgid);
305 SafeFlush(ToServer);
306 if (fgets(buff, sizeof buff, FromServer) == NULL
307 || atoi(buff) != NNTP_OK_HEAD) {
308 if (JustReturn)
309 return;
310 die("server has no such article");
311 }
312
313 /* Read the headers, looking for the From or Sender. */
314 remotefrom[0] = '\0';
315 while (fgets(buff, sizeof buff, FromServer) != NULL) {
316 if ((p = strchr(buff, '\r')) != NULL)
317 *p = '\0';
318 if ((p = strchr(buff, '\n')) != NULL)
319 *p = '\0';
320 if (buff[0] == '.' && buff[1] == '\0')
321 break;
322 if (strncasecmp(buff, "Sender:", 7) == 0)
323 strlcpy(remotefrom, TrimSpaces(&buff[7]), SMBUF);
324 else if (remotefrom[0] == '\0' && strncasecmp(buff, "From:", 5) == 0)
325 strlcpy(remotefrom, TrimSpaces(&buff[5]), SMBUF);
326 }
327 if (remotefrom[0] == '\0') {
328 if (JustReturn)
329 return;
330 die("article is garbled");
331 }
332 HeaderCleanFrom(remotefrom);
333
334 /* Get the local user. */
335 strlcpy(localfrom, HDR(_sender) ? HDR(_sender) : HDR(_from), SMBUF);
336 HeaderCleanFrom(localfrom);
337
338 /* Is the right person cancelling? */
339 if (strcasecmp(localfrom, remotefrom) != 0)
340 die("article was posted by \"%s\" and you are \"%s\"", remotefrom,
341 localfrom);
342 }
343
344
345 /*
346 ** See if the user is the news administrator.
347 */
348 static bool
AnAdministrator(void)349 AnAdministrator(void)
350 {
351 uid_t news_uid;
352 gid_t news_gid;
353
354 if (Revoked)
355 return false;
356
357 /* Find out who we are. */
358 if (get_news_uid_gid(&news_uid, &news_gid, false) != 0) {
359 /* Silent failure; clients might not have the group. */
360 return false;
361 }
362 if (getuid() == news_uid)
363 return true;
364
365 /* See if we are in the right group and examine process
366 * supplementary groups, rather than the group(5) file entry.
367 */
368 #ifdef HAVE_GETGROUPS
369 {
370 int ngroups = getgroups(0, 0);
371 GETGROUPS_T *groups, *gp;
372 int rv;
373 int rest;
374
375 groups = (GETGROUPS_T *) xmalloc(ngroups * sizeof(GETGROUPS_T));
376 if ((rv = getgroups(ngroups, groups)) < 0) {
377 /* Silent failure; client doesn't have the group. */
378 return false;
379 }
380 for (rest = ngroups, gp = groups; rest > 0; rest--, gp++) {
381 if (*gp == (GETGROUPS_T) news_gid)
382 return true;
383 }
384 }
385 #endif
386
387 return false;
388 }
389
390
391 /*
392 ** Check the control message, and see if it's legit.
393 */
394 static void
CheckControl(char * ctrl)395 CheckControl(char *ctrl)
396 {
397 char *p;
398 char *q;
399 char save;
400
401 /* Snip off the first word. */
402 for (p = ctrl; ISWHITE(*p); p++)
403 continue;
404 for (ctrl = p; *p && !ISWHITE(*p); p++)
405 continue;
406 if (p == ctrl)
407 die("empty control message");
408 save = *p;
409 *p = '\0';
410
411 if (strcasecmp(ctrl, "cancel") == 0) {
412 for (q = p + 1; ISWHITE(*q); q++)
413 continue;
414 if (*q == '\0')
415 die("message ID missing in cancel");
416 if (!Spooling)
417 CheckCancel(q, false);
418 }
419 else if (strcasecmp(ctrl, "checkgroups") == 0
420 || strcasecmp(ctrl, "ihave") == 0
421 || strcasecmp(ctrl, "sendme") == 0
422 || strcasecmp(ctrl, "newgroup") == 0
423 || strcasecmp(ctrl, "rmgroup") == 0) {
424 if (!AnAdministrator())
425 die("ask your news administrator to do the %s for you", ctrl);
426 }
427 else {
428 die("%s is not a valid control message", ctrl);
429 }
430 *p = save;
431 }
432
433
434
435 /*
436 ** Parse the GECOS field to get the user's full name. This comes Sendmail's
437 ** buildfname routine. Ignore leading stuff like "23-" "stuff]-" or
438 ** "stuff -" as well as trailing whitespace, or anything that comes after
439 ** a comma, semicolon, or in parentheses. This seems to strip off most of
440 ** the UCB or ATT stuff people fill out the entries with. Also, turn &
441 ** into the login name, with perhaps an initial capital. (Everyone seems
442 ** to hate that, but everyone also supports it.)
443 */
444 static char *
FormatUserName(struct passwd * pwp,char * node)445 FormatUserName(struct passwd *pwp, char *node)
446 {
447 char outbuff[SMBUF];
448 char *buff;
449 char *out;
450 char *p;
451
452 #if !defined(DONT_MUNGE_GETENV)
453 memset(outbuff, 0, SMBUF);
454 if ((p = getenv("NAME")) != NULL)
455 strlcpy(outbuff, p, SMBUF);
456 if (strlen(outbuff) == 0) {
457 #endif /* !defined(DONT_MUNGE_GETENV) */
458
459
460 #ifndef DO_MUNGE_GECOS
461 strlcpy(outbuff, pwp->pw_gecos, SMBUF);
462 #else
463 /* Be very careful here. If we're not, we can potentially overflow our
464 * buffer. Remember that on some Unix systems, the content of the GECOS
465 * field is under (untrusted) user control and we could be setgid. */
466 p = pwp->pw_gecos;
467 int left = SMBUF - 1;
468 if (*p == '*')
469 p++;
470 for (out = outbuff; *p && !GECOSTERM(*p) && left; p++) {
471 if (*p == '&') {
472 strncpy(out, pwp->pw_name, left);
473 if (islower((unsigned char) *out)
474 && (out == outbuff || !isalpha((unsigned char) out[-1])))
475 *out = toupper((unsigned char) *out);
476 while (*out) {
477 out++;
478 left--;
479 }
480 }
481 else if (*p == '-'
482 && p > pwp->pw_gecos
483 && (isdigit((unsigned char) p[-1]) || isspace((unsigned char) p[-1])
484 || p[-1] == ']')) {
485 out = outbuff;
486 left = SMBUF - 1;
487 }
488 else {
489 *out++ = *p;
490 left--;
491 }
492 }
493 *out = '\0';
494 #endif /* DO_MUNGE_GECOS */
495
496 #if !defined(DONT_MUNGE_GETENV)
497 }
498 #endif /* !defined(DONT_MUNGE_GETENV) */
499
500 out = TrimSpaces(outbuff);
501 if (out[0])
502 buff = concat(pwp->pw_name, "@", node, " (", out, ")", (char *) 0);
503 else
504 buff = concat(pwp->pw_name, "@", node, (char *) 0);
505 return buff;
506 }
507
508
509 /*
510 ** Check the Distribution header, and exit on error.
511 */
CheckDistribution(char * p)512 static void CheckDistribution(char *p)
513 {
514 static char SEPS[] = " \t,";
515 const char * const *dp;
516
517 if ((p = strtok(p, SEPS)) == NULL)
518 die("cannot parse Distribution header");
519 do {
520 for (dp = BadDistribs; *dp; dp++)
521 if (uwildmat(p, *dp))
522 die("illegal distribution %s", p);
523 } while ((p = strtok((char *)NULL, SEPS)) != NULL);
524 }
525
526
527 /*
528 ** Process all the headers. FYI, they're done in RFC-order.
529 */
530 static void
ProcessHeaders(bool AddOrg,int linecount,struct passwd * pwp)531 ProcessHeaders(bool AddOrg, int linecount, struct passwd *pwp)
532 {
533 static char PATHFLUFF[] = PATHMASTER;
534 HEADER *hp;
535 char *p;
536 char buff[SMBUF];
537 char from[SMBUF];
538
539 /* Do some preliminary fix-ups. */
540 for (hp = Table; hp < ARRAY_END(Table); hp++) {
541 if (!hp->CanSet && hp->Value)
542 die("cannot set system header %s", hp->Name);
543 if (hp->Value) {
544 hp->Value = TrimSpaces(hp->Value);
545 if (*hp->Value == '\0')
546 hp->Value = NULL;
547 }
548 }
549
550 /* Set From or Sender. */
551 if ((p = innconf->fromhost) == NULL)
552 sysdie("cannot get hostname");
553 if (HDR(_from) == NULL)
554 HDR(_from) = FormatUserName(pwp, p);
555 else {
556 if (strlen(pwp->pw_name) + strlen(p) + 2 > sizeof(buff))
557 die("username and host are too long");
558 sprintf(buff, "%s@%s", pwp->pw_name, p);
559 strlcpy(from, HDR(_from), SMBUF);
560 HeaderCleanFrom(from);
561 if (strcmp(from, buff) != 0)
562 HDR(_sender) = xstrdup(buff);
563 }
564
565 if (HDR(_date) == NULL) {
566 /* Set Date. */
567 if (!makedate(-1, true, buff, sizeof(buff)))
568 die("cannot generate Date header");
569 HDR(_date) = xstrdup(buff);
570 }
571
572 /* Newsgroups are checked later. */
573
574 /* Set Subject; Control overrides the subject. */
575 if (HDR(_control)) {
576 CheckControl(HDR(_control));
577 }
578 else {
579 p = HDR(_subject);
580 if (p == NULL)
581 die("required Subject header is missing or empty");
582 else if (HDR(_alsocontrol))
583 CheckControl(HDR(_alsocontrol));
584 #if 0
585 if (strncmp(p, "Re: ", 4) == 0 && HDR(_references) == NULL)
586 die("article subject begins with \"Re: \" but has no references");
587 #endif /* 0 */
588 }
589
590 /* Set Message-ID */
591 if (HDR(_messageid) == NULL) {
592 if ((p = GenerateMessageID(innconf->domain)) == NULL)
593 die("cannot generate Message-ID header");
594 HDR(_messageid) = xstrdup(p);
595 }
596 else if ((p = strchr(HDR(_messageid), '@')) == NULL
597 || strchr(++p, '@') != NULL) {
598 die("message ID must have exactly one @");
599 }
600
601 /* Set Path */
602 if (HDR(_path) == NULL) {
603 #if defined(DO_INEWS_PATH)
604 if ((p = innconf->pathhost) != NULL) {
605 if (*p)
606 HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
607 else
608 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
609 }
610 else if (innconf->server != NULL) {
611 p = inn_getfqdn(innconf->domain);
612 if (p == NULL)
613 sysdie("cannot get hostname");
614 HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
615 free(p);
616 }
617 else {
618 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
619 }
620 #else
621 HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
622 #endif /* defined(DO_INEWS_PATH) */
623 }
624
625 /* Reply-To; left alone. */
626 /* Sender; set above. */
627 /* Followup-To; checked with Newsgroups. */
628
629 /* Check Expires. */
630 if (HDR(_expires) && parsedate_rfc5322_lax(HDR(_expires)) == -1)
631 die("cannot parse \"%s\" as an expiration date", HDR(_expires));
632
633 /* References; left alone. */
634 /* Control; checked above. */
635
636 /* Distribution. */
637 if ((p = HDR(_distribution)) != NULL) {
638 p = xstrdup(p);
639 CheckDistribution(p);
640 free(p);
641 }
642
643 /* Set Organization. */
644 if (AddOrg
645 && HDR(_organization) == NULL
646 && (p = innconf->organization) != NULL) {
647 HDR(_organization) = xstrdup(p);
648 }
649
650 /* Keywords; left alone. */
651 /* Summary; left alone. */
652 /* Approved; left alone. */
653
654 /* Set Lines */
655 sprintf(buff, "%d", linecount);
656 HDR(_lines) = xstrdup(buff);
657
658 /* Check Supersedes. */
659 if (HDR(_supersedes))
660 CheckCancel(HDR(_supersedes), true);
661
662 /* Now make sure everything is there. */
663 for (hp = Table; hp < ARRAY_END(Table); hp++)
664 if (hp->Type == HTreq && hp->Value == NULL)
665 die("required header %s is missing or empty", hp->Name);
666 }
667
668
669 /*
670 ** Try to append $HOME/.signature to the article. When in doubt, exit
671 ** out in order to avoid postings like "Sorry, I forgot my .signature
672 ** -- here's the article again."
673 */
674 static char *
AppendSignature(bool UseMalloc,char * article,char * homedir,int * linesp)675 AppendSignature(bool UseMalloc, char *article, char *homedir, int *linesp)
676 {
677 int i;
678 int length;
679 size_t artsize;
680 char *p;
681 char buff[BUFSIZ];
682 FILE *F;
683
684 /* Open the file. */
685 *linesp = 0;
686 if (strlen(homedir) > sizeof(buff) - 14)
687 die("home directory path too long");
688 snprintf(buff, sizeof(buff), "%s/.signature", homedir);
689 if ((F = fopen(buff, "r")) == NULL) {
690 if (errno == ENOENT)
691 return article;
692 fprintf(stderr, "Can't add your .signature (%s), article not posted",
693 strerror(errno));
694 QuitServer(1);
695 }
696
697 /* Read it in. */
698 length = fread(buff, 1, sizeof buff - 2, F);
699 i = feof(F);
700 fclose(F);
701 if (length == 0)
702 die("signature file is empty");
703 if (length < 0)
704 sysdie("cannot read signature file");
705 if (length == sizeof buff - 2 && !i)
706 die("signature is too large");
707
708 /* Make sure the buffer ends with \n\0. */
709 if (buff[length - 1] != '\n')
710 buff[length++] = '\n';
711 buff[length] = '\0';
712
713 /* Count the lines. */
714 for (i = 0, p = buff; (p = strchr(p, '\n')) != NULL; p++)
715 if (++i > SIG_MAXLINES)
716 die("signature has too many lines");
717 *linesp = 1 + i;
718
719 /* Grow the article to have the signature. */
720 i = strlen(article);
721 artsize = i + sizeof(SIGSEP) - 1 + length + 1;
722 if (UseMalloc) {
723 p = xmalloc(artsize);
724 strlcpy(p, article, artsize);
725 article = p;
726 }
727 else
728 article = xrealloc(article, artsize);
729 strlcat(article, SIGSEP, artsize);
730 strlcat(article, buff, artsize);
731 return article;
732 }
733
734
735 /*
736 ** See if the user has more included text than new text. Simple-minded, but
737 ** reasonably effective for catching neophyte's mistakes. A line starting
738 ** with > is included text. Decrement the count on lines starting with <
739 ** so that we don't reject diff(1) output.
740 */
741 static void
CheckIncludedText(char * p,int lines)742 CheckIncludedText(char *p, int lines)
743 {
744 int i;
745
746 for (i = 0; ; p++) {
747 switch (*p) {
748 case '>':
749 i++;
750 break;
751 case '|':
752 i++;
753 break;
754 case ':':
755 i++;
756 break;
757 case '<':
758 i--;
759 break;
760 }
761 if ((p = strchr(p, '\n')) == NULL)
762 break;
763 }
764 if ((i * 2 > lines) && (lines > 40))
765 die("more included text than new text");
766 }
767
768
769
770 /*
771 ** Read stdin into a string and return it. Can't use ReadInDescriptor
772 ** since that will fail if stdin is a tty.
773 */
774 static char *
ReadStdin(void)775 ReadStdin(void)
776 {
777 int size;
778 char *p;
779 char *article;
780 char *end;
781 int i;
782
783 size = BUFSIZ;
784 article = xmalloc(size);
785 end = &article[size - 3];
786 for (p = article; (i = getchar()) != EOF; *p++ = (char)i)
787 if (p == end) {
788 article = xrealloc(article, size + BUFSIZ);
789 p = &article[size - 3];
790 size += BUFSIZ;
791 end = &article[size - 3];
792 }
793
794 /* Force a \n terminator. */
795 if (p > article && p[-1] != '\n')
796 *p++ = '\n';
797 *p = '\0';
798 return article;
799 }
800
801
802
803 /*
804 ** Offer the article to the server, return its reply.
805 */
806 static int
OfferArticle(char * buff,bool Authorized)807 OfferArticle(char *buff, bool Authorized)
808 {
809 fprintf(ToServer, "post\r\n");
810 SafeFlush(ToServer);
811 if (fgets(buff, MED_BUFFER, FromServer) == NULL)
812 sysdie(Authorized ? "Can't offer article to server (authorized)"
813 : "Can't offer article to server");
814 return atoi(buff);
815 }
816
817
818 /*
819 ** Spool article to temp file.
820 */
821 static void
Spoolit(char * article,size_t Length,char * deadfile)822 Spoolit(char *article, size_t Length, char *deadfile)
823 {
824 HEADER *hp;
825 FILE *F;
826 int i;
827
828 /* Try to write to the deadfile. */
829 if (deadfile == NULL)
830 return;
831 F = xfopena(deadfile);
832 if (F == NULL)
833 sysdie("cannot create spool file");
834
835 /* Write the headers and a blank line. */
836 for (hp = Table; hp < ARRAY_END(Table); hp++)
837 if (hp->Value)
838 fprintf(F, "%s: %s\n", hp->Name, hp->Value);
839 for (i = 0; i < OtherCount; i++)
840 fprintf(F, "%s\n", OtherHeaders[i]);
841 fprintf(F, "\n");
842 if (FLUSH_ERROR(F))
843 sysdie("cannot write headers");
844
845 /* Write the article and exit. */
846 if (fwrite(article, 1, Length, F) != Length)
847 sysdie("cannot write article");
848 if (FLUSH_ERROR(F))
849 sysdie("cannot write article");
850 if (fclose(F) == EOF)
851 sysdie("cannot close spool file");
852 }
853
854
855 /*
856 ** Print usage message and exit.
857 */
858 static void
Usage(void)859 Usage(void)
860 {
861 fprintf(stderr, "Usage: inews [-D] [-h] [header_flags] [article]\n");
862 /* Don't call QuitServer here -- connection isn't open yet. */
863 exit(1);
864 }
865
866
867 int
main(int ac,char * av[])868 main(int ac, char *av[])
869 {
870 static char NOCONNECT[] = "cannot connect to server";
871 int i;
872 char *p;
873 HEADER *hp;
874 int j;
875 int port;
876 int Mode;
877 int SigLines;
878 struct passwd *pwp;
879 char *article;
880 char *deadfile;
881 char buff[MED_BUFFER];
882 char SpoolMessage[MED_BUFFER];
883 bool DoSignature;
884 bool AddOrg;
885 size_t Length;
886 uid_t uid;
887
888 /* First thing, set up logging and our identity. */
889 message_program_name = "inews";
890
891 /* Find out who we are. */
892 uid = geteuid();
893 if (uid == (uid_t) -1)
894 sysdie("cannot get your user ID");
895 if ((pwp = getpwuid(uid)) == NULL)
896 sysdie("cannot get your passwd entry");
897
898 /* Set defaults. */
899 Mode = '\0';
900 Dump = false;
901 DoSignature = true;
902 AddOrg = true;
903 port = 0;
904
905 if (!innconf_read(NULL))
906 exit(1);
907
908 umask(NEWSUMASK);
909
910 /* Parse JCL. */
911 while ((i = getopt(ac, av, "DNAVWORShx:a:c:d:e:f:n:p:r:t:F:o:w:")) != EOF)
912 switch (i) {
913 default:
914 Usage();
915 /* NOTREACHED */
916 case 'D':
917 case 'N':
918 Dump = true;
919 break;
920 case 'A':
921 case 'V':
922 case 'W':
923 /* Ignore C News options. */
924 break;
925 case 'O':
926 AddOrg = false;
927 break;
928 case 'R':
929 Revoked = true;
930 break;
931 case 'S':
932 DoSignature = false;
933 break;
934 case 'h':
935 Mode = i;
936 break;
937 case 'x':
938 Exclusions = concat(optarg, "!", (char *) 0);
939 break;
940 case 'p':
941 port = atoi(optarg);
942 break;
943 /* Header lines that can be specified on the command line. */
944 case 'a': HDR(_approved) = optarg; break;
945 case 'c': HDR(_control) = optarg; break;
946 case 'd': HDR(_distribution) = optarg; break;
947 case 'e': HDR(_expires) = optarg; break;
948 case 'f': HDR(_from) = optarg; break;
949 case 'n': HDR(_newsgroups) = optarg; break;
950 case 'r': HDR(_replyto) = optarg; break;
951 case 't': HDR(_subject) = optarg; break;
952 case 'F': HDR(_references) = optarg; break;
953 case 'o': HDR(_organization) = optarg; break;
954 case 'w': HDR(_followupto) = optarg; break;
955 }
956 ac -= optind;
957 av += optind;
958
959 /* Parse positional arguments; at most one, the input file. */
960 switch (ac) {
961 default:
962 Usage();
963 /* NOTREACHED */
964 case 0:
965 /* Read stdin. */
966 article = ReadStdin();
967 break;
968 case 1:
969 /* Read named file. */
970 article = ReadInFile(av[0], (struct stat *)NULL);
971 if (article == NULL)
972 sysdie("cannot read input file");
973 break;
974 }
975
976 if (port == 0)
977 port = NNTP_PORT;
978
979 /* Try to open a connection to the server. */
980 if (NNTPremoteopen(port, &FromServer, &ToServer, buff, sizeof(buff)) < 0) {
981 Spooling = true;
982 if ((p = strchr(buff, '\n')) != NULL)
983 *p = '\0';
984 if ((p = strchr(buff, '\r')) != NULL)
985 *p = '\0';
986 strlcpy(SpoolMessage, buff[0] ? buff : NOCONNECT,
987 sizeof(SpoolMessage));
988 deadfile = concatpath(pwp->pw_dir, "dead.article");
989 }
990 else {
991 /* We now have an open server connection, so close it on failure. */
992 message_fatal_cleanup = fatal_cleanup;
993
994 /* See if we can post. */
995 i = atoi(buff);
996
997 /* Tell the server we're posting. */
998 setbuf(FromServer, xmalloc(BUFSIZ));
999 setbuf(ToServer, xmalloc(BUFSIZ));
1000 fprintf(ToServer, "mode reader\r\n");
1001 SafeFlush(ToServer);
1002 if (fgets(buff, MED_BUFFER, FromServer) == NULL)
1003 sysdie("cannot tell server we're reading");
1004 if ((j = atoi(buff)) != NNTP_ERR_COMMAND)
1005 i = j;
1006
1007 if (i != NNTP_OK_BANNER_POST) {
1008 /* We try to authenticate in case it is all the same possible
1009 * to post. */
1010 if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
1011 die("you do not have permission to post");
1012 }
1013 deadfile = NULL;
1014 }
1015
1016 /* Basic processing. */
1017 for (hp = Table; hp < ARRAY_END(Table); hp++)
1018 hp->Size = strlen(hp->Name);
1019 if (Mode == 'h')
1020 article = StripOffHeaders(article);
1021 for (i = 0, p = article; (p = strchr(p, '\n')) != NULL; i++, p++)
1022 continue;
1023 if (innconf->checkincludedtext)
1024 CheckIncludedText(article, i);
1025 if (DoSignature)
1026 article = AppendSignature(Mode == 'h', article, pwp->pw_dir, &SigLines);
1027 else
1028 SigLines = 0;
1029 ProcessHeaders(AddOrg, i + SigLines, pwp);
1030 Length = strlen(article);
1031 if ((innconf->localmaxartsize != 0)
1032 && (Length > innconf->localmaxartsize))
1033 die("article is larger than local limit of %lu bytes",
1034 innconf->localmaxartsize);
1035
1036 /* Do final checks. */
1037 if (i == 0 && HDR(_control) == NULL)
1038 die("article is empty");
1039
1040 if (Dump) {
1041 /* Write the headers and a blank line. */
1042 for (hp = Table; hp < ARRAY_END(Table); hp++)
1043 if (hp->Value)
1044 printf("%s: %s\n", hp->Name, hp->Value);
1045 for (i = 0; i < OtherCount; i++)
1046 printf("%s\n", OtherHeaders[i]);
1047 printf("\n");
1048 if (FLUSH_ERROR(stdout))
1049 sysdie("cannot write headers");
1050
1051 /* Write the article and exit. */
1052 if (fwrite(article, 1, Length, stdout) != Length)
1053 sysdie("cannot write article");
1054 SafeFlush(stdout);
1055 QuitServer(0);
1056 }
1057
1058 if (Spooling) {
1059 warn("warning: %s", SpoolMessage);
1060 warn("article will be spooled");
1061 Spoolit(article, Length, deadfile);
1062 exit(0);
1063 }
1064
1065 /* Article is prepared, offer it to the server. */
1066 i = OfferArticle(buff, false);
1067 if (i == NNTP_FAIL_AUTH_NEEDED) {
1068 /* Posting not allowed, try to authorize. */
1069 if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
1070 sysdie("authorization error");
1071 i = OfferArticle(buff, true);
1072 }
1073 if (i != NNTP_CONT_POST)
1074 die("server doesn't want the article: %s", buff);
1075
1076 /* Write the headers, a blank line, then the article. */
1077 for (hp = Table; hp < ARRAY_END(Table); hp++)
1078 if (hp->Value)
1079 fprintf(ToServer, "%s: %s\r\n", hp->Name, hp->Value);
1080 for (i = 0; i < OtherCount; i++)
1081 fprintf(ToServer, "%s\r\n", OtherHeaders[i]);
1082 fprintf(ToServer, "\r\n");
1083 if (NNTPsendarticle(article, ToServer, true) < 0)
1084 sysdie("cannot send article to server");
1085 SafeFlush(ToServer);
1086
1087 if (fgets(buff, sizeof buff, FromServer) == NULL)
1088 sysdie("no reply from server after sending the article");
1089 if ((p = strchr(buff, '\r')) != NULL)
1090 *p = '\0';
1091 if ((p = strchr(buff, '\n')) != NULL)
1092 *p = '\0';
1093 if (atoi(buff) != NNTP_OK_POST)
1094 die("cannot send article to server: %s", buff);
1095
1096 /* Close up. */
1097 QuitServer(0);
1098 /* NOTREACHED */
1099 return 1;
1100 }
1101