"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : misc.c
    4  *  Author    : I. Lea & R. Skrenta
    5  *  Created   : 1991-04-01
    6  *  Updated   : 2016-10-10
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1991-2017 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 awfull
  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 = 0;
  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     snprintf(buf, sizeof(buf), "mkdir %s", path); /* redirect stderr to /dev/null? use invoke_cmd()? */
  716     if (stat(path, &sb) == -1) {
  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 != 0 && 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 ridd of hardcoded 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         fgets(buf, (int) sizeof(buf), fp);
 1143         fclose(fp);
 1144         error_message(2, "\n%s: Already started pid=[%d] on %s", tin_progname, atoi(buf), 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 = s + maxsize;
 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     for (; *format && s < endp - 1; format++) {
 1196         tbuf[0] = '\0';
 1197 
 1198         if (*format != '\\' && *format != '%') {
 1199             *s++ = *format;
 1200             continue;
 1201         }
 1202 
 1203         if (*format == '\\') {
 1204             switch (*++format) {
 1205                 case '\0':
 1206                     *s++ = '\\';
 1207                     goto out;
 1208                     /* NOTREACHED */
 1209                     break;
 1210 
 1211                 case 'n':   /* linefeed */
 1212                     strcpy(tbuf, "\n");
 1213                     break;
 1214 
 1215                 case 't':   /* tab */
 1216                     strcpy(tbuf, "\t");
 1217                     break;
 1218 
 1219                 default:
 1220                     tbuf[0] = '%';
 1221                     tbuf[1] = *format;
 1222                     tbuf[2] = '\0';
 1223                     break;
 1224             }
 1225             i = strlen(tbuf);
 1226             if (i) {
 1227                 if (s + i < endp - 1) {
 1228                     strcpy(s, tbuf);
 1229                     s += i;
 1230                 } else
 1231                     return 0;
 1232             }
 1233         }
 1234         if (*format == '%') {
 1235             switch (*++format) {
 1236 
 1237                 case '\0':
 1238                     *s++ = '%';
 1239                     goto out;
 1240                     /* NOTREACHED */
 1241                     break;
 1242 
 1243                 case '%':
 1244                     *s++ = '%';
 1245                     continue;
 1246 
 1247                 case 'A':   /* Articles Email address */
 1248                     STRCPY(tbuf, arts[respnum].from);
 1249                     break;
 1250 
 1251                 case 'C':   /* First Name of author */
 1252                     if (arts[respnum].name != NULL) {
 1253                         STRCPY(tbuf, arts[respnum].name);
 1254                         if (strchr(tbuf, ' '))
 1255                             *(strchr(tbuf, ' ')) = '\0';
 1256                     } else {
 1257                         STRCPY(tbuf, arts[respnum].from);
 1258                     }
 1259                     break;
 1260 
 1261                 case 'D':   /* Articles Date (reformatted as specified in attributes->date_format) */
 1262                     if (!my_strftime(tbuf, LEN - 1, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
 1263                         STRCPY(tbuf, BlankIfNull(pgart.hdr.date));
 1264                     }
 1265                     break;
 1266 
 1267                 case 'F':   /* Articles Address+Name */
 1268                     if (arts[respnum].name)
 1269                         snprintf(tbuf, sizeof(tbuf), "%s <%s>", arts[respnum].name, arts[respnum].from);
 1270                     else {
 1271                         STRCPY(tbuf, arts[respnum].from);
 1272                     }
 1273                     break;
 1274 
 1275                 case 'G':   /* Groupname of Article */
 1276                     STRCPY(tbuf, group);
 1277                     break;
 1278 
 1279                 case 'I':   /* Initials of author */
 1280                     STRCPY(tbuf, ((arts[respnum].name != NULL) ? arts[respnum].name : arts[respnum].from));
 1281                     j = 0;
 1282                     iflag = TRUE;
 1283                     for (i = 0; tbuf[i]; i++) {
 1284                         if (iflag && tbuf[i] != ' ') {
 1285                             tbuf[j++] = tbuf[i];
 1286                             iflag = FALSE;
 1287                         }
 1288                         if (strchr(" ._@", tbuf[i]))
 1289                             iflag = TRUE;
 1290                     }
 1291                     tbuf[j] = '\0';
 1292                     break;
 1293 
 1294                 case 'M':   /* Articles Message-ID */
 1295                     STRCPY(tbuf, BlankIfNull(pgart.hdr.messageid));
 1296                     break;
 1297 
 1298                 case 'N':   /* Articles Name of author */
 1299                     STRCPY(tbuf, ((arts[respnum].name != NULL) ? arts[respnum].name : arts[respnum].from));
 1300                     break;
 1301 
 1302                 default:
 1303                     tbuf[0] = '%';
 1304                     tbuf[1] = *format;
 1305                     tbuf[2] = '\0';
 1306                     break;
 1307             }
 1308             i = strlen(tbuf);
 1309             if (i) {
 1310                 if (s + i < endp - 1) {
 1311                     strcpy(s, tbuf);
 1312                     s += i;
 1313                 } else
 1314                     return 0;
 1315             }
 1316         }
 1317     }
 1318 out:
 1319     if (s < endp && *format == '\0') {
 1320         *s = '\0';
 1321         return (s - start);
 1322     } else
 1323         return 0;
 1324 }
 1325 
 1326 
 1327 /*
 1328  * strfeditor() - produce formatted editor string
 1329  *   %E  Editor
 1330  *   %F  Filename
 1331  *   %N  Linenumber
 1332  */
 1333 static int
 1334 strfeditor(
 1335     char *editor,
 1336     int linenum,
 1337     const char *filename,
 1338     char *s,
 1339     size_t maxsize,
 1340     char *format)
 1341 {
 1342     char *endp = s + maxsize;
 1343     char *start = s;
 1344     char tbuf[PATH_LEN];
 1345     int i;
 1346 
 1347     if (s == NULL || format == NULL || maxsize == 0)
 1348         return 0;
 1349 
 1350     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 1351         return 0;
 1352 
 1353     for (; *format && s < endp - 1; format++) {
 1354         tbuf[0] = '\0';
 1355 
 1356         if (*format != '\\' && *format != '%') {
 1357             *s++ = *format;
 1358             continue;
 1359         }
 1360 
 1361         if (*format == '\\') {
 1362             switch (*++format) {
 1363                 case '\0':
 1364                     *s++ = '\\';
 1365                     goto out;
 1366                     /* NOTREACHED */
 1367                     break;
 1368 
 1369                 case 'n':   /* linefeed */
 1370                     strcpy(tbuf, "\n");
 1371                     break;
 1372 
 1373                 default:
 1374                     tbuf[0] = '%';
 1375                     tbuf[1] = *format;
 1376                     tbuf[2] = '\0';
 1377                     break;
 1378             }
 1379             i = strlen(tbuf);
 1380             if (i) {
 1381                 if (s + i < endp - 1) {
 1382                     strcpy(s, tbuf);
 1383                     s += i;
 1384                 } else
 1385                     return 0;
 1386             }
 1387         }
 1388         if (*format == '%') {
 1389             switch (*++format) {
 1390                 case '\0':
 1391                     *s++ = '%';
 1392                     goto out;
 1393                     /* NOTREACHED */
 1394                     break;
 1395 
 1396                 case '%':
 1397                     *s++ = '%';
 1398                     continue;
 1399 
 1400                 case 'E':   /* Editor */
 1401                     STRCPY(tbuf, editor);
 1402                     break;
 1403 
 1404                 case 'F':   /* Filename */
 1405                     STRCPY(tbuf, filename);
 1406                     break;
 1407 
 1408                 case 'N':   /* Line number */
 1409                     sprintf(tbuf, "%d", linenum);
 1410                     break;
 1411 
 1412                 default:
 1413                     tbuf[0] = '%';
 1414                     tbuf[1] = *format;
 1415                     tbuf[2] = '\0';
 1416                     break;
 1417             }
 1418             i = strlen(tbuf);
 1419             if (i) {
 1420                 if (s + i < endp - 1) {
 1421                     strcpy(s, tbuf);
 1422                     s += i;
 1423                 } else
 1424                     return 0;
 1425             }
 1426         }
 1427     }
 1428 out:
 1429     if (s < endp && *format == '\0') {
 1430         *s = '\0';
 1431         return (s - start);
 1432     } else
 1433         return 0;
 1434 }
 1435 
 1436 
 1437 /*
 1438  * Helper function for strfpath() to copy expanded strings
 1439  * into the output buffer. Return new output buffer or NULL
 1440  * if we overflowed it.
 1441  */
 1442 static char *
 1443 strfpath_cp(
 1444     char *str,
 1445     char *tbuf,
 1446     char *endp)
 1447 {
 1448     size_t i;
 1449 
 1450     if ((i = strlen(tbuf))) {
 1451         if (str + i < endp - 1) {
 1452             strcpy(str, tbuf);
 1453             str += i;
 1454         } else {
 1455             str[0] = '\0';
 1456             return NULL;
 1457         }
 1458     }
 1459     return str;
 1460 }
 1461 
 1462 
 1463 /*
 1464  * strfpath - produce formatted pathname expansion. Handles following forms:
 1465  *   ~/News    -> $HOME/News
 1466  *   ~abc/News -> /usr/abc/News
 1467  *   $var/News -> /env/var/News
 1468  *   =file     -> $HOME/Mail/file
 1469  *   =         -> $HOME/Mail/group.name
 1470  *   +file     -> savedir/group.name/file
 1471  *
 1472  * Interestingly, %G is not documented as such and apparently unused
 1473  *   ~/News/%G -> $HOME/News/group.name
 1474  *
 1475  * Inputs:
 1476  *   format     The string to be converted
 1477  *   str        Return buffer
 1478  *   maxsize    Size of str
 1479  *   group      ptr to current group
 1480  *   expand_all true if '+' and '=' should be expanded
 1481  * Returns:
 1482  *   0          on error
 1483  *   1          if generated pathname is a mailbox
 1484  *   2          success
 1485  *
 1486  * TODO: add %X (Article number), %M (Message-ID)?
 1487  */
 1488 static int
 1489 _strfpath(
 1490     const char *format,
 1491     char *str,
 1492     size_t maxsize,
 1493     struct t_group *group,
 1494     t_bool expand_all)
 1495 {
 1496     char *endp = str + maxsize;
 1497     const char *startp = format;
 1498     char defbuf[PATH_LEN];
 1499     char tbuf[PATH_LEN];
 1500     char *envptr;
 1501     int i;
 1502     struct passwd *pwd;
 1503     t_bool is_mailbox = FALSE;
 1504 
 1505     if (str == NULL || format == NULL || maxsize == 0)
 1506         return 0;
 1507 
 1508     if (strlen(format) + 1 >= maxsize)
 1509         return 0;
 1510 
 1511     for (; *format && str < endp - 1; format++) {
 1512         tbuf[0] = '\0';
 1513 
 1514         /*
 1515          * If just a normal part of the pathname copy it
 1516          */
 1517         if (!strchr("~$=+%", *format)) {
 1518             *str++ = *format;
 1519             continue;
 1520         }
 1521 
 1522         switch (*format) {
 1523             case '~':           /* Users or another users homedir */
 1524                 switch (*++format) {
 1525                     case '/':   /* users homedir */
 1526                         joinpath(tbuf, sizeof(tbuf), homedir, "");
 1527                         break;
 1528 
 1529                     default:    /* some other users homedir */
 1530                         i = 0;
 1531                         while (*format && *format != '/')
 1532                             tbuf[i++] = *format++;
 1533                         tbuf[i] = '\0';
 1534                         /*
 1535                          * OK lookup the username in /etc/passwd
 1536                          */
 1537                         if ((pwd = getpwnam(tbuf)) == NULL) {
 1538                             str[0] = '\0';
 1539                             return 0;
 1540                         } else
 1541                             sprintf(tbuf, "%s/", pwd->pw_dir);
 1542                         break;
 1543                 }
 1544                 if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1545                     return 0;
 1546                 break;
 1547 
 1548             case '$':   /* Read the envvar and use its value */
 1549                 i = 0;
 1550                 format++;
 1551                 if (*format && *format == '{') {
 1552                     format++;
 1553                     while (*format && !(strchr("}-", *format)))
 1554                         tbuf[i++] = *format++;
 1555                     tbuf[i] = '\0';
 1556                     i = 0;
 1557                     if (*format && *format == '-') {
 1558                         format++;
 1559                         while (*format && *format != '}')
 1560                             defbuf[i++] = *format++;
 1561                     }
 1562                     defbuf[i] = '\0';
 1563                 } else {
 1564                     while (*format && *format != '/')
 1565                         tbuf[i++] = *format++;
 1566                     tbuf[i] = '\0';
 1567                     format--;
 1568                     defbuf[0] = '\0';
 1569                 }
 1570                 /*
 1571                  * OK lookup the variable in the shells environment
 1572                  */
 1573                 envptr = getenv(tbuf);
 1574                 if (envptr == NULL || (*envptr == '\0'))
 1575                     strncpy(tbuf, defbuf, sizeof(tbuf) - 1);
 1576                 else
 1577                     strncpy(tbuf, envptr, sizeof(tbuf) - 1);
 1578                 if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1579                     return 0;
 1580                 else if (*tbuf == '\0') {
 1581                     str[0] = '\0';
 1582                     return 0;
 1583                 }
 1584                 break;
 1585 
 1586             case '=':
 1587                 /*
 1588                  * Mailbox name expansion
 1589                  * Only expand if 1st char in format
 1590                  * =dir expands to maildir/dir
 1591                  * =    expands to maildir/groupname
 1592                  */
 1593                 if (startp == format && group != NULL && expand_all) {
 1594                     char buf[PATH_LEN];
 1595 
 1596                     is_mailbox = TRUE;
 1597                     if (strfpath((cmdline.args & CMDLINE_MAILDIR) ? cmdline.maildir : group->attribute->maildir, buf, sizeof(buf), group, FALSE)) {
 1598                         if (*(format + 1) == '\0')              /* Just an = */
 1599                             joinpath(tbuf, sizeof(tbuf), buf, group->name);
 1600                         else
 1601                             joinpath(tbuf, sizeof(tbuf), buf, "");
 1602                         if ((str = strfpath_cp(str, tbuf, endp)) == NULL)
 1603                             return 0;
 1604                     } else {
 1605                         str[0] = '\0';
 1606                         return 0;
 1607                     }
 1608                 } else                  /* Wasn't the first char in format */
 1609                     *str++ = *format;
 1610                 break;
 1611 
 1612             case '+':
 1613                 /*
 1614                  * Group name expansion
 1615                  * Only convert if 1st char in format
 1616                  * +file expands to savedir/group.name/file
 1617                  */
 1618 
 1619                 if (startp == format && group != NULL && expand_all) {
 1620                     char buf[PATH_LEN];
 1621 
 1622                     /*
 1623                      * Start with the savedir name
 1624                      */
 1625                     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : group->attribute->savedir, buf, sizeof(buf), group, FALSE)) {
 1626                         char tmp[PATH_LEN];
 1627 #ifdef HAVE_LONG_FILE_NAMES
 1628                         my_strncpy(tmp, group->name, sizeof(tmp) - 1);
 1629 #else
 1630                         my_strncpy(tmp, group->name, 14);
 1631 #endif /* HAVE_LONG_FILE_NAMES */
 1632                         joinpath(tbuf, sizeof(tbuf), buf, tmp); /* Add the group name */
 1633                         joinpath(tmp, sizeof(tmp), tbuf, "");
 1634                         if ((str = strfpath_cp(str, tmp, endp)) == NULL)
 1635                             return 0;
 1636                     } else {
 1637                         str[0] = '\0';
 1638                         return 0;
 1639                     }
 1640                 } else                  /* Wasn't the first char in format */
 1641                     *str++ = *format;
 1642                 break;
 1643 
 1644             case '%':   /* Different forms of parsing cmds */
 1645                 format++;
 1646                 if (*format && *format == 'G' && group != NULL) {
 1647                     memset(tbuf, 0, sizeof(tbuf));
 1648                     STRCPY(tbuf, group->name);
 1649                     i = strlen(tbuf);
 1650                     if (((str + i) < (endp - 1)) && (i > 0)) {
 1651                         strcpy(str, tbuf);
 1652                         str += i;
 1653                         break;
 1654                     } else {
 1655                         str[0] = '\0';
 1656                         return 0;
 1657                     }
 1658                 } else
 1659                     *str++ = *format;
 1660                 /* FALLTHROUGH */
 1661             default:
 1662                 break;
 1663         }
 1664     }
 1665 
 1666     if (str < endp && *format == '\0') {
 1667         *str = '\0';
 1668         if (is_mailbox)
 1669             return 1;
 1670         else
 1671             return 2;
 1672     } else {
 1673         str[0] = '\0';
 1674         return 0;
 1675     }
 1676 }
 1677 
 1678 
 1679 /*
 1680  * The real entry point, exists only to expand leading '$'
 1681  */
 1682 int
 1683 strfpath(
 1684     const char *format,
 1685     char *str,
 1686     size_t maxsize,
 1687     struct t_group *group,
 1688     t_bool expand_all)
 1689 {
 1690     /*
 1691      * Expand any leading env vars first in case they themselves contain
 1692      * formatting chars
 1693      */
 1694     if (format[0] == '$') {
 1695         char buf[PATH_LEN];
 1696 
 1697         if (_strfpath(format, buf, sizeof(buf), group, expand_all))
 1698             return (_strfpath(buf, str, maxsize, group, expand_all));
 1699     }
 1700 
 1701     return (_strfpath(format, str, maxsize, group, expand_all));
 1702 }
 1703 
 1704 
 1705 /*
 1706  * TODO: Properly explain this
 1707  */
 1708 char *
 1709 escape_shell_meta(
 1710     const char *source,
 1711     int quote_area)
 1712 {
 1713     static char buf[PATH_LEN];
 1714     char *dest = buf;
 1715     int space = sizeof(buf) - 2;
 1716 
 1717     switch (quote_area) {
 1718         case no_quote:
 1719             while (*source && (space > 0)) {
 1720                 if (*source == '\'' || *source == '\\' || *source == '"' ||
 1721                     *source == '$' || *source == '`' || *source == '*' ||
 1722                     *source == '&' || *source == '|' || *source == '<' ||
 1723                     *source == '>' || *source == ';' || *source == '(' ||
 1724                     *source == ')') {
 1725                     *dest++ = '\\';
 1726                     space--;
 1727                 }
 1728                 *dest++ = *source++;
 1729                 space--;
 1730             }
 1731             break;
 1732 
 1733         case dbl_quote:
 1734             while (*source && (space > 0)) {
 1735                 if (*source == '\\' || *source == '"' || *source == '$' ||
 1736                     *source == '`') {
 1737                     *dest++ = '\\';
 1738                     space--;
 1739                 }
 1740                 *dest++ = *source++;
 1741                 space--;
 1742             }
 1743             break;
 1744 
 1745         case sgl_quote:
 1746             while (*source && (space > 4)) {
 1747                 if (*source == '\'') {
 1748                     *dest++ = '\'';
 1749                     *dest++ = '\\';
 1750                     *dest++ = '\'';
 1751                     space -= 3;
 1752                 }
 1753                 *dest++ = *source++;
 1754                 space--;
 1755             }
 1756             break;
 1757 
 1758         default:
 1759             break;
 1760     }
 1761 
 1762     *dest = '\0';
 1763     return buf;
 1764 }
 1765 
 1766 
 1767 /*
 1768  * strfmailer() - produce formatted mailer string
 1769  *   %M  Mailer
 1770  *   %F  Filename
 1771  *   %T  To
 1772  *   %S  Subject
 1773  *   %U  User
 1774  * Returns length of produced string (is always ignored currently).
 1775  */
 1776 int
 1777 strfmailer(
 1778     const char *mail_prog,
 1779     char *subject,  /* FIXME: should be const char */
 1780     char *to, /* FIXME: should be const char */
 1781     const char *filename,
 1782     char *dest,
 1783     size_t maxsize,
 1784     const char *format)
 1785 {
 1786     char *endp = dest + maxsize;
 1787     char *start = dest;
 1788     char tbuf[PATH_LEN];
 1789     int quote_area = no_quote;
 1790 
 1791     /*
 1792      * safe guards: no destination to write to, no format, no space to
 1793      * write, or nothing to replace and format string longer than available
 1794      * space => return without any action
 1795      */
 1796     if (dest == NULL || format == NULL || maxsize == 0)
 1797         return 0;
 1798 
 1799     /*
 1800      * TODO: shouldn't we better check for no % OR format > maxsize?
 1801      *       as no replacemnt doesn't make sense (hardcoded To, Subject
 1802      *       and filename) and the resulting string usuly is longer after
 1803      *       replacemnts were done (nobody uses enough %% to make the
 1804      *       result shorter than the input).
 1805      */
 1806     if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
 1807         return 0;
 1808 
 1809     /*
 1810      * walk through format until end of format or end of available space
 1811      * and replace place holders
 1812      */
 1813     for (; *format && dest < endp - 1; format++) {
 1814         tbuf[0] = '\0';
 1815 
 1816         /*
 1817          * take over any character other than '\' and '%' and continue with
 1818          * next character in format; remember quote area
 1819          */
 1820         if (*format != '\\' && *format != '%') {
 1821             if (*format == '"' && quote_area != sgl_quote)
 1822                 quote_area = (quote_area == dbl_quote ? no_quote : dbl_quote);
 1823             if (*format == '\'' && quote_area != dbl_quote)
 1824                 quote_area = (quote_area == sgl_quote ? no_quote : sgl_quote);
 1825             *dest++ = *format;
 1826             continue;
 1827         }
 1828 
 1829         /*
 1830          * handle sequences introduced by '\':
 1831          * - "\n" gets line feed
 1832          * - '\' followed by NULL gets '\' and leaves loop
 1833          * - '\' followed by any other character is copied literally and
 1834          *   shell escaped; if that exceeds the available space, return 0
 1835          */
 1836         if (*format == '\\') {
 1837             switch (*++format) {
 1838                 case '\0':
 1839                     *dest++ = '\\';
 1840                     goto out;
 1841                     /* NOTREACHED */
 1842                     break;
 1843 
 1844                 case 'n':   /* linefeed */
 1845                     strcpy(tbuf, "\n");
 1846                     break;
 1847 
 1848                 default:
 1849                     tbuf[0] = '\\';
 1850                     tbuf[1] = *format;
 1851                     tbuf[2] = '\0';
 1852                     break;
 1853             }
 1854             if (*tbuf) {
 1855                 if (sh_format(dest, endp - dest, "%s", tbuf) >= 0)
 1856                     dest += strlen(dest);
 1857                 else
 1858                     return 0;
 1859             }
 1860         }
 1861 
 1862         /*
 1863          * handle sequences introduced by '%'
 1864          * - '%' followed by NULL gets '%' and leaves loop
 1865          * - '%%' gets '%'
 1866          * - '%F' expands to filename
 1867          * - '%M' expands to mailer program
 1868          * - '%S' expands to subject of message
 1869          * - '%T' expands to recipient(s) of message
 1870          * - '%U' expands to userid
 1871          * - '%' followed by any other character is copied literally
 1872          */
 1873         if (*format == '%') {
 1874             char *p;
 1875             t_bool ismail = TRUE;
 1876             t_bool escaped = FALSE;
 1877             switch (*++format) {
 1878                 case '\0':
 1879                     *dest++ = '%';
 1880                     goto out;
 1881 
 1882                 case '%':
 1883                     *dest++ = '%';
 1884                     continue;
 1885 
 1886                 case 'F':   /* Filename */
 1887                     STRCPY(tbuf, filename);
 1888                     break;
 1889 
 1890                 case 'M':   /* Mailer */
 1891                     STRCPY(tbuf, mail_prog);
 1892                     break;
 1893 
 1894                 case 'S':   /* Subject */
 1895                     /* don't MIME encode Subject if using external mail client */
 1896                     if (INTERACTIVE_NONE != tinrc.interactive_mailer)
 1897                         strncpy(tbuf, escape_shell_meta(subject, quote_area), sizeof(tbuf) - 1);
 1898                     else {
 1899 #ifdef CHARSET_CONVERSION
 1900                         p = rfc1522_encode(subject, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1901 #else
 1902                         p = rfc1522_encode(subject, tinrc.mm_charset, ismail);
 1903 #endif /* CHARSET_CONVERSION */
 1904                         strncpy(tbuf, escape_shell_meta(p, quote_area), sizeof(tbuf) - 1);
 1905                         free(p);
 1906                     }
 1907                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1908                     escaped = TRUE;
 1909                     break;
 1910 
 1911                 case 'T':   /* To */
 1912                     /* don't MIME encode To if using external mail client */
 1913                     if (INTERACTIVE_NONE != tinrc.interactive_mailer)
 1914                         strncpy(tbuf, escape_shell_meta(to, quote_area), sizeof(tbuf) - 1);
 1915                     else {
 1916 #ifdef CHARSET_CONVERSION
 1917                         p = rfc1522_encode(to, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1918 #else
 1919                         p = rfc1522_encode(to, tinrc.mm_charset, ismail);
 1920 #endif /* CHARSET_CONVERSION */
 1921                         strncpy(tbuf, escape_shell_meta(p, quote_area), sizeof(tbuf) - 1);
 1922                         free(p);
 1923                     }
 1924                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1925                     escaped = TRUE;
 1926                     break;
 1927 
 1928                 case 'U':   /* User */
 1929                     /* don't MIME encode User if using external mail client */
 1930                     if (INTERACTIVE_NONE != tinrc.interactive_mailer)
 1931                         strncpy(tbuf, userid, sizeof(tbuf) - 1);
 1932                     else {
 1933 #ifdef CHARSET_CONVERSION
 1934                         p = rfc1522_encode(userid, txt_mime_charsets[tinrc.mm_network_charset], ismail);
 1935 #else
 1936                         p = rfc1522_encode(userid, tinrc.mm_charset, ismail);
 1937 #endif /* CHARSET_CONVERSION */
 1938                         strncpy(tbuf, p, sizeof(tbuf) - 1);
 1939                         free(p);
 1940                     }
 1941                     tbuf[sizeof(tbuf) - 1] = '\0';  /* just in case */
 1942                     break;
 1943 
 1944                 default:
 1945                     tbuf[0] = '%';
 1946                     tbuf[1] = *format;
 1947                     tbuf[2] = '\0';
 1948                     break;
 1949             }
 1950             if (*tbuf) {
 1951                 if (escaped) {
 1952                     if (endp - dest > 0) {
 1953                         strncpy(dest, tbuf, endp - dest);
 1954                         dest += strlen(dest);
 1955                     }
 1956                 } else if (sh_format(dest, endp - dest, "%s", tbuf) >= 0) {
 1957                     dest += strlen(dest);
 1958                 } else
 1959                     return 0;
 1960             }
 1961         }
 1962     }
 1963 out:
 1964     if (dest < endp && *format == '\0') {
 1965         *dest = '\0';
 1966         return (dest - start);
 1967     } else
 1968         return 0;
 1969 }
 1970 
 1971 
 1972 /*
 1973  * get_initials() - get initial letters of a posters name
 1974  */
 1975 int
 1976 get_initials(
 1977     struct t_article *art,
 1978     char *s,
 1979     int maxsize) /* return value is always ignored */
 1980 {
 1981     char tbuf[PATH_LEN];
 1982     int i, j = 0;
 1983     t_bool iflag = FALSE;
 1984 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1985     wchar_t *wtmp, *wbuf;
 1986 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 1987 
 1988     if (s == NULL || maxsize <= 0)
 1989         return 0;
 1990 
 1991     STRCPY(tbuf, ((art->name != NULL) ? art->name : art->from));
 1992 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 1993     if ((wtmp = char2wchar_t(tbuf)) != NULL) {
 1994         wbuf = my_malloc(sizeof(wchar_t) * (maxsize + 1));
 1995         for (i = 0; wtmp[i] && j < maxsize; i++) {
 1996             if (iswalpha((wint_t) wtmp[i])) {
 1997                 if (!iflag) {
 1998                     wbuf[j++] = wtmp[i];
 1999                     iflag = TRUE;
 2000                 }
 2001             } else
 2002                 iflag = FALSE;
 2003         }
 2004         wbuf[j] = (wchar_t) '\0';
 2005         s[0] = '\0';
 2006         if (wcstombs(tbuf, wbuf, sizeof(tbuf) - 1) != (size_t) -1)
 2007             strcat(s, tbuf);
 2008         free(wtmp);
 2009         free(wbuf);
 2010     }
 2011 #else
 2012     for (i = 0; tbuf[i] && j < maxsize; i++) {
 2013         if (isalpha((int)(unsigned char) tbuf[i])) {
 2014             if (!iflag) {
 2015                 s[j++] = tbuf[i];
 2016                 iflag = TRUE;
 2017             }
 2018         } else
 2019             iflag = FALSE;
 2020     }
 2021     s[j] = '\0';
 2022 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 2023     return 0;
 2024 }
 2025 
 2026 
 2027 void
 2028 get_cwd(
 2029     char *buf)
 2030 {
 2031 #ifdef HAVE_GETCWD
 2032     getcwd(buf, PATH_LEN);
 2033 #else
 2034 #   ifdef HAVE_GETWD
 2035     getwd(buf);
 2036 #   else
 2037     *buf = '\0';
 2038 #   endif /* HAVE_GETWD */
 2039 #endif /* HAVE_GETCWD */
 2040 }
 2041 
 2042 
 2043 /*
 2044  * Convert a newsgroup name to a newsspool path
 2045  * No effect when reading via NNTP
 2046  */
 2047 void
 2048 make_group_path(
 2049     const char *name,
 2050     char *path)
 2051 {
 2052     while (*name) {
 2053         *path = ((*name == '.') ? '/' : *name);
 2054         name++;
 2055         path++;
 2056     }
 2057     *path++ = '/';
 2058     *path = '\0';
 2059 }
 2060 
 2061 
 2062 /*
 2063  * Given a base pathname & a newsgroup name build an absolute pathname.
 2064  * base_dir = /usr/spool/news
 2065  * group_name = alt.sources
 2066  * group_path = /usr/spool/news/alt/sources
 2067  */
 2068 void
 2069 make_base_group_path(
 2070     const char *base_dir,
 2071     const char *group_name,
 2072     char *group_path,
 2073     size_t group_path_len)
 2074 {
 2075     char buf[LEN];
 2076 
 2077     make_group_path(group_name, buf);
 2078     joinpath(group_path, group_path_len, base_dir, buf);
 2079 }
 2080 
 2081 
 2082 /*
 2083  * Delete index lock
 2084  */
 2085 void
 2086 cleanup_tmp_files(
 2087     void)
 2088 {
 2089     /*
 2090      * only required if update_index == TRUE, but update_index is
 2091      * unknown here
 2092      */
 2093     if (batch_mode)
 2094         unlink(lock_file);
 2095 }
 2096 
 2097 
 2098 #ifndef M_UNIX
 2099 void
 2100 make_post_process_cmd(
 2101     char *cmd,
 2102     char *dir,
 2103     char *file)
 2104 {
 2105     char buf[LEN];
 2106     char currentdir[PATH_LEN];
 2107 
 2108     get_cwd(currentdir);
 2109     chdir(dir);
 2110     sh_format(buf, sizeof(buf), cmd, file);
 2111     invoke_cmd(buf);
 2112     chdir(currentdir);
 2113 }
 2114 #endif /* !M_UNIX */
 2115 
 2116 
 2117 /*
 2118  * returns filesize in bytes
 2119  * -1 in case of an error (file not found, or !S_IFREG)
 2120  */
 2121 long /* we use long here as off_t might be unsigned on some systems */
 2122 file_size(
 2123     const char *file)
 2124 {
 2125     struct stat statbuf;
 2126 
 2127     return (stat(file, &statbuf) == -1 ? -1L : (S_ISREG(statbuf.st_mode)) ? (long) statbuf.st_size : -1L);
 2128 }
 2129 
 2130 
 2131 /*
 2132  * returns mtime
 2133  * -1 in case of an error (file not found, or !S_IFREG)
 2134  */
 2135 long /* we use long (not time_t) here */
 2136 file_mtime(
 2137     const char *file)
 2138 {
 2139     struct stat statbuf;
 2140 
 2141     return (stat(file, &statbuf) == -1 ? -1L : (S_ISREG(statbuf.st_mode)) ? (long) statbuf.st_mtime : -1L);
 2142 }
 2143 
 2144 
 2145 /*
 2146  * TODO: this seems to be unix specific, avoid !M_UNIX calls or
 2147  *       add code for other OS
 2148  *       this feature also isn't documented anywhere
 2149  */
 2150 char *
 2151 random_organization(
 2152     char *in_org)
 2153 {
 2154     FILE *orgfp;
 2155     int nool = 0, sol;
 2156     static char selorg[512];
 2157 
 2158     *selorg = '\0';
 2159 
 2160     if (*in_org != '/') /* M_UNIXism?! */
 2161         return in_org;
 2162 
 2163     srand((unsigned int) time(NULL));
 2164 
 2165     if ((orgfp = fopen(in_org, "r")) == NULL)
 2166         return selorg;
 2167 
 2168     /* count lines */
 2169     while (fgets(selorg, (int) sizeof(selorg), orgfp))
 2170         nool++;
 2171 
 2172     if (!nool) {
 2173         fclose(orgfp);
 2174         return selorg;
 2175     }
 2176 
 2177     rewind(orgfp);
 2178     sol = rand() % nool + 1;
 2179     nool = 0;
 2180     while ((nool != sol) && (fgets(selorg, (int) sizeof(selorg), orgfp)))
 2181         nool++;
 2182 
 2183     fclose(orgfp);
 2184 
 2185     return selorg;
 2186 }
 2187 
 2188 
 2189 void
 2190 read_input_history_file(
 2191     void)
 2192 {
 2193     FILE *fp;
 2194     char *chr;
 2195     char buf[HEADER_LEN];
 2196     int his_w = 0, his_e = 0, his_free = 0;
 2197 
 2198     /* this is usually .tin/.inputhistory */
 2199     if ((fp = fopen(local_input_history_file, "r")) == NULL)
 2200         return;
 2201 
 2202     if (!batch_mode)
 2203         wait_message(0, _(txt_reading_input_history_file));
 2204 
 2205     /* to be safe ;-) */
 2206     memset((void *) input_history, 0, sizeof(input_history));
 2207     memset((void *) hist_last, 0, sizeof(hist_last));
 2208     memset((void *) hist_pos, 0, sizeof(hist_pos));
 2209 
 2210     while (fgets(buf, (int) sizeof(buf), fp)) {
 2211         if ((chr = strpbrk(buf, "\n\r")) != NULL)
 2212             *chr = '\0';
 2213 
 2214         if (*buf)
 2215             input_history[his_w][his_e] = my_strdup(buf);
 2216         else {
 2217             /* empty lines in tin_getline's history buf are stored as NULL pointers */
 2218             input_history[his_w][his_e] = NULL;
 2219 
 2220             /* get the empty slot in the circular buf */
 2221             if (!his_free)
 2222                 his_free = his_e;
 2223         }
 2224 
 2225         his_e++;
 2226         /* check if next type is reached */
 2227         if (his_e >= HIST_SIZE) {
 2228             hist_pos[his_w] = hist_last[his_w] = his_free;
 2229             his_free = his_e = 0;
 2230             his_w++;
 2231         }
 2232         /* check if end is reached */
 2233         if (his_w > HIST_MAXNUM)
 2234             break;
 2235     }
 2236     fclose(fp);
 2237 
 2238     if (cmd_line)
 2239         printf("\r\n");
 2240 }
 2241 
 2242 
 2243 static void
 2244 write_input_history_file(
 2245     void)
 2246 {
 2247     FILE *fp;
 2248     char *chr;
 2249     char *file_tmp;
 2250     int his_w, his_e;
 2251     mode_t mask;
 2252 
 2253     if (no_write)
 2254         return;
 2255 
 2256     mask = umask((mode_t) (S_IRWXO|S_IRWXG));
 2257 
 2258     /* generate tmp-filename */
 2259     file_tmp = get_tmpfilename(local_input_history_file);
 2260 
 2261     if ((fp = fopen(file_tmp, "w")) == NULL) {
 2262         error_message(2, _(txt_filesystem_full_backup), local_input_history_file);
 2263         /* free memory for tmp-filename */
 2264         free(file_tmp);
 2265         umask(mask);
 2266         return;
 2267     }
 2268 
 2269     for (his_w = 0; his_w <= HIST_MAXNUM; his_w++) {
 2270         for (his_e = 0; his_e < HIST_SIZE; his_e++) {
 2271             /* write an empty line for empty slots */
 2272             if (input_history[his_w][his_e] == NULL)
 2273                 fprintf(fp, "\n");
 2274             else {
 2275                 if ((chr = strpbrk(input_history[his_w][his_e], "\n\r")) != NULL)
 2276                     *chr = '\0';
 2277                 fprintf(fp, "%s\n", input_history[his_w][his_e]);
 2278             }
 2279         }
 2280     }
 2281 
 2282     fchmod(fileno(fp), (mode_t) (S_IRUSR|S_IWUSR)); /* rename_file() preserves mode */
 2283 
 2284     if ((his_w = ferror(fp)) || fclose(fp)) {
 2285         error_message(2, _(txt_filesystem_full), local_input_history_file);
 2286 #ifdef HAVE_CHMOD
 2287         /* fix modes for all pre 1.4.1 local_input_history_file files */
 2288         chmod(local_input_history_file, (mode_t) (S_IRUSR|S_IWUSR));
 2289 #endif /* HAVE_CHMOD */
 2290         if (his_w) {
 2291             clearerr(fp);
 2292             fclose(fp);
 2293         }
 2294     } else
 2295         rename_file(file_tmp, local_input_history_file);
 2296 
 2297     umask(mask);
 2298     free(file_tmp); /* free memory for tmp-filename */
 2299 }
 2300 
 2301 
 2302 /*
 2303  * quotes wildcards * ? \ [ ] with \
 2304  */
 2305 char *
 2306 quote_wild(
 2307     char *str)
 2308 {
 2309     char *target;
 2310     static char buff[2 * LEN];  /* on the safe side */
 2311 
 2312     for (target = buff; *str != '\0'; str++) {
 2313         if (tinrc.wildcard) { /* regex */
 2314             /*
 2315              * quote meta characters ()[]{}\^$*+?.#
 2316              * replace whitespace with '\s' (pcre)
 2317              */
 2318             if (*str == '(' || *str == ')' || *str == '[' || *str == ']' || *str == '{' || *str == '}'
 2319                 || *str == '\\' || *str == '^' || *str == '$'
 2320                 || *str == '*' || *str == '+' || *str == '?' || *str == '.'
 2321                 || *str == '#'
 2322                 || *str == ' ' || *str == '\t') {
 2323                 *target++ = '\\';
 2324                 *target++ = ((*str == ' ' || *str == '\t') ? 's' : *str);
 2325             } else
 2326                 *target++ = *str;
 2327         } else {    /* wildmat */
 2328             if (*str == '*' || *str == '\\' || *str == '[' || *str == ']' || *str == '?')
 2329                 *target++ = '\\';
 2330             *target++ = *str;
 2331         }
 2332     }
 2333     *target = '\0';
 2334     return buff;
 2335 }
 2336 
 2337 
 2338 /*
 2339  * quotes whitespace in regexps for pcre
 2340  */
 2341 char *
 2342 quote_wild_whitespace(
 2343     char *str)
 2344 {
 2345     char *target;
 2346     static char buff[2 * LEN];  /* on the safe side */
 2347 
 2348     for (target = buff; *str != '\0'; str++) {
 2349         if (tinrc.wildcard) { /* regex */
 2350             /*
 2351              * replace whitespace with '\s' (pcre)
 2352              */
 2353             if (*str == ' ' || *str == '\t') {
 2354                 *target++ = '\\';
 2355                 *target++ = 's';
 2356             } else
 2357                 *target++ = *str;
 2358         } else  /* wildmat */
 2359             *target++ = *str;
 2360     }
 2361     *target = '\0';
 2362     return buff;
 2363 }
 2364 
 2365 
 2366 /*
 2367  * strip_name() removes the realname part from a given e-mail address
 2368  */
 2369 void
 2370 strip_name(
 2371     const char *from,
 2372     char *address)
 2373 {
 2374     char name[HEADER_LEN];
 2375 
 2376     gnksa_do_check_from(from, address, name);
 2377 }
 2378 
 2379 
 2380 #ifdef CHARSET_CONVERSION
 2381 static t_bool
 2382 buffer_to_local(
 2383     char **line,
 2384     size_t *max_line_len,
 2385     const char *network_charset,
 2386     const char *local_charset)
 2387 {
 2388     /* FIXME: this should default in RFC2046.c to US-ASCII */
 2389     if ((network_charset && *network_charset)) {    /* Content-Type: had a charset parameter */
 2390         if (strcasecmp(network_charset, local_charset)) { /* different charsets */
 2391             char *clocal_charset;
 2392             iconv_t cd0, cd1, cd2;
 2393 
 2394             clocal_charset = my_malloc(strlen(local_charset) + strlen("//TRANSLIT") + 1);
 2395             strcpy(clocal_charset, local_charset);
 2396 #   ifdef HAVE_ICONV_OPEN_TRANSLIT
 2397             if (tinrc.translit)
 2398                 strcat(clocal_charset, "//TRANSLIT");
 2399 #   endif /* HAVE_ICONV_OPEN_TRANSLIT */
 2400 
 2401             /* iconv() might crash on broken multibyte sequences so check them */
 2402             if (!strcasecmp(network_charset, "UTF-8") || !strcasecmp(network_charset, "utf8"))
 2403                 (void) utf8_valid(*line);
 2404 
 2405             /*
 2406              * TODO: hardcode unknown_ucs4 (0x00 0x00 0x00 0x3f)
 2407              *       instead of converting it?
 2408              */
 2409             cd0 = iconv_open("UCS-4", "US-ASCII");
 2410             cd1 = iconv_open("UCS-4", network_charset);
 2411             cd2 = iconv_open(clocal_charset, "UCS-4");
 2412             if (cd0 != (iconv_t) (-1) && cd1 != (iconv_t) (-1) && cd2 != (iconv_t) (-1)) {
 2413                 ICONV_CONST char *inbuf;
 2414                 char unknown = '?';
 2415                 ICONV_CONST char *unknown_ascii = &unknown;
 2416                 char *unknown_buf;
 2417                 char unknown_ucs4[4];
 2418                 char *obuf, *outbuf;
 2419                 char *tmpbuf, *tbuf;
 2420                 ICONV_CONST char *cur_inbuf;
 2421                 int used;
 2422                 size_t inbytesleft = 1;
 2423                 size_t unknown_bytesleft = 4;
 2424                 size_t tmpbytesleft, tsize;
 2425                 size_t outbytesleft, osize;
 2426                 size_t cur_obl, cur_ibl;
 2427                 size_t result;
 2428 
 2429                 unknown_buf = unknown_ucs4;
 2430 
 2431                 /* convert '?' from ASCII to UCS-4 */
 2432                 iconv(cd0, &unknown_ascii, &inbytesleft, &unknown_buf, &unknown_bytesleft);
 2433 
 2434                 /* temporarily convert to UCS-4 */
 2435                 inbuf = (ICONV_CONST char *) *line;
 2436                 inbytesleft = strlen(*line);
 2437                 tmpbytesleft = inbytesleft * 4 + 4; /* should be enough */
 2438                 tsize = tmpbytesleft;
 2439                 tbuf = my_malloc(tsize);
 2440                 tmpbuf = (char *) tbuf;
 2441 
 2442                 do {
 2443                     errno = 0;
 2444                     result = iconv(cd1, &inbuf, &inbytesleft, &tmpbuf, &tmpbytesleft);
 2445                     if (result == (size_t) (-1)) {
 2446                         switch (errno) {
 2447                             case EILSEQ:
 2448                                 memcpy(tmpbuf, unknown_ucs4, 4);
 2449                                 tmpbuf += 4;
 2450                                 tmpbytesleft -= 4;
 2451                                 inbuf++;
 2452                                 inbytesleft--;
 2453                                 break;
 2454 
 2455                             case E2BIG:
 2456                                 tbuf = my_realloc(tbuf, tsize * 2);
 2457                                 tmpbuf = (char *) (tbuf + tsize - tmpbytesleft);
 2458                                 tmpbytesleft += tsize;
 2459                                 tsize <<= 1; /* double size */
 2460                                 break;
 2461 
 2462                             default:
 2463                                 inbytesleft = 0;
 2464                         }
 2465                     }
 2466                 } while (inbytesleft > 0);
 2467 
 2468                 /* now convert from UCS-4 to local charset */
 2469                 inbuf = (ICONV_CONST char *) tbuf;
 2470                 inbytesleft = tsize - tmpbytesleft;
 2471                 outbytesleft = inbytesleft;
 2472                 osize = outbytesleft;
 2473                 obuf = my_malloc(osize + 1);
 2474                 outbuf = (char *) obuf;
 2475 
 2476                 do {
 2477                     /*
 2478                      * save the parameters we need to redo the call of iconv
 2479                      * if we get into the E2BIG case
 2480                      */
 2481                     cur_inbuf = inbuf;
 2482                     cur_ibl = inbytesleft;
 2483                     used = outbuf - obuf;
 2484                     cur_obl = outbytesleft;
 2485 
 2486                     errno = 0;
 2487                     result = iconv(cd2, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
 2488                     if (result == (size_t) (-1)) {
 2489                         switch (errno) {
 2490                             case EILSEQ:
 2491                                 **&outbuf = '?';
 2492                                 outbuf++;
 2493                                 outbytesleft--;
 2494                                 inbuf += 4;
 2495                                 inbytesleft -= 4;
 2496                                 break;
 2497 
 2498                             case E2BIG:
 2499                                 /*
 2500                                  * outbuf was too small
 2501                                  * As some input could be converted successfully
 2502                                  * and we don`t know where the last complete char
 2503                                  * ends, redo the last conversion completely.
 2504                                  */
 2505                                 /* resize the output buffer */
 2506                                 obuf = my_realloc(obuf, osize * 2 + 1);
 2507                                 outbuf = obuf + used;
 2508                                 outbytesleft = cur_obl + osize;
 2509                                 osize <<= 1; /* double size */
 2510                                 /* reset the other params */
 2511                                 inbuf = cur_inbuf;
 2512                                 inbytesleft = cur_ibl;
 2513                                 break;
 2514 
 2515                             default:
 2516                                 inbytesleft = 0;
 2517                         }
 2518                     }
 2519                 } while (inbytesleft > 0);
 2520 
 2521                 **&outbuf = '\0';
 2522                 if (*max_line_len < strlen(obuf) + 1) {
 2523                     *max_line_len = strlen(obuf) + 1;
 2524                     *line = my_realloc(*line, *max_line_len);
 2525                 }
 2526                 strcpy(*line, obuf);
 2527                 iconv_close(cd2);
 2528                 iconv_close(cd1);
 2529                 iconv_close(cd0);
 2530                 free(obuf);
 2531                 free(tbuf);
 2532             } else {
 2533                 if (cd2 != (iconv_t) (-1))
 2534                     iconv_close(cd2);
 2535                 if (cd1 != (iconv_t) (-1))
 2536                     iconv_close(cd1);
 2537                 if (cd0 != (iconv_t) (-1))
 2538                     iconv_close(cd0);
 2539                 free(clocal_charset);
 2540                 return FALSE;
 2541             }
 2542             free(clocal_charset);
 2543         }
 2544     }
 2545     return TRUE;
 2546 }
 2547 
 2548 
 2549 /* convert from local_charset to txt_mime_charsets[mmnwcharset] */
 2550 t_bool
 2551 buffer_to_network(
 2552     char *line,
 2553     int mmnwcharset)
 2554 {
 2555     char *obuf;
 2556     char *outbuf;
 2557     ICONV_CONST char *inbuf;
 2558     iconv_t cd;
 2559     size_t result, osize;
 2560     size_t inbytesleft, outbytesleft;
 2561     t_bool conv_success = TRUE;
 2562 
 2563     if (strcasecmp(txt_mime_charsets[mmnwcharset], tinrc.mm_local_charset)) {
 2564         if ((cd = iconv_open(txt_mime_charsets[mmnwcharset], tinrc.mm_local_charset)) != (iconv_t) (-1)) {
 2565             inbytesleft = strlen(line);
 2566             inbuf = (char *) line;
 2567             outbytesleft = 1 + inbytesleft * 4;
 2568             osize = outbytesleft;
 2569             obuf = my_malloc(osize + 1);
 2570             outbuf = (char *) obuf;
 2571 
 2572             do {
 2573                 errno = 0;
 2574                 result = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
 2575                 if (result == (size_t) (-1)) {
 2576                     switch (errno) {
 2577                         case EILSEQ:
 2578                             /* TODO: only one '?' for each multibyte sequence ? */
 2579                             **&outbuf = '?';
 2580                             outbuf++;
 2581                             inbuf++;
 2582                             inbytesleft--;
 2583                             conv_success = FALSE;
 2584                             break;
 2585 
 2586                         case E2BIG:
 2587                             obuf = my_realloc(obuf, osize * 2);
 2588                             outbuf = (char *) (obuf + osize - outbytesleft);
 2589                             outbytesleft += osize;
 2590                             osize <<= 1; /* double size */
 2591                             break;
 2592 
 2593                         default:    /* EINVAL */
 2594                             inbytesleft = 0;
 2595                             conv_success = FALSE;
 2596                     }
 2597                 }
 2598             } while (inbytesleft > 0);
 2599 
 2600             **&outbuf = '\0';
 2601             strcpy(line, obuf); /* FIXME: here we assume that line is big enough to hold obuf */
 2602             free(obuf);
 2603             iconv_close(cd);
 2604         }
 2605     }
 2606     return conv_success;
 2607 }
 2608 #endif /* CHARSET_CONVERSION */
 2609 
 2610 
 2611 char *
 2612 buffer_to_ascii(
 2613     char *c)
 2614 {
 2615     char *a = c;
 2616 
 2617     while (*c != '\0') {
 2618         /* reduce to US-ASCII, other non-prints are filtered later */
 2619         if ((unsigned char) *c >= 128)
 2620             *c = '?';
 2621         c++;
 2622     }
 2623     return a;
 2624 }
 2625 
 2626 
 2627 /*
 2628  * do some character set processing
 2629  *
 2630  * this is called for headers, overview data, and article bodies
 2631  * to set non-ASCII characters to '?'
 2632  * (only with MIME_STRICT_CHARSET and !NO_LOCALE or CHARSET_CONVERSION
 2633  * and network_charset=="US-ASCII")
 2634  */
 2635 void
 2636 process_charsets(
 2637     char **line,
 2638     size_t *max_line_len,
 2639     const char *network_charset,
 2640     const char *local_charset,
 2641     t_bool conv_tex2iso)
 2642 {
 2643     char *p;
 2644 
 2645 #ifdef CHARSET_CONVERSION
 2646     if (strcasecmp(network_charset, "US-ASCII")) {  /* network_charset is NOT US-ASCII */
 2647         if (iso2asc_supported >= 0)
 2648             p = my_strdup("ISO-8859-1");
 2649         else
 2650             p = my_strdup(local_charset);
 2651         if (!buffer_to_local(line, max_line_len, network_charset, p))
 2652             buffer_to_ascii(*line);
 2653         free(p);
 2654     } else /* set non-ASCII characters to '?' */
 2655         buffer_to_ascii(*line);
 2656 #else
 2657 #   if defined(MIME_STRICT_CHARSET) && !defined(NO_LOCALE)
 2658     if ((local_charset && strcasecmp(network_charset, local_charset)) || !strcasecmp(network_charset, "US-ASCII"))
 2659         /* different charsets || network charset is US-ASCII (see below) */
 2660         buffer_to_ascii(*line);
 2661 #   endif /* MIME_STRICT_CHARSET && !NO_LOCALE */
 2662     /* charset conversion (codepage version) */
 2663 #endif /* CHARSET_CONVERSION */
 2664 
 2665     /*
 2666      * TEX2ISO conversion should be done before ISO2ASC conversion
 2667      * to allow TEX2ISO && ISO2ASC, i.e. "a -> auml -> ae
 2668      */
 2669     if (conv_tex2iso) {
 2670         p = my_strdup(*line);
 2671         convert_tex2iso(p, *line);
 2672         free(p);
 2673     }
 2674 
 2675     /* iso2asc support */
 2676 #ifdef CHARSET_CONVERSION
 2677     if (iso2asc_supported >= 0)
 2678 #else
 2679     if (iso2asc_supported >= 0 && !strcasecmp(network_charset, "ISO-8859-1"))
 2680 #endif /* CHARSET_CONVERSION */
 2681     {
 2682         p = my_strdup(*line);
 2683         convert_iso2asc(p, line, max_line_len, iso2asc_supported);
 2684         free(p);
 2685     }
 2686 }
 2687 
 2688 
 2689 /*
 2690  * checking of mail addresses for GNKSA compliance
 2691  *
 2692  * son of RFC 1036:
 2693  *   article         = 1*header separator body
 2694  *   header          = start-line *continuation
 2695  *   start-line      = header-name ":" space [ nonblank-text ] eol
 2696  *   continuation    = space nonblank-text eol
 2697  *   header-name     = 1*name-character *( "-" 1*name-character )
 2698  *   name-character  = letter / digit
 2699  *   letter          = <ASCII letter A-Z or a-z>
 2700  *   digit           = <ASCII digit 0-9>
 2701  *   separator       = eol
 2702  *   body            = *( [ nonblank-text / space ] eol )
 2703  *   eol             = <EOL>
 2704  *   nonblank-text   = [ space ] text-character *( space-or-text )
 2705  *   text-character  = <any ASCII character except NUL (ASCII 0),
 2706  *                       HT (ASCII 9), LF (ASCII 10), CR (ASCII 13),
 2707  *                       or blank (ASCII 32)>
 2708  *   space           = 1*( <HT (ASCII 9)> / <blank (ASCII 32)> )
 2709  *   space-or-text   = space / text-character
 2710  *   encoded-word  = "=?" charset "?" encoding "?" codes "?="
 2711  *   charset       = 1*tag-char
 2712  *   encoding      = 1*tag-char
 2713  *   tag-char      = <ASCII printable character except !()<>@,;:\"[]/?=>
 2714  *   codes         = 1*code-char
 2715  *   code-char     = <ASCII printable character except ?>
 2716  *   From-content  = address [ space "(" paren-phrase ")" ]
 2717  *                 /  [ plain-phrase space ] "<" address ">"
 2718  *   paren-phrase  = 1*( paren-char / space / encoded-word )
 2719  *   paren-char    = <ASCII printable character except ()<>\>
 2720  *   plain-phrase  = plain-word *( space plain-word )
 2721  *   plain-word    = unquoted-word / quoted-word / encoded-word
 2722  *   unquoted-word = 1*unquoted-char
 2723  *   unquoted-char = <ASCII printable character except !()<>@,;:\".[]>
 2724  *   quoted-word   = quote 1*( quoted-char / space ) quote
 2725  *   quote         = <" (ASCII 34)>
 2726  *   quoted-char   = <ASCII printable character except "()<>\>
 2727  *   address       = local-part "@" domain
 2728  *   local-part    = unquoted-word *( "." unquoted-word )
 2729  *   domain        = unquoted-word *( "." unquoted-word )
 2730 */
 2731 
 2732 
 2733 /*
 2734  * legal domain name components according to RFC 1034
 2735  * includes also '.' as valid separator
 2736  */
 2737 static char gnksa_legal_fqdn_chars[256] = {
 2738 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2739 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2740 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2741 /* 0x20 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,1,0,
 2742 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
 2743 /* 0x40 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2744 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
 2745 /* 0x60 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2746 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
 2747 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2748 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2749 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2750 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2751 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2752 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2753 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2754 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2755 };
 2756 
 2757 
 2758 /*
 2759  * legal localpart components according to son of RFC 1036
 2760  * includes also '.' as valid separator
 2761  */
 2762 static char gnksa_legal_localpart_chars[256] = {
 2763 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2764 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2765 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2766 /* 0x20 */ 0,1,0,1, 1,1,1,1, 0,0,1,1, 0,1,1,1,
 2767 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,1,0,1,
 2768 /* 0x40 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2769 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,1,1,
 2770 /* 0x60 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2771 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0,
 2772 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2773 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2774 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2775 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2776 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2777 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2778 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2779 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2780 };
 2781 
 2782 
 2783 /*
 2784  * legal realname characters according to son of RFC 1036
 2785  *
 2786  * we also allow CR & LF for folding
 2787  */
 2788 static char gnksa_legal_realname_chars[256] = {
 2789 /*         0 1 2 3  4 5 6 7  8 9 a b  c d e f */
 2790 /* 0x00 */ 0,0,0,0, 0,0,0,0, 0,0,1,0, 0,1,0,0,
 2791 /* 0x10 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2792 /* 0x20 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2793 /* 0x30 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2794 /* 0x40 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2795 /* 0x50 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2796 /* 0x60 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
 2797 /* 0x70 */ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0,
 2798 /* 0x80 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2799 /* 0x90 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2800 /* 0xa0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2801 /* 0xb0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2802 /* 0xc0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2803 /* 0xd0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2804 /* 0xe0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
 2805 /* 0xf0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
 2806 };
 2807 
 2808 
 2809 /*
 2810  * return error message string for given code
 2811  */
 2812 const char *
 2813 gnksa_strerror(
 2814     int errcode)
 2815 {
 2816     const char *message;
 2817 
 2818     switch (errcode) {
 2819         case GNKSA_INTERNAL_ERROR:
 2820             message = _(txt_error_gnksa_internal);
 2821             break;
 2822 
 2823         case GNKSA_LANGLE_MISSING:
 2824             message = txt_error_gnksa_langle;
 2825             break;
 2826 
 2827         case GNKSA_LPAREN_MISSING:
 2828             message = _(txt_error_gnksa_lparen);
 2829             break;
 2830 
 2831         case GNKSA_RPAREN_MISSING:
 2832             message = _(txt_error_gnksa_rparen);
 2833             break;
 2834 
 2835         case GNKSA_ATSIGN_MISSING:
 2836             message = _(txt_error_gnksa_atsign);
 2837             break;
 2838 
 2839         case GNKSA_SINGLE_DOMAIN:
 2840             message = _(txt_error_gnksa_sgl_domain);
 2841             break;
 2842 
 2843         case GNKSA_INVALID_DOMAIN:
 2844             message = _(txt_error_gnksa_inv_domain);
 2845             break;
 2846 
 2847         case GNKSA_ILLEGAL_DOMAIN:
 2848             message = _(txt_error_gnksa_ill_domain);
 2849             break;
 2850 
 2851         case GNKSA_UNKNOWN_DOMAIN:
 2852             message = _(txt_error_gnksa_unk_domain);
 2853             break;
 2854 
 2855         case GNKSA_INVALID_FQDN_CHAR:
 2856             message = _(txt_error_gnksa_fqdn);
 2857             break;
 2858 
 2859         case GNKSA_ZERO_LENGTH_LABEL:
 2860             message = _(txt_error_gnksa_zero);
 2861             break;
 2862 
 2863         case GNKSA_ILLEGAL_LABEL_LENGTH:
 2864             message = _(txt_error_gnksa_length);
 2865             break;
 2866 
 2867         case GNKSA_ILLEGAL_LABEL_HYPHEN:
 2868             message = _(txt_error_gnksa_hyphen);
 2869             break;
 2870 
 2871         case GNKSA_ILLEGAL_LABEL_BEGNUM:
 2872             message = _(txt_error_gnksa_begnum);
 2873             break;
 2874 
 2875         case GNKSA_BAD_DOMAIN_LITERAL:
 2876             message = _(txt_error_gnksa_bad_lit);
 2877             break;
 2878 
 2879         case GNKSA_LOCAL_DOMAIN_LITERAL:
 2880             message = _(txt_error_gnksa_local_lit);
 2881             break;
 2882 
 2883         case GNKSA_RBRACKET_MISSING:
 2884             message = _(txt_error_gnksa_rbracket);
 2885             break;
 2886 
 2887         case GNKSA_LOCALPART_MISSING:
 2888             message = _(txt_error_gnksa_lp_missing);
 2889             break;
 2890 
 2891         case GNKSA_INVALID_LOCALPART:
 2892             message = _(txt_error_gnksa_lp_invalid);
 2893             break;
 2894 
 2895         case GNKSA_ZERO_LENGTH_LOCAL_WORD:
 2896             message = _(txt_error_gnksa_lp_zero);
 2897             break;
 2898 
 2899         case GNKSA_ILLEGAL_UNQUOTED_CHAR:
 2900             message = _(txt_error_gnksa_rn_unq);
 2901             break;
 2902 
 2903         case GNKSA_ILLEGAL_QUOTED_CHAR:
 2904             message = _(txt_error_gnksa_rn_qtd);
 2905             break;
 2906 
 2907         case GNKSA_ILLEGAL_ENCODED_CHAR:
 2908             message = _(txt_error_gnksa_rn_enc);
 2909             break;
 2910 
 2911         case GNKSA_BAD_ENCODE_SYNTAX:
 2912             message = _(txt_error_gnksa_rn_encsyn);
 2913             break;
 2914 
 2915         case GNKSA_ILLEGAL_PAREN_CHAR:
 2916             message = _(txt_error_gnksa_rn_paren);
 2917             break;
 2918 
 2919         case GNKSA_INVALID_REALNAME:
 2920             message = _(txt_error_gnksa_rn_invalid);
 2921             break;
 2922 
 2923         case GNKSA_OK:
 2924         default:
 2925             /* shouldn't happen */
 2926             message = "";
 2927             break;
 2928     }
 2929 
 2930     return message;
 2931 }
 2932 
 2933 
 2934 /*
 2935  * decode realname into displayable string
 2936  * this only does RFC822 decoding, decoding RFC2047 encoded parts must
 2937  * be done by another call to the appropriate function
 2938  */
 2939 static int
 2940 gnksa_dequote_plainphrase(
 2941     char *realname,
 2942     char *decoded,
 2943     int addrtype)
 2944 {
 2945     char *rpos; /* read position */
 2946     char *wpos; /* write position */
 2947     int initialstate;   /* initial state */
 2948     int state;  /* current state */
 2949 
 2950     /* initialize state machine */
 2951     switch (addrtype) {
 2952         case GNKSA_ADDRTYPE_ROUTE:
 2953             initialstate = 0;
 2954             break;
 2955 
 2956         case GNKSA_ADDRTYPE_OLDSTYLE:
 2957             initialstate = 5;
 2958             break;
 2959 
 2960         default:
 2961             /* shouldn't happen */
 2962             return GNKSA_INTERNAL_ERROR;
 2963             /* NOTREACHED */
 2964             break;
 2965     }
 2966     state = initialstate;
 2967     rpos = realname;
 2968     wpos = decoded;
 2969 
 2970     /* decode realname */
 2971     while (*rpos) {
 2972         if (!gnksa_legal_realname_chars[(unsigned char) *rpos])
 2973             return GNKSA_INVALID_REALNAME;
 2974 
 2975         switch (state) {
 2976             case 0:
 2977                 /* in unquoted word, route address style */
 2978                 switch (*rpos) {
 2979                     case '"':
 2980                         state = 1;
 2981                         rpos++;
 2982                         break;
 2983 
 2984                     case '!':
 2985                     case '(':
 2986                     case ')':
 2987                     case '<':
 2988                     case '>':
 2989                     case '@':
 2990                     case ',':
 2991                     case ';':
 2992                     case ':':
 2993                     case '\\':
 2994                     case '.':
 2995                     case '[':
 2996                     case ']':
 2997                         return GNKSA_ILLEGAL_UNQUOTED_CHAR;
 2998                         /* NOTREACHED */
 2999                         break;
 3000 
 3001                     case '=':
 3002                         *(wpos++) = *(rpos++);
 3003                         if ('?' == *rpos) {
 3004                             state = 2;
 3005                             *(wpos++) = *(rpos++);
 3006                         } else
 3007                             state = 0;
 3008                         break;
 3009 
 3010                     default:
 3011                         state = 0;
 3012                         *(wpos++) = *(rpos++);
 3013                         break;
 3014                 }
 3015                 break;
 3016 
 3017             case 1:
 3018                 /* in quoted word */
 3019                 switch (*rpos) {
 3020                     case '"':
 3021                         state = 0;
 3022                         rpos++;
 3023                         break;
 3024 
 3025                     case '(':
 3026                     case ')':
 3027                     case '<':
 3028                     case '>':
 3029                     case '\\':
 3030                         return GNKSA_ILLEGAL_QUOTED_CHAR;
 3031                         /* NOTREACHED */
 3032                         break;
 3033 
 3034                     default:
 3035                         state = 1;
 3036                         *(wpos++) = *(rpos++);
 3037                         break;
 3038                 }
 3039                 break;
 3040 
 3041             case 2:
 3042                 /* in encoded word, charset part */
 3043                 switch (*rpos) {
 3044                     case '?':
 3045                         state = 3;
 3046                         *(wpos++) = *(rpos++);
 3047                         break;
 3048 
 3049                     case '!':
 3050                     case '(':
 3051                     case ')':
 3052                     case '<':
 3053                     case '>':
 3054                     case '@':
 3055                     case ',':
 3056                     case ';':
 3057                     case ':':
 3058                     case '\\':
 3059                     case '"':
 3060                     case '[':
 3061                     case ']':
 3062                     case '/':
 3063                     case '=':
 3064                         return GNKSA_ILLEGAL_ENCODED_CHAR;
 3065                         /* NOTREACHED */
 3066                         break;
 3067 
 3068                     default:
 3069                         state = 2;
 3070                         *(wpos++) = *(rpos++);
 3071                         break;
 3072                 }
 3073                 break;
 3074 
 3075             case 3:
 3076                 /* in encoded word, encoding part */
 3077                 switch (*rpos) {
 3078                     case '?':
 3079                         state = 4;
 3080                         *(wpos++) = *(rpos++);
 3081                         break;
 3082 
 3083                     case '!':
 3084                     case '(':
 3085                     case ')':
 3086                     case '<':
 3087                     case '>':
 3088                     case '@':
 3089                     case ',':
 3090                     case ';':
 3091                     case ':':
 3092                     case '\\':
 3093                     case '"':
 3094                     case '[':
 3095                     case ']':
 3096                     case '/':
 3097                     case '=':
 3098                         return GNKSA_ILLEGAL_ENCODED_CHAR;
 3099                         /* NOTREACHED */
 3100                         break;
 3101 
 3102                     default:
 3103                         state = 3;
 3104                         *(wpos++) = *(rpos++);
 3105                         break;
 3106                 }
 3107                 break;
 3108 
 3109             case 4:
 3110                 /* in encoded word, codes part */
 3111                 switch (*rpos) {
 3112                     case '?':
 3113                         *(wpos++) = *(rpos++);
 3114                         if ('=' == *rpos) {
 3115                             state = initialstate;
 3116                             *(wpos++) = *(rpos++);
 3117                         } else
 3118                             return GNKSA_BAD_ENCODE_SYNTAX;
 3119                         break;
 3120 
 3121                     default:
 3122                         state = 4;
 3123                         *(wpos++) = *(rpos++);
 3124                         break;
 3125                 }
 3126                 break;
 3127 
 3128             case 5:
 3129                 /* in word, old style address */
 3130                 switch (*rpos) {
 3131                     case '(':
 3132                     case ')':
 3133                     case '<':
 3134                     case '>':
 3135                     case '\\':
 3136                         return GNKSA_ILLEGAL_PAREN_CHAR;
 3137                         /* NOTREACHED */
 3138                         break;
 3139 
 3140                     case '=':
 3141                         *(wpos++) = *(rpos++);
 3142                         if ('?' == *rpos) {
 3143                             state = 2;
 3144                             *(wpos++) = *(rpos++);
 3145                         } else
 3146                             state = 5;
 3147                         break;
 3148 
 3149                     default:
 3150                         state = 5;
 3151                         *(wpos++) = *(rpos++);
 3152                         break;
 3153                 }
 3154                 break;
 3155 
 3156             default:
 3157                 /* shouldn't happen */
 3158                 return GNKSA_INTERNAL_ERROR;
 3159         }
 3160     }
 3161 
 3162     /* successful */
 3163     *wpos = '\0';
 3164     return GNKSA_OK;
 3165 }
 3166 
 3167 
 3168 /*
 3169  * check domain literal
 3170  */
 3171 static int
 3172 gnksa_check_domain_literal(
 3173     char *domain)
 3174 {
 3175     char term;
 3176     int n;
 3177     unsigned int x1, x2, x3, x4;
 3178 
 3179     /* parse domain literal into ip number */
 3180     x1 = x2 = x3 = x4 = 666;
 3181     term = '\0';
 3182 
 3183     if ('[' == *domain) { /* literal bracketed */
 3184         n = sscanf(domain, "[%u.%u.%u.%u%c", &x1, &x2, &x3, &x4, &term);
 3185         if (5 != n)
 3186             return GNKSA_BAD_DOMAIN_LITERAL;
 3187 
 3188         if (']' != term)
 3189             return GNKSA_BAD_DOMAIN_LITERAL;
 3190 
 3191     } else { /* literal not bracketed */
 3192 #ifdef REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
 3193         return GNKSA_RBRACKET_MISSING;
 3194 #else
 3195         n = sscanf(domain, "%u.%u.%u.%u%c", &x1, &x2, &x3, &x4, &term);
 3196         /* there should be no terminating character present */
 3197         if (4 != n)
 3198             return GNKSA_BAD_DOMAIN_LITERAL;
 3199 #endif /* REQUIRE_BRACKETS_IN_DOMAIN_LITERAL */
 3200     }
 3201 
 3202     /* check ip parts for legal range */
 3203     if ((255 < x1) || (255 < x2) || (255 < x3) || (255 < x4))
 3204         return GNKSA_BAD_DOMAIN_LITERAL;
 3205 
 3206     /* check for private ip or localhost - see RFC 5735, RFC 5737 */
 3207     if ((!disable_gnksa_domain_check)
 3208         && ((0 == x1)               /* local network */
 3209         || (10 == x1)               /* private class A */
 3210         || ((172 == x1) && (16 == (x2 & 0xf0))) /* private /12 */
 3211         || ((192 == x1) && (168 == x2))     /* private class B */
 3212         || ((192 == x1) && (0 == x2) && (2 == x3)) /* TEST NET-1 */
 3213         || ((198 == x1) && (51 == x2) && (100 == x3)) /* TEST NET-2 */
 3214         || ((203 == x1) && (0 == x2) && (113 == x3)) /* TEST NET-3 */
 3215         || (127 == x1)))            /* loopback */
 3216         return GNKSA_LOCAL_DOMAIN_LITERAL;
 3217 
 3218     return GNKSA_OK;
 3219 }
 3220 
 3221 
 3222 static int
 3223 gnksa_check_domain(
 3224     char *domain)
 3225 {
 3226     char *aux;
 3227     char *last;
 3228     int i;
 3229     int result;
 3230 
 3231     /* check for domain literal */
 3232     if ('[' == *domain) /* check value of domain literal */
 3233         return gnksa_check_domain_literal(domain);
 3234 
 3235     /* check for leading or trailing dot */
 3236     if (('.' == *domain) || ('.' == *(domain + strlen(domain) - 1)))
 3237         return GNKSA_ZERO_LENGTH_LABEL;
 3238 
 3239     /* look for TLD start */
 3240     aux = strrchr(domain, '.');
 3241     if (NULL == aux)
 3242         return GNKSA_SINGLE_DOMAIN;
 3243 
 3244     aux++;
 3245 
 3246     /* check existence of toplevel domain */
 3247     switch ((int) strlen(aux)) {
 3248         case 1:
 3249             /* no numeric components allowed */
 3250             if (('0' <= *aux) && ('9' >= *aux))
 3251                 return gnksa_check_domain_literal(domain);
 3252 
 3253             /* single letter TLDs do not exist */
 3254             return GNKSA_ILLEGAL_DOMAIN;
 3255             /* NOTREACHED */
 3256             break;
 3257 
 3258         case 2:
 3259             /* no numeric components allowed */
 3260             if (('0' <= *aux) && ('9' >= *aux)
 3261                 && ('0' <= *(aux + 1)) && ('9' >= *(aux + 1)))
 3262                 return gnksa_check_domain_literal(domain);
 3263 
 3264             if (('a' <= *aux) && ('z' >= *aux)
 3265                 && ('a' <= *(aux + 1)) && ('z' >= *(aux + 1))) {
 3266                 i = ((*aux - 'a') * 26) + (*(aux + 1)) - 'a';
 3267                 if (!gnksa_country_codes[i])
 3268                     return GNKSA_UNKNOWN_DOMAIN;
 3269             } else
 3270                 return GNKSA_ILLEGAL_DOMAIN;
 3271             break;
 3272 
 3273         case 3:
 3274             /* no numeric components allowed */
 3275             if (('0' <= *aux) && ('9' >= *aux)
 3276                 && ('0' <= *(aux + 1)) && ('9' >= *(aux + 1))
 3277                 && ('0' <= *(aux + 2)) && ('9' >= *(aux + 2)))
 3278                 return gnksa_check_domain_literal(domain);
 3279             /* FALLTHROUGH */
 3280         default:
 3281             /* check for valid domains */
 3282             result = GNKSA_INVALID_DOMAIN;
 3283             for (i = 0; *gnksa_domain_list[i]; i++) {
 3284                 if (!strcmp(aux, gnksa_domain_list[i]))
 3285                     result = GNKSA_OK;
 3286             }
 3287             if (disable_gnksa_domain_check)
 3288                 result = GNKSA_OK;
 3289             if (GNKSA_OK != result)
 3290                 return result;
 3291             break;
 3292     }
 3293 
 3294     /* check for illegal labels */
 3295     last = domain;
 3296     for (aux = domain; *aux; aux++) {
 3297         if ('.' == *aux) {
 3298             if (aux - last - 1 > 63)
 3299                 return GNKSA_ILLEGAL_LABEL_LENGTH;
 3300 
 3301             if ('.' == *(aux + 1))
 3302                 return GNKSA_ZERO_LENGTH_LABEL;
 3303 
 3304             if (('-' == *(aux + 1)) || ('-' == *(aux - 1)))
 3305                 return GNKSA_ILLEGAL_LABEL_HYPHEN;
 3306 
 3307 #ifdef ENFORCE_RFC1034
 3308             if (('0' <= *(aux + 1)) && ('9' >= *(aux + 1)))
 3309                 return GNKSA_ILLEGAL_LABEL_BEGNUM;
 3310 #endif /* ENFORCE_RFC1034 */
 3311             last = aux;
 3312         }
 3313     }
 3314 
 3315     /* check for illegal characters in FQDN */
 3316     for (aux = domain; *aux; aux++) {
 3317         if (!gnksa_legal_fqdn_chars[(unsigned char) *aux])
 3318             return GNKSA_INVALID_FQDN_CHAR;
 3319     }
 3320 
 3321     return GNKSA_OK;
 3322 }
 3323 
 3324 
 3325 /*
 3326  * check localpart of address
 3327  */
 3328 static int
 3329 gnksa_check_localpart(
 3330     const char *localpart)
 3331 {
 3332     const char *aux;
 3333 
 3334     /* no localpart at all? */
 3335     if (!*localpart)
 3336         return GNKSA_LOCALPART_MISSING;
 3337 
 3338     /* check for zero-length domain parts */
 3339     if (('.' == *localpart) || ('.' == *(localpart + strlen(localpart) -1)))
 3340         return GNKSA_ZERO_LENGTH_LOCAL_WORD;
 3341 
 3342     for (aux = localpart; *aux; aux++) {
 3343         if (('.' == *aux) && ('.' == *(aux + 1)))
 3344             return GNKSA_ZERO_LENGTH_LOCAL_WORD;
 3345     }
 3346 
 3347     /* check for illegal characters in FQDN */
 3348     for (aux = localpart; *aux; aux++) {
 3349         if (!gnksa_legal_localpart_chars[(unsigned char) *aux])
 3350             return GNKSA_INVALID_LOCALPART;
 3351     }
 3352 
 3353     return GNKSA_OK;
 3354 }
 3355 
 3356 
 3357 /*
 3358  * split mail address into realname and address parts
 3359  */
 3360 int
 3361 gnksa_split_from(
 3362     const char *from,
 3363     char *address,
 3364     char *realname,
 3365     int *addrtype)
 3366 {
 3367     char *addr_begin;
 3368     char *addr_end;
 3369     char work[HEADER_LEN];
 3370 
 3371     /* init return variables */
 3372     *address = *realname = '\0';
 3373 
 3374     /* copy raw address into work area */
 3375     strncpy(work, from, HEADER_LEN - 2);
 3376     work[HEADER_LEN - 2] = '\0';
 3377     work[HEADER_LEN - 1] = '\0';
 3378 
 3379     /* skip trailing whitespace */
 3380     addr_end = work + strlen(work) - 1;
 3381     while (addr_end >= work && (' ' == *addr_end || '\t' == *addr_end))
 3382         addr_end--;
 3383 
 3384     if (addr_end < work) {
 3385         *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;
 3386         return GNKSA_LPAREN_MISSING;
 3387     }
 3388 
 3389     *(addr_end + 1) = '\0';
 3390     *(addr_end + 2) = '\0';
 3391 
 3392     if ('>' == *addr_end) {
 3393         /* route-address used */
 3394         *addrtype = GNKSA_ADDRTYPE_ROUTE;
 3395 
 3396         /* get address part */
 3397         addr_begin = addr_end;
 3398         while (('<' != *addr_begin) && (addr_begin > work))
 3399             addr_begin--;
 3400 
 3401         if ('<' != *addr_begin) /* syntax error in mail address */
 3402             return GNKSA_LANGLE_MISSING;
 3403 
 3404         /* copy route address */
 3405         *addr_end = *addr_begin = '\0';
 3406         strcpy(address, addr_begin + 1);
 3407 
 3408         /* get realname part */
 3409         addr_end = addr_begin - 1;
 3410         addr_begin = work;
 3411 
 3412         /* strip surrounding whitespace */
 3413         while (addr_end >= work && (' ' == *addr_end || '\t' == *addr_end))
 3414             addr_end--;
 3415 
 3416         while ((' ' == *addr_begin) || ('\t' == *addr_begin))
 3417             addr_begin++;
 3418 
 3419         *++addr_end = '\0';
 3420         /* copy realname */
 3421         strcpy(realname, addr_begin);
 3422     } else {
 3423         /* old-style address used */
 3424         *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;
 3425 
 3426         /* get address part */
 3427         /* skip leading whitespace */
 3428         addr_begin = work;
 3429         while ((' ' == *addr_begin) || ('\t' == *addr_begin))
 3430             addr_begin++;
 3431 
 3432         /* scan forward to next whitespace or null */
 3433         addr_end = addr_begin;
 3434         while ((' ' != *addr_end) && ('\t' != *addr_end) && (*addr_end))
 3435             addr_end++;
 3436 
 3437         *addr_end = '\0';
 3438         /* copy route address */
 3439         strcpy(address, addr_begin);
 3440 
 3441         /* get realname part */
 3442         addr_begin = addr_end + 1;
 3443         addr_end = addr_begin + strlen(addr_begin) -1;
 3444         /* strip surrounding whitespace */
 3445         while ((' ' == *addr_end) || ('\t' == *addr_end))
 3446             addr_end--;
 3447 
 3448         while ((' ' == *addr_begin) || ('\t' == *addr_begin))
 3449             addr_begin++;
 3450 
 3451         /* any realname at all? */
 3452         if (*addr_begin) {
 3453             /* check for parentheses */
 3454             if ('(' != *addr_begin)
 3455                 return GNKSA_LPAREN_MISSING;
 3456 
 3457             if (')' != *addr_end)
 3458                 return GNKSA_RPAREN_MISSING;
 3459 
 3460             /* copy realname */
 3461             *addr_end = '\0';
 3462             strcpy(realname, addr_begin + 1);
 3463         }
 3464     }
 3465 
 3466     /*
 3467      * if we allow <> as From: we must disallow <> as Mesage-ID,
 3468      * see code in post.c:check_article_to_be_posted()
 3469      */
 3470 #if 0
 3471     if (!strchr(address, '@') && *address) /* check for From: without an @ but allow <> */
 3472 #else
 3473     if (!strchr(address, '@')) /* check for From: without an @ */
 3474 #endif /* 0 */
 3475         return GNKSA_ATSIGN_MISSING;
 3476 
 3477     /* split successful */
 3478     return GNKSA_OK;
 3479 }
 3480 
 3481 
 3482 /*
 3483  * restrictive check for valid address conforming to RFC 1036, son of RFC 1036
 3484  * and draft-usefor-article-xx.txt
 3485  */
 3486 int
 3487 gnksa_do_check_from(
 3488     const char *from,
 3489     char *address,
 3490     char *realname)
 3491 {
 3492     char *addr_begin;
 3493     char decoded[HEADER_LEN];
 3494     int result, code, addrtype;
 3495 
 3496     decoded[0] = '\0';
 3497 
 3498 #ifdef DEBUG
 3499     if (debug & DEBUG_MISC)
 3500         wait_message(0, "From:=[%s]", from);
 3501 #endif /* DEBUG */
 3502 
 3503     /* split from */
 3504     code = gnksa_split_from(from, address, realname, &addrtype);
 3505     if ('\0' == *address) /* address missing or not extractable */
 3506         return code;
 3507 
 3508 #ifdef DEBUG
 3509     if (debug & DEBUG_MISC)
 3510         wait_message(0, "address=[%s]", address);
 3511 #endif /* DEBUG */
 3512 
 3513     /* parse address */
 3514     addr_begin = strrchr(address, '@');
 3515 
 3516     if (NULL != addr_begin) {
 3517         /* temporarily terminate string at separator position */
 3518         *addr_begin++ = '\0';
 3519 
 3520 #ifdef DEBUG
 3521         if (debug & DEBUG_MISC)
 3522             wait_message(0, "FQDN=[%s]", addr_begin);
 3523 #endif /* DEBUG */
 3524 
 3525         /* convert FQDN part to lowercase */
 3526         str_lwr(addr_begin);
 3527 
 3528         if (GNKSA_OK != (result = gnksa_check_domain(addr_begin))
 3529             && (GNKSA_OK == code)) /* error detected */
 3530             code = result;
 3531 
 3532         if (GNKSA_OK != (result = gnksa_check_localpart(address))
 3533             && (GNKSA_OK == code)) /* error detected */
 3534             code = result;
 3535 
 3536         /* restore separator character */
 3537         *--addr_begin = '@';
 3538     }
 3539 
 3540 #ifdef DEBUG
 3541     if (debug & DEBUG_MISC)
 3542         wait_message(0, "realname=[%s]", realname);
 3543 #endif /* DEBUG */
 3544 
 3545     /* check realname */
 3546     if (GNKSA_OK != (result = gnksa_dequote_plainphrase(realname, decoded, addrtype))) {
 3547         if (GNKSA_OK == code) /* error detected */
 3548             code = result;
 3549     } else  /* copy dequoted realname to result variable */
 3550         strcpy(realname, decoded);
 3551 
 3552 #ifdef DEBUG
 3553     if (debug & DEBUG_MISC) { /* TODO: dump to a file instead of wait_message() */
 3554         if (GNKSA_OK != code)
 3555             wait_message(2, "From:=[%s], GNKSA=[%d]", from, code);
 3556         else
 3557             wait_message(0, "GNKSA=[%d]", code);
 3558     }
 3559 #endif /* DEBUG */
 3560 
 3561     return code;
 3562 }
 3563 
 3564 
 3565 /*
 3566  * check given address
 3567  */
 3568 int
 3569 gnksa_check_from(
 3570     char *from)
 3571 {
 3572     char address[HEADER_LEN];   /* will be initialised in gnksa_split_from() */
 3573     char realname[HEADER_LEN];  /* which is called by gnksa_do_check_from() */
 3574 
 3575     return gnksa_do_check_from(from, address, realname);
 3576 }
 3577 
 3578 
 3579 /*
 3580  * parse given address
 3581  * return error code on GNKSA check failure
 3582  */
 3583 int
 3584 parse_from(
 3585     const char *from,
 3586     char *address,
 3587     char *realname)
 3588 {
 3589     return gnksa_do_check_from(from, address, realname);
 3590 }
 3591 
 3592 
 3593 /*
 3594  * Strip trailing blanks, tabs, \r and \n
 3595  */
 3596 char *
 3597 strip_line(
 3598     char *line)
 3599 {
 3600     char *ptr = line + strlen(line) - 1;
 3601 
 3602     while ((ptr >= line) && (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n'))
 3603         ptr--;
 3604 
 3605     *++ptr = '\0';
 3606 
 3607     return line;
 3608 }
 3609 
 3610 
 3611 #if defined(CHARSET_CONVERSION) || (defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE))
 3612 /*
 3613  * 'check' a given UTF-8 strig and '?'-out illegal sequences
 3614  * TODO: is this check complete?
 3615  *
 3616  * UTF-8           = ASCII / UTF-8-non-ascii
 3617  * ASCII           = %x00-%x7F
 3618  * UTF-8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4
 3619  * UTF8-1          = %x80-BF
 3620  * UTF8-2          = %xC2-DF 1*UTF8-1
 3621  * UTF8-3          = %xE0 %xA0-BF 1*UTF8-1 / %xE1-EC 2*UTF8-1 /
 3622  *                   %xED %x80-9F 1*UTF8-1 / %xEE-EF 2*UTF8-1
 3623  * UTF8-4          = %xF0 %x90-BF 2*UTF8-1 / %xF1-F3 3*UTF8-1 /
 3624  *                   %xF4 %x80-8F 2*UTF8-1
 3625  */
 3626 char *
 3627 utf8_valid(
 3628     char *line)
 3629 {
 3630     char *c;
 3631     unsigned char d, e, f, g;
 3632     int numc;
 3633     t_bool illegal;
 3634 
 3635     c = line;
 3636 
 3637     while (*c != '\0') {
 3638         if (!(*c & 0x80)) { /* plain US-ASCII? */
 3639             c++;
 3640             continue;
 3641         }
 3642 
 3643         numc = 0;
 3644         illegal = FALSE;
 3645         d = (*c & 0x7c);    /* remove bits 7,1,0 */
 3646 
 3647         do {
 3648             numc++;
 3649         } while ((d <<= 1) & 0x80); /* get sequence length */
 3650 
 3651         if (c + numc > line + strlen(line)) { /* sequence runs past end of string */
 3652             illegal = TRUE;
 3653             numc = line + strlen(line) - c;
 3654         }
 3655 
 3656         if (!illegal) {
 3657             d = *c;
 3658             e = *(c + 1);
 3659 
 3660             switch (numc) {
 3661                 case 2:
 3662                     /* out of range or sequences which would also fit into 1 byte */
 3663                     if (d < 0xc2 || d > 0xdf)
 3664                         illegal = TRUE;
 3665                     break;
 3666 
 3667                 case 3:
 3668                     f = *(c + 2);
 3669                     /* out of range or sequences which would also fit into 2 bytes */
 3670                     if (d < 0xe0 || d > 0xef || (d == 0xe0 && e < 0xa0))
 3671                         illegal = TRUE;
 3672                     /* U+D800 ... U+DBFF, U+DC00 ... U+DFFF (high-surrogates, low-surrogates) */
 3673                     if (d == 0xed && e > 0x9f)
 3674                         illegal = TRUE;
 3675                     /* U+FDD0 ... U+FDEF */
 3676                     if (d == 0xef && e == 0xb7 && (f >= 0x90 && f <= 0xaf))
 3677                         illegal = TRUE;
 3678                     /* U+FFFE, U+FFFF (noncharacters) */
 3679                     if (d == 0xef && e == 0xbf && (f == 0xbe || f == 0xbf))
 3680                         illegal = TRUE;
 3681                     break;
 3682 
 3683                 case 4:
 3684                     f = *(c + 2);
 3685                     g = *(c + 3);
 3686                     /* out of range or sequences which would also fit into 3 bytes */
 3687                     if (d < 0xf0 || d > 0xf7 || (d == 0xf0 && e < 0x90))
 3688                         illegal = TRUE;
 3689                     /* largest current used sequence */
 3690                     if ((d == 0xf4 && e > 0x8f) || d > 0xf4)
 3691                         illegal = TRUE;
 3692                     /* Unicode 3.1 noncharacters */
 3693                     /* U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF; (Unicode 3.1) */
 3694                     if (d == 0xf0 && (e == 0x9f || e == 0xaf || e == 0xbf) && f == 0xbf && (g == 0xbe || g == 0xbf))
 3695                         illegal = TRUE;
 3696                     /* Unicode 3.1 noncharacters */
 3697                     /* U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE, U+7FFFF */
 3698                     /* U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF */
 3699                     /* U+CFFFE, U+CFFFF, U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF */
 3700                     if ((d == 0xf1 || d == 0xf2 || d == 0xf3) && (e == 0x8f || e == 0x9f || e == 0xaf || e == 0xbf) && f == 0xbf && (g == 0xbe || g == 0xbf))
 3701                         illegal = TRUE;
 3702                     /* Unicode 3.1 noncharacters */
 3703                     /* U+10FFFE, U+10FFFF */
 3704                     if (d == 0xf4 && e == 0x8f && f == 0xbf && (g == 0xbe || g == 0xbf))
 3705                         illegal = TRUE;
 3706                     break;
 3707 
 3708 #   if 0    /* currently not used, see also check above */
 3709                 case 5:
 3710                     /* out of range or sequences which would also fit into 4 bytes */
 3711                     if (d < 0xf8 || d > 0xfb || (d == 0xf8 && e < 0x88))
 3712                         illegal = TRUE;
 3713                     break;
 3714 
 3715                 case 6:
 3716                     /* out of range or sequences which would also fit into 5 bytes */
 3717                     if (d < 0xfc || d > 0xfd || (d == 0xfc && e < 0x84))
 3718                         illegal = TRUE;
 3719                     break;
 3720 #   endif /* 0 */
 3721 
 3722                 default:
 3723                     /*
 3724                      * with the check for plain US-ASCII above all other sequence
 3725                      * length are illegal.
 3726                      */
 3727                     illegal = TRUE;
 3728                     break;
 3729             }
 3730         }
 3731 
 3732         for (d = 1; d < numc && !illegal; d++) {
 3733             e = *(c + d);
 3734             if (e < 0x80 || e > 0xbf || e == '\0' || e == '\n')
 3735                 illegal = TRUE;
 3736         }
 3737 
 3738         if (!illegal)
 3739             c += numc; /* skip over valid sequence */
 3740         else {
 3741             while (numc--) {
 3742                 if (*c == '\0' || *c == '\n')
 3743                     break;
 3744                 if (*c & 0x80)  /* replace 'dangerous' bytes */
 3745                     *c = '?';
 3746                 c++;
 3747             }
 3748         }
 3749     }
 3750     return line;
 3751 }
 3752 #endif /* CHARSET_CONVERSION || (MULTIBYTE_ABLE && !NO_LOCALE) */
 3753 
 3754 
 3755 char *
 3756 idna_decode(
 3757     char *in)
 3758 {
 3759     char *out = my_strdup(in);
 3760 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
 3761 /* IDNA 2008 */
 3762 #   if defined(HAVE_LIBIDNKIT) && defined(HAVE_IDN_DECODENAME)
 3763     idn_result_t res;
 3764     char *q, *r = NULL;
 3765 
 3766     if ((q = strrchr(out, '@'))) {
 3767         q++;
 3768         r = strrchr(in, '@');
 3769         r++;
 3770     } else {
 3771         r = in;
 3772         q = out;
 3773     }
 3774     if ((res = idn_decodename(IDN_DECODE_LOOKUP, r, q, out + strlen(out) - q + 1)) == idn_success)
 3775         return out;
 3776     else { /* IDNA 2008 failed, try again with IDNA 2003 if available */
 3777         free(out);
 3778         out = my_strdup(in);
 3779     }
 3780 #       ifdef DEBUG
 3781     if (debug & DEBUG_MISC)
 3782         wait_message(2, "idn_decodename(%s): %s", r, idn_result_tostring(res));
 3783 #       endif /* DEBUG */
 3784 #   endif /* HAVE_LIBIDNKIT && HAVE_IDN_DECODENAME */
 3785 
 3786 /* IDNA 2003 */
 3787 #   ifdef HAVE_LIBICUUC
 3788     {
 3789         UChar *src;
 3790         UChar dest[1024];
 3791         UErrorCode err = U_ZERO_ERROR;
 3792         char *s;
 3793 
 3794         if ((s = strrchr(out, '@')))
 3795             s++;
 3796         else
 3797             s = out;
 3798 
 3799         src = char2UChar(s);
 3800         uidna_IDNToUnicode(src, -1, dest, 1023, UIDNA_USE_STD3_RULES, NULL, &err);
 3801         free(src);
 3802         if (!(U_FAILURE(err))) {
 3803             char *t;
 3804 
 3805             *s = '\0'; /* cut off domainpart */
 3806             s = UChar2char(dest); /* convert domainpart */
 3807             t = my_malloc(strlen(out) + strlen(s) + 1);
 3808             sprintf(t, "%s%s", out, s);
 3809             free(s);
 3810             free(out);
 3811             out = t;
 3812         }
 3813     }
 3814 #   else
 3815 #       if defined(HAVE_LIBIDN) && defined(HAVE_IDNA_TO_UNICODE_LZLZ)
 3816     if (stringprep_check_version("0.3.0")) {
 3817         char *t, *s = NULL;
 3818         int rs = IDNA_SUCCESS;
 3819 
 3820         if ((t = strrchr(out, '@')))
 3821             t++;
 3822         else
 3823             t = out;
 3824 
 3825 #           ifdef HAVE_IDNA_USE_STD3_ASCII_RULES
 3826         if ((rs = idna_to_unicode_lzlz(t, &s, IDNA_USE_STD3_ASCII_RULES)) == IDNA_SUCCESS)
 3827 #           else
 3828         if ((rs = idna_to_unicode_lzlz(t, &s, 0)) == IDNA_SUCCESS)
 3829 #           endif /* HAVE_IDNA_USE_STD3_ASCII_RULES */
 3830             strcpy(t, s);
 3831 #           ifdef DEBUG
 3832         else {
 3833             if (debug & DEBUG_MISC)
 3834 #               ifdef HAVE_IDNA_STRERROR
 3835                 wait_message(2, "idna_to_unicode_lzlz(%s): %s", t, idna_strerror(rs));
 3836 #               else
 3837                 wait_message(2, "idna_to_unicode_lzlz(%s): %d", t, rs);
 3838 #               endif /* HAVE_IDNA_STRERROR */
 3839         }
 3840 #           endif /* DEBUG */
 3841         FreeIfNeeded(s);
 3842     }
 3843 #       endif /* HAVE_LIBIDN && HAVE_IDNA_TO_UNICODE_LZLZ */
 3844 #   endif /* HAVE_LIBICUUC */
 3845 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
 3846     return out;
 3847 }
 3848 
 3849 
 3850 int
 3851 tin_version_info(
 3852     FILE *fp)
 3853 {
 3854     int wlines = 0; /* written lines */
 3855 
 3856 #if defined(__DATE__) && defined(__TIME__)
 3857     fprintf(fp, _("Version: %s %s release %s (\"%s\") %s %s\n"),
 3858         PRODUCT, VERSION, RELEASEDATE, RELEASENAME, __DATE__, __TIME__);
 3859 #else
 3860     fprintf(fp, _("Version: %s %s release %s (\"%s\")\n"),
 3861         PRODUCT, VERSION, RELEASEDATE, RELEASENAME);
 3862 #endif /* __DATE__ && __TIME__ */
 3863     wlines++;
 3864 
 3865 #ifdef SYSTEM_NAME
 3866     fprintf(fp, "Platform:\n");
 3867     fprintf(fp, "\tOS-Name  = \"%s\"\n", SYSTEM_NAME);
 3868     wlines += 2;
 3869 #endif /* SYSTEM_NAME */
 3870 
 3871 #ifdef TIN_CC
 3872     fprintf(fp, "Compiler:\n");
 3873     fprintf(fp, "\tCC       = \"%s\"\n", TIN_CC);
 3874     wlines += 2;
 3875 #   ifdef TIN_CFLAGS
 3876         fprintf(fp, "\tCFLAGS   = \"%s\"\n", TIN_CFLAGS);
 3877         wlines++;
 3878 #   endif /* TIN_CFLAGS */
 3879 #   ifdef TIN_CPP
 3880         fprintf(fp, "\tCPP      = \"%s\"\n", TIN_CPP);
 3881         wlines++;
 3882 #   endif /* TIN_CPP */
 3883 #   ifdef TIN_CPPFLAGS
 3884         fprintf(fp, "\tCPPFLAGS = \"%s\"\n", TIN_CPPFLAGS);
 3885         wlines++;
 3886 #   endif /* TIN_CPPFLAGS */
 3887 #endif /* TIN_CC */
 3888 
 3889 #ifdef TIN_LD
 3890     fprintf(fp, "Linker and Libraries:\n");
 3891     fprintf(fp, "\tLD       = \"%s\"\n", TIN_LD);
 3892     wlines += 2;
 3893 #   ifdef TIN_LDFLAGS
 3894         fprintf(fp, "\tLDFLAGS  = \"%s\"\n", TIN_LDFLAGS);
 3895         wlines++;
 3896 #   endif /* TIN_LDFLAGS */
 3897 #   ifdef TIN_LIBS
 3898         fprintf(fp, "\tLIBS     = \"%s\"\n", TIN_LIBS);
 3899         wlines++;
 3900 #   endif /* TIN_LIBS */
 3901 #endif /* TIN_LD */
 3902     fprintf(fp, "\tPCRE     = \"%s\"\n", pcre_version());
 3903     wlines++;
 3904 
 3905     fprintf(fp, "Characteristics:\n\t"
 3906 /* TODO: complete list and do some useful grouping; show only in -vV case? */
 3907 #ifdef DEBUG
 3908             "+DEBUG "
 3909 #else
 3910             "-DEBUG "
 3911 #endif /* DEBUG */
 3912 #ifdef NNTP_ONLY
 3913             "+NNTP_ONLY "
 3914 #else
 3915 #   ifdef NNTP_ABLE
 3916             "+NNTP_ABLE "
 3917 #   else
 3918             "-NNTP_ABLE "
 3919 #   endif /* NNTP_ABLE */
 3920 #endif /* NNTP_ONLY */
 3921 #ifdef NO_POSTING
 3922             "+NO_POSTING "
 3923 #else
 3924             "-NO_POSTING "
 3925 #endif /* NO_POSTING */
 3926 #ifdef BROKEN_LISTGROUP
 3927             "+BROKEN_LISTGROUP "
 3928 #else
 3929             "-BROKEN_LISTGROUP "
 3930 #endif /* BROKEN_LISTGROUP */
 3931 #ifdef XHDR_XREF
 3932             "+XHDR_XREF"
 3933 #else
 3934             "-XHDR_XREF"
 3935 #endif /* XHDR_XREF */
 3936             "\n\t"
 3937 #ifdef HAVE_FASCIST_NEWSADMIN
 3938             "+HAVE_FASCIST_NEWSADMIN "
 3939 #else
 3940             "-HAVE_FASCIST_NEWSADMIN "
 3941 #endif /* HAVE_FASCIST_NEWSADMIN */
 3942 #ifdef ENABLE_IPV6
 3943             "+ENABLE_IPV6 "
 3944 #else
 3945             "-ENABLE_IPV6 "
 3946 #endif /* ENABLE_IPV6 */
 3947 #ifdef HAVE_COREFILE
 3948             "+HAVE_COREFILE"
 3949 #else
 3950             "-HAVE_COREFILE"
 3951 #endif /* HAVE_COREFILE */
 3952             "\n\t"
 3953 #ifdef NO_SHELL_ESCAPE
 3954             "+NO_SHELL_ESCAPE "
 3955 #else
 3956             "-NO_SHELL_ESCAPE "
 3957 #endif /* NO_SHELL_ESCAPE */
 3958 #ifdef DISABLE_PRINTING
 3959             "+DISABLE_PRINTING "
 3960 #else
 3961             "-DISABLE_PRINTING "
 3962 #endif /* DISABLE_PRINTING */
 3963 #ifdef DONT_HAVE_PIPING
 3964             "+DONT_HAVE_PIPING "
 3965 #else
 3966             "-DONT_HAVE_PIPING "
 3967 #endif /* DONT_HAVE_PIPING */
 3968 #ifdef NO_ETIQUETTE
 3969             "+NO_ETIQUETTE"
 3970 #else
 3971             "-NO_ETIQUETTE"
 3972 #endif /* NO_ETIQUETTE */
 3973             "\n\t"
 3974 #ifdef HAVE_LONG_FILE_NAMES
 3975             "+HAVE_LONG_FILE_NAMES "
 3976 #else
 3977             "-HAVE_LONG_FILE_NAMES "
 3978 #endif /* HAVE_LONG_FILE_NAMES */
 3979 #ifdef APPEND_PID
 3980             "+APPEND_PID "
 3981 #else
 3982             "-APPEND_PID "
 3983 #endif /* APPEND_PID */
 3984 #ifdef HAVE_MH_MAIL_HANDLING
 3985             "+HAVE_MH_MAIL_HANDLING"
 3986 #else
 3987             "-HAVE_MH_MAIL_HANDLING"
 3988 #endif /* HAVE_MH_MAIL_HANDLING */
 3989             "\n\t"
 3990 #ifdef HAVE_ISPELL
 3991             "+HAVE_ISPELL "
 3992 #else
 3993             "-HAVE_ISPELL "
 3994 #endif /* HAVE_ISPELL */
 3995 #ifdef HAVE_METAMAIL
 3996             "+HAVE_METAMAIL "
 3997 #else
 3998             "-HAVE_METAMAIL "
 3999 #endif /* HAVE_METAMAIL */
 4000 #ifdef HAVE_SUM
 4001             "+HAVE_SUM"
 4002 #else
 4003             "-HAVE_SUM"
 4004 #endif /* HAVE_SUM */
 4005             "\n\t"
 4006 #ifdef HAVE_COLOR
 4007             "+HAVE_COLOR "
 4008 #else
 4009             "-HAVE_COLOR "
 4010 #endif /* HAVE_COLOR */
 4011 #ifdef HAVE_PGP
 4012             "+HAVE_PGP "
 4013 #else
 4014             "-HAVE_PGP "
 4015 #endif /* HAVE_PGP */
 4016 #ifdef HAVE_PGPK
 4017             "+HAVE_PGPK "
 4018 #else
 4019             "-HAVE_PGPK "
 4020 #endif /* HAVE_PGPK */
 4021 #ifdef HAVE_GPG
 4022             "+HAVE_GPG"
 4023 #else
 4024             "-HAVE_GPG"
 4025 #endif /* HAVE_GPG */
 4026             "\n\t"
 4027 #ifdef MIME_BREAK_LONG_LINES
 4028             "+MIME_BREAK_LONG_LINES "
 4029 #else
 4030             "-MIME_BREAK_LONG_LINES "
 4031 #endif /* MIME_BREAK_LONG_LINES */
 4032 #ifdef MIME_STRICT_CHARSET
 4033             "+MIME_STRICT_CHARSET "
 4034 #else
 4035             "-MIME_STRICT_CHARSET "
 4036 #endif /* MIME_STRICT_CHARSET */
 4037 #ifdef CHARSET_CONVERSION
 4038             "+CHARSET_CONVERSION"
 4039 #else
 4040             "-CHARSET_CONVERSION"
 4041 #endif /* CHARSET_CONVERSION */
 4042             "\n\t"
 4043 #ifdef MULTIBYTE_ABLE
 4044             "+MULTIBYTE_ABLE "
 4045 #else
 4046             "-MULTIBYTE_ABLE "
 4047 #endif /* MULTIBYTE_ABLE */
 4048 #ifdef NO_LOCALE
 4049             "+NO_LOCALE "
 4050 #else
 4051             "-NO_LOCALE "
 4052 #endif /* NO_LOCALE */
 4053 #ifdef USE_LONG_ARTICLE_NUMBERS
 4054             "+USE_LONG_ARTICLE_NUMBERS"
 4055 #else
 4056             "-USE_LONG_ARTICLE_NUMBERS"
 4057 #endif /* USE_LONG_ARTICLE_NUMBERS */
 4058             "\n\t"
 4059 #ifdef USE_CANLOCK
 4060             "+USE_CANLOCK "
 4061 #else
 4062             "-USE_CANLOCK "
 4063 #endif /* USE_CANLOCK */
 4064 #ifdef EVIL_INSIDE
 4065             "+EVIL_INSIDE "
 4066 #else
 4067             "-EVIL_INSIDE "
 4068 #endif /* EVIL_INSIDE */
 4069 #ifdef FORGERY
 4070             "+FORGERY "
 4071 #else
 4072             "-FORGERY "
 4073 #endif /* FORGERY */
 4074 #ifdef TINC_DNS
 4075             "+TINC_DNS "
 4076 #else
 4077             "-TINC_DNS "
 4078 #endif /* TINC_DNS */
 4079 #ifdef ENFORCE_RFC1034
 4080             "+ENFORCE_RFC1034"
 4081 #else
 4082             "-ENFORCE_RFC1034"
 4083 #endif /* ENFORCE_RFC1034 */
 4084             "\n\t"
 4085 #ifdef REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
 4086             "+REQUIRE_BRACKETS_IN_DOMAIN_LITERAL "
 4087 #else
 4088             "-REQUIRE_BRACKETS_IN_DOMAIN_LITERAL "
 4089 #endif /* REQUIRE_BRACKETS_IN_DOMAIN_LITERAL */
 4090 #ifdef FOLLOW_USEFOR_DRAFT
 4091             "+FOLLOW_USEFOR_DRAFT"
 4092 #else
 4093             "-FOLLOW_USEFOR_DRAFT"
 4094 #endif /* FOLLOW_USEFOR_DRAFT */
 4095             "\n");
 4096     wlines += 11;
 4097     fflush(fp);
 4098     return wlines;
 4099 }
 4100 
 4101 
 4102 void
 4103 draw_mark_selected(
 4104     int i)
 4105 {
 4106     MoveCursor(INDEX2LNUM(i), mark_offset);
 4107     StartInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
 4108     my_fputc(tinrc.art_marked_selected, stdout);
 4109     EndInverse();   /* ToggleInverse() doesn't work correct with ncurses4.x */
 4110     return;
 4111 }
 4112 
 4113 
 4114 int
 4115 tin_gettime(
 4116     struct t_tintime *tt)
 4117 {
 4118 #ifdef HAVE_CLOCK_GETTIME
 4119     static struct timespec cgt;
 4120 #endif /* HAVE_CLOCK_GETTIME */
 4121 #ifdef HAVE_GETTIMEOFDAY
 4122     static struct timeval gt;
 4123 #endif /* HAVE_GETTIMEOFDAY */
 4124 
 4125 #ifdef HAVE_CLOCK_GETTIME
 4126     if (clock_gettime(CLOCK_REALTIME, &cgt) == 0) {
 4127         tt->tv_sec = cgt.tv_sec;
 4128         tt->tv_nsec = cgt.tv_nsec;
 4129         return 0;
 4130     }
 4131 #endif /* HAVE_CLOCK_GETTIME */
 4132 #ifdef HAVE_GETTIMEOFDAY
 4133     if (gettimeofday(&gt, NULL) == 0) {
 4134         tt->tv_sec = gt.tv_sec;
 4135         tt->tv_nsec = gt.tv_usec * 1000;
 4136         return 0;
 4137     }
 4138 #endif /* HAVE_GETTIMEOFDAY */
 4139     tt->tv_sec = 0;
 4140     tt->tv_nsec = 0;
 4141     return -1;
 4142 }
 4143 
 4144 
 4145 #if 0
 4146 /*
 4147  * Stat a mail/news article to see if it still exists
 4148  */
 4149 static t_bool
 4150 stat_article(
 4151     t_artnum art,
 4152     const char *group_path)
 4153 {
 4154     struct t_group currgrp;
 4155 
 4156     currgrp = CURR_GROUP;
 4157 
 4158 #   ifdef NNTP_ABLE
 4159     if (read_news_via_nntp && currgrp.type == GROUP_TYPE_NEWS) {
 4160         char buf[NNTP_STRLEN];
 4161 
 4162         snprintf(buf, sizeof(buf), "STAT %"T_ARTNUM_PFMT, art);
 4163         return (nntp_command(buf, OK_NOTEXT, NULL, 0) != NULL);
 4164     } else
 4165 #   endif /* NNTP_ABLE */
 4166     {
 4167         char filename[PATH_LEN];
 4168         struct stat sb;
 4169 
 4170         joinpath(filename, sizeof(filename), currgrp.spooldir, group_path);
 4171         snprintf(&filename[strlen(filename)], sizeof(filename), "/%"T_ARTNUM_PFMT, art);
 4172 
 4173         return (stat(filename, &sb) != -1);
 4174     }
 4175 }
 4176 #endif /* 0 */