"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/post.c" (22 Dec 2021, 162659 Bytes) of package /linux/misc/tin-2.6.1.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.6.0_vs_2.6.1.

A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.


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