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