"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/misc.c" (24 Nov 2018, 95767 Bytes) of package /linux/misc/tin-2.4.3.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.2_vs_2.4.3.

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