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