"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/misc.c" (9 Dec 2022, 102795 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


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

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