"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/post.c" (1 Dec 2020, 155485 Bytes) of package /linux/misc/tin-2.4.5.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "post.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.4_vs_2.4.5.

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