"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.

    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]);
 5125     FreeIfNeeded(all_addresses);
 5126 
 5127     return num_all;
 5128 }
 5129 
 5130 
 5131 #ifdef EVIL_INSIDE