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