"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/post.c" (12 Oct 2016, 152473 Bytes) of package /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : post.c
    4  *  Author    : I. Lea
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2016-10-10
    7  *  Notes     : mail/post/replyto/followup/repost & cancel articles
    8  *
    9  * Copyright (c) 1991-2017 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         char key[1024]; \
   52         char *kptr; \
   53         key[0] = '\0'; \
   54         if ((kptr = build_cankey(id, get_secret())) != NULL) { \
   55             STRCPY(key, kptr); \
   56             free(kptr); \
   57             msg_add_header("Cancel-Key", key); \
   58         } \
   59     }
   60     /*
   61      * only add lock here if we use an external inews
   62      * and generate our own Message-IDs (EVIL_INSIDE)
   63      * otherwise inews.c adds the canlock (if possible:
   64      * i.e EVIL_INSIDE or server passed id on POST or
   65      * user supplied ID by hand) for us!
   66      */
   67 #   ifdef EVIL_INSIDE
   68 #       define ADD_CAN_LOCK(id) { \
   69             char lock[1024]; \
   70             char *lptr = (char *) 0; \
   71             lock[0] = '\0'; \
   72             if ((lptr = build_canlock(id, get_secret())) != NULL) { \
   73                 STRCPY(lock, lptr); \
   74                 free(lptr); \
   75                 msg_add_header("Cancel-Lock", lock); \
   76             } \
   77         }
   78 #   endif /* EVIL_INSIDE */
   79 #else
   80 #   define ADD_CAN_KEY(id)
   81 #   ifdef EVIL_INSIDE
   82 #       define ADD_CAN_LOCK(id)
   83 #   endif /* EVIL_INSIDE */
   84 #endif /* USE_CANLOCK */
   85 
   86 #ifdef EVIL_INSIDE
   87 /* gee! ugly hack - but works */
   88 #   define ADD_MSG_ID_HEADER()  { \
   89         char mid[1024]; \
   90         const char *mptr = (const char *) 0; \
   91         mid[0] = '\0'; \
   92         if ((mptr = build_messageid()) != NULL) { \
   93             STRCPY(mid, mptr); \
   94             msg_add_header("Message-ID", mid); \
   95             ADD_CAN_LOCK(mid); \
   96         } \
   97     }
   98 #else
   99 #   define ADD_MSG_ID_HEADER()
  100 #endif /* EVIL_INSIDE */
  101 
  102 #define MAX_MSG_HEADERS 20  /* shouldn't this be dynamic? */
  103 
  104 /* Different posting types for post_loop() */
  105 #define POST_QUICK      0
  106 #define POST_POSTPONED  1
  107 #define POST_NORMAL     2
  108 #define POST_RESPONSE   3
  109 #define POST_REPOST     4
  110 #define POST_SUPERSEDED 5
  111 
  112 /* When prompting for subject, display no more than 20 characters */
  113 #define DISPLAY_SUBJECT_LEN 20
  114 
  115 static int start_line_offset = 1;       /* used by invoke_editor for line no. */
  116 
  117 char bug_addr[LEN];         /* address to add send bug reports to */
  118 static char my_distribution[LEN];       /* Distribution: */
  119 static char reply_to[LEN];      /* Reply-To: address */
  120 
  121 static struct msg_header {
  122     char *name;
  123     char *text;
  124 } msg_headers[MAX_MSG_HEADERS];
  125 
  126 
  127 /*
  128  * Local prototypes
  129  */
  130 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);
  131 static char **build_nglist(char *ngs_list, int *ngcnt);
  132 static char **split_address_list(const char *addresses, unsigned int *cnt);
  133 static int add_mail_quote(FILE *fp, int respnum);
  134 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);
  135 static int mail_loop(const char *filename, t_function func, char *subject, const char *groupname, const char *prompt, FILE *articlefp);
  136 static int msg_add_x_body(FILE *fp_out, const char *body);
  137 static int msg_write_headers(FILE *fp);
  138 static int post_loop(int type, struct t_group *group, t_function func, const char *posting_msg, int art_type, int offset);
  139 static unsigned int get_recipients(struct t_header *hdr, char *buf, size_t buflen);
  140 static size_t skip_id(const char *id);
  141 static struct t_group *check_moderated(const char *groups, int *art_type, const char *failmsg);
  142 static t_bool address_in_list(const char *addresses, const char *address);
  143 static t_bool append_mail(const char *the_article, const char *addr, const char *the_mailbox);
  144 static t_bool backup_article(const char *the_article);
  145 static t_bool check_for_spamtrap(const char *addr);
  146 static t_bool create_normal_article_headers(struct t_group *group, const char *newsgroups, int art_type);
  147 static t_bool damaged_id(const char *id);
  148 static t_bool fetch_postponed_article(const char tmp_file[], char subject[], char newsgroups[]);
  149 static t_bool insert_from_header(const char *infile);
  150 static t_bool is_crosspost(const char *xref);
  151 static t_bool must_include(const char *id);
  152 static t_bool repair_article(t_function *result, struct t_group *group);
  153 static t_bool stripped_double_ngs(char **newsgroups, int *ngcnt);
  154 static t_bool submit_mail_file(const char *file, struct t_group *group, FILE *articlefp, t_bool include_text);
  155 static t_function prompt_rejected(void);
  156 static t_function prompt_to_send(const char *subject);
  157 static void add_headers(const char *infile, const char *a_message_id);
  158 static void appendid(char **where, const char **what);
  159 static void find_reply_to_addr(char *from_addr, t_bool parse, struct t_header *hdr);
  160 static void join_references(char *buffer, const char *oldrefs, const char *newref);
  161 static void msg_add_header(const char *name, const char *text);
  162 static void msg_add_x_headers(const char *headers);
  163 static void msg_free_headers(void);
  164 static void msg_init_headers(void);
  165 static void post_postponed_article(int ask, const char *subject, const char *newsgroups);
  166 static void postpone_article(const char *the_article);
  167 static void setup_check_article_screen(int *init);
  168 static void show_followup_info(void);
  169 static void strip_double_ngs(char *ngs_list);
  170 static void update_active_after_posting(char *newsgroups);
  171 static void update_posted_info_file(const char *group, int action, const char *subj, const char *a_message_id);
  172 #ifdef FORGERY
  173     static void make_path_header(char *line);
  174     static void show_cancel_info(t_bool author, t_bool use_cache);
  175 #else
  176     static void show_cancel_info(void);
  177 #endif /* FORGERY */
  178 #ifdef EVIL_INSIDE
  179     static const char *build_messageid(void);
  180     static char *radix32(unsigned long int num);
  181 #endif /* EVIL_INSIDE */
  182 #ifdef USE_CANLOCK
  183     static char *build_cankey(const char *messageid, const char *secret);
  184 #endif /* USE_CANLOCK */
  185 
  186 
  187 static t_function
  188 prompt_to_send(
  189     const char *subject)
  190 {
  191     char *smsg;
  192     char buf[LEN];
  193     char keyedit[MAXKEYLEN];
  194     char keyquit[MAXKEYLEN];
  195     char keysend[MAXKEYLEN];
  196 #ifdef HAVE_ISPELL
  197     char keyispell[MAXKEYLEN];
  198 #endif /* HAVE_ISPELL */
  199 #ifdef HAVE_PGP_GPG
  200     char keypgp[MAXKEYLEN];
  201 #endif /* HAVE_PGP_GPG */
  202     t_function func;
  203 
  204 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
  205     snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
  206                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_send_keys)),
  207                     printascii(keyedit, func_to_key(POST_EDIT, post_send_keys)),
  208                     printascii(keyispell, func_to_key(POST_ISPELL, post_send_keys)),
  209                     printascii(keypgp, func_to_key(POST_PGP, post_send_keys)),
  210                     printascii(keysend, func_to_key(POST_SEND, post_send_keys)));
  211 #else
  212 #   ifdef HAVE_ISPELL
  213     snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
  214                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_send_keys)),
  215                     printascii(keyedit, func_to_key(POST_EDIT, post_send_keys)),
  216                     printascii(keyispell, func_to_key(POST_ISPELL, post_send_keys)),
  217                     printascii(keysend, func_to_key(POST_SEND, post_send_keys)));
  218 #   else
  219 #       ifdef HAVE_PGP_GPG
  220     snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
  221                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_send_keys)),
  222                     printascii(keyedit, func_to_key(POST_EDIT, post_send_keys)),
  223                     printascii(keypgp, func_to_key(POST_PGP, post_send_keys)),
  224                     printascii(keysend, func_to_key(POST_SEND, post_send_keys)));
  225 #       else
  226     snprintf(buf, sizeof(buf), _(txt_quit_edit_send),
  227                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_send_keys)),
  228                     printascii(keyedit, func_to_key(POST_EDIT, post_send_keys)),
  229                     printascii(keysend, func_to_key(POST_SEND, post_send_keys)));
  230 #       endif /* HAVE_PGP_GPG */
  231 #   endif /* HAVE_ISPELL */
  232 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
  233 
  234     func = prompt_slk_response(POST_SEND, post_send_keys, "%s",
  235                 sized_message(&smsg, buf, subject));
  236     free(smsg);
  237     return func;
  238 }
  239 
  240 
  241 static t_function
  242 prompt_rejected(
  243     void)
  244 {
  245     char keyedit[MAXKEYLEN], keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
  246 
  247 /* FIXME (what does this mean?) fix screen pos. */
  248     Raw(FALSE);
  249     /* TODO: replace hardcoded key-name in txt_post_error_ask_postpone */
  250     my_fprintf(stderr, "\n\n%s\n\n", _(txt_post_error_ask_postpone));
  251     my_fflush(stderr);
  252     Raw(TRUE);
  253 
  254     return prompt_slk_response(POST_EDIT, post_edit_keys,
  255                 _(txt_quit_edit_postpone),
  256                 printascii(keyquit, func_to_key(GLOBAL_QUIT, post_edit_keys)),
  257                 printascii(keyedit, func_to_key(POST_EDIT, post_edit_keys)),
  258                 printascii(keypostpone, func_to_key(POST_POSTPONE, post_edit_keys)));
  259 }
  260 
  261 
  262 /*
  263  * Set up posting specific environment
  264  */
  265 void
  266 init_postinfo(
  267     void)
  268 {
  269     char *ptr;
  270 
  271     /*
  272      * check environment for REPLYTO
  273      */
  274     reply_to[0] = '\0';
  275     if ((ptr = getenv("REPLYTO")) != NULL)
  276         my_strncpy(reply_to, ptr, sizeof(reply_to) - 1);
  277 
  278     /*
  279      * check environment for DISTRIBUTION
  280      */
  281     my_distribution[0] = '\0';
  282     if ((ptr = getenv("DISTRIBUTION")) != NULL)
  283         my_strncpy(my_distribution, ptr, sizeof(my_distribution) - 1);
  284 }
  285 
  286 
  287 /*
  288  * TODO: add p'o'stpone function here? would be nice but difficult
  289  *       as the postpone fetcher looks for articles with correct headers
  290  */
  291 static t_bool
  292 repair_article(
  293     t_function *result,
  294     struct t_group *group)
  295 {
  296     char keyedit[MAXKEYLEN], keymenu[MAXKEYLEN], keyquit[MAXKEYLEN];
  297     t_function func;
  298 
  299     func = prompt_slk_response(POST_EDIT, post_edit_ext_keys, _(txt_bad_article),
  300                 printascii(keyquit, func_to_key(GLOBAL_QUIT, post_edit_ext_keys)),
  301                 printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_edit_ext_keys)),
  302                 printascii(keyedit, func_to_key(POST_EDIT, post_edit_ext_keys)));
  303 
  304     *result = func;
  305     if (func == POST_EDIT) {
  306         if (invoke_editor(article_name, start_line_offset, group))
  307             return TRUE;
  308     } else if (func == GLOBAL_OPTION_MENU) {
  309         config_page(group->name, signal_context);
  310         return TRUE;
  311     }
  312     return FALSE;
  313 }
  314 
  315 
  316 /*
  317  * make a backup copy of ~/TIN_ARTICLE_NAME, this is necessary since
  318  * submit_news_file adds headers, does q-p conversion etc
  319  * TODO: why not use BACKUP_FILE_EXT like in misc.c?
  320  */
  321 char *
  322 backup_article_name(
  323     const char *the_article)
  324 {
  325     static char name[PATH_LEN];
  326 
  327     snprintf(name, sizeof(name), "%s.bak", the_article);
  328     return name;
  329 }
  330 
  331 
  332 static t_bool
  333 backup_article(
  334     const char *the_article)
  335 {
  336     return backup_file(the_article, backup_article_name(the_article));
  337 }
  338 
  339 
  340 static void
  341 msg_init_headers(
  342     void)
  343 {
  344     int i;
  345 
  346     for (i = 0; i < MAX_MSG_HEADERS; i++) {
  347         msg_headers[i].name = NULL;
  348         msg_headers[i].text = NULL;
  349     }
  350 }
  351 
  352 
  353 static void
  354 msg_free_headers(
  355     void)
  356 {
  357     int i;
  358 
  359     for (i = 0; i < MAX_MSG_HEADERS; i++) {
  360         FreeAndNull(msg_headers[i].name);
  361         FreeAndNull(msg_headers[i].text);
  362     }
  363 }
  364 
  365 
  366 static void
  367 msg_add_header(
  368     const char *name,
  369     const char *text)
  370 {
  371     const char *p;
  372     char *ptr;
  373     char *new_name;
  374     char *new_text = NULL;
  375     int i;
  376     t_bool done = FALSE;
  377 
  378     if (name) {
  379         /*
  380          * Remove : if one is attached to name
  381          */
  382         new_name = my_strdup(name);
  383         ptr = strchr(new_name, ':');
  384         if (ptr)
  385             *ptr = '\0';
  386 
  387         /*
  388          * Check if header already exists and if update text
  389          */
  390         for (i = 0; i < MAX_MSG_HEADERS && msg_headers[i].name; i++) {
  391             if (STRCMPEQ(msg_headers[i].name, new_name)) {
  392                 FreeAndNull(msg_headers[i].text);
  393                 if (text) {
  394                     for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
  395                         ;
  396                     new_text = my_strdup(p);
  397                     ptr = strrchr(new_text, '\n');
  398                     if (ptr)
  399                         *ptr = '\0';
  400 
  401                     msg_headers[i].text = my_strdup(new_text);
  402                     FreeIfNeeded(new_text);
  403                 }
  404                 done = TRUE;
  405             }
  406         }
  407 
  408         /*
  409          * if header does not exist then add it
  410          */
  411         if (i < MAX_MSG_HEADERS && !(done || msg_headers[i].name)) {
  412             msg_headers[i].name = my_strdup(new_name);
  413             if (text) {
  414                 for (p = text; *p && (*p == ' ' || *p == '\t'); p++)
  415                     ;
  416                 new_text = my_strdup(p);
  417                 ptr = strrchr(new_text, '\n');
  418                 if (ptr)
  419                     *ptr = '\0';
  420 
  421                 msg_headers[i].text = my_strdup(new_text);
  422                 FreeIfNeeded(new_text);
  423             }
  424         }
  425         FreeIfNeeded(new_name);
  426     }
  427 }
  428 
  429 
  430 static int
  431 msg_write_headers(
  432     FILE *fp)
  433 {
  434     int i;
  435     int wrote = 1;
  436     char *p;
  437 
  438     for (i = 0; i < MAX_MSG_HEADERS; i++) {
  439         if (msg_headers[i].name) {
  440             fprintf(fp, "%s: %s\n", msg_headers[i].name, BlankIfNull(msg_headers[i].text));
  441             wrote++;
  442             if ((p = msg_headers[i].text)) {
  443                 while ((p = strchr(p, '\n'))) {
  444                     p++;
  445                     wrote++;
  446                 }
  447             }
  448         }
  449     }
  450     fputc('\n', fp);
  451 
  452     return wrote;
  453 }
  454 
  455 
  456 /* TODO: handle optional Message-ID: field */
  457 t_bool
  458 user_posted_messages(
  459     void)
  460 {
  461     FILE *fp;
  462     char buf[LEN];
  463     int no_of_lines = 0;
  464     size_t group_len = 0, i = 0, j, k;
  465     struct t_posted *posted;
  466 
  467     if ((fp = fopen(posted_info_file, "r")) == NULL) {
  468         clear_message();
  469         return FALSE;
  470     }
  471 
  472     while (fgets(buf, (int) sizeof(buf), fp) != NULL)
  473         no_of_lines++;
  474 
  475     if (!no_of_lines) {
  476         fclose(fp);
  477         info_message(_(txt_no_arts_posted));
  478         return FALSE;
  479     }
  480     rewind(fp);
  481     posted = my_malloc((no_of_lines + 1) * sizeof(struct t_posted));
  482 
  483     while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
  484         if (buf[0] == '#' || buf[0] == '\n')
  485             continue;
  486 
  487         for (j = 0, k = 0; buf[j] != '|' && buf[j] != '\n'; j++)
  488             if (k < sizeof(posted[i].date) - 1)
  489                 posted[i].date[k++] = buf[j];   /* posted date */
  490 
  491         if (buf[j] == '\n') {
  492             error_message(3, _(txt_error_corrupted_file), posted_info_file);
  493             fclose(fp);
  494             clear_message();
  495             free(posted);
  496             return FALSE;
  497         }
  498         posted[i].date[k] = '\0';
  499         posted[i + 1].date[0] = '\0';
  500 
  501         posted[i].action = buf[++j];
  502         j += 2;
  503 
  504         for (k = 0; buf[j] != '|' && buf[j] != ','; j++) {
  505             if (k < sizeof(posted[i].group) - 1)
  506                 posted[i].group[k++] = buf[j];
  507         }
  508         if (buf[j] == ',') {
  509             while (buf[j] != '|' && buf[j] != '\n')
  510                 j++;
  511 
  512             if (k > sizeof(posted[i].group) - 5)
  513                 k = sizeof(posted[i].group) - 5;
  514             posted[i].group[k++] = ',';
  515             posted[i].group[k++] = '.';
  516             posted[i].group[k++] = '.';
  517             posted[i].group[k++] = '.';
  518         }
  519         posted[i].group[k] = '\0';
  520         if (k > group_len)
  521             group_len = k;
  522         j++;
  523 
  524         for (k = 0; buf[j] != '\n'; j++) {
  525             if (k < sizeof(posted[i].subj) - 1)
  526                 posted[i].subj[k++] = buf[j];
  527         }
  528         posted[i].subj[k] = '\0';
  529         i++;
  530     }
  531     posted[i].date[0] = '\0';   /* end-marker for display */
  532     fclose(fp);
  533 
  534     if (!(fp = tmpfile())) {
  535         free(posted);
  536         return FALSE;
  537     }
  538     for (; i > 0; i--) {
  539         snprintf(buf, sizeof(buf), "%8s  %c  %-*s  %s",
  540             posted[i - 1].date, posted[i - 1].action,
  541             (int) group_len, posted[i - 1].group, posted[i - 1].subj);
  542         buf[cCOLS - 2] = '\0';
  543         fprintf(fp, "%s%s", buf, cCRLF);
  544     }
  545     free(posted);
  546     info_pager(fp, _(txt_post_history_menu), TRUE);
  547     fclose(fp);
  548     info_pager(NULL, NULL, TRUE); /* free mem */
  549 
  550     return TRUE;
  551 }
  552 
  553 
  554 /*
  555  * TODO:
  556  * - mime-encode subject so we get the right charset (it may be different
  557  *   in subsequent sessions); update user_posted_messages accordingly.
  558  */
  559 static void
  560 update_posted_info_file(
  561     const char *group,
  562     int action,
  563     const char *subj,
  564     const char *a_message_id)
  565 {
  566     FILE *fp;
  567     char *file_tmp;
  568     time_t epoch;
  569 
  570     if (no_write)
  571         return;
  572 
  573     file_tmp = get_tmpfilename(posted_info_file);
  574     if (!backup_file(posted_info_file, file_tmp)) {
  575         error_message(2, _(txt_filesystem_full_backup), posted_info_file);
  576         free(file_tmp);
  577         return;
  578     }
  579 
  580     if ((fp = fopen(posted_info_file, "a")) != NULL) {
  581         int err;
  582         char logdate[10];
  583 
  584         if (time(&epoch) != (time_t) -1) {
  585             if (!my_strftime(logdate, sizeof(logdate) - 1, "%d-%m-%y", localtime(&epoch)))
  586                 strcpy(logdate, "NO  DATE");
  587         } else
  588             strcpy(logdate, "NO  DATE");
  589 
  590         if (*a_message_id) {
  591             char *mid = my_strdup(a_message_id);
  592 
  593             fprintf(fp, "%s|%c|%s|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj), BlankIfNull(str_trim(mid)));
  594             free(mid);
  595         } else
  596             fprintf(fp, "%s|%c|%s|%s\n", logdate, action, BlankIfNull(group), BlankIfNull(subj));
  597 
  598         if ((err = ferror(fp)) || fclose(fp)) {
  599             error_message(2, _(txt_filesystem_full), posted_info_file);
  600             rename_file(file_tmp, posted_info_file);
  601             if (err) {
  602                 clearerr(fp);
  603                 fclose(fp);
  604             }
  605         } else
  606             unlink(file_tmp);
  607     } else
  608         rename_file(file_tmp, posted_info_file);
  609 
  610     free(file_tmp);
  611 }
  612 
  613 
  614 /*
  615  * appends the content of the_article to the_mailbox, with a From_ line of
  616  * addr, does mboxo/mboxrd From_ line quoting if needed (!MMDF-style mbox)
  617  */
  618 static t_bool
  619 append_mail(
  620     const char *the_article,
  621     const char *addr,
  622     const char *the_mailbox)
  623 {
  624     FILE *fp_in, *fp_out;
  625     char *bufp;
  626     char buf[LEN];
  627     time_t epoch;
  628     t_bool mmdf = FALSE;
  629     t_bool rval = FALSE;
  630 #ifndef NO_LOCKING
  631     int fd;
  632     unsigned int retrys = 11;   /* maximum lock retrys + 1 */
  633 #endif /* NO_LOCKING */
  634 
  635     if (!strcasecmp(txt_mailbox_formats[tinrc.mailbox_format], "MMDF") && the_mailbox != postponed_articles_file)
  636         mmdf = TRUE;
  637 
  638     if ((fp_in = fopen(the_article, "r")) == NULL)
  639         return rval;
  640 
  641     if ((fp_out = fopen(the_mailbox, "a+")) != NULL) {
  642 #ifndef NO_LOCKING
  643         fd = fileno(fp_out);
  644         /* TODO: move the retry/error stuff into a function? */
  645         while (--retrys && fd_lock(fd, FALSE))
  646             wait_message(1, _(txt_trying_lock), retrys, the_mailbox);
  647         if (!retrys) {
  648             wait_message(5, _(txt_error_couldnt_lock), the_mailbox);
  649             fclose(fp_out);
  650             fclose(fp_in);
  651             return rval;
  652         }
  653         retrys++;
  654         while (--retrys && !dot_lock(the_mailbox))
  655             wait_message(1, _(txt_trying_dotlock), retrys, the_mailbox);
  656         if (!retrys) {
  657             wait_message(5, _(txt_error_couldnt_dotlock), the_mailbox);
  658             fd_unlock(fd);
  659             fclose(fp_out);
  660             fclose(fp_in);
  661             return rval;
  662         }
  663 #endif /* !NO_LOCKING */
  664 
  665         if (mmdf)
  666             fprintf(fp_out, "%s", MMDFHDRTXT);
  667         else {
  668             (void) time(&epoch);
  669             fprintf(fp_out, "From %s %s", addr, ctime(&epoch));
  670         }
  671         while (fgets(buf, (int) sizeof(buf), fp_in) != NULL) {
  672             if (!mmdf) { /* moboxo/mboxrd style From_ quoting required */
  673                 /*
  674                  * TODO: add Content-Length: header when using MBOXO
  675                  *       so tin actually write MBOXCL instead of MBOXO?
  676                  */
  677                 if (tinrc.mailbox_format == 1) { /* MBOXRD */
  678                     /* mboxrd: quote quoted and plain From_ lines in the body */
  679                     bufp = buf;
  680                     while (*bufp == '>')
  681                         bufp++;
  682                     if (strncmp(bufp, "From ", 5) == 0)
  683                         fputc('>', fp_out);
  684                 } else { /* MBOXO (MBOXCL) */
  685                     if (strncmp(buf, "From ", 5) == 0)
  686                         fputc('>', fp_out);
  687                 }
  688             }
  689             fputs(buf, fp_out);
  690         }
  691         print_art_separator_line(fp_out, mmdf);
  692 
  693         fflush(fp_out);
  694 #ifndef NO_LOCKING
  695         if (fd_unlock(fd) || !dot_unlock(the_mailbox))
  696             wait_message(4, _(txt_error_cant_unlock), the_mailbox);
  697 #endif /* !NO_LOCKING */
  698 
  699         fclose(fp_out);
  700         rval = TRUE;
  701     }
  702     fclose(fp_in);
  703     return rval;
  704 }
  705 
  706 
  707 /*
  708  * TODO:
  709  * - cleanup!!
  710  * - check for illegal (8bit) chars in References, X-Face, MIME-Version,
  711  *   Content-Type, Content-Transfer-Encoding, Content-Disposition, Supersedes
  712  * - check for 'illegal' headers: Xref, Injection-Info, (NNTP-Posting-Host,
  713  *   NNTP-Posting-Date, X-Trace, X-Complaints-To), Date-Received,
  714  *   Posting-Version, Relay-Version, Also-Control, Article-Names,
  715  *   Article-Updates, See-Also
  716  * - check for special newsgroups: to, ctl, all, control, junk
  717  *   [RFC 5536 3.1.4]
  718  * - check for Supersedes in Control messages [RFC 5536 3.2.3]
  719  * - check for 'illegal' distribultion: all [RFC 5536 3.2.4]
  720  *
  721  * Check the article file for correct header syntax and if there
  722  * is a blank between the header information and the text.
  723  *
  724  * Additionally make **group point to one of the groups we are actually posting to.
  725  *
  726  * 1.  Subject header present
  727  * 2.  Newsgroups header present
  728  *     From header present
  729  * 3.  Space after every colon in header
  730  * 4.  Colon in every header line
  731  * 5.  Newsgroups line has no spaces, only comma separated
  732  * 6.  List of newsgroups is presented to user with description
  733  * 7.  Lines in body that are to long causes a warning to be printed
  734  * 8.  Group(s) must be listed in the active file
  735  * 9.  No Sender: header allowed (limit forging) and rejection by
  736  *     inn servers
  737  * 10. Check for charset != US-ASCII when using non-7bit-encoding
  738  * 11. Warn if transfer encoding is base64 or quoted-printable and using
  739  *     external inews
  740  * 12. Check that Subject, Newsgroups and if present Followup-To
  741  *     headers are uniq
  742  * 13. Display an 'are you sure' message before posting article
  743  */
  744 #define CA_ERROR_HEADER_LINE_BLANK        0x0000001
  745 #define CA_ERROR_MISSING_BODY_SEPERATOT   0x0000002
  746 #define CA_ERROR_MISSING_FROM             0x0000004
  747 #define CA_ERROR_DUPLICATED_FROM          0x0000008
  748 #define CA_ERROR_MISSING_SUBJECT          0x0000010
  749 #define CA_ERROR_DUPLICATED_SUBJECT       0x0000020
  750 #define CA_ERROR_EMPTY_SUBJECT            0x0000040
  751 #define CA_ERROR_MISSING_NEWSGROUPS       0x0000080
  752 #define CA_ERROR_DUPLICATED_NEWSGROUPS    0x0000100
  753 #define CA_ERROR_EMPTY_NEWSGROUPS         0x0000200
  754 #define CA_ERROR_DUPLICATED_FOLLOWUP_TO   0x0000400
  755 #define CA_ERROR_BAD_CHARSET              0x0000800
  756 #define CA_ERROR_BAD_ENCODING             0x0001000
  757 #define CA_ERROR_BAD_MESSAGE_ID           0x0002000
  758 #define CA_ERROR_BAD_DATE                 0x0004000
  759 #define CA_ERROR_BAD_EXPIRES              0x0008000
  760 #define CA_ERROR_NEWSGROUPS_NOT_7BIT      0x0010000
  761 #define CA_ERROR_FOLLOWUP_TO_NOT_7BIT     0x0020000
  762 #define CA_ERROR_DISTRIBUTIOIN_NOT_7BIT   0x0040000
  763 #define CA_ERROR_NEWSGROUPS_POSTER        0x0080000
  764 #define CA_ERROR_FOLLOWUP_TO_POSTER       0x0100000
  765 #ifndef FOLLOW_USEFOR_DRAFT
  766 #   define CA_ERROR_SPACE_IN_NEWSGROUPS    0x0200000
  767 #   define CA_ERROR_NEWLINE_IN_NEWSGROUPS  0x0400000
  768 #   define CA_ERROR_SPACE_IN_FOLLOWUP_TO   0x0800000
  769 #   define CA_ERROR_NEWLINE_IN_FOLLOWUP_TO 0x1000000
  770 #endif /* !FOLLOW_USEFOR_DRAFT */
  771 #define CA_WARNING_SPACES_ONLY_SUBJECT      0x000001
  772 #define CA_WARNING_RE_WITHOUT_REFERENCES    0x000002
  773 #define CA_WARNING_REFERENCES_WITHOUT_RE    0x000004
  774 #define CA_WARNING_MULTIPLE_SIGDASHES       0x000008
  775 #define CA_WARNING_WRONG_SIGDASHES          0x000010
  776 #define CA_WARNING_LONG_SIGNATURE           0x000020
  777 #define CA_WARNING_ENCODING_EXTERNAL_INEWS  0x000040
  778 #define CA_WARNING_NEWSGROUPS_EXAMPLE       0x000080
  779 #define CA_WARNING_FOLLOWUP_TO_EXAMPLE      0x000100
  780 #ifdef CHARSET_CONVERSION
  781 #   define CA_WARNING_CHARSET_CONVERSION    0x000200
  782 #endif /* CHARSET_CONVERSION */
  783 #ifdef FOLLOW_USEFOR_DRAFT
  784 #   define CA_WARNING_SPACE_IN_NEWSGROUPS    0x000400
  785 #   define CA_WARNING_NEWLINE_IN_NEWSGROUPS  0x000800
  786 #   define CA_WARNING_SPACE_IN_FOLLOWUP_TO   0x001000
  787 #   define CA_WARNING_NEWLINE_IN_FOLLOWUP_TO 0x002000
  788 #endif /* FOLLOW_USEFOR_DRAFT */
  789 
  790 /*
  791  * TODO: cleanup!
  792  *
  793  * return values:
  794  *  0   article ok
  795  *  1   article contains errors
  796  *  2   article caused warnings
  797  */
  798 static int
  799 check_article_to_be_posted(
  800     const char *the_article,
  801     int art_type,
  802     struct t_group **group,
  803     t_bool art_unchanged,
  804     t_bool use_cache)
  805 {
  806     FILE *fp;
  807     char **newsgroups = NULL;
  808     char **followupto = NULL;
  809     char *line, *cp, *cp2;
  810     char *to = NULL;
  811     char references[HEADER_LEN];
  812     char subject[HEADER_LEN];
  813     int cnt = 0;
  814     int col, i;
  815     int errors = 0;
  816     int warnings = 0;
  817     int init = 1;
  818     int ngcnt = 0, ftngcnt = 0;
  819     int oldraw;     /* save previous raw state */
  820     int saw_sig_dashes = 0;
  821     int sig_lines = 0;
  822     int found_followup_to_lines = 0;
  823     int found_from_lines = 0;
  824     int found_newsgroups_lines = 0;
  825     int found_subject_lines = 0;
  826     int errors_catbp = 0; /* sum of error-codes */
  827     int warnings_catbp = 0; /* sum of warning-codes */
  828     int must_break_line = 0;
  829     struct t_group *psGrp;
  830     t_bool end_of_header = FALSE;
  831     t_bool got_long_line = FALSE;
  832     t_bool saw_references = FALSE;
  833     t_bool saw_wrong_sig_dashes = FALSE;
  834     t_bool mime_7bit = TRUE;
  835     t_bool mime_usascii = FALSE;
  836     t_bool contains_8bit = FALSE;
  837 #ifdef CHARSET_CONVERSION
  838     t_bool charset_conversion_fails = FALSE;
  839     int mmnwcharset;
  840 #endif /* CHARSET_CONVERSION */
  841     static const char *c_article;
  842     static int c_art_type;
  843     static struct t_group **c_group;
  844     static t_bool c_art_unchanged;
  845 
  846     /*
  847      * Cache values for the case when called
  848      * from refresh_post_screen()
  849      */
  850     if (!use_cache) {
  851         c_article = the_article;
  852         c_art_type = art_type;
  853         c_group = group;
  854         c_art_unchanged = art_unchanged;
  855     }
  856 
  857 #ifdef CHARSET_CONVERSION
  858     mmnwcharset = *c_group ? (*c_group)->attribute->mm_network_charset : tinrc.mm_network_charset;
  859 #endif /* CHARSET_CONVERSION */
  860 
  861     if ((fp = fopen(c_article, "r")) == NULL) {
  862         perror_message(_(txt_cannot_open), c_article);
  863         return 0;
  864     }
  865     oldraw = RawState();    /* save state */
  866     subject[0] = '\0';
  867 
  868     /* check the header of the article */
  869     setup_check_article_screen(&init);
  870 
  871     while ((line = tin_fgets(fp, TRUE)) != NULL) {
  872         cnt++;
  873         if (!end_of_header && !strlen(line)) { /* end of header reached */
  874             if (cnt == 1)
  875                 errors_catbp |= CA_ERROR_HEADER_LINE_BLANK;
  876             end_of_header = TRUE;
  877             break;
  878         }
  879 
  880         for (cp = line; *cp && !contains_8bit; cp++) {
  881             if (!isascii(*cp)) {
  882                 contains_8bit = TRUE;
  883                 break;
  884             }
  885         }
  886 
  887 #ifdef CHARSET_CONVERSION
  888         /* are all characters in article contained in network_charset? */
  889         if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
  890             cp = my_malloc(strlen(line) * 4 + 1);
  891             strcpy(cp, line);
  892             charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
  893             free(cp);
  894         }
  895 #endif /* CHARSET_CONVERSION */
  896 
  897         if ((cp = strchr(line, ':')) == NULL) {
  898             StartInverse();
  899             my_fprintf(stderr, _(txt_error_header_line_colon), cnt, line);
  900             EndInverse();
  901             my_fflush(stderr);
  902             errors++;
  903             continue;
  904         }
  905         if (cp[1] != ' ') {
  906             StartInverse();
  907             my_fprintf(stderr, _(txt_error_header_line_space), cnt, line);
  908             EndInverse();
  909             my_fflush(stderr);
  910             errors++;
  911         }
  912 
  913         if (cp - line == 7 && !strncasecmp(line, "Subject", 7)) {
  914             found_subject_lines++;
  915             strncpy(subject, cp + 2, cCOLS - 6);
  916             subject[cCOLS - 6] = '\0';
  917         }
  918 
  919 /*
  920  * only allow hand supplied Sender in FORGERY case or
  921  * with external inews and not HAVE_FASCIST_NEWSADMIN
  922  */
  923 #ifndef FORGERY
  924 #   ifdef HAVE_FASCIST_NEWSADMIN
  925         if (cp - line == 6 && !strncasecmp(line, "Sender", 6))
  926 #   else
  927         if (!strcasecmp(tinrc.inews_prog, INTERNAL_CMD) && cp - line == 6 && !strncasecmp(line, "Sender", 6))
  928 #   endif /* HAVE_FASCIST_NEWSADMIN */
  929         {
  930             StartInverse();
  931             my_fprintf(stderr, _(txt_error_sender_in_header_not_allowed), cnt);
  932             EndInverse();
  933             my_fflush(stderr);
  934             errors++;
  935         }
  936 #endif /* !FORGERY */
  937 
  938         if (cp - line == 8 && !strncasecmp(line, "Approved", 8)) {
  939             if (tinrc.beginner_level) {
  940                 /* StartInverse(); */
  941                 my_fprintf(stderr, "%s", _(txt_error_approved)); /* this is only a Warning: */
  942                 /* EndInverse(); */
  943                 my_fflush(stderr);
  944 #ifdef HAVE_FASCIST_NEWSADMIN
  945                 errors++;
  946 #else
  947                 warnings++;
  948 #endif /* HAVE_FASCIST_NEWSADMIN */
  949             }
  950 #ifdef CHARSET_CONVERSION
  951             cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
  952 #else
  953             cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
  954 #endif /* CHARSET_CONVERSION */
  955             if (GNKSA_OK != (i = gnksa_check_from(cp2 + (cp - line) + 1))) {
  956                 StartInverse();
  957                 my_fprintf(stderr, "%s", _(txt_error_bad_approved));
  958                 my_fprintf(stderr, gnksa_strerror(i), i);
  959                 EndInverse();
  960                 my_fflush(stderr);
  961 #ifndef FORGERY
  962                 errors++;
  963 #endif /* !FORGERY */
  964             }
  965             free(cp2);
  966         }
  967 
  968         if (cp - line == 4 && !strncasecmp(line, "From", 4)) {
  969             found_from_lines++;
  970 #ifdef CHARSET_CONVERSION
  971             cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
  972 #else
  973             cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
  974 #endif /* CHARSET_CONVERSION */
  975             if (GNKSA_OK != (i = gnksa_check_from(cp2 + (cp - line) + 1))) {
  976                 StartInverse();
  977                 my_fprintf(stderr, "%s", _(txt_error_bad_from));
  978                 my_fprintf(stderr, gnksa_strerror(i), i);
  979                 EndInverse();
  980                 my_fflush(stderr);
  981 #ifndef FORGERY
  982                 errors++;
  983 #endif /* !FORGERY */
  984             }
  985             free(cp2);
  986         }
  987 
  988         if (cp - line == 8 && !strncasecmp(line, "Reply-To", 8)) {
  989 #ifdef CHARSET_CONVERSION
  990             cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
  991 #else
  992             cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
  993 #endif /* CHARSET_CONVERSION */
  994             if (GNKSA_OK != (i = gnksa_check_from(cp2 + (cp - line) + 1))) {
  995                 StartInverse();
  996                 my_fprintf(stderr, "%s", _(txt_error_bad_replyto));
  997                 my_fprintf(stderr, gnksa_strerror(i), i);
  998                 EndInverse();
  999                 my_fflush(stderr);
 1000 #ifndef FORGERY
 1001                 errors++;
 1002 #endif /* !FORGERY */
 1003             }
 1004             free(cp2);
 1005         }
 1006 
 1007         if (cp - line == 2 && !strncasecmp(line, "To", 2)) {
 1008 #ifdef CHARSET_CONVERSION
 1009             cp2 = rfc1522_encode(line, txt_mime_charsets[mmnwcharset], FALSE);
 1010 #else
 1011             cp2 = rfc1522_encode(line, tinrc.mm_charset, FALSE);
 1012 #endif /* CHARSET_CONVERSION */
 1013             if (GNKSA_OK != (i = gnksa_check_from(cp2 + (cp - line) + 1))) {
 1014                 StartInverse();
 1015                 my_fprintf(stderr, "%s", _(txt_error_bad_to));
 1016                 my_fprintf(stderr, gnksa_strerror(i), i);
 1017                 EndInverse();
 1018                 my_fflush(stderr);
 1019 #ifndef FORGERY
 1020                 errors++;
 1021 #endif /* !FORGERY */
 1022             }
 1023             to = my_strdup(cp2 + (cp - line) + 1);
 1024             free(cp2);
 1025         }
 1026 
 1027         if (cp - line == 10 && !strncasecmp(line, "Message-ID", 10)) {
 1028 #if 0 /* see comment about "<>" in misc.c:gnksa_split_from() */
 1029             char addr[HEADER_LEN], name[HEADER_LEN];
 1030             int type;
 1031 
 1032             i = gnksa_check_from(++cp);
 1033             gnksa_split_from(cp, addr, name, &type);
 1034             if (((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i)) || !*addr)
 1035 #else
 1036             i = gnksa_check_from(++cp);
 1037             if ((GNKSA_OK != i) && (GNKSA_LOCALPART_MISSING > i))
 1038 #endif /* 0 */
 1039             {
 1040                 StartInverse();
 1041                 my_fprintf(stderr, "%s", _(txt_error_bad_msgidfqdn));
 1042                 my_fprintf(stderr, gnksa_strerror(i), i);
 1043                 EndInverse();
 1044                 my_fflush(stderr);
 1045 #ifndef FORGERY
 1046                 errors++;
 1047 #endif /* !FORGERY */
 1048             }
 1049             if (damaged_id(cp))
 1050                 errors_catbp |= CA_ERROR_BAD_MESSAGE_ID;
 1051         }
 1052 
 1053         if (cp - line == 10 && !strncasecmp(line, "References", 10)) {
 1054             for (cp = line + 11; *cp == ' '; cp++)
 1055                 ;
 1056             STRCPY(references, cp);
 1057             if (strlen(references))
 1058                 saw_references = TRUE;
 1059         }
 1060 
 1061         if (cp - line == 4 && !strncasecmp(line, "Date", 4)) {
 1062             if ((cp2 = parse_header(line, "Date", FALSE, FALSE, FALSE))) {
 1063                 if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
 1064                     errors_catbp |= CA_ERROR_BAD_DATE;
 1065             } else {
 1066                 errors_catbp |= CA_ERROR_BAD_DATE;
 1067             }
 1068         }
 1069 
 1070         if (cp - line == 7 && !strncasecmp(line, "Expires", 7)) {
 1071             if ((cp2 = parse_header(line, "Expires", FALSE, FALSE, FALSE))) {
 1072                 if (parsedate(cp2, (struct _TIMEINFO *) 0) <= 0)
 1073                     errors_catbp |= CA_ERROR_BAD_EXPIRES;
 1074             } else {
 1075                 errors_catbp |= CA_ERROR_BAD_EXPIRES;
 1076             }
 1077         }
 1078 
 1079         /*
 1080          * TODO: also check for other illegal chars?
 1081          *       a 'common' error is to use a semicolon instead of a comma.
 1082          */
 1083         if (cp - line == 10 && !strncasecmp(line, "Newsgroups", 10)) {
 1084             found_newsgroups_lines++;
 1085             for (cp = line + 11; *cp == ' '; cp++)
 1086                 ;
 1087             if (strchr(cp, ' ') || strchr(cp, '\t')) {
 1088 #ifdef FOLLOW_USEFOR_DRAFT
 1089                 warnings_catbp |= CA_WARNING_SPACE_IN_NEWSGROUPS;
 1090 #else
 1091                 errors_catbp |= CA_ERROR_SPACE_IN_NEWSGROUPS;
 1092 #endif /* FOLLOW_USEFOR_DRAFT */
 1093             }
 1094             if (strchr(cp, '\n')) {
 1095 #ifdef FOLLOW_USEFOR_DRAFT
 1096                 warnings_catbp |= CA_WARNING_NEWLINE_IN_NEWSGROUPS;
 1097 #else
 1098                 errors_catbp |= CA_ERROR_NEWLINE_IN_NEWSGROUPS;
 1099 #endif /* FOLLOW_USEFOR_DRAFT */
 1100                 unfold_header(line);
 1101             }
 1102 
 1103             newsgroups = build_nglist(cp, &ngcnt);
 1104             if (newsgroups && ngcnt)
 1105                 (void) stripped_double_ngs(newsgroups, &ngcnt);
 1106 
 1107             if (!ngcnt)
 1108                 errors_catbp |= CA_ERROR_EMPTY_NEWSGROUPS;
 1109             else {
 1110                 for (cp = line + 11; *cp; cp++) {
 1111                     if (!isascii(*cp)) {
 1112                         errors_catbp |= CA_ERROR_NEWSGROUPS_NOT_7BIT;
 1113                         break;
 1114                     }
 1115                 }
 1116             }
 1117             { /* check for poster, example, example.* */
 1118                 char *groups;
 1119 
 1120                 for (cp = line + 11; *cp == ' '; cp++)
 1121                     ;
 1122                 cp2 = groups = my_strdup(cp);
 1123 
 1124                 cp = strtok(groups, ",");
 1125                 do {
 1126                     if (!strcmp(cp, "poster"))
 1127                         errors_catbp |= CA_ERROR_NEWSGROUPS_POSTER;
 1128                     if (!strcmp(cp, "example"))
 1129                         warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
 1130                     if (!strncmp(cp, "example.", 8))
 1131                         warnings_catbp |= CA_WARNING_NEWSGROUPS_EXAMPLE;
 1132                     /* TODO: also check for to, ctl, all, control, junk */
 1133                 } while ((cp = strtok(NULL, ",")) != NULL);
 1134                 free(cp2);
 1135             }
 1136         }
 1137 
 1138         if (cp - line == 12 && !strncasecmp(line, "Distribution", 12)) {
 1139             for (cp = line + 13; *cp; cp++) {
 1140                 if (!isascii(*cp)) {
 1141                     errors_catbp |= CA_ERROR_DISTRIBUTIOIN_NOT_7BIT;
 1142                     break;
 1143                 }
 1144             }
 1145         }
 1146 
 1147         if (cp - line == 11 && !strncasecmp(line, "Followup-To", 11)) {
 1148             for (cp = line + 12; *cp == ' '; cp++)
 1149                 ;
 1150             if (strlen(cp)) /* Followup-To not empty */
 1151                 found_followup_to_lines++;
 1152             if (strchr(cp, ' ') || strchr(cp, '\t')) {
 1153 #ifdef FOLLOW_USEFOR_DRAFT
 1154                 warnings_catbp |= CA_WARNING_SPACE_IN_FOLLOWUP_TO;
 1155 #else
 1156                 errors_catbp |= CA_ERROR_SPACE_IN_FOLLOWUP_TO;
 1157 #endif /* FOLLOW_USEFOR_DRAFT */
 1158             }
 1159             if (strchr(cp, '\n')) {
 1160 #ifdef FOLLOW_USEFOR_DRAFT
 1161                 warnings_catbp |= CA_WARNING_NEWLINE_IN_FOLLOWUP_TO;
 1162 #else
 1163                 errors_catbp |= CA_ERROR_NEWLINE_IN_FOLLOWUP_TO;
 1164 #endif /* FOLLOW_USEFOR_DRAFT */
 1165                 unfold_header(line);
 1166             }
 1167 
 1168             followupto = build_nglist(cp, &ftngcnt);
 1169             if (followupto && ftngcnt) {
 1170                 char *groups;
 1171 
 1172                 (void) stripped_double_ngs(followupto, &ftngcnt);
 1173                 for (cp = line + 12; *cp; cp++) {
 1174                     if (!isascii(*cp)) {
 1175                         errors_catbp |= CA_ERROR_FOLLOWUP_TO_NOT_7BIT;
 1176                         break;
 1177                     }
 1178                 }
 1179 
 1180                 for (cp = line + 12; *cp == ' '; cp++)
 1181                     ;
 1182                 cp2 = groups = my_strdup(cp);
 1183 
 1184                 cp = strtok(groups, ",");
 1185                 do {
 1186                     if (!strcmp(cp, "poster") && ftngcnt > 1)
 1187                         errors_catbp |= CA_ERROR_FOLLOWUP_TO_POSTER;
 1188                     if (!strcmp(cp, "example"))
 1189                         warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
 1190                     if (!strncmp(cp, "example.", 8))
 1191                         warnings_catbp |= CA_WARNING_FOLLOWUP_TO_EXAMPLE;
 1192                     /* TODO: also check for to, ctl, all, control, junk */
 1193                 } while ((cp = strtok(NULL, ",")) != NULL);
 1194                 free(cp2);
 1195             }
 1196         }
 1197     }
 1198 
 1199     if (subject[0] == '\0')
 1200         errors_catbp |= CA_ERROR_EMPTY_SUBJECT;
 1201     else {
 1202         cp2 = my_strdup(subject);
 1203         if (!strtok(cp2, " \t")) {  /* only blanks in Subject? */
 1204             warnings_catbp |= CA_WARNING_SPACES_ONLY_SUBJECT;
 1205             free(cp2);
 1206         } else {
 1207             free(cp2);
 1208             /* Warn if Subject: begins with "Re: " but there are no References: */
 1209             if (!strncmp(subject, "Re: ", 4) && !saw_references)
 1210                 warnings_catbp |= CA_WARNING_RE_WITHOUT_REFERENCES;
 1211             /*
 1212              * Warn if there are References: but no "Re: " at the beginning of
 1213              * and no "(was:" in the Subject.
 1214              */
 1215             if (saw_references && strncmp(subject, "Re: ", 4)) {
 1216                 t_bool was_found = FALSE;
 1217 
 1218                 cp2 = subject;
 1219                 while (!was_found && (cp2 = strchr(cp2, '(')))
 1220                     was_found = (strncmp(++cp2, "was:", 4) == 0);
 1221 
 1222                 if (!was_found)
 1223                     warnings_catbp |= CA_WARNING_REFERENCES_WITHOUT_RE;
 1224             }
 1225         }
 1226     }
 1227 
 1228     if (!found_from_lines)
 1229         errors_catbp |= CA_ERROR_MISSING_FROM;
 1230     else {
 1231         if (found_from_lines > 1)
 1232             errors_catbp |= CA_ERROR_DUPLICATED_FROM;
 1233     }
 1234 
 1235     if (!found_newsgroups_lines && c_art_type == GROUP_TYPE_NEWS)
 1236         errors_catbp |= CA_ERROR_MISSING_NEWSGROUPS;
 1237 
 1238     if (found_newsgroups_lines > 1)
 1239         errors_catbp |= CA_ERROR_DUPLICATED_NEWSGROUPS;
 1240 
 1241     if (!found_subject_lines)
 1242         errors_catbp |= CA_ERROR_MISSING_SUBJECT;
 1243     else {
 1244         if (found_subject_lines > 1)
 1245             errors_catbp |= CA_ERROR_DUPLICATED_SUBJECT;
 1246     }
 1247 
 1248     if (found_followup_to_lines > 1)
 1249         errors_catbp |= CA_ERROR_DUPLICATED_FOLLOWUP_TO;
 1250 
 1251     /*
 1252      * Check the body of the article for long lines
 1253      * check if article contains non-7bit-ASCII characters
 1254      * check if sig is shorter then MAX_SIG_LINES lines
 1255      */
 1256     while ((line = tin_fgets(fp, FALSE))) {
 1257         cnt++;
 1258 
 1259         if (saw_sig_dashes || saw_wrong_sig_dashes)
 1260             sig_lines++;
 1261 
 1262         /* SIGDASHES excluding the terminating \n as tin_fgets strips it */
 1263         if (strlen(line) == 3 && !strncmp(line, SIGDASHES, 3)) {
 1264             saw_wrong_sig_dashes = FALSE;
 1265             saw_sig_dashes++;
 1266             sig_lines = 0;
 1267         }
 1268 
 1269         /* SIGDASHES excluding the tailing SPACE (and '\n', see comment above) */
 1270         if (strlen(line) == 2 && !strncmp(line, SIGDASHES, 2) && !saw_sig_dashes) {
 1271             saw_wrong_sig_dashes = TRUE;
 1272             sig_lines = 0;
 1273         }
 1274 
 1275 #ifdef CHARSET_CONVERSION
 1276         /* are all characters in article contained in network_charset? */
 1277         if (strcasecmp(tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]) && !charset_conversion_fails) { /* local_charset != network_charset */
 1278             cp = my_malloc(strlen(line) * 4 + 1);
 1279             strcpy(cp, line);
 1280             charset_conversion_fails = !buffer_to_network(cp, mmnwcharset);
 1281             free(cp);
 1282         }
 1283 #endif /* CHARSET_CONVERSION */
 1284 
 1285         {
 1286 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1287             int num_bytes, wc_width;
 1288             wchar_t wc;
 1289 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1290 
 1291             col = 0;
 1292             for (cp = line; *cp; ) {
 1293                 if (*cp == '\t') {
 1294                     col += 8 - (col % 8);
 1295                     cp++;
 1296                 } else {
 1297 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1298                     if ((num_bytes = mbtowc(&wc, cp, MB_CUR_MAX)) != -1) {
 1299                         cp += num_bytes;
 1300                         if (!contains_8bit && num_bytes > 1)
 1301                             contains_8bit = TRUE;
 1302                         if (iswprint(wc) && ((wc_width = wcwidth(wc)) != -1))
 1303                             col += wc_width;
 1304                         else
 1305                             col++;
 1306                     } else {
 1307                         cp++;
 1308                         col++;
 1309                     }
 1310 #else
 1311                     if (!contains_8bit && !isascii(*cp))
 1312                         contains_8bit = TRUE;
 1313                     cp++;
 1314                     col++;
 1315 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1316                 }
 1317             }
 1318         }
 1319         if (col > MAX_COL && !got_long_line) {
 1320             my_fprintf(stderr, _(txt_warn_art_line_too_long), MAX_COL, cnt, line);
 1321             my_fflush(stderr);
 1322             got_long_line = TRUE;
 1323 
 1324             warnings++;
 1325         }
 1326         if (strlen(line) > 998 && !must_break_line)
 1327             must_break_line = cnt;
 1328     }
 1329 
 1330 #if 1
 1331 /*
 1332  * TODO: cleanup, test me, move to the right location, strings -> lang.c, ...
 1333  *       define a macro for 998
 1334  */
 1335     if (must_break_line && ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_BASE64)) {
 1336 #   ifdef MIME_BREAK_LONG_LINES
 1337         if (contains_8bit) {
 1338             if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_QP)
 1339                 my_fprintf(stderr, _("Line %d is longer than 998 octets and should be folded, but\nencoding is neither set to %s nor to %s\n"), must_break_line, txt_quoted_printable, txt_base64);
 1340         } else
 1341 #   endif /* MIME_BREAK_LONG_LINES */
 1342         {
 1343             if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) == MIME_ENCODING_QP)
 1344                 my_fprintf(stderr, _("Line %d is longer than 998 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, txt_quoted_printable);
 1345             else
 1346                 my_fprintf(stderr, _("Line %d is longer than 998 octets and should be folded, but\nencoding is not set to %s\n"), must_break_line, txt_base64);
 1347         }
 1348         my_fflush(stderr);
 1349         warnings++;
 1350     }
 1351 #endif /* 1 */
 1352 
 1353     if (saw_sig_dashes > 1)
 1354         warnings_catbp |= CA_WARNING_MULTIPLE_SIGDASHES;
 1355 
 1356     if (saw_wrong_sig_dashes)
 1357         warnings_catbp |= CA_WARNING_WRONG_SIGDASHES;
 1358 
 1359     if (sig_lines > MAX_SIG_LINES) {
 1360         warnings_catbp |= CA_WARNING_LONG_SIGNATURE;
 1361 #ifdef HAVE_FASCIST_NEWSADMIN
 1362         errors++;
 1363 #endif /* HAVE_FASCIST_NEWSADMIN */
 1364     }
 1365 
 1366 #ifdef CHARSET_CONVERSION
 1367     if (charset_conversion_fails)
 1368         warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
 1369 #endif /* CHARSET_CONVERSION */
 1370 
 1371     if (!end_of_header)
 1372         errors_catbp |= CA_ERROR_MISSING_BODY_SEPERATOT;
 1373 
 1374     /*
 1375      * check for MIME Content-Type and Content-Transfer-Encoding
 1376      *
 1377      * If the user has modified the Newsgroups-header **group might not
 1378      * point to the correct newsgroup any more.
 1379      * Take first group in Newsgroups-header to pass it along to
 1380      * submit_news_file et.al. to use it for group-attributes, or if there is
 1381      * no Newsgroups:-header (mailing_list) stay with given group.
 1382      *
 1383      * Is this correct for crosspostings?
 1384      */
 1385     if (ngcnt)
 1386         *c_group = group_find(newsgroups[0], FALSE);
 1387 
 1388     /*
 1389      * check for known 7bit charsets
 1390      */
 1391     for (i = 0; txt_mime_7bit_charsets[i] != NULL; i++) {
 1392 #ifdef CHARSET_CONVERSION
 1393         if (!strcasecmp(txt_mime_charsets[mmnwcharset], txt_mime_7bit_charsets[i]))
 1394 #else
 1395         if (!strcasecmp(tinrc.mm_charset, txt_mime_7bit_charsets[i]))
 1396 #endif /* CHARSET_CONVERSION */
 1397         {
 1398             mime_usascii = TRUE;
 1399             break;
 1400         }
 1401     }
 1402     if ((*c_group ? (*c_group)->attribute->post_mime_encoding : tinrc.post_mime_encoding) != MIME_ENCODING_7BIT)
 1403         mime_7bit = FALSE;
 1404     if (contains_8bit && mime_usascii)
 1405 #ifndef CHARSET_CONVERSION
 1406         errors_catbp |= CA_ERROR_BAD_CHARSET;
 1407 #else /* we catch this case later on again */
 1408         warnings_catbp |= CA_WARNING_CHARSET_CONVERSION;
 1409 #endif /* !CHARSET_CONVERSION */
 1410 
 1411     if (contains_8bit && mime_7bit)
 1412         errors_catbp |= CA_ERROR_BAD_ENCODING;
 1413 
 1414     /*
 1415      * Warn when poster is using a non-plain encoding such as quoted-printable
 1416      * or base64 and external inews because if that external inews appends a
 1417      * signature it will not be encoded. We might additionally check if there's
 1418      * a file named ~/.signature and skip the warning if it is not present.
 1419      */
 1420     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))
 1421         warnings_catbp |= CA_WARNING_ENCODING_EXTERNAL_INEWS;
 1422 
 1423     /* give most error messages */
 1424     if (errors_catbp) {
 1425         StartInverse();
 1426 
 1427         /* missing headers */
 1428         if (errors_catbp & CA_ERROR_HEADER_LINE_BLANK)
 1429             my_fprintf(stderr, "%s", _(txt_error_header_line_blank));
 1430         if (errors_catbp & CA_ERROR_MISSING_BODY_SEPERATOT)
 1431             my_fprintf(stderr, "%s", _(txt_error_header_and_body_not_separate));
 1432         if (errors_catbp & CA_ERROR_MISSING_FROM)
 1433             my_fprintf(stderr, _(txt_error_header_line_missing), "From");
 1434         if (errors_catbp & CA_ERROR_MISSING_SUBJECT)
 1435             my_fprintf(stderr, _(txt_error_header_line_missing), "Subject");
 1436         if (errors_catbp & CA_ERROR_MISSING_NEWSGROUPS)
 1437             my_fprintf(stderr, _(txt_error_header_line_missing), "Newsgroups");
 1438 
 1439         /* duplicated headers */
 1440         if (errors_catbp & CA_ERROR_DUPLICATED_FROM)
 1441             my_fprintf(stderr, _(txt_error_header_duplicate), found_from_lines, "From");
 1442         if (errors_catbp & CA_ERROR_DUPLICATED_SUBJECT)
 1443             my_fprintf(stderr, _(txt_error_header_duplicate), found_subject_lines, "Subject");
 1444         if (errors_catbp & CA_ERROR_DUPLICATED_NEWSGROUPS)
 1445             my_fprintf(stderr, _(txt_error_header_duplicate), found_newsgroups_lines, "Newsgroups");
 1446         if (errors_catbp & CA_ERROR_DUPLICATED_FOLLOWUP_TO)
 1447             my_fprintf(stderr, _(txt_error_header_duplicate), found_followup_to_lines, "Followup-To");
 1448 
 1449         /* empty headers */
 1450         if (errors_catbp & CA_ERROR_EMPTY_SUBJECT)
 1451             my_fprintf(stderr, _(txt_error_header_line_empty), "Subject");
 1452         if (errors_catbp & CA_ERROR_EMPTY_NEWSGROUPS)
 1453             my_fprintf(stderr, _(txt_error_header_line_empty), "Newsgroups");
 1454 
 1455 #ifndef FOLLOW_USEFOR_DRAFT
 1456         /* illegal space in headers */
 1457         if (errors_catbp & CA_ERROR_SPACE_IN_NEWSGROUPS)
 1458             my_fprintf(stderr, _(txt_error_header_line_comma), "Newsgroups");
 1459         if (errors_catbp & CA_ERROR_SPACE_IN_FOLLOWUP_TO)
 1460             my_fprintf(stderr, _(txt_error_header_line_comma), "Followup-To");
 1461 
 1462         /* illegal newline in headers */
 1463         if (errors_catbp & CA_ERROR_NEWLINE_IN_NEWSGROUPS)
 1464             my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Newsgroups");
 1465         if (errors_catbp & CA_ERROR_NEWLINE_IN_FOLLOWUP_TO)
 1466             my_fprintf(stderr, _(txt_error_header_line_groups_contd), "Followup-To");
 1467 #endif /* !FOLLOW_USEFOR_DRAFT */
 1468 
 1469         /* illegal group names / combinations */
 1470         if (errors_catbp & CA_ERROR_NEWSGROUPS_POSTER)
 1471             my_fprintf(stderr, "%s", _(txt_error_newsgroups_poster));
 1472         if (errors_catbp & CA_ERROR_FOLLOWUP_TO_POSTER)
 1473             my_fprintf(stderr, "%s", _(txt_error_followup_poster));
 1474 
 1475         /* encoding/charset trouble */
 1476         if (errors_catbp & CA_ERROR_BAD_CHARSET)
 1477             my_fprintf(stderr, "%s", _(txt_error_header_line_bad_charset));
 1478         if (errors_catbp & CA_ERROR_BAD_ENCODING)
 1479             my_fprintf(stderr, "%s", _(txt_error_header_line_bad_encoding));
 1480 
 1481         if (errors_catbp & CA_ERROR_DISTRIBUTIOIN_NOT_7BIT)
 1482             my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Distribution");
 1483         if (errors_catbp & CA_ERROR_NEWSGROUPS_NOT_7BIT)
 1484             my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Newsgroups");
 1485         if (errors_catbp & CA_ERROR_FOLLOWUP_TO_NOT_7BIT)
 1486             my_fprintf(stderr, _(txt_error_header_line_not_7bit), "Followup-To");
 1487 
 1488         if (errors_catbp & CA_ERROR_BAD_MESSAGE_ID)
 1489             my_fprintf(stderr, _(txt_error_header_format), "Message-ID");
 1490         if (errors_catbp & CA_ERROR_BAD_DATE)
 1491             my_fprintf(stderr, _(txt_error_header_format), "Date");
 1492         if (errors_catbp & CA_ERROR_BAD_EXPIRES)
 1493             my_fprintf(stderr, _(txt_error_header_format), "Expires");
 1494 
 1495         EndInverse();
 1496         my_fflush(stderr);
 1497         errors += errors_catbp;
 1498     }
 1499 
 1500     /* give most warnings */
 1501     if (warnings_catbp) {
 1502 
 1503         if (warnings_catbp & CA_WARNING_SPACES_ONLY_SUBJECT)
 1504             my_fprintf(stderr, "%s", _(txt_warn_blank_subject));
 1505         if (warnings_catbp & CA_WARNING_RE_WITHOUT_REFERENCES)
 1506             my_fprintf(stderr, "%s", _(txt_warn_re_but_no_references));
 1507         if (warnings_catbp & CA_WARNING_REFERENCES_WITHOUT_RE)
 1508             my_fprintf(stderr, "%s", _(txt_warn_references_but_no_re));
 1509 
 1510         if ((warnings_catbp & CA_WARNING_NEWSGROUPS_EXAMPLE) || (warnings_catbp & CA_WARNING_FOLLOWUP_TO_EXAMPLE))
 1511             my_fprintf(stderr, "%s", _(txt_warn_example_hierarchy));
 1512 
 1513 #ifdef FOLLOW_USEFOR_DRAFT /* TODO: give useful warning */
 1514         if (warnings_catbp & CA_WARNING_SPACE_IN_NEWSGROUPS)
 1515             my_fprintf(stderr, _(txt_warn_header_line_comma), "Newsgroups");
 1516         if (warnings_catbp & CA_WARNING_SPACE_IN_FOLLOWUP_TO)
 1517             my_fprintf(stderr, _(txt_warn_header_line_comma), "Followup-To");
 1518         if (warnings_catbp & CA_WARNING_NEWLINE_IN_NEWSGROUPS)
 1519             my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Newsgroups");
 1520         if (warnings_catbp & CA_WARNING_NEWLINE_IN_FOLLOWUP_TO)
 1521             my_fprintf(stderr, _(txt_warn_header_line_groups_contd), "Followup-To");
 1522 #endif /* FOLLOW_USEFOR_DRAFT */
 1523 
 1524         if (warnings_catbp & CA_WARNING_MULTIPLE_SIGDASHES)
 1525             my_fprintf(stderr, _(txt_warn_multiple_sigs), saw_sig_dashes);
 1526         if (warnings_catbp & CA_WARNING_WRONG_SIGDASHES)
 1527             my_fprintf(stderr, "%s", _(txt_warn_wrong_sig_format));
 1528         if (warnings_catbp & CA_WARNING_LONG_SIGNATURE)
 1529             my_fprintf(stderr, _(txt_warn_sig_too_long), MAX_SIG_LINES);
 1530 
 1531         if (warnings_catbp & CA_WARNING_ENCODING_EXTERNAL_INEWS)
 1532             my_fprintf(stderr, "%s", _(txt_warn_encoding_and_external_inews));
 1533 
 1534 #ifdef CHARSET_CONVERSION
 1535         if (warnings_catbp & CA_WARNING_CHARSET_CONVERSION)
 1536             my_fprintf(stderr, _(txt_warn_charset_conversion), tinrc.mm_local_charset, txt_mime_charsets[mmnwcharset]);
 1537 #endif /* CHARSET_CONVERSION */
 1538 
 1539         my_fflush(stderr);
 1540         warnings += warnings_catbp;
 1541     }
 1542 
 1543     if (!errors) {
 1544         /*
 1545          * Print a note about each newsgroup
 1546          */
 1547         if (c_art_unchanged)
 1548             my_fprintf(stderr, "%s", _(txt_warn_article_unchanged));
 1549 
 1550         if (ngcnt)
 1551             my_fprintf(stderr, _(txt_art_newsgroups), subject, PLURAL(ngcnt, txt_newsgroup));
 1552 
 1553         if (c_art_type == GROUP_TYPE_MAIL)
 1554             my_fprintf(stderr, _(txt_art_mailgroups), subject, BlankIfNull(to));
 1555         else {
 1556             for (i = 0; i < ngcnt; i++) {
 1557                 if ((psGrp = group_find(newsgroups[i], FALSE))) {
 1558                     if (psGrp->aliasedto) {
 1559 #ifdef HAVE_FASCIST_NEWSADMIN
 1560                         StartInverse();
 1561                         errors++;
 1562                         my_fprintf(stderr, N_(txt_error_grp_renamed), newsgroups[i], psGrp->aliasedto);
 1563                         EndInverse();
 1564                         my_fflush(stderr);
 1565 #else
 1566                         my_fprintf(stderr, N_(txt_warn_grp_renamed), newsgroups[i], psGrp->aliasedto);
 1567                         warnings++;
 1568 #endif /* HAVE_FASCIST_NEWSADMIN */
 1569                     } else
 1570                         my_fprintf(stderr, "  %s\t %s\n", newsgroups[i], BlankIfNull(psGrp->description));
 1571                 } else {
 1572 #ifdef HAVE_FASCIST_NEWSADMIN
 1573                     StartInverse();
 1574                     errors++;
 1575                     my_fprintf(stderr, _(txt_error_not_valid_newsgroup), newsgroups[i]);
 1576                     EndInverse();
 1577                     my_fflush(stderr);
 1578 #else
 1579                     my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), newsgroups[i]);
 1580                     warnings++;
 1581 #endif /* HAVE_FASCIST_NEWSADMIN */
 1582                 }
 1583             }
 1584             if (!found_followup_to_lines && ngcnt > 1 && !errors) {
 1585 #ifdef HAVE_FASCIST_NEWSADMIN
 1586                 StartInverse();
 1587                 my_fprintf(stderr, _(txt_error_missing_followup_to), ngcnt);
 1588                 EndInverse();
 1589                 my_fflush(stderr);
 1590                 errors++;
 1591 #else
 1592                 my_fprintf(stderr, _(txt_warn_missing_followup_to), ngcnt);
 1593                 warnings++;
 1594 #endif /* HAVE_FASCIST_NEWSADMIN */
 1595             }
 1596 
 1597             if (ftngcnt && !errors) {
 1598                 if (ftngcnt > 1) {
 1599 #ifdef HAVE_FASCIST_NEWSADMIN
 1600                     StartInverse();
 1601                     my_fprintf(stderr, "%s", _(txt_error_followup_to_several_groups));
 1602                     EndInverse();
 1603                     my_fflush(stderr);
 1604                     errors++;
 1605 #else
 1606                     my_fprintf(stderr, "%s", _(txt_warn_followup_to_several_groups));
 1607                     warnings++;
 1608 #endif /* HAVE_FASCIST_NEWSADMIN */
 1609                 }
 1610 #ifdef HAVE_FASCIST_NEWSADMIN
 1611                 if (!errors) {
 1612 #endif /* HAVE_FASCIST_NEWSADMIN */
 1613                     my_fprintf(stderr, _(txt_followup_newsgroups), PLURAL(ftngcnt, txt_newsgroup));
 1614                     for (i = 0; i < ftngcnt; i++) {
 1615                         if ((psGrp = group_find(followupto[i], FALSE))) {
 1616                             if (psGrp->aliasedto) {
 1617 #ifdef HAVE_FASCIST_NEWSADMIN
 1618                                 StartInverse();
 1619                                 errors++;
 1620                                 my_fprintf(stderr, N_(txt_error_grp_renamed), followupto[i], psGrp->aliasedto);
 1621                                 EndInverse();
 1622                                 my_fflush(stderr);
 1623 #else
 1624                                 my_fprintf(stderr, N_(txt_warn_grp_renamed), followupto[i], psGrp->aliasedto);
 1625                                 warnings++;
 1626 #endif /* HAVE_FASCIST_NEWSADMIN */
 1627                             } else
 1628                                 my_fprintf(stderr, "  %s\t %s\n", followupto[i], BlankIfNull(psGrp->description));
 1629                         } else {
 1630                             if (STRCMPEQ("poster", followupto[i]))
 1631                                 my_fprintf(stderr, _(txt_followup_poster), followupto[i]);
 1632                             else {
 1633 #ifdef HAVE_FASCIST_NEWSADMIN
 1634                                 StartInverse();
 1635                                 my_fprintf(stderr, _(txt_error_not_valid_newsgroup), followupto[i]);
 1636                                 EndInverse();
 1637                                 my_fflush(stderr);
 1638                                 errors++;
 1639 #else
 1640                                 my_fprintf(stderr, (!list_active ? /* did we read the whole active file? */ _(txt_warn_not_in_newsrc) : _(txt_warn_not_valid_newsgroup)), followupto[i]);
 1641                                 warnings++;
 1642 #endif /* HAVE_FASCIST_NEWSADMIN */
 1643                             }
 1644                         }
 1645                     }
 1646 #ifdef HAVE_FASCIST_NEWSADMIN
 1647                 }
 1648 #endif /* HAVE_FASCIST_NEWSADMIN */
 1649             }
 1650 
 1651 #ifndef NO_ETIQUETTE
 1652             if (tinrc.beginner_level)
 1653                 my_fprintf(stderr, "%s", _(txt_warn_posting_etiquette));
 1654 #endif /* !NO_ETIQUETTE */
 1655             my_fflush(stderr);
 1656         }
 1657     }
 1658     fclose(fp);
 1659 
 1660     Raw(oldraw);        /* restore raw/unraw state */
 1661 
 1662     /* free memory */
 1663     if (newsgroups && ngcnt) {
 1664         FreeIfNeeded(*newsgroups);
 1665         FreeIfNeeded(newsgroups);
 1666     }
 1667     if (followupto && ftngcnt) {
 1668         FreeIfNeeded(*followupto);
 1669         FreeIfNeeded(followupto);
 1670     }
 1671     FreeIfNeeded(to);
 1672 
 1673     return (errors ? 1 : (warnings ? 2 : 0));
 1674 }
 1675 
 1676 
 1677 static void
 1678 setup_check_article_screen(
 1679     int *init)
 1680 {
 1681     if (*init) {
 1682         ClearScreen();
 1683         center_line(0, TRUE, _(txt_check_article));
 1684         MoveCursor(INDEX_TOP, 0);
 1685         Raw(FALSE);
 1686         *init = 0;
 1687     }
 1688 }
 1689 
 1690 
 1691 #if defined(SIGWINCH) || defined(SIGTSTP)
 1692 void
 1693 refresh_post_screen(
 1694     int context)
 1695 {
 1696     switch (context) {
 1697         case cPost:
 1698             ClearScreen();
 1699             center_line(0, TRUE, _(txt_check_article));
 1700             MoveCursor(INDEX_TOP, 0);
 1701             check_article_to_be_posted(NULL, 0, NULL, FALSE, TRUE);
 1702             break;
 1703 
 1704         case cPostCancel:
 1705             {
 1706                 int oldraw = RawState();
 1707 
 1708                 ClearScreen();
 1709                 center_line(0, TRUE, _(txt_check_article));
 1710                 MoveCursor(INDEX_TOP, 0);
 1711                 Raw(FALSE);
 1712 #ifdef FORGERY
 1713                 show_cancel_info(FALSE, TRUE);
 1714 #else
 1715                 show_cancel_info();
 1716 #endif /* FORGERY */
 1717                 Raw(oldraw);
 1718             }
 1719             break;
 1720 
 1721         case cPostFup:
 1722             show_followup_info();
 1723             break;
 1724 
 1725         default:
 1726             break;
 1727     }
 1728 }
 1729 #endif /* SIGWINCH || SIGTSTP */
 1730 
 1731 
 1732 /*
 1733  * edit/present an article, perform spell/PGP etc., operations if required
 1734  * submit the article and perform all necessary backend processing
 1735  */
 1736 static int
 1737 post_loop(
 1738     int type,               /* type of posting */
 1739     struct t_group *group,
 1740     t_function func,
 1741     const char *posting_msg, /* displayed just prior to article submission */
 1742     int art_type,           /* news, mail etc. */
 1743     int offset)             /* editor start offset */
 1744 {
 1745     char a_message_id[HEADER_LEN];  /* Message-ID of the article if known */
 1746     int ret_code = POSTED_NONE;
 1747     int i = 1;
 1748     int save_signal_context = signal_context;
 1749     long artchanged;        /* artchanged work was not done in post_postponed_article */
 1750     struct t_group *ogroup = curr_group;
 1751     t_bool art_unchanged;
 1752 
 1753     a_message_id[0] = '\0';
 1754 
 1755     forever {
 1756 post_article_loop:
 1757         art_unchanged = FALSE;
 1758         switch (func) {
 1759             case POST_EDIT:
 1760                 /*
 1761                  * This was VERY different in repost_article Code existed to
 1762                  * recheck subject and restart editor, but is not enabled
 1763                  */
 1764                 artchanged = file_mtime(article_name);
 1765                 if (!invoke_editor(article_name, offset, group)) {
 1766                     if (file_size(article_name) > 0L) {
 1767                         if (artchanged != file_mtime(article_name)) {
 1768                             unlink(backup_article_name(article_name));
 1769                             rename_file(article_name, dead_article);
 1770                             if (tinrc.keep_dead_articles)
 1771                                 append_file(dead_article, dead_articles);
 1772                         }
 1773                     }
 1774                     goto post_article_postponed;
 1775                 }
 1776                 ret_code = POSTED_REDRAW;
 1777 
 1778                 /* This might be erroneous with posting postponed */
 1779                 if (file_size(article_name) > 0L) {
 1780                     if (artchanged == file_mtime(article_name))
 1781                         art_unchanged = TRUE;
 1782                     while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
 1783                         ;
 1784                     if (func == POST_EDIT || func == GLOBAL_OPTION_MENU)
 1785                         break;
 1786                 }
 1787                 /* FALLTHROUGH */
 1788 
 1789             case GLOBAL_QUIT:
 1790             case GLOBAL_ABORT:
 1791                 if (tinrc.unlink_article) {
 1792 #if 0 /* useful? */
 1793                     if (tinrc.keep_dead_articles)
 1794                         append_file(dead_article, dead_articles);
 1795 #endif /* 0 */
 1796                     unlink(article_name);
 1797                 }
 1798                 clear_message();
 1799                 return ret_code;
 1800 
 1801             case GLOBAL_OPTION_MENU:
 1802                 config_page(group->name, signal_context);
 1803                 while ((i = check_article_to_be_posted(article_name, art_type, &group, art_unchanged, FALSE)) == 1 && repair_article(&func, group))
 1804                     ;
 1805                 break;
 1806 
 1807 #ifdef HAVE_ISPELL
 1808             case POST_ISPELL:
 1809                 invoke_ispell(article_name, group);
 1810                 ret_code = POSTED_REDRAW; /* not all versions did this */
 1811                 break;
 1812 #endif /* HAVE_ISPELL */
 1813 
 1814 #ifdef HAVE_PGP_GPG
 1815             case POST_PGP:
 1816                 invoke_pgp_news(article_name);
 1817                 break;
 1818 #endif /* HAVE_PGP_GPG */
 1819 
 1820             case GLOBAL_POST:
 1821                 wait_message(0, posting_msg);
 1822                 backup_article(article_name);
 1823 
 1824                 /* Functions that didn't handle mail didn't do this */
 1825                 if (art_type == GROUP_TYPE_NEWS) {
 1826                     if (submit_news_file(article_name, group, a_message_id))
 1827                         ret_code = POSTED_OK;
 1828                 } else {
 1829                     if (submit_mail_file(article_name, group, NULL, FALSE)) /* mailing_list */
 1830                         ret_code = POSTED_OK;
 1831                 }
 1832 
 1833                 if (ret_code == POSTED_OK) {
 1834                     unlink(backup_article_name(article_name));
 1835                     wait_message(2, _(txt_art_posted), *a_message_id ? a_message_id : "");
 1836                     goto post_article_done;
 1837                 } else {
 1838                     if ((func = prompt_rejected()) == POST_POSTPONE)
 1839                         /* reuse clean copy which didn't get modified by submit_news_file() */
 1840                         postpone_article(backup_article_name(article_name));
 1841                     else if (func == POST_EDIT) {
 1842                         /* replace modified article with clean backup */
 1843                         rename_file(backup_article_name(article_name), article_name);
 1844                         goto post_article_loop;
 1845                     } else {
 1846                         unlink(backup_article_name(article_name));
 1847                         rename_file(article_name, dead_article);
 1848                         if (tinrc.keep_dead_articles)
 1849                             append_file(dead_article, dead_articles);
 1850                         wait_message(2, _(txt_art_rejected), dead_article);
 1851                     }
 1852                     return ret_code;
 1853                 }
 1854 
 1855             case POST_POSTPONE:
 1856                 postpone_article(article_name);
 1857                 goto post_article_postponed;
 1858 
 1859             default:
 1860                 break;
 1861         }
 1862         signal_context = cPost;
 1863         if (type != POST_REPOST && type != POST_SUPERSEDED) {
 1864             char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
 1865             char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
 1866             char keymenu[MAXKEYLEN];
 1867 #ifdef HAVE_ISPELL
 1868             char keyispell[MAXKEYLEN];
 1869 #endif /* HAVE_ISPELL */
 1870 #ifdef HAVE_PGP_GPG
 1871             char keypgp[MAXKEYLEN];
 1872 #endif /* HAVE_PGP_GPG */
 1873 
 1874 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
 1875             func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
 1876                     post_post_keys, _(txt_quit_edit_post),
 1877                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1878                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1879                     printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
 1880                     printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
 1881                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1882                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1883                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1884 #else
 1885 #   ifdef HAVE_ISPELL
 1886             func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
 1887                     post_post_keys, _(txt_quit_edit_post),
 1888                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1889                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1890                     printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
 1891                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1892                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1893                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1894 #   else
 1895 #       ifdef HAVE_PGP_GPG
 1896             func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
 1897                     post_post_keys, _(txt_quit_edit_post),
 1898                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1899                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1900                     printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
 1901                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1902                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1903                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1904 #       else
 1905             func = prompt_slk_response((i ? POST_EDIT : art_unchanged ? POST_POSTPONE : GLOBAL_POST),
 1906                     post_post_keys, _(txt_quit_edit_post),
 1907                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1908                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1909                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1910                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1911                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1912 #       endif /* HAVE_PGP_GPG */
 1913 #   endif /* HAVE_ISPELL */
 1914 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
 1915         } else {
 1916             char *smsg;
 1917             char buf[LEN];
 1918             char keyedit[MAXKEYLEN], keypost[MAXKEYLEN];
 1919             char keypostpone[MAXKEYLEN], keyquit[MAXKEYLEN];
 1920             char keymenu[MAXKEYLEN];
 1921 #ifdef HAVE_ISPELL
 1922             char keyispell[MAXKEYLEN];
 1923 #endif /* HAVE_ISPELL */
 1924 #ifdef HAVE_PGP_GPG
 1925             char keypgp[MAXKEYLEN];
 1926 #endif /* HAVE_PGP_GPG */
 1927 
 1928 #if defined(HAVE_ISPELL) && defined(HAVE_PGP_GPG)
 1929             snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
 1930                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1931                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1932                     printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
 1933                     printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
 1934                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1935                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1936                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1937 #else
 1938 #   ifdef HAVE_ISPELL
 1939             snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
 1940                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1941                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1942                     printascii(keyispell, func_to_key(POST_ISPELL, post_post_keys)),
 1943                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1944                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1945                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1946 #   else
 1947 #       ifdef HAVE_PGP_GPG
 1948             snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
 1949                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1950                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1951                     printascii(keypgp, func_to_key(POST_PGP, post_post_keys)),
 1952                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1953                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1954                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1955 #       else
 1956             snprintf(buf, sizeof(buf), _(txt_quit_edit_xpost),
 1957                     printascii(keyquit, func_to_key(GLOBAL_QUIT, post_post_keys)),
 1958                     printascii(keyedit, func_to_key(POST_EDIT, post_post_keys)),
 1959                     printascii(keymenu, func_to_key(GLOBAL_OPTION_MENU, post_post_keys)),
 1960                     printascii(keypost, func_to_key(GLOBAL_POST, post_post_keys)),
 1961                     printascii(keypostpone, func_to_key(POST_POSTPONE, post_post_keys)));
 1962 #       endif /* HAVE_PGP_GPG */
 1963 #   endif /* HAVE_ISPELL */
 1964 #endif /* HAVE_ISPELL && HAVE_PGP_GPG */
 1965 
 1966             /* Superfluous force_command stuff not used in current code */
 1967             func = ( /* force_command ? ch_default : */ prompt_slk_response(func,
 1968                         post_post_keys, "%s", sized_message(&smsg, buf,
 1969                         "" /* TODO: was note_h.subj */ )));
 1970             free(smsg);
 1971         }
 1972         signal_context = save_signal_context;
 1973     }
 1974 
 1975 post_article_done:
 1976     if (ret_code == POSTED_OK) {
 1977         FILE *art_fp;
 1978         struct t_header header;
 1979 
 1980         memset(&header, 0, sizeof(struct t_header));
 1981 
 1982         if ((art_fp = fopen(article_name, "r")) == NULL)
 1983             perror_message(_(txt_cannot_open), article_name);
 1984         else {
 1985             curr_group = group;
 1986             parse_rfc822_headers(&header, art_fp, NULL);
 1987             fclose(art_fp);
 1988         }
 1989 
 1990         if (art_type == GROUP_TYPE_NEWS) {
 1991             if (header.newsgroups) {
 1992                 update_active_after_posting(header.newsgroups);
 1993                 /* In POST_RESPONSE, this was copied from note_h.newsgroups if !followup to poster */
 1994                 my_strncpy(tinrc.default_post_newsgroups, header.newsgroups, sizeof(tinrc.default_post_newsgroups) - 1);
 1995             }
 1996         }
 1997 
 1998         if (header.subj && header.newsgroups) {
 1999             char tag;
 2000             /*
 2001              * When crossposting postponed articles we currently do not add
 2002              * autoselect since we don't know which group the article was
 2003              * actually in
 2004              * FIXME: This logic is faithful to the original, but awful
 2005              */
 2006             if (group) { /* we might be (x-)posting to an unavailable group */
 2007                 if (art_type == GROUP_TYPE_NEWS && group->attribute->add_posted_to_filter && (type == POST_QUICK || type == POST_POSTPONED || type == POST_NORMAL)) {
 2008                     if ((group = group_find(header.newsgroups, FALSE)) && (type != POST_POSTPONED || (type == POST_POSTPONED && !strchr(header.newsgroups, ',')))) {
 2009                         quick_filter_select_posted_art(group, header.subj, a_message_id);
 2010                         if (type == POST_QUICK || (type == POST_POSTPONED && post_postponed_and_exit))
 2011                             write_filter_file(filter_file);
 2012                     }
 2013                 }
 2014             }
 2015 
 2016             switch (type) {
 2017                 case POST_POSTPONED:
 2018                     tag = (header.references) ? 'f' : 'w';
 2019                     break;
 2020 
 2021                 case POST_RESPONSE:
 2022                     tag = 'f';
 2023                     break;
 2024 
 2025                 case POST_REPOST:
 2026                 case POST_SUPERSEDED:
 2027                     tag = 'x';
 2028                     break;
 2029 
 2030                 case POST_NORMAL:
 2031                 case POST_QUICK:
 2032                 default:
 2033                     tag = 'w';
 2034                     break;
 2035             }
 2036 
 2037             switch (art_type) {
 2038                 case GROUP_TYPE_NEWS:
 2039                     update_posted_info_file(header.newsgroups, tag, header.subj, a_message_id);
 2040                     break;
 2041 
 2042                 case GROUP_TYPE_MAIL:
 2043                     update_posted_info_file(header.to, tag, header.subj, "");
 2044                     break;
 2045 
 2046                 default:
 2047                     break;
 2048             }
 2049 
 2050             my_strncpy(tinrc.default_post_subject, header.subj, sizeof(tinrc.default_post_subject) - 1);
 2051         }
 2052 
 2053         if (*tinrc.posted_articles_file && type != POST_REPOST) {
 2054             char a_mailbox[LEN];
 2055             char posted_msgs_file[PATH_LEN];
 2056 
 2057             joinpath(posted_msgs_file, sizeof(posted_msgs_file), (cmdline.args & CMDLINE_MAILDIR) ? cmdline.maildir : (group ? group->attribute->maildir : tinrc.maildir), tinrc.posted_articles_file);
 2058             /*
 2059              * log Message-ID if given in a_message_id,
 2060              * add Date:, remove empty headers
 2061              */
 2062             add_headers(article_name, a_message_id);
 2063             if (!strfpath(posted_msgs_file, a_mailbox, sizeof(a_mailbox), group, TRUE))
 2064                 STRCPY(a_mailbox, posted_msgs_file);
 2065             if (!append_mail(article_name, userid, a_mailbox)) {
 2066                 /* TODO: error handling */
 2067             }
 2068         }
 2069         free_and_init_header(&header);
 2070     }
 2071 
 2072 post_article_postponed:
 2073     curr_group = ogroup;
 2074     if (tinrc.unlink_article)
 2075         unlink(article_name);
 2076 
 2077     return ret_code;
 2078 }
 2079 
 2080 
 2081 /*
 2082  * Parse the list of newsgroups. For each, check group flag status. If it is
 2083  * possible to post to the group and the user agrees, then keep going. Return
 2084  * pointer to the first group in the list (the posting code needs this)
 2085  * Any one failure => return NULL
 2086  */
 2087 static struct t_group *
 2088 check_moderated(
 2089     const char *groups,
 2090     int *art_type,
 2091     const char *failmsg)
 2092 {
 2093     char *groupname;
 2094     char *ogroupn;
 2095     char newsgroups[HEADER_LEN];
 2096     struct t_group *group;
 2097     struct t_group *first_group = NULL;
 2098     int vnum = 0, bnum = 0;
 2099 
 2100     /* Take copy - strtok() modifies its args */
 2101     STRCPY(newsgroups, groups);
 2102 
 2103     if ((ogroupn = groupname = strtok(newsgroups, ",")) == NULL)
 2104         return NULL;
 2105 
 2106     do {
 2107         vnum++; /* number of newsgroups */
 2108 
 2109         if (!(group = group_find(groupname, FALSE))) {
 2110             bnum++; /* number of bogus groups */
 2111             continue;
 2112         }
 2113 
 2114         if (!first_group)               /* Save ptr to the 1st group */
 2115             first_group = group;
 2116 
 2117         /*
 2118          * Testing for !attribute here is a useful check for other brokenness
 2119          * Generally only bogus groups should have no attributes
 2120          */
 2121         if (group->bogus) {
 2122             error_message(2, _(txt_group_bogus), groupname);
 2123             return NULL;
 2124         }
 2125 
 2126         if (group->attribute->mailing_list != NULL)
 2127             *art_type = GROUP_TYPE_MAIL;
 2128 
 2129         if (!can_post && *art_type == GROUP_TYPE_NEWS) {
 2130             info_message(_(txt_cannot_post));
 2131             return NULL;
 2132         }
 2133 
 2134         if (group->moderated == 'x' || group->moderated == 'n' || group->moderated == 'j') {
 2135             error_message(2, _(txt_cannot_post_group), group->name);
 2136             return NULL;
 2137         }
 2138 
 2139         if (group->moderated == 'm') {
 2140             char *prompt = fmt_string(_(txt_group_is_moderated), groupname);
 2141             if (prompt_yn(prompt, TRUE) != 1) {
 2142                 error_message(*failmsg ? 2 : 0, failmsg);
 2143                 free(prompt);
 2144                 return NULL;
 2145             }
 2146             free(prompt);
 2147         }
 2148     } while ((groupname = strtok(NULL, ",")) != NULL);
 2149 
 2150     if (vnum > bnum)
 2151         return first_group;
 2152     else {
 2153         error_message(2, _(txt_not_in_active_file), ogroupn);
 2154         return NULL;
 2155     }
 2156 }
 2157 
 2158 
 2159 /*
 2160  * Build the standard headers used by quick_post_article() and post_article()
 2161  * Return TRUE or FALSE if things went wrong - there seems to be little
 2162  * error checking possible in here
 2163  */
 2164 static t_bool
 2165 create_normal_article_headers(
 2166     struct t_group *group,
 2167     const char *newsgroups,
 2168     int art_type)
 2169 {
 2170     FILE *fp;
 2171     char from_name[HEADER_LEN];
 2172 #ifdef FORGERY
 2173     char tmp[HEADER_LEN];
 2174 #endif /* FORGERY */
 2175     char *prompt, *tmp2;
 2176 
 2177     /* Get subject for posting article - Limit the display if needed */
 2178     tmp2 = strunc(tinrc.default_post_subject, DISPLAY_SUBJECT_LEN);
 2179 
 2180     prompt = fmt_string(_(txt_post_subject), tmp2);
 2181 
 2182     if (!(prompt_string_default(prompt, tinrc.default_post_subject, _(txt_no_subject), HIST_POST_SUBJECT))) {
 2183         free(prompt);
 2184         free(tmp2);
 2185         return FALSE;
 2186     }
 2187     free(prompt);
 2188     free(tmp2);
 2189 
 2190     if ((fp = fopen(article_name, "w")) == NULL) {
 2191         perror_message(_(txt_cannot_open), article_name);
 2192         return FALSE;
 2193     }
 2194 
 2195     fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
 2196 
 2197     get_from_name(from_name, group);
 2198 #ifdef FORGERY
 2199     make_path_header(tmp);
 2200     msg_add_header("Path", tmp);
 2201 #endif /* FORGERY */
 2202     msg_add_header("From", from_name);
 2203     msg_add_header("Subject", tinrc.default_post_subject);
 2204 
 2205     if (art_type == GROUP_TYPE_MAIL)
 2206         msg_add_header("To", group->attribute->mailing_list);
 2207     else {
 2208         msg_add_header("Newsgroups", newsgroups);
 2209         ADD_MSG_ID_HEADER();
 2210     }
 2211 
 2212     if (art_type == GROUP_TYPE_NEWS) {
 2213         if (group->attribute->followup_to != NULL)
 2214             msg_add_header("Followup-To", group->attribute->followup_to);
 2215         else {
 2216             if (group->attribute->prompt_followupto)
 2217                 msg_add_header("Followup-To", "");
 2218         }
 2219     }
 2220 
 2221     if (*reply_to)
 2222         msg_add_header("Reply-To", reply_to);
 2223 
 2224     if (group->attribute->organization != NULL)
 2225         msg_add_header("Organization", random_organization(group->attribute->organization));
 2226 
 2227     if (*my_distribution && art_type == GROUP_TYPE_NEWS)
 2228         msg_add_header("Distribution", my_distribution);
 2229 
 2230     msg_add_header("Summary", "");
 2231     msg_add_header("Keywords", "");
 2232 
 2233     msg_add_x_headers(group->attribute->x_headers);
 2234 
 2235     start_line_offset = msg_write_headers(fp) + 1;
 2236     fprintf(fp, "\n");          /* add a newline to keep vi from bitching */
 2237     msg_free_headers();
 2238 
 2239     start_line_offset += msg_add_x_body(fp, group->attribute->x_body);
 2240 
 2241     msg_write_signature(fp, FALSE, group);
 2242     fclose(fp);
 2243     cursoron();
 2244     return TRUE;
 2245 }
 2246 
 2247 
 2248 /*
 2249  * Quick post an article (not a followup)
 2250  */
 2251 void
 2252 quick_post_article(
 2253     t_bool postponed_only)
 2254 {
 2255     char buf[HEADER_LEN];
 2256     int art_type = GROUP_TYPE_NEWS;
 2257     struct t_group *group;
 2258 
 2259     msg_init_headers();
 2260     ClearScreen();
 2261 
 2262     /*
 2263      * check for postponed articles first
 2264      * first param is whether to ask the user if they want to do it or not.
 2265      * it's opposite to the command line switch.
 2266      * second param is whether to assume yes to all which is the same as
 2267      * the command line switch.
 2268      */
 2269 
 2270     if (pickup_postponed_articles(!postponed_only, postponed_only) || postponed_only)
 2271         return;
 2272 
 2273     /*
 2274      * Get groupname
 2275      */
 2276     snprintf(buf, sizeof(buf), _(txt_post_newsgroups), tinrc.default_post_newsgroups);
 2277     if (!(prompt_string_default(buf, tinrc.default_post_newsgroups, _(txt_no_newsgroups), HIST_POST_NEWSGROUPS)))
 2278         return;
 2279 
 2280     /*
 2281      * Strip double newsgroups
 2282      */
 2283     strip_double_ngs(tinrc.default_post_newsgroups);
 2284 
 2285     /*
 2286      * Check/see if any of the newsgroups are not postable.
 2287      */
 2288     if ((group = check_moderated(tinrc.default_post_newsgroups, &art_type, _(txt_exiting))) == NULL)
 2289         return;
 2290 
 2291     if (!create_normal_article_headers(group, tinrc.default_post_newsgroups, art_type))
 2292         return;
 2293 
 2294     post_loop(POST_QUICK, group, POST_EDIT, _(txt_posting), art_type, start_line_offset);
 2295 }
 2296 
 2297 
 2298 /*
 2299  *  Post an article that is already written (for postponed articles)
 2300  */
 2301 static void
 2302 post_postponed_article(
 2303     int ask,
 2304     const char *subject,
 2305     const char *newsgroups)
 2306 {
 2307     char *ng;
 2308     char *p;
 2309     char buf[LEN];
 2310 
 2311     if (!can_post) {
 2312         info_message(_(txt_cannot_post));
 2313         return;
 2314     }
 2315 
 2316     ng = my_strdup(newsgroups);
 2317     if ((p = strchr(ng, ',')) != NULL)
 2318         *p = '\0';
 2319 
 2320     snprintf(buf, sizeof(buf), _("Posting: %.*s ..."), cCOLS - 14, subject); /* TODO: -> lang.c, use strunc() */
 2321     post_loop(POST_POSTPONED, group_find(ng, FALSE), (ask ? POST_EDIT : GLOBAL_POST), buf, GROUP_TYPE_NEWS, 0);
 2322     free(ng);
 2323     return;
 2324 }
 2325 
 2326 
 2327 /*
 2328  * count how many articles are in postponed.articles. Essentially,
 2329  * we count '^From ' lines
 2330  */
 2331 int
 2332 count_postponed_articles(
 2333     void)
 2334 {
 2335     FILE *fp = fopen(postponed_articles_file, "r");
 2336     char line[HEADER_LEN];
 2337     int count = 0;
 2338 
 2339     if (!fp)
 2340         return 0;
 2341 
 2342     while (fgets(line, (int) sizeof(line), fp)) {
 2343         if (strncmp(line, "From ", 5) == 0)
 2344             count++;
 2345     }
 2346     fclose(fp);
 2347     return count;
 2348 }
 2349 
 2350 
 2351 /*
 2352  * Copy the first postponed article and remove it from the postponed file
 2353  */
 2354 static t_bool
 2355 fetch_postponed_article(
 2356     const char tmp_file[],
 2357     char subject[],
 2358     char newsgroups[])
 2359 {
 2360     FILE *in, *out;
 2361     FILE *tmp;
 2362     char *bufp = NULL;
 2363     char postponed_tmp[PATH_LEN];
 2364     char line[HEADER_LEN];
 2365     t_bool first_article;
 2366     t_bool prev_line_nl;
 2367     t_bool anything_left;
 2368 
 2369     snprintf(postponed_tmp, sizeof(postponed_tmp), "%s_", postponed_articles_file);
 2370     in = fopen(postponed_articles_file, "r");
 2371     out = fopen(tmp_file, "w");
 2372     tmp = fopen(postponed_tmp, "w");
 2373 
 2374     if (in == NULL || out == NULL || tmp == NULL) {
 2375         if (in)
 2376             fclose(in);
 2377         if (out)
 2378             fclose(out);
 2379         if (tmp)
 2380             fclose(tmp);
 2381         return FALSE;
 2382     }
 2383 
 2384     fgets(line, (int) sizeof(line), in);
 2385 
 2386     if (strncmp(line, "From ", 5) != 0) {
 2387         fclose(in);
 2388         fclose(out);
 2389         fclose(tmp);
 2390         return FALSE;
 2391     }
 2392 
 2393     first_article = TRUE;
 2394     prev_line_nl = FALSE;
 2395     anything_left = FALSE;
 2396 
 2397     /*
 2398      * we have one minor problem with copying the article, we have added
 2399      * a newline at the end of the article and we have to remove that,
 2400      * but we don't know we are on the last line until we read the next
 2401      * line containing "From "
 2402      */
 2403 
 2404     while (fgets(line, (int) sizeof(line), in) != NULL) {
 2405         if (tinrc.mailbox_format == 1)
 2406             bufp = line;
 2407         if (strncmp(line, "From ", 5) == 0)
 2408             first_article = FALSE;
 2409         if (first_article) {
 2410             match_string(line, "Newsgroups: ", newsgroups, HEADER_LEN);
 2411             match_string(line, "Subject: ", subject, HEADER_LEN);
 2412 
 2413             if (prev_line_nl)
 2414                 fputc('\n', out);
 2415 
 2416             if (strlen(line) && line[strlen(line) - 1] == '\n') {
 2417                 prev_line_nl = TRUE;
 2418                 line[strlen(line) - 1] = '\0';
 2419             } else
 2420                 prev_line_nl = FALSE;
 2421 
 2422             /* unquote quoted From_ lines */
 2423             if (tinrc.mailbox_format == 1) {
 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
 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 /*
 5142  * build_canlock(messageid, secret)
 5143  * returns *(cancel-lock) or NULL
 5144  */
 5145 char *
 5146 build_canlock(
 5147     const char *messageid,
 5148     const char *secret)
 5149 {
 5150     if ((messageid == NULL) || (secret == NULL) || (*secret == '\0'))
 5151         return ((char *) 0);
 5152     else
 5153         return (char *) (sha_lock((const unsigned char *) secret, strlen(secret), (const unsigned char *) messageid, strlen(messageid)));
 5154 }
 5155 
 5156 
 5157 /*
 5158  * build_cankey(messageid, secret)
 5159  * returns *(cancel-key) or NULL
 5160  */
 5161 static char *
 5162 build_cankey(
 5163     const char *messageid,
 5164     const char *secret)
 5165 {
 5166     if ((messageid == NULL) || (secret == NULL) || (*secret == '\0'))
 5167         return ((char *) 0);
 5168     else
 5169         return (sha_key((const unsigned char *) secret, strlen(secret), (const unsigned char *) messageid, strlen(messageid)));
 5170 }
 5171 
 5172 
 5173 /*
 5174  * get_secret()
 5175  * returns *(secret) or NULL
 5176  */
 5177 #   define SECRET_FILE ".cancelsecret"
 5178 char *
 5179 get_secret(
 5180     void)
 5181 {
 5182     FILE *fp_secret;
 5183     char *ptr;
 5184     char path_secret[PATH_LEN];
 5185     static char cancel_secret[HEADER_LEN];
 5186     int fd;
 5187     struct stat statbuf;
 5188