"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/misc.c" (1 Dec 2020, 96968 Bytes) of package /linux/misc/tin-2.4.5.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.4.4_vs_2.4.5.

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