tin  2.4.5
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.4.5.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 : 2020-09-23
7  * Notes : mail/post/replyto/followup/repost & cancel articles
8  *
9  * Copyright (c) 1991-2021 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 
122 static int start_line_offset = 1; /* used by invoke_editor for line no. */
123 
124 char bug_addr[LEN]; /* address to add send bug reports to */
125 static char my_distribution[LEN]; /* Distribution: */
126 static char reply_to[LEN]; /* Reply-To: address */
127 
128 static struct msg_header {
129  char *name;
130  char *text;
132 
133 
134 /*
135  * Local prototypes
136  */
137 static FILE *create_mail_headers(char *filename, size_t filename_len, const char *suffix, const char *to, const char *subject, struct t_header *extra_hdrs);
138 static char **build_nglist(char *ngs_list, int *ngcnt);
139 static char **split_address_list(const char *addresses, unsigned int *cnt);
140 static int add_mail_quote(FILE *fp, int respnum);
141 static int append_mail(const char *the_article, const char *addr, const char *the_mailbox);
142 static 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);
143 static int mail_loop(const char *filename, t_function func, char *subject, const char *groupname, const char *prompt, FILE *articlefp);
144 static int msg_add_x_body(FILE *fp_out, const char *body);
145 static int msg_write_headers(FILE *fp);
146 static int post_loop(int type, struct t_group *group, t_function func, const char *posting_msg, int art_type, int offset);
147 static unsigned int get_recipients(struct t_header *hdr, char *buf, size_t buflen);
148 static size_t skip_id(const char *id);
149 static struct t_group *check_moderated(const char *groups, int *art_type, const char *failmsg);
150 static t_bool address_in_list(const char *addresses, const char *address);
151 static t_bool backup_article(const char *the_article);
152 static t_bool check_for_spamtrap(const char *addr);
153 static t_bool create_normal_article_headers(struct t_group *group, const char *newsgroups, int art_type);
154 static t_bool damaged_id(const char *id);
155 static t_bool fetch_postponed_article(const char tmp_file[], char subject[], char newsgroups[]);
156 static t_bool insert_from_header(const char *infile);
157 static t_bool is_crosspost(const char *xref);
158 static t_bool must_include(const char *id);
159 static t_bool repair_article(t_function *result, struct t_group *group);
160 static t_bool stripped_double_ngs(char **newsgroups, int *ngcnt);
161 static t_bool submit_mail_file(const char *file, struct t_group *group, FILE *articlefp, t_bool include_text);
162 static t_function prompt_rejected(void);
163 static t_function prompt_to_send(const char *subject);
164 static void add_headers(const char *infile, const char *a_message_id);
165 static void appendid(char **where, const char **what);
166 static void find_reply_to_addr(char *from_addr, t_bool parse, struct t_header *hdr);
167 static void join_references(char *buffer, const char *oldrefs, const char *newref);
168 static void msg_add_header(const char *name, const char *text);
169 static void msg_add_x_headers(const char *headers);
170 static void msg_free_headers(void);
171 static void msg_init_headers(void);
172 static void post_postponed_article(int ask, const char *subject, const char *newsgroups);
173 static void postpone_article(const char *the_article);
174 static void setup_check_article_screen(int *init);
175 static void show_followup_info(void);
176 static void strip_double_ngs(char *ngs_list);
177 static void update_active_after_posting(char *newsgroups);
178 static void update_posted_info_file(const char *group, int action, const char *subj, const char *a_message_id);
179 #ifdef FORGERY
180  static void make_path_header(char *line);
181  static void show_cancel_info(t_bool author, t_bool use_cache);
182 #else
183  static void show_cancel_info(void);
184 #endif /* FORGERY */
185 #ifdef EVIL_INSIDE
186  static const char *build_messageid(void);
187  static char *radix32(unsigned long int num);
188 #endif /* EVIL_INSIDE */
189 #ifdef USE_CANLOCK
190  static char *build_cankey(const char *messageid, const char *secret);
191  static cl_hash_version get_cancel_lock_algo(void);
192 #endif /* USE_CANLOCK */
193 
194 
195 static t_function
197  const char *subject)
198 {
199  char *smsg;
200  char buf[LEN];
201  char keyedit[MAXKEYLEN];
202  char keyquit[MAXKEYLEN];
203  char keysend[MAXKEYLEN];
204 #ifdef HAVE_ISPELL
205  char keyispell[MAXKEYLEN];
206 #endif /* HAVE_ISPELL */
207 #ifdef HAVE_PGP_GPG
208  char keypgp[MAXKEYLEN];
209 #endif /* HAVE_PGP_GPG */
211 
212 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
213  snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
216  printascii(keyispell, func_to_key(POST_ISPELL, post_send_keys)),
217  printascii(keypgp, func_to_key(POST_PGP, post_send_keys)),
219 #else
220 # ifdef HAVE_ISPELL
221  snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
224  printascii(keyispell, func_to_key(POST_ISPELL, post_send_keys)),
226 # else
227 # ifdef HAVE_PGP_GPG
228  snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
231  printascii(keypgp, func_to_key(POST_PGP, post_send_keys)),
233 # else
234  snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
238 # endif /* HAVE_PGP_GPG */
239 # endif /* HAVE_ISPELL */
240 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
241 
243  sized_message(&smsg, buf, subject));
244  free(smsg);
245  return func;
246 }
247 
248 
249 static t_function
251  void)
252 {
253  char keyedit[MAXKEYLEN], keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
254 
255 /* FIXME (what does this mean?) fix screen pos. */
256  Raw(FALSE);
257  /* TODO: replace hard coded key-name in txt_post_error_ask_postpone */
258  my_fprintf(stderr, "\n\n%s\n\n", _(txt_post_error_ask_postpone));
259  my_fflush(stderr);
260  Raw(TRUE);
261 
267 }
268 
269 
270 /*
271  * Set up posting specific environment
272  */
273 void
275  void)
276 {
277  char *ptr;
278 
279  /*
280  * check environment for REPLYTO
281  */
282  reply_to[0] = '\0';
283  if ((ptr = getenv("REPLYTO")) != NULL)
284  my_strncpy(reply_to, ptr, sizeof(reply_to) - 1);
285 
286  /*
287  * check environment for DISTRIBUTION
288  */
289  my_distribution[0] = '\0';
290  if ((ptr = getenv("DISTRIBUTION")) != NULL)
291  my_strncpy(my_distribution, ptr, sizeof(my_distribution) - 1);
292 }
293 
294 
295 /*
296  * TODO: add p'o'stpone function here? would be nice but difficult
297  * as the postpone fetcher looks for articles with correct headers
298  */
299 static t_bool
301  t_function *result,
302  struct t_group *group)
303 {
304  char keyedit[MAXKEYLEN], keymenu[MAXKEYLEN], keyquit[MAXKEYLEN];
306 
311 
312  *result = func;
313  if (func == POST_EDIT) {
315  return TRUE;
316  } else if (func == GLOBAL_OPTION_MENU) {
318  return TRUE;
319  }
320  return FALSE;
321 }
322 
323 
324 /*
325  * make a backup copy of ~/TIN_ARTICLE_NAME, this is necessary since
326  * submit_news_file adds headers, does q-p conversion etc
327  * TODO: why not use BACKUP_FILE_EXT like in misc.c?
328  */
329 char *
331  const char *the_article)
332 {
333  static char name[PATH_LEN];
334 
335  snprintf(name, sizeof(name), "%s.bak", the_article);
336  return name;
337 }
338 
339 
340 static t_bool
342  const char *the_article)
343 {
344  return backup_file(the_article, backup_article_name(the_article));
345 }
346 
347 
348 static void
350  void)
351 {
352  int i;
353 
354  for (i = 0; i < MAX_MSG_HEADERS; i++) {
355  msg_headers[i].name = NULL;
356  msg_headers[i].text = NULL;
357  }
358 }
359 
360 
361 static void
363  void)
364 {
365  int i;
366 
367  for (i = 0; i < MAX_MSG_HEADERS; i++) {
369  FreeAndNull(msg_headers[i].text);
370  }
371 }
372 
373 
374 static void
376  const char *name,
377  const char *text)
378 {
379  const char *p;
380  char *ptr;
381  char *new_name;
382  char *new_text = NULL;
383  int i;
384  t_bool done = FALSE;
385 
386  if (name) {
387  /*
388  * Remove : if one is attached to name
389  */
390  new_name = my_strdup(name);
391  ptr = strchr(new_name, ':');
392  if (ptr)
393  *ptr = '\0';
394 
395  /*
396  * Check if header already exists and if update text
397  */
398  for (i = 0; i < MAX_MSG_HEADERS && msg_headers[i].name; i++) {
399  if (STRCMPEQ(msg_headers[i].name, new_name)) {
400  FreeAndNull(msg_headers[i].text);
401  if (text) {
402  for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
403  ;
404  new_text = my_strdup(p);
405  ptr = strrchr(new_text, '\n');
406  if (ptr)
407  *ptr = '\0';
408 
409  msg_headers[i].text = my_strdup(new_text);
410  FreeIfNeeded(new_text);
411  }
412  done = TRUE;
413  }
414  }
415 
416  /*
417  * if header does not exist then add it
418  */
419  if (i < MAX_MSG_HEADERS && !(done || msg_headers[i].name)) {
420  msg_headers[i].name = my_strdup(new_name);
421  if (text) {
422  for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
423  ;
424  new_text = my_strdup(p);
425  ptr = strrchr(new_text, '\n');
426  if (ptr)
427  *ptr = '\0';
428 
429  msg_headers[i].text = my_strdup(new_text);
430  FreeIfNeeded(new_text);
431  }
432  }
433  FreeIfNeeded(new_name);
434  }
435 }
436 
437 
438 static int
440  FILE *fp)
441 {
442  int i;
443  int wrote = 1;
444  char *p;
445 
446  for (i = 0; i < MAX_MSG_HEADERS; i++) {
447  if (msg_headers[i].name) {
448  fprintf(fp, "%s: %s\n", msg_headers[i].name, BlankIfNull(msg_headers[i].text));
449  wrote++;
450  if ((p = msg_headers[i].text)) {
451  while ((p = strchr(p, '\n'))) {
452  p++;
453  wrote++;
454  }
455  }
456  }
457  }
458  fputc('\n', fp);
459 
460  return wrote;
461 }
462 
463 
464 /* TODO: handle optional Message-ID: field */
465 t_bool
467  void)
468 {
469  FILE *fp;
470  char buf[LEN];
471  int no_of_lines = 0;
472  size_t group_len = 0, i = 0, j, k;
473  struct t_posted *posted;
474 
475  if ((fp = fopen(posted_info_file, "r")) == NULL) {
476  clear_message();
477  return FALSE;
478  }
479 
480  while (fgets(buf, (int) sizeof(buf), fp) != NULL)
481  no_of_lines++;
482 
483  if (!no_of_lines) {
484  fclose(fp);
486  return FALSE;
487  }
488  rewind(fp);
489  posted = my_malloc((no_of_lines + 1) * sizeof(struct t_posted));
490 
491  while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
492  if (buf[0] == '#' || buf[0] == '\n')
493  continue;
494 
495  for (j = 0, k = 0; buf[j] != '|' && buf[j] != '\n'; j++)
496  if (k < sizeof(posted[i].date) - 1)
497  posted[i].date[k++] = buf[j]; /* posted date */
498 
499  if (buf[j] == '\n') {
501  fclose(fp);
502  clear_message();
503  free(posted);
504  return FALSE;
505  }
506  posted[i].date[k] = '\0';
507  posted[i + 1].date[0] = '\0';
508 
509  posted[i].action = buf[++j];
510  j += 2;
511 
512  for (k = 0; buf[j] != '|' && buf[j] != ','; j++) {
513  if (k < sizeof(posted[i].group) - 1)
514  posted[i].group[k++] = buf[j];
515  }
516  if (buf[j] == ',') {
517  while (buf[j] != '|' && buf[j] != '\n')
518  j++;
519 
520  if (k > sizeof(posted[i].group) - 5)
521  k = sizeof(posted[i].group) - 5;
522  posted[i].group[k++] = ',';
523  posted[i].group[k++] = '.';
524  posted[i].group[k++] = '.';
525  posted[i].group[k++] = '.';
526  }
527  posted[i].group[k] = '\0';
528  if (k > group_len)
529  group_len = k;
530  j++;
531 
532  for (k = 0; buf[j] != '\n'; j++) {
533  if (k < sizeof(posted[i].subj) - 1)
534  posted[i].subj[k++] = buf[j];
535  }
536  posted[i].subj[k] = '\0';
537  i++;
538  }
539  posted[i].date[0] = '\0'; /* end-marker for display */
540  fclose(fp);
541 
542  if (!(fp = tmpfile())) {
543  free(posted);
544  return FALSE;
545  }
546  for (; i > 0; i--) {
547  snprintf(buf, sizeof(buf), "%8s %c %-*s %s",
548  posted[i - 1].date, posted[i - 1].action,
549  (int) group_len, posted[i - 1].group, posted[i - 1].subj);
550  fprintf(fp, "%s%s", buf, cCRLF);
551  }
552  free(posted);
554  fclose(fp);
555  info_pager(NULL, NULL, TRUE); /* free mem */
556 
557  return TRUE;
558 }
559 
560 
561 /*
562  * TODO:
563  * - mime-encode subject so we get the right charset (it may be different
564  * in subsequent sessions); update user_posted_messages accordingly.
565  */
566 static void
568  const char *group,
569  int action,
570  const char *subj,
571  const char *a_message_id)
572 {
573  FILE *fp;
574  char *file_tmp;
575  time_t epoch;
576 
577  if (no_write)
578  return;
579 
580  file_tmp = get_tmpfilename(posted_info_file);
581  if (!backup_file(posted_info_file, file_tmp)) {
583  free(file_tmp);
584  return;
585  }
586 
587  if ((fp = fopen(posted_info_file, "a")) != NULL) {
588  int err;
589  char logdate[10];
590 
591  if (time(&epoch) != (time_t) -1) {
592  if (!my_strftime(logdate, sizeof(logdate) - 1, "%d-%m-%y", localtime(&epoch)))
593  strcpy(logdate, "NO DATE");
594  } else
595  strcpy(logdate, "NO DATE");
596 
597  if (*a_message_id) {
598  char *mid = my_strdup(a_message_id);
599 
600  fprintf(fp, "%s|%c|%s|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj), BlankIfNull(str_trim(mid)));
601  free(mid);
602  } else
603  fprintf(fp, "%s|%c|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj));
604 
605  if ((err = ferror(fp)) || fclose(fp)) {
607  rename_file(file_tmp, posted_info_file);
608  if (err) {
609  clearerr(fp);
610  fclose(fp);
611  }
612  } else
613  unlink(file_tmp);
614  } else
615  rename_file(file_tmp, posted_info_file);
616 
617  free(file_tmp);
618 }
619 
620 
621 /*
622  * appends the content of the_article to the_mailbox, with a From_ line of
623  * addr, does mboxo/mboxrd From_ line quoting if needed (!MMDF-style mbox)
624  */
625 static int
627  const char *the_article,
628  const char *addr,
629  const char *the_mailbox)
630 {
631  FILE *fp_in, *fp_out;
632  char *bufp;
633  char buf[LEN];
634  time_t epoch;
635  t_bool mmdf = FALSE;
636  int rval;
637 #ifndef NO_LOCKING
638  int fd;
639  unsigned int retrys = 11; /* maximum lock retrys + 1 */
640 #endif /* !NO_LOCKING */
641 
643  mmdf = TRUE;
644 
645  if ((fp_in = fopen(the_article, "r")) == NULL)
646  return errno;
647 
648  if ((fp_out = fopen(the_mailbox, "a+")) != NULL) {
649 #ifndef NO_LOCKING
650  fd = fileno(fp_out);
651 
652  while ((rval = fd_lock(fd, FALSE)) && --retrys)
653  wait_message(1, _(txt_trying_lock), retrys, the_mailbox);
654 
655  if (!retrys) {
656  wait_message(5, _(txt_error_couldnt_lock), the_mailbox);
657  fclose(fp_out);
658  fclose(fp_in);
659  return rval;
660  }
661  retrys++;
662 
663  while (--retrys && !dot_lock(the_mailbox))
664  wait_message(1, _(txt_trying_dotlock), retrys, the_mailbox);
665 
666  if (!retrys) {
667  wait_message(5, _(txt_error_couldnt_dotlock), the_mailbox);
668  fd_unlock(fd);
669  fclose(fp_out);
670  fclose(fp_in);
671  return ENOENT; /* FIXME! dot_lock() doesn't return more info yet */
672  }
673 #endif /* !NO_LOCKING */
674 
675  if (mmdf)
676  fprintf(fp_out, "%s", MMDFHDRTXT);
677  else {
678  (void) time(&epoch);
679  fprintf(fp_out, "From %s %s", addr, ctime(&epoch));
680  }
681  while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
682  if (!mmdf) { /* moboxo/mboxrd style From_ quoting required */
683  /*
684  * TODO: add Content-Length: header when using MBOXO
685  * so tin actually write MBOXCL instead of MBOXO?
686  */
687  if (tinrc.mailbox_format == 1) { /* MBOXRD */
688  /* mboxrd: quote quoted and plain From_ lines in the body */
689  bufp = buf;
690  while (*bufp == '>')
691  bufp++;
692  if (strncmp(bufp, "From ", 5) == 0)
693  fputc('>', fp_out);
694  } else { /* MBOXO (MBOXCL) */
695  if (strncmp(buf, "From ", 5) == 0)
696  fputc('>', fp_out);
697  }
698  }
699  fputs(buf, fp_out);
700  }
701  print_art_separator_line(fp_out, mmdf);
702 
703  fflush(fp_out);
704 #ifndef NO_LOCKING
705  if ((rval = fd_unlock(fd)) || !dot_unlock(the_mailbox))
706  wait_message(4, _(txt_error_cant_unlock), the_mailbox);
707 #endif /* !NO_LOCKING */
708 
709  fclose(fp_out);
710  } else
711  rval = errno;
712 
713  fclose(fp_in);
714  return rval;
715 }
716 
717 
718 /*
719  * TODO:
720  * - cleanup!!
721  * - check for illegal (8bit) chars in References, X-Face, MIME-Version,
722  * Content-Type, Content-Transfer-Encoding, Content-Disposition, Supersedes
723  * - check for 'illegal' headers: Xref, Injection-Info, (NNTP-Posting-Host,
724  * NNTP-Posting-Date, X-Trace, X-Complaints-To), Date-Received,
725  * Posting-Version, Relay-Version, Also-Control, Article-Names,
726  * Article-Updates, See-Also
727  * - check for special newsgroups: to, ctl, all, control, junk
728  * [RFC 5536 3.1.4]
729  * - check for Supersedes in Control messages [RFC 5536 3.2.3]
730  * - check for 'illegal' distribution: all [RFC 5536 3.2.4]
731  *
732  * Check the article file for correct header syntax and if there
733  * is a blank between the header information and the text.
734  *
735  * Additionally make **group point to one of the groups we are actually posting to.
736  *
737  * 1. Subject header present
738  * 2. Newsgroups header present
739  * From header present
740  * 3. Space after every colon in header
741  * 4. Colon in every header line
742  * 5. Newsgroups line has no spaces, only comma separated
743  * 6. List of newsgroups is presented to user with description
744  * 7. Lines in body that are to long causes a warning to be printed
745  * 8. Group(s) must be listed in the active file
746  * 9. No Sender: header allowed (limit forging) and rejection by
747  * inn servers
748  * 10. Check for charset != US-ASCII when using non-7bit-encoding
749  * 11. Warn if transfer encoding is base64 or quoted-printable and using
750  * external inews
751  * 12. Check that Subject, Newsgroups and if present Followup-To
752  * headers are unique
753  * 13. Display an 'are you sure' message before posting article
754  */
755 #define CA_ERROR_HEADER_LINE_BLANK 0x0000001
756 #define CA_ERROR_MISSING_BODY_SEPARATOR 0x0000002
757 #define CA_ERROR_MISSING_FROM 0x0000004
758 #define CA_ERROR_DUPLICATED_FROM 0x0000008
759 #define CA_ERROR_MISSING_SUBJECT 0x0000010
760 #define CA_ERROR_DUPLICATED_SUBJECT 0x0000020
761 #define CA_ERROR_EMPTY_SUBJECT 0x0000040
762 #define CA_ERROR_MISSING_NEWSGROUPS 0x0000080
763 #define CA_ERROR_DUPLICATED_NEWSGROUPS 0x0000100
764 #define CA_ERROR_EMPTY_NEWSGROUPS 0x0000200
765 #define CA_ERROR_DUPLICATED_FOLLOWUP_TO 0x0000400
766 #define CA_ERROR_BAD_CHARSET 0x0000800
767 #define CA_ERROR_BAD_ENCODING 0x0001000
768 #define CA_ERROR_BAD_MESSAGE_ID 0x0002000
769 #define CA_ERROR_BAD_DATE 0x0004000
770 #define CA_ERROR_BAD_EXPIRES 0x0008000
771 #define CA_ERROR_NEWSGROUPS_NOT_7BIT 0x0010000
772 #define CA_ERROR_FOLLOWUP_TO_NOT_7BIT 0x0020000
773 #define CA_ERROR_DISTRIBUTIOIN_NOT_7BIT 0x0040000
774 #define CA_ERROR_NEWSGROUPS_POSTER 0x0080000
775 #define CA_ERROR_FOLLOWUP_TO_POSTER 0x0100000
776 #ifndef ALLOW_FWS_IN_NEWSGROUPLIST
777 # define CA_ERROR_SPACE_IN_NEWSGROUPS 0x0200000
778 # define CA_ERROR_NEWLINE_IN_NEWSGROUPS 0x0400000
779 # define CA_ERROR_SPACE_IN_FOLLOWUP_TO 0x0800000
780 # define CA_ERROR_NEWLINE_IN_FOLLOWUP_TO 0x1000000
781 #endif /* !ALLOW_FWS_IN_NEWSGROUPLIST */
782 #define CA_WARNING_SPACES_ONLY_SUBJECT 0x000001
783 #define CA_WARNING_RE_WITHOUT_REFERENCES 0x000002
784 #define CA_WARNING_REFERENCES_WITHOUT_RE 0x000004
785 #define CA_WARNING_MULTIPLE_SIGDASHES 0x000008
786 #define CA_WARNING_WRONG_SIGDASHES 0x000010
787 #define CA_WARNING_LONG_SIGNATURE 0x000020
788 #define CA_WARNING_ENCODING_EXTERNAL_INEWS 0x000040
789 #define CA_WARNING_NEWSGROUPS_EXAMPLE 0x000080
790 #define CA_WARNING_FOLLOWUP_TO_EXAMPLE 0x000100
791 #ifdef CHARSET_CONVERSION
792 # define CA_WARNING_CHARSET_CONVERSION 0x000200
793 #endif /* CHARSET_CONVERSION */
794 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
795 # define CA_WARNING_SPACE_IN_NEWSGROUPS 0x000400
796 # define CA_WARNING_NEWLINE_IN_NEWSGROUPS 0x000800
797 # define CA_WARNING_SPACE_IN_FOLLOWUP_TO 0x001000
798 # define CA_WARNING_NEWLINE_IN_FOLLOWUP_TO 0x002000
799 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
800 
801 /*
802  * TODO: cleanup!
803  *
804  * return values:
805  * 0 article ok
806  * 1 article contains errors
807  * 2 article caused warnings
808  */
809 static int
811  const char *the_article,
812  int art_type,
813  struct t_group **group,
814  t_bool art_unchanged,
815  t_bool use_cache)
816 {
817  FILE *fp;
818  char **newsgroups = NULL;
819  char **followupto = NULL;
820  char *line, *cp, *cp2;
821  char *to = NULL;
822  char references[HEADER_LEN];
823  char subject[HEADER_LEN];
824  int cnt = 0;
825  int col, i;
826  int errors = 0;
827  int warnings = 0;
828  int init = 1;
829  int ngcnt = 0, ftngcnt = 0;
830  int oldraw; /* save previous raw state */
831  int saw_sig_dashes = 0;
832  int sig_lines = 0;
833  int found_followup_to_lines = 0;
834  int found_from_lines = 0;
835  int found_newsgroups_lines = 0;
836  int found_subject_lines = 0;
837  int errors_catbp = 0; /* sum of error-codes */
838  int warnings_catbp = 0; /* sum of warning-codes */
839  int must_break_line = 0;
840  struct t_group *psGrp;
841  t_bool end_of_header = FALSE;
842  t_bool got_long_line = FALSE;
843  t_bool saw_references = FALSE;
844  t_bool saw_wrong_sig_dashes = FALSE;
845  t_bool mime_7bit = TRUE;
846  t_bool mime_usascii = FALSE;
847  t_bool contains_8bit = FALSE;
848 #ifdef CHARSET_CONVERSION
849  t_bool charset_conversion_fails = FALSE;
850  int mmnwcharset;
851 #endif /* CHARSET_CONVERSION */
852  static const char *c_article;
853  static int c_art_type;
854  static struct t_group **c_group;
855  static t_bool c_art_unchanged;
856 
857  /*
858  * Cache values for the case when called
859  * from refresh_post_screen()
860  */
861  if (!use_cache) {
862  c_article = the_article;
863  c_art_type = art_type;
864  c_group = group;
865  c_art_unchanged = art_unchanged;
866  }
867 
868 #ifdef CHARSET_CONVERSION
869  mmnwcharset = *c_group ? (*c_group)->attribute->mm_network_charset : tinrc.mm_network_charset;
870 #endif /* CHARSET_CONVERSION */
871 
872  if ((fp = fopen(c_article, "r")) == NULL) {
873  perror_message(_(txt_cannot_open), c_article);
874  return 0;
875  }
876  oldraw = RawState(); /* save state */
877  subject[0] = '\0';
878 
879  /* check the header of the article */
881 
882  while ((line = tin_fgets(fp, TRUE)) != NULL) {
883  cnt++;
884  if (!end_of_header && !strlen(line)) { /* end of header reached */
885  if (cnt == 1)
886  errors_catbp |= CA_ERROR_HEADER_LINE_BLANK;
887  end_of_header = TRUE;
888  break;
889  }
890 
891  for (cp = line; *cp && !contains_8bit; cp++) {
892  if (!isascii(*cp)) {
893  contains_8bit = TRUE;
894  break;
895  }
896  }
897 
898 #ifdef CHARSET_CONVERSION
899  /* are all characters in article contained in network_charset? */
900  if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
901  cp = my_malloc(strlen(line) * 4 + 1);
902  strcpy(cp, line);
903  charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
904  free(cp);
905  }
906 #endif /* CHARSET_CONVERSION */
907 
908  if ((cp = strchr(line, ':')) == NULL) {
909  StartInverse();
910  my_fprintf(stderr, _(txt_error_header_line_colon), cnt, line);
911  EndInverse();
912  my_fflush(stderr);
913  errors++;
914  continue;
915  }
916  if (cp[1] != ' ') {
917  StartInverse();
918  my_fprintf(stderr, _(txt_error_header_line_space), cnt, line);
919  EndInverse();
920  my_fflush(stderr);
921  errors++;
922  }
923 
924  if (cp - line == 7 && !strncasecmp(line, "Subject", 7)) {
925  found_subject_lines++;
926  strncpy(subject, cp + 2, cCOLS - 6);
927  subject[cCOLS - 6] = '\0';
928  }
929 
930 /*
931  * only allow hand supplied Sender in FORGERY case or
932  * with external inews and not HAVE_FASCIST_NEWSADMIN
933  */
934 #ifndef FORGERY
935 # ifdef HAVE_FASCIST_NEWSADMIN
936  if (cp - line == 6 && !strncasecmp(line, "Sender", 6))
937 # else
938  if (!strcasecmp(tinrc.inews_prog, INTERNAL_CMD) && cp - line == 6 && !strncasecmp(line, "Sender", 6))
939 # endif /* HAVE_FASCIST_NEWSADMIN */
940  {
941  StartInverse();
943  EndInverse();
944  my_fflush(stderr);
945  errors++;
946  }
947 #endif /* !FORGERY */
948 
949  if (cp - line == 8 && !strncasecmp(line, "Approved", 8)) {
950  if (tinrc.beginner_level) {
951  /* StartInverse(); */
952  my_fprintf(stderr, "%s", _(txt_error_approved)); /* this is only a Warning: */
953  /* EndInverse(); */
954  my_fflush(stderr);
955 #ifdef HAVE_FASCIST_NEWSADMIN
956  errors++;
957 #else
958  warnings++;
959 #endif /* HAVE_FASCIST_NEWSADMIN */
960  }
961 #ifdef CHARSET_CONVERSION
962  cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
963 #else
964  cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
965 #endif /* CHARSET_CONVERSION */
966  i = gnksa_check_from(cp2 + (cp - line) + 1);
967  if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
968  StartInverse();
969  my_fprintf(stderr, "%s", _(txt_error_bad_approved));
970  my_fprintf(stderr, "%s\n", cp2);
971  my_fprintf(stderr, gnksa_strerror(i), i);
972  EndInverse();
973  my_fflush(stderr);
974 #ifndef FORGERY
975  errors++;
976 #endif /* !FORGERY */
977  }
978  free(cp2);
979  }
980 
981  if (cp - line == 4 && !strncasecmp(line, "From", 4)) {
982  found_from_lines++;
983 #ifdef CHARSET_CONVERSION
984  cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
985 #else
986  cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
987 #endif /* CHARSET_CONVERSION */
988  i = gnksa_check_from(cp2 + (cp - line) + 1);
989  if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
990  StartInverse();
991  my_fprintf(stderr, "%s", _(txt_error_bad_from));
992  my_fprintf(stderr, "%s\n", cp2);
993  my_fprintf(stderr, gnksa_strerror(i), i);
994  EndInverse();
995  my_fflush(stderr);
996 #ifndef FORGERY
997  errors++;
998 #endif /* !FORGERY */
999  }
1000  free(cp2);
1001  }
1002 
1003  if (cp - line == 8 && !strncasecmp(line, "Reply-To", 8)) {
1004 #ifdef CHARSET_CONVERSION
1005  cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1006 #else
1007  cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1008 #endif /* CHARSET_CONVERSION */
1009  i = gnksa_check_from(cp2 + (cp - line) + 1);
1010  if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1011  StartInverse();
1012  my_fprintf(stderr, "%s", _(txt_error_bad_replyto));
1013  my_fprintf(stderr, "%s\n", cp2);
1014  my_fprintf(stderr, gnksa_strerror(i), i);
1015  EndInverse();
1016  my_fflush(stderr);
1017 #ifndef FORGERY
1018  errors++;
1019 #endif /* !FORGERY */
1020  }
1021  free(cp2);
1022  }
1023 
1024  if (cp - line == 2 && !strncasecmp(line, "To", 2)) {
1025 #ifdef CHARSET_CONVERSION
1026  cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
1027 #else
1028  cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
1029 #endif /* CHARSET_CONVERSION */
1030  i = gnksa_check_from(cp2 + (cp - line) + 1);
1031  if (i > GNKSA_OK && i < GNKSA_MISSING_REALNAME) {
1032  StartInverse();
1033  my_fprintf(stderr, "%s", _(txt_error_bad_to));
1034  my_fprintf(stderr, "%s\n", cp2);
1035  my_fprintf(stderr, gnksa_strerror(i), i);
1036  EndInverse();
1037  my_fflush(stderr);
1038 #ifndef FORGERY
1039  errors++;
1040 #endif /* !FORGERY */
1041  }
1042  to = my_strdup(cp2 + (cp - line) + 1);
1043  free(cp2);
1044  }
1045 
1046  if (cp - line == 10 && !strncasecmp(line, "Message-ID", 10)) {
1047 #if 0 /* see comment about "<>" in misc.c:gnksa_split_from() */
1048  char addr[HEADER_LEN], name[HEADER_LEN];
1049  int type;
1050 
1051  i = gnksa_check_from(++cp);
1052  gnksa_split_from(cp, addr, name, &type);
1053  if (((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i)) || !*addr)
1054 #else
1055  i = gnksa_check_from(++cp);
1056  if ((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i))
1057 #endif /* 0 */
1058  {
1059  StartInverse();
1060  my_fprintf(stderr, "%s", _(txt_error_bad_msgidfqdn));
1061  my_fprintf(stderr, "%s\n", line);
1062  my_fprintf(stderr, gnksa_strerror(i), i);
1063  EndInverse();
1064  my_fflush(stderr);
1065 #ifndef FORGERY
1066  errors++;
1067 #endif /* !FORGERY */
1068  }
1069  if (damaged_id(cp))
1070  errors_catbp |= CA_ERROR_BAD_MESSAGE_ID;
1071  }
1072 
1073  if (cp - line == 10 && !strncasecmp(line, "References", 10)) {
1074  for (cp = line + 11; *cp == ' '; cp++)
1075  ;
1076  STRCPY(references, cp);
1077  if (strlen(references))
1078  saw_references = TRUE;
1079  }
1080 
1081  if (cp - line == 4 && !strncasecmp(line, "Date", 4)) {
1082  if ((cp2 = parse_header(line, "Date", FALSE, FALSE, FALSE))) {
1083  if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
1084  errors_catbp |= CA_ERROR_BAD_DATE;
1085  } else {
1086  errors_catbp |= CA_ERROR_BAD_DATE;
1087  }
1088  }
1089 
1090  if (cp - line == 7 && !strncasecmp(line, "Expires", 7)) {
1091  if ((cp2 = parse_header(line, "Expires", FALSE, FALSE, FALSE))) {
1092  if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
1093  errors_catbp |= CA_ERROR_BAD_EXPIRES;
1094  } else {
1095  errors_catbp |= CA_ERROR_BAD_EXPIRES;
1096  }
1097  }
1098 
1099  /*
1100  * TODO: also check for other illegal chars?
1101  * a 'common' error is to use a semicolon instead of a comma.
1102  */
1103  if (cp - line == 10 && !strncasecmp(line, "Newsgroups", 10)) {
1104  found_newsgroups_lines++;
1105  for (cp = line + 11; *cp == ' '; cp++)
1106  ;
1107  if (strchr(cp, ' ') || strchr(cp, '\t')) {
1108 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1109  warnings_catbp |= CA_WARNING_SPACE_IN_NEWSGROUPS;
1110 #else
1111  errors_catbp |= CA_ERROR_SPACE_IN_NEWSGROUPS;
1112 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1113  }
1114  if (strchr(cp, '\n')) {
1115 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1116  warnings_catbp |= CA_WARNING_NEWLINE_IN_NEWSGROUPS;
1117 #else
1118  errors_catbp |= CA_ERROR_NEWLINE_IN_NEWSGROUPS;
1119 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1120  unfold_header(line);
1121  }
1122 
1123  newsgroups = build_nglist(cp, &ngcnt);
1124  if (newsgroups && ngcnt)
1125  (void) stripped_double_ngs(newsgroups, &ngcnt);
1126 
1127  if (!ngcnt)
1128  errors_catbp |= CA_ERROR_EMPTY_NEWSGROUPS;
1129  else {
1130  for (cp = line + 11; *cp; cp++) {
1131  if (!isascii(*cp)) {
1132  errors_catbp |= CA_ERROR_NEWSGROUPS_NOT_7BIT;
1133  break;
1134  }
1135  }
1136  }
1137  { /* check for poster, example, example.* */
1138  char *groups;
1139 
1140  for (cp = line + 11; *cp == ' '; cp++)
1141  ;
1142  cp2 = groups = my_strdup(cp);
1143 
1144  cp = strtok(groups, ",");
1145  do {
1146  if (!strcmp(cp, "poster"))
1147  errors_catbp |= CA_ERROR_NEWSGROUPS_POSTER;
1148  if (!strcmp(cp, "example"))
1149  warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
1150  if (!strncmp(cp, "example.", 8))
1151  warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
1152  /* TODO: also check for to, ctl, all, control, junk */
1153  } while ((cp = strtok(NULL, ",")) != NULL);
1154  free(cp2);
1155  }
1156  }
1157 
1158  if (cp - line == 12 && !strncasecmp(line, "Distribution", 12)) {
1159  for (cp = line + 13; *cp; cp++) {
1160  if (!isascii(*cp)) {
1161  errors_catbp |= CA_ERROR_DISTRIBUTIOIN_NOT_7BIT;
1162  break;
1163  }
1164  }
1165  }
1166 
1167  if (cp - line == 11 && !strncasecmp(line, "Followup-To", 11)) {
1168  for (cp = line + 12; *cp == ' '; cp++)
1169  ;
1170  if (strlen(cp)) /* Followup-To not empty */
1171  found_followup_to_lines++;
1172  if (strchr(cp, ' ') || strchr(cp, '\t')) {
1173 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1174  warnings_catbp |= CA_WARNING_SPACE_IN_FOLLOWUP_TO;
1175 #else
1176  errors_catbp |= CA_ERROR_SPACE_IN_FOLLOWUP_TO;
1177 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1178  }
1179  if (strchr(cp, '\n')) {
1180 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1181  warnings_catbp |= CA_WARNING_NEWLINE_IN_FOLLOWUP_TO;
1182 #else
1183  errors_catbp |= CA_ERROR_NEWLINE_IN_FOLLOWUP_TO;
1184 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1185  unfold_header(line);
1186  }
1187 
1188  followupto = build_nglist(cp, &ftngcnt);
1189  if (followupto && ftngcnt) {
1190  char *groups;
1191 
1192  (void) stripped_double_ngs(followupto, &ftngcnt);
1193  for (cp = line + 12; *cp; cp++) {
1194  if (!isascii(*cp)) {
1195  errors_catbp |= CA_ERROR_FOLLOWUP_TO_NOT_7BIT;
1196  break;
1197  }
1198  }
1199 
1200  for (cp = line + 12; *cp == ' '; cp++)
1201  ;
1202  cp2 = groups = my_strdup(cp);
1203 
1204  cp = strtok(groups, ",");
1205  do {
1206  if (!strcmp(cp, "poster") && ftngcnt > 1)
1207  errors_catbp |= CA_ERROR_FOLLOWUP_TO_POSTER;
1208  if (!strcmp(cp, "example"))
1209  warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
1210  if (!strncmp(cp, "example.", 8))
1211  warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
1212  /* TODO: also check for to, ctl, all, control, junk */
1213  } while ((cp = strtok(NULL, ",")) != NULL);
1214  free(cp2);
1215  }
1216  }
1217  }
1218 
1219  if (subject[0] == '\0')
1220  errors_catbp |= CA_ERROR_EMPTY_SUBJECT;
1221  else {
1222  cp2 = my_strdup(subject);
1223  if (!strtok(cp2, " \t")) { /* only blanks in Subject? */
1224  warnings_catbp |= CA_WARNING_SPACES_ONLY_SUBJECT;
1225  free(cp2);
1226  } else {
1227  free(cp2);
1228  /* Warn if Subject: begins with "Re: " but there are no References: */
1229  if (!strncmp(subject, "Re: ", 4) && !saw_references)
1230  warnings_catbp |= CA_WARNING_RE_WITHOUT_REFERENCES;
1231  /*
1232  * Warn if there are References: but no "Re: " at the beginning of
1233  * and no "(was:" in the Subject.
1234  */
1235  if (saw_references && strncmp(subject, "Re: ", 4)) {
1236  t_bool was_found = FALSE;
1237 
1238  cp2 = subject;
1239  while (!was_found && (cp2 = strchr(cp2, '(')))
1240  was_found = (strncmp(++cp2, "was:", 4) == 0);
1241 
1242  if (!was_found)
1243  warnings_catbp |= CA_WARNING_REFERENCES_WITHOUT_RE;
1244  }
1245  }
1246  }
1247 
1248  if (!found_from_lines)
1249  errors_catbp |= CA_ERROR_MISSING_FROM;
1250  else {
1251  if (found_from_lines > 1)
1252  errors_catbp |= CA_ERROR_DUPLICATED_FROM;
1253  }
1254 
1255  if (!found_newsgroups_lines && c_art_type == GROUP_TYPE_NEWS)
1256  errors_catbp |= CA_ERROR_MISSING_NEWSGROUPS;
1257 
1258  if (found_newsgroups_lines > 1)
1259  errors_catbp |= CA_ERROR_DUPLICATED_NEWSGROUPS;
1260 
1261  if (!found_subject_lines)
1262  errors_catbp |= CA_ERROR_MISSING_SUBJECT;
1263  else {
1264  if (found_subject_lines > 1)
1265  errors_catbp |= CA_ERROR_DUPLICATED_SUBJECT;
1266  }
1267 
1268  if (found_followup_to_lines > 1)
1269  errors_catbp |= CA_ERROR_DUPLICATED_FOLLOWUP_TO;
1270 
1271  /*
1272  * Check the body of the article for long lines
1273  * check if article contains non-7bit-ASCII characters
1274  * check if sig is shorter then MAX_SIG_LINES lines
1275  */
1276  while ((line = tin_fgets(fp, FALSE))) {
1277  cnt++;
1278 
1279  if (saw_sig_dashes || saw_wrong_sig_dashes)
1280  sig_lines++;
1281 
1282  /* SIGDASHES excluding the terminating \n as tin_fgets strips it */
1283  if (strlen(line) == 3 && !strncmp(line, SIGDASHES, 3)) {
1284  saw_wrong_sig_dashes = FALSE;
1285  saw_sig_dashes++;
1286  sig_lines = 0;
1287  }
1288 
1289  /* SIGDASHES excluding the tailing SPACE (and '\n', see comment above) */
1290  if (strlen(line) == 2 && !strncmp(line, SIGDASHES, 2) && !saw_sig_dashes) {
1291  saw_wrong_sig_dashes = TRUE;
1292  sig_lines = 0;
1293  }
1294 
1295 #ifdef CHARSET_CONVERSION
1296  /* are all characters in article contained in network_charset? */
1297  if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
1298  cp = my_malloc(strlen(line) * 4 + 1);
1299  strcpy(cp, line);
1300  charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
1301  free(cp);
1302  }
1303 #endif /* CHARSET_CONVERSION */
1304 
1305  {
1306 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1307  int num_bytes, wc_width;
1308  wchar_t wc;
1309 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1310 
1311  col = 0;
1312  for (cp = line; *cp; ) {
1313  if (*cp == '\t') {
1314  col += 8 - (col % 8);
1315  cp++;
1316  } else {
1317 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1318  if ((num_bytes = mbtowc(&wc, cp, MB_CUR_MAX)) != -1) {
1319  cp += num_bytes;
1320  if (!contains_8bit && num_bytes > 1)
1321  contains_8bit = TRUE;
1322  if (iswprint(wc) && ((wc_width = wcwidth(wc)) != -1))
1323  col += wc_width;
1324  else
1325  col++;
1326  } else {
1327  cp++;
1328  col++;
1329  }
1330 #else
1331  if (!contains_8bit && !isascii(*cp))
1332  contains_8bit = TRUE;
1333  cp++;
1334  col++;
1335 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1336  }
1337  }
1338  }
1339  if (col > MAX_COL && !got_long_line) {
1340  my_fprintf(stderr, _(txt_warn_art_line_too_long), MAX_COL, cnt, line);
1341  my_fflush(stderr);
1342  got_long_line = TRUE;
1343 
1344  warnings++;
1345  }
1346  if (strlen(line) > IMF_LINE_LEN && !must_break_line)
1347  must_break_line = cnt;
1348  }
1349 
1350 /*
1351  * TODO: cleanup, test me, move to the right location, strings -> lang.c, ...
1352  */
1353  if (must_break_line && ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_BASE64)) {
1354 # ifdef MIME_BREAK_LONG_LINES
1355  if (contains_8bit) {
1356  if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_QP)
1357  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);
1358  } else
1359 # endif /* MIME_BREAK_LONG_LINES */
1360  {
1361  if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) == MIME_ENCODING_QP)
1362  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);
1363  else
1364  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);
1365  }
1366  my_fflush(stderr);
1367  warnings++;
1368  }
1369 
1370  if (saw_sig_dashes > 1)
1371  warnings_catbp |= CA_WARNING_MULTIPLE_SIGDASHES;
1372 
1373  if (saw_wrong_sig_dashes)
1374  warnings_catbp |= CA_WARNING_WRONG_SIGDASHES;
1375 
1376  if (sig_lines > MAX_SIG_LINES) {
1377  warnings_catbp |= CA_WARNING_LONG_SIGNATURE;
1378 #ifdef HAVE_FASCIST_NEWSADMIN
1379  errors++;
1380 #endif /* HAVE_FASCIST_NEWSADMIN */
1381  }
1382 
1383 #ifdef CHARSET_CONVERSION
1384  if (charset_conversion_fails)
1385  warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
1386 #endif /* CHARSET_CONVERSION */
1387 
1388  if (!end_of_header)
1389  errors_catbp |= CA_ERROR_MISSING_BODY_SEPARATOR;
1390 
1391  /*
1392  * check for MIME Content-Type and Content-Transfer-Encoding
1393  *
1394  * If the user has modified the Newsgroups-header **group might not
1395  * point to the correct newsgroup any more.
1396  * Take first group in Newsgroups-header to pass it along to
1397  * submit_news_file et.al. to use it for group-attributes, or if there is
1398  * no Newsgroups:-header (mailing_list) stay with given group.
1399  *
1400  * Is this correct for crosspostings?
1401  */
1402  if (ngcnt)
1403  *c_group = group_find(newsgroups[0], FALSE);
1404 
1405  /*
1406  * check for known 7bit charsets
1407  */
1408  for (i = 0; txt_mime_7bit_charsets[i] != NULL; i++) {
1409 #ifdef CHARSET_CONVERSION
1410  if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i]))
1411 #else
1413 #endif /* CHARSET_CONVERSION */
1414  {
1415  mime_usascii = TRUE;
1416  break;
1417  }
1418  }
1419  if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_7BIT)
1420  mime_7bit = FALSE;
1421  if (contains_8bit && mime_usascii)
1422 #ifndef CHARSET_CONVERSION
1423  errors_catbp |= CA_ERROR_BAD_CHARSET;
1424 #else /* we catch this case later on again */
1425  warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
1426 #endif /* !CHARSET_CONVERSION */
1427 
1428  if (contains_8bit && mime_7bit)
1429  errors_catbp |= CA_ERROR_BAD_ENCODING;
1430 
1431  /*
1432  * Warn when poster is using a non-plain encoding such as quoted-printable
1433  * or base64 and external inews because if that external inews appends a
1434  * signature it will not be encoded. We might additionally check if there's
1435  * a file named ~/.signature and skip the warning if it is not present.
1436  */
1438  warnings_catbp |= CA_WARNING_ENCODING_EXTERNAL_INEWS;
1439 
1440  /* give most error messages */
1441  if (errors_catbp) {
1442  StartInverse();
1443 
1444  /* missing headers */
1445  if (errors_catbp & CA_ERROR_HEADER_LINE_BLANK)
1446  my_fprintf(stderr, "%s", _(txt_error_header_line_blank));
1447  if (errors_catbp & CA_ERROR_MISSING_BODY_SEPARATOR)
1449  if (errors_catbp & CA_ERROR_MISSING_FROM)
1450  my_fprintf(stderr, _(txt_error_header_line_missing), "From");
1451  if (errors_catbp & CA_ERROR_MISSING_SUBJECT)
1452  my_fprintf(stderr, _(txt_error_header_line_missing), "Subject");
1453  if (errors_catbp & CA_ERROR_MISSING_NEWSGROUPS)
1454  my_fprintf(stderr, _(txt_error_header_line_missing), "Newsgroups");
1455 
1456  /* duplicated headers */
1457  if (errors_catbp & CA_ERROR_DUPLICATED_FROM)
1458  my_fprintf(stderr, _(txt_error_header_duplicate), found_from_lines, "From");
1459  if (errors_catbp & CA_ERROR_DUPLICATED_SUBJECT)
1460  my_fprintf(stderr, _(txt_error_header_duplicate), found_subject_lines, "Subject");
1461  if (errors_catbp & CA_ERROR_DUPLICATED_NEWSGROUPS)
1462  my_fprintf(stderr, _(txt_error_header_duplicate), found_newsgroups_lines, "Newsgroups");
1463  if (errors_catbp & CA_ERROR_DUPLICATED_FOLLOWUP_TO)
1464  my_fprintf(stderr, _(txt_error_header_duplicate), found_followup_to_lines, "Followup-To");
1465 
1466  /* empty headers */
1467  if (errors_catbp & CA_ERROR_EMPTY_SUBJECT)
1468  my_fprintf(stderr, _(txt_error_header_line_empty), "Subject");
1469  if (errors_catbp & CA_ERROR_EMPTY_NEWSGROUPS)
1470  my_fprintf(stderr, _(txt_error_header_line_empty), "Newsgroups");
1471 
1472 #ifndef ALLOW_FWS_IN_NEWSGROUPLIST
1473  /* illegal space in headers */
1474  if (errors_catbp & CA_ERROR_SPACE_IN_NEWSGROUPS)
1475  my_fprintf(stderr, _(txt_error_header_line_comma), "Newsgroups");
1476  if (errors_catbp & CA_ERROR_SPACE_IN_FOLLOWUP_TO)
1477  my_fprintf(stderr, _(txt_error_header_line_comma), "Followup-To");
1478 
1479  /* illegal newline in headers */
1480  if (errors_catbp & CA_ERROR_NEWLINE_IN_NEWSGROUPS)
1481  my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Newsgroups");
1482  if (errors_catbp & CA_ERROR_NEWLINE_IN_FOLLOWUP_TO)
1483  my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Followup-To");
1484 #endif /* !ALLOW_FWS_IN_NEWSGROUPLIST */
1485 
1486  /* illegal group names / combinations */
1487  if (errors_catbp & CA_ERROR_NEWSGROUPS_POSTER)
1488  my_fprintf(stderr, "%s", _(txt_error_newsgroups_poster));
1489  if (errors_catbp & CA_ERROR_FOLLOWUP_TO_POSTER)
1490  my_fprintf(stderr, "%s", _(txt_error_followup_poster));
1491 
1492  /* encoding/charset trouble */
1493  if (errors_catbp & CA_ERROR_BAD_CHARSET)
1495  if (errors_catbp & CA_ERROR_BAD_ENCODING)
1497 
1498  if (errors_catbp & CA_ERROR_DISTRIBUTIOIN_NOT_7BIT)
1499  my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Distribution");
1500  if (errors_catbp & CA_ERROR_NEWSGROUPS_NOT_7BIT)
1501  my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Newsgroups");
1502  if (errors_catbp & CA_ERROR_FOLLOWUP_TO_NOT_7BIT)
1503  my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Followup-To");
1504 
1505  if (errors_catbp & CA_ERROR_BAD_MESSAGE_ID)
1506  my_fprintf(stderr, _(txt_error_header_format), "Message-ID");
1507  if (errors_catbp & CA_ERROR_BAD_DATE)
1508  my_fprintf(stderr, _(txt_error_header_format), "Date");
1509  if (errors_catbp & CA_ERROR_BAD_EXPIRES)
1510  my_fprintf(stderr, _(txt_error_header_format), "Expires");
1511 
1512  EndInverse();
1513  my_fflush(stderr);
1514  errors += errors_catbp;
1515  }
1516 
1517  /* give most warnings */
1518  if (warnings_catbp) {
1519 
1520  if (warnings_catbp & CA_WARNING_SPACES_ONLY_SUBJECT)
1521  my_fprintf(stderr, "%s", _(txt_warn_blank_subject));
1522  if (warnings_catbp & CA_WARNING_RE_WITHOUT_REFERENCES)
1523  my_fprintf(stderr, "%s", _(txt_warn_re_but_no_references));
1524  if (warnings_catbp & CA_WARNING_REFERENCES_WITHOUT_RE)
1525  my_fprintf(stderr, "%s", _(txt_warn_references_but_no_re));
1526 
1527  if ((warnings_catbp & CA_WARNING_NEWSGROUPS_EXAMPLE) || (warnings_catbp & CA_WARNING_FOLLOWUP_TO_EXAMPLE))
1528  my_fprintf(stderr, "%s", _(txt_warn_example_hierarchy));
1529 
1530 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
1531  if (warnings_catbp & CA_WARNING_SPACE_IN_NEWSGROUPS)
1532  my_fprintf(stderr, _(txt_warn_header_line_comma), "Newsgroups");
1533  if (warnings_catbp & CA_WARNING_SPACE_IN_FOLLOWUP_TO)
1534  my_fprintf(stderr, _(txt_warn_header_line_comma), "Followup-To");
1535  if (warnings_catbp & CA_WARNING_NEWLINE_IN_NEWSGROUPS)
1536  my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Newsgroups");
1537  if (warnings_catbp & CA_WARNING_NEWLINE_IN_FOLLOWUP_TO)
1538  my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Followup-To");
1539 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
1540 
1541  if (warnings_catbp & CA_WARNING_MULTIPLE_SIGDASHES)
1542  my_fprintf(stderr, _(txt_warn_multiple_sigs), saw_sig_dashes);
1543  if (warnings_catbp & CA_WARNING_WRONG_SIGDASHES)
1544  my_fprintf(stderr, "%s", _(txt_warn_wrong_sig_format));
1545  if (warnings_catbp & CA_WARNING_LONG_SIGNATURE)
1547 
1548  if (warnings_catbp & CA_WARNING_ENCODING_EXTERNAL_INEWS)
1550 
1551 #ifdef CHARSET_CONVERSION
1552  if (warnings_catbp & CA_WARNING_CHARSET_CONVERSION)
1553  my_fprintf(stderr, _(txt_warn_charset_conversion), tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]);
1554 #endif /* CHARSET_CONVERSION */
1555 
1556  my_fflush(stderr);
1557  warnings += warnings_catbp;
1558  }
1559 
1560  if (!errors) {
1561  /*
1562  * Print a note about each newsgroup
1563  */
1564  if (c_art_unchanged)
1565  my_fprintf(stderr, "%s", _(txt_warn_article_unchanged));
1566 
1567  if (ngcnt)
1568  my_fprintf(stderr, _(txt_art_newsgroups), subject, PLURAL(ngcnt, txt_newsgroup));
1569 
1570  if (c_art_type == GROUP_TYPE_MAIL)
1571  my_fprintf(stderr, _(txt_art_mailgroups), subject, BlankIfNull(to));
1572  else {
1573  for (i = 0; i < ngcnt; i++) {
1574  if ((psGrp = group_find(newsgroups[i], FALSE))) {
1575  if (psGrp->aliasedto) {
1576 #ifdef HAVE_FASCIST_NEWSADMIN
1577  StartInverse();
1578  errors++;
1579  my_fprintf(stderr, N_(txt_error_grp_renamed), newsgroups[i], psGrp->aliasedto);
1580  EndInverse();
1581  my_fflush(stderr);
1582 #else
1583  my_fprintf(stderr, N_(txt_warn_grp_renamed), newsgroups[i], psGrp->aliasedto);
1584  warnings++;
1585 #endif /* HAVE_FASCIST_NEWSADMIN */
1586  } else
1587  my_fprintf(stderr, " %s\t %s\n", newsgroups[i], BlankIfNull(psGrp->description));
1588  } else {
1589 #ifdef HAVE_FASCIST_NEWSADMIN
1590  StartInverse();
1591  errors++;
1592  my_fprintf(stderr, _(txt_error_not_valid_newsgroup), newsgroups[i]);
1593  EndInverse();
1594  my_fflush(stderr);
1595 #else
1596  my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), newsgroups[i]);
1597  warnings++;
1598 #endif /* HAVE_FASCIST_NEWSADMIN */
1599  }
1600  }
1601  if (!found_followup_to_lines && ngcnt > 1 && !errors) {
1602 #ifdef HAVE_FASCIST_NEWSADMIN
1603  StartInverse();
1604  my_fprintf(stderr, _(txt_error_missing_followup_to), ngcnt);
1605  EndInverse();
1606  my_fflush(stderr);
1607  errors++;
1608 #else
1609  my_fprintf(stderr, _(txt_warn_missing_followup_to), ngcnt);
1610  warnings++;
1611 #endif /* HAVE_FASCIST_NEWSADMIN */
1612  }
1613 
1614  if (ftngcnt && !errors) {
1615  if (ftngcnt > 1) {
1616 #ifdef HAVE_FASCIST_NEWSADMIN
1617  StartInverse();
1618  my_fprintf(stderr, "%s", _(txt_error_followup_to_several_groups));
1619  EndInverse();
1620  my_fflush(stderr);
1621  errors++;
1622 #else
1624  warnings++;
1625 #endif /* HAVE_FASCIST_NEWSADMIN */
1626  }
1627 #ifdef HAVE_FASCIST_NEWSADMIN
1628  if (!errors) {
1629 #endif /* HAVE_FASCIST_NEWSADMIN */
1631  for (i = 0; i < ftngcnt; i++) {
1632  if ((psGrp = group_find(followupto[i], FALSE))) {
1633  if (psGrp->aliasedto) {
1634 #ifdef HAVE_FASCIST_NEWSADMIN
1635  StartInverse();
1636  errors++;
1637  my_fprintf(stderr, N_(txt_error_grp_renamed), followupto[i], psGrp->aliasedto);
1638  EndInverse();
1639  my_fflush(stderr);
1640 #else
1641  my_fprintf(stderr, N_(txt_warn_grp_renamed), followupto[i], psGrp->aliasedto);
1642  warnings++;
1643 #endif /* HAVE_FASCIST_NEWSADMIN */
1644  } else
1645  my_fprintf(stderr, " %s\t %s\n", followupto[i], BlankIfNull(psGrp->description));
1646  } else {
1647  if (STRCMPEQ("poster", followupto[i]))
1648  my_fprintf(stderr, _(txt_followup_poster), followupto[i]);
1649  else {
1650 #ifdef HAVE_FASCIST_NEWSADMIN
1651  StartInverse();
1652  my_fprintf(stderr, _(txt_error_not_valid_newsgroup), followupto[i]);
1653  EndInverse();
1654  my_fflush(stderr);
1655  errors++;
1656 #else
1657  my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), followupto[i]);
1658  warnings++;
1659 #endif /* HAVE_FASCIST_NEWSADMIN */
1660  }
1661  }
1662  }
1663 #ifdef HAVE_FASCIST_NEWSADMIN
1664  }
1665 #endif /* HAVE_FASCIST_NEWSADMIN */
1666  }
1667 
1668 #ifndef NO_ETIQUETTE
1669  if (tinrc.beginner_level)
1670  my_fprintf(stderr, "%s", _(txt_warn_posting_etiquette));
1671 #endif /* !NO_ETIQUETTE */
1672  my_fflush(stderr);
1673  }
1674  }
1675  fclose(fp);
1676 
1677  Raw(oldraw); /* restore raw/unraw state */
1678 
1679  /* free memory */
1680  if (newsgroups && ngcnt) {
1681  FreeIfNeeded(*newsgroups);
1682  FreeIfNeeded(newsgroups);
1683  }
1684  if (followupto && ftngcnt) {
1685  FreeIfNeeded(*followupto);
1686  FreeIfNeeded(followupto);
1687  }
1688  FreeIfNeeded(to);
1689 
1690  return (errors ? 1 : (warnings ? 2 : 0));
1691 }
1692 
1693 
1694 static void
1696  int *init)
1697 {
1698  if (*init) {
1699  ClearScreen();
1701  MoveCursor(INDEX_TOP, 0);
1702  Raw(FALSE);
1703  *init = 0;
1704  }
1705 }
1706 
1707 
1708 #if defined(SIGWINCH) || defined(SIGTSTP)
1709 void
1710 refresh_post_screen(
1711  int context)
1712 {
1713  switch (context) {
1714  case cPost:
1715  ClearScreen();
1717  MoveCursor(INDEX_TOP, 0);
1718  check_article_to_be_posted(NULL, 0, NULL, FALSE, TRUE);
1719  break;
1720 
1721  case cPostCancel:
1722  {
1723  int oldraw = RawState();
1724 
1725  ClearScreen();
1727  MoveCursor(INDEX_TOP, 0);
1728  Raw(FALSE);
1729 # ifdef FORGERY
1731 # else
1732  show_cancel_info();
1733 # endif /* FORGERY */
1734  Raw(oldraw);
1735  }
1736  break;
1737 
1738  case cPostFup:
1740  break;
1741 
1742  default:
1743  break;
1744  }
1745 }
1746 #endif /* SIGWINCH || SIGTSTP */
1747 
1748 
1749 /*
1750  * edit/present an article, perform spell/PGP etc., operations if required
1751  * submit the article and perform all necessary backend processing
1752  */
1753 static int
1755  int type, /* type of posting */
1756  struct t_group *group,
1757  t_function func,
1758  const char *posting_msg, /* displayed just prior to article submission */
1759  int art_type, /* news, mail etc. */
1760  int offset) /* editor start offset */
1761 {
1762  char a_message_id[HEADER_LEN]; /* Message-ID of the article if known */
1763  int ret_code = POSTED_NONE;
1764  int i = 1;
1765  int save_signal_context = signal_context;
1766  long artchanged; /* artchanged work was not done in post_postponed_article */
1767  struct t_group *ogroup = curr_group;
1768  t_bool art_unchanged;
1769 
1770  a_message_id[0] = '\0';
1771 
1772  forever {
1773 post_article_loop:
1774  art_unchanged = FALSE;
1775  switch (func) {
1776  case POST_EDIT:
1777  /*
1778  * This was VERY different in repost_article Code existed to
1779  * recheck subject and restart editor, but is not enabled
1780  */
1781  artchanged = file_mtime(article_name);
1782  if (!invoke_editor(article_name, offset, group)) {
1783  if (file_size(article_name) > 0L) {
1784  if (artchanged != file_mtime(article_name)) {
1789  }
1790  }
1791  goto post_article_postponed;
1792  }
1794 
1795  /* This might be erroneous with posting postponed */
1796  if (file_size(article_name) > 0L) {
1797  if (artchanged == file_mtime(article_name))
1798  art_unchanged = TRUE;
1799  while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
1800  ;
1801  if (func == POST_EDIT || func == GLOBAL_OPTION_MENU)
1802  break;
1803  }
1804  /* FALLTHROUGH */
1805 
1806  case GLOBAL_QUIT:
1807  case GLOBAL_ABORT:
1808  if (tinrc.unlink_article) {
1809 #if 0 /* useful? */
1812 #endif /* 0 */
1814  }
1815  clear_message();
1816  return ret_code;
1817 
1818  case GLOBAL_OPTION_MENU:
1819  config_page(group->name, signal_context);
1820  while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
1821  ;
1822  break;
1823 
1824 #ifdef HAVE_ISPELL
1825  case POST_ISPELL:
1826  invoke_ispell(article_name, group);
1827  ret_code = POSTED_REDRAW; /* not all versions did this */
1828  break;
1829 #endif /* HAVE_ISPELL */
1830 
1831 #ifdef HAVE_PGP_GPG
1832  case POST_PGP:
1833  invoke_pgp_news(article_name);
1834  break;
1835 #endif /* HAVE_PGP_GPG */
1836 
1837  case GLOBAL_POST:
1838  wait_message(0, posting_msg);
1840 
1841  /* Functions that didn't handle mail didn't do this */
1842  if (art_type == GROUP_TYPE_NEWS) {
1843  if (submit_news_file(article_name, group, a_message_id))
1844  ret_code = POSTED_OK;
1845  } else {
1846  if (submit_mail_file(article_name, group, NULL, FALSE)) /* mailing_list */
1847  ret_code = POSTED_OK;
1848  }
1849 
1850  if (ret_code == POSTED_OK) {
1852  wait_message(2, _(txt_art_posted), *a_message_id ? a_message_id : "");
1853  goto post_article_done;
1854  } else {
1855  if ((func = prompt_rejected()) == POST_POSTPONE)
1856  /* reuse clean copy which didn't get modified by submit_news_file() */
1858  else if (func == POST_EDIT) {
1859  /* replace modified article with clean backup */
1861  goto post_article_loop;
1862  } else {
1868  }
1869  return ret_code;
1870  }
1871 
1872  case POST_POSTPONE:
1874  goto post_article_postponed;
1875 
1876  default:
1877  break;
1878  }
1880  if (type != POST_REPOST && type != POST_SUPERSEDED) {
1881  char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
1882  char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
1883  char keymenu[MAXKEYLEN];
1884 #ifdef HAVE_ISPELL
1885  char keyispell[MAXKEYLEN];
1886 #endif /* HAVE_ISPELL */
1887 #ifdef HAVE_PGP_GPG
1888  char keypgp[MAXKEYLEN];
1889 #endif /* HAVE_PGP_GPG */
1890 
1891 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
1892  func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
1896  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
1897  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
1901 #else
1902 # ifdef HAVE_ISPELL
1903  func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
1907  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
1911 # else
1912 # ifdef HAVE_PGP_GPG
1913  func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
1917  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
1921 # else
1922  func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
1929 # endif /* HAVE_PGP_GPG */
1930 # endif /* HAVE_ISPELL */
1931 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
1932  } else {
1933  char *smsg;
1934  char buf[LEN];
1935  char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
1936  char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
1937  char keymenu[MAXKEYLEN];
1938 #ifdef HAVE_ISPELL
1939  char keyispell[MAXKEYLEN];
1940 #endif /* HAVE_ISPELL */
1941 #ifdef HAVE_PGP_GPG
1942  char keypgp[MAXKEYLEN];
1943 #endif /* HAVE_PGP_GPG */
1944 
1945 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
1946  snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
1949  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
1950  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
1954 #else
1955 # ifdef HAVE_ISPELL
1956  snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
1959  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
1963 # else
1964 # ifdef HAVE_PGP_GPG
1965  snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
1968  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
1972 # else
1973  snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
1979 # endif /* HAVE_PGP_GPG */
1980 # endif /* HAVE_ISPELL */
1981 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
1982 
1983  /* Superfluous force_command stuff not used in current code */
1984  func = ( /* force_command ? ch_default : */ prompt_slk_response(func,
1985  post_post_keys, "%s", sized_message(&smsg, buf,
1986  "" /* TODO: was note_h.subj */ )));
1987  free(smsg);
1988  }
1989  signal_context = save_signal_context;
1990  }
1991 
1992 post_article_done:
1993  if (ret_code == POSTED_OK) {
1994  FILE *art_fp;
1995  struct t_header header;
1996 
1997  memset(&header, 0, sizeof(struct t_header));
1998 
1999  if ((art_fp = fopen(article_name, "r")) == NULL)
2001  else {
2002  curr_group = group;
2003  parse_rfc822_headers(&header, art_fp, NULL);
2004  fclose(art_fp);
2005  }
2006 
2007  if (art_type == GROUP_TYPE_NEWS) {
2008  if (header.newsgroups) {
2010  /* In POST_RESPONSE, this was copied from note_h.newsgroups if !followup to poster */
2012  }
2013  }
2014 
2015  if (header.subj && header.newsgroups) {
2016  char tag;
2017  /*
2018  * When crossposting postponed articles we currently do not add
2019  * autoselect since we don't know which group the article was
2020  * actually in
2021  * FIXME: This logic is faithful to the original, but awful
2022  */
2023  if (group) { /* we might be (x-)posting to an unavailable group */
2024  if (art_type == GROUP_TYPE_NEWS && group->attribute->add_posted_to_filter && (type == POST_QUICK || type == POST_POSTPONED || type == POST_NORMAL)) {
2025  if ((group = group_find(header.newsgroups, FALSE)) && (type != POST_POSTPONED || (type == POST_POSTPONED && !strchr(header.newsgroups, ',')))) {
2026  quick_filter_select_posted_art(group, header.subj, a_message_id);
2027  if (type == POST_QUICK || (type == POST_POSTPONED && post_postponed_and_exit))
2029  }
2030  }
2031  }
2032 
2033  switch (type) {
2034  case POST_POSTPONED:
2035  tag = (header.references) ? 'f' : 'w';
2036  break;
2037 
2038  case POST_RESPONSE:
2039  tag = 'f';
2040  break;
2041 
2042  case POST_REPOST:
2043  case POST_SUPERSEDED:
2044  tag = 'x';
2045  break;
2046 
2047  case POST_NORMAL:
2048  case POST_QUICK:
2049  default:
2050  tag = 'w';
2051  break;
2052  }
2053 
2054  switch (art_type) {
2055  case GROUP_TYPE_NEWS:
2056  update_posted_info_file(header.newsgroups, tag, header.subj, a_message_id);
2057  break;
2058 
2059  case GROUP_TYPE_MAIL:
2060  update_posted_info_file(header.to, tag, header.subj, "");
2061  break;
2062 
2063  default:
2064  break;
2065  }
2066 
2068  }
2069 
2070  if (*tinrc.posted_articles_file && type != POST_REPOST) { /* TODO: either document the !POST_REPOST logic or remove it */
2071  char a_mailbox[PATH_LEN];
2072  char posted_msgs_file[PATH_LEN];
2073 
2074  if (!strfpath(tinrc.posted_articles_file, posted_msgs_file, sizeof(posted_msgs_file), group, TRUE))
2075  STRCPY(posted_msgs_file, tinrc.posted_articles_file);
2076  else {
2077  if (!strcmp(tinrc.posted_articles_file, posted_msgs_file)) /* only prefix tinrc.posted_articles_file if it was a plain file without path */
2078  joinpath(posted_msgs_file, sizeof(posted_msgs_file), (cmdline.args & CMDLINE_MAILDIR) ? cmdline.maildir : (group ? group->attribute->maildir : tinrc.maildir), tinrc.posted_articles_file);
2079  }
2080 
2081  /* re-strfpath as maildir may also need expansion */
2082  if (!strfpath(posted_msgs_file, a_mailbox, sizeof(a_mailbox), group, TRUE))
2083  STRCPY(a_mailbox, posted_msgs_file);
2084 
2085  /*
2086  * log Message-ID if given in a_message_id,
2087  * add Date: if required, remove empty headers
2088  */
2089  add_headers(article_name, a_message_id);
2090 
2091  if ((errno = append_mail(article_name, userid, a_mailbox))) {
2093  }
2094  }
2095  free_and_init_header(&header);
2096  }
2097 
2098 post_article_postponed:
2099  curr_group = ogroup;
2100  if (tinrc.unlink_article)
2102 
2103  return ret_code;
2104 }
2105 
2106 
2107 /*
2108  * Parse the list of newsgroups. For each, check group flag status. If it is
2109  * possible to post to the group and the user agrees, then keep going. Return
2110  * pointer to the first group in the list (the posting code needs this)
2111  * Any one failure => return NULL
2112  */
2113 static struct t_group *
2115  const char *groups,
2116  int *art_type,
2117  const char *failmsg)
2118 {
2119  char *groupname;
2120  char *ogroupn;
2121  char newsgroups[HEADER_LEN];
2122  struct t_group *group;
2123  struct t_group *first_group = NULL;
2124  int vnum = 0, bnum = 0;
2125 
2126  /* Take copy - strtok() modifies its args */
2127  STRCPY(newsgroups, groups);
2128 
2129  if ((ogroupn = groupname = strtok(newsgroups, ",")) == NULL)
2130  return NULL;
2131 
2132  do {
2133  vnum++; /* number of newsgroups */
2134 
2135  if (!(group = group_find(groupname, FALSE))) {
2136  bnum++; /* number of bogus groups */
2137  continue;
2138  }
2139 
2140  if (!first_group) /* Save ptr to the 1st group */
2141  first_group = group;
2142 
2143  /*
2144  * Testing for !attribute here is a useful check for other brokenness
2145  * Generally only bogus groups should have no attributes
2146  */
2147  if (group->bogus) {
2148  error_message(2, _(txt_group_bogus), groupname);
2149  return NULL;
2150  }
2151 
2152  if (group->attribute->mailing_list != NULL)
2153  *art_type = GROUP_TYPE_MAIL;
2154 
2155  if (!can_post && *art_type == GROUP_TYPE_NEWS) {
2157  return NULL;
2158  }
2159 
2160  if (group->moderated == 'x' || group->moderated == 'n' || group->moderated == 'j') {
2162  return NULL;
2163  }
2164 
2165  if (group->moderated == 'm') {
2166  char *prompt = fmt_string(_(txt_group_is_moderated), groupname);
2167  if (prompt_yn(prompt, TRUE) != 1) {
2168  error_message(*failmsg ? 2 : 0, failmsg);
2169  free(prompt);
2170  return NULL;
2171  }
2172  free(prompt);
2173  }
2174  } while ((groupname = strtok(NULL, ",")) != NULL);
2175 
2176  if (vnum > bnum)
2177  return first_group;
2178  else {
2179  error_message(2, _(txt_not_in_active_file), ogroupn);
2180  return NULL;
2181  }
2182 }
2183 
2184 
2185 /*
2186  * Build the standard headers used by quick_post_article() and post_article()
2187  * Return TRUE or FALSE if things went wrong - there seems to be little
2188  * error checking possible in here
2189  */
2190 static t_bool
2192  struct t_group *group,
2193  const char *newsgroups,
2194  int art_type)
2195 {
2196  FILE *fp;
2197  char from_name[HEADER_LEN];
2198 #ifdef FORGERY
2199  char tmp[HEADER_LEN];
2200 #endif /* FORGERY */
2201  char *prompt, *tmp2;
2202 
2203  /* Get subject for posting article - Limit the display if needed */
2205 
2206  prompt = fmt_string(_(txt_post_subject), tmp2);
2207 
2209  free(prompt);
2210  free(tmp2);
2211  return FALSE;
2212  }
2213  free(prompt);
2214  free(tmp2);
2215 
2216  if ((fp = fopen(article_name, "w")) == NULL) {
2218  return FALSE;
2219  }
2220 
2221 #ifdef HAVE_FCHMOD
2222  fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
2223 #else
2224 # ifdef HAVE_CHMOD
2225  chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
2226 # endif /* HAVE_CHMOD */
2227 #endif /* HAVE_FCHMOD */
2228 
2229  get_from_name(from_name, group);
2230 #ifdef FORGERY
2231  make_path_header(tmp);
2232  msg_add_header("Path", tmp);
2233 #endif /* FORGERY */
2234  msg_add_header("From", from_name);
2236 
2237  if (art_type == GROUP_TYPE_MAIL)
2238  msg_add_header("To", group->attribute->mailing_list);
2239  else {
2240  msg_add_header("Newsgroups", newsgroups);
2242  }
2243 
2244  if (art_type == GROUP_TYPE_NEWS) {
2245  if (group->attribute->followup_to != NULL)
2246  msg_add_header("Followup-To", group->attribute->followup_to);
2247  else {
2248  if (group->attribute->prompt_followupto)
2249  msg_add_header("Followup-To", "");
2250  }
2251  }
2252 
2253  if (*reply_to)
2254  msg_add_header("Reply-To", reply_to);
2255 
2256  if (group->attribute->organization != NULL)
2257  msg_add_header("Organization", random_organization(group->attribute->organization));
2258 
2259  if (*my_distribution && art_type == GROUP_TYPE_NEWS)
2260  msg_add_header("Distribution", my_distribution);
2261 
2262  msg_add_header("Summary", "");
2263  msg_add_header("Keywords", "");
2264 
2266 
2268  fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
2269  msg_free_headers();
2270 
2272 
2273  msg_write_signature(fp, FALSE, group);
2274  fclose(fp);
2275  cursoron();
2276  return TRUE;
2277 }
2278 
2279 
2280 /*
2281  * Quick post an article (not a followup)
2282  */
2283 void
2285  t_bool postponed_only,
2286  int num_cmd_line_groups)
2287 {
2288  char buf[HEADER_LEN];
2289  int art_type = GROUP_TYPE_NEWS;
2290  struct t_group *group;
2291 
2292  msg_init_headers();
2293  ClearScreen();
2294 
2295  /*
2296  * check for postponed articles first
2297  * first param is whether to ask the user if they want to do it or not.
2298  * it's opposite to the command line switch.
2299  * second param is whether to assume yes to all which is the same as
2300  * the command line switch.
2301  */
2302 
2303  if (pickup_postponed_articles(!postponed_only, postponed_only) || postponed_only)
2304  return;
2305 
2306  /*
2307  * post_article_and_exit
2308  * Get groupname, but skip query if group was given on the cmd.-line
2309  */
2310  if (!num_cmd_line_groups) {
2313  return;
2314 
2316  }
2317 
2318  /*
2319  * Check/see if any of the newsgroups are not postable.
2320  */
2321  if ((group = check_moderated(tinrc.default_post_newsgroups, &art_type, _(txt_exiting))) == NULL)
2322  return;
2323 
2325  return;
2326 
2328 }
2329 
2330 
2331 /*
2332  * Post an article that is already written (for postponed articles)
2333  */
2334 static void
2336  int ask,
2337  const char *subject,
2338  const char *newsgroups)
2339 {
2340  char *ng;
2341  char *p;
2342  char buf[LEN];
2343 
2344  if (!can_post) {
2346  return;
2347  }
2348 
2349  ng = my_strdup(newsgroups);
2350  if ((p = strchr(ng, ',')) != NULL)
2351  *p = '\0';
2352 
2353  snprintf(buf, sizeof(buf), _("Posting: %.*s ..."), cCOLS - 14, subject); /* TODO: -> lang.c, use strunc() */
2355  free(ng);
2356 }
2357 
2358 
2359 /*
2360  * count how many articles are in postponed.articles. Essentially,
2361  * we count '^From ' lines
2362  */
2363 int
2365  void)
2366 {
2367  FILE *fp = fopen(postponed_articles_file, "r");
2368  char line[HEADER_LEN];
2369  int count = 0;
2370 
2371  if (!fp)
2372  return 0;
2373 
2374  while (fgets(line, (int) sizeof(line), fp)) {
2375  if (strncmp(line, "From ", 5) == 0)
2376  count++;
2377  }
2378  fclose(fp);
2379  return count;
2380 }
2381 
2382 
2383 /*
2384  * Copy the first postponed article and remove it from the postponed file
2385  */
2386 static t_bool
2388  const char tmp_file[],
2389  char subject[],
2390  char newsgroups[])
2391 {
2392  FILE *in, *out;
2393  FILE *tmp;
2394  char *bufp = NULL;
2395  char postponed_tmp[PATH_LEN];
2396  char line[HEADER_LEN];
2397  t_bool first_article;
2398  t_bool prev_line_nl;
2399  t_bool anything_left;
2400 
2401  snprintf(postponed_tmp, sizeof(postponed_tmp), "%s_", postponed_articles_file);
2402  in = fopen(postponed_articles_file, "r");
2403  out = fopen(tmp_file, "w");
2404  tmp = fopen(postponed_tmp, "w");
2405 
2406  if (in == NULL || out == NULL || tmp == NULL) {
2407  if (in)
2408  fclose(in);
2409  if (out)
2410  fclose(out);
2411  if (tmp)
2412  fclose(tmp);
2413  return FALSE;
2414  }
2415 
2416  fgets(line, (int) sizeof(line), in);
2417 
2418  if (strncmp(line, "From ", 5) != 0) {
2419  fclose(in);
2420  fclose(out);
2421  fclose(tmp);
2422  return FALSE;
2423  }
2424 
2425  first_article = TRUE;
2426  prev_line_nl = FALSE;
2427  anything_left = FALSE;
2428 
2429  /*
2430  * we have one minor problem with copying the article, we have added
2431  * a newline at the end of the article and we have to remove that,
2432  * but we don't know we are on the last line until we read the next
2433  * line containing "From "
2434  */
2435 
2436  while (fgets(line, (int) sizeof(line), in) != NULL) {
2437  if (strncmp(line, "From ", 5) == 0)
2438  first_article = FALSE;
2439  if (first_article) {
2440  match_string(line, "Newsgroups: ", newsgroups, HEADER_LEN);
2441  match_string(line, "Subject: ", subject, HEADER_LEN);
2442 
2443  if (prev_line_nl)
2444  fputc('\n', out);
2445 
2446  if (strlen(line) && line[strlen(line) - 1] == '\n') {
2447  prev_line_nl = TRUE;
2448  line[strlen(line) - 1] = '\0';
2449  } else
2450  prev_line_nl = FALSE;
2451 
2452  /* unquote quoted From_ lines */
2453  if (tinrc.mailbox_format == 1) {
2454  bufp = line;
2455  while (*bufp == '>')
2456  bufp++;
2457  if (strncmp(bufp, "From ", 5) == 0)
2458  fputs(line + 1, out);
2459  else
2460  fputs(line, out);
2461  } else {
2462  if (strncmp(line, ">From ", 6) == 0)
2463  fputs(line + 1, out);
2464  else
2465  fputs(line, out);
2466  }
2467  } else {
2468  fputs(line, tmp);
2469  anything_left = TRUE;
2470  }
2471  }
2472 
2473  fclose(in);
2474  fclose(out);
2475  fclose(tmp);
2476 
2478 
2479  if (anything_left)
2480  rename_file(postponed_tmp, postponed_articles_file);
2481  else
2482  unlink(postponed_tmp);
2483 
2484  return TRUE;
2485 }
2486 
2487 
2488 /* pick up any postponed articles and ask if the user wants to use them */
2489 t_bool
2491  t_bool ask,
2492  t_bool all)
2493 {
2494  char newsgroups[HEADER_LEN];
2495  char subject[HEADER_LEN];
2496  char question[HEADER_LEN];
2498  int i;
2500 
2501  if (!count) {
2502  if (!ask)
2504  return FALSE;
2505  }
2506 
2507  snprintf(question, sizeof(question), _(txt_prompt_see_postponed), count);
2508 
2509  if (ask && prompt_yn(question, TRUE) != 1)
2510  return FALSE;
2511 
2512  for (i = 0; i < count; i++) {
2513  if (!fetch_postponed_article(article_name, subject, newsgroups))
2514  return TRUE;
2515 
2516  if (!all) {
2517  char *smsg;
2518  char buf[LEN];
2519  char keyall[MAXKEYLEN], keyno[MAXKEYLEN], keyoverride[MAXKEYLEN];
2520  char keyquit[MAXKEYLEN], keyyes[MAXKEYLEN];
2521 
2522  snprintf(buf, sizeof(buf), _(txt_postpone_repost),
2528 
2530  "%s", sized_message(&smsg, buf, subject));
2531  free(smsg);
2532 
2533  if (func == POSTPONE_ALL)
2534  all = TRUE;
2535  }
2536 
2537  /* No else here since all changes in previous if */
2538  if (all)
2540 
2541  switch (func) {
2542  case PROMPT_YES:
2543  case POSTPONE_OVERRIDE:
2544  post_postponed_article(func == PROMPT_YES, subject, newsgroups);
2545  Raw(TRUE);
2546  break;
2547 
2548  case PROMPT_NO:
2549  case GLOBAL_QUIT:
2550  case GLOBAL_ABORT:
2554  if (func != PROMPT_NO)
2555  return TRUE;
2556  break;
2557 
2558  default:
2559  break;
2560  }
2561  }
2562  return TRUE;
2563 }
2564 
2565 
2566 static void
2568  const char *the_article)
2569 {
2571  if ((errno = append_mail(the_article, userid, postponed_articles_file)))
2573 }
2574 
2575 
2576 /*
2577  * Post an original article (not a followup)
2578  */
2579 t_bool
2581  const char *groupname)
2582 {
2583  int art_type = GROUP_TYPE_NEWS;
2584  struct t_group *group;
2586 
2587  msg_init_headers();
2588 
2589  /*
2590  * Check that we can post to all the groups we want to
2591  */
2592  if ((group = check_moderated(groupname, &art_type, "")) == NULL)
2593  return redraw_screen;
2594 
2595  if (!create_normal_article_headers(group, groupname, art_type))
2596  return redraw_screen;
2597 
2598  return (post_loop(POST_NORMAL, group, POST_EDIT, _(txt_posting), art_type, start_line_offset) != POSTED_NONE);
2599 }
2600 
2601 
2602 /*
2603  * yeah, right, that's from the same Chris who is telling Jason he's
2604  * doing obfuscated C :-)
2605  */
2606 static void
2608  char **where,
2609  const char **what)
2610 {
2611  char *oldpos;
2612 
2613  oldpos = *where;
2614  while (**what && **what != '<')
2615  (*what)++;
2616  if (**what) {
2617  while (**what && **what != '>' && !isspace((unsigned char) **what))
2618  *(*where)++ = *(*what)++;
2619  if (**what != '>')
2620  *where = oldpos;
2621  else {
2622  (*what)++;
2623  *(*where)++ = '>';
2624  }
2625  }
2626 }
2627 
2628 
2629 /*
2630  * check given Message-ID for "_-_@" which (should) indicate(s)
2631  * a Subject: change
2632  */
2633 static t_bool
2635  const char *id)
2636 {
2637  while (*id && *id != '<')
2638  id++;
2639  while (*id && *id != '>') {
2640  if (*++id != '_')
2641  continue;
2642  if (*++id != '-')
2643  continue;
2644  if (*++id != '_')
2645  continue;
2646  if (*++id == '@')
2647  return TRUE;
2648  }
2649  return FALSE;
2650 }
2651 
2652 
2653 static size_t
2655  const char *id)
2656 {
2657  size_t skipped = 0;
2658 
2659  while (id[skipped] != '\0' && isspace((unsigned char) id[skipped]))
2660  skipped++;
2661 
2662  if (id[skipped] != '\0') {
2663  while (id[skipped] != '\0' && !isspace((unsigned char) id[skipped]))
2664  skipped++;
2665  }
2666  return skipped;
2667 }
2668 
2669 
2670 /*
2671  * Checks if Message-ID has valid format
2672  * Returns FALSE if it does, TRUE if it does not
2673  * TODO: combine with refs.c:valid_msgid() (return values swapped)
2674  */
2675 static t_bool
2677  const char *id)
2678 {
2679  while (*id && isspace((unsigned char) *id))
2680  id++;
2681 
2682  if (*id != '<')
2683  return TRUE;
2684 
2685  while (isascii((unsigned char) *id) && isgraph((unsigned char) *id) && !iscntrl((unsigned char) *id) && *id != '>')
2686  id++;
2687 
2688  if (*id != '>')
2689  return TRUE;
2690 
2691  return FALSE;
2692 }
2693 
2694 
2695 /*
2696  * A real crossposting test had to run on Newsgroups but we only have Xref in
2697  * t_article, so we use this.
2698  */
2699 static t_bool
2701  const char *xref)
2702 {
2703  int count = 0;
2704 
2705  for (; *xref; xref++)
2706  if (*xref == ':')
2707  count++;
2708 
2709  return (count >= 2) ? TRUE : FALSE;
2710 }
2711 
2712 
2713 /*
2714  * RFC 5537 3.4.4
2715  * "If the resulting References header field would, after unfolding, exceed
2716  * 998 characters in length (including its field name but not the final
2717  * CRLF), it MUST be trimmed (and otherwise MAY be trimmed)."
2718  */
2719 #ifdef NNTP_ONLY
2720 # define MAXREFSIZE 998
2721 #else /* some extern inews (required for posting right into the spool) can't handle 1k-lines */
2722 # define MAXREFSIZE 512
2723 #endif /* NNTP_ONLY */
2724 
2725 
2726 /*
2727  * TODO: if we have the art[x] that we are following up to, then
2728  * get_references(art[x].refptr) will give us the new refs line
2729  */
2730 static void
2732  char *buffer,
2733  const char *oldrefs,
2734  const char *newref)
2735 {
2736  /*
2737  * First of all: shortening references is a VERY BAD IDEA.
2738  * Nevertheless, current software usually has restrictions in
2739  * header length (their programmers seem to misinterpret RFC821
2740  * as valid for news, and the command length limit of RFC977
2741  * as valid for headers)
2742  *
2743  * construct a new references line, then trim it if necessary
2744  *
2745  * do some sanity cleanups: remove damaged ids, make
2746  * sure there is space between ids (tabs and commas are stripped)
2747  *
2748  * note that we're not doing strict son of RFC 1036 here: we don't
2749  * take any precautions to keep the last three message ids, but
2750  * it's not very likely that MAXREFSIZE chars can't hold at least
2751  * 4 refs
2752  */
2753  char *b, *c, *d;
2754  const char *e;
2755  int space = 0;
2756 
2757  b = my_malloc(strlen(oldrefs) + strlen(newref) + 64);
2758  c = b;
2759  e = oldrefs;
2760 
2761  while (*e) {
2762  if (*e == ' ') {
2763  /* keep existing spaces */
2764  space++;
2765  *c++ = ' ';
2766  e++;
2767  continue;
2768  } else if (*e != '<') { /* strip everything besides spaces and */
2769  e++; /* message-ids */
2770  continue;
2771  }
2772  if (damaged_id(e)) { /* remove damaged message ids and mark
2773  the gap if that's not already done */
2774  e += skip_id(e);
2775  while (space < 3) {
2776  space++;
2777  *c++ = ' ';
2778  }
2779  continue;
2780  }
2781  if (!space)
2782  *c++ = ' ';
2783  else
2784  space = 0;
2785  appendid(&c, &e);
2786  }
2787  while (space) {
2788  c--;
2789  space--; /* remove superfluous space at the end */
2790  }
2791  *c++ = ' ';
2792  appendid(&c, &newref);
2793  *c = '\0';
2794 
2795  /* now see if we need to remove ids */
2796  while (strlen(b) > (MAXREFSIZE - strlen("References: ") - 2)) {
2797  c = b;
2798  c += skip_id(c); /* keep the first one */
2799  while (*c && must_include(c))
2800  c += skip_id(c); /* skip those marked with _-_ */
2801  d = c;
2802  c += skip_id(c); /* ditch one */
2803  *d++ = ' ';
2804  *d++ = ' ';
2805  *d++ = ' '; /* and mark this appropriately */
2806  while (*c == ' ')
2807  c++;
2808 #ifdef HAVE_MEMMOVE /* TODO: put into a function? */
2809  memmove(d, c, strlen(c) + 1);
2810 #else
2811 # ifdef HAVE_BCOPY
2812  bcopy(c, d, strlen(c) + 1);
2813 # else
2814  {
2815  size_t l = strlen(c) + 1;
2816 
2817  if (c < d && d < c + l) {
2818  d += l;
2819  c += l;
2820  while (l--)
2821  *--d= *--c;
2822  } else {
2823  while (l--)
2824  *d++ = *c++;
2825  }
2826  }
2827 # endif /* HAVE_BCOPY */
2828 #endif /* HAVE_MEMMOVE */
2829  }
2830 
2831  strcpy(buffer, b);
2832  free(b);
2833  return;
2834 
2835  /*
2836  * son of RFC 1036 says:
2837  * Followup agents SHOULD not shorten References headers. If
2838  * it is absolutely necessary to shorten the header, as a des-
2839  * perate last resort, a followup agent MAY do this by deleting
2840  * some of the message IDs. However, it MUST not delete the
2841  * first message ID, the last three message IDs (including that
2842  * of the immediate precursor), or any message ID mentioned in
2843  * the body of the followup. If it is possible for the fol-
2844  * lowup agent to determine the Subject content of the articles
2845  * identified in the References header, it MUST not delete the
2846  * message ID of any article where the Subject content changed
2847  * (other than by prepending of a back reference). The fol-
2848  * lowup agent MUST not delete any message ID whose local part
2849  * ends with "_-_" (underscore (ASCII 95), hyphen (ASCII 45),
2850  * underscore); followup agents are urged to use this form to
2851  * mark subject changes, and to avoid using it otherwise.
2852  * [...]
2853  * When a References header is shortened, at least three blanks
2854  * SHOULD be left between adjacent message IDs at each point
2855  * where deletions were made. Software preparing new Refer-
2856  * ences headers SHOULD preserve multiple blanks in older Ref-
2857  * erences content.
2858  */
2859 }
2860 
2861 
2862 static void
2864  void)
2865 {
2866  char *ptr;
2867  struct t_header note_h = pgart.hdr;
2868 
2869  /*
2870  * note that comparing newsgroups and followup-to isn't
2871  * really correct, since the order of the newsgroups may be
2872  * different, but testing that also isn't really worth
2873  * it. The main culprit for the duplication is tin <=1.22, BTW.
2874  */
2875  MoveCursor(cLINES / 2, 0);
2876  CleartoEOS();
2877  center_line((cLINES / 2) + 2, TRUE, _(txt_resp_redirect));
2878  MoveCursor((cLINES / 2) + 4, 0);
2879 
2880  my_fputs(" ", stdout);
2881  /*
2882  * TODO: check if any valid groups are in the Followup-To:-line
2883  * and if not inform the user and use Newsgroups: instead
2884  */
2885  ptr = note_h.followup;
2886  while (*ptr) {
2887  if (*ptr != ',')
2888  my_fputc(*ptr, stdout);
2889  else {
2890  my_fputs(cCRLF, stdout);
2891  my_fputs(" ", stdout);
2892  }
2893  ptr++;
2894  }
2895  my_flush();
2896 }
2897 
2898 
2899 int /* return code is currently ignored! */
2901  const char *groupname,
2902  int respnum,
2903  t_bool copy_text,
2904  t_bool with_headers,
2905  t_bool raw_data)
2906 {
2907  FILE *fp;
2908  char *ptr;
2909  char bigbuf[HEADER_LEN];
2910  char buf[HEADER_LEN];
2911  char from_name[HEADER_LEN];
2912  char initials[64];
2913  int art_type = GROUP_TYPE_NEWS;
2914  int ret_code = POSTED_NONE;
2915  struct t_group *group;
2916  struct t_header note_h = pgart.hdr;
2917  t_bool use_followup_to = TRUE;
2918 #ifdef FORGERY
2919  char line[HEADER_LEN];
2920 #endif /* FORGERY */
2921  t_function func;
2922 
2923  msg_init_headers();
2925 
2926  /*
2927  * Remove duplicates in Newsgroups and Followup-To line
2928  *
2929  * RFC 5536 3.1.4, 3.2.6 allows FWS but discourages it
2930  * -> remove FWS from newsgroups and followup
2931  *
2932  * TODO: also remove WSP
2933  */
2936  if (note_h.followup) {
2939  }
2940 
2941  if (note_h.followup && STRCMPEQ(note_h.followup, "poster")) {
2942  char keymail[MAXKEYLEN], keypost[MAXKEYLEN], keyquit[MAXKEYLEN];
2943 
2944 /* clear_message(); */
2949  switch (func) {
2950  case GLOBAL_POST:
2951  use_followup_to = FALSE;
2952  break;
2953 
2954  case GLOBAL_QUIT:
2955  case GLOBAL_ABORT:
2956  return ret_code;
2957 
2958  case POST_MAIL:
2959  return mail_to_author(groupname, respnum, copy_text, with_headers, FALSE);
2960 
2961  default:
2962  break;
2963  }
2964  } else if (note_h.followup && strcmp(note_h.followup, groupname) != 0
2965  && strcmp(note_h.followup, note_h.newsgroups) != 0) {
2966  char keyignore[MAXKEYLEN], keypost[MAXKEYLEN], keyquit[MAXKEYLEN];
2967  int save_signal_context = signal_context;
2968 
2976  signal_context = save_signal_context;
2977  switch (func) {
2978  case GLOBAL_QUIT:
2979  case GLOBAL_ABORT:
2980  return ret_code;
2981 
2982  case POST_IGNORE_FUPTO:
2983  use_followup_to = FALSE;
2984  break;
2985 
2986  case GLOBAL_POST:
2987  default:
2988  break;
2989  }
2990  }
2991 
2992  if ((fp = fopen(article_name, "w")) == NULL) {
2994  return ret_code;
2995  }
2996 
2997 #ifdef HAVE_FCHMOD
2998  fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
2999 #else
3000 # ifdef HAVE_CHMOD
3001  chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
3002 # endif /* HAVE_CHMOD */
3003 #endif /* HAVE_FCHMOD */
3004 
3005  group = group_find(groupname, FALSE);
3006  get_from_name(from_name, group);
3007 #ifdef FORGERY
3008  make_path_header(line);
3009  msg_add_header("Path", line);
3010 #endif /* FORGERY */
3011  msg_add_header("From", from_name);
3012 
3013  ptr = my_strdup(note_h.subj);
3014  snprintf(bigbuf, sizeof(bigbuf), "Re: %s", eat_re(ptr, TRUE));
3015  msg_add_header("Subject", bigbuf);
3016  free(ptr);
3017 
3018  if (group && group->attribute->x_comment_to && note_h.from)
3019  msg_add_header("X-Comment-To", note_h.from);
3020  if (note_h.followup && use_followup_to) {
3021  msg_add_header("Newsgroups", note_h.followup);
3022  if (group && group->attribute->prompt_followupto)
3023  msg_add_header("Followup-To", (strchr(note_h.followup, ',') != NULL) ? note_h.followup : "");
3024  } else {
3025  if (group && group->attribute->mailing_list) {
3026  msg_add_header("To", group->attribute->mailing_list);
3027  art_type = GROUP_TYPE_MAIL;
3028  } else {
3029  msg_add_header("Newsgroups", note_h.newsgroups);
3030  if (group && group->attribute->prompt_followupto)
3031  msg_add_header("Followup-To", (strchr(note_h.newsgroups, ',') != NULL) ? note_h.newsgroups : "");
3032  if (group && group->attribute->followup_to != NULL)
3033  msg_add_header("Followup-To", group->attribute->followup_to);
3034  else {
3035  if (strchr(note_h.newsgroups, ','))
3036  msg_add_header("Followup-To", note_h.newsgroups);
3037  }
3038  }
3039  }
3040 
3041  /*
3042  * Append to References: line if its already there
3043  */
3044  if (note_h.references) {
3046  msg_add_header("References", bigbuf);
3047  } else
3048  msg_add_header("References", note_h.messageid);
3049 
3050  if (group && group->attribute->organization != NULL)
3051  msg_add_header("Organization", random_organization(group->attribute->organization));
3052 
3053  if (*reply_to)
3054  msg_add_header("Reply-To", reply_to);
3055 
3056  if (art_type != GROUP_TYPE_MAIL) {
3058  if (note_h.distrib)
3059  msg_add_header("Distribution", note_h.distrib);
3060  else if (*my_distribution)
3061  msg_add_header("Distribution", my_distribution);
3062  }
3063 
3064  if (group && group->attribute->x_headers)
3066 
3068  msg_free_headers();
3069  if (group && group->attribute->x_body)
3071 
3072  if (copy_text) {
3073  if (arts[respnum].xref && is_crosspost(arts[respnum].xref)) {
3074  if (strfquote(group ? group->name : groupname, respnum, buf, sizeof(buf), tinrc.xpost_quote_format))
3075  fprintf(fp, "%s\n", buf);
3076  } else if (strfquote(groupname, respnum, buf, sizeof(buf), (group && group->attribute->news_quote_format != NULL) ? group->attribute->news_quote_format : tinrc.news_quote_format))
3077  fprintf(fp, "%s\n", buf);
3079 
3080  /*
3081  * check if tinrc.xpost_quote_format or tinrc.news_quote_format
3082  * is longer than 1 line and correct start_line_offset
3083  */
3084  for (ptr = buf; *ptr; ptr++) {
3085  if (*ptr == '\n')
3087  }
3088 
3089  get_initials(&arts[respnum], initials, sizeof(initials) - 1);
3090 
3091  if (raw_data) /* rewind raw article if needed */
3092  fseek(pgart.raw, 0L, SEEK_SET);
3093 
3094  if (with_headers && raw_data)
3095  copy_body(pgart.raw, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, TRUE);
3096  else {
3097  if (raw_data) {
3098  long offset = 0L;
3099  char buffer[8192];
3100 
3101  /* skip headers + header/body separator */
3102  while (fgets(buffer, (int) sizeof(buffer), pgart.raw) != NULL) {
3103  offset += strlen(buffer);
3104  if (buffer[0] == '\n' || buffer[0] == '\r')
3105  break;
3106  }
3107  fseek(pgart.raw, offset, SEEK_SET);
3108  copy_body(pgart.raw, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, TRUE);
3109  } else { /* cooked art */
3111  if (with_headers) {
3112  /*
3113  * unfortunately this includes only those headers
3114  * mentioned in news_headers_to_display as article
3115  * cooking 'hides' all other headers
3116  */
3117  fseek(pgart.cooked, 0L, SEEK_SET); /* rewind cooked art */
3118  } else { /* without headers */
3119  int i = 0;
3120 
3121  while (pgart.cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
3122  i++;
3123 
3124  if (i) /* cooked art contained any headers, so skip also the header/body separator */
3125  i++;
3126 
3127  fseek(pgart.cooked, pgart.cookl[i].offset, SEEK_SET); /* skip headers and header/body separator */
3128  }
3129  copy_body(pgart.cooked, fp, (group ? group->attribute->quote_chars : tinrc.quote_chars), initials, FALSE);
3130  }
3131  }
3132  } else /* !copy_text */
3133  fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
3134 
3135  msg_write_signature(fp, FALSE, group);
3136  fclose(fp);
3137 
3138  resize_article(TRUE, &pgart); /* rebreak long lines */
3139  if (raw_data) /* we've been in raw mode, reenter it */
3140  toggle_raw(group);
3141 
3142  return (post_loop(POST_RESPONSE, group, POST_EDIT, _(txt_posting), art_type, start_line_offset));
3143 }
3144 
3145 
3146 /*
3147  * Generates the basic header for a mailed article
3148  * Returns an open fp or NULL if article couldn't be created
3149  * The name of the temp. article file is written to 'filename'
3150  * If extra_hdrs is defined, then additional headers are added, see the code
3151  */
3152 static FILE *
3154  char *filename,
3155  size_t filename_len,
3156  const char *suffix,
3157  const char *to,
3158  const char *subject,
3159  struct t_header *extra_hdrs)
3160 {
3161  FILE *fp;
3162 
3163  msg_init_headers();
3164  joinpath(filename, filename_len, homedir, suffix);
3165 
3166 #ifdef APPEND_PID
3167  snprintf(filename + strlen(filename), filename_len - strlen(filename), ".%ld", (long) process_id);
3168 #endif /* APPEND_PID */
3169 
3170  if ((fp = fopen(filename, "w")) == NULL) {
3171  perror_message(_(txt_cannot_open), filename);
3172  return NULL;
3173  }
3174 
3175 #ifdef HAVE_FCHMOD
3176  fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
3177 #else
3178 # ifdef HAVE_CHMOD
3179  chmod(filename, (mode_t) (S_IRUSR|S_IWUSR));
3180 # endif /* HAVE_CHMOD */
3181 #endif /* HAVE_FCHMOD */
3182 
3183  if ((INTERACTIVE_NONE == tinrc.interactive_mailer) || (INTERACTIVE_WITH_HEADERS == tinrc.interactive_mailer)) { /* tin should include headers for editing */
3184  char from_buf[HEADER_LEN];
3185  char *from_address;
3186 
3188  from_address = curr_group->attribute->from;
3189  else /* i.e. called from select.c without any groups */
3190  from_address = tinrc.mail_address;
3191 
3192  if ((from_address == NULL) || !strlen(from_address)) {
3193  get_from_name(from_buf, (struct t_group *) 0);
3194  from_address = &from_buf[0];
3195  } /* from_address is now always a valid pointer to a string */
3196 
3197  if (strlen(from_address))
3198  msg_add_header("From", from_address);
3199 
3200  msg_add_header("To", to);
3201  msg_add_header("Subject", subject);
3202 
3203  if (*reply_to)
3204  msg_add_header("Reply-To", reply_to);
3205 
3206  /*
3207  * Only add own address if it is not already there.
3208  *
3209  * Note: get_recipients() strips out duplicated addresses later, but
3210  * only for displaying; the MTA has to deal with it. They shouldn't be
3211  * put in the file in the first place, so we don't do it.
3212  */
3213  if (!address_in_list(to, strlen(from_address) ? from_address : userid)) {
3215  msg_add_header("Cc", strlen(from_address) ? from_address : userid);
3216 
3218  msg_add_header("Bcc", strlen(from_address) ? from_address : userid);
3219  }
3220 
3223 
3224  if (*default_organization)
3226 
3227  if (extra_hdrs) {
3228  /*
3229  * Write Message-ID as In-Reply-To to the mail
3230  */
3231  msg_add_header("In-Reply-To", extra_hdrs->messageid);
3232 
3233  /*
3234  * Rewrite Newsgroups: as X-Newsgroups: as RFC 822 doesn't define it.
3235  */
3236  strip_double_ngs(extra_hdrs->newsgroups);
3237  msg_add_header("X-Newsgroups", extra_hdrs->newsgroups);
3238  }
3239 
3242  }
3244  msg_free_headers();
3245 
3246  return fp;
3247 }
3248 
3249 
3250 /*
3251  * Handle editing/spellcheck/PGP etc., operations on a mail article
3252  * Submit/abort the article as required and return POSTED_{NONE,REDRAW,OK}
3253  * Replaces core of mail_to_someone(), mail_bug_report(), mail_to_author()
3254  */
3255 static int
3257  const char *filename, /* Temp. filename being used */
3258  t_function func, /* default function */
3259  char *subject,
3260  const char *groupname, /* Newsgroup we are posting from */
3261  const char *prompt, /* If set, used for final query before posting */
3262  FILE *articlefp)
3263 {
3264  FILE *fp;
3265  int ret = POSTED_NONE;
3266  long artchanged;
3267  struct t_header hdr;
3268  struct t_group *group = (struct t_group *) 0;
3269  t_bool is_changed = FALSE;
3270 #ifdef HAVE_PGP_GPG
3271  char mail_to[HEADER_LEN];
3272 #endif /* HAVE_PGP_GPG */
3273 
3274  if (groupname)
3275  group = group_find(groupname, FALSE);
3276 
3277  forever {
3278  switch (func) {
3279  case POST_EDIT:
3280  artchanged = file_mtime(filename);
3281 
3282  if (!(invoke_editor(filename, start_line_offset, group)))
3283  return ret;
3284 
3285  ret = POSTED_REDRAW;
3286  if (((artchanged == file_mtime(filename)) && (prompt_yn(_(txt_prompt_unchanged_mail), TRUE) > 0)) || (file_size(filename) <= 0L)) {
3287  clear_message();
3288  return ret;
3289  }
3290 
3291  if (artchanged != file_mtime(filename))
3292  is_changed = TRUE;
3293 
3294  if (!(fp = fopen(filename, "r"))) { /* Oops */
3295  clear_message();
3296  return ret;
3297  }
3298  parse_rfc822_headers(&hdr, fp, NULL);
3299  fclose(fp);
3300  if (hdr.subj) {
3301  strncpy(subject, hdr.subj, HEADER_LEN - 1);
3302  subject[HEADER_LEN - 1] = '\0';
3303  } else
3305  if (!hdr.to && !hdr.cc && !hdr.bcc)
3307  free_and_init_header(&hdr);
3308  break;
3309 
3310 #ifdef HAVE_ISPELL
3311  case POST_ISPELL:
3312  invoke_ispell(filename, group);
3313 /* ret = POSTED_REDRAW; TODO: is this needed, not that REDRAW does not imply OK */
3314  break;
3315 #endif /* HAVE_ISPELL */
3316 
3317 #ifdef HAVE_PGP_GPG
3318  case POST_PGP:
3319  if (!(fp = fopen(filename, "r"))) { /* Oops */
3320  clear_message();
3321  return ret;
3322  }
3323  parse_rfc822_headers(&hdr, fp, NULL);
3324  fclose(fp);
3325  if (get_recipients(&hdr, mail_to, sizeof(mail_to) - 1))
3326  invoke_pgp_mail(filename, mail_to);
3327  else
3329  free_and_init_header(&hdr);
3330  break;
3331 #endif /* HAVE_PGP_GPG */
3332 
3333  case GLOBAL_QUIT:
3334  case GLOBAL_ABORT:
3335  clear_message();
3336  return ret;
3337 
3338  case POST_SEND:
3339  {
3340  t_bool confirm = TRUE;
3341 
3342  if (prompt) {
3343  clear_message();
3344  if (prompt_yn(prompt, FALSE) != 1)
3345  confirm = FALSE;
3346  }
3347 
3348  if (confirm && submit_mail_file(filename, group, articlefp, is_changed)) {
3350  return POSTED_OK;
3351  }
3352  }
3353  return ret;
3354  /* NOTREACHED */
3355  break;
3356 
3357  default:
3358  break;
3359  }
3360  func = prompt_to_send(subject);
3361  }
3362 
3363  /* NOTREACHED */
3364  return ret;
3365 }
3366 
3367 
3368 /*
3369  * Add the mail_quote_format string to 'fp', return the number of lines of text
3370  * added to the file
3371  */
3372 static int
3374  FILE *fp,
3375  int respnum)
3376 {
3377  char *s;
3378  char buf[HEADER_LEN];
3379  int line_count = 0;
3380 
3381  if (strfquote(CURR_GROUP.name, respnum, buf, sizeof(buf), tinrc.mail_quote_format)) {
3382  fprintf(fp, "%s\n", buf);
3383  line_count++;
3384 
3385  for (s = buf; *s; s++) {
3386  if (*s == '\n')
3387  ++line_count;
3388  }
3389  }
3390  return line_count;
3391 }
3392 
3393 
3394 /*
3395  * Return a POSTED_* code
3396  */
3397 int
3399  const char *address,
3400  t_bool confirm_to_mail,
3401  t_openartinfo *artinfo,
3402  const struct t_group *group)
3403 {
3404  FILE *fp;
3405  char nam[PATH_LEN];
3406  char subject[HEADER_LEN];
3407  int ret_code = POSTED_NONE;
3408  struct t_header note_h = artinfo->hdr;
3409  t_bool mime_forward = group->attribute->mime_forward;
3411 
3412  clear_message();
3413  snprintf(subject, sizeof(subject), "(fwd) %s\n", note_h.subj);
3414 
3415  /*
3416  * don't add extra headers in the mail_to_someone() case as we include
3417  * the full original headers in either the body of the mail or a separate
3418  * message/rfc822 MIME part.
3419  */
3420  if ((fp = create_mail_headers(nam, sizeof(nam), TIN_LETTER_NAME, address, subject, NULL)) == NULL)
3421  return ret_code;
3422 
3423  /*
3424  * TODO: This is an undocumented hack!
3425  * in the !mime_forward case we should get the charset of each part
3426  * and convert it to the local one (as this is also needed for the
3427  * interactive_mailer case).
3428  */
3429  if (note_h.ext->type == TYPE_MULTIPART)
3430  mime_forward = TRUE; /* force mime_forward for multipart articles */
3431 
3432  if (!mime_forward || tinrc.interactive_mailer != INTERACTIVE_NONE) {
3433  rewind(artinfo->raw);
3434  fprintf(fp, "%s", _(txt_forwarded));
3435 
3436  if (!note_h.mime)
3437  copy_fp(artinfo->raw, fp);
3438  else {
3439  const char *charset;
3440  char *line, *buff = my_malloc(LEN);
3441  size_t l, last = LEN;
3442  t_bool in_head = TRUE;
3443 
3444  /* intentionally no undeclared_charset support here! */
3445  if (!(charset = get_param(note_h.ext->params, "charset")))
3446  charset = "US-ASCII";
3447 
3448  while ((line = tin_fgets(artinfo->raw, FALSE)) != NULL) {
3449  if (*line == '\0')
3450  in_head = FALSE;
3451  l = strlen(line) * 4 + 4; /* should suffice for -> UTF-8 */
3452  if (l > last) { /* realloc if needed */
3453  buff = my_realloc(buff, l);
3454  last = l;
3455  }
3456  strcpy(buff, line);
3457  if (!in_head) /* just convert body */
3458  process_charsets(&buff, &l, charset, tinrc.mm_local_charset, FALSE);
3459  strcat(buff, "\n");
3460  fwrite(buff, 1, strlen(buff), fp);
3461  }
3462  free(buff);
3463  }
3464  fprintf(fp, "%s", _(txt_forwarded_end));
3465  }
3466 
3469 
3470  fclose(fp);
3471 
3472  if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader */
3473  char buf[HEADER_LEN];
3474  char *p;
3475 
3477  subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
3478  p = my_strdup(address); /* FIXME: strfmailer() won't take const arg 3 */
3479  strfmailer(mailer, subject, p, nam, buf, sizeof(buf), tinrc.mailer_format);
3480  free(p);
3481  if (invoke_cmd(buf))
3482  ret_code = POSTED_OK;
3483  } else {
3484  if (confirm_to_mail)
3485  func = prompt_to_send(subject);
3486  ret_code = mail_loop(nam, func, subject, group->name, NULL, mime_forward ? artinfo->raw : NULL);
3487  }
3488 
3489  if (tinrc.unlink_article)
3490  unlink(nam);
3491 
3492  return ret_code;
3493 }
3494 
3495 
3496 t_bool
3498  void) /* FIXME: return value is always ignored */
3499 {
3500  FILE *fp;
3501  const char *domain;
3502  char buf[LEN], nam[PATH_LEN];
3503  char tmesg[LEN];
3504  char subject[HEADER_LEN];
3505  t_bool ret_code = FALSE;
3506 
3508  snprintf(subject, sizeof(subject), "BUG REPORT %s\n", page_header);
3509 
3510  if ((fp = create_mail_headers(nam, sizeof(nam), TIN_BUGREPORT_NAME, bug_addr, subject, NULL)) == NULL)
3511  return FALSE;
3512 
3514 #if defined(HAVE_SYS_UTSNAME_H) && defined(HAVE_UNAME)
3515 # ifdef _AIX
3516  fprintf(fp, "BOX1 : %s %s.%s", system_info.sysname, system_info.version, system_info.release);
3517 # else
3518 # if defined(SEIUX) || defined(__riscos)
3519 /*
3520  * #if defined(host_mips) && defined(MIPSEB)
3521  * #if defined(SYSTYPE_SYSV) || defined(SYSTYPE_SVR4) || defined(SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
3522  * RISC/os
3523  * #endif
3524  * #endif
3525  */
3526  fprintf(fp, "BOX1 : %s %s", system_info.version, system_info.release);
3527 # else
3528  fprintf(fp, "BOX1 : %s %s (%s)", system_info.sysname, system_info.release, system_info.machine);
3529 # endif /* SEIUX || __riscos */
3530 # endif /* _AIX */
3531 #else
3532  fprintf(fp, "BOX1 : Please enter the following information: Machine+OS");
3533 #endif /* HAVE_SYS_UTSNAME_H && HAVE_UNAME */
3534 
3535 #ifdef DOMAIN_NAME
3536  domain = DOMAIN_NAME;
3537 #else
3538  domain = "";
3539 #endif /* DOMAIN_NAME */
3540 
3541  fprintf(fp, "\nCFG1 : active=%d, arts=%d, reread=%d, nntp_xover=%s\n",
3546  fprintf(fp, "CFG2 : debug=%d, threading=%d\n", debug, tinrc.thread_articles);
3547  fprintf(fp, "CFG3 : domain=[%s]\n", BlankIfNull(domain));
3548  start_line_offset += 4;
3549 
3550 #ifdef NNTP_ABLE
3551  if (read_news_via_nntp) {
3552  if (*bug_nntpserver1) {
3553  fprintf(fp, "NNTP1: %s\n", bug_nntpserver1);
3555  }
3556  if (*bug_nntpserver2) {
3557  fprintf(fp, "NNTP2: %s\n", bug_nntpserver2);
3559  }
3560  if (nntp_caps.implementation) {
3561  fprintf(fp, "IMPLE: %s\n", nntp_caps.implementation);
3563  }
3564  }
3565 #endif /* NNTP_ABLE */
3566 
3567  fprintf(fp, "\nPlease enter _detailed_ bug report, gripe or comment:\n\n");
3568  start_line_offset += 2;
3569 
3571  msg_write_signature(fp, TRUE, (selmenu.curr == -1) ? NULL : &CURR_GROUP);
3572 
3573  fclose(fp);
3574 
3575  if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader */
3576  subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
3577  strfmailer(mailer, subject, bug_addr, nam, buf, sizeof(buf), tinrc.mailer_format);
3578  if (invoke_cmd(buf))
3579  ret_code = TRUE;
3580  } else {
3581  snprintf(tmesg, sizeof(tmesg), _(txt_mail_bug_report_confirm), bug_addr);
3582  ret_code = mail_loop(nam, POST_EDIT, subject, NULL, tmesg, NULL) ? TRUE : FALSE;
3583  }
3584 
3585  unlink(nam);
3586  return ret_code;
3587 }
3588 
3589 
3590 int /* return value is always ignored */
3592  const char *group,
3593  int respnum,
3594  t_bool copy_text,
3595  t_bool with_headers,
3596  t_bool raw_data)
3597 {
3598  FILE *fp;
3599  char *p;
3600  char from_addr[HEADER_LEN];
3601  char nam[PATH_LEN];
3602  char subject[HEADER_LEN];
3603  char initials[64];
3604  int ret_code = POSTED_NONE;
3605  int i;
3606  struct t_header note_h = pgart.hdr;
3607 
3609  find_reply_to_addr(from_addr, FALSE, &pgart.hdr);
3610 
3611  i = gnksa_check_from(from_addr);
3612 
3613  /* TODO: make gnksa error level configurable */
3614  if (check_for_spamtrap(from_addr) || (i > GNKSA_OK && i < GNKSA_ILLEGAL_UNQUOTED_CHAR)) {
3615  char keyabort[MAXKEYLEN], keycont[MAXKEYLEN];
3616  t_function func;
3617 
3622  switch (func) {
3623  case POST_ABORT:
3624  case GLOBAL_ABORT:
3625  clear_message();
3626  return ret_code;
3627 
3628  case POST_CONTINUE:
3629  break;
3630 
3631  /* the user wants to continue anyway, so we do nothing special here */
3632  default:
3633  break;
3634  }
3635  }
3636 
3637  p = my_strdup(note_h.subj);
3638  snprintf(subject, sizeof(subject), "Re: %s\n", eat_re(p, TRUE));
3639  free(p);
3640 
3641  /*
3642  * add extra headers in the mail_to_author() case as we don't include the
3643  * full original headers in the body of the mail
3644  */
3645  if ((fp = create_mail_headers(nam, sizeof(nam), TIN_LETTER_NAME, from_addr, subject, &note_h)) == NULL)
3646  return ret_code;
3647 
3648  if (copy_text) {
3649  start_line_offset += add_mail_quote(fp, respnum);
3650  get_initials(&arts[respnum], initials, sizeof(initials) - 1);
3651 
3652  if (raw_data) /* rewind raw article if needed */
3653  fseek(pgart.raw, 0L, SEEK_SET);
3654 
3655  if (with_headers && raw_data)
3656  copy_body(pgart.raw, fp, tinrc.quote_chars, initials, TRUE);
3657  else {
3658  if (raw_data) { /* raw data && !with_headers */
3659  long offset = 0L;
3660  char buffer[8192];
3661 
3662  /* skip headers + header/body separator */
3663  while (fgets(buffer, (int) sizeof(buffer), pgart.raw) != NULL) {
3664  offset += strlen(buffer);
3665  if (buffer[0] == '\n' || buffer[0] == '\r')
3666  break;
3667  }
3668  fseek(pgart.raw, offset, SEEK_SET);
3669  copy_body(pgart.raw, fp, tinrc.quote_chars, initials, TRUE);
3670  } else { /* cooked art */
3672  if (with_headers) {
3673  /*
3674  * unfortunately this includes only those headers
3675  * mentioned in news_headers_to_display as article
3676  * cooking 'hides' all other headers
3677  */
3678  fseek(pgart.cooked, 0L, SEEK_SET);
3679  } else { /* without headers */
3680  i = 0;
3681  while (pgart.cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
3682  i++;
3683  if (i) /* cooked art contained any headers, so skip also the header/body separator */
3684  i++;
3685  fseek(pgart.cooked, pgart.cookl[i].offset, SEEK_SET);
3686  }
3687  copy_body(pgart.cooked, fp, tinrc.quote_chars, initials, FALSE);
3688  }
3689  }
3690  } else /* !copy_text */
3691  fprintf(fp, "\n"); /* add a newline to keep vi from bitching */
3692 
3695 
3696  fclose(fp);
3697 
3698  {
3699  char mail_to[HEADER_LEN];
3700 
3701  find_reply_to_addr(mail_to, TRUE, &pgart.hdr);
3702 
3703  if (tinrc.interactive_mailer != INTERACTIVE_NONE) { /* user wants to use his own mailreader for reply */
3704  char buf[HEADER_LEN];
3705 
3706  subject[strlen(subject) - 1] = '\0'; /* cut trailing '\n' */
3707  strfmailer(mailer, subject, mail_to, nam, buf, sizeof(buf), tinrc.mailer_format);
3708  if (invoke_cmd(buf))
3709  ret_code = POSTED_OK;
3710  } else
3711  ret_code = mail_loop(nam, POST_EDIT, subject, group, NULL, NULL);
3712 
3713  /*
3714  * If interactive_mailer!=NONE and the user changed the subject in his
3715  * mailreader, the entry generated here is wrong, strictly speaking.
3716  * But since we don't have a chance to get the final subject back from
3717  * the mailer I think this is the best solution. -dn, 2000-03-16
3718  */
3719  /*
3720  * same with mail_to, if user changes To: in the editor tin
3721  * doesn't notice it and logs the original value.
3722  */
3723  if (ret_code == POSTED_OK)
3724  update_posted_info_file(mail_to, 'r', subject, ""); /* TODO: update_posted_info_file elsewhere? */
3725  }
3726 
3727  if (tinrc.unlink_article)
3728  unlink(nam);
3729 
3730  resize_article(TRUE, &pgart); /* rebreak long lines */
3731 
3732  if (raw_data) /* we've been in raw mode */
3733  toggle_raw(group_find(group, FALSE));
3734 
3735  return ret_code;
3736 }
3737 
3738 
3739 /*
3740  * compare the given e-mail address with a list of components
3741  * in tinrc.spamtrap_warning_addresses
3742  */
3743 static t_bool
3745  const char *addr)
3746 {
3747  char *env;
3748  char *ptr;
3749  char *tmp;
3750 
3751  if (!strlen(tinrc.spamtrap_warning_addresses) || !addr || !*addr)
3752  return FALSE;
3753 
3755 
3756  while (strlen(tmp)) {
3757  ptr = strchr(tmp, ',');
3758  if (ptr != NULL)
3759  *ptr = '\0';
3760  if (strcasestr(addr, tmp)) {
3761  free(env);
3762  return TRUE;
3763  }
3764  tmp += strlen(tmp);
3765  if (ptr != NULL)
3766  tmp++;
3767  }
3768  free(env);
3769  return FALSE;
3770 }
3771 
3772 
3773 static void
3775 #ifdef FORGERY
3776  t_bool author,
3777  t_bool use_cache)
3778 #else
3779  void)
3780 #endif /* FORGERY */
3781 {
3782  struct t_header note_h = pgart.hdr;
3783 #ifdef FORGERY
3784  static t_bool c_author;
3785 
3786  /*
3787  * Cache value for the case when called
3788  * from refresh_post_screen()
3789  */
3790  if (!use_cache)
3791  c_author = author;
3792 
3793  if (!c_author) {
3794  my_fprintf(stderr, "%s", _(txt_warn_cancel_forgery));
3795  my_fprintf(stderr, "From: %s\n", BlankIfNull(note_h.from));
3796  } else
3797 #endif /* FORGERY */
3798  my_fprintf(stderr, "%s", _(txt_warn_cancel));
3799 
3800  my_fprintf(stderr, "Subject: %s\n", BlankIfNull(note_h.subj));
3801  my_fprintf(stderr, "Date: %s\n", BlankIfNull(note_h.date));
3802  my_fprintf(stderr, "Message-ID: %s\n", BlankIfNull(note_h.messageid));
3803  my_fprintf(stderr, "Newsgroups: %s\n", BlankIfNull(note_h.newsgroups));
3804 }
3805 
3806 
3807 t_bool
3809  struct t_group *group,
3810  struct t_article *art,
3811  int respnum)
3812 {
3813  FILE *fp;
3814  char buf[HEADER_LEN];
3815  char cancel[PATH_LEN];
3816  char from_name[HEADER_LEN];
3817  char a_message_id[HEADER_LEN];
3818 #ifdef FORGERY
3819  char line[HEADER_LEN];
3820  t_bool author = TRUE;
3821 #endif /* FORGERY */
3822  int init = 1;
3823  int oldraw;
3824  struct t_header note_h = pgart.hdr, hdr;
3826  t_function func;
3827  t_function default_func = POST_CANCEL;
3828 
3829  msg_init_headers();
3830 
3831  /*
3832  * Check if news / mail / save group
3833  */
3834  if (group->type == GROUP_TYPE_MAIL || group->type == GROUP_TYPE_SAVE) {
3836  return FALSE;
3837  }
3838  get_from_name(from_name, group);
3839 #ifdef FORGERY
3840  make_path_header(line);
3841 #endif /* FORGERY */
3842 
3843 #ifdef DEBUG
3844  if (debug & DEBUG_MISC)
3845  error_message(2, "From=[%s] Cancel=[%s]", art->from, from_name);
3846 #endif /* DEBUG */
3847 
3848  if (!strcasestr(from_name, art->from)) {
3849 #ifdef FORGERY
3850  author = FALSE;
3851 #else
3853  return redraw_screen;
3854 #endif /* FORGERY */
3855  }
3856 
3857  {
3858  char *smsg;
3859  char buff[LEN];
3860  char keycancel[MAXKEYLEN], keyquit[MAXKEYLEN], keysupersede[MAXKEYLEN];
3861 
3862  snprintf(buff, sizeof(buff), _(txt_cancel_article),
3866 
3867  func = prompt_slk_response(default_func, post_delete_keys,
3868  "%s", sized_message(&smsg, buff, art->subject));
3869  free(smsg);
3870 
3871  switch (func) {
3872  case POST_CANCEL:
3873  break;
3874 
3875  case POST_SUPERSEDE:
3876  repost_article(note_h.newsgroups, respnum, TRUE, &pgart);
3877  return TRUE; /* force screen redraw */
3878 
3879  default:
3880  return redraw_screen;
3881  }
3882  }
3883 
3884  clear_message();
3885 
3886  joinpath(cancel, sizeof(cancel), homedir, TIN_CANCEL_NAME);
3887 #ifdef APPEND_PID
3888  snprintf(cancel + strlen(cancel), sizeof(cancel) - strlen(cancel), ".%ld", (long) process_id);
3889 #endif /* APPEND_PID */
3890  if ((fp = fopen(cancel, "w")) == NULL) {
3891  perror_message(_(txt_cannot_open), cancel);
3892  return redraw_screen;
3893  }
3894 
3895 #ifdef HAVE_FCHMOD
3896  fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
3897 #else
3898 # ifdef HAVE_CHMOD
3899  chmod(cancel, (mode_t) (S_IRUSR|S_IWUSR));
3900 # endif /* HAVE_CHMOD */
3901 #endif /* HAVE_FCHMOD */
3902 
3903 #ifdef FORGERY
3904  if (!author) {
3905  char line2[HEADER_LEN];
3906 
3907  snprintf(line2, sizeof(line2), "cyberspam!%s", line);
3908  msg_add_header("Path", line2);
3909  msg_add_header("From", from_name);
3910  msg_add_header("Sender", note_h.from);
3911  snprintf(line, sizeof(line), "<cancel.%s", note_h.messageid + 1);
3912  msg_add_header("Message-ID", line);
3913  msg_add_header("X-Cancelled-By", from_name);
3914  /*
3915  * Original Subject is includet in the body but some
3916  * stupid bots like it in the header as well
3917  */
3918  msg_add_header("X-Orig-Subject", note_h.subj);
3919  } else {
3920  msg_add_header("Path", line);
3921  if (art->name)
3922  snprintf(line, sizeof(line), "%s <%s>", art->name, art->from);
3923  else
3924  snprintf(line, sizeof(line), "<%s>", art->from);
3925  msg_add_header("From", line);
3928  }
3929 #else
3930  msg_add_header("From", from_name);
3933 #endif /* FORGERY */
3934  snprintf(buf, sizeof(buf), "cmsg cancel %s", note_h.messageid);
3935  msg_add_header("Subject", buf);
3936 
3937  /*
3938  * remove duplicates from Newsgroups header
3939  */
3941  msg_add_header("Newsgroups", note_h.newsgroups);
3942  if (group->attribute->prompt_followupto)
3943  msg_add_header("Followup-To", "");
3944  snprintf(buf, sizeof(buf), "cancel %s", note_h.messageid);
3945  msg_add_header("Control", buf);
3946 
3947  /* TODO: does this catch x-posts to moderated groups? */
3948  if (group->moderated == 'm')
3949  msg_add_header("Approved", from_name);
3950 
3951  if (group->attribute->organization != NULL)
3952  msg_add_header("Organization", random_organization(group->attribute->organization));
3953 
3954  if (note_h.distrib)
3955  msg_add_header("Distribution", note_h.distrib);
3956  else if (*my_distribution)
3957  msg_add_header("Distribution", my_distribution);
3958 
3959  /* some ppl. like X-Headers: in cancels */
3961 
3963  msg_free_headers();
3964 
3965 #ifdef FORGERY
3966  if (author) {
3967  fprintf(fp, "%s", txt_article_cancelled);
3969  } else {
3970  rewind(pgart.raw);
3971  copy_fp(pgart.raw, fp);
3972  }
3973  fclose(fp);
3974  invoke_editor(cancel, start_line_offset, group);
3975 #else
3976  fprintf(fp, "%s", txt_article_cancelled);
3978  fclose(fp);
3979 #endif /* FORGERY */
3980 
3981  redraw_screen = TRUE;
3982  oldraw = RawState();
3984 
3985 #ifdef FORGERY
3986  show_cancel_info(author, FALSE);
3987 #else
3988  show_cancel_info();
3989 #endif /* FORGERY */
3990  Raw(oldraw);
3991 
3992  if (!(fp = fopen(cancel, "r"))) {
3993  /* Oops */
3994  unlink(cancel);
3995  clear_message();
3996  return redraw_screen;
3997  }
3998  parse_rfc822_headers(&hdr, fp, NULL);
3999  fclose(fp);
4000 
4001  forever {
4002  {
4003  char *smsg;
4004  char buff[LEN];
4005  char keycancel[MAXKEYLEN], keyedit[MAXKEYLEN], keyquit[MAXKEYLEN];
4006  int save_signal_context = signal_context;
4007 
4008  snprintf(buff, sizeof(buff), _(txt_quit_cancel),
4012 
4014  func = prompt_slk_response(default_func, post_cancel_keys, "%s", sized_message(&smsg, buff, note_h.subj));
4015  signal_context = save_signal_context;
4016  free(smsg);
4017  }
4018 
4019  switch (func) {
4020  case POST_EDIT:
4021  free_and_init_header(&hdr);
4022  invoke_editor(cancel, start_line_offset, group);
4023  if (!(fp = fopen(cancel, "r"))) {
4024  /* Oops */
4025  unlink(cancel);
4026  clear_message();
4027  return redraw_screen;
4028  }
4029  parse_rfc822_headers(&hdr, fp, NULL);
4030  fclose(fp);
4031  break;
4032 
4033  case POST_CANCEL:
4035  if (submit_news_file(cancel, group, a_message_id)) {
4037  if (hdr.subj)
4038  update_posted_info_file(group->name, 'd', hdr.subj, a_message_id);
4039  else
4041  unlink(cancel);
4042  free_and_init_header(&hdr);
4043  return redraw_screen;
4044  }
4045  break;
4046 
4047  case GLOBAL_QUIT:
4048  case GLOBAL_ABORT:
4049  unlink(cancel);
4050  clear_message();
4051  free_and_init_header(&hdr);
4052  return redraw_screen;
4053  /* NOTREACHED */
4054  break;
4055 
4056  default:
4057  break;
4058  }
4059  }
4060  /* NOTREACHED */
4061  return redraw_screen;
4062 }
4063 
4064 
4065 #define FromSameUser (strcasestr(from_name, arts[respnum].from))
4066 #ifndef FORGERY
4067 # define NotSuperseding (!supersede || (!FromSameUser) || art_type != GROUP_TYPE_NEWS)
4068 # define Superseding (supersede && FromSameUser && art_type == GROUP_TYPE_NEWS)
4069 #else
4070 # define NotSuperseding (!supersede || art_type != GROUP_TYPE_NEWS)
4071 # define Superseding (supersede && art_type == GROUP_TYPE_NEWS)
4072 #endif /* !FORGERY */
4073 
4074 /*
4075  * Repost an already existing article to another group (ie. local group)
4076  */
4077 int
4079  const char *groupname,
4080  int respnum,
4081  t_bool supersede,
4082  t_openartinfo *artinfo)
4083 {
4084  FILE *fp;
4085  char buf[HEADER_LEN];
4086  char from_name[HEADER_LEN];
4087  char full_name[128];
4088  char user_name[128];
4089  int art_type = GROUP_TYPE_NEWS;
4090  int ret_code = POSTED_NONE;
4091  struct t_group *group;
4092  struct t_header note_h = artinfo->hdr;
4093  t_bool force_command = FALSE;
4094 # ifdef FORGERY
4095  char line[HEADER_LEN];
4096 # endif /* FORGERY */
4097  t_function func, default_func = GLOBAL_POST;
4098 
4099  msg_init_headers();
4100 
4101  /*
4102  * remove duplicates from Newsgroups header
4103  */
4105 
4106  /*
4107  * Check if any of the newsgroups are moderated.
4108  */
4109  if ((group = check_moderated(groupname, &art_type, _(txt_art_not_posted))) == NULL)
4110  return ret_code;
4111 
4112  /*
4113  * check for GROUP_TYPE_MAIL
4114  */
4115  if (group->attribute->mailing_list)
4116  art_type = GROUP_TYPE_MAIL;
4117 
4118  if (art_type == GROUP_TYPE_MAIL && supersede) {
4119  error_message(3, _("Can't supersede in mailgroups, try repost instead.")); /* TODO: -> lang.c */
4120  return ret_code;
4121  }
4122 
4123  if ((fp = fopen(article_name, "w")) == NULL) {
4125  return ret_code;
4126  }
4127 #ifdef HAVE_FCHMOD
4128  fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
4129 #else
4130 # ifdef HAVE_CHMOD
4131  chmod(article_name, (mode_t) (S_IRUSR|S_IWUSR));
4132 # endif /* HAVE_CHMOD */
4133 #endif /* HAVE_FCHMOD */
4134 
4135  get_from_name(from_name, group);
4136  get_user_info(user_name, full_name);
4137 
4138  if (Superseding) {
4139 
4140 # ifdef FORGERY
4141  make_path_header(line);
4142  msg_add_header("Path", line);
4143 
4144  msg_add_header("From", (note_h.from ? note_h.from : from_name));
4145 
4146  find_reply_to_addr(line, FALSE, &artinfo->hdr);
4147  if (*line)
4148  msg_add_header("Reply-To", line);
4149 
4150  msg_add_header("X-Superseded-By", from_name);
4151 
4152  if (note_h.org)
4153  msg_add_header("Organization", note_h.org);
4154 
4155  snprintf(line, sizeof(line), "<supersede.%s", note_h.messageid + 1);
4156  msg_add_header("Message-ID", line);
4157  if (FromSameUser) { /* just add can-key for own articles */
4159  }
4160 # else
4161  msg_add_header("From", from_name);
4162  if (*reply_to)
4163  msg_add_header("Reply-To", reply_to);
4166 # endif /* FORGERY */
4167  msg_add_header("Supersedes", note_h.messageid);
4168 
4169  if (note_h.followup)
4170  msg_add_header("Followup-To", note_h.followup);
4171 
4172  if (note_h.keywords)
4173  msg_add_header("Keywords", note_h.keywords);
4174 
4175  if (note_h.summary)
4176  msg_add_header("Summary", note_h.summary);
4177 
4178  if (note_h.distrib)
4179  msg_add_header("Distribution", note_h.distrib);
4180  } else { /* !Superseding */
4181  msg_add_header("From", from_name);
4182  if (*reply_to)
4183  msg_add_header("Reply-To", reply_to);
4184  }
4185  msg_add_header("Subject", note_h.subj);
4186 
4187  if (group->attribute->mailing_list)
4188  msg_add_header("To", group->attribute->mailing_list);
4189  else {
4190  msg_add_header("Newsgroups", groupname);
4192  }
4193 
4194  if (note_h.references) {
4196  msg_add_header("References", buf);
4197  }
4198  if (NotSuperseding) {
4199  if (group->attribute->organization != NULL)
4200  msg_add_header("Organization", random_organization(group->attribute->organization));
4201  else if (*default_organization)
4203 
4204  if (*reply_to)
4205  msg_add_header("Reply-To", reply_to);
4206 
4207  if (*my_distribution)
4208  msg_add_header("Distribution", my_distribution);
4209 
4210  } else {
4211  if (note_h.org)
4212  msg_add_header("Organization", note_h.org);
4213  else {
4214  if (group->attribute->organization != NULL)
4215  msg_add_header("Organization", random_organization(group->attribute->organization));
4216  else if (*default_organization)
4218  }
4219  }
4220 
4221  /*
4222  * some ppl. like X-Headers: in reposts
4223  * X-Headers got lost on supersede, re-add
4224  */
4226 
4228  msg_free_headers();
4229 
4230  if (NotSuperseding) {
4231  /*
4232  * all string lengths are calculated to a maximum line length
4233  * of 76 characters, this should look ok (sven@tin.org)
4234  *
4235  * TODO : use strunc() on note_h.subj?
4236  */
4237  fprintf(fp, "[ %-*s ]\n", (int) (72 + strlen(_(txt_article_reposted)) - strwidth(_(txt_article_reposted))), _(txt_article_reposted));
4238  fprintf(fp, "[ From: %-*s ]\n", (int) (66 + strlen(note_h.from) - strwidth(note_h.from)), note_h.from);
4239  fprintf(fp, "[ Subject: %-*s ]\n", (int) (63 + strlen(note_h.subj) - strwidth(note_h.subj)), note_h.subj);
4240  fprintf(fp, "[ Newsgroups: %-*s ]\n", (int) (60 + strlen(note_h.newsgroups) - strwidth(note_h.newsgroups)), note_h.newsgroups);
4241  if (note_h.messageid)
4242  fprintf(fp, "[ Message-ID: %-60s ]\n\n", note_h.messageid);
4243  } else /* don't break long lines if superseeding. TODO: what about uu/mime-parts? */
4244  resize_article(FALSE, artinfo);
4245 
4246  {
4247  int i = 0;
4248 
4249  while (artinfo->cookl[i].flags & C_HEADER) /* skip headers in cooked art if any */
4250  i++;
4251  if (i) /* cooked art contained any headers, so skip also the header/body separator */
4252  i++;
4253  fseek(artinfo->cooked, artinfo->cookl[i].offset, SEEK_SET);
4254  copy_fp(artinfo->cooked, fp);
4255  }
4256 
4257  /* only append signature when NOT superseding own articles */
4258  if (NotSuperseding && group->attribute->signature_repost)
4259  msg_write_signature(fp, FALSE, group);
4260 
4261  fclose(fp);
4262 
4263  /*
4264  * on supersede change default-key
4265  *
4266  * FIXME: this is only useful when entering the editor.
4267  * After leaving the editor it should be GLOBAL_POST
4268  */
4269  if (Superseding) {
4270  default_func = POST_EDIT;
4271  force_command = TRUE;
4272  /* rebreak long-lines that we don't grabble screen if user aborts posting ... */
4273  resize_article(TRUE, artinfo);
4274  }
4275 
4276  func = default_func;
4277  if (!force_command) {
4278  char *smsg;
4279  char buff[LEN];
4280  char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
4281  char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
4282  char keymenu[MAXKEYLEN];
4283 #ifdef HAVE_ISPELL
4284  char keyispell[MAXKEYLEN];
4285 #endif /* HAVE_ISPELL */
4286 #ifdef HAVE_PGP_GPG
4287  char keypgp[MAXKEYLEN];
4288 #endif /* HAVE_PGP_GPG */
4289 
4290 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
4291  snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4294  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
4295  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
4299 #else
4300 # ifdef HAVE_ISPELL
4301  snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4304  printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
4308 # else
4309 # ifdef HAVE_PGP_GPG
4310  snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4313  printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
4317 # else
4318  snprintf(buff, sizeof(buff), _(txt_quit_edit_xpost),
4324 # endif /* HAVE_PGP_GPG */
4325 # endif /* HAVE_ISPELL */
4326 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
4327 
4328  func = prompt_slk_response(default_func, post_post_keys,
4329  "%s", sized_message(&smsg, buff, note_h.subj));
4330  free(smsg);
4331  }
4333 }
4334 
4335 
4336 static void
4338  const char *headers)
4339 {
4340  FILE *fp = NULL;
4341  char *ptr;
4342  char **x_hdrs = NULL;
4343  char file[PATH_LEN];
4344  char line[HEADER_LEN];
4345  int num_x_hdrs = 0;
4346  int i;
4347 
4348  if (!headers)
4349  return;
4350 
4351  if (headers[0] != '/' && headers[0] != '~' && headers[0] != '!') {
4352  STRCPY(line, headers);
4353  if ((ptr = strchr(line, ':')) != NULL) {
4354  *ptr++ = '\0';
4355  if (*ptr == ' ' || *ptr == '\t') {
4356  msg_add_header(line, ptr);
4357  return;
4358  }
4359  }
4360  } else {
4361  /*
4362  * without this else a "x_headers=name" without a ':' would be
4363  * treated as a filename in the current dir - IMHO not very useful
4364  */
4365  if (!strfpath(headers, file, sizeof(file), &CURR_GROUP, FALSE))
4366  STRCPY(file, headers);
4367 
4368 #ifndef DONT_HAVE_PIPING
4369  if (file[0] == '!') {
4370  if ((fp = popen(file + 1, "r")) == NULL)
4371  return;
4372  }
4373 #endif /* !DONT_HAVE_PIPING */
4374  if (!fp && ((fp = fopen(file, "r")) == NULL))
4375  return;
4376 
4377  while (fgets(line, (int) sizeof(line), fp) != NULL) {
4378  if (line[0] != '\n' && line[0] != '#') {
4379  if (line[0] != ' ' && line[0] != '\t') {
4380  x_hdrs = my_realloc(x_hdrs, (num_x_hdrs + 1) * sizeof(char *));
4381  x_hdrs[num_x_hdrs++] = my_strdup(line);
4382  } else {
4383  if (!num_x_hdrs) /* folded line, but no previous header */
4384  continue;
4385  i = strlen(x_hdrs[num_x_hdrs - 1]);
4386  x_hdrs[num_x_hdrs - 1] = my_realloc(x_hdrs[num_x_hdrs - 1], i + strlen(line) + 1);
4387  strcpy(x_hdrs[num_x_hdrs - 1] + i, line);
4388  }
4389  }
4390  }
4391 
4392  if (num_x_hdrs) {
4393  for (i = 0; i < num_x_hdrs; i++) {
4394  if ((ptr = strchr(x_hdrs[i], ':')) != NULL) {
4395  *ptr++ = '\0';
4396  msg_add_header(x_hdrs[i], ptr);
4397  }
4398  free(x_hdrs[i]);
4399  }
4400  free(x_hdrs);
4401  }
4402 
4403 #ifndef DONT_HAVE_PIPING
4404  if (file[0] == '!')
4405  pclose(fp);
4406  else
4407 #endif /* !DONT_HAVE_PIPING */
4408  fclose(fp);
4409  }
4410 }
4411 
4412 
4413 /*
4414  * Add an x_body attribute to an article if it exists.
4415  * Can be a piece of text or the name of a file to append
4416  * Returns the # of lines appended.
4417  */
4418 static int
4420  FILE *fp_out,
4421  const char *body)
4422 {
4423  FILE *fp;
4424  char *ptr;
4425  char file[PATH_LEN];
4426  char line[HEADER_LEN];
4427  int wrote = 0;
4428 
4429  if (!body)
4430  return 0;
4431 
4432  if (body[0] != '/' && body[0] != '~') { /* FIXME: Unix'ism */
4433  STRCPY(line, body);
4434  if ((ptr = strrchr(line, '\n')) != NULL)
4435  *ptr = '\0';
4436 
4437  fprintf(fp_out, "%s\n", line);
4438  wrote++;
4439  } else {
4440  if (!strfpath(body, file, sizeof(file), &CURR_GROUP, FALSE))
4441  STRCPY(file, body);
4442 
4443  if ((fp = fopen(file, "r")) != NULL) {
4444  while (fgets(line, (int) sizeof(line), fp) != NULL) {
4445  fputs(line, fp_out);
4446  wrote++;
4447  }
4448  fclose(fp);
4449  }
4450  }
4451  if (wrote > 1) {
4452  fputc('\n', fp_out);
4453  wrote++;
4454  }
4455  return wrote;
4456 }
4457 
4458 
4459 /*
4460  * Add the User-Agent header after the other headers
4461  * Strip duplicate newsgroups. Only write followup header if it differs
4462  * from the newsgroups headers.
4463  * Remove Fcc header and return pointer to it. (Must be freed by
4464  * invoking function.)
4465  */
4466 char *
4468  const char *infile,
4469  struct t_group *group)
4470 {
4471  FILE *fp_in, *fp_out;
4472  char *fcc = NULL;
4473  char *l;
4474  char *ptr;
4475  char newsgroups[HEADER_LEN];
4476  char line[HEADER_LEN];
4477  char outfile[PATH_LEN];
4478 
4479  newsgroups[0] = '\0';
4480 
4481  if ((fp_in = fopen(infile, "r")) == NULL)
4482  return NULL;
4483 
4484  snprintf(outfile, sizeof(outfile), "%s.%ld", infile, (long) process_id);
4485 
4486  if ((fp_out = fopen(outfile, "w")) == NULL) {
4487  fclose(fp_in);
4488  return NULL;
4489  }
4490 
4491  while ((l = tin_fgets(fp_in, TRUE)) != NULL) {
4492  if (l[0] == '\0') /* end of headers */
4493  break;
4494 
4495  if ((ptr = parse_header(l, "Newsgroups", FALSE, FALSE, FALSE))) {
4496  strip_double_ngs(ptr);
4497  STRCPY(newsgroups, ptr);
4498  snprintf(line, sizeof(line), "Newsgroups: %s\n", newsgroups);
4499  fputs(line, fp_out);
4500  } else if ((ptr = parse_header(l, "Followup-To", FALSE, FALSE, FALSE))) {
4501  strip_double_ngs(ptr);
4502  /*
4503  * Only write followup header if not blank or followups != newsgroups
4504  */
4505  if (*ptr && strcasecmp(newsgroups, ptr)) {
4506  snprintf(line, sizeof(line), "Followup-To: %s\n", ptr);
4507  fputs(line, fp_out);
4508  }
4509  } else if ((ptr = parse_header(l, "Fcc", FALSE, FALSE, FALSE))) {
4510  FreeIfNeeded(fcc); /* TODO: this is last match counts - desired? or append? */
4511  fcc = my_strdup(ptr);
4512  } else if ((ptr = strchr(l, ':')) != NULL) { /* valid header? */
4513  if (strlen(ptr) > 2) /* skip empty headers ": \0" */
4514  fprintf(fp_out, "%s\n", l);
4515  }
4516  } /* end of headers */
4517 
4518  if ((group && group->attribute->advertising) || (!group && tinrc.advertising)) { /* Add after other headers */
4519  char suffix[HEADER_LEN];
4520 
4521  suffix[0] = '\0';
4522 #if defined(HAVE_SYS_UTSNAME_H) && defined(HAVE_UNAME)
4523  if (*system_info.release) {
4524 # ifdef _AIX
4525  snprintf(suffix, sizeof(suffix), "(%s/%s.%s)",
4526  system_info.sysname, system_info.version, system_info.release);
4527 # else
4528 # if defined(SEIUX) || defined(__riscos)
4529  snprintf(suffix, sizeof(suffix), "(%s/%s)",
4530  system_info.version, system_info.release);
4531 # else
4532  snprintf(suffix, sizeof(suffix), "(%s/%s (%s))",
4533  system_info.sysname, system_info.release, system_info.machine);
4534 # endif /* SEIUX || __riscos */
4535 # endif /* _AIX */
4536  }
4537 #endif /* HAVE_SYS_UTSNAME_H && HAVE_UNAME */
4538 #ifdef SYSTEM_NAME
4539  if (!*suffix && strlen(SYSTEM_NAME))
4540  snprintf(suffix, sizeof(suffix), "(%s)", SYSTEM_NAME);
4541 #endif /* SYSTEM_NAME */
4542 
4543  fprintf(fp_out, "User-Agent: %s/%s-%s (\"%s\") %s\n",
4545  }
4546 
4547  fputs("\n", fp_out); /* header/body separator */
4548 
4549  while ((l = tin_fgets(fp_in, FALSE)) != NULL)
4550  fprintf(fp_out, "%s\n", l);
4551 
4552  fclose(fp_out);
4553  fclose(fp_in);
4554  rename_file(outfile, infile);
4555  return fcc;
4556 }
4557 
4558 
4559 static t_bool
4561  const char *infile)
4562 {
4563  FILE *fp_in, *fp_out;
4564  char *line;
4565  char *p;
4566  char from_name[HEADER_LEN];
4567  char outfile[PATH_LEN];
4568  int r;
4569  t_bool from_found = FALSE;
4570  t_bool in_header = TRUE;
4571 
4572  if ((fp_in = fopen(infile, "r")) != NULL) {
4573  snprintf(outfile, sizeof(outfile), "%s.%ld", infile, (long) process_id);
4574  if ((fp_out = fopen(outfile, "w")) != NULL) {
4575  strcpy(from_name, "From: ");
4576  if (*tinrc.mail_address)
4577  snprintf(from_name + 6, sizeof(from_name) - 7, "%s", tinrc.mail_address);
4578  else
4579  get_from_name(from_name + 6, (struct t_group *) 0);
4580 
4581 # ifdef DEBUG
4582  if (debug & DEBUG_MISC)
4583  wait_message(2, "insert_from_header [%s]", from_name + 6);
4584 # endif /* DEBUG */
4585 
4586  while ((line = tin_fgets(fp_in, in_header)) != NULL) {
4587  if (in_header && !strncasecmp(line, "From: ", 6)) {
4588  char from_buff[HEADER_LEN];
4589 
4590  from_found = TRUE;
4591  STRCPY(from_buff, line + 6);
4592  unfold_header(from_buff);
4593 
4594  /* Check the From: line */
4595  /*
4596  * insert_from_header() is only called
4597  * from submit_mail_file() so the 3rd
4598  * arg is TRUE
4599  */
4600 # ifdef CHARSET_CONVERSION
4601  p = rfc1522_encode(from_buff, txt_mime_charsets[tinrc.mm_network_charset], TRUE);
4602 # else
4603  p = rfc1522_encode(from_buff, tinrc.mm_charset, TRUE);
4604 # endif /* CHARSET_CONVERSION */
4605  r = gnksa_check_from(p);
4606  if (r > GNKSA_OK && r < GNKSA_MISSING_REALNAME) { /* error in address */
4607  error_message(2, _(txt_invalid_from), from_buff);
4608  free(p);
4609  unlink(outfile);
4610  fclose(fp_out);
4611  fclose(fp_in);
4612  return FALSE;
4613  }
4614  free(p);
4615  }
4616  if (*line == '\0' && in_header) {
4617  if (!from_found) {
4618  /* Check the From: line */
4619 # ifdef CHARSET_CONVERSION
4620  p = rfc1522_encode(from_name, txt_mime_charsets[tinrc.mm_network_charset], FALSE);
4621 # else
4622  p = rfc1522_encode(from_name, tinrc.mm_charset, FALSE);
4623 # endif /* CHARSET_CONVERSION */
4624  r = gnksa_check_from(p + 6);
4625  if (r > GNKSA_OK && r < GNKSA_MISSING_REALNAME) { /* error in address */
4626  error_message(2, _(txt_invalid_from), from_name + 6);
4627  free(p);
4628  unlink(outfile);
4629  fclose(fp_out);
4630  fclose(fp_in);
4631  return FALSE;
4632  }
4633  free(p);
4634  fprintf(fp_out, "%s\n", from_name);
4635  }
4636  in_header = FALSE;
4637  }
4638  fprintf(fp_out, "%s\n", line);
4639  }
4640 
4641  fclose(fp_out);
4642  fclose(fp_in);
4643  rename_file(outfile, infile);
4644 
4645  return TRUE;
4646  } else
4647  fclose(fp_in);
4648  }
4649  return FALSE;
4650 }
4651 
4652 
4653 /*
4654  * Copy the appropriate reply-to address
4655  * from Reply-To (or From as a fallback) into 'from_addr'
4656  * If 'parse' is set, full syntax validation is performed and
4657  * the address portion is split off.
4658  */
4659 static void
4661  char *from_addr,
4662  t_bool parse,
4663  struct t_header *hdr)
4664 {
4665  char fname[HEADER_LEN];
4666  const char *ptr;
4667 
4668  /*
4669  * we shouldn't see any articles without a From: (and a Reply-To:) line,
4670  * but for the rare case a fallback to '<>' is better than to crash.
4671  */
4672  ptr = (hdr->replyto) ? hdr->replyto : (hdr->from ? hdr->from : "<>");
4673 
4674  /*
4675  * We do this to save a redundant strcpy when we don't want to parse
4676  */
4677  if (parse) {
4678 #if 1
4679  /* TODO: Return code ignored? */
4680  parse_from(ptr, from_addr, fname);
4681 #else
4682  /* Or should we decode from_addr? */
4683  parse_from(ptr, tmp, fname);
4684  strcpy(from_addr, rfc1522_decode(tmp));
4685 #endif /* 1 */
4686  } else
4687  strcpy(from_addr, ptr);
4688 }
4689 
4690 
4691 /*
4692  * If any arts have been posted by the user reread the active
4693  * file so that they are shown in the unread articles number
4694  * for each group at the group selection level.
4695  */
4696 t_bool
4698  void)
4699 {
4700  int i;
4701  t_artnum old_min;
4702  t_artnum old_max;
4703  struct t_group *group;
4704  t_bool modified = FALSE;
4705 
4708 
4709  for_each_group(i) {
4710  if ((group = &active[i])) {
4711  if (group->subscribed && group->art_was_posted) {
4712  group->art_was_posted = FALSE;
4713 
4714  wait_message(0, _(txt_group_rereading), group->name);
4715  old_min = group->xmin;
4716  old_max = group->xmax;
4717  group_get_art_info(group->spooldir, group->name, group->type, &group->count, &group->xmax, &group->xmin);
4718 
4719  if (group->newsrc.num_unread > group->count) {
4720 #ifdef DEBUG
4721  if (debug & DEBUG_NEWSRC) { /* TODO: is this the right debug-level? */
4722  my_printf(cCRLF "Unread WRONG grp=[%s] unread=[%"T_ARTNUM_PFMT"] count=[%"T_ARTNUM_PFMT"]",
4723  group->name, group->newsrc.num_unread, group->count);
4724  my_flush();
4725  }
4726 #endif /* DEBUG */
4727  group->newsrc.num_unread = group->count;
4728  }
4729  if (group->xmin != old_min || group->xmax != old_max) {
4730 #ifdef DEBUG
4731  if (debug & DEBUG_NEWSRC) { /* TODO: is this the right debug-level? */
4732  my_printf(cCRLF "Min/Max DIFF grp=[%s] old=[%"T_ARTNUM_PFMT"-%"T_ARTNUM_PFMT"] new=[%"T_ARTNUM_PFMT"-%"T_ARTNUM_PFMT"]",
4733  group->name, old_min, old_max, group->xmin, group->xmax);
4734  my_flush();
4735  }
4736 #endif /* DEBUG */
4737  expand_bitmap(group, 0);
4738  modified = TRUE;
4739  }
4740  clear_message();
4741  }
4742  }
4743  }
4744  }
4745  return modified;
4746 }
4747 
4748 
4749 /*
4750  * If posting was successful parse the Newgroups: line and set a flag in each
4751  * posted to newsgroup for later processing to update num of unread articles
4752  */
4753 static void
4755  char *newsgroups)
4756 {
4757  char *src, *dst;
4758  char groupname[HEADER_LEN] = { '\0' };
4759  struct t_group *group;
4760 
4761  /*
4762  * remove duplicates from Newsgroups header
4763  */
4764  strip_double_ngs(newsgroups);
4765 
4766  src = newsgroups;
4767  dst = groupname;
4768 
4769  while (*src) {
4770  if (*src != ' ')
4771  *dst = *src;
4772 
4773  src++;
4774  if (*dst == ',' || *dst == '\0') {
4775  *dst = '\0';
4776  group = group_find(groupname, FALSE);
4777  if (group != NULL && group->subscribed) {
4779  group->art_was_posted = TRUE;
4780  }
4781  dst = groupname;
4782  } else
4783  dst++;
4784  }
4785 }
4786 
4787 
4788 static t_bool
4790  const char *file,
4791  struct t_group *group,
4792  FILE *articlefp,
4793  t_bool include_text)
4794 {
4795  FILE *fp;
4796  char *fcc;
4797  char buf[HEADER_LEN];
4798  char mail_to[HEADER_LEN];
4799  struct t_header hdr;
4800  t_bool mailed = FALSE;
4801 
4802  fcc = checknadd_headers(file, group);
4803 
4804  if (insert_from_header(file)) {
4805  if ((fp = fopen(file, "r"))) {
4806  parse_rfc822_headers(&hdr, fp, NULL);
4807  fclose(fp);
4808  if (get_recipients(&hdr, mail_to, sizeof(mail_to) - 1)) {
4809  wait_message(0, _(txt_mailing_to), mail_to);
4810 
4811  /* Use group-attribute for mailing_list */
4812 
4813  if (articlefp != NULL)
4814  compose_mail_mime_forwarded(file, articlefp, include_text, group);
4815  else /* text/plain */
4816  compose_mail_text_plain(file, group);
4817 
4818  strfmailer(mailer, hdr.subj, mail_to, file, buf, sizeof(buf), tinrc.mailer_format);
4819 
4820  if (invoke_cmd(buf))
4821  mailed = TRUE;
4822  } else
4824  free_and_init_header(&hdr);
4825  }
4826  }
4827  if (fcc != NULL) {
4828  if (mailed && strlen(fcc)) {
4829  char a_mailbox[PATH_LEN];
4830 
4831  if (!strfpath(fcc, a_mailbox, sizeof(a_mailbox), group, TRUE))
4832  STRCPY(a_mailbox, fcc);
4833  if ((errno = append_mail(file, userid, a_mailbox)))
4835  }
4836  FreeIfNeeded(fcc);
4837  }
4838  return mailed;
4839 }
4840 
4841 
4842 #ifdef FORGERY
4843 static void
4844 make_path_header(
4845  char *line)
4846 {
4847  char full_name[128];
4848  char user_name[128];
4849 
4850  get_user_info(user_name, full_name);
4851  sprintf(line, "%s!%s", domain_name, user_name);
4852  return;
4853 }
4854 #endif /* FORGERY */
4855 
4856 
4857 /*
4858  * Splits a list of e-mail addresses according to RFC 2822 into separate
4859  * strings containing one address each. Returns an array of pointers to
4860  * those strings. Side effects: changes the value of cnt to the number of
4861  * addresses found.
4862  *
4863  * You must free each of the strings separately. You must free the array you
4864  * got returned.
4865  */
4866 static char **
4868  const char *addresses,
4869  unsigned int *cnt)
4870 {
4871  char **argv = NULL;
4872  char *addr;
4873  const char *start, *end, *curr;
4874  size_t len, addr_len;
4875  unsigned int argc = 0, dquotes = 0, parens = 0;
4876 
4877  if (!addresses) {
4878  *cnt = 0;
4879  return NULL;
4880  }
4881 
4882  len = strlen(addresses);
4883  curr = addresses;
4884 
4885  while (len > 0) {
4886  /* skip white space at beginning */
4887  while (len && isspace((int) *curr)) {
4888  curr++;
4889  len--;
4890  }
4891  if (len == 0)
4892  break;
4893 
4894  /* new address starts here */
4895  /*
4896  * Commas are the separator between addresses. But quoted-string areas
4897  * (text between double quotation marks) and comment areas (text
4898  * between braces) have a special meaning where a comma is not to be
4899  * treated as a separator. Inside those areas there may be
4900  * quoted-pairs (backslash followed by a character that now doesn't
4901  * have any special meaning). Comments may be nested, too,
4902  * quoted-strings cannot.
4903  */
4904  start = curr;
4905  while (len && (*curr != ',')) {
4906  switch (*curr) {
4907  case '\"':
4908  /* quoted-string area */
4909  curr++;
4910  len--;
4911  dquotes++;
4912  while (len && dquotes) {
4913  switch (*curr) {
4914  case '\"':
4915  /* end of quoted-string */
4916  dquotes--;
4917  break;
4918 
4919  case '\\':
4920  /* quoted-pair: ignore next char */
4921  if (len > 1) {
4922  curr++;
4923  len--;
4924  }
4925  break;
4926 
4927  default:
4928  /* nothing special, just step over it */
4929  break;
4930  }
4931  curr++;
4932  len--;
4933  }
4934  break;
4935 
4936  case '(':
4937  /* comment area */
4938  curr++;
4939  len--;
4940  parens++;
4941  while (len && parens) {
4942  switch (*curr) {
4943  case '(':
4944  /* comments may be nested */
4945  parens++;
4946  break;
4947 
4948  case ')':
4949  parens--;
4950  break;
4951 
4952  case '\\':
4953  /* quoted-pair: ignore next char */
4954  if (len > 1) {
4955  curr++;
4956  len--;
4957  }
4958  break;
4959 
4960  default:
4961  /* nothing special, just step over it */
4962  break;
4963  }
4964  curr++;
4965  len--;
4966  }
4967  break;
4968 
4969  default:
4970  /* nothing special, just step over it */
4971  break;
4972  }
4973  if (len > 0) {
4974  /* avoid going after end of addresses (may occur in broken address lists) */
4975  curr++;
4976  len--;
4977  }
4978  }
4979  /* end of address */
4980  end = curr;
4981  if (end > start) {
4982  end--;
4983  while ((end > start) && isspace((int) *end))
4984  end--; /* skip trailing white space */
4985  if (!isspace((int) *end))
4986  end++;
4987  addr_len = end - start;
4988  if (addr_len > 0) {
4989  addr = my_malloc(addr_len + 1);
4990  strncpy(addr, start, addr_len);
4991  addr[addr_len] = '\0';
4992  argc++;
4993  argv = my_realloc(argv, argc * sizeof(char *));
4994  argv[argc - 1] = addr;
4995  }
4996  }
4997  if (len > 0) {
4998  curr++;
4999  len--;
5000  }
5001  }
5002  /* end of buffer, end of addresses. now return array of strings */
5003  *cnt = argc;
5004  return argv;
5005 }
5006 
5007 
5008 /*
5009  * Returns TRUE if address is in addresses, FALSE otherwise. Only e-mail
5010  * addresses are of interest here, comments and quoted-strings are ignored.
5011  */
5012 static t_bool
5014  const char *addresses,
5015  const char *address)
5016 {
5017  char **addr_list;
5018  char *curr_address = NULL, *this_address;
5019  t_bool found = FALSE;
5020  unsigned int num_addr = 0, i;
5021 
5022  if ((addresses == NULL) || (address == NULL))
5023  return FALSE;
5024 
5025  addr_list = split_address_list(addresses, &num_addr);
5026  if (num_addr == 0)
5027  return FALSE;
5028 
5029  this_address = my_malloc(strlen(address) + 1);
5030  strip_name(address, this_address);
5031 
5032  for (i = 0; i < num_addr; i++) {
5033  curr_address = my_realloc(curr_address, strlen(addr_list[i]) + 1);
5034  strip_name(addr_list[i], curr_address);
5035  if (!strcasecmp(curr_address, this_address))
5036  found = TRUE;
5037  FreeIfNeeded(addr_list[i]);
5038  }
5039  FreeIfNeeded(addr_list);
5040  FreeIfNeeded(curr_address);
5041  FreeIfNeeded(this_address);
5042 
5043  return found;
5044 }
5045 
5046 
5047 /*
5048  * Gets all recipient addresses in header, i.e. contents of To:, Cc: and
5049  * Bcc:, but strips out duplicates. Returns number of recipients found.
5050  * Side effects: changes content of buf to space separated list of recipient
5051  * addresses
5052  */
5053 static unsigned int
5055  struct t_header *hdr,
5056  char *buf,
5057  size_t buflen)
5058 {
5059  char **to_addresses, **cc_addresses, **bcc_addresses, **all_addresses;
5060  char *dest, *src;
5061  unsigned int num_to = 0, num_cc = 0, num_bcc = 0, num_all, j = 0, i;
5062 
5063  /* get individual e-mail addresses from To, Cc and Bcc headers */
5064  to_addresses = split_address_list(hdr->to, &num_to);
5065  cc_addresses = split_address_list(hdr->cc, &num_cc);
5066  bcc_addresses = split_address_list(hdr->bcc, &num_bcc);
5067 
5068  if (!(num_all = num_to + num_cc + num_bcc))
5069  return 0;
5070 
5071  all_addresses = my_malloc(num_all * sizeof(char *));
5072  for (i = 0; i < num_to; i++, j++) {
5073  all_addresses[j] = my_malloc(strlen(to_addresses[i]) + 1);
5074  strip_name(to_addresses[i], all_addresses[j]);
5075  }
5076  for (i = 0; i < num_cc; i++, j++) {
5077  all_addresses[j] = my_malloc(strlen(cc_addresses[i]) + 1);
5078  strip_name(cc_addresses[i], all_addresses[j]);
5079  }
5080  for (i = 0; i < num_bcc; i++, j++) {
5081  all_addresses[j] = my_malloc(strlen(bcc_addresses[i]) + 1);
5082  strip_name(bcc_addresses[i], all_addresses[j]);
5083  }
5084 
5085  /* strip double addresses */
5086  for (i = 0; i < (num_all - 1); i++) {
5087  if (!all_addresses[i])
5088  continue;
5089  for (j = i + 1; j < num_all; j++) {
5090  if (!all_addresses[j])
5091  continue;
5092  if (!strcasecmp(all_addresses[i], all_addresses[j]))
5093  FreeAndNull(all_addresses[j]);
5094  }
5095  }
5096  /* build list of space separated e-mail addresses */
5097  dest = buf;
5098  for (i = 0; i < num_all; i++) {
5099  if (all_addresses[i]) {
5100  for (src = all_addresses[i]; (*src && buflen); src++, dest++) {
5101  *dest = *src;
5102  buflen--;
5103  }
5104  if (buflen > 0) {
5105  *dest++ = ' ';
5106  buflen--;
5107  }
5108  }
5109  }
5110  if (dest > buf)
5111  *--dest = '\0';
5112 
5113  /* free all allocated memory */
5114  for (i = 0; i < num_to; i++)
5115  FreeIfNeeded(to_addresses[i]);
5116  FreeIfNeeded(to_addresses);
5117  for (i = 0; i < num_cc; i++)
5118  FreeIfNeeded(cc_addresses[i]);
5119  FreeIfNeeded(cc_addresses);
5120  for (i = 0; i < num_bcc; i++)
5121  FreeIfNeeded(bcc_addresses[i]);
5122  FreeIfNeeded(bcc_addresses);
5123  for (i = 0; i < num_all; i++)
5124  FreeIfNeeded(all_addresses[i]);
5125  FreeIfNeeded(all_addresses);
5126 
5127  return num_all;
5128 }
5129 
5130 
5131 #ifdef EVIL_INSIDE
5132 /*
5133  * build_messageid()
5134  * returns *(<Message-ID>)
5135  */
5136 static const char *
5137 build_messageid(
5138  void)
5139 {
5140  int i;
5141  size_t j;
5142  static char buf[NNTP_STRLEN];