"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/post.c" (8 Dec 2017, 153029 Bytes) of package /linux/misc/tin-2.4.2.tar.xz:


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

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