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