tin  2.4.4
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.4.4.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

misc.c
Go to the documentation of this file.
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 *
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
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
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
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 */
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
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
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 *
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
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) {
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
492  void)
493 {
494  char *p, *tmp;
495  char shell[LEN];
496 
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)
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
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
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 
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  */
589  for (i = 0; i < selmenu.max; i++) {
590  group = &active[my_group[i]];
591  if (group->read_during_session) {
592  if (ask) {
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)
616  break;
617  }
618 
619  if (wrote_newsrc_lines < read_newsrc_lines) {
620  /* FIXME: prompt for retry? (i.e. remove break) */
622  (read_newsrc_lines - wrote_newsrc_lines),
623  PLURAL(read_newsrc_lines - wrote_newsrc_lines, txt_group),
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 
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 */
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
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
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
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  }
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 
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)
830 
831  return success;
832 #endif /* IGNORE_SYSTEM_STATUS */
833 }
834 
835 
836 void
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
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
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
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 *
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
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
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 
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
1059  void)
1060 {
1062  tinrc.draw_arrow = TRUE;
1063 #ifndef USE_INVERSE_HACK
1064 # if 0
1065  else
1067 # endif /* 0 */
1068 #endif /* !USE_INVERSE_HACK */
1069 }
1070 
1071 
1072 void
1074  void)
1075 {
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
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
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 */
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
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 *
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
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;
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
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 *
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
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 */
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 */
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 */
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
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
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
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
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
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 */
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 */
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 *
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
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)
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
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 */
2258 
2259  if ((fp = fopen(file_tmp, "w")) == NULL) {
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)) {
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
2294 
2295  umask(mask);
2296  free(file_tmp); /* free memory for tmp-filename */
2297 }
2298 
2299 
2300 /*
2301  * quotes wildcards * ? \ [ ] with \
2302  */
2303 char *
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 *
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
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 *
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
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 *
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 
2854  message = _(txt_error_gnksa_fqdn);
2855  break;
2856 
2858  message = _(txt_error_gnksa_zero);
2859  break;
2860 
2862  message = _(txt_error_gnksa_length);
2863  break;
2864 
2866  message = _(txt_error_gnksa_hyphen);
2867  break;
2868 
2870  message = _(txt_error_gnksa_begnum);
2871  break;
2872 
2874  message = _(txt_error_gnksa_bad_lit);
2875  break;
2876 
2878  message = _(txt_error_gnksa_local_lit);
2879  break;
2880 
2882  message = _(txt_error_gnksa_rbracket);
2883  break;
2884 
2886  message = _(txt_error_gnksa_lp_missing);
2887  break;
2888 
2890  message = _(txt_error_gnksa_lp_invalid);
2891  break;
2892 
2894  message = _(txt_error_gnksa_lp_zero);
2895  break;
2896 
2898  message = _(txt_error_gnksa_rn_unq);
2899  break;
2900 
2902  message = _(txt_error_gnksa_rn_qtd);
2903  break;
2904 
2906  message = _(txt_error_gnksa_rn_enc);
2907  break;
2908 
2910  message = _(txt_error_gnksa_rn_encsyn);
2911  break;
2912 
2914  message = _(txt_error_gnksa_rn_paren);
2915  break;
2916 
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
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 
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 ']':
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 '\\':
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 '=':
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 '=':
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
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 */
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 */
3215 
3216  return GNKSA_OK;
3217 }
3218 
3219 
3220 static int
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  }
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)
3297 
3298  if (*(aux + 1) == '.')
3299  return GNKSA_ZERO_LENGTH_LABEL;
3300 
3301  if ((*(aux + 1) == '-') || (*(aux - 1) == '-'))
3303 
3304 #ifdef ENFORCE_RFC1034
3305  if ((*(aux + 1) >= '0') && (*(aux + 1) <= '9'))
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
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) == '.'))
3338 
3339  for (aux = localpart; *aux; aux++) {
3340  if ((*aux == '.') && (*(aux + 1) == '.'))
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
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
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
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
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 *
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 *
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
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"),
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
4124  int i)
4125 {
4127  StartInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
4129  EndInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
4130 }
4131 
4132 
4133 int
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 */
txt_error_gnksa_lp_invalid
constext txt_error_gnksa_lp_invalid[]
Definition: lang.c:215
name
const char * name
Definition: signal.c:117
GNKSA_BAD_ENCODE_SYNTAX
#define GNKSA_BAD_ENCODE_SYNTAX
Definition: extern.h:1599
txt_error_gnksa_rn_qtd
constext txt_error_gnksa_rn_qtd[]
Definition: lang.c:218
t_config::wildcard
int wildcard
Definition: tinrc.h:155
txt_error_gnksa_rn_unq
constext txt_error_gnksa_rn_unq[]
Definition: lang.c:217
EOF
#define EOF
Definition: tin.h:2445
t_article
Definition: tin.h:1510
t_header::date
char * date
Definition: rfc2046.h:132
NNTP_STRLEN
#define NNTP_STRLEN
Definition: nntplib.h:155
policy.h
file_mtime
long file_mtime(const char *file)
Definition: misc.c:2136
create_index_lock_file
void create_index_lock_file(char *the_lock_file)
Definition: misc.c:1127
txt_command_failed
constext txt_command_failed[]
Definition: lang.c:150
strcasecmp
int strcasecmp(const char *p, const char *q)
Definition: string.c:468
t_cmdlineopts::maildir
char maildir[255]
Definition: tin.h:1468
GNKSA_ILLEGAL_PAREN_CHAR
#define GNKSA_ILLEGAL_PAREN_CHAR
Definition: extern.h:1600
DEBUG_MISC
#define DEBUG_MISC
Definition: debug.h:54
_
#define _(Text)
Definition: tin.h:94
GNKSA_LANGLE_MISSING
#define GNKSA_LANGLE_MISSING
Definition: extern.h:1574
txt_error_gnksa_bad_lit
constext txt_error_gnksa_bad_lit[]
Definition: lang.c:211
my_realloc
#define my_realloc(ptr, size)
Definition: tin.h:2198
get_initials
int get_initials(struct t_article *art, char *s, int maxsize)
Definition: misc.c:1995
INDEX_TOP
#define INDEX_TOP
Definition: tin.h:1008
txt_newsrc_again
constext txt_newsrc_again[]
Definition: lang.c:662
lock_file
char lock_file[PATH_LEN]
Definition: init.c:88
GNKSA_BAD_DOMAIN_LITERAL
#define GNKSA_BAD_DOMAIN_LITERAL
Definition: extern.h:1588
t_header::messageid
char * messageid
Definition: rfc2046.h:137
my_strdup
char * my_strdup(const char *str)
Definition: string.c:133
txt_error_copy_fp
constext txt_error_copy_fp[]
Definition: lang.c:192
center_line
void center_line(int line, t_bool inverse, const char *str)
Definition: screen.c:258
txt_reading_input_history_file
constext txt_reading_input_history_file[]
Definition: lang.c:765
t_group
Definition: tin.h:1772
t_tintime
Definition: tin.h:2034
txt_error_gnksa_rparen
constext txt_error_gnksa_rparen[]
Definition: lang.c:200
process_id
pid_t process_id
Definition: init.c:124
_strfpath
static int _strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all)
Definition: misc.c:1484
my_isprint
int my_isprint(int c)
Definition: misc.c:986
txt_error_gnksa_zero
constext txt_error_gnksa_zero[]
Definition: lang.c:207
prefix
static const char * prefix[]
Definition: pcregrep.c:219
my_mkdir
int my_mkdir(char *path, mode_t mode)
Definition: misc.c:708
pcre_exec
int pcre_exec(const pcre *, const pcre_extra *, const char *, int, int, int, int *, int)
Definition: pcre_exec.c:3690
txt_error_gnksa_rn_enc
constext txt_error_gnksa_rn_enc[]
Definition: lang.c:219
cLINES
int cLINES
Definition: curses.c:52
t_config::start_editor_offset
t_bool start_editor_offset
Definition: tinrc.h:237
GNKSA_INVALID_FQDN_CHAR
#define GNKSA_INVALID_FQDN_CHAR
Definition: extern.h:1583
gnksa_legal_realname_chars
static char gnksa_legal_realname_chars[256]
Definition: misc.c:2786
GROUP_TYPE_NEWS
#define GROUP_TYPE_NEWS
Definition: tin.h:1059
TIN_EDITOR_FMT_ON
#define TIN_EDITOR_FMT_ON
Definition: tin.h:2119
GNKSA_INVALID_DOMAIN
#define GNKSA_INVALID_DOMAIN
Definition: extern.h:1580
t_config::quote_style
int quote_style
Definition: tinrc.h:230
read_news_via_nntp
t_bool read_news_via_nntp
Definition: init.c:150
random_organization
char * random_organization(char *in_org)
Definition: misc.c:2149
txt_group
constext txt_group[]
Definition: lang.c:1217
my_flush
#define my_flush()
Definition: tcurses.h:171
ClearScreen
void ClearScreen(void)
Definition: curses.c:410
txt_error_gnksa_hyphen
constext txt_error_gnksa_hyphen[]
Definition: lang.c:209
S_IWUSR
#define S_IWUSR
Definition: tin.h:2137
cCRLF
#define cCRLF
Definition: tcurses.h:150
txt_filesystem_full
constext txt_filesystem_full[]
Definition: lang.c:272
quote_wild_whitespace
char * quote_wild_whitespace(char *str)
Definition: misc.c:2340
strip_line
char * strip_line(char *line)
Definition: misc.c:3599
art
static t_openartinfo * art
Definition: cook.c:78
shell_escape
void shell_escape(void)
Definition: misc.c:491
GNKSA_INVALID_LOCALPART
#define GNKSA_INVALID_LOCALPART
Definition: extern.h:1593
tinrc
struct t_config tinrc
Definition: init.c:191
perror_message
void perror_message(const char *fmt,...)
Definition: screen.c:220
WEXITSTATUS
#define WEXITSTATUS(status)
Definition: tin.h:327
wait_message
void wait_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:133
cMain
@ cMain
Definition: tin.h:107
txt_error_gnksa_langle
constext txt_error_gnksa_langle[]
Definition: lang.c:198
curr_group
struct t_group * curr_group
Definition: group.c:55
trace.h
signal_context
int signal_context
Definition: signal.c:105
get_tmpfilename
char * get_tmpfilename(const char *filename)
Definition: misc.c:101
RELEASEDATE
#define RELEASEDATE
Definition: version.h:48
code
int code
Definition: signal.c:116
write_input_history_file
static void write_input_history_file(void)
Definition: misc.c:2242
regex_cache::extra
pcre_extra * extra
Definition: tin.h:1919
copy_body
void copy_body(FILE *fp_ip, FILE *fp_op, char *prefix, char *initl, t_bool raw_data)
Definition: misc.c:257
append_file
void append_file(char *old_filename, char *new_filename)
Definition: misc.c:120
GNKSA_ILLEGAL_LABEL_BEGNUM
#define GNKSA_ILLEGAL_LABEL_BEGNUM
Definition: extern.h:1587
S_IRWXG
#define S_IRWXG
Definition: tin.h:2140
txt_error_gnksa_rbracket
constext txt_error_gnksa_rbracket[]
Definition: lang.c:213
fmt_string
char * fmt_string(const char *fmt,...)
Definition: string.c:1379
GNKSA_ILLEGAL_LABEL_HYPHEN
#define GNKSA_ILLEGAL_LABEL_HYPHEN
Definition: extern.h:1586
my_fputc
#define my_fputc(ch, stream)
Definition: tcurses.h:152
GNKSA_ILLEGAL_DOMAIN
#define GNKSA_ILLEGAL_DOMAIN
Definition: extern.h:1581
OLDNEWSRC_FILE
#define OLDNEWSRC_FILE
Definition: tin.h:732
t_config::interactive_mailer
int interactive_mailer
Definition: tinrc.h:256
CLOSEDIR
#define CLOSEDIR(DIR)
Definition: tin.h:2355
RELEASENAME
#define RELEASENAME
Definition: version.h:49
TRACE
#define TRACE(p)
Definition: trace.h:65
t_config::art_marked_selected
char art_marked_selected
Definition: tinrc.h:62
MoveCursor
void MoveCursor(int row, int col)
Definition: curses.c:441
state
state
Definition: save.c:56
tcurses.h
txt_error_asfail
constext txt_error_asfail[]
Definition: lang.c:181
GNKSA_ILLEGAL_ENCODED_CHAR
#define GNKSA_ILLEGAL_ENCODED_CHAR
Definition: extern.h:1598
GNKSA_ZERO_LENGTH_LABEL
#define GNKSA_ZERO_LENGTH_LABEL
Definition: extern.h:1584
version.h
t_menu::max
int max
Definition: tin.h:2007
info_message
void info_message(const char *fmt,...)
Definition: screen.c:102
tin.h
invoke_cmd
t_bool invoke_cmd(const char *nam)
Definition: misc.c:793
CMDLINE_SAVEDIR
#define CMDLINE_SAVEDIR
Definition: tin.h:1094
no_quote
@ no_quote
Definition: tin.h:1245
asfail
void asfail(const char *file, int line, const char *cond)
Definition: misc.c:142
gnksa_check_domain_literal
static int gnksa_check_domain_literal(char *domain)
Definition: misc.c:3170
draw_percent_mark
void draw_percent_mark(long cur_num, long max_num)
Definition: misc.c:837
base_name
void base_name(const char *fullpath, char *file)
Definition: misc.c:868
SHOW_FROM_NONE
#define SHOW_FROM_NONE
Definition: tin.h:1141
iso2asc_supported
int iso2asc_supported
Definition: init.c:120
cmdline
struct t_cmdlineopts cmdline
Definition: init.c:189
strfpath_cp
static char * strfpath_cp(char *str, char *tbuf, char *endp)
Definition: misc.c:1439
SHOW_FROM_BOTH
#define SHOW_FROM_BOTH
Definition: tin.h:1144
tin_gettime
int tin_gettime(struct t_tintime *tt)
Definition: misc.c:4134
QUOTE_COMPRESS
#define QUOTE_COMPRESS
Definition: tin.h:1212
GNKSA_LOCALPART_MISSING
#define GNKSA_LOCALPART_MISSING
Definition: extern.h:1592
PATH_LEN
#define PATH_LEN
Definition: tin.h:837
txt_error_gnksa_rn_paren
constext txt_error_gnksa_rn_paren[]
Definition: lang.c:221
EndInverse
void EndInverse(void)
Definition: curses.c:564
sigdisp
RETSIGTYPE(*)(SIG_ARGS) sigdisp(int signum, RETSIGTYPE(*func)(SIG_ARGS))
Definition: signal.c:174
GNKSA_LOCAL_DOMAIN_LITERAL
#define GNKSA_LOCAL_DOMAIN_LITERAL
Definition: extern.h:1589
mail_check
t_bool mail_check(const char *mailbox_name)
Definition: misc.c:911
txt_error_gnksa_rn_encsyn
constext txt_error_gnksa_rn_encsyn[]
Definition: lang.c:220
system_status
int system_status
Definition: init.c:121
txt_error_gnksa_internal
constext txt_error_gnksa_internal[]
Definition: lang.c:197
tin_progname
char * tin_progname
Definition: init.c:105
active
struct t_group * active
Definition: memory.c:66
my_fprintf
#define my_fprintf
Definition: tcurses.h:170
need_resize
int need_resize
Definition: signal.c:107
Raw
void Raw(int state)
Definition: curses.c:624
t_attribute::maildir
char * maildir
Definition: tin.h:1565
GNKSA_UNKNOWN_DOMAIN
#define GNKSA_UNKNOWN_DOMAIN
Definition: extern.h:1582
GNKSA_RBRACKET_MISSING
#define GNKSA_RBRACKET_MISSING
Definition: extern.h:1590
gnksa_country_codes
static char gnksa_country_codes[26 *26]
Definition: policy.h:243
strfeditor
static int strfeditor(char *editor, int linenum, const char *filename, char *s, size_t maxsize, char *format)
Definition: misc.c:1329
txt_error_gnksa_ill_domain
constext txt_error_gnksa_ill_domain[]
Definition: lang.c:204
txt_inverse_off
constext txt_inverse_off[]
Definition: lang.c:581
strwidth
int strwidth(const char *str)
Definition: string.c:1043
my_strncpy
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:190
mark_offset
int mark_offset
Definition: screen.c:48
dir_name
void dir_name(const char *fullpath, char *dir)
Definition: misc.c:889
PRODUCT
#define PRODUCT
Definition: version.h:44
T_ARTNUM_PFMT
#define T_ARTNUM_PFMT
Definition: tin.h:227
homedir
char homedir[PATH_LEN]
Definition: init.c:78
txt_newsrc_saved
constext txt_newsrc_saved[]
Definition: lang.c:664
txt_shell_escape
constext txt_shell_escape[]
Definition: lang.c:1207
rfc1522_encode
char * rfc1522_encode(char *s, const char *charset, t_bool ismail)
Definition: rfc2047.c:854
t_config::thread_articles
int thread_articles
Definition: tinrc.h:152
DEFAULT_EDITOR
#define DEFAULT_EDITOR
Definition: tin.h:548
tin_version_info
int tin_version_info(FILE *fp)
Definition: misc.c:3871
giveup
void giveup(void)
Definition: main.c:1049
get_val
const char * get_val(const char *env, const char *def)
Definition: misc.c:355
PLURAL
#define PLURAL(x, y)
Definition: tin.h:1053
idna_decode
char * idna_decode(char *in)
Definition: misc.c:3758
copy_fp
t_bool copy_fp(FILE *fp_ip, FILE *fp_op)
Definition: misc.c:179
t_config::mm_local_charset
char mm_local_charset[LEN]
Definition: tinrc.h:103
GNKSA_ADDRTYPE_OLDSTYLE
#define GNKSA_ADDRTYPE_OLDSTYLE
Definition: extern.h:1605
strfmailer
int strfmailer(const char *mail_prog, char *subject, char *to, const char *filename, char *dest, size_t maxsize, const char *format)
Definition: misc.c:1795
t_config::mm_charset
char mm_charset[LEN]
Definition: tinrc.h:99
quote_wild
char * quote_wild(char *str)
Definition: misc.c:2304
cmd_line
t_bool cmd_line
Definition: init.c:128
buf
static char buf[16]
Definition: langinfo.c:50
ARRAY_SIZE
#define ARRAY_SIZE(array)
Definition: tin.h:2201
free_all_arrays
void free_all_arrays(void)
Definition: memory.c:233
file_size
long file_size(const char *file)
Definition: misc.c:2122
VERSION
#define VERSION
Definition: version.h:46
FreeIfNeeded
#define FreeIfNeeded(p)
Definition: tin.h:2203
gnksa_do_check_from
int gnksa_do_check_from(const char *from, char *address, char *realname)
Definition: misc.c:3484
txt_rename_error
constext txt_rename_error[]
Definition: lang.c:777
t_attribute::savedir
char * savedir
Definition: tin.h:1566
buffer_to_ascii
char * buffer_to_ascii(char *c)
Definition: misc.c:2610
txt_error_gnksa_begnum
constext txt_error_gnksa_begnum[]
Definition: lang.c:210
txt_error_gnksa_fqdn
constext txt_error_gnksa_fqdn[]
Definition: lang.c:206
_nc_visbuf
char * _nc_visbuf(const char *s)
Definition: trace.c:80
txt_warn_newsrc
constext txt_warn_newsrc[]
Definition: lang.c:972
GNKSA_ZERO_LENGTH_LOCAL_WORD
#define GNKSA_ZERO_LENGTH_LOCAL_WORD
Definition: extern.h:1594
THREAD_NONE
#define THREAD_NONE
Definition: tin.h:1127
hist_last
int hist_last[HIST_URL+1]
Definition: init.c:118
cCOLS
int cCOLS
Definition: curses.c:53
LEN
#define LEN
Definition: tin.h:854
strstr
char * strstr(const char *text, const char *pattern)
Definition: string.c:327
txt_error_gnksa_unk_domain
constext txt_error_gnksa_unk_domain[]
Definition: lang.c:205
t_cmdlineopts::savedir
char savedir[255]
Definition: tin.h:1470
GNKSA_SINGLE_DOMAIN
#define GNKSA_SINGLE_DOMAIN
Definition: extern.h:1579
gnksa_check_localpart
static int gnksa_check_localpart(const char *localpart)
Definition: misc.c:3326
txt_error_gnksa_lp_zero
constext txt_error_gnksa_lp_zero[]
Definition: lang.c:216
t_tintime::tv_nsec
long tv_nsec
Definition: tin.h:2036
t_tintime::tv_sec
time_t tv_sec
Definition: tin.h:2035
str_lwr
void str_lwr(char *str)
Definition: string.c:284
txt_error_gnksa_sgl_domain
constext txt_error_gnksa_sgl_domain[]
Definition: lang.c:202
currmenu
t_menu * currmenu
Definition: init.c:165
write_newsrc
signed long int write_newsrc(void)
Definition: newsrc.c:197
t_config::default_shell_command
char default_shell_command[LEN]
Definition: tinrc.h:92
parse_from
int parse_from(const char *from, char *address, char *realname)
Definition: misc.c:3586
cYes
@ cYes
Definition: tin.h:109
gnksa_check_from
int gnksa_check_from(char *from)
Definition: misc.c:3571
input_history
char * input_history[HIST_URL+1][15+1]
Definition: init.c:168
t_config::catchup_read_groups
t_bool catchup_read_groups
Definition: tinrc.h:210
GNKSA_INTERNAL_ERROR
#define GNKSA_INTERNAL_ERROR
Definition: extern.h:1572
draw_mark_selected
void draw_mark_selected(int i)
Definition: misc.c:4123
t_menu::redraw
void(* redraw)(void)
Definition: tin.h:2009
txt_error_gnksa_local_lit
constext txt_error_gnksa_local_lit[]
Definition: lang.c:212
PATH_ISPELL
#define PATH_ISPELL
Definition: tin.h:563
QUOTE_EMPTY
#define QUOTE_EMPTY
Definition: tin.h:1214
t_attribute::editor_format
char * editor_format
Definition: tin.h:1572
txt_more
constext txt_more[]
Definition: lang.c:651
cursoron
void cursoron(void)
Definition: curses.c:712
txt_catchup_group
constext txt_catchup_group[]
Definition: lang.c:137
is_mailbox
static t_bool is_mailbox
Definition: feed.c:50
regex_cache::re
pcre * re
Definition: tin.h:1918
show_inverse_video_status
void show_inverse_video_status(void)
Definition: misc.c:1073
read_newsrc_lines
signed long int read_newsrc_lines
Definition: main.c:51
gnksa_check_domain
static int gnksa_check_domain(char *domain)
Definition: misc.c:3221
my_fputs
#define my_fputs(str, stream)
Definition: tcurses.h:153
gnksa_legal_fqdn_chars
static char gnksa_legal_fqdn_chars[256]
Definition: misc.c:2735
EndWin
void EndWin(void)
Definition: curses.c:368
BlankIfNull
#define BlankIfNull(p)
Definition: tin.h:2206
process_charsets
void process_charsets(char **line, size_t *max_line_len, const char *network_charset, const char *local_charset, t_bool conv_tex2iso)
Definition: misc.c:2634
txt_error_gnksa_lparen
constext txt_error_gnksa_lparen[]
Definition: lang.c:199
SHOW_FROM_NAME
#define SHOW_FROM_NAME
Definition: tin.h:1143
txt_inverse_on
constext txt_inverse_on[]
Definition: lang.c:582
bool_not
#define bool_not(b)
Definition: bool.h:81
backup_file
t_bool backup_file(const char *filename, const char *backupname)
Definition: misc.c:213
strip_name
void strip_name(const char *from, char *address)
Definition: misc.c:2368
batch_mode
t_bool batch_mode
Definition: init.c:126
t_config::editor_format
char editor_format[PATH_LEN]
Definition: tinrc.h:68
strrstr
char * strrstr(const char *str, const char *pat)
Definition: string.c:787
NNTP_ERROR_EXIT
#define NNTP_ERROR_EXIT
Definition: tin.h:1281
eat_re
const char * eat_re(char *s, t_bool eat_was)
Definition: misc.c:957
tin_done
void tin_done(int ret, const char *fmt,...)
Definition: misc.c:557
t_cmdlineopts::args
unsigned int args
Definition: tin.h:1471
gnksa_dequote_plainphrase
static int gnksa_dequote_plainphrase(char *realname, char *decoded, int addrtype)
Definition: misc.c:2938
atoi
int atoi(const char *s)
INDEX2LNUM
#define INDEX2LNUM(i)
Definition: tin.h:1009
t_group::name
char * name
Definition: tin.h:1773
SIGDASHES
#define SIGDASHES
Definition: tin.h:743
GNKSA_LPAREN_MISSING
#define GNKSA_LPAREN_MISSING
Definition: extern.h:1575
InitWin
void InitWin(void)
Definition: curses.c:355
dbl_quote
@ dbl_quote
Definition: tin.h:1246
make_base_group_path
void make_base_group_path(const char *base_dir, const char *group_name, char *group_path, size_t group_path_len)
Definition: misc.c:2087
txt_shell_command
constext txt_shell_command[]
Definition: lang.c:1206
FALSE
#define FALSE
Definition: bool.h:70
txt_cannot_open
constext txt_cannot_open[]
Definition: lang.c:128
MAILDIR_NEW
#define MAILDIR_NEW
Definition: misc.c:909
STRCPY
#define STRCPY(dst, src)
Definition: tin.h:814
strfpath
int strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all)
Definition: misc.c:1701
debug
unsigned short debug
Definition: debug.c:51
txt_catchup_all_read_groups
constext txt_catchup_all_read_groups[]
Definition: lang.c:138
S_IRWXO
#define S_IRWXO
Definition: tin.h:2145
this_resp
int this_resp
Definition: page.c:71
toggle_inverse_video
void toggle_inverse_video(void)
Definition: misc.c:1058
verbose
int verbose
Definition: init.c:153
no_write
t_bool no_write
Definition: init.c:144
QUOTE_SIGS
#define QUOTE_SIGS
Definition: tin.h:1213
joinpath
void joinpath(char *result, size_t result_size, const char *dir, const char *file)
Definition: joinpath.c:50
grp_mark_read
void grp_mark_read(struct t_group *group, struct t_article *art)
Definition: newsrc.c:690
sgl_quote
@ sgl_quote
Definition: tin.h:1247
OK_NOTEXT
#define OK_NOTEXT
Definition: nntplib.h:104
GNKSA_ATSIGN_MISSING
#define GNKSA_ATSIGN_MISSING
Definition: extern.h:1577
HIST_SHELL_COMMAND
@ HIST_SHELL_COMMAND
Definition: extern.h:1553
rename_file
void rename_file(const char *old_filename, const char *new_filename)
Definition: misc.c:733
newsrc
char newsrc[PATH_LEN]
Definition: init.c:96
t_attribute::date_format
char * date_format
Definition: tin.h:1571
snprintf
#define snprintf
Definition: tin.h:2417
WIFEXITED
#define WIFEXITED(status)
Definition: tin.h:331
BACKUP_FILE_EXT
#define BACKUP_FILE_EXT
Definition: misc.c:370
GNKSA_ILLEGAL_LABEL_LENGTH
#define GNKSA_ILLEGAL_LABEL_LENGTH
Definition: extern.h:1585
get_cwd
void get_cwd(char *buf)
Definition: misc.c:2047
my_group
int * my_group
Definition: memory.c:64
txt_error_gnksa_lp_missing
constext txt_error_gnksa_lp_missing[]
Definition: lang.c:214
openartinfo::hdr
struct t_header hdr
Definition: rfc2046.h:185
t_artnum
long t_artnum
Definition: tin.h:226
error_message
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:184
disable_gnksa_domain_check
t_bool disable_gnksa_domain_check
Definition: init.c:131
show_subject
t_bool show_subject
Definition: thread.c:54
local_input_history_file
char local_input_history_file[PATH_LEN]
Definition: init.c:85
TIN_EDITOR_FMT_OFF
#define TIN_EDITOR_FMT_OFF
Definition: tin.h:2118
t_group::attribute
struct t_attribute * attribute
Definition: tin.h:1790
cleanup_tmp_files
void cleanup_tmp_files(void)
Definition: misc.c:2105
txt_error_gnksa_length
constext txt_error_gnksa_length[]
Definition: lang.c:208
pcre_version
const char * pcre_version(void)
Definition: pcre_version.c:79
StartInverse
void StartInverse(void)
Definition: curses.c:540
S_IRUSR
#define S_IRUSR
Definition: tin.h:2136
fmt_message
char * fmt_message(const char *fmt, va_list ap)
Definition: screen.c:73
txt_error_gnksa_rn_invalid
constext txt_error_gnksa_rn_invalid[]
Definition: lang.c:222
convert_iso2asc
void convert_iso2asc(char *iso, char **asc_buffer, size_t *max_line_len, int t)
Definition: charset.c:163
HIST_SIZE
#define HIST_SIZE
Definition: extern.h:1562
t_attribute::start_editor_offset
unsigned start_editor_offset
Definition: tin.h:1630
GNKSA_ADDRTYPE_ROUTE
#define GNKSA_ADDRTYPE_ROUTE
Definition: extern.h:1604
strip_was_regex
struct regex_cache strip_was_regex
read_input_history_file
void read_input_history_file(void)
Definition: misc.c:2188
gnksa_legal_localpart_chars
static char gnksa_legal_localpart_chars[256]
Definition: misc.c:2760
userid
char userid[PATH_LEN]
Definition: init.c:107
selmenu
t_menu selmenu
Definition: select.c:85
get_author
void get_author(t_bool thread, struct t_article *art, char *str, size_t len)
Definition: misc.c:1019
HIST_MAXNUM
#define HIST_MAXNUM
Definition: extern.h:1560
IS_LOCAL_CHARSET
#define IS_LOCAL_CHARSET(c)
Definition: tin.h:776
gnksa_split_from
int gnksa_split_from(const char *from, char *address, char *realname, int *addrtype)
Definition: misc.c:3358
txt_filesystem_full_backup
constext txt_filesystem_full_backup[]
Definition: lang.c:273
GNKSA_INVALID_REALNAME
#define GNKSA_INVALID_REALNAME
Definition: extern.h:1601
DIR_BUF
#define DIR_BUF
Definition: tin.h:380
S_ISREG
#define S_ISREG(m)
Definition: tin.h:2131
CURR_GROUP
#define CURR_GROUP
Definition: tin.h:1043
S_IFMT
#define S_IFMT
Definition: tin.h:2153
t_bool
unsigned t_bool
Definition: bool.h:77
errno
int errno
convert_tex2iso
void convert_tex2iso(char *from, char *to)
Definition: charset.c:263
t_group::read_during_session
t_bool read_during_session
Definition: tin.h:1783
gnksa_strerror
const char * gnksa_strerror(int errcode)
Definition: misc.c:2811
INTERACTIVE_NONE
@ INTERACTIVE_NONE
Definition: tin.h:1157
prompt_yn
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
TRUE
#define TRUE
Definition: bool.h:74
t_config::inverse_okay
t_bool inverse_okay
Definition: tinrc.h:216
GNKSA_ILLEGAL_UNQUOTED_CHAR
#define GNKSA_ILLEGAL_UNQUOTED_CHAR
Definition: extern.h:1596
HEADER_LEN
#define HEADER_LEN
Definition: tin.h:857
my_fflush
#define my_fflush(stream)
Definition: tcurses.h:172
txt_error_gnksa_inv_domain
constext txt_error_gnksa_inv_domain[]
Definition: lang.c:203
arts
struct t_article * arts
Definition: memory.c:69
prompt_continue
void prompt_continue(void)
Definition: prompt.c:774
hist_pos
int hist_pos[HIST_URL+1]
Definition: init.c:119
BLANK_PAGE_COLS
#define BLANK_PAGE_COLS
Definition: tin.h:939
my_strftime
size_t my_strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr)
Definition: strftime.c:64
make_group_path
void make_group_path(const char *name, char *path)
Definition: misc.c:2067
set_signal_catcher
void set_signal_catcher(int flag)
Definition: signal.c:526
sh_format
int sh_format(char *dst, size_t len, const char *fmt,...)
Definition: string.c:670
GNKSA_ILLEGAL_QUOTED_CHAR
#define GNKSA_ILLEGAL_QUOTED_CHAR
Definition: extern.h:1597
SHOW_FROM_ADDR
#define SHOW_FROM_ADDR
Definition: tin.h:1142
txt_error_gnksa_atsign
constext txt_error_gnksa_atsign[]
Definition: lang.c:201
unlink
#define unlink(file)
Definition: tin.h:384
strpbrk
char * strpbrk(const char *str1, const char *str2)
Definition: string.c:305
invoke_editor
t_bool invoke_editor(const char *filename, int lineno, struct t_group *group)
Definition: misc.c:372
DIRSEP
#define DIRSEP
Definition: tin.h:2104
NOTESLINES
int NOTESLINES
Definition: signal.c:111
t_attribute::show_author
unsigned show_author
Definition: tin.h:1634
escape_shell_meta
char * escape_shell_meta(const char *source, int quote_area)
Definition: misc.c:1727
GNKSA_RPAREN_MISSING
#define GNKSA_RPAREN_MISSING
Definition: extern.h:1576
strip_re_regex
struct regex_cache strip_re_regex
GNKSA_OK
#define GNKSA_OK
Definition: extern.h:1571
pgart
t_openartinfo pgart
Definition: page.c:63
do_shell_escape
void do_shell_escape(void)
Definition: misc.c:542
gnksa_domain_list
static const char * gnksa_domain_list[]
Definition: policy.h:278
ENV_VAR_SHELL
#define ENV_VAR_SHELL
Definition: tin.h:2117
CMDLINE_MAILDIR
#define CMDLINE_MAILDIR
Definition: tin.h:1092
clear_message
void clear_message(void)
Definition: screen.c:243
nntp_close
void nntp_close(t_bool send_no_quit)
Definition: nntplib.c:1723
t_config::draw_arrow
t_bool draw_arrow
Definition: tinrc.h:212
strfquote
int strfquote(const char *group, int respnum, char *s, size_t maxsize, char *format)
Definition: misc.c:1170
prompt_string
t_bool prompt_string(const char *prompt, char *buf, int which_hist)
Definition: prompt.c:93
my_malloc
#define my_malloc(size)
Definition: tin.h:2196