"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/misc.c" (20 Nov 2019, 95975 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


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

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