"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/post.c" (20 Nov 2019, 153581 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


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

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