"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/misc.c" (22 Dec 2021, 97880 Bytes) of package /linux/misc/tin-2.6.1.tar.xz:


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : misc.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2021-09-22
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2022 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 #ifndef VERSION_H
   45 #   include "version.h"
   46 #endif /* !VERSION_H */
   47 #ifndef TCURSES_H
   48 #   include "tcurses.h"
   49 #endif /* !TCURSES_H */
   50 #ifndef included_trace_h
   51 #   include "trace.h"
   52 #endif /* !included_trace_h */
   53 #ifndef TIN_POLICY_H
   54 #   include "policy.h"
   55 #endif /* !TIN_POLICY_H */
   56 
   57 #if defined(HAVE_IDNA_H) && !defined(_IDNA_H)
   58 #   include <idna.h>
   59 #endif /* HAVE_IDNA_H && !_IDNA_H */
   60 #if defined(HAVE_STRINGPREP_H) && !defined(_STRINGPREP_H)
   61 #   include <stringprep.h>
   62 #endif /* HAVE_STRINGPREP_H && !_STRINGPREP_H */
   63 
   64 #if defined(HAVE_IDN_API_H) && !defined(IDN_API_H)
   65 #   include <idn/api.h>
   66 #endif /* HAVE_IDN_API_H && !IDN_API_H */
   67 
   68 
   69 /*
   70  * defines to control GNKSA-checks behavior:
   71  * - ENFORCE_RFC1034
   72  *   require domain name components not to start with a digit
   73  *
   74  * - REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
   75  *   require domain literals to be enclosed in square brackets
   76  */
   77 
   78 /*
   79  * Local prototypes
   80  */
   81 static char *strfpath_cp(char *str, char *tbuf, const char *endp);
   82 static int _strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all);
   83 static int gnksa_check_domain(char *domain);
   84 static int gnksa_check_domain_literal(char *domain);
   85 static int gnksa_check_localpart(const char *localpart);
   86 static int gnksa_dequote_plainphrase(char *realname, char *decoded, int addrtype);
   87 static int strfeditor(char *editor, int linenum, const char *filename, char *s, size_t maxsize, char *format);
   88 static void write_input_history_file(void);
   89 #ifdef CHARSET_CONVERSION
   90     static t_bool buffer_to_local(char **line, size_t *max_line_len, const char *network_charset, const char *local_charset);
   91 #endif /* CHARSET_CONVERSION */
   92 #if 0 /* currently unused */
   93     static t_bool stat_article(t_artnum art, const char *group_path);
   94 #endif /* 0 */
   95 
   96 
   97 /*
   98  * generate tmp-filename
   99  */
  100 char *
  101 get_tmpfilename(
  102     const char *filename)
  103 {
  104     char *file_tmp;
  105 
  106     /* alloc memory for tmp-filename */
  107     file_tmp = my_malloc(strlen(filename) + 5);
  108 
  109     /* generate tmp-filename */
  110     sprintf(file_tmp, "%s.tmp", filename);
  111     return file_tmp;
  112 }
  113 
  114 
  115 /*
  116  * append_file instead of rename_file
  117  * minimum error trapping
  118  */
  119 void
  120 append_file(
  121     char *old_filename,
  122     char *new_filename)
  123 {
  124     FILE *fp_old, *fp_new;
  125 
  126     if ((fp_old = fopen(old_filename, "r")) == NULL) {
  127         perror_message(_(txt_cannot_open), old_filename);
  128         return;
  129     }
  130     if ((fp_new = fopen(new_filename, "a")) == NULL) {
  131         perror_message(_(txt_cannot_open), new_filename);
  132         fclose(fp_old);
  133         return;
  134     }
  135     copy_fp(fp_old, fp_new);
  136     fclose(fp_old);
  137     fclose(fp_new);
  138 }
  139 
  140 
  141 void
  142 asfail(
  143     const char *file,
  144     int line,
  145     const char *cond)
  146 {
  147     my_fprintf(stderr, txt_error_asfail, tin_progname, file, line, cond);
  148     my_fflush(stderr);
  149 
  150 /*
  151  * create a core dump
  152  */
  153 #ifdef HAVE_COREFILE
  154 #   ifdef SIGABRT
  155         sigdisp(SIGABRT, SIG_DFL);
  156         kill(process_id, SIGABRT);
  157 #   else
  158 #       ifdef SIGILL
  159             sigdisp(SIGILL, SIG_DFL);
  160             kill(process_id, SIGILL);
  161 #       else
  162 #           ifdef SIGIOT
  163                 sigdisp(SIGIOT, SIG_DFL);
  164                 kill(process_id, SIGIOT);
  165 #           endif /* SIGIOT */
  166 #       endif /* SIGILL */
  167 #   endif /* SIGABRT */
  168 #endif /* HAVE_COREFILE */
  169 
  170     giveup();
  171 }
  172 
  173 
  174 /*
  175  * Quick copying of files
  176  * Returns FALSE if copy failed. Caller may wish to check for errno == EPIPE.
  177  */
  178 t_bool
  179 copy_fp(
  180     FILE *fp_ip,
  181     FILE *fp_op)
  182 {
  183     char buf[8192];
  184     size_t have, sent;
  185 
  186     errno = 0;  /* To check errno after write, clear it here */
  187 
  188     while ((have = fread(buf, 1, sizeof(buf), fp_ip)) != 0) {
  189         sent = fwrite(buf, 1, have, fp_op);
  190         if (sent != have) {
  191             TRACE(("copy_fp wrote %d of %d:{%.*s}", sent, have, (int) sent, buf));
  192             if (errno && errno != EPIPE) /* not a broken pipe => more serious error */
  193                 perror_message(_(txt_error_copy_fp));
  194             return FALSE;
  195         }
  196         TRACE(("copy_fp wrote %d:{%.*s}", sent, (int) sent, buf));
  197     }
  198     return TRUE;
  199 }
  200 
  201 
  202 /*
  203  * backup_file(filename, backupname)
  204  *
  205  * try to backup filename as backupname. on success backupname has the same
  206  * permissions as filename.
  207  *
  208  * return codes:
  209  * TRUE  = backup complete or source file was missing
  210  * FALSE = backup failed
  211  */
  212 t_bool
  213 backup_file(
  214     const char *filename,
  215     const char *backupname)
  216 {
  217     FILE *fp_in, *fp_out;
  218     int fd;
  219     mode_t mode = (mode_t) (S_IRUSR|S_IWUSR);
  220     struct stat statbuf;
  221     t_bool ret = FALSE;
  222 
  223     if ((fp_in = fopen(filename, "r")) == NULL) /* a missing sourcefile is not a real bug */
  224         return TRUE;
  225 
  226     /* don't follow links when writing backup files - do we really want this? */
  227     unlink(backupname);
  228     if ((fp_out = fopen(backupname, "w")) == NULL) {
  229         fclose(fp_in);
  230         return ret;
  231     }
  232 
  233     if ((fd = fileno(fp_in)) != -1) {
  234         if (!fstat(fd, &statbuf))
  235             mode = statbuf.st_mode;
  236     }
  237 
  238     ret = copy_fp(fp_in, fp_out);
  239 
  240     if ((fd = fileno(fp_out)) != -1)
  241 #ifdef HAVE_FCHMOD
  242         fchmod(fd, mode);
  243 #else
  244 #   ifdef HAVE_CHMOD
  245         chmod(backupname, mode);
  246 #   endif /* HAVE_CHMOD */
  247 #endif /* HAVE_FCHMOD */
  248 
  249     fclose(fp_out);
  250     fclose(fp_in);
  251     return ret;
  252 }
  253 
  254 
  255 /*
  256  * copy the body of articles with given file pointers,
  257  * prefix (= quote_chars), initials of the articles author
  258  * with_sig is set if the signature should be quoted
  259  *
  260  * TODO: rewrite from scratch, the code is awful.
  261  */
  262 void
  263 copy_body(
  264     FILE *fp_ip,
  265     FILE *fp_op,
  266     char *prefix,
  267     char *initl,
  268     t_bool raw_data)
  269 {
  270     char buf[8192];
  271     char buf2[8192];
  272     char prefixbuf[256];
  273     char *p = prefixbuf;
  274     char *q = prefix;
  275     int i;
  276     int retcode;
  277     size_t maxlen = sizeof(prefixbuf) - 1;
  278     size_t ilen = strlen(initl);
  279     t_bool initials = FALSE;
  280     t_bool status_char;
  281     t_bool status_space;
  282 
  283     /* This is a shortcut for speed reasons: if no prefix (= quote_chars) is given just copy */
  284     if (!prefix || !*prefix) {
  285         copy_fp(fp_ip, fp_op);
  286         return;
  287     }
  288 
  289     while (maxlen > 0 && *q) {
  290         if (*q == '%' && *(q + 1) == 'I') {
  291             if (maxlen < ilen) /* not enough space left for %I expansion */
  292                 break;
  293 
  294             strcpy(p, initl);
  295             maxlen -= ilen;
  296             p += ilen;
  297             q += 2; /* skip over "%I" */
  298             initials = TRUE;
  299         } else {
  300             *p++ = *q++;
  301             maxlen--;
  302         }
  303     }
  304     *p = '\0';
  305 
  306     /* no QUOTE_COMPRESS with initials */
  307     if ((tinrc.quote_style & QUOTE_COMPRESS) && !initials) {
  308         if (prefixbuf[strlen(prefixbuf) - 1] == ' ')
  309             prefixbuf[strlen(prefixbuf) - 1] = '\0';
  310     }
  311 
  312     /*
  313      * if raw_data is true, the signature is exceptionally quoted, even if
  314      * tinrc tells us not to do so. This extraordinary behavior occurs when
  315      * replying or following up with the 'raw' message shown.
  316      */
  317     while (fgets(buf, (int) sizeof(buf), fp_ip) != NULL) {
  318         if (!(tinrc.quote_style & QUOTE_SIGS) && !strcmp(buf, SIGDASHES) && !raw_data)
  319             break;
  320 
  321         if (initials) { /* initials wanted */
  322             if (buf[0] != '\n') { /* line is not empty */
  323                 if (strchr(buf, '>')) {
  324                     status_space = FALSE;
  325                     status_char = TRUE;
  326                     for (i = 0; buf[i] && (buf[i] != '>'); i++) {
  327                         buf2[i] = buf[i];
  328                         if (buf[i] != ' ')
  329                             status_space = TRUE;
  330                         if ((status_space) && !(isalpha((int)(unsigned char) buf[i]) || buf[i] == '>'))
  331                             status_char = FALSE;
  332                     }
  333                     buf2[i] = '\0';
  334                     if (status_char)    /* already quoted */
  335                         retcode = fprintf(fp_op, "%s>%s", buf2, BlankIfNull(strchr(buf, '>')));
  336                     else    /* ... to be quoted ... */
  337                         retcode = fprintf(fp_op, "%s%s", prefixbuf, buf);
  338                 } else  /* line was not already quoted (no >) */
  339                     retcode = fprintf(fp_op, "%s%s", prefixbuf, buf);
  340             } else  /* line is empty */
  341                 retcode = fprintf(fp_op, "%s\n", ((tinrc.quote_style & QUOTE_EMPTY) ? prefixbuf : ""));
  342         } else {        /* no initials in quote_string, just copy */
  343             if ((buf[0] != '\n') || (tinrc.quote_style & QUOTE_EMPTY))
  344                 retcode = fprintf(fp_op, "%s%s", (buf[0] == '>' ? prefixbuf : prefix), buf);    /* use blank-stripped quote string if line is already quoted */
  345             else
  346                 retcode = fprintf(fp_op, "\n");
  347         }
  348         if (retcode == EOF) {
  349             perror_message("copy_body() failed");
  350             return;
  351         }
  352     }
  353 }
  354 
  355 
  356 /*
  357  * Lookup 'env' in the environment. If it exists, return its value,
  358  * else return 'def'
  359  */
  360 const char *
  361 get_val(
  362     const char *env,    /* Environment variable we're looking for */
  363     const char *def)    /* Default value if no environ value found */
  364 {
  365     const char *ptr;
  366 
  367     return ((ptr = getenv(env)) != NULL ? ptr : def);
  368 }
  369 
  370 
  371 /*
  372  * IMHO it's not tins job to take care about dumb editor backupfiles
  373  * otherwise BACKUP_FILE_EXT should be configurable via 'M'enu
  374  */
  375 #define BACKUP_FILE_EXT ".b"
  376 t_bool
  377 invoke_editor(
  378     const char *filename,
  379     int lineno,
  380     struct t_group *group) /* return value is always ignored */
  381 {
  382     char buf[PATH_LEN];
  383     char editor_format[PATH_LEN];
  384     static char editor[PATH_LEN];
  385     static t_bool first = TRUE;
  386     t_bool retcode;
  387 #ifdef BACKUP_FILE_EXT
  388     char fnameb[PATH_LEN];
  389 #endif /* BACKUP_FILE_EXT */
  390 
  391     if (first) {
  392         my_strncpy(editor, get_val("VISUAL", get_val("EDITOR", DEFAULT_EDITOR)), sizeof(editor) - 1);
  393         first = FALSE;
  394     }
  395 
  396     if (group != NULL)
  397         my_strncpy(editor_format, (*group->attribute->editor_format ? group->attribute->editor_format : TIN_EDITOR_FMT), sizeof(editor_format) - 1);
  398     else
  399         my_strncpy(editor_format, (*tinrc.editor_format ? tinrc.editor_format : TIN_EDITOR_FMT), sizeof(editor_format) - 1);
  400 
  401     if (!strfeditor(editor, lineno, filename, buf, sizeof(buf), editor_format))
  402         sh_format(buf, sizeof(buf), "%s %s", editor, filename);
  403 
  404     cursoron();
  405     my_flush();
  406     retcode = invoke_cmd(buf);
  407 
  408 #ifdef BACKUP_FILE_EXT
  409     if (strlen(filename) + strlen(BACKUP_FILE_EXT) < sizeof(fnameb)) {
  410         STRCPY(fnameb, filename);
  411         strcat(fnameb, BACKUP_FILE_EXT);
  412         unlink(fnameb);
  413     }
  414 #endif /* BACKUP_FILE_EXT */
  415     return retcode;
  416 }
  417 
  418 
  419 #ifdef HAVE_ISPELL
  420 t_bool
  421 invoke_ispell(
  422     const char *nam,
  423     struct t_group *group) /* return value is always ignored */
  424 {
  425     FILE *fp_all, *fp_body, *fp_head;
  426     char buf[PATH_LEN], nam_body[PATH_LEN], nam_head[PATH_LEN];
  427     char ispell[PATH_LEN];
  428     t_bool retcode;
  429 
  430     if (group && group->attribute->ispell != NULL)
  431         STRCPY(ispell, group->attribute->ispell);
  432     else
  433         STRCPY(ispell, get_val("ISPELL", PATH_ISPELL));
  434 
  435     /*
  436      * Now separating the header and body in two different files so that
  437      * the header is not checked by ispell
  438      */
  439 #   ifdef HAVE_LONG_FILE_NAMES
  440     snprintf(nam_body, sizeof(nam_body), "%s%s", nam, ".body");
  441     snprintf(nam_head, sizeof(nam_head), "%s%s", nam, ".head");
  442 #   else
  443     snprintf(nam_body, sizeof(nam_body), "%s%s", nam, ".b");
  444     snprintf(nam_head, sizeof(nam_head), "%s%s", nam, ".h");
  445 #   endif /* HAVE_LONG_FILE_NAMES */
  446 
  447     if ((fp_all = fopen(nam, "r")) == NULL) {
  448         perror_message(_(txt_cannot_open), nam);
  449         return FALSE;
  450     }
  451 
  452     if ((fp_head = fopen(nam_head, "w")) == NULL) {
  453         perror_message(_(txt_cannot_open), nam_head);
  454         fclose(fp_all);
  455         return FALSE;
  456     }
  457 
  458     if ((fp_body = fopen(nam_body, "w")) == NULL) {
  459         perror_message(_(txt_cannot_open), nam_body);
  460         fclose(fp_head);
  461         fclose(fp_all);
  462         return FALSE;
  463     }
  464 
  465     while (fgets(buf, (int) sizeof(buf), fp_all) != NULL) {
  466         fputs(buf, fp_head);
  467         if (buf[0] == '\n' || buf[0] == '\r') {
  468             fclose(fp_head);
  469             fp_head = NULL;
  470             break;
  471         }
  472     }
  473 
  474     if (fp_head)
  475         fclose(fp_head);
  476 
  477     while (fgets(buf, (int) sizeof(buf), fp_all) != NULL)
  478         fputs(buf, fp_body);
  479 
  480     fclose(fp_body);
  481     fclose(fp_all);
  482 
  483     sh_format(buf, sizeof(buf), "%s %s", ispell, nam_body);
  484     retcode = invoke_cmd(buf);
  485 
  486     append_file(nam_body, nam_head);
  487     unlink(nam_body);
  488     rename_file(nam_head, nam);
  489     return retcode;
  490 }
  491 #endif /* HAVE_ISPELL */
  492 
  493 
  494 #ifndef NO_SHELL_ESCAPE
  495 void
  496 shell_escape(
  497     void)
  498 {
  499     char *p, *tmp;
  500     char shell[LEN];
  501 
  502     tmp = fmt_string(_(txt_shell_escape), tinrc.default_shell_command);
  503 
  504     if (!prompt_string(tmp, shell, HIST_SHELL_COMMAND)) {
  505         free(tmp);
  506         return;
  507     }
  508     free(tmp);
  509 
  510     for (p = shell; *p && isspace((int) *p); p++)
  511         continue;
  512 
  513     if (*p)
  514         my_strncpy(tinrc.default_shell_command, p, sizeof(tinrc.default_shell_command) - 1);
  515     else {
  516         my_strncpy(shell, (*tinrc.default_shell_command ? tinrc.default_shell_command : (get_val(ENV_VAR_SHELL, DEFAULT_SHELL))), sizeof(shell) - 1);
  517         p = shell;
  518     }
  519 
  520     ClearScreen();
  521     tmp = fmt_string(_(txt_shell_command), p);
  522     center_line(0, TRUE, tmp);
  523     free(tmp);
  524     MoveCursor(INDEX_TOP, 0);
  525 
  526     (void) invoke_cmd(p);
  527 
  528 #   ifndef USE_CURSES
  529     EndWin();
  530     Raw(FALSE);
  531 #   endif /* !USE_CURSES */
  532     prompt_continue();
  533 #   ifndef USE_CURSES
  534     Raw(TRUE);
  535     InitWin();
  536 #   endif /* !USE_CURSES */
  537 
  538     if (tinrc.draw_arrow)
  539         ClearScreen();
  540 }
  541 
  542 
  543 /*
  544  * shell out, if supported
  545  */
  546 void
  547 do_shell_escape(
  548     void)
  549 {
  550     shell_escape();
  551     currmenu->redraw();
  552 }
  553 #endif /* !NO_SHELL_ESCAPE */
  554 
  555 
  556 /*
  557  * Exits tin cleanly.
  558  * Has recursion protection - this may happen if the NNTP connection aborts
  559  * and is not re-established
  560  */
  561 _Noreturn void
  562 tin_done(
  563     int ret,
  564     const char *fmt,
  565     ...)
  566 {
  567     char *buf = NULL;
  568     int i;
  569     signed long int wrote_newsrc_lines;
  570     static int nested = 0;
  571     struct t_group *group;
  572     t_bool ask = TRUE;
  573     va_list ap;
  574 
  575     if (nested++)
  576         giveup();
  577 
  578     if (fmt && *fmt) {
  579         va_start(ap, fmt);
  580         buf = fmt_message(fmt, ap);
  581         va_end(ap);
  582     }
  583 
  584     signal_context = cMain;
  585 
  586 #ifdef USE_CURSES
  587     scrollok(stdscr, TRUE);         /* Allows display of multi-line messages */
  588 #endif /* USE_CURSES */
  589 
  590     /*
  591      * check if any groups were read & ask if they should marked read
  592      */
  593     if (tinrc.catchup_read_groups && !cmd_line && !no_write) {
  594         for (i = 0; i < selmenu.max; i++) {
  595             group = &active[my_group[i]];
  596             if (group->read_during_session) {
  597                 if (ask) {
  598                     if (prompt_yn(_(txt_catchup_all_read_groups), FALSE) == 1) {
  599                         ask = FALSE;
  600                         tinrc.thread_articles = THREAD_NONE;    /* speeds up index loading */
  601                     } else
  602                         break;
  603                 }
  604                 wait_message(0, _(txt_catchup_group), group->name);
  605                 grp_mark_read(group, NULL);
  606             }
  607         }
  608     }
  609 
  610     /*
  611      * Save the newsrc file. If it fails for some reason, give the user a
  612      * chance to try again
  613      */
  614     if (!no_write) {
  615         i = 3; /* max retries */
  616         while (i--) {
  617             wrote_newsrc_lines = write_newsrc();
  618             if ((wrote_newsrc_lines >= 0L) && (wrote_newsrc_lines >= read_newsrc_lines)) {
  619                 if (/* !batch_mode || */ verbose)
  620                     wait_message(0, _(txt_newsrc_saved));
  621                 break;
  622             }
  623 
  624             if (wrote_newsrc_lines < read_newsrc_lines) {
  625                 /* FIXME: prompt for retry? (i.e. remove break) */
  626                 wait_message(0, _(txt_warn_newsrc), newsrc,
  627                     (read_newsrc_lines - wrote_newsrc_lines),
  628                     PLURAL(read_newsrc_lines - wrote_newsrc_lines, txt_group),
  629                     OLDNEWSRC_FILE);
  630                 if (!batch_mode)
  631                     prompt_continue();
  632                 break;
  633             }
  634 
  635             if (!batch_mode) {
  636                 if (prompt_yn(_(txt_newsrc_again), TRUE) <= 0)
  637                     break;
  638             }
  639         }
  640 
  641         write_input_history_file();
  642 
  643 #ifdef HAVE_MH_MAIL_HANDLING
  644         write_mail_active_file();
  645 #endif /* HAVE_MH_MAIL_HANDLING */
  646     }
  647 
  648 #ifdef XFACE_ABLE
  649     slrnface_stop();
  650 #endif /* XFACE_ABLE */
  651 
  652     /* Do this sometime after we save the newsrc in case this hangs up for any reason */
  653     nntp_close((ret == NNTP_ERROR_EXIT));           /* disconnect from NNTP server */
  654 
  655     free_all_arrays();
  656 
  657     /*
  658      * TODO:
  659      * why do we make this exception "Terminate gracefully but do
  660      * not restore terminal" here?
  661      */
  662 #ifdef SIGUSR1
  663     if (ret != -SIGUSR1) {
  664 #endif /* SIGUSR1 */
  665 #ifdef HAVE_COLOR
  666 #   ifndef USE_CURSES
  667         reset_screen_attr();
  668 #   endif /* !USE_CURSES */
  669         use_color = FALSE;
  670         EndInverse();
  671 #else
  672         if (!cmd_line)
  673 #endif /* HAVE_COLOR */
  674         {
  675             cursoron();
  676             if (!ret)
  677                 ClearScreen();
  678         }
  679         EndWin();
  680         Raw(FALSE);
  681 #ifdef SIGUSR1
  682     } else
  683         ret = SIGUSR1;
  684 #endif /* SIGUSR1 */
  685 #ifdef HAVE_COLOR
  686 #   ifdef USE_CURSES
  687     free_color_pair_arrays();
  688 #   endif /* USE_CURSES */
  689 #endif /* HAVE_COLOR */
  690     cleanup_tmp_files();
  691 
  692     if (buf && *buf) {
  693         my_fputs(buf, stderr);
  694         my_fputs(cCRLF, stderr);
  695         my_fflush(stderr);
  696         free(buf);
  697     }
  698 
  699 #ifdef DOALLOC
  700     no_leaks(); /* free permanent stuff */
  701     show_alloc();   /* memory-leak testing */
  702 #endif /* DOALLOC */
  703 
  704 #ifdef USE_DBMALLOC
  705     /* force a dump, circumvents a bug in Linux libc */
  706     {
  707         extern int malloc_errfd;    /* FIXME */
  708         malloc_dump(malloc_errfd);
  709     }
  710 #endif /* USE_DBMALLOC */
  711 
  712     exit(ret);
  713 }
  714 
  715 
  716 int
  717 my_mkdir(
  718     char *path,
  719     mode_t mode)
  720 {
  721 #ifndef HAVE_MKDIR
  722     char buf[LEN];
  723     struct stat sb;
  724 
  725     if (stat(path, &sb) == -1) {
  726         snprintf(buf, sizeof(buf), "mkdir %s", path); /* redirect stderr to /dev/null? use invoke_cmd()? */
  727         system(buf);
  728 #   ifdef HAVE_CHMOD
  729         return chmod(path, mode);
  730 #   else
  731         return 0; /* chmod via system() like for mkdir? */
  732 #   endif /* HAVE_CHMOD */
  733     } else
  734         return -1;
  735 #else
  736         return mkdir(path, mode);
  737 #endif /* !HAVE_MKDIR */
  738 }
  739 
  740 
  741 void
  742 rename_file(
  743     const char *old_filename,
  744     const char *new_filename)
  745 {
  746     FILE *fp_old, *fp_new;
  747     int fd;
  748     mode_t mode = (mode_t) (S_IRUSR|S_IWUSR);
  749     struct stat statbuf;
  750 
  751     if (unlink(new_filename) == -1) {
  752         if (errno == EPERM) { /* TODO: != ENOENT ? and -> lang.c */
  753             perror_message(_("Error: unlink %s"), new_filename);
  754             return;
  755         }
  756     }
  757 
  758 #ifdef HAVE_LINK
  759     if (link(old_filename, new_filename) == -1)
  760 #else
  761     if (rename(old_filename, new_filename) < 0)
  762 #endif /* HAVE_LINK */
  763     {
  764         if (errno == EXDEV) {   /* create & copy file across filesystem */
  765             if ((fp_old = fopen(old_filename, "r")) == NULL) {
  766                 perror_message(_(txt_cannot_open), old_filename);
  767                 return;
  768             }
  769             if ((fp_new = fopen(new_filename, "w")) == NULL) {
  770                 perror_message(_(txt_cannot_open), new_filename);
  771                 fclose(fp_old);
  772                 return;
  773             }
  774 
  775             if ((fd = fileno(fp_old)) != -1) {
  776                 if (!fstat(fd, &statbuf))
  777                     mode = statbuf.st_mode;
  778             }
  779 
  780             copy_fp(fp_old, fp_new);
  781 
  782             if ((fd = fileno(fp_new)) != -1)
  783 #ifdef HAVE_FCHMOD
  784                 fchmod(fd, mode);
  785 #else
  786 #   ifdef HAVE_CHMOD
  787                 chmod(new_filename, mode);
  788 #   endif /* HAVE_CHMOD */
  789 #endif /* HAVE_FCHMOD */
  790 
  791             fclose(fp_new);
  792             fclose(fp_old);
  793             errno = 0;
  794         } else {
  795             perror_message(_(txt_rename_error), old_filename, new_filename);
  796             return;
  797         }
  798     }
  799 #ifdef HAVE_LINK
  800     if (unlink(old_filename) == -1) {
  801         perror_message(_(txt_rename_error), old_filename, new_filename);
  802         return;
  803     }
  804 #endif /* HAVE_LINK */
  805 }
  806 
  807 
  808 /*
  809  * Note that we exit screen/curses mode when invoking
  810  * external commands
  811  */
  812 t_bool
  813 invoke_cmd(
  814     const char *nam)
  815 {
  816     int ret;
  817     t_bool save_cmd_line = cmd_line;
  818 #ifndef IGNORE_SYSTEM_STATUS
  819     t_bool success;
  820 #endif /* !IGNORE_SYSTEM_STATUS */
  821 
  822     if (!save_cmd_line) {
  823         EndWin();
  824         Raw(FALSE);
  825     }
  826     set_signal_catcher(FALSE);
  827 
  828     TRACE(("called system(%s)", _nc_visbuf(nam)));
  829     ret = system(nam);
  830 #ifndef USE_SYSTEM_STATUS
  831     system_status = (ret >= 0 && WIFEXITED(ret)) ? WEXITSTATUS(ret) : 0;
  832 #endif /* !USE_SYSTEM_STATUS */
  833     TRACE(("return %d (%d)", ret, system_status));
  834 
  835     set_signal_catcher(TRUE);
  836     if (!save_cmd_line) {
  837         Raw(TRUE);
  838         InitWin();
  839         need_resize = cYes;     /* Flag a redraw */
  840     }
  841 
  842 #ifdef IGNORE_SYSTEM_STATUS
  843     return TRUE;
  844 #else
  845 
  846     success = (ret == 0);
  847 
  848     if (!success || system_status != 0)
  849         error_message(2, _(txt_command_failed), nam);
  850 
  851     return success;
  852 #endif /* IGNORE_SYSTEM_STATUS */
  853 }
  854 
  855 
  856 /*
  857  * grab file portion of fullpath
  858  */
  859 void
  860 base_name(
  861     const char *fullpath,       /* /foo/bar/baz */
  862     char *file)             /* baz */
  863 {
  864     size_t i;
  865 
  866     strcpy(file, fullpath);
  867 
  868     for (i = strlen(fullpath) - 1; i; i--) {
  869         if (fullpath[i] == DIRSEP) {
  870             strcpy(file, fullpath + i + 1);
  871             break;
  872         }
  873     }
  874 }
  875 
  876 
  877 /*
  878  * grab dir portion of fullpath
  879  */
  880 void
  881 dir_name(
  882     const char *fullpath,   /* /foo/bar/baz */
  883     char *dir)      /* /foo/bar/ */
  884 {
  885     char *d, *f, *p;
  886 
  887     d = my_strdup(fullpath);
  888     f = my_strdup(fullpath);
  889     base_name(d, f);
  890     if ((p = strrstr(d, f)) != NULL)
  891         *p = '\0';
  892     strcpy(dir, d);
  893     free(f);
  894     free(d);
  895 }
  896 
  897 
  898 /*
  899  * Return TRUE if new mail has arrived
  900  */
  901 #define MAILDIR_NEW "new"
  902 t_bool
  903 mail_check(
  904     const char *mailbox_name)
  905 {
  906     struct stat buf;
  907 
  908     if (mailbox_name != NULL && stat(mailbox_name, &buf) >= 0) {
  909         if ((int) (buf.st_mode & S_IFMT) == (int) S_IFDIR) { /* maildir setup */
  910             char *maildir_box;
  911             size_t maildir_box_len = strlen(mailbox_name) + strlen(MAILDIR_NEW) + 2;
  912             DIR *dirp;
  913             DIR_BUF *dp;
  914 
  915             maildir_box = my_malloc(maildir_box_len);
  916             joinpath(maildir_box, maildir_box_len, mailbox_name, MAILDIR_NEW);
  917 
  918             if (!(dirp = opendir(maildir_box))) {
  919                 free(maildir_box);
  920                 return FALSE;
  921             }
  922             free(maildir_box);
  923             while ((dp = readdir(dirp)) != NULL) {
  924                 if ((strcmp(dp->d_name, ".")) && (strcmp(dp->d_name, ".."))) {
  925                     CLOSEDIR(dirp);
  926                     return TRUE;
  927                 }
  928             }
  929             CLOSEDIR(dirp);
  930         } else {
  931             if (buf.st_atime < buf.st_mtime && buf.st_size > 0)
  932                 return TRUE;
  933         }
  934     }
  935     return FALSE;
  936 }
  937 
  938 
  939 /*
  940  * Return a pointer into s eliminating any leading Re:'s. Example:
  941  *
  942  *  Re: Reorganization of misc.jobs
  943  *      ^   ^
  944  *  Re^2: Reorganization of misc.jobs
  945  *
  946  * now also strips trailing (was: ...) (obw)
  947  */
  948 const char *
  949 eat_re(
  950     char *s,
  951     t_bool eat_was)
  952 {
  953     int data;
  954     int offsets[6];
  955     int size_offsets = ARRAY_SIZE(offsets);
  956 
  957     if (!s || !*s)
  958         return "";
  959 
  960     do {
  961         data = pcre_exec(strip_re_regex.re, strip_re_regex.extra, s, (int) strlen(s), 0, 0, offsets, size_offsets);
  962         if (offsets[0] == 0)
  963             s += offsets[1];
  964     } while (data >= 0);
  965 
  966     if (eat_was) do {
  967         data = pcre_exec(strip_was_regex.re, strip_was_regex.extra, s, (int) strlen(s), 0, 0, offsets, size_offsets);
  968         if (offsets[0] > 0)
  969             s[offsets[0]] = '\0';
  970     } while (data >= 0);
  971 
  972     return s;
  973 }
  974 
  975 
  976 #if defined(NO_LOCALE) || !defined(MULTIBYTE_ABLE)
  977 int
  978 my_isprint(
  979     int c)
  980 {
  981 #   ifndef NO_LOCALE
  982     /* use locale */
  983     return isprint(c);
  984 #   else
  985     if (IS_LOCAL_CHARSET("ISO-8859"))
  986         return (isprint(c) || (c >= 0xa0 && c <= 0xff));
  987     else if (IS_LOCAL_CHARSET("ISO-2022"))
  988         return (isprint(c) || (c == 0x1b));
  989     else if (IS_LOCAL_CHARSET("Big5"))
  990         return (isprint(c) || (c >= 0x40 && c <= 0xfe && c != 0x7f));
  991     else if (IS_LOCAL_CHARSET("EUC-"))
  992         return 1;
  993     else /* KOI8-* and UTF-8 */
  994         return (isprint(c) || (c >= 0x80 && c <= 0xff));
  995 #   endif /* !NO_LOCALE */
  996 }
  997 #endif /* NO_LOCALE || !MULTIBYTE_ABLE */
  998 
  999 
 1000 /*
 1001  * Returns author information
 1002  * thread   if true, assume we're on thread menu and show all author info if
 1003  *          subject not shown
 1004  * art      ptr to article
 1005  * str      ptr in which to return the author. Must be a valid data area
 1006  * len      max length of data to return
 1007  *
 1008  * The data will be null terminated
 1009  */
 1010 void
 1011 get_author(
 1012     t_bool thread,
 1013     struct t_article *art,
 1014     char *str,
 1015     size_t len)
 1016 {
 1017     char *p = idna_decode(art->from);
 1018     int author;
 1019 
 1020     author = ((thread && !show_subject && curr_group->attribute->show_author == SHOW_FROM_NONE) ? SHOW_FROM_BOTH : curr_group->attribute->show_author);
 1021 
 1022     switch (author) {
 1023         case SHOW_FROM_ADDR:
 1024             strncpy(str, p, len);
 1025             break;
 1026 
 1027         case SHOW_FROM_NAME:
 1028             strncpy(str, (art->name ? art->name : p), len);
 1029             break;
 1030 
 1031         case SHOW_FROM_BOTH:
 1032             if (art->name)
 1033                 snprintf(str, len, "%s <%s>", art->name, p);
 1034             else
 1035                 strncpy(str, p, len);
 1036             break;
 1037 
 1038         case SHOW_FROM_NONE:
 1039         default:
 1040             len = 0;
 1041             break;
 1042     }
 1043 
 1044     free(p);
 1045     *(str + len) = '\0';                /* NULL terminate */
 1046 }
 1047 
 1048 
 1049 void
 1050 toggle_inverse_video(
 1051     void)
 1052 {
 1053     if (!(tinrc.inverse_okay = bool_not(tinrc.inverse_okay)))
 1054         tinrc.draw_arrow = TRUE;
 1055 #ifndef USE_INVERSE_HACK
 1056 #   if 0
 1057     else
 1058         tinrc.draw_arrow = FALSE;
 1059 #   endif /* 0 */
 1060 #endif /* !USE_INVERSE_HACK */
 1061 }
 1062 
 1063 
 1064 void
 1065 show_inverse_video_status(
 1066     void)
 1067 {
 1068     info_message((tinrc.inverse_okay ? _(txt_inverse_on) : _(txt_inverse_off)));
 1069 }
 1070 
 1071 
 1072 #ifdef HAVE_COLOR
 1073 t_bool
 1074 toggle_color(
 1075     void)
 1076 {
 1077 #   ifdef USE_CURSES
 1078     if (!has_colors()) {
 1079         use_color = FALSE;
 1080         info_message(_(txt_no_colorterm));
 1081         return FALSE;
 1082     }
 1083     if (use_color)
 1084         reset_color();
 1085 #   endif /* USE_CURSES */
 1086     use_color = bool_not(use_color);
 1087 
 1088     if (use_color) {
 1089 #   ifdef USE_CURSES
 1090         fcol(tinrc.col_normal);
 1091 #   endif /* USE_CURSES */
 1092         bcol(tinrc.col_back);
 1093     }
 1094 #   ifndef USE_CURSES
 1095     else
 1096         reset_screen_attr();
 1097 #   endif /* !USE_CURSES */
 1098 
 1099     return TRUE;
 1100 }
 1101 
 1102 
 1103 void
 1104 show_color_status(
 1105     void)
 1106 {
 1107     info_message((use_color ? _(txt_color_on) : _(txt_color_off)));
 1108 }
 1109 #endif /* HAVE_COLOR */
 1110 
 1111 
 1112 /*
 1113  * Check for lock file to stop multiple copies of tin -u running and if it
 1114  * does not exist create it so this is the only copy running
 1115  *
 1116  * FIXME: get rid of hard coded pid-length as pid_t might be long
 1117  */
 1118 void
 1119 create_index_lock_file(
 1120     char *the_lock_file)
 1121 {
 1122     FILE *fp;
 1123     char buf[64];
 1124     int err;
 1125     time_t epoch;
 1126 
 1127     if ((fp = fopen(the_lock_file, "r")) != NULL) {
 1128         err = (fgets(buf, (int) sizeof(buf), fp) == NULL);
 1129         fclose(fp);
 1130         error_message(2, "\n%s: Already started pid=[%d] on %s", tin_progname, err ? 0 : atoi(buf), err ? "-" : buf + 8);
 1131         free(tin_progname);
 1132         giveup();
 1133     }
 1134     if ((fp = fopen(the_lock_file, "w")) != NULL) {
 1135 #ifdef HAVE_FCHMOD
 1136         fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR));
 1137 #else
 1138 #   ifdef HAVE_CHMOD
 1139         chmod(the_lock_file, (mode_t) (S_IRUSR|S_IWUSR));
 1140 #   endif /* HAVE_CHMOD */
 1141 #endif /* HAVE_FCHMOD */
 1142         (void) time(&epoch);
 1143         fprintf(fp, "%6d  %s\n", (int) process_id, ctime(&epoch));
 1144         if ((err = ferror(fp)) || fclose(fp)) {
 1145             error_message(2, _(txt_filesystem_full), the_lock_file);
 1146             if (err) {
 1147                 clearerr(fp);
 1148                 fclose(fp);
 1149             }
 1150         }
 1151     }
 1152 }
 1153 
 1154 
 1155 /*
 1156  * strfquote() - produce formatted quote string
 1157  *   %A  Articles Email address
 1158  *   %D  Articles Date (uses tinrc.date_format)
 1159  *   %F  Articles Address+Name
 1160  *   %G  Groupname of Article
 1161  *   %M  Articles MessageId
 1162  *   %N  Articles Name of author
 1163  *   %C  First Name of author
 1164  *   %I  Initials of author
 1165  * Return number of characters written (???) or 0 on failure
 1166  */
 1167 int
 1168 strfquote(
 1169     const char *group,
 1170     int respnum,
 1171     char *s,
 1172     size_t maxsize,
 1173     char *format)
 1174 {
 1175     const char *endp;
 1176     char *start = s;
 1177     char tbuf[LEN];
 1178     int i, j;
 1179     t_bool iflag;
 1180 
 1181     if (s == NULL || format == NULL || maxsize == 0)
 1182         return 0;
 1183 
 1184     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 1185         return 0;
 1186 
 1187     endp = s + maxsize;
 1188     for (; *format && s < endp - 1; format++) {
 1189         tbuf[0] = '\0';
 1190 
 1191         if (*format != '\\' && *format != '%') {
 1192             *s++ = *format;
 1193             continue;
 1194         }
 1195 
 1196         if (*format == '\\') {
 1197             switch (*++format) {
 1198                 case '\0':
 1199                     *s++ = '\\';
 1200                     goto out;
 1201                     /* NOTREACHED */
 1202                     break;
 1203 
 1204                 case 'n':   /* linefeed */
 1205                     strcpy(tbuf, "\n");
 1206                     break;
 1207 
 1208                 case 't':   /* tab */
 1209                     strcpy(tbuf, "\t");
 1210                     break;
 1211 
 1212                 default:
 1213                     tbuf[0] = '%';
 1214                     tbuf[1] = *format;
 1215                     tbuf[2] = '\0';
 1216                     break;
 1217             }
 1218             i = (int) strlen(tbuf);
 1219             if (i) {
 1220                 if (s + i < endp - 1) {
 1221                     strcpy(s, tbuf);
 1222                     s += i;
 1223                 } else
 1224                     return 0;
 1225             }
 1226         }
 1227         if (*format == '%') {
 1228             switch (*++format) {
 1229 
 1230                 case '\0':
 1231                     *s++ = '%';
 1232                     goto out;
 1233                     /* NOTREACHED */
 1234                     break;
 1235 
 1236                 case '%':
 1237                     *s++ = '%';
 1238                     continue;
 1239 
 1240                 case 'A':   /* Articles Email address */
 1241                     STRCPY(tbuf, arts[respnum].from);
 1242                     break;
 1243 
 1244                 case 'C':   /* First Name of author */
 1245                     if (arts[respnum].name != NULL) {
 1246                         STRCPY(tbuf, arts[respnum].name);
 1247                         if (strchr(tbuf, ' '))
 1248                             *(strchr(tbuf, ' ')) = '\0';
 1249                     } else {
 1250                         STRCPY(tbuf, arts[respnum].from);
 1251                     }
 1252                     break;
 1253 
 1254                 case 'D':   /* Articles Date (reformatted as specified in attributes->date_format) */
 1255                     if (!my_strftime(tbuf, LEN - 1, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
 1256                         STRCPY(tbuf, BlankIfNull(pgart.hdr.date));
 1257                     }
 1258                     break;
 1259 
 1260                 case 'F':   /* Articles Address+Name */
 1261                     if (arts[respnum].name)
 1262                         snprintf(tbuf, sizeof(tbuf), "%s <%s>", arts[respnum].name, arts[respnum].from);
 1263                     else {
 1264                         STRCPY(tbuf, arts[respnum].from);
 1265                     }
 1266                     break;
 1267 
 1268                 case 'G':   /* Groupname of Article */
 1269                     STRCPY(tbuf, group);
 1270                     break;
 1271 
 1272                 case 'I':   /* Initials of author */
 1273                     STRCPY(tbuf, ((arts[respnum].name != NULL) ? arts[respnum].name : arts[respnum].from));
 1274                     j = 0;
 1275                     iflag = TRUE;
 1276                     for (i = 0; tbuf[i]; i++) {
 1277                         if (iflag && tbuf[i] != ' ') {
 1278                             tbuf[j++] = tbuf[i];
 1279                             iflag = FALSE;
 1280                         }
 1281                         if (strchr(" ._@", tbuf[i]))
 1282                             iflag = TRUE;
 1283                     }
 1284                     tbuf[j] = '\0';
 1285                     break;
 1286 
 1287                 case 'M':   /* Articles Message-ID */
 1288                     STRCPY(tbuf, BlankIfNull(pgart.hdr.messageid));
 1289                     break;
 1290 
 1291                 case 'N':   /* Articles Name of author */
 1292                     STRCPY(tbuf, ((arts[respnum].name != NULL) ? arts[respnum].name : arts[respnum].from));
 1293                     break;
 1294 
 1295                 default:
 1296                     tbuf[0] = '%';
 1297                     tbuf[1] = *format;
 1298                     tbuf[2] = '\0';
 1299                     break;
 1300             }
 1301             i = (int) strlen(tbuf);
 1302             if (i) {
 1303                 if (s + i < endp - 1) {
 1304                     strcpy(s, tbuf);
 1305                     s += i;
 1306                 } else
 1307                     return 0;
 1308             }
 1309         }
 1310     }
 1311 out:
 1312     if (s < endp && *format == '\0') {
 1313         *s = '\0';
 1314         return (int) (s - start);
 1315     } else
 1316         return 0;
 1317 }
 1318 
 1319 
 1320 /*
 1321  * strfeditor() - produce formatted editor string
 1322  *   %E  Editor
 1323  *   %F  Filename
 1324  *   %N  Linenumber
 1325  */
 1326 static int
 1327 strfeditor(
 1328     char *editor,
 1329     int linenum,
 1330     const char *filename,
 1331     char *s,
 1332     size_t maxsize,
 1333     char *format)
 1334 {
 1335     const char *endp;
 1336     char *start = s;
 1337     char tbuf[PATH_LEN];
 1338     int i;
 1339 
 1340     if (s == NULL || format == NULL || maxsize == 0)
 1341         return 0;
 1342 
 1343     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 1344         return 0;
 1345 
 1346     endp = s + maxsize;
 1347     for (; *format && s < endp - 1; format++) {
 1348         tbuf[0] = '\0';
 1349 
 1350         if (*format != '\\' && *format != '%') {
 1351             *s++ = *format;
 1352             continue;
 1353         }
 1354 
 1355         if (*format == '\\') {
 1356             switch (*++format) {
 1357                 case '\0':
 1358                     *s++ = '\\';
 1359                     goto out;
 1360                     /* NOTREACHED */
 1361                     break;
 1362 
 1363                 case 'n':   /* linefeed */
 1364                     strcpy(tbuf, "\n");
 1365                     break;
 1366 
 1367                 default:
 1368                     tbuf[0] = '%';
 1369                     tbuf[1] = *format;
 1370                     tbuf[2] = '\0';
 1371                     break;
 1372             }
 1373             i = (int) strlen(tbuf);
 1374             if (i) {
 1375                 if (s + i < endp - 1) {
 1376                     strcpy(s, tbuf);
 1377                     s += i;
 1378                 } else
 1379                     return 0;
 1380             }
 1381         }
 1382         if (*format == '%') {
 1383             switch (*++format) {
 1384                 case '\0':
 1385                     *s++ = '%';
 1386                     goto out;
 1387                     /* NOTREACHED */
 1388                     break;
 1389 
 1390                 case '%':
 1391                     *s++ = '%';
 1392                     continue;
 1393 
 1394                 case 'E':   /* Editor */
 1395                     STRCPY(tbuf, editor);
 1396                     break;
 1397 
 1398                 case 'F':   /* Filename */
 1399                     STRCPY(tbuf, filename);
 1400                     break;
 1401 
 1402                 case 'N':   /* Line number */
 1403                     sprintf(tbuf, "%d", linenum);
 1404                     break;
 1405 
 1406                 default:
 1407                     tbuf[0] = '%';
 1408                     tbuf[1] = *format;
 1409                     tbuf[2] = '\0';
 1410                     break;
 1411             }
 1412             i = (int) strlen(tbuf);
 1413             if (i) {
 1414                 if (s + i < endp - 1) {
 1415                     strcpy(s, tbuf);
 1416                     s += i;
 1417                 } else
 1418                     return 0;
 1419             }
 1420         }
 1421     }
 1422 out:
 1423     if (s < endp && *format == '\0') {
 1424         *s = '\0';
 1425         return (int) (s - start);
 1426     } else
 1427         return 0;
 1428 }
 1429 
 1430 
 1431 /*
 1432  * Helper function for strfpath() to copy expanded strings
 1433  * into the output buffer. Return new output buffer or NULL
 1434  * if we overflowed it.
 1435  */
 1436 static char *
 1437 strfpath_cp(
 1438     char *str,
 1439     char *tbuf,
 1440     const char *endp)
 1441 {
 1442     size_t i;
 1443 
 1444     if ((i = strlen(tbuf))) {
 1445         if (str + i < endp - 1) {
 1446             strcpy(str, tbuf);
 1447             str += i;
 1448         } else {
 1449             str[0] = '\0';
 1450             return NULL;
 1451         }
 1452     }
 1453     return str;
 1454 }
 1455 
 1456 
 1457 /*
 1458  * strfpath - produce formatted pathname expansion. Handles following forms:
 1459  *   ~/News    -> $HOME/News
 1460  *   ~abc/News -> /home/abc/News
 1461  *   $var/News -> /env/var/News
 1462  *   =file     -> $HOME/Mail/file
 1463  *   =         -> $HOME/Mail/group.name   (shorthand for =%G)
 1464  *   +file     -> savedir/group.name/file
 1465  *   %G        -> group.name              (group.name is a file )
 1466  *   %G/file   -> group.name/file         (group.name is a dir)
 1467  *   %P        -> group/name              (name is a file)
 1468  *   %P/file   -> group/name/file         (name is a dir)
 1469  *
 1470  * Inputs:
 1471  *   format     The string to be converted
 1472  *   str        Return buffer
 1473  *   maxsize    Size of str
 1474  *   group      ptr to current group
 1475  *   expand_all true if '+' and '=' should be expanded
 1476  * Returns:
 1477  *   0          on error
 1478  *   1          if generated pathname is a mailbox
 1479  *   2          success
 1480  */
 1481 static int
 1482 _strfpath(
 1483     const char *format,
 1484     char *str,
 1485     size_t maxsize,
 1486     struct t_group *group,
 1487     t_bool expand_all)
 1488 {
 1489     const char *endp;
 1490     char *envptr;
 1491     char defbuf[PATH_LEN];
 1492     char tbuf[PATH_LEN];
 1493     const char *startp = format;
 1494     int i;
 1495     struct passwd *pwd;
 1496     t_bool is_mailbox = FALSE;
 1497 
 1498     if (str == NULL || format == NULL || maxsize == 0)
 1499         return 0;
 1500 
 1501     if (strlen(format) + 1 >= maxsize)
 1502         return 0;
 1503 
 1504     endp = str + maxsize;
 1505     for (; *format && str < endp - 1; format++) {
 1506         tbuf[0] = '\0';
 1507 
 1508         /*
 1509          * If just a normal part of the pathname copy it
 1510          */
 1511         if (!strchr("~$=+%", *format)) {
 1512             *str++ = *format;
 1513             continue;
 1514         }
 1515 
 1516         switch (*format) {
 1517             case '~':           /* Users or another users homedir */
 1518                 switch (*++format) {
 1519                     case '/':   /* users homedir */
 1520                         joinpath(tbuf, sizeof(tbuf), homedir, "");
 1521                         break;
 1522 
 1523                     default:    /* some other users homedir */
 1524                         i = 0;
 1525                         while (*format && *format != '/')
 1526                             tbuf[i++] = *format++;
 1527                         tbuf[i] = '\0';
 1528                         /*
 1529                          * OK lookup the username in /etc/passwd
 1530                          */
 1531                         if ((pwd = getpwnam(tbuf)) == NULL) {
 1532                             str[0] = '\0';
 1533                             return 0;
 1534                         } else
 1535                             sprintf(tbuf, "%s/", pwd->pw_dir);
 1536                         break;
 1537                 }
 1538                 if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1539                     return 0;
 1540                 break;
 1541 
 1542             case '$':   /* Read the envvar and use its value */
 1543                 i = 0;
 1544                 format++;
 1545                 if (*format == '{') {
 1546                     format++;
 1547                     while (*format && !(strchr("}-", *format)))
 1548                         tbuf[i++] = *format++;
 1549                     tbuf[i] = '\0';
 1550                     i = 0;
 1551                     if (*format == '-') {
 1552                         format++;
 1553                         while (*format && *format != '}')
 1554                             defbuf[i++] = *format++;
 1555                     }
 1556                     defbuf[i] = '\0';
 1557                 } else {
 1558                     while (*format && *format != '/')
 1559                         tbuf[i++] = *format++;
 1560                     tbuf[i] = '\0';
 1561                     format--;
 1562                     defbuf[0] = '\0';
 1563                 }
 1564                 /*
 1565                  * OK lookup the variable in the shells environment
 1566                  */
 1567                 envptr = getenv(tbuf);
 1568                 if (envptr == NULL || (*envptr == '\0'))
 1569                     strncpy(tbuf, defbuf, sizeof(tbuf) - 1);
 1570                 else
 1571                     strncpy(tbuf, envptr, sizeof(tbuf) - 1);
 1572                 if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1573                     return 0;
 1574                 else if (*tbuf == '\0') {
 1575                     str[0] = '\0';
 1576                     return 0;
 1577                 }
 1578                 break;
 1579 
 1580             case '=':
 1581                 /*
 1582                  * Mailbox name expansion
 1583                  * Only expand if 1st char in format
 1584                  * =dir expands to maildir/dir
 1585                  * =    expands to maildir/groupname
 1586                  */
 1587                 if (startp == format && group != NULL && expand_all) {
 1588                     char buf[PATH_LEN];
 1589 
 1590                     is_mailbox = TRUE;
 1591                     if (strfpath((cmdline.args & CMDLINE_MAILDIR) ? cmdline.maildir : group->attribute->maildir, buf, sizeof(buf), group, FALSE)) {
 1592                         if (*(format + 1) == '\0')              /* Just an = */
 1593                             joinpath(tbuf, sizeof(tbuf), buf, group->name);
 1594                         else
 1595                             joinpath(tbuf, sizeof(tbuf), buf, "");
 1596                         if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1597                             return 0;
 1598                     } else {
 1599                         str[0] = '\0';
 1600                         return 0;
 1601                     }
 1602                 } else                  /* Wasn't the first char in format */
 1603                     *str++ = *format;
 1604                 break;
 1605 
 1606             case '+':
 1607                 /*
 1608                  * Group name expansion
 1609                  * Only convert if 1st char in format
 1610                  * +file expands to savedir/group.name/file
 1611                  */
 1612 
 1613                 if (startp == format && group != NULL && expand_all) {
 1614                     char buf[PATH_LEN];
 1615 
 1616                     /*
 1617                      * Start with the savedir name
 1618                      */
 1619                     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : group->attribute->savedir, buf, sizeof(buf), group, FALSE)) {
 1620                         char tmp[PATH_LEN];
 1621 #ifdef HAVE_LONG_FILE_NAMES
 1622                         my_strncpy(tmp, group->name, sizeof(tmp) - 1);
 1623 #else
 1624                         my_strncpy(tmp, group->name, 14);
 1625 #endif /* HAVE_LONG_FILE_NAMES */
 1626                         joinpath(tbuf, sizeof(tbuf), buf, tmp); /* Add the group name */
 1627                         joinpath(tmp, sizeof(tmp), tbuf, "");
 1628                         if ((str = strfpath_cp(str, tmp, endp)) == NULL)
 1629                             return 0;
 1630                     } else {
 1631                         str[0] = '\0';
 1632                         return 0;
 1633                     }
 1634                 } else                  /* Wasn't the first char in format */
 1635                     *str++ = *format;
 1636                 break;
 1637 
 1638             case '%':   /* Different forms of parsing cmds */
 1639                 format++;
 1640                 if (group != NULL && *format == 'G') {
 1641                     memset(tbuf, 0, sizeof(tbuf));
 1642                     STRCPY(tbuf, group->name);
 1643                     i = (int) strlen(tbuf);
 1644                     if (((str + i) < (endp - 1)) && (i > 0)) {
 1645                         strcpy(str, tbuf);
 1646                         str += i;
 1647                     } else {
 1648                         str[0] = '\0';
 1649                         return 0;
 1650                     }
 1651                     break;
 1652                 }
 1653                 if (group != NULL && *format == 'P') {
 1654                     char *pbuf = my_malloc(strlen(group->name) + 2); /* trailing "/\0" */
 1655 
 1656                     make_group_path(group->name, pbuf);
 1657                     if ((i = (int) strlen(pbuf)))
 1658                         pbuf[i--] = '\0'; /* remove trailing '/' */
 1659                     else {
 1660                         str[0] = '\0';
 1661                         free(pbuf);
 1662                         return 0;
 1663                     }
 1664                     if (((str + i) < (endp - 1)) && (i > 0)) {
 1665                         strcpy(str, pbuf);
 1666                         free(pbuf);
 1667                         str += i;
 1668                     } else {
 1669                         str[0] = '\0';
 1670                         free(pbuf);
 1671                         return 0;
 1672                     }
 1673                     break;
 1674                 }
 1675                 *str++ = *format;
 1676                 /* FALLTHROUGH */
 1677             default:
 1678                 break;
 1679         }
 1680     }
 1681 
 1682     if (str < endp && *format == '\0') {
 1683         *str = '\0';
 1684         if (is_mailbox)
 1685             return 1;
 1686         else
 1687             return 2;
 1688     } else {
 1689         str[0] = '\0';
 1690         return 0;
 1691     }
 1692 }
 1693 
 1694 
 1695 /*
 1696  * The real entry point, exists only to expand leading '$'
 1697  */
 1698 int
 1699 strfpath(
 1700     const char *format,
 1701     char *str,
 1702     size_t maxsize,
 1703     struct t_group *group,
 1704     t_bool expand_all)
 1705 {
 1706     /*
 1707      * Expand any leading env vars first in case they themselves contain
 1708      * formatting chars
 1709      */
 1710     if (format[0] == '$') {
 1711         char buf[PATH_LEN];
 1712 
 1713         if (_strfpath(format, buf, sizeof(buf), group, expand_all))
 1714             return (_strfpath(buf, str, maxsize, group, expand_all));
 1715     }
 1716 
 1717     return (_strfpath(format, str, maxsize, group, expand_all));
 1718 }
 1719 
 1720 
 1721 /*
 1722  * TODO: Properly explain this
 1723  */
 1724 char *
 1725 escape_shell_meta(
 1726     const char *source,
 1727     int quote_area)
 1728 {
 1729     static char buf[PATH_LEN];
 1730     char *dest = buf;
 1731     int space = sizeof(buf) - 2;
 1732 
 1733     switch (quote_area) {
 1734         case no_quote:
 1735             while (*source && (space > 0)) {
 1736                 if (*source == '\'' || *source == '\\' || *source == '"' ||
 1737                     *source == '$' || *source == '`' || *source == '*' ||
 1738                     *source == '&' || *source == '|' || *source == '<' ||
 1739                     *source == '>' || *source == ';' || *source == '(' ||
 1740                     *source == ')') {
 1741                     *dest++ = '\\';
 1742                     space--;
 1743                 }
 1744                 *dest++ = *source++;
 1745                 space--;
 1746             }
 1747             break;
 1748 
 1749         case dbl_quote:
 1750             while (*source && (space > 0)) {
 1751                 if (*source == '\\' || *source == '"' || *source == '$' ||
 1752                     *source == '`') {
 1753                     *dest++ = '\\';
 1754                     space--;
 1755                 }
 1756                 *dest++ = *source++;
 1757                 space--;
 1758             }
 1759             break;
 1760 
 1761         case sgl_quote:
 1762             while (*source && (space > 4)) {
 1763                 if (*source == '\'') {
 1764                     *dest++ = '\'';
 1765                     *dest++ = '\\';
 1766                     *dest++ = '\'';
 1767                     space -= 3;
 1768                 }
 1769                 *dest++ = *source++;
 1770                 space--;
 1771             }
 1772             break;
 1773 
 1774         default:
 1775             break;
 1776     }
 1777 
 1778     *dest = '\0';
 1779     return buf;
 1780 }
 1781 
 1782 
 1783 /*
 1784  * strfmailer() - produce formatted mailer string
 1785  *   %M  Mailer
 1786  *   %F  Filename
 1787  *   %T  To
 1788  *   %S  Subject
 1789  *   %U  User
 1790  * Returns length of produced string (is always ignored currently).
 1791  */
 1792 int
 1793 strfmailer(
 1794     const char *mail_prog,
 1795     char *subject,  /* FIXME: should be const char */
 1796     char *to, /* FIXME: should be const char */
 1797     const char *filename,
 1798     char *dest,
 1799     size_t maxsize,
 1800     const char *format)
 1801 {
 1802     const char *endp;
 1803     char *start = dest;
 1804     char tbuf[PATH_LEN];
 1805     int quote_area = no_quote;
 1806 
 1807     /*
 1808      * safe guards: no destination to write to, no format, no space to
 1809      * write, or nothing to replace and format string longer than available
 1810      * space => return without any action
 1811      */
 1812     if (dest == NULL || format == NULL || maxsize == 0)
 1813         return 0;
 1814 
 1815     /*
 1816      * TODO: shouldn't we better check for no % OR format > maxsize?
 1817      *       as no replacement doesn't make sense (hard coded To, Subject
 1818      *       and filename) and the resulting string usually is longer after
 1819      *       replacements were done (nobody uses enough %% to make the
 1820      *       result shorter than the input).
 1821      */
 1822     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 1823         return 0;
 1824 
 1825     /*
 1826      * walk through format until end of format or end of available space
 1827      * and replace place holders
 1828      */
 1829     endp = dest + maxsize;
 1830     for (; *format && dest < endp - 1; format++) {
 1831         tbuf[0] = '\0';
 1832 
 1833         /*
 1834          * take over any character other than '\' and '%' and continue with
 1835          * next character in format; remember quote area
 1836          */
 1837         if (*format != '\\' && *format != '%') {
 1838             if (*format == '"' && quote_area != sgl_quote)
 1839                 quote_area = (quote_area == dbl_quote ? no_quote : dbl_quote);
 1840             if (*format == '\'' && quote_area != dbl_quote)
 1841                 quote_area = (quote_area == sgl_quote ? no_quote : sgl_quote);
 1842             *dest++ = *format;
 1843             continue;
 1844         }
 1845 
 1846         /*
 1847          * handle sequences introduced by '\':
 1848          * - "\n" gets line feed
 1849          * - '\' followed by NULL gets '\' and leaves loop
 1850          * - '\' followed by any other character is copied literally and
 1851          *   shell escaped; if that exceeds the available space, return 0
 1852          */
 1853         if (*format == '\\') {
 1854             switch (*++format) {
 1855                 case '\0':
 1856                     *dest++ = '\\';
 1857                     goto out;
 1858                     /* NOTREACHED */
 1859                     break;
 1860 
 1861                 case 'n':   /* linefeed */
 1862                     strcpy(tbuf, "\n");
 1863                     break;
 1864 
 1865                 default:
 1866                     tbuf[0] = '\\';
 1867                     tbuf[1] = *format;
 1868                     tbuf[2] = '\0';
 1869                     break;
 1870             }
 1871             if (*tbuf) {
 1872                 if (sh_format(dest, (size_t) (endp - dest), "%s", tbuf) >= 0)
 1873                     dest += strlen(dest);
 1874                 else
 1875                     return 0;
 1876             }
 1877         }
 1878 
 1879         /*
 1880          * handle sequences introduced by '%'
 1881          * - '%' followed by NULL gets '%' and leaves loop
 1882          * - '%%' gets '%'
 1883          * - '%F' expands to filename
 1884          * - '%M' expands to mailer program
 1885          * - '%S' expands to subject of message
 1886          * - '%T' expands to recipient(s) of message
 1887          * - '%U' expands to userid
 1888          * - '%' followed by any other character is copied literally
 1889          */
 1890         if (*format == '%') {
 1891             char *p;
 1892             t_bool ismail = TRUE;
 1893             t_bool escaped = FALSE;
 1894             switch (*++format) {
 1895                 case '\0':
 1896                     *dest++ = '%';
 1897                     goto out;
 1898 
 1899                 case '%':
 1900                     *dest++ = '%';
 1901                     continue;
 1902 
 1903                 case 'F':   /* Filename */
 1904                     STRCPY(tbuf, filename);
 1905                     break;
 1906 
 1907                 case 'M':   /* Mailer */
 1908                     STRCPY(tbuf, mail_prog);
 1909                     break;
 1910 
 1911                 case 'S':   /* Subject */
 1912                     /* don't MIME encode Subject if using external mail client */
 1913                     if (tinrc.interactive_mailer != INTERACTIVE_NONE)
 1914                         strncpy(tbuf, escape_shell_meta(subject, quote_area), sizeof(tbuf) - 1);
 1915                     else {
 1916 #ifdef CHARSET_CONVERSION
 1917                         p = rfc1522_encode(subject, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1918 #else
 1919                         p = rfc1522_encode(subject, tinrc.mm_charset, ismail);
 1920 #endif /* CHARSET_CONVERSION */
 1921                         strncpy(tbuf, escape_shell_meta(p, quote_area), sizeof(tbuf) - 1);
 1922                         free(p);
 1923                     }
 1924                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1925                     escaped = TRUE;
 1926                     break;
 1927 
 1928                 case 'T':   /* To */
 1929                     /* don't MIME encode To if using external mail client */
 1930                     if (tinrc.interactive_mailer != INTERACTIVE_NONE)
 1931                         strncpy(tbuf, escape_shell_meta(to, quote_area), sizeof(tbuf) - 1);
 1932                     else {
 1933 #ifdef CHARSET_CONVERSION
 1934                         p = rfc1522_encode(to, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1935 #else
 1936                         p = rfc1522_encode(to, tinrc.mm_charset, ismail);
 1937 #endif /* CHARSET_CONVERSION */
 1938                         strncpy(tbuf, escape_shell_meta(p, quote_area), sizeof(tbuf) - 1);
 1939                         free(p);
 1940                     }
 1941                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1942                     escaped = TRUE;
 1943                     break;
 1944 
 1945                 case 'U':   /* User */
 1946                     /* don't MIME encode User if using external mail client */
 1947                     if (tinrc.interactive_mailer != INTERACTIVE_NONE)
 1948                         strncpy(tbuf, userid, sizeof(tbuf) - 1);
 1949                     else {
 1950 #ifdef CHARSET_CONVERSION
 1951                         p = rfc1522_encode(userid, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1952 #else
 1953                         p = rfc1522_encode(userid, tinrc.mm_charset, ismail);
 1954 #endif /* CHARSET_CONVERSION */
 1955                         strncpy(tbuf, p, sizeof(tbuf) - 1);
 1956                         free(p);
 1957                     }
 1958                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1959                     break;
 1960 
 1961                 default:
 1962                     tbuf[0] = '%';
 1963                     tbuf[1] = *format;
 1964                     tbuf[2] = '\0';
 1965                     break;
 1966             }
 1967             if (*tbuf) {
 1968                 if (escaped) {
 1969                     if (endp - dest > 0) {
 1970                         strncpy(dest, tbuf, (size_t) (endp - dest));
 1971                         dest += strlen(dest);
 1972                     }
 1973                 } else if (sh_format(dest, (size_t) (endp - dest), "%s", tbuf) >= 0) {
 1974                     dest += strlen(dest);
 1975                 } else
 1976                     return 0;
 1977             }
 1978         }
 1979     }
 1980 out:
 1981     if (dest < endp && *format == '\0') {
 1982         *dest = '\0';
 1983         return (int) (dest - start);
 1984     } else
 1985         return 0;
 1986 }
 1987 
 1988 
 1989 /*
 1990  * get_initials() - get initial letters of a posters name
 1991  */
 1992 int
 1993 get_initials(
 1994     struct t_article *art,
 1995     char *s,
 1996     int maxsize) /* return value is always 0 and ignored */
 1997 {
 1998     char tbuf[PATH_LEN];
 1999     int i, j = 0;
 2000     t_bool iflag = FALSE;
 2001 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2002     wchar_t *wtmp, *wbuf;
 2003 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2004 
 2005     if (s == NULL || maxsize <= 0)
 2006         return 0;
 2007 
 2008     s[0] = '\0';
 2009     STRCPY(tbuf, ((art->name != NULL) ? art->name : art->from));
 2010 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 2011     if ((wtmp = char2wchar_t(tbuf)) != NULL) {
 2012         wbuf = my_malloc(sizeof(wchar_t) * (size_t) (maxsize + 1));
 2013         for (i = 0; wtmp[i] && j < maxsize; i++) {
 2014             if (iswalpha((wint_t) wtmp[i])) {
 2015                 if (!iflag) {
 2016                     wbuf[j++] = wtmp[i];
 2017                     iflag = TRUE;
 2018                 }
 2019             } else
 2020                 iflag = FALSE;
 2021         }
 2022         wbuf[j] = (wchar_t) '\0';
 2023         s[0] = '\0';
 2024         if (wcstombs(tbuf, wbuf, sizeof(tbuf) - 1) != (size_t) -1)
 2025             strcat(s, tbuf);
 2026         free(wtmp);
 2027         free(wbuf);
 2028     }
 2029 #else
 2030     for (i = 0; tbuf[i] && j < maxsize; i++) {
 2031         if (isalpha((int)(unsigned char) tbuf[i])) {
 2032             if (!iflag) {
 2033                 s[j++] = tbuf[i];
 2034                 iflag = TRUE;
 2035             }
 2036         } else
 2037             iflag = FALSE;
 2038     }
 2039     s[j] = '\0';
 2040 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2041     return 0;
 2042 }
 2043 
 2044 
 2045 void
 2046 get_cwd(
 2047     char *buf)
 2048 {
 2049 #ifdef HAVE_GETCWD
 2050     if (getcwd(buf, PATH_LEN) == NULL) {
 2051 #   ifdef DEBUG
 2052         int e = errno;
 2053         if (debug & DEBUG_MISC)
 2054             error_message(2, "getcwd(%s): Error: %s", buf, strerror(e));
 2055 #   endif /* DEBUG */
 2056     }
 2057 #else
 2058 #   ifdef HAVE_GETWD
 2059     if (getwd(buf) == NULL) {
 2060 #       ifdef DEBUG
 2061         int e = errno;
 2062         if (debug & DEBUG_MISC)
 2063             error_message(2, "getwd(%s): Error: %s", buf, strerror(e));
 2064 #       endif /* DEBUG */
 2065     }
 2066 #   else
 2067     *buf = '\0';
 2068 #   endif /* HAVE_GETWD */
 2069 #endif /* HAVE_GETCWD */
 2070 }
 2071 
 2072 
 2073 /*
 2074  * Convert a newsgroup name to a newsspool path
 2075  * No effect when reading via NNTP
 2076  */
 2077 void
 2078 make_group_path(
 2079     const char *name,
 2080     char *path)
 2081 {
 2082     while (*name) {
 2083         *path++ = ((*name == '.') ? '/' : *name);
 2084         name++;
 2085     }
 2086     *path++ = '/';
 2087     *path = '\0';
 2088 }
 2089 
 2090 
 2091 /*
 2092  * Given a base pathname & a newsgroup name build an absolute pathname.
 2093  * base_dir = /usr/spool/news
 2094  * group_name = alt.sources
 2095  * group_path = /usr/spool/news/alt/sources
 2096  */
 2097 void
 2098 make_base_group_path(
 2099     const char *base_dir,
 2100     const char *group_name,
 2101     char *group_path,
 2102     size_t group_path_len)
 2103 {
 2104     char *buf = my_malloc(strlen(group_name) + 2); /* trailing "/\0" */
 2105 
 2106     make_group_path(group_name, buf);
 2107     joinpath(group_path, group_path_len, base_dir, buf);
 2108     free(buf);
 2109 }
 2110 
 2111 
 2112 /*
 2113  * Delete index lock
 2114  */
 2115 void
 2116 cleanup_tmp_files(
 2117     void)
 2118 {
 2119     /*
 2120      * only required if update_index == TRUE, but update_index is
 2121      * unknown here
 2122      */
 2123     if (batch_mode)
 2124         unlink(lock_file);
 2125 }
 2126 
 2127 
 2128 /*
 2129  * returns filesize in bytes
 2130  * -1 in case of an error (file not found, or !S_IFREG)
 2131  */
 2132 long /* we use long here as off_t might be unsigned on some systems */
 2133 file_size(
 2134     const char *file)
 2135 {
 2136     struct stat statbuf;
 2137 
 2138     return (stat(file, &statbuf) == -1 ? -1L : (S_ISREG(statbuf.st_mode)) ? (long) statbuf.st_size : -1L);
 2139 }
 2140 
 2141 
 2142 /*
 2143  * returns mtime
 2144  * -1 in case of an error (file not found, or !S_IFREG)
 2145  */
 2146 long /* we use long (not time_t) here */
 2147 file_mtime(
 2148     const char *file)
 2149 {
 2150     struct stat statbuf;
 2151 
 2152     return (stat(file, &statbuf) == -1 ? -1L : (S_ISREG(statbuf.st_mode)) ? (long) statbuf.st_mtime : -1L);
 2153 }
 2154 
 2155 
 2156 /*
 2157  * TODO: this feature isn't documented anywhere
 2158  */
 2159 char *
 2160 random_organization(
 2161     char *in_org)
 2162 {
 2163     FILE *orgfp;
 2164     int nool = 0, sol;
 2165     static char selorg[512];
 2166 
 2167     *selorg = '\0';
 2168 
 2169     if (*in_org != '/')
 2170         return in_org;
 2171 
 2172     srand((unsigned int) time(NULL));
 2173 
 2174     if ((orgfp = fopen(in_org, "r")) == NULL)
 2175         return selorg;
 2176 
 2177     /* count lines */
 2178     while (fgets(selorg, (int) sizeof(selorg), orgfp))
 2179         nool++;
 2180 
 2181     if (!nool) {
 2182         fclose(orgfp);
 2183         return selorg;
 2184     }
 2185 
 2186     rewind(orgfp);
 2187     sol = rand() % nool + 1;
 2188     nool = 0;
 2189     while ((nool != sol) && (fgets(selorg, (int) sizeof(selorg), orgfp)))
 2190         nool++;
 2191 
 2192     fclose(orgfp);
 2193 
 2194     return selorg;
 2195 }
 2196 
 2197 
 2198 void
 2199 read_input_history_file(
 2200     void)
 2201 {
 2202     FILE *fp;
 2203     char *chr;
 2204     char buf[HEADER_LEN];
 2205     int his_w = 0, his_e = 0, his_free = 0;
 2206 
 2207     /* this is usually .tin/.inputhistory */
 2208     if ((fp = fopen(local_input_history_file, "r")) == NULL)
 2209         return;
 2210 
 2211     if (!batch_mode)
 2212         wait_message(0, _(txt_reading_input_history_file));
 2213 
 2214     /* to be safe ;-) */
 2215     memset((void *) input_history, 0, sizeof(input_history));
 2216     memset((void *) hist_last, 0, sizeof(hist_last));
 2217     memset((void *) hist_pos, 0, sizeof(hist_pos));
 2218 
 2219     while (fgets(buf, (int) sizeof(buf), fp)) {
 2220         if ((chr = strpbrk(buf, "\n\r")) != NULL)
 2221             *chr = '\0';
 2222 
 2223         if (*buf)
 2224             input_history[his_w][his_e] = my_strdup(buf);
 2225         else {
 2226             /* empty lines in tin_getline's history buf are stored as NULL pointers */
 2227             input_history[his_w][his_e] = NULL;
 2228 
 2229             /* get the empty slot in the circular buf */
 2230             if (!his_free)
 2231                 his_free = his_e;
 2232         }
 2233 
 2234         his_e++;
 2235         /* check if next type is reached */
 2236         if (his_e >= HIST_SIZE) {
 2237             hist_pos[his_w] = hist_last[his_w] = his_free;
 2238             his_free = his_e = 0;
 2239             his_w++;
 2240         }
 2241         /* check if end is reached */
 2242         if (his_w > HIST_MAXNUM)
 2243             break;
 2244     }
 2245     fclose(fp);
 2246 
 2247     if (cmd_line)
 2248         printf("\r\n");
 2249 }
 2250 
 2251 
 2252 static void
 2253 write_input_history_file(
 2254     void)
 2255 {
 2256     FILE *fp;
 2257     char *chr;
 2258     char *file_tmp;
 2259     int his_w, his_e;
 2260     mode_t mask;
 2261 
 2262     if (no_write)
 2263         return;
 2264 
 2265     mask = umask((mode_t) (S_IRWXO|S_IRWXG));
 2266 
 2267     /* generate tmp-filename */
 2268     file_tmp = get_tmpfilename(local_input_history_file);
 2269 
 2270     if ((fp = fopen(file_tmp, "w")) == NULL) {
 2271         error_message(2, _(txt_filesystem_full_backup), local_input_history_file);
 2272         /* free memory for tmp-filename */
 2273         free(file_tmp);
 2274         umask(mask);
 2275         return;
 2276     }
 2277 
 2278     for (his_w = 0; his_w <= HIST_MAXNUM; his_w++) {
 2279         for (his_e = 0; his_e < HIST_SIZE; his_e++) {
 2280             /* write an empty line for empty slots */
 2281             if (input_history[his_w][his_e] == NULL)
 2282                 fprintf(fp, "\n");
 2283             else {
 2284                 if ((chr = strpbrk(input_history[his_w][his_e], "\n\r")) != NULL)
 2285                     *chr = '\0';
 2286                 fprintf(fp, "%s\n", input_history[his_w][his_e]);
 2287             }
 2288         }
 2289     }
 2290 #ifdef HAVE_FCHMOD
 2291     fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR)); /* rename_file() preserves mode */
 2292 #else
 2293 #   ifdef HAVE_CHMOD
 2294     chmod(file_tmp, (mode_t) (S_IRUSR|S_IWUSR)); /* rename_file() preserves mode */
 2295 #   endif /* HAVE_CHMOD */
 2296 #endif /* HAVE_FCHMOD */
 2297 
 2298     if ((his_w = ferror(fp)) || fclose(fp)) {
 2299         error_message(2, _(txt_filesystem_full), local_input_history_file);
 2300 #ifdef HAVE_CHMOD
 2301         /* fix modes for all pre 1.4.1 local_input_history_file files */
 2302         if (chmod(local_input_history_file, (mode_t) (S_IRUSR|S_IWUSR)) == -1) {
 2303 #   ifdef DEBUG
 2304             int e = errno;
 2305             if (debug & DEBUG_MISC)
 2306                 error_message(2, "chmod(%s, %d): Error: %s", local_input_history_file, (mode_t) (S_IRUSR|S_IWUSR), strerror(e));
 2307 #   endif /* DEBUG */
 2308         }
 2309 #endif /* HAVE_CHMOD */
 2310         if (his_w) {
 2311             clearerr(fp);
 2312             fclose(fp);
 2313         }
 2314     } else
 2315         rename_file(file_tmp, local_input_history_file);
 2316 
 2317     umask(mask);
 2318     free(file_tmp); /* free memory for tmp-filename */
 2319 }
 2320 
 2321 
 2322 /*
 2323  * quotes wildcards * ? \ [ ] with \
 2324  */
 2325 char *
 2326 quote_wild(
 2327     char *str)
 2328 {
 2329     char *target;
 2330     static char buff[2 * LEN];  /* on the safe side */
 2331 
 2332     for (target = buff; *str != '\0'; str++) {
 2333         if (tinrc.wildcard) { /* regex */
 2334             /*
 2335              * quote meta characters ()[]{}\^$*+?.#
 2336              * replace whitespace with '\s' (pcre)
 2337              */
 2338             if (*str == '(' || *str == ')' || *str == '[' || *str == ']' || *str == '{' || *str == '}'
 2339                 || *str == '\\' || *str == '^' || *str == '$'
 2340                 || *str == '*' || *str == '+' || *str == '?' || *str == '.'
 2341                 || *str == '#'
 2342                 || *str == ' ' || *str == '\t') {
 2343                 *target++ = '\\';
 2344                 *target++ = ((*str == ' ' || *str == '\t') ? 's' : *str);
 2345             } else
 2346                 *target++ = *str;
 2347         } else {    /* wildmat */
 2348             if (*str == '*' || *str == '\\' || *str == '[' || *str == ']' || *str == '?')
 2349                 *target++ = '\\';
 2350             *target++ = *str;
 2351         }
 2352     }
 2353     *target = '\0';
 2354     return buff;
 2355 }
 2356 
 2357 
 2358 /*
 2359  * quotes whitespace in regexps for pcre
 2360  */
 2361 char *
 2362 quote_wild_whitespace(
 2363     char *str)
 2364 {
 2365     char *target;
 2366     static char buff[2 * LEN];  /* on the safe side */
 2367 
 2368     for (target = buff; *str != '\0'; str++) {
 2369         if (tinrc.wildcard) { /* regex */
 2370             /*
 2371              * replace whitespace with '\s' (pcre)
 2372              */
 2373             if (*str == ' ' || *str == '\t') {
 2374                 *target++ = '\\';
 2375                 *target++ = 's';
 2376             } else
 2377                 *target++ = *str;
 2378         } else  /* wildmat */
 2379             *target++ = *str;
 2380     }
 2381     *target = '\0';
 2382     return buff;
 2383 }
 2384 
 2385 
 2386 /*
 2387  * strip_name() removes the realname part from a given e-mail address
 2388  */
 2389 void
 2390 strip_name(
 2391     const char *from,
 2392     char *address)
 2393 {
 2394     char name[HEADER_LEN];
 2395 
 2396     gnksa_do_check_from(from, address, name);
 2397 }
 2398 
 2399 
 2400 #ifdef CHARSET_CONVERSION
 2401 static t_bool
 2402 buffer_to_local(
 2403     char **line,
 2404     size_t *max_line_len,
 2405     const char *network_charset,
 2406     const char *local_charset)
 2407 {
 2408     /* FIXME: this should default in RFC2046.c to US-ASCII */
 2409     if ((network_charset && *network_charset)) {    /* Content-Type: had a charset parameter */
 2410         if (strcasecmp(network_charset, local_charset)) { /* different charsets */
 2411             char *clocal_charset;
 2412             iconv_t cd0, cd1, cd2;
 2413 
 2414             clocal_charset = my_malloc(strlen(local_charset) + strlen("//TRANSLIT") + 1);
 2415             strcpy(clocal_charset, local_charset);
 2416 #   ifdef HAVE_ICONV_OPEN_TRANSLIT
 2417             if (tinrc.translit)
 2418                 strcat(clocal_charset, "//TRANSLIT");
 2419 #   endif /* HAVE_ICONV_OPEN_TRANSLIT */
 2420 
 2421             /* iconv() might crash on broken multibyte sequences so check them */
 2422             if (!strcasecmp(network_charset, "UTF-8") || !strcasecmp(network_charset, "utf8"))
 2423                 (void) utf8_valid(*line);
 2424 
 2425             /*
 2426              * TODO: hardcode unknown_ucs4 (0x00 0x00 0x00 0x3f)
 2427              *       instead of converting it?
 2428              */
 2429             cd0 = iconv_open("UCS-4", "US-ASCII");
 2430             cd1 = iconv_open("UCS-4", network_charset);
 2431             cd2 = iconv_open(clocal_charset, "UCS-4");
 2432             if (cd0 != (iconv_t) (-1) && cd1 != (iconv_t) (-1) && cd2 != (iconv_t) (-1)) {
 2433                 char unknown = '?';
 2434                 char *unknown_buf;
 2435                 char unknown_ucs4[4];
 2436                 char *obuf, *outbuf;
 2437                 char *tmpbuf, *tbuf;
 2438                 ICONV_CONST char *inbuf;
 2439                 ICONV_CONST char *unknown_ascii = &unknown;
 2440                 ICONV_CONST char *cur_inbuf;
 2441                 int used;
 2442                 size_t inbytesleft = 1;
 2443                 size_t unknown_bytesleft = 4;
 2444                 size_t tmpbytesleft, tsize;
 2445                 size_t outbytesleft, osize;
 2446                 size_t cur_obl, cur_ibl;
 2447                 size_t result;
 2448 
 2449                 unknown_buf = unknown_ucs4;
 2450 
 2451                 /* convert '?' from ASCII to UCS-4 */
 2452                 iconv(cd0, &unknown_ascii, &inbytesleft, &unknown_buf, &unknown_bytesleft);
 2453 
 2454                 /* temporarily convert to UCS-4 */
 2455                 inbuf = (ICONV_CONST char *) *line;
 2456                 inbytesleft = strlen(*line);
 2457                 tmpbytesleft = inbytesleft * 4 + 4; /* should be enough */
 2458                 tsize = tmpbytesleft;
 2459                 tbuf = my_malloc(tsize);
 2460                 tmpbuf = (char *) tbuf;
 2461 
 2462                 do {
 2463                     errno = 0;
 2464                     result = iconv(cd1, &inbuf, &inbytesleft, &tmpbuf, &tmpbytesleft);
 2465                     if (result == (size_t) (-1)) {
 2466                         switch (errno) {
 2467                             case EILSEQ:
 2468                                 memcpy(tmpbuf, unknown_ucs4, 4);
 2469                                 tmpbuf += 4;
 2470                                 tmpbytesleft -= 4;
 2471                                 inbuf++;
 2472                                 inbytesleft--;
 2473                                 break;
 2474 
 2475                             case E2BIG:
 2476                                 tbuf = my_realloc(tbuf, tsize * 2);
 2477                                 tmpbuf = (char *) (tbuf + tsize - tmpbytesleft);
 2478                                 tmpbytesleft += tsize;
 2479                                 tsize <<= 1; /* double size */
 2480                                 break;
 2481 
 2482                             default:
 2483                                 inbytesleft = 0;
 2484                         }
 2485                     }
 2486                 } while (inbytesleft > 0);
 2487 
 2488                 /* now convert from UCS-4 to local charset */
 2489                 inbuf = (ICONV_CONST char *) tbuf;
 2490                 inbytesleft = tsize - tmpbytesleft;
 2491                 outbytesleft = inbytesleft;
 2492                 osize = outbytesleft;
 2493                 obuf = my_malloc(osize + 1);
 2494                 outbuf = (char *) obuf;
 2495 
 2496                 do {
 2497                     /*
 2498                      * save the parameters we need to redo the call of iconv
 2499                      * if we get into the E2BIG case
 2500                      */
 2501                     cur_inbuf = inbuf;
 2502                     cur_ibl = inbytesleft;
 2503                     used = (int) (outbuf - obuf);
 2504                     cur_obl = outbytesleft;
 2505 
 2506                     errno = 0;
 2507                     result = iconv(cd2, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
 2508                     if (result == (size_t) (-1)) {
 2509                         switch (errno) {
 2510                             case EILSEQ:
 2511                                 **&outbuf = '?';
 2512                                 outbuf++;
 2513                                 outbytesleft--;
 2514                                 inbuf += 4;
 2515                                 inbytesleft -= 4;
 2516                                 break;
 2517 
 2518                             case E2BIG:
 2519                                 /*
 2520                                  * outbuf was too small
 2521                                  * As some input could be converted successfully
 2522                                  * and we don`t know where the last complete char
 2523                                  * ends, redo the last conversion completely.
 2524                                  */
 2525                                 /* resize the output buffer */
 2526                                 obuf = my_realloc(obuf, osize * 2 + 1);
 2527                                 outbuf = obuf + used;
 2528                                 outbytesleft = cur_obl + osize;
 2529                                 osize <<= 1; /* double size */
 2530                                 /* reset the other params */
 2531                                 inbuf = cur_inbuf;
 2532                                 inbytesleft = cur_ibl;
 2533                                 break;
 2534 
 2535                             default:
 2536                                 inbytesleft = 0;
 2537                         }
 2538                     }
 2539                 } while (inbytesleft > 0);
 2540 
 2541                 **&outbuf = '\0';
 2542                 if (*max_line_len < strlen(obuf) + 1) {
 2543                     *max_line_len = strlen(obuf) + 1;
 2544                     *line = my_realloc(*line, *max_line_len);
 2545                 }
 2546                 strcpy(*line, obuf);
 2547                 iconv_close(cd2);
 2548                 iconv_close(cd1);
 2549                 iconv_close(cd0);
 2550                 free(obuf);
 2551                 free(tbuf);
 2552             } else {
 2553                 if (cd2 != (iconv_t) (-1))
 2554                     iconv_close(cd2);
 2555                 if (cd1 != (iconv_t) (-1))
 2556                     iconv_close(cd1);
 2557                 if (cd0 != (iconv_t) (-1))
 2558                     iconv_close(cd0);
 2559                 free(clocal_charset);
 2560                 return FALSE;
 2561             }
 2562             free(clocal_charset);
 2563         }
 2564     }
 2565     return TRUE;
 2566 }
 2567 
 2568 
 2569 /* convert from local_charset to txt_mime_charsets[mmnwcharset] */
 2570 t_bool
 2571 buffer_to_network(
 2572     char *line,
 2573     int mmnwcharset)
 2574 {
 2575     char *obuf;
 2576     char *outbuf;
 2577     ICONV_CONST char *inbuf;
 2578     iconv_t cd;
 2579     size_t result, osize;
 2580     size_t inbytesleft, outbytesleft;
 2581     t_bool conv_success = TRUE;
 2582 
 2583     if (strcasecmp(txt_mime_charsets[mmnwcharset], tinrc.mm_local_charset)) {
 2584         if ((cd = iconv_open(txt_mime_charsets[mmnwcharset], tinrc.mm_local_charset)) != (iconv_t) (-1)) {
 2585             inbytesleft = strlen(line);
 2586             inbuf = (char *) line;
 2587             outbytesleft = 1 + inbytesleft * 4;
 2588             osize = outbytesleft;
 2589             obuf = my_malloc(osize + 1);
 2590             outbuf = (char *) obuf;
 2591 
 2592             do {
 2593                 errno = 0;
 2594                 result = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
 2595                 if (result == (size_t) (-1)) {
 2596                     switch (errno) {
 2597                         case EILSEQ:
 2598                             /* TODO: only one '?' for each multibyte sequence ? */
 2599                             **&outbuf = '?';
 2600                             outbuf++;
 2601                             inbuf++;
 2602                             inbytesleft--;
 2603                             conv_success = FALSE;
 2604                             break;
 2605 
 2606                         case E2BIG:
 2607                             obuf = my_realloc(obuf, osize * 2);
 2608                             outbuf = (char *) (obuf + osize - outbytesleft);
 2609                             outbytesleft += osize;
 2610                             osize <<= 1; /* double size */
 2611                             break;
 2612 
 2613                         default:    /* EINVAL */
 2614                             inbytesleft = 0;
 2615                             conv_success = FALSE;
 2616                     }
 2617                 }
 2618             } while (inbytesleft > 0);
 2619 
 2620             **&outbuf = '\0';
 2621             strcpy(line, obuf); /* FIXME: here we assume that line is big enough to hold obuf */
 2622             free(obuf);
 2623             iconv_close(cd);
 2624         }
 2625     }
 2626     return conv_success;
 2627 }
 2628 #endif /* CHARSET_CONVERSION */
 2629 
 2630 
 2631 char *
 2632 buffer_to_ascii(
 2633     char *c)
 2634 {
 2635     char *a = c;
 2636 
 2637     while (*c != '\0') {
 2638         /* reduce to US-ASCII, other non-prints are filtered later */
 2639         if ((unsigned char) *c >= 128)
 2640             *c = '?';
 2641         c++;
 2642     }
 2643     return a;
 2644 }
 2645 
 2646 
 2647 /*
 2648  * do some character set processing
 2649  *
 2650  * this is called for headers, overview data, and article bodies
 2651  * to set non-ASCII characters to '?'
 2652  * (only with MIME_STRICT_CHARSET and !NO_LOCALE or CHARSET_CONVERSION
 2653  * and network_charset=="US-ASCII")
 2654  */
 2655 void
 2656 process_charsets(
 2657     char **line,
 2658     size_t *max_line_len,
 2659     const char *network_charset,
 2660     const char *local_charset,
 2661     t_bool conv_tex2iso)
 2662 {
 2663     char *p;
 2664 
 2665 #ifdef CHARSET_CONVERSION
 2666     if (strcasecmp(network_charset, "US-ASCII")) {  /* network_charset is NOT US-ASCII */
 2667         if (iso2asc_supported >= 0)
 2668             p = my_strdup("ISO-8859-1");
 2669         else
 2670             p = my_strdup(local_charset);
 2671         if (!buffer_to_local(line, max_line_len, network_charset, p))
 2672             buffer_to_ascii(*line);
 2673         free(p);
 2674     } else /* set non-ASCII characters to '?' */
 2675         buffer_to_ascii(*line);
 2676 #else
 2677 #   if defined(MIME_STRICT_CHARSET) && !defined(NO_LOCALE)
 2678     if ((local_charset && strcasecmp(network_charset, local_charset)) || !strcasecmp(network_charset, "US-ASCII"))
 2679         /* different charsets || network charset is US-ASCII (see below) */
 2680         buffer_to_ascii(*line);
 2681 #   endif /* MIME_STRICT_CHARSET && !NO_LOCALE */
 2682     /* charset conversion (codepage version) */
 2683 #endif /* CHARSET_CONVERSION */
 2684 
 2685     /*
 2686      * TEX2ISO conversion should be done before ISO2ASC conversion
 2687      * to allow TEX2ISO && ISO2ASC, i.e. "a -> auml -> ae
 2688      */
 2689     if (conv_tex2iso) {
 2690         p = my_strdup(*line);
 2691         convert_tex2iso(p, *line);
 2692         free(p);
 2693     }
 2694 
 2695     /* iso2asc support */
 2696 #ifdef CHARSET_CONVERSION
 2697     if (iso2asc_supported >= 0)
 2698 #else
 2699     if (iso2asc_supported >= 0 && !strcasecmp(network_charset, "ISO-8859-1"))
 2700 #endif /* CHARSET_CONVERSION */
 2701     {
 2702         p = my_strdup(*line);
 2703         convert_iso2asc(p, line, max_line_len, iso2asc_supported);
 2704         free(p);
 2705     }
 2706 }
 2707 
 2708 
 2709 /*
 2710  * checking of mail addresses for GNKSA compliance
 2711  *
 2712  * son of RFC 1036:
 2713  *   article         = 1*header separator body
 2714  *   header          = start-line *continuation
 2715  *   start-line      = header-name ":" space [ nonblank-text ] eol
 2716  *   continuation    = space nonblank-text eol
 2717  *   header-name     = 1*name-character *( "-" 1*name-character )
 2718  *   name-character  = letter / digit
 2719  *   letter          = <ASCII letter A-Z or a-z>
 2720  *   digit           = <ASCII digit 0-9>
 2721  *   separator       = eol
 2722  *   body            = *( [ nonblank-text / space ] eol )
 2723  *   eol             = <EOL>
 2724  *   nonblank-text   = [ space ] text-character *( space-or-text )
 2725  *   text-character  = <any ASCII character except NUL (ASCII 0),
 2726  *                       HT (ASCII 9), LF (ASCII 10), CR (ASCII 13),
 2727  *                       or blank (ASCII 32)>
 2728  *   space           = 1*( <HT (ASCII 9)> / <blank (ASCII 32)> )
 2729  *   space-or-text   = space / text-character
 2730  *   encoded-word  = "=?" charset "?" encoding "?" codes "?="
 2731  *   charset       = 1*tag-char
 2732  *   encoding      = 1*tag-char
 2733  *   tag-char      = <ASCII printable character except !()<>@,;:\"[]/?=>
 2734  *   codes         = 1*code-char
 2735  *   code-char     = <ASCII printable character except ?>
 2736  *   From-content  = address [ space "(" paren-phrase ")" ]
 2737  *                 /  [ plain-phrase space ] "<" address ">"
 2738  *   paren-phrase  = 1*( paren-char / space / encoded-word )
 2739  *   paren-char    = <ASCII printable character except ()<>\>
 2740  *   plain-phrase  = plain-word *( space plain-word )
 2741  *   plain-word    = unquoted-word / quoted-word / encoded-word
 2742  *   unquoted-word = 1*unquoted-char
 2743  *   unquoted-char = <ASCII printable character except !()<>@,;:\".[]>
 2744  *   quoted-word   = quote 1*( quoted-char / space ) quote
 2745  *   quote         = <" (ASCII 34)>
 2746  *   quoted-char   = <ASCII printable character except "()<>\>
 2747  *   address       = local-part "@" domain
 2748  *   local-part    = unquoted-word *( "." unquoted-word )
 2749  *   domain        = unquoted-word *( "." unquoted-word )
 2750 */
 2751 
 2752 
 2753 /*
 2754  * legal domain name components according to RFC 1034
 2755  * includes also '.' as valid separator
 2756  */
 2757 static char gnksa_legal_fqdn_chars[256] = {
 2758 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2759 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2760 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2761 /* 0x20 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0,
 2762 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
 2763 /* 0x40 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2764 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
 2765 /* 0x60 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2766 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
 2767 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2768 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2769 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2770 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2771 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2772 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2773 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2774 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2775 };
 2776 
 2777 
 2778 /*
 2779  * legal localpart components according to son of RFC 1036
 2780  * includes also '.' as valid separator
 2781  */
 2782 static char gnksa_legal_localpart_chars[256] = {
 2783 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2784 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2785 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2786 /* 0x20 */ 0,1,0,1, 1,1,1,1, 0,0,1,1, 0,1,1,1,
 2787 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,1,0,1,
 2788 /* 0x40 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2789 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,1,1,
 2790 /* 0x60 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2791 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0,
 2792 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2793 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2794 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2795 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2796 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2797 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2798 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2799 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2800 };
 2801 
 2802 
 2803 /*
 2804  * legal realname characters according to son of RFC 1036
 2805  *
 2806  * we also allow CR & LF for folding
 2807  */
 2808 static char gnksa_legal_realname_chars[256] = {
 2809 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2810 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,1,0, 0,1,0,0,
 2811 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2812 /* 0x20 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2813 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2814 /* 0x40 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2815 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2816 /* 0x60 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2817 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0,
 2818 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2819 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2820 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2821 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2822 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2823 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2824 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2825 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2826 };
 2827 
 2828 
 2829 /*
 2830  * return error message string for given code
 2831  */
 2832 const char *
 2833 gnksa_strerror(
 2834     int errcode)
 2835 {
 2836     const char *message;
 2837 
 2838     switch (errcode) {
 2839         case GNKSA_INTERNAL_ERROR:
 2840             message = _(txt_error_gnksa_internal);
 2841             break;
 2842 
 2843         case GNKSA_LANGLE_MISSING:
 2844             message = txt_error_gnksa_langle;
 2845             break;
 2846 
 2847         case GNKSA_LPAREN_MISSING:
 2848             message = _(txt_error_gnksa_lparen);
 2849             break;
 2850 
 2851         case GNKSA_RPAREN_MISSING:
 2852             message = _(txt_error_gnksa_rparen);
 2853             break;
 2854 
 2855         case GNKSA_ATSIGN_MISSING:
 2856             message = _(txt_error_gnksa_atsign);
 2857             break;
 2858 
 2859         case GNKSA_SINGLE_DOMAIN:
 2860             message = _(txt_error_gnksa_sgl_domain);
 2861             break;
 2862 
 2863         case GNKSA_INVALID_DOMAIN:
 2864             message = _(txt_error_gnksa_inv_domain);
 2865             break;
 2866 
 2867         case GNKSA_ILLEGAL_DOMAIN:
 2868             message = _(txt_error_gnksa_ill_domain);
 2869             break;
 2870 
 2871         case GNKSA_UNKNOWN_DOMAIN:
 2872             message = _(txt_error_gnksa_unk_domain);
 2873             break;
 2874 
 2875         case GNKSA_INVALID_FQDN_CHAR:
 2876             message = _(txt_error_gnksa_fqdn);
 2877             break;
 2878 
 2879         case GNKSA_ZERO_LENGTH_LABEL:
 2880             message = _(txt_error_gnksa_zero);
 2881             break;
 2882 
 2883         case GNKSA_ILLEGAL_LABEL_LENGTH:
 2884             message = _(txt_error_gnksa_length);
 2885             break;
 2886 
 2887         case GNKSA_ILLEGAL_LABEL_HYPHEN:
 2888             message = _(txt_error_gnksa_hyphen);
 2889             break;
 2890 
 2891         case GNKSA_ILLEGAL_LABEL_BEGNUM:
 2892             message = _(txt_error_gnksa_begnum);
 2893             break;
 2894 
 2895         case GNKSA_BAD_DOMAIN_LITERAL:
 2896             message = _(txt_error_gnksa_bad_lit);
 2897             break;
 2898 
 2899         case GNKSA_LOCAL_DOMAIN_LITERAL:
 2900             message = _(txt_error_gnksa_local_lit);
 2901             break;
 2902 
 2903         case GNKSA_RBRACKET_MISSING:
 2904             message = _(txt_error_gnksa_rbracket);
 2905             break;
 2906 
 2907         case GNKSA_LOCALPART_MISSING:
 2908             message = _(txt_error_gnksa_lp_missing);
 2909             break;
 2910 
 2911         case GNKSA_INVALID_LOCALPART:
 2912             message = _(txt_error_gnksa_lp_invalid);
 2913             break;
 2914 
 2915         case GNKSA_ZERO_LENGTH_LOCAL_WORD:
 2916             message = _(txt_error_gnksa_lp_zero);
 2917             break;
 2918 
 2919         case GNKSA_ILLEGAL_UNQUOTED_CHAR:
 2920             message = _(txt_error_gnksa_rn_unq);
 2921             break;
 2922 
 2923         case GNKSA_ILLEGAL_QUOTED_CHAR:
 2924             message = _(txt_error_gnksa_rn_qtd);
 2925             break;
 2926 
 2927         case GNKSA_ILLEGAL_ENCODED_CHAR:
 2928             message = _(txt_error_gnksa_rn_enc);
 2929             break;
 2930 
 2931         case GNKSA_BAD_ENCODE_SYNTAX:
 2932             message = _(txt_error_gnksa_rn_encsyn);
 2933             break;
 2934 
 2935         case GNKSA_ILLEGAL_PAREN_CHAR:
 2936             message = _(txt_error_gnksa_rn_paren);
 2937             break;
 2938 
 2939         case GNKSA_INVALID_REALNAME:
 2940             message = _(txt_error_gnksa_rn_invalid);
 2941             break;
 2942 
 2943         case GNKSA_OK:
 2944         default:
 2945             /* shouldn't happen */
 2946             message = "";
 2947             break;
 2948     }
 2949 
 2950     return message;
 2951 }
 2952 
 2953 
 2954 /*
 2955  * decode realname into displayable string
 2956  * this only does RFC822 decoding, decoding RFC2047 encoded parts must
 2957  * be done by another call to the appropriate function
 2958  */
 2959 static int
 2960 gnksa_dequote_plainphrase(
 2961     char *realname,
 2962     char *decoded,
 2963     int addrtype)
 2964 {
 2965     char *rpos; /* read position */
 2966     char *wpos; /* write position */
 2967     int initialstate;   /* initial state */
 2968     int state;  /* current state */
 2969 
 2970     if (!*realname)
 2971         return GNKSA_MISSING_REALNAME;
 2972 
 2973     /* initialize state machine */
 2974     switch (addrtype) {
 2975         case GNKSA_ADDRTYPE_ROUTE:
 2976             initialstate = 0;
 2977             break;
 2978 
 2979         case GNKSA_ADDRTYPE_OLDSTYLE:
 2980             initialstate = 5;
 2981             break;
 2982 
 2983         default:
 2984             /* shouldn't happen */
 2985             return GNKSA_INTERNAL_ERROR;
 2986             /* NOTREACHED */
 2987             break;
 2988     }
 2989     state = initialstate;
 2990     rpos = realname;
 2991     wpos = decoded;
 2992 
 2993     /* decode realname */
 2994     while (*rpos) {
 2995         if (!gnksa_legal_realname_chars[(unsigned char) *rpos])
 2996             return GNKSA_INVALID_REALNAME;
 2997 
 2998         switch (state) {
 2999             case 0:
 3000                 /* in unquoted word, route address style */
 3001                 switch (*rpos) {
 3002                     case '"':
 3003                         state = 1;
 3004                         rpos++;
 3005                         break;
 3006 
 3007                     case '!':
 3008                     case '(':
 3009                     case ')':
 3010                     case '<':
 3011                     case '>':
 3012                     case '@':
 3013                     case ',':
 3014                     case ';':
 3015                     case ':':
 3016                     case '\\':
 3017                     case '.':
 3018                     case '[':
 3019                     case ']':
 3020                         return GNKSA_ILLEGAL_UNQUOTED_CHAR;
 3021                         /* NOTREACHED */
 3022                         break;
 3023 
 3024                     case '=':
 3025                         *(wpos++) = *(rpos++);
 3026                         if (*rpos == '?') {
 3027                             state = 2;
 3028                             *(wpos++) = *(rpos++);
 3029                         } else
 3030                             state = 0;
 3031                         break;
 3032 
 3033                     default:
 3034                         state = 0;
 3035                         *(wpos++) = *(rpos++);
 3036                         break;
 3037                 }
 3038                 break;
 3039 
 3040             case 1:
 3041                 /* in quoted word */
 3042                 switch (*rpos) {
 3043                     case '"':
 3044                         state = 0;
 3045                         rpos++;
 3046                         break;
 3047 
 3048 #if 0 /* RFC 5322 3.2.4. does not prohibit these anymore as RFC 1036 did */
 3049                     case '(':
 3050                     case ')':
 3051                     case '<':
 3052                     case '>':
 3053 #endif /* 0 */
 3054                     /*
 3055                      * FIXME: \ is allowed in dquotes as of 5322,
 3056                      * but we need to ensure that \" doesn't end
 3057                      * current state as it currently would.
 3058                      */
 3059                     case '\\':
 3060                         return GNKSA_ILLEGAL_QUOTED_CHAR;
 3061                         /* NOTREACHED */
 3062                         break;
 3063 
 3064                     default:
 3065                         state = 1;
 3066                         *(wpos++) = *(rpos++);
 3067                         break;
 3068                 }
 3069                 break;
 3070 
 3071             case 2:
 3072                 /* in encoded word, charset part */
 3073                 switch (*rpos) {
 3074                     case '?':
 3075                         state = 3;
 3076                         *(wpos++) = *(rpos++);
 3077                         break;
 3078 
 3079                     case '!':
 3080                     case '(':
 3081                     case ')':
 3082                     case '<':
 3083                     case '>':
 3084                     case '@':
 3085                     case ',':
 3086                     case ';':
 3087                     case ':':
 3088                     case '\\':
 3089                     case '"':
 3090                     case '[':
 3091                     case ']':
 3092                     case '/':
 3093                     case '=':
 3094                         return GNKSA_ILLEGAL_ENCODED_CHAR;
 3095                         /* NOTREACHED */
 3096                         break;
 3097 
 3098                     default:
 3099                         state = 2;
 3100                         *(wpos++) = *(rpos++);
 3101                         break;
 3102                 }
 3103                 break;
 3104 
 3105             case 3:
 3106                 /* in encoded word, encoding part */
 3107                 switch (*rpos) {
 3108                     case '?':
 3109                         state = 4;
 3110                         *(wpos++) = *(rpos++);
 3111                         break;
 3112 
 3113                     case '!':
 3114                     case '(':
 3115                     case ')':
 3116                     case '<':
 3117                     case '>':
 3118                     case '@':
 3119                     case ',':
 3120                     case ';':
 3121                     case ':':
 3122                     case '\\':
 3123                     case '"':
 3124                     case '[':
 3125                     case ']':
 3126                     case '/':
 3127                     case '=':
 3128                         return GNKSA_ILLEGAL_ENCODED_CHAR;
 3129                         /* NOTREACHED */
 3130                         break;
 3131 
 3132                     default:
 3133                         state = 3;
 3134                         *(wpos++) = *(rpos++);
 3135                         break;
 3136                 }
 3137                 break;
 3138 
 3139             case 4:
 3140                 /* in encoded word, codes part */
 3141                 switch (*rpos) {
 3142                     case '?':
 3143                         *(wpos++) = *(rpos++);
 3144                         if (*rpos == '=') {
 3145                             state = initialstate;
 3146                             *(wpos++) = *(rpos++);
 3147                         } else
 3148                             return GNKSA_BAD_ENCODE_SYNTAX;
 3149                         break;
 3150 
 3151                     default:
 3152                         state = 4;
 3153                         *(wpos++) = *(rpos++);
 3154                         break;
 3155                 }
 3156                 break;
 3157 
 3158             case 5:
 3159                 /* in word, old style address */
 3160                 switch (*rpos) {
 3161                     case '(':
 3162                     case ')':
 3163                     case '<':
 3164                     case '>':
 3165                     case '\\':
 3166                         return GNKSA_ILLEGAL_PAREN_CHAR;
 3167                         /* NOTREACHED */
 3168                         break;
 3169 
 3170                     case '=':
 3171                         *(wpos++) = *(rpos++);
 3172                         if (*rpos == '?') {
 3173                             state = 2;
 3174                             *(wpos++) = *(rpos++);
 3175                         } else
 3176                             state = 5;
 3177                         break;
 3178 
 3179                     default:
 3180                         state = 5;
 3181                         *(wpos++) = *(rpos++);
 3182                         break;
 3183                 }
 3184                 break;
 3185 
 3186             default:
 3187                 /* shouldn't happen */
 3188                 return GNKSA_INTERNAL_ERROR;
 3189         }
 3190     }
 3191 
 3192     /* successful */
 3193     *wpos = '\0';
 3194     return GNKSA_OK;
 3195 }
 3196 
 3197 
 3198 /*
 3199  * check domain literal
 3200  */
 3201 static int
 3202 gnksa_check_domain_literal(
 3203     char *domain)
 3204 {
 3205     char term;
 3206     int n;
 3207     unsigned int x1, x2, x3, x4;
 3208 
 3209     /* parse domain literal into ip number */
 3210     x1 = x2 = x3 = x4 = 666;
 3211     term = '\0';
 3212 
 3213     if (*domain == '[') { /* literal bracketed */
 3214         n = sscanf(domain, "[%u.%u.%u.%u%c", &x1, &x2, &x3, &x4, &term);
 3215         if (n != 5)
 3216             return GNKSA_BAD_DOMAIN_LITERAL;
 3217 
 3218         if (term != ']')
 3219             return GNKSA_BAD_DOMAIN_LITERAL;
 3220 
 3221     } else { /* literal not bracketed */
 3222 #ifdef REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
 3223         return GNKSA_RBRACKET_MISSING;
 3224 #else
 3225         n = sscanf(domain, "%u.%u.%u.%u%c", &x1, &x2, &x3, &x4, &term);
 3226         /* there should be no terminating character present */
 3227         if (n != 4)
 3228             return GNKSA_BAD_DOMAIN_LITERAL;
 3229 #endif /* REQUIRE_BRACKETS_IN_DOMAIN_LITERAL */
 3230     }
 3231 
 3232     /* check ip parts for legal range */
 3233     if ((x1 > 255) || (x2 > 255) || (x3 > 255) || (x4 > 255))
 3234         return GNKSA_BAD_DOMAIN_LITERAL;
 3235 
 3236     /* check for private ip or localhost - see RFC 5735, RFC 5737 */
 3237     if ((!disable_gnksa_domain_check)
 3238         && ((x1 == 0)               /* local network */
 3239         || (x1 == 10)               /* private class A */
 3240         || ((x1 == 172) && ((x2 & 0xf0) == 16)) /* private /12 */
 3241         || ((x1 == 192) && (x2 == 168))     /* private class B */
 3242         || ((x1 == 192) && (x2 == 0) && (x3 == 2)) /* TEST NET-1 */
 3243         || ((x1 == 198) && (x2 == 51) && (x3 == 100)) /* TEST NET-2 */
 3244         || ((x1 == 203) && (x2 == 0) && (x3 == 113)) /* TEST NET-3 */
 3245         || (x1 == 127)))            /* loopback */
 3246         return GNKSA_LOCAL_DOMAIN_LITERAL;
 3247 
 3248     return GNKSA_OK;
 3249 }
 3250 
 3251 
 3252 static int
 3253 gnksa_check_domain(
 3254     char *domain)
 3255 {
 3256     char *aux;
 3257     char *last;
 3258     int i;
 3259     int result;
 3260 
 3261     /* check for domain literal */
 3262     if (*domain == '[') /* check value of domain literal */
 3263         return gnksa_check_domain_literal(domain);
 3264 
 3265     /* check for leading or trailing dot */
 3266     if ((*domain == '.') || (*(domain + strlen(domain) - 1) == '.'))
 3267         return GNKSA_ZERO_LENGTH_LABEL;
 3268 
 3269     /* look for TLD start */
 3270     if ((aux = strrchr(domain, '.')) == NULL)
 3271         return GNKSA_SINGLE_DOMAIN;
 3272 
 3273     aux++;
 3274 
 3275     /* check existence of toplevel domain */
 3276     switch ((int) strlen(aux)) {
 3277         case 1:
 3278             /* no numeric components allowed */
 3279             if ((*aux >= '0') && (*aux <= '9'))
 3280                 return gnksa_check_domain_literal(domain);
 3281 
 3282             /* single letter TLDs do not exist */
 3283             return GNKSA_ILLEGAL_DOMAIN;
 3284             /* NOTREACHED */
 3285             break;
 3286 
 3287         case 2:
 3288             /* no numeric components allowed */
 3289             if ((*aux >= '0') && (*aux <= '9')
 3290                 && (*(aux + 1) >= '0') && (*(aux + 1) <= '9'))
 3291                 return gnksa_check_domain_literal(domain);
 3292 
 3293             if ((*aux >= 'a') && (*aux <= 'z')
 3294                 && (*(aux + 1) >= 'a') && (*(aux + 1) <= 'z')) {
 3295                 i = ((*aux - 'a') * 26) + (*(aux + 1)) - 'a';
 3296                 if (!gnksa_country_codes[i])
 3297                     return GNKSA_UNKNOWN_DOMAIN;
 3298             } else
 3299                 return GNKSA_ILLEGAL_DOMAIN;
 3300             break;
 3301 
 3302         case 3:
 3303             /* no numeric components allowed */
 3304             if ((*aux >= '0') && (*aux <= '9')
 3305                 && (*(aux + 1) >= '0') && (*(aux + 1) <= '9')
 3306                 && (*(aux + 2) >= '0') && (*(aux + 2) <= '9'))
 3307                 return gnksa_check_domain_literal(domain);
 3308             /* FALLTHROUGH */
 3309         default:
 3310             /* check for valid domains */
 3311             result = GNKSA_INVALID_DOMAIN;
 3312             for (i = 0; *gnksa_domain_list[i]; i++) {
 3313                 if (!strcmp(aux, gnksa_domain_list[i]))
 3314                     result = GNKSA_OK;
 3315             }
 3316             if (disable_gnksa_domain_check)
 3317                 result = GNKSA_OK;
 3318             if (result != GNKSA_OK)
 3319                 return result;
 3320             break;
 3321     }
 3322 
 3323     /* check for illegal labels */
 3324     last = domain;
 3325     for (aux = domain; *aux; aux++) {
 3326         if (*aux == '.') {
 3327             if (aux - last - 1 > 63)
 3328                 return GNKSA_ILLEGAL_LABEL_LENGTH;
 3329 
 3330             if (*(aux + 1) == '.')
 3331                 return GNKSA_ZERO_LENGTH_LABEL;
 3332 
 3333             if ((*(aux + 1) == '-') || (*(aux - 1) == '-'))
 3334                 return GNKSA_ILLEGAL_LABEL_HYPHEN;
 3335 
 3336 #ifdef ENFORCE_RFC1034
 3337             if ((*(aux + 1) >= '0') && (*(aux + 1) <= '9'))
 3338                 return GNKSA_ILLEGAL_LABEL_BEGNUM;
 3339 #endif /* ENFORCE_RFC1034 */
 3340             last = aux;
 3341         }
 3342     }
 3343 
 3344     /* check for illegal characters in FQDN */
 3345     for (aux = domain; *aux; aux++) {
 3346         if (!gnksa_legal_fqdn_chars[(unsigned char) *aux])
 3347             return GNKSA_INVALID_FQDN_CHAR;
 3348     }
 3349 
 3350     return GNKSA_OK;
 3351 }
 3352 
 3353 
 3354 /*
 3355  * check localpart of address
 3356  */
 3357 static int
 3358 gnksa_check_localpart(
 3359     const char *localpart)
 3360 {
 3361     const char *aux;
 3362 
 3363     /* no localpart at all? */
 3364     if (!*localpart)
 3365         return GNKSA_LOCALPART_MISSING;
 3366 
 3367     /* check for zero-length domain parts */
 3368     if ((*localpart == '.') || (*(localpart + strlen(localpart) - 1) == '.'))
 3369         return GNKSA_ZERO_LENGTH_LOCAL_WORD;
 3370 
 3371     for (aux = localpart; *aux; aux++) {
 3372         if ((*aux == '.') && (*(aux + 1) == '.'))
 3373             return GNKSA_ZERO_LENGTH_LOCAL_WORD;
 3374     }
 3375 
 3376     /* check for illegal characters in FQDN */
 3377     for (aux = localpart; *aux; aux++) {
 3378         if (!gnksa_legal_localpart_chars[(unsigned char) *aux])
 3379             return GNKSA_INVALID_LOCALPART;
 3380     }
 3381 
 3382     return GNKSA_OK;
 3383 }
 3384 
 3385 
 3386 /*
 3387  * split mail address into realname and address parts
 3388  */
 3389 int
 3390 gnksa_split_from(
 3391     const char *from,
 3392     char *address,
 3393     char *realname,
 3394     int *addrtype)
 3395 {
 3396     char *addr_begin;
 3397     char *addr_end;
 3398     char work[HEADER_LEN];
 3399 
 3400     /* init return variables */
 3401     *address = *realname = '\0';
 3402 
 3403     /* copy raw address into work area */
 3404     STRCPY(work, from);
 3405     strip_line(work);
 3406     strcpy(address, work);
 3407 
 3408     if (!*work) {
 3409         *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;
 3410         return GNKSA_ATSIGN_MISSING; /* GNKSA_LPAREN_MISSING */
 3411     }
 3412 
 3413     /* skip trailing whitespace */
 3414     addr_end = work + strlen(work) - 1;
 3415 
 3416     if (*addr_end == '>') {
 3417         /* route-address used */
 3418         *addrtype = GNKSA_ADDRTYPE_ROUTE;
 3419 
 3420         /* get address part */
 3421         addr_begin = addr_end;
 3422         while (('<' != *addr_begin) && (addr_begin > work))
 3423             addr_begin--;
 3424 
 3425         if (*addr_begin != '<') /* syntax error in mail address */
 3426             return GNKSA_LANGLE_MISSING;
 3427 
 3428         *addr_end = *addr_begin = '\0';
 3429         /* copy route address */
 3430         strcpy(address, addr_begin + 1);
 3431 
 3432         /* missing realname */
 3433         if (addr_begin == work)
 3434             return GNKSA_MISSING_REALNAME;
 3435 
 3436         /* get realname part */
 3437         addr_end = addr_begin - 1;
 3438         addr_begin = work;
 3439 
 3440         /* strip surrounding whitespace */
 3441         strip_line(addr_end);
 3442         while ((*addr_begin == ' ') || (*addr_begin == '\t'))
 3443             addr_begin++;
 3444 
 3445 #if 0   /* whitespace only realname */
 3446         strip_line(addr_begin);
 3447         if (!strlen(addr_begin))
 3448             return GNKSA_WHITESPACE_REALNAME;
 3449         else
 3450 #endif /* 0 */
 3451         /* copy realname */
 3452         strcpy(realname, addr_begin);
 3453     } else {
 3454         size_t l;
 3455         /* old-style address used */
 3456         *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;
 3457 
 3458         /* get address part */
 3459         /* skip leading whitespace */
 3460         addr_begin = work;
 3461         while ((*addr_begin == ' ') || (*addr_begin == '\t'))
 3462             addr_begin++;
 3463 
 3464         if (*addr_begin == '<') {
 3465             *addrtype = GNKSA_ADDRTYPE_ROUTE;
 3466             return GNKSA_RANGLE_MISSING;
 3467         }
 3468 
 3469         /* scan forward to next whitespace or null */
 3470         l = strlen(addr_begin);
 3471         addr_end = addr_begin;
 3472 
 3473         while ((*addr_end != ' ') && (*addr_end != '\t') && (*addr_end))
 3474             addr_end++;
 3475 
 3476         *addr_end = '\0';
 3477 
 3478         if (l == strlen(addr_begin))
 3479             return GNKSA_MISSING_REALNAME;
 3480 
 3481         /* copy route address */
 3482         strcpy(address, addr_begin);
 3483 
 3484         /* get realname part */
 3485         addr_begin = addr_end + 1;
 3486         addr_end = addr_begin + strlen(addr_begin) - 1;
 3487 
 3488         /* strip surrounding whitespace */
 3489         strip_line(addr_end);
 3490         while ((*addr_begin == ' ') || (*addr_begin == '\t'))
 3491             addr_begin++;
 3492 
 3493         /* any realname at all? */
 3494         if (*addr_begin) {
 3495             /* check for parentheses */
 3496             if (*addr_begin != '(')
 3497                 return GNKSA_LPAREN_MISSING;
 3498 
 3499             if (*addr_end != ')')
 3500                 return GNKSA_RPAREN_MISSING;
 3501 
 3502             /* copy realname */
 3503             *addr_end = '\0';
 3504             strcpy(realname, addr_begin + 1);
 3505         }
 3506     }
 3507 
 3508     /*
 3509      * if we allow <> as From: we must disallow <> as Message-ID,
 3510      * see code in post.c:check_article_to_be_posted()
 3511      */
 3512 #if 0
 3513     if (!strchr(address, '@') && *address) /* check for From: without an @ but allow <> */
 3514 #else
 3515     if (!strchr(address, '@')) /* check for From: without an @ */
 3516 #endif /* 0 */
 3517         return GNKSA_ATSIGN_MISSING;
 3518 
 3519     /* split successful */
 3520     return GNKSA_OK;
 3521 }
 3522 
 3523 
 3524 /*
 3525  * restrictive check for valid address conforming to RFC 1036, son of RFC 1036
 3526  * and draft-usefor-article-xx.txt
 3527  */
 3528 int
 3529 gnksa_do_check_from(
 3530     const char *from,
 3531     char *address,
 3532     char *realname)
 3533 {
 3534     char *addr_begin;
 3535     char decoded[HEADER_LEN];
 3536     int result, code, addrtype;
 3537 
 3538     decoded[0] = '\0';
 3539 
 3540 #ifdef DEBUG
 3541     if (debug & DEBUG_MISC)
 3542         debug_print_file("GNKSA", "From:=[%s]", from);
 3543 #endif /* DEBUG */
 3544 
 3545     /* split from */
 3546     code = gnksa_split_from(from, address, realname, &addrtype);
 3547     if (*address == '\0') /* address missing or not extractable */
 3548         return code;
 3549 
 3550 #ifdef DEBUG
 3551     if (debug & DEBUG_MISC)
 3552         debug_print_file("GNKSA", "address=[%s]", address);
 3553 #endif /* DEBUG */
 3554 
 3555     /* parse address */
 3556     addr_begin = strrchr(address, '@');
 3557 
 3558     if (addr_begin != NULL) {
 3559         /* temporarily terminate string at separator position */
 3560         *addr_begin++ = '\0';
 3561 
 3562 #ifdef DEBUG
 3563         if (debug & DEBUG_MISC)
 3564             debug_print_file("GNKSA", "FQDN=[%s]", addr_begin);
 3565 #endif /* DEBUG */
 3566 
 3567         /* convert FQDN part to lowercase */
 3568         str_lwr(addr_begin);
 3569 
 3570 #ifdef DEBUG
 3571         if (debug & DEBUG_MISC)
 3572             debug_print_file("GNKSA", "str_lwr(FQDN)=[%s]", addr_begin);
 3573 #endif /* DEBUG */
 3574 
 3575         if ((result = gnksa_check_domain(addr_begin)) != GNKSA_OK
 3576             && (code == GNKSA_OK)) /* error detected */
 3577             code = result;
 3578 
 3579         if ((result = gnksa_check_localpart(address)) != GNKSA_OK
 3580             && (code == GNKSA_OK)) /* error detected */
 3581             code = result;
 3582 
 3583         /* restore separator character */
 3584         *--addr_begin = '@';
 3585     }
 3586 
 3587 #ifdef DEBUG
 3588     if (debug & DEBUG_MISC)
 3589         debug_print_file("GNKSA", "realname=[%s]", realname);
 3590 #endif /* DEBUG */
 3591 
 3592     /* check realname */
 3593     if ((result = gnksa_dequote_plainphrase(realname, decoded, addrtype)) != GNKSA_OK) {
 3594         if (code == GNKSA_OK) /* error detected */
 3595             code = result;
 3596     } else  /* copy dequoted realname to result variable */
 3597         strcpy(realname, decoded);
 3598 
 3599 #ifdef DEBUG
 3600     if (debug & DEBUG_MISC) { /* TODO: dump to a file instead of wait_message() */
 3601         if (code != GNKSA_OK)
 3602             debug_print_file("GNKSA", "From:=[%s], GNKSA=[%d]", from, code);
 3603         else
 3604             debug_print_file("GNKSA", "GNKSA=[%d]", code);
 3605     }
 3606 #endif /* DEBUG */
 3607 
 3608     return code;
 3609 }
 3610 
 3611 
 3612 /*
 3613  * check given address
 3614  */
 3615 int
 3616 gnksa_check_from(
 3617     char *from)
 3618 {
 3619     char address[HEADER_LEN];   /* will be initialised in gnksa_split_from() */
 3620     char realname[HEADER_LEN];  /* which is called by gnksa_do_check_from() */
 3621 
 3622     return gnksa_do_check_from(from, address, realname);
 3623 }
 3624 
 3625 
 3626 /*
 3627  * parse given address
 3628  * return error code on GNKSA check failure
 3629  */
 3630 int
 3631 parse_from(
 3632     const char *from,
 3633     char *address,
 3634     char *realname)
 3635 {
 3636     return gnksa_do_check_from(from, address, realname);
 3637 }
 3638 
 3639 
 3640 /*
 3641  * Strip trailing blanks, tabs, \r and \n
 3642  */
 3643 char *
 3644 strip_line(
 3645     char *line)
 3646 {
 3647     char *ptr;
 3648 
 3649     if (!*line)
 3650         return line;
 3651 
 3652     ptr = line + strlen(line);
 3653     do {
 3654         ptr--;
 3655         if (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')
 3656             *ptr = '\0';
 3657         else
 3658             break;
 3659     } while (ptr > line);
 3660 
 3661     return line;
 3662 }
 3663 
 3664 
 3665 #if defined(CHARSET_CONVERSION) || (defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE))
 3666 /*
 3667  * 'check' a given UTF-8 string and '?'-out illegal sequences
 3668  * TODO: is this check complete?
 3669  *
 3670  * UTF-8           = ASCII / UTF-8-non-ascii
 3671  * ASCII           = %x00-%x7F
 3672  * UTF-8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4
 3673  * UTF8-1          = %x80-BF
 3674  * UTF8-2          = %xC2-DF 1*UTF8-1
 3675  * UTF8-3          = %xE0 %xA0-BF 1*UTF8-1 / %xE1-EC 2*UTF8-1 /
 3676  *                   %xED %x80-9F 1*UTF8-1 / %xEE-EF 2*UTF8-1
 3677  * UTF8-4          = %xF0 %x90-BF 2*UTF8-1 / %xF1-F3 3*UTF8-1 /
 3678  *                   %xF4 %x80-8F 2*UTF8-1
 3679  */
 3680 char *
 3681 utf8_valid(
 3682     char *line)
 3683 {
 3684     char *c;
 3685     unsigned char d, e, f, g;
 3686     int numc;
 3687     t_bool illegal;
 3688 
 3689     c = line;
 3690 
 3691     while (*c != '\0') {
 3692         if (!(*c & 0x80)) { /* plain US-ASCII? */
 3693             c++;
 3694             continue;
 3695         }
 3696 
 3697         numc = 0;
 3698         illegal = FALSE;
 3699         d = (*c & 0x7c);    /* remove bits 7,1,0 */
 3700 
 3701         do {
 3702             numc++;
 3703         } while ((d <<= 1) & 0x80); /* get sequence length */
 3704 
 3705         if (c + numc > line + strlen(line)) { /* sequence runs past end of string */
 3706             illegal = TRUE;
 3707             numc = (int) (line + strlen(line) - c);
 3708         }
 3709 
 3710         if (!illegal) {
 3711             d = (unsigned char)*c;
 3712             e = (unsigned char) *(c + 1);
 3713 
 3714             switch (numc) {
 3715                 case 2:
 3716                     /* out of range or sequences which would also fit into 1 byte */
 3717                     if (d < 0xc2 || d > 0xdf)
 3718                         illegal = TRUE;
 3719                     break;
 3720 
 3721                 case 3:
 3722                     f = (unsigned char) *(c + 2);
 3723                     /* out of range or sequences which would also fit into 2 bytes */
 3724                     if (d < 0xe0 || d > 0xef || (d == 0xe0 && e < 0xa0))
 3725                         illegal = TRUE;
 3726                     /* U+D800 ... U+DBFF, U+DC00 ... U+DFFF (high-surrogates, low-surrogates) */
 3727                     if (d == 0xed && e > 0x9f)
 3728                         illegal = TRUE;
 3729                     /* U+FDD0 ... U+FDEF */
 3730                     if (d == 0xef && e == 0xb7 && (f >= 0x90 && f <= 0xaf))
 3731                         illegal = TRUE;
 3732                     /* U+FFFE, U+FFFF (noncharacters) */
 3733                     if (d == 0xef && e == 0xbf && (f == 0xbe || f == 0xbf))
 3734                         illegal = TRUE;
 3735                     break;
 3736 
 3737                 case 4:
 3738                     f = (unsigned char) *(c + 2);
 3739                     g = (unsigned char) *(c + 3);
 3740                     /* out of range or sequences which would also fit into 3 bytes */
 3741                     if (d < 0xf0 || d > 0xf7 || (d == 0xf0 && e < 0x90))
 3742                         illegal = TRUE;
 3743                     /* largest current used sequence */
 3744                     if ((d == 0xf4 && e > 0x8f) || d > 0xf4)
 3745                         illegal = TRUE;
 3746                     /* Unicode 3.1 noncharacters */
 3747                     /* U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF; (Unicode 3.1) */
 3748                     if (d == 0xf0 && (e == 0x9f || e == 0xaf || e == 0xbf) && f == 0xbf && (g == 0xbe || g == 0xbf))
 3749                         illegal = TRUE;
 3750                     /* Unicode 3.1 noncharacters */
 3751                     /* U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE, U+7FFFF */
 3752                     /* U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF */
 3753                     /* U+CFFFE, U+CFFFF, U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF */
 3754                     if ((d == 0xf1 || d == 0xf2 || d == 0xf3) && (e == 0x8f || e == 0x9f || e == 0xaf || e == 0xbf) && f == 0xbf && (g == 0xbe || g == 0xbf))
 3755                         illegal = TRUE;
 3756                     /* Unicode 3.1 noncharacters */
 3757                     /* U+10FFFE, U+10FFFF */
 3758                     if (d == 0xf4 && e == 0x8f && f == 0xbf && (g == 0xbe || g == 0xbf))
 3759                         illegal = TRUE;
 3760                     break;
 3761 
 3762 #   if 0    /* currently not used, see also check above */
 3763                 case 5:
 3764                     /* out of range or sequences which would also fit into 4 bytes */
 3765                     if (d < 0xf8 || d > 0xfb || (d == 0xf8 && e < 0x88))
 3766                         illegal = TRUE;
 3767                     break;
 3768 
 3769                 case 6:
 3770                     /* out of range or sequences which would also fit into 5 bytes */
 3771                     if (d < 0xfc || d > 0xfd || (d == 0xfc && e < 0x84))
 3772                         illegal = TRUE;
 3773                     break;
 3774 #   endif /* 0 */
 3775 
 3776                 default:
 3777                     /*
 3778                      * with the check for plain US-ASCII above all other sequence
 3779                      * length are illegal.
 3780                      */
 3781                     illegal = TRUE;
 3782                     break;
 3783             }
 3784         }
 3785 
 3786         for (d = 1; d < numc && !illegal; d++) {
 3787             e = (unsigned char) *(c + d);
 3788             if (e < 0x80 || e > 0xbf || e == '\0' || e == '\n')
 3789                 illegal = TRUE;
 3790         }
 3791 
 3792         if (!illegal)
 3793             c += numc; /* skip over valid sequence */
 3794         else {
 3795             while (numc--) {
 3796                 if (*c == '\0' || *c == '\n')
 3797                     break;
 3798                 if (*c & 0x80)  /* replace 'dangerous' bytes */
 3799                     *c = '?';
 3800                 c++;
 3801             }
 3802         }
 3803     }
 3804     return line;
 3805 }
 3806 #endif /* CHARSET_CONVERSION || (MULTIBYTE_ABLE && !NO_LOCALE) */
 3807 
 3808 
 3809 char *
 3810 idna_decode(
 3811     char *in)
 3812 {
 3813     char *out = my_strdup(in);
 3814 
 3815     /* decoding needed? */
 3816     if (!strstr(in, "xn--"))
 3817         return out;
 3818 
 3819 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 3820     /* IDNA 2008 */
 3821 #   if defined(HAVE_LIBIDNKIT) && defined(HAVE_IDN_DECODENAME)
 3822     {
 3823         idn_result_t res;
 3824         char *q, *r;
 3825 
 3826         if ((q = strrchr(out, '@'))) {
 3827             q++;
 3828             if ((r = strrchr(in, '@')))
 3829                 r++;
 3830             else { /* just to make static analyzer happy */
 3831                 r = in;
 3832                 q = out;
 3833             }
 3834         } else {
 3835             r = in;
 3836             q = out;
 3837         }
 3838         if ((res = idn_decodename(IDN_DECODE_LOOKUP, r, q, out + strlen(out) - q + 1)) == idn_success)
 3839             return out;
 3840         else { /* IDNA 2008 failed, try again with IDNA 2003 if available */
 3841             free(out);
 3842             out = my_strdup(in);
 3843         }
 3844 #       ifdef DEBUG
 3845         if (debug & DEBUG_MISC)
 3846             wait_message(2, "idn_decodename(%s): %s", r, idn_result_tostring(res));
 3847 #       endif /* DEBUG */
 3848     }
 3849 #   endif /* HAVE_LIBIDNKIT && HAVE_IDN_DECODENAME */
 3850 
 3851     /* IDNA 2003 */
 3852 #   ifdef HAVE_LIBICUUC
 3853     {
 3854         UChar *src;
 3855         UChar dest[1024];
 3856         UErrorCode err = U_ZERO_ERROR;
 3857         char *s;
 3858 #       ifdef HAVE_LIBICUUC_46_API
 3859         UIDNA *uts46;
 3860         UIDNAInfo info = UIDNA_INFO_INITIALIZER;
 3861 #       endif /* HAVE_LIBICUUC_46_API */
 3862 
 3863         if ((s = strrchr(out, '@')))
 3864             s++;
 3865         else
 3866             s = out;
 3867 
 3868         src = char2UChar(s);
 3869 #       ifndef HAVE_LIBICUUC_46_API
 3870         uidna_IDNToUnicode(src, -1, dest, 1023, UIDNA_USE_STD3_RULES, NULL, &err);
 3871 #       else
 3872         uts46 = uidna_openUTS46(UIDNA_USE_STD3_RULES, &err);
 3873         uidna_nameToUnicode(uts46, src, u_strlen(src), dest, 1023, &info, &err);
 3874         uidna_close(uts46);
 3875 #       endif /* !HAVE_LIBICUUC_46_API */
 3876         free(src);
 3877         if (!(U_FAILURE(err))) {
 3878             char *t;
 3879 
 3880             *s = '\0'; /* cut off domainpart */
 3881             if ((s = UChar2char(dest)) != NULL) { /* convert domainpart */
 3882                 t = my_malloc(strlen(out) + strlen(s) + 1);
 3883                 sprintf(t, "%s%s", out, s);
 3884                 free(s);
 3885                 free(out);
 3886                 out = t;
 3887             }
 3888         }
 3889     }
 3890 #   else
 3891 #       if defined(HAVE_LIBIDN) && defined(HAVE_IDNA_TO_UNICODE_LZLZ)
 3892     if (stringprep_check_version("0.3.0")) {
 3893         char *t, *s = NULL;
 3894         int rs;
 3895 
 3896         if ((t = strrchr(out, '@')))
 3897             t++;
 3898         else
 3899             t = out;
 3900 
 3901 #           ifdef HAVE_IDNA_USE_STD3_ASCII_RULES
 3902         if ((rs = idna_to_unicode_lzlz(t, &s, IDNA_USE_STD3_ASCII_RULES)) == IDNA_SUCCESS)
 3903 #           else
 3904         if ((rs = idna_to_unicode_lzlz(t, &s, 0)) == IDNA_SUCCESS)
 3905 #           endif /* HAVE_IDNA_USE_STD3_ASCII_RULES */
 3906             strcpy(t, s);
 3907 #           ifdef DEBUG
 3908         else {
 3909             if (debug & DEBUG_MISC)
 3910 #               ifdef HAVE_IDNA_STRERROR
 3911                 wait_message(2, "idna_to_unicode_lzlz(%s): %s", t, idna_strerror(rs));
 3912 #               else
 3913                 wait_message(2, "idna_to_unicode_lzlz(%s): %d", t, rs);
 3914 #               endif /* HAVE_IDNA_STRERROR */
 3915         }
 3916 #           endif /* DEBUG */
 3917         FreeIfNeeded(s);
 3918     }
 3919 #       endif /* HAVE_LIBIDN && HAVE_IDNA_TO_UNICODE_LZLZ */
 3920 #   endif /* HAVE_LIBICUUC */
 3921 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 3922     return out;
 3923 }
 3924 
 3925 
 3926 int
 3927 tin_version_info(
 3928     FILE *fp)
 3929 {
 3930     int wlines = 0; /* written lines */
 3931 
 3932 #if defined(__DATE__) && defined(__TIME__)
 3933     fprintf(fp, _("Version: %s %s release %s (\"%s\") %s %s\n"),
 3934         PRODUCT, VERSION, RELEASEDATE, RELEASENAME, __DATE__, __TIME__);
 3935 #else
 3936     fprintf(fp, _("Version: %s %s release %s (\"%s\")\n"),
 3937         PRODUCT, VERSION, RELEASEDATE, RELEASENAME);
 3938 #endif /* __DATE__ && __TIME__ */
 3939     wlines++;
 3940 
 3941 #ifdef SYSTEM_NAME
 3942     fprintf(fp, "Platform:\n");
 3943     fprintf(fp, "\tOS-Name  = \"%s\"\n", SYSTEM_NAME);
 3944     wlines += 2;
 3945 #endif /* SYSTEM_NAME */
 3946 
 3947 #ifdef TIN_CC
 3948     fprintf(fp, "Compiler:\n");
 3949     fprintf(fp, "\tCC       = \"%s\"\n", TIN_CC);
 3950     wlines += 2;
 3951 #   ifdef TIN_CFLAGS
 3952         fprintf(fp, "\tCFLAGS   = \"%s\"\n", TIN_CFLAGS);
 3953         wlines++;
 3954 #   endif /* TIN_CFLAGS */
 3955 #   ifdef TIN_CPP
 3956         fprintf(fp, "\tCPP      = \"%s\"\n", TIN_CPP);
 3957         wlines++;
 3958 #   endif /* TIN_CPP */
 3959 #   ifdef TIN_CPPFLAGS
 3960         fprintf(fp, "\tCPPFLAGS = \"%s\"\n", TIN_CPPFLAGS);
 3961         wlines++;
 3962 #   endif /* TIN_CPPFLAGS */
 3963 #endif /* TIN_CC */
 3964 
 3965 #ifdef TIN_LD
 3966     fprintf(fp, "Linker and Libraries:\n");
 3967     fprintf(fp, "\tLD       = \"%s\"\n", TIN_LD);
 3968     wlines += 2;
 3969 #   ifdef TIN_LDFLAGS
 3970         fprintf(fp, "\tLDFLAGS  = \"%s\"\n", TIN_LDFLAGS);
 3971         wlines++;
 3972 #   endif /* TIN_LDFLAGS */
 3973 #   ifdef TIN_LIBS
 3974         fprintf(fp, "\tLIBS     = \"%s\"\n", TIN_LIBS);
 3975         wlines++;
 3976 #   endif /* TIN_LIBS */
 3977 #endif /* TIN_LD */
 3978     fprintf(fp, "\tPCRE     = \"%s\"\n", pcre_version());
 3979     wlines++;
 3980 
 3981     fprintf(fp, "Characteristics:\n\t"
 3982 /* TODO: complete list and do some useful grouping; show only in -vV case? */
 3983 #ifdef DEBUG
 3984             "+DEBUG "
 3985 #else
 3986             "-DEBUG "
 3987 #endif /* DEBUG */
 3988 #ifdef NNTP_ONLY
 3989             "+NNTP_ONLY "
 3990 #else
 3991 #   ifdef NNTP_ABLE
 3992             "+NNTP_ABLE "
 3993 #   else
 3994             "-NNTP_ABLE "
 3995 #   endif /* NNTP_ABLE */
 3996 #endif /* NNTP_ONLY */
 3997 #ifdef NO_POSTING
 3998             "+NO_POSTING "
 3999 #else
 4000             "-NO_POSTING "
 4001 #endif /* NO_POSTING */
 4002 #ifdef BROKEN_LISTGROUP
 4003             "+BROKEN_LISTGROUP "
 4004 #else
 4005             "-BROKEN_LISTGROUP "
 4006 #endif /* BROKEN_LISTGROUP */
 4007 #ifdef XHDR_XREF
 4008             "+XHDR_XREF"
 4009 #else
 4010             "-XHDR_XREF"
 4011 #endif /* XHDR_XREF */
 4012             "\n\t"
 4013 #ifdef HAVE_FASCIST_NEWSADMIN
 4014             "+HAVE_FASCIST_NEWSADMIN "
 4015 #else
 4016             "-HAVE_FASCIST_NEWSADMIN "
 4017 #endif /* HAVE_FASCIST_NEWSADMIN */
 4018 #ifdef ENABLE_IPV6
 4019             "+ENABLE_IPV6 "
 4020 #else
 4021             "-ENABLE_IPV6 "
 4022 #endif /* ENABLE_IPV6 */
 4023 #ifdef HAVE_COREFILE
 4024             "+HAVE_COREFILE"
 4025 #else
 4026             "-HAVE_COREFILE"
 4027 #endif /* HAVE_COREFILE */
 4028             "\n\t"
 4029 #ifdef NO_SHELL_ESCAPE
 4030             "+NO_SHELL_ESCAPE "
 4031 #else
 4032             "-NO_SHELL_ESCAPE "
 4033 #endif /* NO_SHELL_ESCAPE */
 4034 #ifdef DISABLE_PRINTING
 4035             "+DISABLE_PRINTING "
 4036 #else
 4037             "-DISABLE_PRINTING "
 4038 #endif /* DISABLE_PRINTING */
 4039 #ifdef DONT_HAVE_PIPING
 4040             "+DONT_HAVE_PIPING "
 4041 #else
 4042             "-DONT_HAVE_PIPING "
 4043 #endif /* DONT_HAVE_PIPING */
 4044 #ifdef NO_ETIQUETTE
 4045             "+NO_ETIQUETTE"
 4046 #else
 4047             "-NO_ETIQUETTE"
 4048 #endif /* NO_ETIQUETTE */
 4049             "\n\t"
 4050 #ifdef HAVE_LONG_FILE_NAMES
 4051             "+HAVE_LONG_FILE_NAMES "
 4052 #else
 4053             "-HAVE_LONG_FILE_NAMES "
 4054 #endif /* HAVE_LONG_FILE_NAMES */
 4055 #ifdef APPEND_PID
 4056             "+APPEND_PID "
 4057 #else
 4058             "-APPEND_PID "
 4059 #endif /* APPEND_PID */
 4060 #ifdef HAVE_MH_MAIL_HANDLING
 4061             "+HAVE_MH_MAIL_HANDLING"
 4062 #else
 4063             "-HAVE_MH_MAIL_HANDLING"
 4064 #endif /* HAVE_MH_MAIL_HANDLING */
 4065             "\n\t"
 4066 #ifdef HAVE_ISPELL
 4067             "+HAVE_ISPELL "
 4068 #else
 4069             "-HAVE_ISPELL "
 4070 #endif /* HAVE_ISPELL */
 4071 #ifdef HAVE_METAMAIL
 4072             "+HAVE_METAMAIL "
 4073 #else
 4074             "-HAVE_METAMAIL "
 4075 #endif /* HAVE_METAMAIL */
 4076 #ifdef HAVE_SUM
 4077             "+HAVE_SUM"
 4078 #else
 4079             "-HAVE_SUM"
 4080 #endif /* HAVE_SUM */
 4081             "\n\t"
 4082 #ifdef HAVE_COLOR
 4083             "+HAVE_COLOR "
 4084 #else
 4085             "-HAVE_COLOR "
 4086 #endif /* HAVE_COLOR */
 4087 #ifdef HAVE_PGP
 4088             "+HAVE_PGP "
 4089 #else
 4090             "-HAVE_PGP "
 4091 #endif /* HAVE_PGP */
 4092 #ifdef HAVE_PGPK
 4093             "+HAVE_PGPK "
 4094 #else
 4095             "-HAVE_PGPK "
 4096 #endif /* HAVE_PGPK */
 4097 #ifdef HAVE_GPG
 4098             "+HAVE_GPG"
 4099 #else
 4100             "-HAVE_GPG"
 4101 #endif /* HAVE_GPG */
 4102             "\n\t"
 4103 #ifdef MIME_BREAK_LONG_LINES
 4104             "+MIME_BREAK_LONG_LINES "
 4105 #else
 4106             "-MIME_BREAK_LONG_LINES "
 4107 #endif /* MIME_BREAK_LONG_LINES */
 4108 #ifdef MIME_STRICT_CHARSET
 4109             "+MIME_STRICT_CHARSET "
 4110 #else
 4111             "-MIME_STRICT_CHARSET "
 4112 #endif /* MIME_STRICT_CHARSET */
 4113 #ifdef CHARSET_CONVERSION
 4114             "+CHARSET_CONVERSION"
 4115 #else
 4116             "-CHARSET_CONVERSION"
 4117 #endif /* CHARSET_CONVERSION */
 4118             "\n\t"
 4119 #ifdef MULTIBYTE_ABLE
 4120             "+MULTIBYTE_ABLE "
 4121 #else
 4122             "-MULTIBYTE_ABLE "
 4123 #endif /* MULTIBYTE_ABLE */
 4124 #ifdef NO_LOCALE
 4125             "+NO_LOCALE "
 4126 #else
 4127             "-NO_LOCALE "
 4128 #endif /* NO_LOCALE */
 4129 #ifdef USE_LONG_ARTICLE_NUMBERS
 4130             "+USE_LONG_ARTICLE_NUMBERS"
 4131 #else
 4132             "-USE_LONG_ARTICLE_NUMBERS"
 4133 #endif /* USE_LONG_ARTICLE_NUMBERS */
 4134             "\n\t"
 4135 #ifdef USE_CANLOCK
 4136             "+USE_CANLOCK "
 4137 #else
 4138             "-USE_CANLOCK "
 4139 #endif /* USE_CANLOCK */
 4140 #ifdef EVIL_INSIDE
 4141             "+EVIL_INSIDE "
 4142 #else
 4143             "-EVIL_INSIDE "
 4144 #endif /* EVIL_INSIDE */
 4145 #ifdef FORGERY
 4146             "+FORGERY "
 4147 #else
 4148             "-FORGERY "
 4149 #endif /* FORGERY */
 4150 #ifdef TINC_DNS
 4151             "+TINC_DNS "
 4152 #else
 4153             "-TINC_DNS "
 4154 #endif /* TINC_DNS */
 4155 #ifdef ENFORCE_RFC1034
 4156             "+ENFORCE_RFC1034"
 4157 #else
 4158             "-ENFORCE_RFC1034"
 4159 #endif /* ENFORCE_RFC1034 */
 4160             "\n\t"
 4161 #ifdef REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
 4162             "+REQUIRE_BRACKETS_IN_DOMAIN_LITERAL "
 4163 #else
 4164             "-REQUIRE_BRACKETS_IN_DOMAIN_LITERAL "
 4165 #endif /* REQUIRE_BRACKETS_IN_DOMAIN_LITERAL */
 4166 #ifdef ALLOW_FWS_IN_NEWSGROUPLIST
 4167             "+ALLOW_FWS_IN_NEWSGROUPLIST"
 4168 #else
 4169             "-ALLOW_FWS_IN_NEWSGROUPLIST"
 4170 #endif /* ALLOW_FWS_IN_NEWSGROUPLIST */
 4171             "\n");
 4172     wlines += 11;
 4173     fflush(fp);
 4174     return wlines;
 4175 }
 4176 
 4177 
 4178 void
 4179 draw_mark_selected(
 4180     int i)
 4181 {
 4182 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 4183     int j;
 4184 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 4185 
 4186     MoveCursor(INDEX2LNUM(i), mark_offset);
 4187 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 4188     for (j = art_mark_width - wcwidth(tinrc.art_marked_selected); j > 0; --j)
 4189         my_fputc(' ', stdout);
 4190     StartInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
 4191     my_fputwc(tinrc.art_marked_selected, stdout);
 4192 #else
 4193     StartInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
 4194     my_fputc(tinrc.art_marked_selected, stdout);
 4195 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 4196     EndInverse();   /* ToggleInverse() doesn't work correct with ncurses4.x */
 4197 }
 4198 
 4199 
 4200 int
 4201 tin_gettime(
 4202     struct t_tintime *tt)
 4203 {
 4204 #ifdef HAVE_CLOCK_GETTIME
 4205     static struct timespec cgt;
 4206 #endif /* HAVE_CLOCK_GETTIME */
 4207 #ifdef HAVE_GETTIMEOFDAY
 4208     static struct timeval gt;
 4209 #endif /* HAVE_GETTIMEOFDAY */
 4210 
 4211 #ifdef HAVE_CLOCK_GETTIME
 4212     if (clock_gettime(CLOCK_REALTIME, &cgt) == 0) {
 4213         tt->tv_sec = cgt.tv_sec;
 4214         tt->tv_nsec = cgt.tv_nsec;
 4215         return 0;
 4216     }
 4217 #endif /* HAVE_CLOCK_GETTIME */
 4218 #ifdef HAVE_GETTIMEOFDAY
 4219     if (gettimeofday(&gt, NULL) == 0) {
 4220         tt->tv_sec = gt.tv_sec;
 4221         tt->tv_nsec = gt.tv_usec * 1000;
 4222         return 0;
 4223     }
 4224 #endif /* HAVE_GETTIMEOFDAY */
 4225     tt->tv_sec = 0;
 4226     tt->tv_nsec = 0;
 4227     return -1;
 4228 }
 4229 
 4230 
 4231 #if 0
 4232 /*
 4233  * Stat a mail/news article to see if it still exists
 4234  */
 4235 static t_bool
 4236 stat_article(
 4237     t_artnum art,
 4238     const char *group_path)
 4239 {
 4240     struct t_group currgrp;
 4241 
 4242     currgrp = CURR_GROUP;
 4243 
 4244 #   ifdef NNTP_ABLE
 4245     if (read_news_via_nntp && currgrp.type == GROUP_TYPE_NEWS) {
 4246         char buf[NNTP_STRLEN];
 4247 
 4248         snprintf(buf, sizeof(buf), "STAT %"T_ARTNUM_PFMT, art);
 4249         return (nntp_command(buf, OK_NOTEXT, NULL, 0) != NULL);
 4250     } else
 4251 #   endif /* NNTP_ABLE */
 4252     {
 4253         char filename[PATH_LEN];
 4254         struct stat sb;
 4255 
 4256         joinpath(filename, sizeof(filename), currgrp.spooldir, group_path);
 4257         snprintf(&filename[strlen(filename)], sizeof(filename), "/%"T_ARTNUM_PFMT, art);
 4258 
 4259         return (stat(filename, &sb) != -1);
 4260     }
 4261 }
 4262 #endif /* 0 */