tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

post.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : post.c
4 * Author : I. Lea
5 * Created : 1991-04-01
6 * Updated : 2021-03-14
7 * Notes : mail/post/replyto/followup/repost & cancel articles
8 *
9 * Copyright (c) 1991-2022 Iain Lea <iain@bricbrac.de>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41#ifndef TIN_H
42# include "tin.h"
43#endif /* !TIN_H */
44#ifndef TCURSES_H
45# include "tcurses.h"
46#endif /* !TCURSES_H */
47#ifndef VERSION_H
48# include "version.h"
49#endif /* !VERSION_H */
50
51
52#ifdef USE_CANLOCK
53# define ADD_CAN_KEY(id) { \
54 if (tinrc.cancel_lock_algo) { \
55 char key[1024]; \
56 char *kptr; \
57 key[0] = '\0'; \
58 if ((kptr = build_cankey(id, get_secret())) != NULL) { \
59 STRCPY(key, kptr); \
60 free(kptr); \
61 msg_add_header("Cancel-Key", key); \
62 } \
63 } \
64 }
65 /*
66 * only add lock here if we use an external inews
67 * and generate our own Message-IDs (EVIL_INSIDE)
68 * otherwise inews.c adds the canlock (if possible:
69 * i.e EVIL_INSIDE or server passed id on POST or
70 * user supplied ID by hand) for us!
71 */
72# ifdef EVIL_INSIDE
73# define ADD_CAN_LOCK(id) { \
74 if (tinrc.cancel_lock_algo) { \
75 char lock[1024]; \
76 char *lptr = (char *) 0; \
77 lock[0] = '\0'; \
78 if ((lptr = build_canlock(id, get_secret())) != NULL) { \
79 STRCPY(lock, lptr); \
80 free(lptr); \
81 msg_add_header("Cancel-Lock", lock); \
82 } \
83 } \
84 }
85# endif /* EVIL_INSIDE */
86#else
87# define ADD_CAN_KEY(id)
88# ifdef EVIL_INSIDE
89# define ADD_CAN_LOCK(id)
90# endif /* EVIL_INSIDE */
91#endif /* USE_CANLOCK */
92
93#ifdef EVIL_INSIDE
94/* gee! ugly hack - but works */
95# define ADD_MSG_ID_HEADER() { \
96 char mid[NNTP_STRLEN]; \
97 const char *mptr = (const char *) 0; \
98 mid[0] = '\0'; \
99 if ((mptr = build_messageid()) != NULL) { \
100 STRCPY(mid, mptr); \
101 msg_add_header("Message-ID", mid); \
102 ADD_CAN_LOCK(mid); \
103 } \
104 }
105#else
106# define ADD_MSG_ID_HEADER()
107#endif /* EVIL_INSIDE */
108
109#define MAX_MSG_HEADERS 20 /* shouldn't this be dynamic? */
110
111/* Different posting types for post_loop() */
112#define POST_QUICK 0
113#define POST_POSTPONED 1
114#define POST_NORMAL 2
115#define POST_RESPONSE 3
116#define POST_REPOST 4
117#define POST_SUPERSEDED 5
118
119/* When prompting for subject, display no more than 20 characters */
120#define DISPLAY_SUBJECT_LEN 20
121
122static int start_line_offset = 1; /* used by invoke_editor for line no. */
123
124char bug_addr[LEN]; /* address to add send bug reports to */
125static char my_distribution[LEN]; /* Distribution: */
126static char reply_to[LEN]; /* Reply-To: address */
127
128static struct msg_header {
129 char *name;
130 char *text;
132
134
135/*
136 * Local prototypes
137 */
138static FILE *create_mail_headers(char *filename, size_t filename_len, const char *suffix, const char *to, const char *subject, struct t_header *extra_hdrs);
139static char **build_nglist(char *ngs_list, int *ngcnt);
140static char **split_address_list(const char *addresses, unsigned int *cnt);
141static int add_mail_quote(FILE *fp, int respnum);
142static int append_mail(const char *the_article, const char *addr, const char *the_mailbox);
143static int build_post_hist_list(void);
144static int check_article_to_be_posted(const char *the_article, int art_type, struct t_group **group, t_bool art_unchanged, t_bool use_cache);
145static int mail_loop(const char *filename, t_function func, char *subject, const char *groupname, const char *prompt, FILE *articlefp);
146static int msg_add_x_body(FILE *fp_out, const char *body);
147static int msg_write_headers(FILE *fp);
148static int post_loop(int type, struct t_group *group, t_function func, const char *posting_msg, int art_type, int offset);
149static int process_post_hist(int n);
150static unsigned int get_recipients(struct t_header *hdr, char *buf, size_t buflen);
151static size_t skip_id(const char *id);
152static struct t_group *check_moderated(const char *groups, int *art_type, const char *failmsg);
153static t_bool address_in_list(const char *addresses, const char *address);
154static t_bool backup_article(const char *the_article);
155static t_bool check_for_spamtrap(const char *addr);
156static t_bool create_normal_article_headers(struct t_group *group, const char *newsgroups, int art_type);
157static t_bool damaged_id(const char *id);
158static t_bool fetch_postponed_article(const char tmp_file[], char subject[], char newsgroups[]);
159static t_bool insert_from_header(const char *infile);
160static t_bool is_crosspost(const char *xref);
161static t_bool must_include(const char *id);
162static t_bool repair_article(t_function *result, struct t_group *group);
163static t_bool stripped_double_ngs(char **newsgroups, int *ngcnt);
164static t_bool submit_mail_file(const char *file, struct t_group *group, FILE *articlefp, t_bool include_text);
165static t_function post_hist_left(void);
166static t_function post_hist_right(void);
167static t_function prompt_rejected(void);
168static t_function prompt_to_send(const char *subject);
169static void add_headers(const char *infile, const char *a_message_id);
170static void build_post_hist_line(int i);
171static void draw_post_hist_arrow(void);
172static void appendid(char **where, const char **what);
173static void find_reply_to_addr(char *from_addr, t_bool parse, struct t_header *hdr);
174static void free_post_hist_list(void);
175static void join_references(char *buffer, const char *oldrefs, const char *newref);
176static void msg_add_header(const char *name, const char *text);
177static void msg_add_x_headers(const char *headers);
178static void msg_free_headers(void);
179static void msg_init_headers(void);
180static void post_postponed_article(int ask, const char *subject, const char *newsgroups);
181static void postpone_article(const char *the_article);
182static void setup_check_article_screen(int *init);
183static void show_followup_info(void);
184static void show_post_hist_page(void);
185static void strip_double_ngs(char *ngs_list);
186static void update_active_after_posting(char *newsgroups);
187static void update_posted_info_file(const char *group, int action, const char *subj, const char *a_message_id);
188#ifdef FORGERY
189 static void make_path_header(char *line);
190 static void show_cancel_info(t_bool author, t_bool use_cache);
191#else
192 static void show_cancel_info(void);
193#endif /* FORGERY */
194#ifdef EVIL_INSIDE
195 static const char *build_messageid(void);
196 static char *radix32(unsigned long int num);
197#endif /* EVIL_INSIDE */
198#ifdef USE_CANLOCK
199 static char *build_cankey(const char *messageid, const char *secret);
200 static cl_hash_version get_cancel_lock_algo(void);
201#endif /* USE_CANLOCK */
202
203
205
206
207static t_function
209 const char *subject)
210{
211 char *smsg;
212 char buf[LEN];
213 char keyedit[MAXKEYLEN];
214 char keyquit[MAXKEYLEN];
215 char keysend[MAXKEYLEN];
216#ifdef HAVE_ISPELL
217 char keyispell[MAXKEYLEN];
218#endif /* HAVE_ISPELL */
219#ifdef HAVE_PGP_GPG
220 char keypgp[MAXKEYLEN];
221#endif /* HAVE_PGP_GPG */
223
224#if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
228 PrintFuncKey(keyispell, POST_ISPELL, post_send_keys),
229 PrintFuncKey(keypgp, POST_PGP, post_send_keys),
231#else
232# ifdef HAVE_ISPELL
236 PrintFuncKey(keyispell, POST_ISPELL, post_send_keys),
238# else
239# ifdef HAVE_PGP_GPG
243 PrintFuncKey(keypgp, POST_PGP, post_send_keys),
245# else
250# endif /* HAVE_PGP_GPG */
251# endif /* HAVE_ISPELL */
252#endif /* HAVE_ISPELL && HAVE_PGP_GPG */
253
255 sized_message(&smsg, buf, subject));
256 free(smsg);
257 return func;
258}
259
260
261static t_function
263 void)
264{
265 char keyedit[MAXKEYLEN], keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
266
267/* FIXME (what does this mean?) fix screen pos. */
268 Raw(FALSE);
269 /* TODO: replace hard coded key-name in txt_post_error_ask_postpone */
270 my_fprintf(stderr, "\n\n%s\n\n", _(txt_post_error_ask_postpone));
271 my_fflush(stderr);
272 Raw(TRUE);
273
279}
280
281
282/*
283 * Set up posting specific environment
284 */
285void
287 void)
288{
289 char *ptr;
290
291 /*
292 * check environment for REPLYTO
293 */
294 reply_to[0] = '\0';
295 if ((ptr = getenv("REPLYTO")) != NULL)
296 my_strncpy(reply_to, ptr, sizeof(reply_to) - 1);
297
298 /*
299 * check environment for DISTRIBUTION
300 */
301 my_distribution[0] = '\0';
302 if ((ptr = getenv("DISTRIBUTION")) != NULL)
303 my_strncpy(my_distribution, ptr, sizeof(my_distribution) - 1);
304}
305
306
307/*
308 * TODO: add p'o'stpone function here? would be nice but difficult
309 * as the postpone fetcher looks for articles with correct headers
310 */
311static t_bool
313 t_function *result,
314 struct t_group *group)
315{
316 char keyedit[MAXKEYLEN], keymenu[MAXKEYLEN], keyquit[MAXKEYLEN];
318
323
324 *result = func;
325 if (func == POST_EDIT) {
327 return TRUE;
328 } else if (func == GLOBAL_OPTION_MENU) {
330 return TRUE;
331 }
332 return FALSE;
333}
334
335
336/*
337 * make a backup copy of ~/TIN_ARTICLE_NAME, this is necessary since
338 * submit_news_file adds headers, does q-p conversion etc
339 */
340char *
342 const char *the_article)
343{
344 static char name[PATH_LEN];
345
346 snprintf(name, sizeof(name), "%s.bak", the_article);
347 return name;
348}
349
350
351static t_bool
353 const char *the_article)
354{
355 return backup_file(the_article, backup_article_name(the_article));
356}
357
358
359static void
361 void)
362{
363 int i;
364
365 for (i = 0; i < MAX_MSG_HEADERS; i++) {
366 msg_headers[i].name = NULL;
367 msg_headers[i].text = NULL;
368 }
369}
370
371
372static void
374 void)
375{
376 int i;
377
378 for (i = 0; i < MAX_MSG_HEADERS; i++) {
380 FreeAndNull(msg_headers[i].text);
381 }
382}
383
384
385static void
387 const char *name,
388 const char *text)
389{
390 const char *p;
391 char *ptr;
392 char *new_name;
393 char *new_text;
394 int i;
395 t_bool done = FALSE;
396
397 if (name) {
398 /*
399 * Remove : if one is attached to name
400 */
401 new_name = my_strdup(name);
402 ptr = strchr(new_name, ':');
403 if (ptr)
404 *ptr = '\0';
405
406 /*
407 * Check if header already exists and if update text
408 */
409 for (i = 0; i < MAX_MSG_HEADERS && msg_headers[i].name; i++) {
410 if (STRCMPEQ(msg_headers[i].name, new_name)) {
411 FreeAndNull(msg_headers[i].text);
412 if (text) {
413 for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
414 ;
415 new_text = my_strdup(p);
416 if ((ptr = strrchr(new_text, '\n')) != NULL)
417 *ptr = '\0';
418
419 msg_headers[i].text = my_strdup(new_text);
420 free(new_text);
421 }
422 done = TRUE;
423 }
424 }
425
426 /*
427 * if header does not exist then add it
428 */
429 if (i < MAX_MSG_HEADERS && !(done || msg_headers[i].name)) {
430 msg_headers[i].name = my_strdup(new_name);
431 if (text) {
432 for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
433 ;
434 new_text = my_strdup(p);
435 if ((ptr = strrchr(new_text, '\n')) != NULL)
436 *ptr = '\0';
437
438 msg_headers[i].text = my_strdup(new_text);
439 free(new_text);
440 }
441 }
442 FreeIfNeeded(new_name);
443 }
444}
445
446
447static int
449 FILE *fp)
450{
451 int i;
452 int wrote = 1;
453 char *p;
454
455 for (i = 0; i < MAX_MSG_HEADERS; i++) {
456 if (msg_headers[i].name) {
457 fprintf(fp, "%s: %s\n", msg_headers[i].name, BlankIfNull(msg_headers[i].text));
458 wrote++;
459 if ((p = msg_headers[i].text)) {
460 while ((p = strchr(p, '\n'))) {
461 p++;
462 wrote++;
463 }
464 }
465 }
466 }
467 fputc('\n', fp);
468
469 return wrote;
470}
471
472
473/*
474 * Posted messages menu
475 */
476static t_function
478 void)
479{
480 return GLOBAL_QUIT;
481}
482
483
484static t_function
486 void)
487{
488 return POSTED_SELECT;
489}
490
491
492static void
494 void)
495{
496 int i;
497
499 currmenu = &phmenu;
500 mark_offset = 0;
501
502 if (phmenu.curr < 0)
503 phmenu.curr = 0;
504
505 ClearScreen();
508
509 for (i = phmenu.first; i < phmenu.first + NOTESLINES && i < phmenu.max; ++i)
511
513
515}
516
517
518t_bool
520 void)
521{
522 char key[MAXKEYLEN];
524 t_menu *oldmenu = NULL;
525
526 if (post_hist_list) {
528 return FALSE;
529 }
530
531 if (currmenu)
532 oldmenu = currmenu;
533 phmenu.curr = 0;
535 if (phmenu.max == 0)
536 return FALSE;
537
541
542 forever {
544 case GLOBAL_QUIT:
546 if (oldmenu)
547 currmenu = oldmenu;
548 return TRUE;
549
550 case DIGIT_1:
551 case DIGIT_2:
552 case DIGIT_3:
553 case DIGIT_4:
554 case DIGIT_5:
555 case DIGIT_6:
556 case DIGIT_7:
557 case DIGIT_8:
558 case DIGIT_9:
559 if (phmenu.max)
561 break;
562
563#ifndef NO_SHELL_ESCAPE
566 break;
567#endif /* !NO_SHELL_ESCAPE */
568
569 case GLOBAL_HELP:
572 break;
573
575 top_of_list();
576 break;
577
578 case GLOBAL_LAST_PAGE:
579 end_of_list();
580 break;
581
583 my_retouch();
585 break;
586
587 case GLOBAL_LINE_DOWN:
588 move_down();
589 break;
590
591 case GLOBAL_LINE_UP:
592 move_up();
593 break;
594
595 case GLOBAL_PAGE_DOWN:
596 page_down();
597 break;
598
599 case GLOBAL_PAGE_UP:
600 page_up();
601 break;
602
604 scroll_down();
605 break;
606
607 case GLOBAL_SCROLL_UP:
608 scroll_up();
609 break;
610
614 break;
615
619 break;
620
621 case POSTED_SELECT:
622 if (phmenu.max) {
623 switch (process_post_hist(phmenu.curr)) {
624 case LOOKUP_REPLY:
626 break;
627
630 break;
631
632 case LOOKUP_NO_LAST:
634 break;
635
636 case LOOKUP_UNAVAIL:
638 break;
639
640 case LOOKUP_FAILED:
643 break;
644
645 default:
647 break;
648 }
649 }
650 break;
651
657 else if (phmenu.max) {
658 int new_pos, old_pos = phmenu.curr;
659
661 if (new_pos != old_pos)
662 move_to_item(new_pos);
663 }
664 break;
665
666 default:
668 break;
669 }
670 }
671}
672
673
674static void
676 void)
677{
680 t_posted *lptr;
681
682 lptr = find_post_hist(phmenu.curr);
683 if (lptr->mid[0])
684 info_message("%s", lptr->mid);
685 } else if (phmenu.curr == phmenu.max - 1)
687}
688
689
690t_posted *
692 int n)
693{
694 t_posted *lptr;
695
696 lptr = post_hist_list;
697 while (n-- > 0 && lptr->next)
698 lptr = lptr->next;
699
700 return lptr;
701}
702
703
704static void
706 int i)
707{
708 char *sptr;
709 int group_len = cCOLS / 5;
710 t_posted *lptr;
711 char *tmp = NULL;
712#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
713 int len = (int)((size_t) cCOLS * MB_CUR_MAX);
714 wchar_t *wtmp, *wtmp2;
715#else
716 int len = cCOLS;
717#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
718
719#ifdef USE_CURSES
720 /*
721 * Allocate line buffer
722 * make it the same size like in !USE_CURSES case to simplify the code
723 */
724 sptr = my_malloc(len + 2);
725#else
726 sptr = screen[INDEX2SNUM(i)].col;
727#endif /* USE_CURSES */
728
729 lptr = find_post_hist(i);
730
731#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
732 if ((wtmp = char2wchar_t(lptr->group)) != NULL) {
733 if (!strchr(lptr->group, '@') && tinrc.abbreviate_groupname)
734 wtmp2 = abbr_wcsgroupname(wtmp, group_len);
735 else
736 wtmp2 = wcspart(wtmp, group_len, FALSE);
737
738 if (wtmp2) {
739 tmp = wchar_t2char(wtmp2);
740 free(wtmp2);
741 }
742 free(wtmp);
743 }
744#else
745 if (!strchr(lptr->group, '@') && tinrc.abbreviate_groupname)
746 tmp = abbr_groupname(lptr->group, group_len);
747 else
748 tmp = my_strdup(lptr->group);
749#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
750
751#if 1
752 snprintf(sptr, (size_t) len, " %s %8s %c %-*.*s \"%s\"", tin_ltoa(i + 1, 4),
753 lptr->date, lptr->action,
754 group_len, group_len, BlankIfNull(tmp),
755 lptr->subj);
756#else
757 /* also show MID */
758 snprintf(sptr, (size_t) len, " %s %8s %c %-*.*s \"%s\" %s", tin_ltoa(i + 1, 4),
759 lptr->date, lptr->action,
760 group_len, group_len, BlankIfNull(tmp),
761 lptr->subj,
762 lptr->mid);
763#endif /* 1 */
764
765#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
766 if ((wtmp = char2wchar_t(sptr)) != NULL) {
767 wtmp2 = wcspart(wtmp, cCOLS - 1, FALSE);
768 if (wtmp2) {
769 free(wtmp);
770 FreeIfNeeded(tmp);
771 if ((tmp = wchar_t2char(wtmp2)) != NULL) {
772 snprintf(sptr, (size_t) len, "%s", tmp);
773 FreeAndNull(tmp);
774 }
775 free(wtmp2);
776 } else
777 free(wtmp);
778 }
779#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
780
781#ifndef USE_CURSES
783 strcat(strip_line(sptr), cCRLF);
784#endif /* !USE_CURSES */
785
786 WriteLine(INDEX2LNUM(i), sptr);
787 FreeIfNeeded(tmp);
788
789#ifdef USE_CURSES
790 free(sptr);
791#endif /* USE_CURSES */
792}
793
794
795static int
797 int n)
798{
799 t_posted *lptr;
800 int ret;
801
802 lptr = find_post_hist(n);
803
804 if (strchr(lptr->group, '@'))
805 ret = LOOKUP_REPLY;
806#ifdef NNTP_ABLE
807 else if (read_news_via_nntp && !read_saved_news) {
808 ret = show_article_by_msgid(lptr->mid);
809 }
810#endif /* NNTP_ABLE */
811 /*
812 * reading from local spool or saved news
813 * - select level is not covered
814 * - if called from thread- or page-level one will be taken back
815 * to group-level after viewing an article
816 */
817 else {
818 ret = LOOKUP_ART_UNAVAIL;
819 if (curr_group != NULL) { /* ! select level */
820 struct t_msgid *msgid;
821
822 if ((msgid = find_msgid(lptr->mid)) != NULL) {
823 if (msgid->article != ART_UNAVAILABLE) {
824 if (show_page(curr_group, msgid->article, NULL))
825 ret = LOOKUP_OK;
826 }
827 }
828 } else
829 ret = LOOKUP_UNAVAIL;
830 }
831 return ret;
832}
833
834
835static int
837 void)
838{
839 FILE *fp;
840 char *p, *q;
841 char buf[LEN];
842 int count = 0;
843 size_t i = 0, j, k, n;
844 t_posted *posted = NULL;
845
846 if ((fp = fopen(posted_info_file, "r")) == NULL) {
848 return 0;
849 }
850
851 while (fgets(buf, (int) sizeof(buf), fp) != NULL)
852 count++;
853
854 if (!count) {
855 fclose(fp);
857 return 0;
858 }
859 rewind(fp);
860 count = 0;
861
862 while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
863 if (buf[0] == '#' || buf[0] == '\n')
864 continue;
865
866 if (!posted) {
868 posted->next = NULL;
869 } else {
870 posted = my_malloc(sizeof(t_posted));
873 }
874 ++count;
875
876 n = 0;
877 q = my_strdup(buf);
878 if (tin_strtok(q, "|") != NULL) {
879 for (; tin_strtok(NULL, "|") != NULL; n++)
880 ;
881 }
882 free(q);
883
884 for (j = 0, k = 0; buf[j] != '|' && buf[j] != '\n'; j++) {
885 if (k < sizeof(posted->date) - 1)
886 posted->date[k++] = buf[j]; /* posted date */
887 }
888
889 /* current expected actions [dfrwx] */
890 if (n < 3 || buf[++j] == '|') { /* too few args and/or empty action */
892 fclose(fp);
895 return 0;
896 }
897 posted->date[k] = '\0';
898
899 posted->action = buf[j];
900 j += 2;
901
902 /* TODO:
903 * - '|' in local-parts of mail addresses will confuse the code
904 */
905 for (k = 0; buf[j] != '|' && buf[j] != ','; j++) {
906 if (k < sizeof(posted->group) - 1)
907 posted->group[k++] = buf[j];
908 }
909 if (buf[j] == ',') {
910 while (buf[j] != '|' && buf[j] != '\n')
911 j++;
912
913 if (k > sizeof(posted->group) - 5)
914 k = sizeof(posted->group) - 5;
915
916 posted->group[k++] = ',';
917 posted->group[k++] = '.';
918 posted->group[k++] = '.';
919 posted->group[k++] = '.';
920 }
921 posted->group[k] = '\0';
922
923 j++;
924
925 p = buf;
926 while ((q = strstr(p, "|<" )) != NULL)
927 p = ++q;
928
929 if (strlen(p) >= 4 && *p == '<' && strlen(p) < sizeof(posted->mid) - 1) { /* <@> */
930 t_bool invalid = FALSE;
931 t_bool has_at = FALSE;
932
933 for (q = p, k = 0; *q != '\n' && !invalid; q++) {
934 if (*q < 33 || !isascii(*q)) {
935 invalid = TRUE;
936 break;
937 }
938 if (*q == '@')
939 has_at = TRUE;
940
941 posted->mid[k++] = *q;
942 }
943
944 if (*q != '\n' || *(q - 1) != '>')
945 invalid = TRUE;
946
947 if (!invalid && has_at) {
948 posted->mid[k] = '\0';
949 *(p - 1) = '\n'; /* so it does not end up in subj */
950 } else
951 posted->mid[0] = '\0';
952 }
953
954 if (p == buf || p == buf + j) /* subject looks like id and no id logged or no id given, clear id */
955 posted->mid[0] = '\0';
956 my_strncpy(posted->subj, buf + j, sizeof(posted->subj) - 1);
957 i++;
958 }
959 fclose(fp);
960
961 return count;
962}
963
964
965static void
967 void)
968{
969 t_posted *p, *q;
970
971 for (p = post_hist_list; p != NULL; p = q) {
972 q = p->next;
973 free(p);
974 }
975 post_hist_list = NULL;
976}
977
978
979/*
980 * TODO:
981 * - mime-encode subject so we get the right charset (it may be different
982 * in subsequent sessions)
983 */
984static void
986 const char *group,
987 int action,
988 const char *subj,
989 const char *a_message_id)
990{
991 FILE *fp;
992 char *file_tmp;
993 time_t epoch;
994
995 if (no_write)
996 return;
997
999 if (!backup_file(posted_info_file, file_tmp)) {
1001 free(file_tmp);
1002 return;
1003 }
1004
1005 if ((fp = fopen(posted_info_file, "a")) != NULL) {
1006 int err;
1007 char logdate[10];
1008
1009 if (time(&epoch) != (time_t) -1) {
1010 if (!my_strftime(logdate, sizeof(logdate) - 1, "%d-%m-%y", localtime(&epoch)))
1011 strcpy(logdate, "NO DATE");
1012 } else
1013 strcpy(logdate, "NO DATE");
1014
1015 if (*a_message_id) {
1016 char *mid = my_strdup(a_message_id);
1017
1018 fprintf(fp, "%s|%c|%s|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj), BlankIfNull(str_trim(mid)));
1019 free(mid);
1020 } else
1021 fprintf(fp, "%s|%c|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj));
1022
1023 if ((err = ferror(fp)) || fclose(fp)) {
1025 rename_file(file_tmp, posted_info_file);
1026 if (err) {
1027 clearerr(fp);
1028 fclose(fp);
1029 }
1030 } else
1031 unlink(file_tmp);
1032 } else
1033 rename_file(file_tmp, posted_info_file);
1034
1035 free(file_tmp);
1036}
1037
1038
1039/*
1040 * appends the content of the_article to the_mailbox, with a From_ line of
1041 * addr, does mboxo/mboxrd From_ line quoting if needed (!MMDF-style mbox)
1042 */
1043static int
1045 const char *the_article,
1046 const char *addr,
1047 const char *the_mailbox)
1048{
1049 FILE *fp_in, *fp_out;
1050 char *bufp;
1051 char buf[LEN];
1052 time_t epoch;
1053 t_bool mmdf = FALSE;
1054 int rval;
1055#ifndef NO_LOCKING
1056 int fd;
1057 unsigned int retrys = 11; /* maximum lock retrys + 1 */
1058#endif /* !NO_LOCKING */
1059
1061 mmdf = TRUE;
1062
1063 if ((fp_in = fopen(the_article, "r")) == NULL)
1064 return errno;
1065
1066 if ((fp_out = fopen(the_mailbox, "a+")) != NULL) {
1067#ifndef NO_LOCKING
1068 fd = fileno(fp_out);
1069
1070 while ((rval = fd_lock(fd, FALSE)) && --retrys)
1071 wait_message(1, _(txt_trying_lock), retrys, the_mailbox);
1072
1073 if (!retrys) {
1074 wait_message(5, _(txt_error_couldnt_lock), the_mailbox);
1075 fclose(fp_out);
1076 fclose(fp_in);
1077 return rval;
1078 }
1079 retrys++;
1080
1081 while (--retrys && !dot_lock(the_mailbox))
1082 wait_message(1, _(txt_trying_dotlock), retrys, the_mailbox);
1083
1084 if (!retrys) {
1085 wait_message(5, _(txt_error_couldnt_dotlock), the_mailbox);
1086 fd_unlock(fd);
1087 fclose(fp_out);
1088 fclose(fp_in);
1089 return ENOENT; /* FIXME! dot_lock() doesn't return more info yet */
1090 }
1091#else
1092 rval = 0;
1093#endif /* !NO_LOCKING */
1094
1095 if (mmdf)
1096 fprintf(fp_out, "%s", MMDFHDRTXT);
1097 else {
1098 (void) time(&epoch);
1099 fprintf(fp_out, "From %s %s", addr, ctime(&epoch));
1100 }
1101 while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
1102 if (!mmdf) { /* moboxo/mboxrd style From_ quoting required */
1103 /*
1104 * TODO: add Content-Length: header when using MBOXO
1105 * so tin actually write MBOXCL instead of MBOXO?
1106 */
1107 if (tinrc.mailbox_format == 1) { /* MBOXRD */
1108 /* mboxrd: quote quoted and plain From_ lines in the body */
1109 bufp = buf;
1110 while (*bufp == '>')
1111 bufp++;
1112 if (strncmp(bufp, "From ", 5) == 0)
1113 fputc('>', fp_out);
1114 } else { /* MBOXO (MBOXCL) */
1115 if (strncmp(buf, "From ", 5) == 0)
1116 fputc('>', fp_out);
1117 }
1118 }
1119 fputs(buf, fp_out);
1120 }
1121 print_art_separator_line(fp_out, mmdf);
1122
1123 fflush(fp_out);
1124#ifndef NO_LOCKING
1125 if ((rval = fd_unlock(fd)) || !dot_unlock(the_mailbox))
1126 wait_message(4, _(txt_error_cant_unlock), the_mailbox);
1127#endif /* !NO_LOCKING */
1128
1129 fclose(fp_out);
1130 } else
1131 rval = errno;
1132
1133 fclose(fp_in);
1134 return rval;
1135}
1136
1137
1138/*
1139 * TODO:
1140 * - cleanup!!
1141 * - check for illegal (8bit) chars in References, X-Face, MIME-Version,
1142 * Content-Type, Content-Transfer-Encoding, Content-Disposition, Supersedes
1143 * - check for 'illegal' headers: Xref, Injection-Info, (NNTP-Posting-Host,
1144 * NNTP-Posting-Date, X-Trace, X-Complaints-To), Date-Received,
1145 * Posting-Version, Relay-Version, Also-Control, Article-Names,
1146 * Article-Updates, See-Also
1147 * - check for special newsgroups: to, ctl, all, control, junk
1148 * [RFC 5536 3.1.4]
1149 * - check for Supersedes in Control messages [RFC 5536 3.2.3]
1150 * - check for 'illegal' distribution: all [RFC 5536 3.2.4]
1151 *
1152 * Check the article file for correct header syntax and if there
1153 * is a blank between the header information and the text.
1154 *
1155 * Additionally make **group point to one of the groups we are actually posting to.
1156 *
1157 * 1. Subject header present
1158 * 2. Newsgroups header present
1159 * From header present
1160 * 3. Space after every colon in header
1161 * 4. Colon in every header line
1162 * 5. Newsgroups line has no spaces, only comma separated
1163 * 6. List of newsgroups is presented to user with description
1164 * 7. Lines in body that are to long causes a warning to be printed
1165 * 8. Group(s) must be listed in the active file
1166 * 9. No Sender: header allowed (limit forging) and rejection by
1167 * inn servers
1168 * 10. Check for charset != US-ASCII when using non-7bit-encoding
1169 * 11. Warn if transfer encoding is base64 or quoted-printable and using
1170 * external inews
1171 * 12. Check that Subject, Newsgroups and if present Followup-To
1172 * headers are unique
1173 * 13. Display an 'are you sure' message before posting article
1174 */
1175#define CA_ERROR_HEADER_LINE_BLANK 0x0000001
1176#define CA_ERROR_MISSING_BODY_SEPARATOR 0x0000002
1177#define CA_ERROR_MISSING_FROM 0x0000004
1178#define CA_ERROR_DUPLICATED_FROM 0x0000008
1179#define CA_ERROR_MISSING_SUBJECT 0x0000010
1180#define CA_ERROR_DUPLICATED_SUBJECT 0x0000020
1181#define CA_ERROR_EMPTY_SUBJECT 0x0000040
1182#define CA_ERROR_MISSING_NEWSGROUPS 0x0000080
1183#define CA_ERROR_DUPLICATED_NEWSGROUPS 0x0000100
1184#define CA_ERROR_EMPTY_NEWSGROUPS 0x0000200
1185#define CA_ERROR_DUPLICATED_FOLLOWUP_TO 0x0000400
1186#define CA_ERROR_BAD_CHARSET 0x0000800
1187#define CA_ERROR_BAD_ENCODING 0x0001000
1188#define CA_ERROR_BAD_MESSAGE_ID 0x0002000
1189#define CA_ERROR_BAD_DATE 0x0004000
1190#define CA_ERROR_BAD_EXPIRES 0x0008000
1191#define CA_ERROR_NEWSGROUPS_NOT_7BIT 0x0010000
1192#define CA_ERROR_FOLLOWUP_TO_NOT_7BIT 0x0020000
1193#define CA_ERROR_DISTRIBUTIOIN_NOT_7BIT 0x0040000
1194#define CA_ERROR_NEWSGROUPS_POSTER 0x0080000
1195#define CA_ERROR_FOLLOWUP_TO_POSTER 0x0100000
1196#ifndef ALLOW_FWS_IN_NEWSGROUPLIST
1197# define CA_ERROR_SPACE_IN_NEWSGROUPS 0x0200000
1198# define CA_ERROR_NEWLINE_IN_NEWSGROUPS 0x0400000
1199# define CA_ERROR_SPACE_IN_FOLLOWUP_TO 0x0800000
1200# define CA_ERROR_NEWLINE_IN_FOLLOWUP_TO 0x1000000
1201#endif /* !ALLOW_FWS_IN_NEWSGROUPLIST */
1202#define CA_WARNING_SPACES_ONLY_SUBJECT 0x000001
1203#define CA_WARNING_RE_WITHOUT_REFERENCES 0x000002
1204#define CA_WARNING_REFERENCES_WITHOUT_RE 0x000004
1205#define CA_WARNING_MULTIPLE_SIGDASHES 0x000008
1206#define CA_WARNING_WRONG_SIGDASHES 0x000010
1207#define CA_WARNING_LONG_SIGNATURE 0x000020
1208#define CA_WARNING_ENCODING_EXTERNAL_INEWS 0x000040
1209#define CA_WARNING_NEWSGROUPS_EXAMPLE 0x000080
1210#define CA_WARNING_FOLLOWUP_TO_EXAMPLE 0x000100
1211#ifdef CHARSET_CONVERSION
1212# define CA_WARNING_CHARSET_CONVERSION 0x000200
1213#endif /* CHARSET_CONVERSION */
1214#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1215# define CA_WARNING_SPACE_IN_NEWSGROUPS 0x000400
1216# define CA_WARNING_NEWLINE_IN_NEWSGROUPS 0x000800
1217# define CA_WARNING_SPACE_IN_FOLLOWUP_TO 0x001000
1218# define CA_WARNING_NEWLINE_IN_FOLLOWUP_TO 0x002000
1219#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1220
1221/*
1222 * TODO: cleanup!
1223 *
1224 * return values:
1225 * 0 article ok
1226 * 1 article contains errors
1227 * 2 article caused warnings
1228 */
1229static int
1231 const char *the_article,
1232 int art_type,
1233 struct t_group **group,
1234 t_bool art_unchanged,
1235 t_bool use_cache)
1236{
1237 FILE *fp;
1238 char **newsgroups = NULL;
1239 char **followupto = NULL;
1240 char *line, *cp, *cp2;
1241 char *to = NULL;
1242 char references[HEADER_LEN];
1243 char subject[HEADER_LEN];
1244 int cnt = 0;
1245 int col, i;
1246 int errors = 0;
1247 int warnings = 0;
1248 int init = 1;
1249 int ngcnt = 0, ftngcnt = 0;
1250 int oldraw; /* save previous raw state */
1251 int saw_sig_dashes = 0;
1252 int sig_lines = 0;
1253 int found_followup_to_lines = 0;
1254 int found_from_lines = 0;
1255 int found_newsgroups_lines = 0;
1256 int found_subject_lines = 0;
1257 int errors_catbp = 0; /* sum of error-codes */
1258 int warnings_catbp = 0; /* sum of warning-codes */
1259 int must_break_line = 0;
1260 struct t_group *psGrp;
1261 t_bool end_of_header = FALSE;
1262 t_bool got_long_line = FALSE;
1263 t_bool saw_references = FALSE;
1264 t_bool saw_wrong_sig_dashes = FALSE;
1265 t_bool mime_7bit = TRUE;
1266 t_bool mime_usascii = FALSE;
1267 t_bool contains_8bit = FALSE;
1268#ifdef CHARSET_CONVERSION
1269 t_bool charset_conversion_fails = FALSE;
1270 int mmnwcharset;
1271#endif /* CHARSET_CONVERSION */
1272 static const char *c_article;
1273 static int c_art_type;
1274 static struct t_group **c_group;
1275 static t_bool c_art_unchanged;
1276
1277 /*
1278 * Cache values for the case when called
1279 * from refresh_post_screen()
1280 */
1281 if (!use_cache) {
1282 c_article = the_article;
1283 c_art_type = art_type;
1284 c_group = group;
1285 c_art_unchanged = art_unchanged;
1286 }
1287
1288#ifdef CHARSET_CONVERSION
1289 mmnwcharset = *c_group ? (*c_group)->attribute->mm_network_charset : tinrc.mm_network_charset;
1290#endif /* CHARSET_CONVERSION */
1291
1292 if ((fp = fopen(c_article, "r")) == NULL) {
1293 perror_message(_(txt_cannot_open), c_article);
1294 return 0;
1295 }
1296 oldraw = RawState(); /* save state */
1297 subject[0] = '\0';
1298
1299 /* check the header of the article */
1301
1302 while ((line = tin_fgets(fp, TRUE)) != NULL) {
1303 cnt++;
1304 if (!end_of_header && !strlen(line)) { /* end of header reached */
1305 if (cnt == 1)
1306 errors_catbp |= CA_ERROR_HEADER_LINE_BLANK;
1307 end_of_header = TRUE;
1308 break;
1309 }
1310
1311 for (cp = line; *cp && !contains_8bit; cp++) {
1312 if (!isascii(*cp)) {
1313 contains_8bit = TRUE;
1314 break;
1315 }
1316 }
1317
1318#ifdef CHARSET_CONVERSION
1319 /* are all characters in article contained in network_charset? */
1320 if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
1321 cp = my_malloc(strlen(line) * 4 + 1);
1322 strcpy(cp, line);
1323 charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
1324 free(cp);
1325 }
1326#endif /* CHARSET_CONVERSION */
1327
1328 if ((cp = strchr(line, ':')) == NULL) {
1329 StartInverse();
1330 my_fprintf(stderr, _(txt_error_header_line_colon), cnt, line);
1331 EndInverse();
1332 my_fflush(stderr);
1333 errors++;
1334 continue;
1335 }
1336 if (cp[1] != ' ') {
1337 StartInverse();
1338 my_fprintf(stderr, _(txt_error_header_line_space), cnt, line);
1339 EndInverse();
1340 my_fflush(stderr);
1341 errors++;
1342 }
1343
1344 if (cp - line == 7 && !strncasecmp(line, "Subject", 7)) {
1345 found_subject_lines++;
1346 strncpy(subject, cp + 2, (size_t) (cCOLS - 6));
1347 subject[cCOLS - 6] = '\0';
1348 }
1349
1350/*
1351 * only allow hand supplied Sender in FORGERY case or
1352 * with external inews and not HAVE_FASCIST_NEWSADMIN
1353 */
1354#ifndef FORGERY
1355# ifdef HAVE_FASCIST_NEWSADMIN
1356 if (cp - line == 6 && !strncasecmp(line, "Sender", 6))
1357# else
1358 if (!strcasecmp(tinrc.inews_prog, INTERNAL_CMD) && cp - line == 6 && !strncasecmp(line, "Sender", 6))
1359# endif /* HAVE_FASCIST_NEWSADMIN */
1360 {
1361 StartInverse();
1363 EndInverse();
1364 my_fflush(stderr);
1365 errors++;
1366 }
1367#endif /* !FORGERY */
1368
1369 if (cp - line == 8 && !strncasecmp(line, "Approved", 8)) {
1370 if (tinrc.beginner_level) {
1371 /* StartInverse(); */
1372 my_fprintf(stderr, "%s", _(txt_error_approved)); /* this is only a Warning: */
1373 /* EndInverse(); */
1374 my_fflush(stderr);
1375#ifdef HAVE_FASCIST_NEWSADMIN
1376 errors++;
1377#else
1378 warnings++;
1379#endif /* HAVE_FASCIST_NEWSADMIN */
1380 }
1381#ifdef CHARSET_CONVERSION
1382 cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1383#else
1384 cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1385#endif /* CHARSET_CONVERSION */
1386 i = gnksa_check_from(cp2 + (cp - line) + 1);
1387 if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1388 StartInverse();
1389 my_fprintf(stderr, "%s", _(txt_error_bad_approved));
1390 my_fprintf(stderr, "%s\n", cp2);
1391 my_fprintf(stderr, gnksa_strerror(i), i);
1392 EndInverse();
1393 my_fflush(stderr);
1394#ifndef FORGERY
1395 errors++;
1396#endif /* !FORGERY */
1397 }
1398 free(cp2);
1399 }
1400
1401 if (cp - line == 4 && !strncasecmp(line, "From", 4)) {
1402 found_from_lines++;
1403#ifdef CHARSET_CONVERSION
1404 cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1405#else
1406 cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1407#endif /* CHARSET_CONVERSION */
1408 i = gnksa_check_from(cp2 + (cp - line) + 1);
1409 if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1410 StartInverse();
1411 my_fprintf(stderr, "%s", _(txt_error_bad_from));
1412 my_fprintf(stderr, "%s\n", cp2);
1413 my_fprintf(stderr, gnksa_strerror(i), i);
1414 EndInverse();
1415 my_fflush(stderr);
1416#ifndef FORGERY
1417 errors++;
1418#endif /* !FORGERY */
1419 }
1420 free(cp2);
1421 }
1422
1423 if (cp - line == 8 && !strncasecmp(line, "Reply-To", 8)) {
1424#ifdef CHARSET_CONVERSION
1425 cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1426#else
1427 cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1428#endif /* CHARSET_CONVERSION */
1429 i = gnksa_check_from(cp2 + (cp - line) + 1);
1430 if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1431 StartInverse();
1432 my_fprintf(stderr, "%s", _(txt_error_bad_replyto));
1433 my_fprintf(stderr, "%s\n", cp2);
1434 my_fprintf(stderr, gnksa_strerror(i), i);
1435 EndInverse();
1436 my_fflush(stderr);
1437#ifndef FORGERY
1438 errors++;
1439#endif /* !FORGERY */
1440 }
1441 free(cp2);
1442 }
1443
1444 if (cp - line == 2 && !strncasecmp(line, "To", 2)) {
1445#ifdef CHARSET_CONVERSION
1446 cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1447#else
1448 cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1449#endif /* CHARSET_CONVERSION */
1450 i = gnksa_check_from(cp2 + (cp - line) + 1);
1451 if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1452 StartInverse();
1453 my_fprintf(stderr, "%s", _(txt_error_bad_to));
1454 my_fprintf(stderr, "%s\n", cp2);
1455 my_fprintf(stderr, gnksa_strerror(i), i);
1456 EndInverse();
1457 my_fflush(stderr);
1458#ifndef FORGERY
1459 errors++;
1460#endif /* !FORGERY */
1461 }
1462 to = my_strdup(cp2 + (cp - line) + 1);
1463 free(cp2);
1464 }
1465
1466 if (cp - line == 10 && !strncasecmp(line, "Message-ID", 10)) {
1467#if 0 /* see comment about "<>" in misc.c:gnksa_split_from() */
1468 char addr[HEADER_LEN], name[HEADER_LEN];
1469 int type;
1470
1471 i = gnksa_check_from(++cp);
1472 gnksa_split_from(cp, addr, name, &type);
1473 if (((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i)) || !*addr)
1474#else
1475 i = gnksa_check_from(++cp);
1476 if ((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i))
1477#endif /* 0 */
1478 {
1479 StartInverse();
1480 my_fprintf(stderr, "%s", _(txt_error_bad_msgidfqdn));
1481 my_fprintf(stderr, "%s\n", line);
1482 my_fprintf(stderr, gnksa_strerror(i), i);
1483 EndInverse();
1484 my_fflush(stderr);
1485#ifndef FORGERY
1486 errors++;
1487#endif /* !FORGERY */
1488 }
1489 if (damaged_id(cp))
1490 errors_catbp |= CA_ERROR_BAD_MESSAGE_ID;
1491 }
1492
1493 if (cp - line == 10 && !strncasecmp(line, "References", 10)) {
1494 for (cp = line + 11; *cp == ' '; cp++)
1495 ;
1496 STRCPY(references, cp);
1497 if (strlen(references))
1498 saw_references = TRUE;
1499 }
1500
1501 if (cp - line == 4 && !strncasecmp(line, "Date", 4)) {
1502 if ((cp2 = parse_header(line, "Date", FALSE, FALSE, FALSE))) {
1503 if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
1504 errors_catbp |= CA_ERROR_BAD_DATE;
1505 } else
1506 errors_catbp |= CA_ERROR_BAD_DATE;
1507 }
1508
1509 if (cp - line == 7 && !strncasecmp(line, "Expires", 7)) {
1510 if ((cp2 = parse_header(line, "Expires", FALSE, FALSE, FALSE))) {
1511 if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
1512 errors_catbp |= CA_ERROR_BAD_EXPIRES;
1513 } else
1514 errors_catbp |= CA_ERROR_BAD_EXPIRES;
1515 }
1516
1517 /*
1518 * TODO: also check for other illegal chars?
1519 * a 'common' error is to use a semicolon instead of a comma.
1520 */
1521 if (cp - line == 10 && !strncasecmp(line, "Newsgroups", 10)) {
1522 found_newsgroups_lines++;
1523 for (cp = line + 11; *cp == ' '; cp++)
1524 ;
1525 if (strchr(cp, ' ') || strchr(cp, '\t')) {
1526#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1527 warnings_catbp |= CA_WARNING_SPACE_IN_NEWSGROUPS;
1528#else
1529 errors_catbp |= CA_ERROR_SPACE_IN_NEWSGROUPS;
1530#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1531 }
1532 if (strchr(cp, '\n')) {
1533#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1534 warnings_catbp |= CA_WARNING_NEWLINE_IN_NEWSGROUPS;
1535#else
1536 errors_catbp |= CA_ERROR_NEWLINE_IN_NEWSGROUPS;
1537#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1538 unfold_header(line);
1539 }
1540
1541 newsgroups = build_nglist(cp, &ngcnt);
1542 if (newsgroups && ngcnt)
1543 (void) stripped_double_ngs(newsgroups, &ngcnt);
1544
1545 if (!ngcnt)
1546 errors_catbp |= CA_ERROR_EMPTY_NEWSGROUPS;
1547 else {
1548 for (cp = line + 11; *cp; cp++) {
1549 if (!isascii(*cp)) {
1550 errors_catbp |= CA_ERROR_NEWSGROUPS_NOT_7BIT;
1551 break;
1552 }
1553 }
1554 }
1555 { /* check for poster, example, example.* */
1556 char *groups;
1557
1558 for (cp = line + 11; *cp == ' '; cp++)
1559 ;
1560 cp2 = groups = my_strdup(cp);
1561
1562 cp = strtok(groups, ",");
1563 do {
1564 if (!strcmp(cp, "poster"))
1565 errors_catbp |= CA_ERROR_NEWSGROUPS_POSTER;
1566 if (!strcmp(cp, "example"))
1567 warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
1568 if (!strncmp(cp, "example.", 8))
1569 warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
1570 /* TODO: also check for to, ctl, all, control, junk */
1571 } while ((cp = strtok(NULL, ",")) != NULL);
1572 free(cp2);
1573 }
1574 }
1575
1576 if (cp - line == 12 && !strncasecmp(line, "Distribution", 12)) {
1577 for (cp = line + 13; *cp; cp++) {
1578 if (!isascii(*cp)) {
1579 errors_catbp |= CA_ERROR_DISTRIBUTIOIN_NOT_7BIT;
1580 break;
1581 }
1582 }
1583 }
1584
1585 if (cp - line == 11 && !strncasecmp(line, "Followup-To", 11)) {
1586 for (cp = line + 12; *cp == ' '; cp++)
1587 ;
1588 if (strlen(cp)) /* Followup-To not empty */
1589 found_followup_to_lines++;
1590 if (strchr(cp, ' ') || strchr(cp, '\t')) {
1591#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1592 warnings_catbp |= CA_WARNING_SPACE_IN_FOLLOWUP_TO;
1593#else
1594 errors_catbp |= CA_ERROR_SPACE_IN_FOLLOWUP_TO;
1595#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1596 }
1597 if (strchr(cp, '\n')) {
1598#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1599 warnings_catbp |= CA_WARNING_NEWLINE_IN_FOLLOWUP_TO;
1600#else
1601 errors_catbp |= CA_ERROR_NEWLINE_IN_FOLLOWUP_TO;
1602#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1603 unfold_header(line);
1604 }
1605
1606 followupto = build_nglist(cp, &ftngcnt);
1607 if (followupto && ftngcnt) {
1608 char *groups;
1609
1610 (void) stripped_double_ngs(followupto, &ftngcnt);
1611 for (cp = line + 12; *cp; cp++) {
1612 if (!isascii(*cp)) {
1613 errors_catbp |= CA_ERROR_FOLLOWUP_TO_NOT_7BIT;
1614 break;
1615 }
1616 }
1617
1618 for (cp = line + 12; *cp == ' '; cp++)
1619 ;
1620 cp2 = groups = my_strdup(cp);
1621
1622 cp = strtok(groups, ",");
1623 do {
1624 if (!strcmp(cp, "poster") && ftngcnt > 1)
1625 errors_catbp |= CA_ERROR_FOLLOWUP_TO_POSTER;
1626 if (!strcmp(cp, "example"))
1627 warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
1628 if (!strncmp(cp, "example.", 8))
1629 warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
1630 /* TODO: also check for to, ctl, all, control, junk */
1631 } while ((cp = strtok(NULL, ",")) != NULL);
1632 free(cp2);
1633 }
1634 }
1635 }
1636
1637 if (subject[0] == '\0')
1638 errors_catbp |= CA_ERROR_EMPTY_SUBJECT;
1639 else {
1640 cp2 = my_strdup(subject);
1641 if (!strtok(cp2, " \t")) { /* only blanks in Subject? */
1642 warnings_catbp |= CA_WARNING_SPACES_ONLY_SUBJECT;
1643 free(cp2);
1644 } else {
1645 free(cp2);
1646 /* Warn if Subject: begins with "Re: " but there are no References: */
1647 if (!strncmp(subject, "Re: ", 4) && !saw_references)
1648 warnings_catbp |= CA_WARNING_RE_WITHOUT_REFERENCES;
1649 /*
1650 * Warn if there are References: but no "Re: " at the beginning of
1651 * and no "(was:" in the Subject.
1652 */
1653 if (saw_references && strncmp(subject, "Re: ", 4)) {
1654 t_bool was_found = FALSE;
1655
1656 cp2 = subject;
1657 while (!was_found && (cp2 = strchr(cp2, '(')))
1658 was_found = (strncmp(++cp2, "was:", 4) == 0);
1659
1660 if (!was_found)
1661 warnings_catbp |= CA_WARNING_REFERENCES_WITHOUT_RE;
1662 }
1663 }
1664 }
1665
1666 if (!found_from_lines)
1667 errors_catbp |= CA_ERROR_MISSING_FROM;
1668 else {
1669 if (found_from_lines > 1)
1670 errors_catbp |= CA_ERROR_DUPLICATED_FROM;
1671 }
1672
1673 if (!found_newsgroups_lines && c_art_type == GROUP_TYPE_NEWS)
1674 errors_catbp |= CA_ERROR_MISSING_NEWSGROUPS;
1675
1676 if (found_newsgroups_lines > 1)
1677 errors_catbp |= CA_ERROR_DUPLICATED_NEWSGROUPS;
1678
1679 if (!found_subject_lines)
1680 errors_catbp |= CA_ERROR_MISSING_SUBJECT;
1681 else {
1682 if (found_subject_lines > 1)
1683 errors_catbp |= CA_ERROR_DUPLICATED_SUBJECT;
1684 }
1685
1686 if (found_followup_to_lines > 1)
1687 errors_catbp |= CA_ERROR_DUPLICATED_FOLLOWUP_TO;
1688
1689 /*
1690 * Check the body of the article for long lines
1691 * check if article contains non-7bit-ASCII characters
1692 * check if sig is shorter then MAX_SIG_LINES lines
1693 */
1694 while ((line = tin_fgets(fp, FALSE))) {
1695 cnt++;
1696
1697 if (saw_sig_dashes || saw_wrong_sig_dashes)
1698 sig_lines++;
1699
1700 /* SIGDASHES excluding the terminating \n as tin_fgets strips it */
1701 if (strlen(line) == 3 && !strncmp(line, SIGDASHES, 3)) {
1702 saw_wrong_sig_dashes = FALSE;
1703 saw_sig_dashes++;
1704 sig_lines = 0;
1705 }
1706
1707 /* SIGDASHES excluding the tailing SPACE (and '\n', see comment above) */
1708 if (strlen(line) == 2 && !strncmp(line, SIGDASHES, 2) && !saw_sig_dashes) {
1709 saw_wrong_sig_dashes = TRUE;
1710 sig_lines = 0;
1711 }
1712
1713#ifdef CHARSET_CONVERSION
1714 /* are all characters in article contained in network_charset? */
1715 if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
1716 cp = my_malloc(strlen(line) * 4 + 1);
1717 strcpy(cp, line);
1718 charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
1719 free(cp);
1720 }
1721#endif /* CHARSET_CONVERSION */
1722
1723 {
1724#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1725 int num_bytes, wc_width;
1726 wchar_t wc;
1727#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1728
1729 col = 0;
1730 for (cp = line; *cp; ) {
1731 if (*cp == '\t') {
1732 col += 8 - (col % 8);
1733 cp++;
1734 } else {
1735#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1736 if ((num_bytes = mbtowc(&wc, cp, MB_CUR_MAX)) != -1) {
1737 cp += num_bytes;
1738 if (!contains_8bit && num_bytes > 1)
1739 contains_8bit = TRUE;
1740 if (iswprint((wint_t) wc) && ((wc_width = wcwidth(wc)) != -1))
1741 col += wc_width;
1742 else
1743 col++;
1744 } else {
1745 cp++;
1746 col++;
1747 }
1748#else
1749 if (!contains_8bit && !isascii(*cp))
1750 contains_8bit = TRUE;
1751 cp++;
1752 col++;
1753#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1754 }
1755 }
1756 }
1757 if (col > MAX_COL && !got_long_line) {
1758 my_fprintf(stderr, _(txt_warn_art_line_too_long), MAX_COL, cnt, line);
1759 my_fflush(stderr);
1760 got_long_line = TRUE;
1761
1762 warnings++;
1763 }
1764 if (strlen(line) > IMF_LINE_LEN && !must_break_line)
1765 must_break_line = cnt;
1766 }
1767
1768/*
1769 * TODO: cleanup, test me, move to the right location, strings -> lang.c, ...
1770 */
1771 if (must_break_line && ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_BASE64)) {
1772#ifdef MIME_BREAK_LONG_LINES
1773 if (contains_8bit) {
1774 if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_QP)
1775 my_fprintf(stderr, _("Line %d is longer than %d octets and should be folded, but\nencoding is neither set to %s nor to %s\n"), must_break_line, IMF_LINE_LEN, txt_quoted_printable, txt_base64);
1776 } else
1777#endif /* MIME_BREAK_LONG_LINES */
1778 {
1779 if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) == MIME_ENCODING_QP)
1780 my_fprintf(stderr, _("Line %d is longer than %d octets and should be folded, but\nencoding is set to %s without enabling MIME_BREAK_LONG_LINES or\nposting doesn't contain any 8bit chars and thus folding won't happen\n"), must_break_line, IMF_LINE_LEN, txt_quoted_printable);
1781 else
1782 my_fprintf(stderr, _("Line %d is longer than %d octets and should be folded, but\nencoding is not set to %s\n"), must_break_line, IMF_LINE_LEN, txt_base64);
1783 }
1784 my_fflush(stderr);
1785 warnings++;
1786 }
1787
1788 if (saw_sig_dashes > 1)
1789 warnings_catbp |= CA_WARNING_MULTIPLE_SIGDASHES;
1790
1791 if (saw_wrong_sig_dashes)
1792 warnings_catbp |= CA_WARNING_WRONG_SIGDASHES;
1793
1794 if (sig_lines > MAX_SIG_LINES) {
1795 warnings_catbp |= CA_WARNING_LONG_SIGNATURE;
1796#ifdef HAVE_FASCIST_NEWSADMIN
1797 errors++;
1798#endif /* HAVE_FASCIST_NEWSADMIN */
1799 }
1800
1801#ifdef CHARSET_CONVERSION
1802 if (charset_conversion_fails)
1803 warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
1804#endif /* CHARSET_CONVERSION */
1805
1806 if (!end_of_header)
1807 errors_catbp |= CA_ERROR_MISSING_BODY_SEPARATOR;
1808
1809 /*
1810 * check for MIME Content-Type and Content-Transfer-Encoding
1811 *
1812 * If the user has modified the Newsgroups-header **group might not
1813 * point to the correct newsgroup any more.
1814 * Take first group in Newsgroups-header to pass it along to
1815 * submit_news_file et.al. to use it for group-attributes, or if there is
1816 * no Newsgroups:-header (mailing_list) stay with given group.
1817 *
1818 * Is this correct for crosspostings?
1819 */
1820 if (ngcnt)
1821 *c_group = group_find(newsgroups[0], FALSE);
1822
1823 /*
1824 * check for known 7bit charsets
1825 */
1826 for (i = 0; txt_mime_7bit_charsets[i] != NULL; i++) {
1827#ifdef CHARSET_CONVERSION
1828 if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i]))
1829#else
1831#endif /* CHARSET_CONVERSION */
1832 {
1833 mime_usascii = TRUE;
1834 break;
1835 }
1836 }
1837 if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_7BIT)
1838 mime_7bit = FALSE;
1839 if (contains_8bit && mime_usascii)
1840#ifndef CHARSET_CONVERSION
1841 errors_catbp |= CA_ERROR_BAD_CHARSET;
1842#else /* we catch this case later on again */
1843 warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
1844#endif /* !CHARSET_CONVERSION */
1845
1846 if (contains_8bit && mime_7bit)
1847 errors_catbp |= CA_ERROR_BAD_ENCODING;
1848
1849 /*
1850 * Warn when poster is using a non-plain encoding such as quoted-printable
1851 * or base64 and external inews because if that external inews appends a
1852 * signature it will not be encoded. We might additionally check if there's
1853 * a file named ~/.signature and skip the warning if it is not present.
1854 */
1856 warnings_catbp |= CA_WARNING_ENCODING_EXTERNAL_INEWS;
1857
1858 /* give most error messages */
1859 if (errors_catbp) {
1860 StartInverse();
1861
1862 /* missing headers */
1863 if (errors_catbp & CA_ERROR_HEADER_LINE_BLANK)
1865 if (errors_catbp & CA_ERROR_MISSING_BODY_SEPARATOR)
1867 if (errors_catbp & CA_ERROR_MISSING_FROM)
1868 my_fprintf(stderr, _(txt_error_header_line_missing), "From");
1869 if (errors_catbp & CA_ERROR_MISSING_SUBJECT)
1870 my_fprintf(stderr, _(txt_error_header_line_missing), "Subject");
1871 if (errors_catbp & CA_ERROR_MISSING_NEWSGROUPS)
1872 my_fprintf(stderr, _(txt_error_header_line_missing), "Newsgroups");
1873
1874 /* duplicated headers */
1875 if (errors_catbp & CA_ERROR_DUPLICATED_FROM)
1876 my_fprintf(stderr, _(txt_error_header_duplicate), found_from_lines, "From");
1877 if (errors_catbp & CA_ERROR_DUPLICATED_SUBJECT)
1878 my_fprintf(stderr, _(txt_error_header_duplicate), found_subject_lines, "Subject");
1879 if (errors_catbp & CA_ERROR_DUPLICATED_NEWSGROUPS)
1880 my_fprintf(stderr, _(txt_error_header_duplicate), found_newsgroups_lines, "Newsgroups");
1881 if (errors_catbp & CA_ERROR_DUPLICATED_FOLLOWUP_TO)
1882 my_fprintf(stderr, _(txt_error_header_duplicate), found_followup_to_lines, "Followup-To");
1883
1884 /* empty headers */
1885 if (errors_catbp & CA_ERROR_EMPTY_SUBJECT)
1886 my_fprintf(stderr, _(txt_error_header_line_empty), "Subject");
1887 if (errors_catbp & CA_ERROR_EMPTY_NEWSGROUPS)
1888 my_fprintf(stderr, _(txt_error_header_line_empty), "Newsgroups");
1889
1890#ifndef ALLOW_FWS_IN_NEWSGROUPLIST
1891 /* illegal space in headers */
1892 if (errors_catbp & CA_ERROR_SPACE_IN_NEWSGROUPS)
1893 my_fprintf(stderr, _(txt_error_header_line_comma), "Newsgroups");
1894 if (errors_catbp & CA_ERROR_SPACE_IN_FOLLOWUP_TO)
1895 my_fprintf(stderr, _(txt_error_header_line_comma), "Followup-To");
1896
1897 /* illegal newline in headers */
1898 if (errors_catbp & CA_ERROR_NEWLINE_IN_NEWSGROUPS)
1899 my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Newsgroups");
1900 if (errors_catbp & CA_ERROR_NEWLINE_IN_FOLLOWUP_TO)
1901 my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Followup-To");
1902#endif /* !ALLOW_FWS_IN_NEWSGROUPLIST */
1903
1904 /* illegal group names / combinations */
1905 if (errors_catbp & CA_ERROR_NEWSGROUPS_POSTER)
1907 if (errors_catbp & CA_ERROR_FOLLOWUP_TO_POSTER)
1908 my_fprintf(stderr, "%s", _(txt_error_followup_poster));
1909
1910 /* encoding/charset trouble */
1911 if (errors_catbp & CA_ERROR_BAD_CHARSET)
1913 if (errors_catbp & CA_ERROR_BAD_ENCODING)
1915
1916 if (errors_catbp & CA_ERROR_DISTRIBUTIOIN_NOT_7BIT)
1917 my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Distribution");
1918 if (errors_catbp & CA_ERROR_NEWSGROUPS_NOT_7BIT)
1919 my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Newsgroups");
1920 if (errors_catbp & CA_ERROR_FOLLOWUP_TO_NOT_7BIT)
1921 my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Followup-To");
1922
1923 if (errors_catbp & CA_ERROR_BAD_MESSAGE_ID)
1924 my_fprintf(stderr, _(txt_error_header_format), "Message-ID");
1925 if (errors_catbp & CA_ERROR_BAD_DATE)
1926 my_fprintf(stderr, _(txt_error_header_format), "Date");
1927 if (errors_catbp & CA_ERROR_BAD_EXPIRES)
1928 my_fprintf(stderr, _(txt_error_header_format), "Expires");
1929
1930 EndInverse();
1931 my_fflush(stderr);
1932 errors += errors_catbp;
1933 }
1934
1935 /* give most warnings */
1936 if (warnings_catbp) {
1937
1938 if (warnings_catbp & CA_WARNING_SPACES_ONLY_SUBJECT)
1939 my_fprintf(stderr, "%s", _(txt_warn_blank_subject));
1940 if (warnings_catbp & CA_WARNING_RE_WITHOUT_REFERENCES)
1942 if (warnings_catbp & CA_WARNING_REFERENCES_WITHOUT_RE)
1944
1945 if ((warnings_catbp & CA_WARNING_NEWSGROUPS_EXAMPLE) || (warnings_catbp & CA_WARNING_FOLLOWUP_TO_EXAMPLE))
1946 my_fprintf(stderr, "%s", _(txt_warn_example_hierarchy));
1947
1948#ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1949 if (warnings_catbp & CA_WARNING_SPACE_IN_NEWSGROUPS)
1950 my_fprintf(stderr, _(txt_warn_header_line_comma), "Newsgroups");
1951 if (warnings_catbp & CA_WARNING_SPACE_IN_FOLLOWUP_TO)
1952 my_fprintf(stderr, _(txt_warn_header_line_comma), "Followup-To");
1953 if (warnings_catbp & CA_WARNING_NEWLINE_IN_NEWSGROUPS)
1954 my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Newsgroups");
1955 if (warnings_catbp & CA_WARNING_NEWLINE_IN_FOLLOWUP_TO)
1956 my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Followup-To");
1957#endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1958
1959 if (warnings_catbp & CA_WARNING_MULTIPLE_SIGDASHES)
1960 my_fprintf(stderr, _(txt_warn_multiple_sigs), saw_sig_dashes);
1961 if (warnings_catbp & CA_WARNING_WRONG_SIGDASHES)
1962 my_fprintf(stderr, "%s", _(txt_warn_wrong_sig_format));
1963 if (warnings_catbp & CA_WARNING_LONG_SIGNATURE)
1965
1966 if (warnings_catbp & CA_WARNING_ENCODING_EXTERNAL_INEWS)
1968
1969#ifdef CHARSET_CONVERSION
1970 if (warnings_catbp & CA_WARNING_CHARSET_CONVERSION)
1971 my_fprintf(stderr, _(txt_warn_charset_conversion), tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]);
1972#endif /* CHARSET_CONVERSION */
1973
1974 my_fflush(stderr);
1975 warnings += warnings_catbp;
1976 }
1977
1978 if (!errors) {
1979 /*
1980 * Print a note about each newsgroup
1981 */
1982 if (c_art_unchanged)
1983 my_fprintf(stderr, "%s", _(txt_warn_article_unchanged));
1984
1985 if (ngcnt)
1986 my_fprintf(stderr, _(txt_art_newsgroups), subject, PLURAL(ngcnt, txt_newsgroup));
1987
1988 if (c_art_type == GROUP_TYPE_MAIL)
1989 my_fprintf(stderr, _(txt_art_mailgroups), subject, BlankIfNull(to));
1990 else {
1991 for (i = 0; i < ngcnt; i++) {
1992 if ((psGrp = group_find(newsgroups[i], FALSE))) {
1993 if (psGrp->aliasedto) {
1994#ifdef HAVE_FASCIST_NEWSADMIN
1995 StartInverse();
1996 errors++;
1997 my_fprintf(stderr, N_(txt_error_grp_renamed), newsgroups[i], psGrp->aliasedto);
1998 EndInverse();
1999 my_fflush(stderr);
2000#else
2001 my_fprintf(stderr, N_(txt_warn_grp_renamed), newsgroups[i], psGrp->aliasedto);
2002 warnings++;
2003#endif /* HAVE_FASCIST_NEWSADMIN */
2004 } else
2005 my_fprintf(stderr, " %s\t %s\n", newsgroups[i], BlankIfNull(psGrp->description));
2006 } else {
2007#ifdef HAVE_FASCIST_NEWSADMIN
2008 StartInverse();
2009 errors++;
2010 my_fprintf(stderr, _(txt_error_not_valid_newsgroup), newsgroups[i]);
2011 EndInverse();
2012 my_fflush(stderr);
2013#else
2014 my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), newsgroups[i]);
2015 warnings++;
2016#endif /* HAVE_FASCIST_NEWSADMIN */
2017 }
2018 }
2019 if (!found_followup_to_lines && ngcnt > 1 && !errors) {
2020#ifdef HAVE_FASCIST_NEWSADMIN
2021 StartInverse();
2022 my_fprintf(stderr, _(txt_error_missing_followup_to), ngcnt);
2023 EndInverse();
2024 my_fflush(stderr);
2025 errors++;
2026#else
2027 my_fprintf(stderr, _(txt_warn_missing_followup_to), ngcnt);
2028 warnings++;
2029#endif /* HAVE_FASCIST_NEWSADMIN */
2030 }
2031
2032 if (ftngcnt && !errors) {
2033 if (ftngcnt > 1) {
2034#ifdef HAVE_FASCIST_NEWSADMIN
2035 StartInverse();
2036 my_fprintf(stderr, "%s", _(txt_error_followup_to_several_groups));
2037 EndInverse();
2038 my_fflush(stderr);
2039 errors++;
2040#else
2042 warnings++;
2043#endif /* HAVE_FASCIST_NEWSADMIN */
2044 }
2045#ifdef HAVE_FASCIST_NEWSADMIN
2046 if (!errors) {
2047#endif /* HAVE_FASCIST_NEWSADMIN */
2049 for (i = 0; i < ftngcnt; i++) {
2050 if ((psGrp = group_find(followupto[i], FALSE))) {
2051 if (psGrp->aliasedto) {
2052#ifdef HAVE_FASCIST_NEWSADMIN
2053 StartInverse();
2054 errors++;
2055 my_fprintf(stderr, N_(txt_error_grp_renamed), followupto[i], psGrp->aliasedto);
2056 EndInverse();
2057 my_fflush(stderr);
2058#else
2059 my_fprintf(stderr, N_(txt_warn_grp_renamed), followupto[i], psGrp->aliasedto);
2060 warnings++;
2061#endif /* HAVE_FASCIST_NEWSADMIN */
2062 } else
2063 my_fprintf(stderr, " %s\t %s\n", followupto[i], BlankIfNull(psGrp->description));
2064 } else {
2065 if (STRCMPEQ("poster", followupto[i]))
2066 my_fprintf(stderr, _(txt_followup_poster), followupto[i]);
2067 else {
2068#ifdef HAVE_FASCIST_NEWSADMIN
2069 StartInverse();
2070 my_fprintf(stderr, _(txt_error_not_valid_newsgroup), followupto[i]);
2071 EndInverse();
2072 my_fflush(stderr);
2073 errors++;
2074#else
2075 my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), followupto[i]);
2076 warnings++;
2077#endif /* HAVE_FASCIST_NEWSADMIN */
2078 }
2079 }
2080 }
2081#ifdef HAVE_FASCIST_NEWSADMIN
2082 }
2083#endif /* HAVE_FASCIST_NEWSADMIN */
2084 }
2085
2086#ifndef NO_ETIQUETTE
2088 my_fprintf(stderr, "%s", _(txt_warn_posting_etiquette));
2089#endif /* !NO_ETIQUETTE */
2090 my_fflush(stderr);
2091 }
2092 }
2093 fclose(fp);
2094
2095 Raw(oldraw); /* restore raw/unraw state */
2096
2097 /* free memory */
2098 if (newsgroups && ngcnt) {
2099 FreeIfNeeded(*newsgroups);
2100 FreeIfNeeded(newsgroups);
2101 }
2102 if (followupto && ftngcnt) {
2103 FreeIfNeeded(*followupto);
2104 FreeIfNeeded(followupto);
2105 }
2106 FreeIfNeeded(to);
2107
2108 return (errors ? 1 : (warnings ? 2 : 0));
2109}
2110
2111
2112static void
2114 int *init)
2115{
2116 if (*init) {
2117 ClearScreen();
2120 Raw(FALSE);
2121 *init = 0;
2122 }
2123}
2124
2125
2126#if defined(SIGWINCH) || defined(SIGTSTP)
2127void
2128refresh_post_screen(
2129 int context)
2130{
2131 switch (context) {
2132 case cPost:
2133 ClearScreen();
2136 check_article_to_be_posted(NULL, 0, NULL, FALSE, TRUE);
2137 break;
2138
2139 case cPostCancel:
2140 {
2141 int oldraw = RawState();
2142
2143 ClearScreen();
2146 Raw(FALSE);
2147# ifdef FORGERY
2149# else
2151# endif /* FORGERY */
2152 Raw(oldraw);
2153 }
2154 break;
2155
2156 case cPostFup:
2158 break;
2159
2160 default:
2161 break;
2162 }
2163}
2164#endif /* SIGWINCH || SIGTSTP */
2165
2166
2167/*
2168 * edit/present an article, perform spell/PGP etc., operations if required
2169 * submit the article and perform all necessary backend processing
2170 */
2171static int
2173 int type, /* type of posting */
2174 struct t_group *group,
2176 const char *posting_msg, /* displayed just prior to article submission */
2177 int art_type, /* news, mail etc. */
2178 int offset) /* editor start offset */
2179{
2180 char a_message_id[HEADER_LEN]; /* Message-ID of the article if known */
2181 int ret_code = POSTED_NONE;
2182 int i = 1;
2183 int save_signal_context = signal_context;
2184 long artchanged; /* artchanged work was not done in post_postponed_article */
2185 struct t_group *ogroup = curr_group;
2186 t_bool art_unchanged;
2187
2188 a_message_id[0] = '\0';
2189
2190 forever {
2191post_article_loop:
2192 art_unchanged = FALSE;
2193 switch (func) {
2194 case POST_EDIT:
2195 /*
2196 * This was VERY different in repost_article Code existed to
2197 * recheck subject and restart editor, but is not enabled
2198 */
2199 artchanged = file_mtime(article_name);
2200 if (!invoke_editor(article_name, offset, group)) {
2201 if (file_size(article_name) > 0L) {
2202 if (artchanged != file_mtime(article_name)) {
2207 }
2208 }
2209 goto post_article_postponed;
2210 }
2212
2213 /* This might be erroneous with posting postponed */
2214 if (file_size(article_name) > 0L) {
2215 if (artchanged == file_mtime(article_name))
2216 art_unchanged = TRUE;
2217 while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
2218 ;
2220 break;
2221 }
2222 /* FALLTHROUGH */
2223
2224 case GLOBAL_QUIT:
2225 case GLOBAL_ABORT:
2226 if (tinrc.unlink_article) {
2227#if 0 /* useful? */
2230#endif /* 0 */
2232 }
2233 clear_message();
2234 return ret_code;
2235
2236 case GLOBAL_OPTION_MENU:
2238 while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
2239 ;
2240 break;
2241
2242#ifdef HAVE_ISPELL
2243 case POST_ISPELL:
2244 invoke_ispell(article_name, group);
2245 ret_code = POSTED_REDRAW; /* not all versions did this */
2246 break;
2247#endif /* HAVE_ISPELL */
2248
2249#ifdef HAVE_PGP_GPG
2250 case POST_PGP:
2251 invoke_pgp_news(article_name);
2252 break;
2253#endif /* HAVE_PGP_GPG */
2254
2255 case GLOBAL_POST:
2256 wait_message(0, posting_msg);
2258
2259 /* Functions that didn't handle mail didn't do this */
2260 if (art_type == GROUP_TYPE_NEWS) {
2261 if (submit_news_file(article_name, group, a_message_id))
2263 } else {
2264 if (submit_mail_file(article_name, group, NULL, FALSE)) /* mailing_list */
2266 }
2267
2268 if (ret_code == POSTED_OK) {
2270 wait_message(2, _(txt_art_posted), *a_message_id ? a_message_id : "");
2271 goto post_article_done;
2272 } else {
2273 if ((func = prompt_rejected()) == POST_POSTPONE)
2274 /* reuse clean copy which didn't get modified by submit_news_file() */
2276 else if (func == POST_EDIT) {
2277 /* replace modified article with clean backup */
2279 goto post_article_loop;
2280 } else {
2286 }
2287 return ret_code;
2288 }
2289
2290 case POST_POSTPONE:
2292 goto post_article_postponed;
2293
2294 default:
2295 break;
2296 }
2298 if (type != POST_REPOST && type != POST_SUPERSEDED) {
2299 char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
2300 char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
2301 char keymenu[MAXKEYLEN];
2302#ifdef HAVE_ISPELL
2303 char keyispell[MAXKEYLEN];
2304#endif /* HAVE_ISPELL */
2305#ifdef HAVE_PGP_GPG
2306 char keypgp[MAXKEYLEN];
2307#endif /* HAVE_PGP_GPG */
2308
2309#if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
2310 func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
2314 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
2315 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
2319#else
2320# ifdef HAVE_ISPELL
2321 func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
2325 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
2329# else
2330# ifdef HAVE_PGP_GPG
2331 func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
2335 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
2339# else
2340 func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
2347# endif /* HAVE_PGP_GPG */
2348# endif /* HAVE_ISPELL */
2349#endif /* HAVE_ISPELL && HAVE_PGP_GPG */
2350 } else {
2351 char *smsg;
2352 char buf[LEN];
2353 char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
2354 char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
2355 char keymenu[MAXKEYLEN];
2356#ifdef HAVE_ISPELL
2357 char keyispell[MAXKEYLEN];
2358#endif /* HAVE_ISPELL */
2359#ifdef HAVE_PGP_GPG
2360 char keypgp[MAXKEYLEN];
2361#endif /* HAVE_PGP_GPG */
2362
2363#if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
2367 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
2368 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
2372#else
2373# ifdef HAVE_ISPELL
2377 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
2381# else
2382# ifdef HAVE_PGP_GPG
2386 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
2390# else
2397# endif /* HAVE_PGP_GPG */
2398# endif /* HAVE_ISPELL */
2399#endif /* HAVE_ISPELL && HAVE_PGP_GPG */
2400
2402 post_post_keys, "%s", sized_message(&smsg, buf,
2403 "" /* TODO: was note_h.subj */ )));
2404 free(smsg);
2405 }
2406 signal_context = save_signal_context;
2407 }
2408
2409post_article_done:
2410 if (ret_code == POSTED_OK) {
2411 FILE *art_fp;
2412 struct t_header header;
2413
2414 memset(&header, 0, sizeof(struct t_header));
2415
2416 if ((art_fp = fopen(article_name, "r")) == NULL)
2418 else {
2419 curr_group = group;
2420 parse_rfc822_headers(&header, art_fp, NULL);
2421 fclose(art_fp);
2422 }
2423
2424 if (art_type == GROUP_TYPE_NEWS) {
2425 if (header.newsgroups) {
2427 /* In POST_RESPONSE, this was copied from note_h.newsgroups if !followup to poster */
2429 }
2430 }
2431
2432 if (header.subj && header.newsgroups) {
2433 char tag;
2434 /*
2435 * When crossposting postponed articles we currently do not add
2436 * autoselect since we don't know which group the article was
2437 * actually in
2438 * FIXME: This logic is faithful to the original, but awful
2439 */
2440 if (group) { /* we might be (x-)posting to an unavailable group */
2441 if (art_type == GROUP_TYPE_NEWS && group->attribute->add_posted_to_filter && (type == POST_QUICK || type == POST_POSTPONED || type == POST_NORMAL)) {
2442 if ((group = group_find(header.newsgroups, FALSE)) && (type != POST_POSTPONED || (type == POST_POSTPONED && !strchr(header.newsgroups, ',')))) {
2443 quick_filter_select_posted_art(group, header.subj, a_message_id);
2444 if (type == POST_QUICK || (type == POST_POSTPONED && post_postponed_and_exit))
2446 }
2447 }
2448 }
2449
2450 switch (type) {
2451 case POST_POSTPONED:
2452 tag = (header.references) ? 'f' : 'w';
2453 break;
2454
2455 case POST_RESPONSE:
2456 tag = 'f';
2457 break;
2458
2459 case POST_REPOST:
2460 case POST_SUPERSEDED:
2461 tag = 'x';
2462 break;
2463
2464 case POST_NORMAL:
2465 case POST_QUICK:
2466 default:
2467 tag = 'w';
2468 break;
2469 }
2470
2471 switch (art_type) {
2472 case GROUP_TYPE_NEWS:
2473 update_posted_info_file(header.newsgroups, tag, header.subj, a_message_id);
2474 break;
2475
2476 case GROUP_TYPE_MAIL:
2477 update_posted_info_file(header.to, tag, header.subj, "");
2478 break;
2479
2480 default:
2481 break;
2482 }
2483
2485 }
2486
2487 if (*tinrc.posted_articles_file && type != POST_REPOST) { /* TODO: either document the !POST_REPOST logic or remove it */
2488 char a_mailbox[PATH_LEN];
2489 char posted_msgs_file[PATH_LEN];
2490
2491 if (!strfpath(tinrc.posted_articles_file, posted_msgs_file, sizeof(posted_msgs_file), group, TRUE))
2492 STRCPY(posted_msgs_file, tinrc.posted_articles_file);
2493 else {
2494 if (!strcmp(tinrc.posted_articles_file, posted_msgs_file)) /* only prefix tinrc.posted_articles_file if it was a plain file without path */
2495 joinpath(posted_msgs_file, sizeof(posted_msgs_file), (cmdline.args & CMDLINE_MAILDIR) ? cmdline.maildir : (group ? group->attribute->maildir : tinrc.maildir), tinrc.posted_articles_file);
2496 }
2497
2498 /* re-strfpath as maildir may also need expansion */
2499 if (!strfpath(posted_msgs_file, a_mailbox, sizeof(a_mailbox), group, TRUE))
2500 STRCPY(a_mailbox, posted_msgs_file);
2501
2502 /*
2503 * log Message-ID if given in a_message_id,
2504 * add Date: if required, remove empty headers
2505 */
2506 add_headers(article_name, a_message_id);
2507
2508 if ((errno = append_mail(article_name, userid, a_mailbox)))
2510 }
2511 free_and_init_header(&header);
2512 }
2513
2514post_article_postponed:
2515 curr_group = ogroup;
2518
2519 return ret_code;
2520}
2521
2522
2523/*
2524 * Parse the list of newsgroups. For each, check group flag status. If it is
2525 * possible to post to the group and the user agrees, then keep going. Return
2526 * pointer to the first group in the list (the posting code needs this)
2527 * Any one failure => return NULL
2528 */
2529static struct t_group *
2531 const char *groups,
2532 int *art_type,
2533 const char *failmsg)
2534{
2535 char *groupname;
2536 char *ogroupn;
2537 char newsgroups[HEADER_LEN];
2538 struct t_group *group;
2539 struct t_group *first_group = NULL;
2540 int vnum = 0, bnum = 0;
2541
2542 /* Take copy - strtok() modifies its args */
2543 STRCPY(newsgroups, groups);
2544
2545 if ((ogroupn = groupname = strtok(newsgroups, ",")) == NULL)
2546 return NULL;
2547
2548 do {
2549 vnum++; /* number of newsgroups */
2550
2551 if (!(group = group_find(groupname, FALSE))) {
2552 bnum++; /* number of bogus groups */
2553 continue;
2554 }
2555
2556 if (!first_group) /* Save ptr to the 1st group */
2557 first_group = group;
2558
2559 /*
2560 * Testing for !attribute here is a useful check for other brokenness
2561 * Generally only bogus groups should have no attributes
2562 */
2563 if (group->bogus) {
2564 error_message(2, _(txt_group_bogus), groupname);
2565 return NULL;
2566 }
2567
2568 if (group->attribute->mailing_list != NULL)
2569 *art_type = GROUP_TYPE_MAIL;
2570
2571 if (!can_post && *art_type == GROUP_TYPE_NEWS) {
2573 return NULL;
2574 }
2575
2576 if (group->moderated == 'x' || group->moderated == 'n' || group->moderated == 'j') {
2578 return NULL;
2579 }
2580
2581 if (group->moderated == 'm') {
2582 char *prompt = fmt_string(_(txt_group_is_moderated), groupname);
2583 if (prompt_yn(prompt, TRUE) != 1) {
2584 error_message(*failmsg ? 2 : 0, failmsg);
2585 free(prompt);
2586 return NULL;
2587 }
2588 free(prompt);
2589 }
2590 } while ((groupname = strtok(NULL, ",")) != NULL);
2591
2592 if (vnum > bnum)
2593 return first_group;
2594 else {
2596 return NULL;
2597 }
2598}
2599
2600
2601/*
2602 * Build the standard headers used by quick_post_article() and post_article()
2603 * Return TRUE or FALSE if things went wrong - there seems to be little
2604 * error checking possible in here
2605 */
2606static t_bool
2608 struct t_group *group,
2609 const char *newsgroups,
2610 int art_type)
2611{
2612 FILE *fp;
2613 char from_name[HEADER_LEN];
2614#ifdef FORGERY
2615 char tmp[HEADER_LEN];
2616#endif /* FORGERY */
2617 char *prompt, *tmp2;
2618
2619 /* Get subject for posting article - Limit the display if needed */
2621
2622 prompt = fmt_string(_(txt_post_subject), tmp2);
2623
2625 free(prompt);
2626 free(tmp2);
2627 return FALSE;
2628 }
2629 free(prompt);
2630 free(tmp2);
2631
2632 if ((fp = fopen(article_name, "w")) == NULL) {
2634 return FALSE;
2635 }
2636
2637#ifdef HAVE_FCHMOD
2638 fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
2639#else
2640# ifdef HAVE_CHMOD
2641 chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
2642# endif /* HAVE_CHMOD */
2643#endif /* HAVE_FCHMOD */
2644
2645 get_from_name(from_name, group);
2646#ifdef FORGERY
2647 make_path_header(tmp);
2648 msg_add_header("Path", tmp);
2649#endif /* FORGERY */
2650 msg_add_header("From", from_name);
2652
2653 if (art_type == GROUP_TYPE_MAIL)
2654 msg_add_header("To", group->attribute->mailing_list);
2655 else {
2656 msg_add_header("Newsgroups", newsgroups);
2658 }
2659
2660 if (art_type == GROUP_TYPE_NEWS) {
2661 if (group->attribute->followup_to != NULL)
2662 msg_add_header("Followup-To", group->attribute->followup_to);
2663 else {
2664 if (group->attribute->prompt_followupto)
2665 msg_add_header("Followup-To", "");
2666 }
2667 }
2668
2669 if (*reply_to)
2670 msg_add_header("Reply-To", reply_to);
2671
2672 if (group->attribute->organization != NULL)
2673 msg_add_header("Organization", random_organization(group->attribute->organization));
2674
2675 if (*my_distribution && art_type == GROUP_TYPE_NEWS)
2676 msg_add_header("Distribution", my_distribution);
2677
2678 msg_add_header("Summary", "");
2679 msg_add_header("Keywords", "");
2680
2682
2684 fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
2686
2688
2689 msg_write_signature(fp, FALSE, group);
2690 fclose(fp);
2691 cursoron();
2692 return TRUE;
2693}
2694
2695
2696/*
2697 * Quick post an article (not a followup)
2698 */
2699void
2701 t_bool postponed_only,
2702 int num_cmd_line_groups)
2703{
2704 char buf[HEADER_LEN];
2705 int art_type = GROUP_TYPE_NEWS;
2706 struct t_group *group;
2707
2709 ClearScreen();
2710
2711 /*
2712 * check for postponed articles first
2713 * first param is whether to ask the user if they want to do it or not.
2714 * it's opposite to the command line switch.
2715 * second param is whether to assume yes to all which is the same as
2716 * the command line switch.
2717 */
2718
2719 if (pickup_postponed_articles(!postponed_only, postponed_only) || postponed_only)
2720 return;
2721
2722 /*
2723 * post_article_and_exit
2724 * Get groupname, but skip query if group was given on the cmd.-line
2725 */
2726 if (!num_cmd_line_groups) {
2729 return;
2730
2732 }
2733
2734 /*
2735 * Check/see if any of the newsgroups are not postable.
2736 */
2737 if ((group = check_moderated(tinrc.default_post_newsgroups, &art_type, _(txt_exiting))) == NULL)
2738 return;
2739
2741 return;
2742
2744}
2745
2746
2747/*
2748 * Post an article that is already written (for postponed articles)
2749 */
2750static void
2752 int ask,
2753 const char *subject,
2754 const char *newsgroups)
2755{
2756 char *ng;
2757 char *p;
2758 char buf[LEN];
2759
2760 if (!can_post) {
2762 return;
2763 }
2764
2765 ng = my_strdup(newsgroups);
2766 if ((p = strchr(ng, ',')) != NULL)
2767 *p = '\0';
2768
2769 snprintf(buf, sizeof(buf), _("Posting: %.*s ..."), cCOLS - 14, subject); /* TODO: -> lang.c, use strunc() */
2771 free(ng);
2772}
2773
2774
2775/*
2776 * count how many articles are in postponed.articles. Essentially,
2777 * we count '^From ' lines
2778 */
2779int
2781 void)
2782{
2783 FILE *fp = fopen(postponed_articles_file, "r");
2784 char line[HEADER_LEN];
2785 int count = 0;
2786
2787 if (!fp)
2788 return 0;
2789
2790 while (fgets(line, (int) sizeof(line), fp)) {
2791 if (strncmp(line, "From ", 5) == 0)
2792 count++;
2793 }
2794 fclose(fp);
2795 return count;
2796}
2797
2798
2799/*
2800 * Copy the first postponed article and remove it from the postponed file
2801 */
2802static t_bool
2804 const char tmp_file[],
2805 char subject[],
2806 char newsgroups[])
2807{
2808 FILE *in, *out;
2809 FILE *tmp;
2810 char *bufp;
2811 char postponed_tmp[PATH_LEN];
2812 char line[HEADER_LEN];
2813 t_bool first_article;
2814 t_bool prev_line_nl;
2815 t_bool anything_left;
2816
2817 snprintf(postponed_tmp, sizeof(postponed_tmp), "%s_", postponed_articles_file);
2818 in = fopen(postponed_articles_file, "r");
2819 out = fopen(tmp_file, "w");
2820 tmp = fopen(postponed_tmp, "w");
2821
2822 if (in == NULL || out == NULL || tmp == NULL) {
2823 if (in)
2824 fclose(in);
2825 if (out)
2826 fclose(out);
2827 if (tmp)
2828 fclose(tmp);
2829 return FALSE;
2830 }
2831
2832 if (fgets(line, (int) sizeof(line), in) == NULL || strncmp(line, "From ", 5) != 0) {
2833 fclose(in);
2834 fclose(out);
2835 fclose(tmp);
2836 return FALSE;
2837 }
2838
2839 first_article = TRUE;
2840 prev_line_nl = FALSE;
2841 anything_left = FALSE;
2842
2843 /*
2844 * we have one minor problem with copying the article, we have added
2845 * a newline at the end of the article and we have to remove that,
2846 * but we don't know we are on the last line until we read the next
2847 * line containing "From "
2848 */
2849
2850 while (fgets(line, (int) sizeof(line), in) != NULL) {
2851 if (strncmp(line, "From ", 5) == 0)
2852 first_article = FALSE;
2853 if (first_article) {
2854 match_string(line, "Newsgroups: ", newsgroups, HEADER_LEN);
2855 match_string(line, "Subject: ", subject, HEADER_LEN);
2856
2857 if (prev_line_nl)
2858 fputc('\n', out);
2859
2860 if (strlen(line) && line[strlen(line) - 1] == '\n') {
2861 prev_line_nl = TRUE;
2862 line[strlen(line) - 1] = '\0';
2863 } else
2864 prev_line_nl = FALSE;
2865
2866 /* unquote quoted From_ lines */
2867 if (tinrc.mailbox_format == 1) {
2868 bufp = line;
2869 while (*bufp == '>')
2870 bufp++;
2871 if (strncmp(bufp, "From ", 5) == 0)
2872 fputs(line + 1, out);
2873 else
2874 fputs(line, out);
2875 } else {
2876 if (strncmp(line, ">From ", 6) == 0)
2877 fputs(line + 1, out);
2878 else
2879 fputs(line, out);
2880 }
2881 } else {
2882 fputs(line, tmp);
2883 anything_left = TRUE;
2884 }
2885 }
2886
2887 fclose(in);
2888 fclose(out);
2889 fclose(tmp);
2890
2892
2893 if (anything_left)
2894 rename_file(postponed_tmp, postponed_articles_file);
2895 else
2896 unlink(postponed_tmp);
2897
2898 return TRUE;
2899}
2900
2901
2902/* pick up any postponed articles and ask if the user wants to use them */
2903t_bool
2905 t_bool ask,
2906 t_bool all)
2907{
2908 char newsgroups[HEADER_LEN];
2909 char subject[HEADER_LEN];
2910 char question[HEADER_LEN];
2912 int i;
2914
2915 if (!count) {
2916 if (!ask)
2918 return FALSE;
2919 }
2920
2921 snprintf(question, sizeof(question), _(txt_prompt_see_postponed), count);
2922
2923 if (ask && prompt_yn(question, TRUE) != 1)
2924 return FALSE;
2925
2926 for (i = 0; i < count; i++) {
2927 if (!fetch_postponed_article(article_name, subject, newsgroups))
2928 return TRUE;
2929
2930 if (!all) {
2931 char *smsg;
2932 char buf[LEN];
2933 char keyall[MAXKEYLEN], keyno[MAXKEYLEN], keyoverride[MAXKEYLEN];
2934 char keyquit[MAXKEYLEN], keyyes[MAXKEYLEN];
2935
2942
2944 "%s", sized_message(&smsg, buf, subject));
2945 free(smsg);
2946
2947 if (func == POSTPONE_ALL)
2948 all = TRUE;
2949 }
2950
2951 /* No else here since all changes in previous if */
2952 if (all)
2954
2955 switch (func) {
2956 case PROMPT_YES:
2957 case POSTPONE_OVERRIDE:
2958 post_postponed_article(func == PROMPT_YES, subject, newsgroups);
2959 Raw(TRUE);
2960 break;
2961
2962 case PROMPT_NO:
2963 case GLOBAL_QUIT:
2964 case GLOBAL_ABORT:
2968 if (func != PROMPT_NO)
2969 return TRUE;
2970 break;
2971
2972 default:
2973 break;
2974 }
2975 }
2976 return TRUE;
2977}
2978
2979
2980static void
2982 const char *the_article)
2983{
2985 if ((errno = append_mail(the_article, userid, postponed_articles_file)))
2987}
2988
2989
2990/*
2991 * Post an original article (not a followup)
2992 */
2993t_bool
2995 const char *groupname)
2996{
2997 int art_type = GROUP_TYPE_NEWS;
2998 struct t_group *group;
3000
3002
3003 /*
3004 * Check that we can post to all the groups we want to
3005 */
3006 if ((group = check_moderated(groupname, &art_type, "")) == NULL)
3007 return redraw_screen;
3008
3009 if (!create_normal_article_headers(group, groupname, art_type))
3010 return redraw_screen;
3011
3012 return (post_loop(POST_NORMAL, group, POST_EDIT, _(txt_posting), art_type, start_line_offset) != POSTED_NONE);
3013}
3014
3015
3016/*
3017 * yeah, right, that's from the same Chris who is telling Jason he's
3018 * doing obfuscated C :-)
3019 */
3020static void
3022 char **where,
3023 const char **what)
3024{
3025 char *oldpos;
3026
3027 oldpos = *where;
3028 while (**what && **what != '<')
3029 (*what)++;
3030 if (**what) {
3031 while (**what && **what != '>' && !isspace((unsigned char) **what))
3032 *(*where)++ = *(*what)++;
3033 if (**what != '>')
3034 *where = oldpos;
3035 else {
3036 (*what)++;
3037 *(*where)++ = '>';
3038 }
3039 }
3040}
3041
3042
3043/*
3044 * check given Message-ID for "_-_@" which (should) indicate(s)
3045 * a Subject: change
3046 */
3047static t_bool
3049 const char *id)
3050{
3051 while (*id && *id != '<')
3052 id++;
3053 while (*id && *id != '>') {
3054 if (*++id != '_')
3055 continue;
3056 if (*++id != '-')
3057 continue;
3058 if (*++id != '_')
3059 continue;
3060 if (*++id == '@')
3061 return TRUE;
3062 }
3063 return FALSE;
3064}
3065
3066
3067static size_t
3069 const char *id)
3070{
3071 size_t skipped = 0;
3072
3073 while (id[skipped] != '\0' && isspace((unsigned char) id[skipped]))
3074 skipped++;
3075
3076 if (id[skipped] != '\0') {
3077 while (id[skipped] != '\0' && !isspace((unsigned char) id[skipped]))
3078 skipped++;
3079 }
3080 return skipped;
3081}
3082
3083
3084/*
3085 * Checks if Message-ID has valid format
3086 * Returns FALSE if it does, TRUE if it does not
3087 * TODO: combine with refs.c:valid_msgid() (return values swapped)
3088 */
3089static t_bool
3091 const char *id)
3092{
3093 while (*id && isspace((unsigned char) *id))
3094 id++;
3095
3096 if (*id != '<')
3097 return TRUE;
3098
3099 while (isascii((unsigned char) *id) && isgraph((unsigned char) *id) && !iscntrl((unsigned char) *id) && *id != '>')
3100 id++;
3101
3102 if (*id != '>')
3103 return TRUE;
3104
3105 return FALSE;
3106}
3107
3108
3109/*
3110 * A real crossposting test had to run on Newsgroups but we only have Xref in
3111 * t_article, so we use this.
3112 */
3113static t_bool
3115 const char *xref)
3116{
3117 int count = 0;
3118
3119 for (; *xref; xref++)
3120 if (*xref == ':')
3121 count++;
3122
3123 return (count >= 2) ? TRUE : FALSE;
3124}
3125
3126
3127/*
3128 * RFC 5537 3.4.4
3129 * "If the resulting References header field would, after unfolding, exceed
3130 * 998 characters in length (including its field name but not the final
3131 * CRLF), it MUST be trimmed (and otherwise MAY be trimmed)."
3132 */
3133#ifdef NNTP_ONLY
3134# define MAXREFSIZE 998
3135#else /* some extern inews (required for posting right into the spool) can't handle 1k-lines */
3136# define MAXREFSIZE 512
3137#endif /* NNTP_ONLY */
3138
3139
3140/*
3141 * TODO: if we have the art[x] that we are following up to, then
3142 * get_references(art[x].refptr) will give us the new refs line
3143 */
3144static void
3146 char *buffer,
3147 const char *oldrefs,
3148 const char *newref)
3149{
3150 /*
3151 * First of all: shortening references is a VERY BAD IDEA.
3152 * Nevertheless, current software usually has restrictions in
3153 * header length (their programmers seem to misinterpret RFC821
3154 * as valid for news, and the command length limit of RFC977
3155 * as valid for headers)
3156 *
3157 * construct a new references line, then trim it if necessary
3158 *
3159 * do some sanity cleanups: remove damaged ids, make
3160 * sure there is space between ids (tabs and commas are stripped)
3161 *
3162 * note that we're not doing strict son of RFC 1036 here: we don't
3163 * take any precautions to keep the last three message ids, but
3164 * it's not very likely that MAXREFSIZE chars can't hold at least
3165 * 4 refs
3166 */
3167 char *b, *c, *d;
3168 const char *e;
3169 int space = 0;
3170
3171 b = my_malloc(strlen(oldrefs) + strlen(newref) + 64);
3172 c = b;
3173 e = oldrefs;
3174
3175 while (*e) {
3176 if (*e == ' ') {
3177 /* keep existing spaces */
3178 space++;
3179 *c++ = ' ';
3180 e++;
3181 continue;
3182 } else if (*e != '<') { /* strip everything besides spaces and */
3183 e++; /* message-ids */
3184 continue;
3185 }
3186 if (damaged_id(e)) { /* remove damaged message ids and mark
3187 the gap if that's not already done */
3188 e += skip_id(e);
3189 while (space < 3) {
3190 space++;
3191 *c++ = ' ';
3192 }
3193 continue;
3194 }
3195 if (!space)
3196 *c++ = ' ';
3197 else
3198 space = 0;
3199 appendid(&c, &e);
3200 }
3201 while (space) {
3202 c--;
3203 space--; /* remove superfluous space at the end */
3204 }
3205 *c++ = ' ';
3206 appendid(&c, &newref);
3207 *c = '\0';
3208
3209 /* now see if we need to remove ids */
3210 while (strlen(b) > (MAXREFSIZE - strlen("References: ") - 2)) {
3211 c = b;
3212 c += skip_id(c); /* keep the first one */
3213 while (*c && must_include(c))
3214 c += skip_id(c); /* skip those marked with _-_ */
3215 d = c;
3216 c += skip_id(c); /* ditch one */
3217 *d++ = ' ';
3218 *d++ = ' ';
3219 *d++ = ' '; /* and mark this appropriately */
3220 while (*c == ' ')
3221 c++;
3222#ifdef HAVE_MEMMOVE /* TODO: put into a function? */
3223 memmove(d, c, strlen(c) + 1);
3224#else
3225# ifdef HAVE_BCOPY
3226 bcopy(c, d, strlen(c) + 1);
3227# else
3228 {
3229 size_t l = strlen(c) + 1;
3230
3231 if (c < d && d < c + l) {
3232 d += l;
3233 c += l;
3234 while (l--)
3235 *--d= *--c;
3236 } else {
3237 while (l--)
3238 *d++ = *c++;
3239 }
3240 }
3241# endif /* HAVE_BCOPY */
3242#endif /* HAVE_MEMMOVE */
3243 }
3244
3245 strcpy(buffer, b);
3246 free(b);
3247 return;
3248
3249 /*
3250 * son of RFC 1036 says:
3251 * Followup agents SHOULD not shorten References headers. If
3252 * it is absolutely necessary to shorten the header, as a des-
3253 * perate last resort, a followup agent MAY do this by deleting
3254 * some of the message IDs. However, it MUST not delete the
3255 * first message ID, the last three message IDs (including that
3256 * of the immediate precursor), or any message ID mentioned in
3257 * the body of the followup. If it is possible for the fol-
3258 * lowup agent to determine the Subject content of the articles
3259 * identified in the References header, it MUST not delete the
3260 * message ID of any article where the Subject content changed
3261 * (other than by prepending of a back reference). The fol-
3262 * lowup agent MUST not delete any message ID whose local part
3263 * ends with "_-_" (underscore (ASCII 95), hyphen (ASCII 45),
3264 * underscore); followup agents are urged to use this form to
3265 * mark subject changes, and to avoid using it otherwise.
3266 * [...]
3267 * When a References header is shortened, at least three blanks
3268 * SHOULD be left between adjacent message IDs at each point
3269 * where deletions were made. Software preparing new Refer-
3270 * ences headers SHOULD preserve multiple blanks in older Ref-
3271 * erences content.
3272 */
3273}
3274
3275
3276static void
3278 void)
3279{
3280 char *ptr;
3281 struct t_header note_h = pgart.hdr;
3282
3283 /*
3284 * note that comparing newsgroups and followup-to isn't
3285 * really correct, since the order of the newsgroups may be
3286 * different, but testing that also isn't really worth
3287 * it. The main culprit for the duplication is tin <=1.22, BTW.
3288 */
3289 MoveCursor(cLINES / 2, 0);
3290 CleartoEOS();
3292 MoveCursor((cLINES / 2) + 4, 0);
3293
3294 my_fputs(" ", stdout);
3295 /*
3296 * TODO: check if any valid groups are in the Followup-To:-line
3297 * and if not inform the user and use Newsgroups: instead
3298 */
3299 ptr = note_h.followup;
3300 while (*ptr) {
3301 if (*ptr != ',')
3302 my_fputc(*ptr, stdout);
3303 else {
3304 my_fputs(cCRLF, stdout);
3305 my_fputs(" ", stdout);
3306 }
3307 ptr++;
3308 }
3309 my_flush();
3310}
3311
3312
3313int /* return code is currently ignored! */
3315 const char *groupname,
3316 int respnum,
3317 t_bool copy_text,
3318 t_bool with_headers,
3319 t_bool raw_data)
3320{
3321 FILE *fp;
3322 char *ptr;
3323 char bigbuf[HEADER_LEN];
3324 char buf[HEADER_LEN];
3325 char from_name[HEADER_LEN];
3326 char initials[64];
3327 int art_type = GROUP_TYPE_NEWS;
3328 int ret_code = POSTED_NONE;
3329 struct t_group *group;
3330 struct t_header note_h = pgart.hdr;
3331 t_bool use_followup_to = TRUE;
3332#ifdef FORGERY
3333 char line[HEADER_LEN];
3334#endif /* FORGERY */
3336
3339
3340 /*
3341 * Remove duplicates in Newsgroups and Followup-To line
3342 *
3343 * RFC 5536 3.1.4, 3.2.6 allows FWS but discourages it
3344 * -> remove FWS from newsgroups and followup
3345 *
3346 * TODO: also remove WSP
3347 */
3350 if (note_h.followup) {
3353 }
3354
3355 if (note_h.followup && STRCMPEQ(note_h.followup, "poster")) {
3356 char keymail[MAXKEYLEN], keypost[MAXKEYLEN], keyquit[MAXKEYLEN];
3357
3358/* clear_message(); */
3363 switch (func) {
3364 case GLOBAL_POST:
3365 use_followup_to = FALSE;
3366 break;
3367
3368 case GLOBAL_QUIT:
3369 case GLOBAL_ABORT:
3370 return ret_code;
3371
3372 case POST_MAIL:
3373 return mail_to_author(groupname, respnum, copy_text, with_headers, FALSE);
3374
3375 default:
3376 break;
3377 }
3378 } else if (note_h.followup && strcmp(note_h.followup, groupname) != 0
3379 && strcmp(note_h.followup, note_h.newsgroups) != 0) {
3380 char keyignore[MAXKEYLEN], keypost[MAXKEYLEN], keyquit[MAXKEYLEN];
3381 int save_signal_context = signal_context;
3382
3390 signal_context = save_signal_context;
3391 switch (func) {
3392 case GLOBAL_QUIT:
3393 case GLOBAL_ABORT:
3394 return ret_code;
3395
3396 case POST_IGNORE_FUPTO:
3397 use_followup_to = FALSE;
3398 break;
3399
3400 case GLOBAL_POST:
3401 default:
3402 break;
3403 }
3404 }
3405
3406 if ((fp = fopen(article_name, "w")) == NULL) {
3408 return ret_code;
3409 }
3410
3411#ifdef HAVE_FCHMOD
3412 fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
3413#else
3414# ifdef HAVE_CHMOD
3415 chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
3416# endif /* HAVE_CHMOD */
3417#endif /* HAVE_FCHMOD */
3418
3419 group = group_find(groupname, FALSE);
3420 get_from_name(from_name, group);
3421#ifdef FORGERY
3422 make_path_header(line);
3423 msg_add_header("Path", line);
3424#endif /* FORGERY */
3425 msg_add_header("From", from_name);
3426
3427 ptr = my_strdup(note_h.subj);
3428 snprintf(bigbuf, sizeof(bigbuf), "Re: %s", eat_re(ptr, TRUE));
3429 msg_add_header("Subject", bigbuf);
3430 free(ptr);
3431
3432 if (group && group->attribute->x_comment_to && note_h.from)
3433 msg_add_header("X-Comment-To", note_h.from);
3434 if (note_h.followup && use_followup_to) {
3435 msg_add_header("Newsgroups", note_h.followup);
3436 if (group && group->attribute->prompt_followupto)
3437 msg_add_header("Followup-To", (strchr(note_h.followup, ',') != NULL) ? note_h.followup : "");
3438 } else {
3439 if (group && group->attribute->mailing_list) {
3440 msg_add_header("To", group->attribute->mailing_list);
3441 art_type = GROUP_TYPE_MAIL;
3442 } else {
3443 msg_add_header("Newsgroups", note_h.newsgroups);
3444 if (group && group->attribute->prompt_followupto)
3445 msg_add_header("Followup-To", (strchr(note_h.newsgroups, ',') != NULL) ? note_h.newsgroups : "");
3446 if (group && group->attribute->followup_to != NULL)
3447 msg_add_header("Followup-To", group->attribute->followup_to);
3448 else {
3449 if (strchr(note_h.newsgroups, ','))
3450 msg_add_header("Followup-To", note_h.newsgroups);
3451 }
3452 }
3453 }
3454
3455 /*
3456 * Append to References: line if its already there
3457 */
3458 if (note_h.references) {
3460 msg_add_header("References", bigbuf);
3461 } else
3462 msg_add_header("References", note_h.messageid);
3463
3464 if (group && group->attribute->organization != NULL)
3465 msg_add_header("Organization", random_organization(group->attribute->organization));
3466
3467 if (*reply_to)
3468 msg_add_header("Reply-To", reply_to);
3469
3470 if (art_type != GROUP_TYPE_MAIL) {
3472 if (note_h.distrib)
3473 msg_add_header("Distribution", note_h.distrib);
3474 else if (*my_distribution)
3475 msg_add_header("Distribution", my_distribution);
3476 }
3477
3478 if (group && group->attribute->x_headers)
3480
3483 if (group && group->attribute->x_body)
3485
3486 if (copy_text) {
3487 if (arts[respnum].xref && is_crosspost(arts[respnum].xref)) {
3488 if (strfquote(group ? group->name : groupname, respnum, buf, sizeof(buf), tinrc.xpost_quote_format))
3489 fprintf(fp, "%s\n", buf);
3490 } else if (strfquote(groupname, respnum, buf, sizeof(buf), (group && group->attribute->news_quote_format != NULL) ? group->attribute->news_quote_format : tinrc.news_quote_format))
3491 fprintf(fp, "%s\n", buf);
3493
3494 /*
3495 * check if tinrc.xpost_quote_format or tinrc.news_quote_format
3496 * is longer than 1 line and correct start_line_offset
3497 */
3498 for (ptr = buf; *ptr; ptr++) {
3499 if (*ptr == '\n')
3501 }
3502
3503 get_initials(&arts[respnum], initials, sizeof(initials) - 1);
3504
3505 if (raw_data) /* rewind raw article if needed */
3506 fseek(pgart.raw, 0L, SEEK_SET);
3507
3508 if (with_headers && raw_data)
3509 copy_body(pgart.raw, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, TRUE);
3510 else {
3511 if (raw_data) {
3512 long offset = 0L;
3513 char buffer[8192];
3514
3515 /* skip headers + header/body separator */
3516 while (fgets(buffer, (int) sizeof(buffer), pgart.raw) != NULL) {
3517 offset = (long) ((size_t) offset + strlen(buffer));
3518 if (buffer[0] == '\n' || buffer[0] == '\r')
3519 break;
3520 }
3521 fseek(pgart.raw, offset, SEEK_SET);
3522 copy_body(pgart.raw, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, TRUE);
3523 } else { /* cooked art */
3525 if (with_headers) {
3526 /*
3527 * unfortunately this includes only those headers
3528 * mentioned in news_headers_to_display as article
3529 * cooking 'hides' all other headers
3530 */
3531 fseek(pgart.cooked, 0L, SEEK_SET); /* rewind cooked art */
3532 } else { /* without headers */
3533 int i = 0;
3534
3535 while (pgart.cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
3536 i++;
3537
3538 if (i) /* cooked art contained any headers, so skip also the header/body separator */
3539 i++;
3540
3541 fseek(pgart.cooked, pgart.cookl[i].offset, SEEK_SET); /* skip headers and header/body separator */
3542 }
3543 copy_body(pgart.cooked, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, FALSE);
3544 }
3545 }
3546 } else /* !copy_text */
3547 fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
3548
3549 msg_write_signature(fp, FALSE, group);
3550 fclose(fp);
3551
3552 resize_article(TRUE, &pgart); /* rebreak long lines */
3553 if (raw_data) /* we've been in raw mode, reenter it */
3554 toggle_raw(group);
3555
3556 return (post_loop(POST_RESPONSE, group, POST_EDIT, _(txt_posting), art_type, start_line_offset));
3557}
3558
3559
3560/*
3561 * Generates the basic header for a mailed article
3562 * Returns an open fp or NULL if article couldn't be created
3563 * The name of the temp. article file is written to 'filename'
3564 * If extra_hdrs is defined, then additional headers are added, see the code
3565 */
3566static FILE *
3568 char *filename,
3569 size_t filename_len,
3570 const char *suffix,
3571 const char *to,
3572 const char *subject,
3573 struct t_header *extra_hdrs)
3574{
3575 FILE *fp;
3576
3578 joinpath(filename, filename_len, homedir, suffix);
3579
3580#ifdef APPEND_PID
3581 snprintf(filename + strlen(filename), filename_len - strlen(filename), ".%ld", (long) process_id);
3582#endif /* APPEND_PID */
3583
3584 if ((fp = fopen(filename, "w")) == NULL) {
3585 perror_message(_(txt_cannot_open), filename);
3586 return NULL;
3587 }
3588
3589#ifdef HAVE_FCHMOD
3590 fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
3591#else
3592# ifdef HAVE_CHMOD
3593 chmod(filename, (mode_t) (S_IRUSR|S_IWUSR));
3594# endif /* HAVE_CHMOD */
3595#endif /* HAVE_FCHMOD */
3596
3597 if ((INTERACTIVE_NONE == tinrc.interactive_mailer) || (INTERACTIVE_WITH_HEADERS == tinrc.interactive_mailer)) { /* tin should include headers for editing */
3598 char from_buf[HEADER_LEN];
3599 char *from_address;
3600
3602 from_address = curr_group->attribute->from;
3603 else /* i.e. called from select.c without any groups */
3604 from_address = tinrc.mail_address;
3605
3606 if ((from_address == NULL) || !strlen(from_address)) {
3607 get_from_name(from_buf, (struct t_group *) 0);
3608 from_address = &from_buf[0];
3609 } /* from_address is now always a valid pointer to a string */
3610
3611 if (strlen(from_address))
3612 msg_add_header("From", from_address);
3613
3614 msg_add_header("To", to);
3615 msg_add_header("Subject", subject);
3616
3617 if (*reply_to)
3618 msg_add_header("Reply-To", reply_to);
3619
3620 /*
3621 * Only add own address if it is not already there.
3622 *
3623 * Note: get_recipients() strips out duplicated addresses later, but
3624 * only for displaying; the MTA has to deal with it. They shouldn't be
3625 * put in the file in the first place, so we don't do it.
3626 */
3627 if (!address_in_list(to, strlen(from_address) ? from_address : userid)) {
3629 msg_add_header("Cc", strlen(from_address) ? from_address : userid);
3630
3632 msg_add_header("Bcc", strlen(from_address) ? from_address : userid);
3633 }
3634
3637
3640
3641 if (extra_hdrs) {
3642 /*
3643 * Write Message-ID as In-Reply-To to the mail
3644 */
3645 msg_add_header("In-Reply-To", extra_hdrs->messageid);
3646
3647 /*
3648 * Rewrite Newsgroups: as X-Newsgroups: as RFC 822 doesn't define it.
3649 */
3650 strip_double_ngs(extra_hdrs->newsgroups);
3651 msg_add_header("X-Newsgroups", extra_hdrs->newsgroups);
3652 }
3653
3656 }
3659
3660 return fp;
3661}
3662
3663
3664/*
3665 * Handle editing/spellcheck/PGP etc., operations on a mail article
3666 * Submit/abort the article as required and return POSTED_{NONE,REDRAW,OK}
3667 * Replaces core of mail_to_someone(), mail_bug_report(), mail_to_author()
3668 */
3669static int
3671 const char *filename, /* Temp. filename being used */
3672 t_function func, /* default function */
3673 char *subject,
3674 const char *groupname, /* Newsgroup we are posting from */
3675 const char *prompt, /* If set, used for final query before posting */
3676 FILE *articlefp)
3677{
3678 FILE *fp;
3679 int ret = POSTED_NONE;
3680 long artchanged;
3681 struct t_header hdr;
3682 struct t_group *group = (struct t_group *) 0;
3683 t_bool is_changed = FALSE;
3684#ifdef HAVE_PGP_GPG
3685 char mail_to[HEADER_LEN];
3686#endif /* HAVE_PGP_GPG */
3687
3688 if (groupname)
3689 group = group_find(groupname, FALSE);
3690
3691 forever {
3692 switch (func) {
3693 case POST_EDIT:
3694 artchanged = file_mtime(filename);
3695
3696 if (!(invoke_editor(filename, start_line_offset, group)))
3697 return ret;
3698
3699 ret = POSTED_REDRAW;
3700 if (((artchanged == file_mtime(filename)) && (prompt_yn(_(txt_prompt_unchanged_mail), TRUE) > 0)) || (file_size(filename) <= 0L)) {
3701 clear_message();
3702 return ret;
3703 }
3704
3705 if (artchanged != file_mtime(filename))
3706 is_changed = TRUE;
3707
3708 if (!(fp = fopen(filename, "r"))) { /* Oops */
3709 clear_message();
3710 return ret;
3711 }
3712 parse_rfc822_headers(&hdr, fp, NULL);
3713 fclose(fp);
3714 if (hdr.subj) {
3715 strncpy(subject, hdr.subj, HEADER_LEN - 1);
3716 subject[HEADER_LEN - 1] = '\0';
3717 } else
3719 if (!hdr.to && !hdr.cc && !hdr.bcc)
3722 break;
3723
3724#ifdef HAVE_ISPELL
3725 case POST_ISPELL:
3726 invoke_ispell(filename, group);
3727/* ret = POSTED_REDRAW; TODO: is this needed, not that REDRAW does not imply OK */
3728 break;
3729#endif /* HAVE_ISPELL */
3730
3731#ifdef HAVE_PGP_GPG
3732 case POST_PGP:
3733 if (!(fp = fopen(filename, "r"))) { /* Oops */
3734 clear_message();
3735 return ret;
3736 }
3737 parse_rfc822_headers(&hdr, fp, NULL);
3738 fclose(fp);
3739 if (get_recipients(&hdr, mail_to, sizeof(mail_to) - 1))
3740 invoke_pgp_mail(filename, mail_to);
3741 else
3744 break;
3745#endif /* HAVE_PGP_GPG */
3746
3747 case GLOBAL_QUIT:
3748 case GLOBAL_ABORT:
3749 clear_message();
3750 return ret;
3751
3752 case POST_SEND:
3753 {
3755
3756 if (prompt) {
3757 clear_message();
3758 if (prompt_yn(prompt, FALSE) != 1)
3759 confirm = FALSE;
3760 }
3761
3762 if (confirm && submit_mail_file(filename, group, articlefp, is_changed)) {
3764 return POSTED_OK;
3765 }
3766 }
3767 return ret;
3768 /* NOTREACHED */
3769 break;
3770
3771 default:
3772 break;
3773 }
3774 func = prompt_to_send(subject);
3775 }
3776
3777 /* NOTREACHED */
3778 return ret;
3779}
3780
3781
3782/*
3783 * Add the mail_quote_format string to 'fp', return the number of lines of text
3784 * added to the file
3785 */
3786static int
3788 FILE *fp,
3789 int respnum)
3790{
3791 char *s;
3792 char buf[HEADER_LEN];
3793 int line_count = 0;
3794
3795 if (strfquote(CURR_GROUP.name, respnum, buf, sizeof(buf), tinrc.mail_quote_format)) {
3796 fprintf(fp, "%s\n", buf);
3797 line_count++;
3798
3799 for (s = buf; *s; s++) {
3800 if (*s == '\n')
3801 ++line_count;
3802 }
3803 }
3804 return line_count;
3805}
3806
3807
3808/*
3809 * Return a POSTED_* code
3810 */
3811int
3813 const char *address,
3814 t_bool confirm_to_mail,
3815 t_openartinfo *artinfo,
3816 const struct t_group *group)
3817{
3818 FILE *fp;
3819 char nam[PATH_LEN];
3820 char subject[HEADER_LEN];
3821 int ret_code = POSTED_NONE;
3822 struct t_header note_h = artinfo->hdr;
3823 t_bool mime_forward = group->attribute->mime_forward;
3825
3826 clear_message();
3827 snprintf(subject, sizeof(subject), "(fwd) %s\n", note_h.subj);
3828
3829 /*
3830 * don't add extra headers in the mail_to_someone() case as we include
3831 * the full original headers in either the body of the mail or a separate
3832 * message/rfc822 MIME part.
3833 */
3834 if ((fp = create_mail_headers(nam, sizeof(nam), TIN_LETTER_NAME, address, subject, NULL)) == NULL)
3835 return ret_code;
3836
3837 /*
3838 * TODO: This is an undocumented hack!
3839 * in the !mime_forward case we should get the charset of each part
3840 * and convert it to the local one (as this is also needed for the
3841 * interactive_mailer case).
3842 */
3843 if (note_h.ext->type == TYPE_MULTIPART)
3844 mime_forward = TRUE; /* force mime_forward for multipart articles */
3845
3846 if (!mime_forward || tinrc.interactive_mailer != INTERACTIVE_NONE) {
3847 rewind(artinfo->raw);
3848 fprintf(fp, "%s", _(txt_forwarded));
3849
3850 if (!note_h.mime)
3851 copy_fp(artinfo->raw, fp);
3852 else {
3853 const char *charset;
3854 char *line, *buff = my_malloc(LEN);
3855 size_t l, last = LEN;
3856 t_bool in_head = TRUE;
3857
3858 /* intentionally no undeclared_charset support here! */
3859 if (!(charset = get_param(note_h.ext->params, "charset")))
3860 charset = "US-ASCII";
3861
3862 while ((line = tin_fgets(artinfo->raw, FALSE)) != NULL) {
3863 if (*line == '\0')
3864 in_head = FALSE;
3865 l = strlen(line) * 4 + 4; /* should suffice for -> UTF-8 */
3866 if (l > last) { /* realloc if needed */
3867 buff = my_realloc(buff, l);
3868 last = l;
3869 }
3870 strcpy(buff, line);
3871 if (!in_head) /* just convert body */
3872 process_charsets(&buff, &l, charset, tinrc.mm_local_charset, FALSE);
3873 strcat(buff, "\n");
3874 fwrite(buff, 1, strlen(buff), fp);
3875 }
3876 free(buff);
3877 }
3878 fprintf(fp, "%s", _(txt_forwarded_end));
3879 }
3880
3883
3884 fclose(fp);
3885
3886 if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader */
3887 char buf[HEADER_LEN];
3888 char *p;
3889
3891 subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
3892 p = my_strdup(address); /* FIXME: strfmailer() won't take const arg 3 */
3893 strfmailer(mailer, subject, p, nam, buf, sizeof(buf), tinrc.mailer_format);
3894 free(p);
3895 if (invoke_cmd(buf))
3897 } else {
3898 if (confirm_to_mail)
3899 func = prompt_to_send(subject);
3900 ret_code = mail_loop(nam, func, subject, group->name, NULL, mime_forward ? artinfo->raw : NULL);
3901 }
3902
3904 unlink(nam);
3905
3906 return ret_code;
3907}
3908
3909
3910t_bool
3912 void) /* FIXME: return value is always ignored */
3913{
3914 FILE *fp;
3915 const char *domain;
3916 char buf[LEN], nam[PATH_LEN];
3917 char tmesg[LEN];
3918 char subject[HEADER_LEN];
3920
3922 snprintf(subject, sizeof(subject), "BUG REPORT %s\n", page_header);
3923
3924 if ((fp = create_mail_headers(nam, sizeof(nam), TIN_BUGREPORT_NAME, bug_addr, subject, NULL)) == NULL)
3925 return FALSE;
3926
3928#if defined(HAVE_SYS_UTSNAME_H) && defined(HAVE_UNAME)
3929# ifdef _AIX
3930 fprintf(fp, "BOX1 : %s %s.%s", system_info.sysname, system_info.version, system_info.release);
3931# else
3932# if defined(SEIUX) || defined(__riscos)
3933/*
3934 * #if defined(host_mips) && defined(MIPSEB)
3935 * #if defined(SYSTYPE_SYSV) || defined(SYSTYPE_SVR4) || defined(SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
3936 * RISC/os
3937 * #endif
3938 * #endif
3939 */
3940 fprintf(fp, "BOX1 : %s %s", system_info.version, system_info.release);
3941# else
3942 fprintf(fp, "BOX1 : %s %s (%s)", system_info.sysname, system_info.release, system_info.machine);
3943# endif /* SEIUX || __riscos */
3944# endif /* _AIX */
3945#else
3946 fprintf(fp, "BOX1 : Please enter the following information: Machine+OS");
3947#endif /* HAVE_SYS_UTSNAME_H && HAVE_UNAME */
3948
3949#ifdef DOMAIN_NAME
3950 domain = DOMAIN_NAME;
3951#else
3952 domain = "";
3953#endif /* DOMAIN_NAME */
3954
3955 fprintf(fp, "\nCFG1 : active=%d, arts=%d, reread=%d, nntp_xover=%s\n",
3960 fprintf(fp, "CFG2 : debug=%d, threading=%d\n", debug, tinrc.thread_articles);
3961 fprintf(fp, "CFG3 : domain=[%s]\n", BlankIfNull(domain));
3962 start_line_offset += 4;
3963
3964#ifdef NNTP_ABLE
3965 if (read_news_via_nntp) {
3966 if (*bug_nntpserver1) {
3967 fprintf(fp, "NNTP1: %s\n", bug_nntpserver1);
3969 }
3970 if (*bug_nntpserver2) {
3971 fprintf(fp, "NNTP2: %s\n", bug_nntpserver2);
3973 }
3975 fprintf(fp, "IMPLE: %s\n", nntp_caps.implementation);
3977 }
3978 }
3979#endif /* NNTP_ABLE */
3980
3981 fprintf(fp, "\nPlease enter _detailed_ bug report, gripe or comment:\n\n");
3982 start_line_offset += 2;
3983
3985 msg_write_signature(fp, TRUE, (selmenu.curr == -1) ? NULL : &CURR_GROUP);
3986
3987 fclose(fp);
3988
3989 if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader */
3990 subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
3991 strfmailer(mailer, subject, bug_addr, nam, buf, sizeof(buf), tinrc.mailer_format);
3992 if (invoke_cmd(buf))
3993 ret_code = TRUE;
3994 } else {
3995 snprintf(tmesg, sizeof(tmesg), _(txt_mail_bug_report_confirm), bug_addr);
3996 ret_code = mail_loop(nam, POST_EDIT, subject, NULL, tmesg, NULL) ? TRUE : FALSE;
3997 }
3998
3999 unlink(nam);
4000 return ret_code;
4001}
4002
4003
4004int /* return value is always ignored */
4006 const char *group,
4007 int respnum,
4008 t_bool copy_text,
4009 t_bool with_headers,
4010 t_bool raw_data)
4011{
4012 FILE *fp;
4013 char *p;
4014 char from_addr[HEADER_LEN];
4015 char nam[PATH_LEN];
4016 char subject[HEADER_LEN];
4017 char initials[64];
4018 int ret_code = POSTED_NONE;
4019 int i;
4020 struct t_header note_h = pgart.hdr;
4021
4023 find_reply_to_addr(from_addr, FALSE, &pgart.hdr);
4024
4025 i = gnksa_check_from(from_addr);
4026
4027 /* TODO: make gnksa error level configurable */
4028 if (check_for_spamtrap(from_addr) || (i > GNKSA_OK && i < GNKSA_ILLEGAL_UNQUOTED_CHAR)) {
4029 char keyabort[MAXKEYLEN], keycont[MAXKEYLEN];
4031
4036 switch (func) {
4037 case POST_ABORT:
4038 case GLOBAL_ABORT:
4039 clear_message();
4040 return ret_code;
4041
4042 case POST_CONTINUE:
4043 break;
4044
4045 /* the user wants to continue anyway, so we do nothing special here */
4046 default:
4047 break;
4048 }
4049 }
4050
4051 p = my_strdup(note_h.subj);
4052 snprintf(subject, sizeof(subject), "Re: %s\n", eat_re(p, TRUE));
4053 free(p);
4054
4055 /*
4056 * add extra headers in the mail_to_author() case as we don't include the
4057 * full original headers in the body of the mail
4058 */
4059 if ((fp = create_mail_headers(nam, sizeof(nam), TIN_LETTER_NAME, from_addr, subject, &note_h)) == NULL)
4060 return ret_code;
4061
4062 if (copy_text) {
4063 start_line_offset += add_mail_quote(fp, respnum);
4064 get_initials(&arts[respnum], initials, sizeof(initials) - 1);
4065
4066 if (raw_data) /* rewind raw article if needed */
4067 fseek(pgart.raw, 0L, SEEK_SET);
4068
4069 if (with_headers && raw_data)
4070 copy_body(pgart.raw, fp, tinrc.quote_chars, initials, TRUE);
4071 else {
4072 if (raw_data) { /* raw data && !with_headers */
4073 long offset = 0L;
4074 char buffer[8192];
4075
4076 /* skip headers + header/body separator */
4077 while (fgets(buffer, (int) sizeof(buffer), pgart.raw) != NULL) {
4078 offset = (long) ((size_t) offset + strlen(buffer));
4079 if (buffer[0] == '\n' || buffer[0] == '\r')
4080 break;
4081 }
4082 fseek(pgart.raw, offset, SEEK_SET);
4083 copy_body(pgart.raw, fp, tinrc.quote_chars, initials, TRUE);
4084 } else { /* cooked art */
4086 if (with_headers) {
4087 /*
4088 * unfortunately this includes only those headers
4089 * mentioned in news_headers_to_display as article
4090 * cooking 'hides' all other headers
4091 */
4092 fseek(pgart.cooked, 0L, SEEK_SET);
4093 } else { /* without headers */
4094 i = 0;
4095 while (pgart.cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
4096 i++;
4097 if (i) /* cooked art contained any headers, so skip also the header/body separator */
4098 i++;
4099 fseek(pgart.cooked, pgart.cookl[i].offset, SEEK_SET);
4100 }
4101 copy_body(pgart.cooked, fp, tinrc.quote_chars, initials, FALSE);
4102 }
4103 }
4104 } else /* !copy_text */
4105 fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
4106
4109
4110 fclose(fp);
4111
4112 {
4113 char mail_to[HEADER_LEN];
4114
4115 find_reply_to_addr(mail_to, TRUE, &pgart.hdr);
4116
4117 if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader for reply */
4118 char buf[HEADER_LEN];
4119
4120 subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
4121 strfmailer(mailer, subject, mail_to, nam, buf, sizeof(buf), tinrc.mailer_format);
4122 if (invoke_cmd(buf))
4124 } else
4125 ret_code = mail_loop(nam, POST_EDIT, subject, group, NULL, NULL);
4126
4127 /*
4128 * If interactive_mailer!=NONE and the user changed the subject in his
4129 * mailreader, the entry generated here is wrong, strictly speaking.
4130 * But since we don't have a chance to get the final subject back from
4131 * the mailer I think this is the best solution. -dn, 2000-03-16
4132 */
4133 /*
4134 * same with mail_to, if user changes To: in the editor tin
4135 * doesn't notice it and logs the original value.
4136 */
4137 if (ret_code == POSTED_OK)
4138 update_posted_info_file(mail_to, 'r', subject, ""); /* TODO: update_posted_info_file elsewhere? */
4139 }
4140
4142 unlink(nam);
4143
4144 resize_article(TRUE, &pgart); /* rebreak long lines */
4145
4146 if (raw_data) /* we've been in raw mode */
4147 toggle_raw(group_find(group, FALSE));
4148
4149 return ret_code;
4150}
4151
4152
4153/*
4154 * compare the given e-mail address with a list of components
4155 * in tinrc.spamtrap_warning_addresses
4156 */
4157static t_bool
4159 const char *addr)
4160{
4161 char *env;
4162 char *ptr;
4163 char *tmp;
4164
4165 if (!strlen(tinrc.spamtrap_warning_addresses) || !addr || !*addr)
4166 return FALSE;
4167
4169
4170 while (strlen(tmp)) {
4171 ptr = strchr(tmp, ',');
4172 if (ptr != NULL)
4173 *ptr = '\0';
4174 if (strcasestr(addr, tmp)) {
4175 free(env);
4176 return TRUE;
4177 }
4178 tmp += strlen(tmp);
4179 if (ptr != NULL)
4180 tmp++;
4181 }
4182 free(env);
4183 return FALSE;
4184}
4185
4186
4187static void
4189#ifdef FORGERY
4190 t_bool author,
4191 t_bool use_cache)
4192#else
4193 void)
4194#endif /* FORGERY */
4195{
4196 struct t_header note_h = pgart.hdr;
4197#ifdef FORGERY
4198 static t_bool c_author;
4199
4200 /*
4201 * Cache value for the case when called
4202 * from refresh_post_screen()
4203 */
4204 if (!use_cache)
4205 c_author = author;
4206
4207 if (!c_author) {
4208 my_fprintf(stderr, "%s", _(txt_warn_cancel_forgery));
4209 my_fprintf(stderr, "From: %s\n", BlankIfNull(note_h.from));
4210 } else
4211#endif /* FORGERY */
4212 my_fprintf(stderr, "%s", _(txt_warn_cancel));
4213
4214 my_fprintf(stderr, "Subject: %s\n", BlankIfNull(note_h.subj));
4215 my_fprintf(stderr, "Date: %s\n", BlankIfNull(note_h.date));
4216 my_fprintf(stderr, "Message-ID: %s\n", BlankIfNull(note_h.messageid));
4217 my_fprintf(stderr, "Newsgroups: %s\n", BlankIfNull(note_h.newsgroups));
4218}
4219
4220
4221t_bool
4223 struct t_group *group,
4224 struct t_article *art,
4225 int respnum)
4226{
4227 FILE *fp;
4228 char buf[HEADER_LEN];
4229 char cancel[PATH_LEN];
4230 char from_name[HEADER_LEN];
4231 char a_message_id[HEADER_LEN];
4232#ifdef FORGERY
4233 char line[HEADER_LEN];
4234 t_bool author = TRUE;
4235#endif /* FORGERY */
4236 int init = 1;
4237 int oldraw;
4238 struct t_header note_h = pgart.hdr, hdr;
4241 t_function default_func = POST_CANCEL;
4242
4244
4245 /*
4246 * Check if news / mail / save group
4247 */
4248 if (group->type == GROUP_TYPE_MAIL || group->type == GROUP_TYPE_SAVE) {
4250 return FALSE;
4251 }
4252 get_from_name(from_name, group);
4253#ifdef FORGERY
4254 make_path_header(line);
4255#endif /* FORGERY */
4256
4257#ifdef DEBUG
4258 if (debug & DEBUG_MISC)
4259 error_message(2, "From=[%s] Cancel=[%s]", art->from, from_name);
4260#endif /* DEBUG */
4261
4262 if (!strcasestr(from_name, art->from)) {
4263#ifdef FORGERY
4264 author = FALSE;
4265#else
4267 return redraw_screen;
4268#endif /* FORGERY */
4269 }
4270
4271 {
4272 char *smsg;
4273 char buff[LEN];
4274 char keycancel[MAXKEYLEN], keyquit[MAXKEYLEN], keysupersede[MAXKEYLEN];
4275
4276 snprintf(buff, sizeof(buff), _(txt_cancel_article),
4280
4282 "%s", sized_message(&smsg, buff, art->subject));
4283 free(smsg);
4284
4285 switch (func) {
4286 case POST_CANCEL:
4287 break;
4288
4289 case POST_SUPERSEDE:
4291 return TRUE; /* force screen redraw */
4292
4293 default:
4294 return redraw_screen;
4295 }
4296 }
4297
4298 clear_message();
4299
4300 joinpath(cancel, sizeof(cancel), homedir, TIN_CANCEL_NAME);
4301#ifdef APPEND_PID
4302 snprintf(cancel + strlen(cancel), sizeof(cancel) - strlen(cancel), ".%ld", (long) process_id);
4303#endif /* APPEND_PID */
4304 if ((fp = fopen(cancel, "w")) == NULL) {
4306 return redraw_screen;
4307 }
4308
4309#ifdef HAVE_FCHMOD
4310 fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
4311#else
4312# ifdef HAVE_CHMOD
4313 chmod(cancel, (mode_t) (S_IRUSR|S_IWUSR));
4314# endif /* HAVE_CHMOD */
4315#endif /* HAVE_FCHMOD */
4316
4317#ifdef FORGERY
4318 if (!author) {
4319 char line2[HEADER_LEN];
4320
4321 snprintf(line2, sizeof(line2), "cyberspam!%s", line);
4322 msg_add_header("Path", line2);
4323 msg_add_header("From", from_name);
4324 msg_add_header("Sender", note_h.from);
4325 snprintf(line, sizeof(line), "<cancel.%s", note_h.messageid + 1);
4326 msg_add_header("Message-ID", line);
4327 msg_add_header("X-Cancelled-By", from_name);
4328 /*
4329 * Original Subject is includet in the body but some
4330 * stupid bots like it in the header as well
4331 */
4332 msg_add_header("X-Orig-Subject", note_h.subj);
4333 } else {
4334 msg_add_header("Path", line);
4335 if (art->name)
4336 snprintf(line, sizeof(line), "%s <%s>", art->name, art->from);
4337 else
4338 snprintf(line, sizeof(line), "<%s>", art->from);
4339 msg_add_header("From", line);
4342 }
4343#else
4344 msg_add_header("From", from_name);
4347#endif /* FORGERY */
4348 snprintf(buf, sizeof(buf), "cmsg cancel %s", note_h.messageid);
4349 msg_add_header("Subject", buf);
4350
4351 /*
4352 * remove duplicates from Newsgroups header
4353 */
4355 msg_add_header("Newsgroups", note_h.newsgroups);
4356 if (group->attribute->prompt_followupto)
4357 msg_add_header("Followup-To", "");
4358 snprintf(buf, sizeof(buf), "cancel %s", note_h.messageid);
4359 msg_add_header("Control", buf);
4360
4361 /* TODO: does this catch x-posts to moderated groups? */
4362 if (group->moderated == 'm')
4363 msg_add_header("Approved", from_name);
4364
4365 if (group->attribute->organization != NULL)
4366 msg_add_header("Organization", random_organization(group->attribute->organization));
4367
4368 if (note_h.distrib)
4369 msg_add_header("Distribution", note_h.distrib);
4370 else if (*my_distribution)
4371 msg_add_header("Distribution", my_distribution);
4372
4373 /* some ppl. like X-Headers: in cancels */
4375
4378
4379#ifdef FORGERY
4380 if (author) {
4381 fprintf(fp, "%s", txt_article_cancelled);
4383 } else {
4384 rewind(pgart.raw);
4385 copy_fp(pgart.raw, fp);
4386 }
4387 fclose(fp);
4388 invoke_editor(cancel, start_line_offset, group);
4389#else
4390 fprintf(fp, "%s", txt_article_cancelled);
4392 fclose(fp);
4393#endif /* FORGERY */
4394
4396 oldraw = RawState();
4398
4399#ifdef FORGERY
4400 show_cancel_info(author, FALSE);
4401#else
4403#endif /* FORGERY */
4404 Raw(oldraw);
4405
4406 if (!(fp = fopen(cancel, "r"))) {
4407 /* Oops */
4408 unlink(cancel);
4409 clear_message();
4410 return redraw_screen;
4411 }
4412 parse_rfc822_headers(&hdr, fp, NULL);
4413 fclose(fp);
4414
4415 forever {
4416 {
4417 char *smsg;
4418 char buff[LEN];
4419 char keycancel[MAXKEYLEN], keyedit[MAXKEYLEN], keyquit[MAXKEYLEN];
4420 int save_signal_context = signal_context;
4421
4422 snprintf(buff, sizeof(buff), _(txt_quit_cancel),
4426
4428 func = prompt_slk_response(default_func, post_cancel_keys, "%s", sized_message(&smsg, buff, note_h.subj));
4429 signal_context = save_signal_context;
4430 free(smsg);
4431 }
4432
4433 switch (func) {
4434 case POST_EDIT:
4436 invoke_editor(cancel, start_line_offset, group);
4437 if (!(fp = fopen(cancel, "r"))) {
4438 /* Oops */
4439 unlink(cancel);
4440 clear_message();
4441 return redraw_screen;
4442 }
4443 parse_rfc822_headers(&hdr, fp, NULL);
4444 fclose(fp);
4445 break;
4446
4447 case POST_CANCEL:
4449 if (submit_news_file(cancel, group, a_message_id)) {
4451 if (hdr.subj)
4452 update_posted_info_file(group->name, 'd', hdr.subj, a_message_id);
4453 else
4455 unlink(cancel);
4457 return redraw_screen;
4458 }
4459 break;
4460
4461 case GLOBAL_QUIT:
4462 case GLOBAL_ABORT:
4463 unlink(cancel);
4464 clear_message();
4466 return redraw_screen;
4467 /* NOTREACHED */
4468 break;
4469
4470 default:
4471 break;
4472 }
4473 }
4474 /* NOTREACHED */
4475 return redraw_screen;
4476}
4477
4478
4479#define FromSameUser (strcasestr(from_name, arts[respnum].from))
4480#ifndef FORGERY
4481# define NotSuperseding (!supersede || (!FromSameUser) || art_type != GROUP_TYPE_NEWS)
4482# define Superseding (supersede && FromSameUser && art_type == GROUP_TYPE_NEWS)
4483#else
4484# define NotSuperseding (!supersede || art_type != GROUP_TYPE_NEWS)
4485# define Superseding (supersede && art_type == GROUP_TYPE_NEWS)
4486#endif /* !FORGERY */
4487
4488/*
4489 * Repost an already existing article to another group (ie. local group)
4490 */
4491int
4493 const char *groupname,
4494 int respnum,
4496 t_openartinfo *artinfo)
4497{
4498 FILE *fp;
4499 char buf[HEADER_LEN];
4500 char from_name[HEADER_LEN];
4501 char full_name[128];
4502 char user_name[128];
4503 int art_type = GROUP_TYPE_NEWS;
4504 int ret_code = POSTED_NONE;
4505 struct t_group *group;
4506 struct t_header note_h = artinfo->hdr;
4507 t_bool force_command = FALSE;
4508#ifdef FORGERY
4509 char line[HEADER_LEN];
4510#endif /* FORGERY */
4511 t_function func, default_func = GLOBAL_POST;
4512
4514
4515 /*
4516 * remove duplicates from Newsgroups header
4517 */
4519
4520 /*
4521 * Check if any of the newsgroups are moderated.
4522 */
4523 if ((group = check_moderated(groupname, &art_type, _(txt_art_not_posted))) == NULL)
4524 return ret_code;
4525
4526 /*
4527 * check for GROUP_TYPE_MAIL
4528 */
4529 if (group->attribute->mailing_list)
4530 art_type = GROUP_TYPE_MAIL;
4531
4532 if (art_type == GROUP_TYPE_MAIL && supersede) {
4533 error_message(3, _("Can't supersede in mailgroups, try repost instead.")); /* TODO: -> lang.c */
4534 return ret_code;
4535 }
4536
4537 if ((fp = fopen(article_name, "w")) == NULL) {
4539 return ret_code;
4540 }
4541#ifdef HAVE_FCHMOD
4542 fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
4543#else
4544# ifdef HAVE_CHMOD
4545 chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
4546# endif /* HAVE_CHMOD */
4547#endif /* HAVE_FCHMOD */
4548
4549 get_from_name(from_name, group);
4550 get_user_info(user_name, full_name);
4551
4552 if (Superseding) {
4553
4554#ifdef FORGERY
4555 make_path_header(line);
4556 msg_add_header("Path", line);
4557
4558 msg_add_header("From", (note_h.from ? note_h.from : from_name));
4559
4560 find_reply_to_addr(line, FALSE, &artinfo->hdr);
4561 if (*line)
4562 msg_add_header("Reply-To", line);
4563
4564 msg_add_header("X-Superseded-By", from_name);
4565
4566 if (note_h.org)
4567 msg_add_header("Organization", note_h.org);
4568
4569 snprintf(line, sizeof(line), "<supersede.%s", note_h.messageid + 1);
4570 msg_add_header("Message-ID", line);
4571 if (FromSameUser) { /* just add can-key for own articles */
4573 }
4574#else
4575 msg_add_header("From", from_name);
4576 if (*reply_to)
4577 msg_add_header("Reply-To", reply_to);
4580#endif /* FORGERY */
4581 msg_add_header("Supersedes", note_h.messageid);
4582
4583 if (note_h.followup)
4584 msg_add_header("Followup-To", note_h.followup);
4585
4586 if (note_h.keywords)
4587 msg_add_header("Keywords", note_h.keywords);
4588
4589 if (note_h.summary)
4590 msg_add_header("Summary", note_h.summary);
4591
4592 if (note_h.distrib)
4593 msg_add_header("Distribution", note_h.distrib);
4594 } else { /* !Superseding */
4595 msg_add_header("From", from_name);
4596 if (*reply_to)
4597 msg_add_header("Reply-To", reply_to);
4598 }
4599 msg_add_header("Subject", note_h.subj);
4600
4601 if (group->attribute->mailing_list)
4602 msg_add_header("To", group->attribute->mailing_list);
4603 else {
4604 msg_add_header("Newsgroups", groupname);
4606 }
4607
4608 if (note_h.references) {
4610 msg_add_header("References", buf);
4611 }
4612 if (NotSuperseding) {
4613 if (group->attribute->organization != NULL)
4614 msg_add_header("Organization", random_organization(group->attribute->organization));
4615 else if (*default_organization)
4617
4618 if (*reply_to)
4619 msg_add_header("Reply-To", reply_to);
4620
4621 if (*my_distribution)
4622 msg_add_header("Distribution", my_distribution);
4623
4624 } else {
4625 if (note_h.org)
4626 msg_add_header("Organization", note_h.org);
4627 else {
4628 if (group->attribute->organization != NULL)
4629 msg_add_header("Organization", random_organization(group->attribute->organization));
4630 else if (*default_organization)
4632 }
4633 }
4634
4635 /*
4636 * some ppl. like X-Headers: in reposts
4637 * X-Headers got lost on supersede, re-add
4638 */
4640
4643
4644 if (NotSuperseding) {
4645 /*
4646 * all string lengths are calculated to a maximum line length
4647 * of 76 characters, this should look ok (sven@tin.org)
4648 *
4649 * TODO : use strunc() on note_h.subj?
4650 */
4651 fprintf(fp, "[ %-*s ]\n", (int) (72 + strlen(_(txt_article_reposted)) - (size_t) strwidth(_(txt_article_reposted))), _(txt_article_reposted));
4652 fprintf(fp, "[ From: %-*s ]\n", (int) (66 + strlen(note_h.from) - (size_t) strwidth(note_h.from)), note_h.from);
4653 fprintf(fp, "[ Subject: %-*s ]\n", (int) (63 + strlen(note_h.subj) - (size_t) strwidth(note_h.subj)), note_h.subj);
4654 fprintf(fp, "[ Newsgroups: %-*s ]\n", (int) (60 + strlen(note_h.newsgroups) - (size_t) strwidth(note_h.newsgroups)), note_h.newsgroups);
4655 if (note_h.messageid)
4656 fprintf(fp, "[ Message-ID: %-60s ]\n\n", note_h.messageid);
4657 } else /* don't break long lines if superseeding. TODO: what about uu/mime-parts? */
4658 resize_article(FALSE, artinfo);
4659
4660 {
4661 int i = 0;
4662
4663 while (artinfo->cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
4664 i++;
4665 if (i) /* cooked art contained any headers, so skip also the header/body separator */
4666 i++;
4667 fseek(artinfo->cooked, artinfo->cookl[i].offset, SEEK_SET);
4668 copy_fp(artinfo->cooked, fp);
4669 }
4670
4671 /* only append signature when NOT superseding own articles */
4673 msg_write_signature(fp, FALSE, group);
4674
4675 fclose(fp);
4676
4677 /*
4678 * on supersede change default-key
4679 *
4680 * FIXME: this is only useful when entering the editor.
4681 * After leaving the editor it should be GLOBAL_POST
4682 */
4683 if (Superseding) {
4684 default_func = POST_EDIT;
4685 force_command = TRUE;
4686 /* rebreak long-lines that we don't grabble screen if user aborts posting ... */
4687 resize_article(TRUE, artinfo);
4688 }
4689
4690 func = default_func;
4691 if (!force_command) {
4692 char *smsg;
4693 char buff[LEN];
4694 char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
4695 char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
4696 char keymenu[MAXKEYLEN];
4697#ifdef HAVE_ISPELL
4698 char keyispell[MAXKEYLEN];
4699#endif /* HAVE_ISPELL */
4700#ifdef HAVE_PGP_GPG
4701 char keypgp[MAXKEYLEN];
4702#endif /* HAVE_PGP_GPG */
4703
4704#if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
4705 snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4708 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
4709 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
4713#else
4714# ifdef HAVE_ISPELL
4715 snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4718 PrintFuncKey(keyispell, POST_ISPELL, post_post_keys),
4722# else
4723# ifdef HAVE_PGP_GPG
4724 snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4727 PrintFuncKey(keypgp, POST_PGP, post_post_keys),
4731# else
4732 snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4738# endif /* HAVE_PGP_GPG */
4739# endif /* HAVE_ISPELL */
4740#endif /* HAVE_ISPELL && HAVE_PGP_GPG */
4741
4742 func = prompt_slk_response(default_func, post_post_keys,
4743 "%s", sized_message(&smsg, buff, note_h.subj));
4744 free(smsg);
4745 }