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

misc.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : misc.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 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 */
81static char *strfpath_cp(char *str, char *tbuf, const char *endp);
82static int _strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all);
83static int gnksa_check_domain(char *domain);
84static int gnksa_check_domain_literal(char *domain);
85static int gnksa_check_localpart(const char *localpart);
86static int gnksa_dequote_plainphrase(char *realname, char *decoded, int addrtype);
87static int strfeditor(char *editor, int linenum, const char *filename, char *s, size_t maxsize, char *format);
88static 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 */
100char *
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 */
119void
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
141void
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 */
178t_bool
180 FILE *fp_ip,
181 FILE *fp_op)
182{
183 char buf[8192];
184 size_t have, sent;
185
186 errno = 0; /* To check errno after write, clear it here */
187
188 while ((have = fread(buf, 1, sizeof(buf), fp_ip)) != 0) {
189 sent = fwrite(buf, 1, have, fp_op);
190 if (sent != have) {
191 TRACE(("copy_fp wrote %d of %d:{%.*s}", sent, have, (int) sent, buf));
192 if (errno && errno != EPIPE) /* not a broken pipe => more serious error */
194 return FALSE;
195 }
196 TRACE(("copy_fp wrote %d:{%.*s}", sent, (int) sent, buf));
197 }
198 return TRUE;
199}
200
201
202/*
203 * backup_file(filename, backupname)
204 *
205 * try to backup filename as backupname. on success backupname has the same
206 * permissions as filename.
207 *
208 * return codes:
209 * TRUE = backup complete or source file was missing
210 * FALSE = backup failed
211 */
212t_bool
214 const char *filename,
215 const char *backupname)
216{
217 FILE *fp_in, *fp_out;
218 int fd;
219 mode_t mode = (mode_t) (S_IRUSR|S_IWUSR);
220 struct stat statbuf;
221 t_bool ret = FALSE;
222
223 if ((fp_in = fopen(filename, "r")) == NULL) /* a missing sourcefile is not a real bug */
224 return TRUE;
225
226 /* don't follow links when writing backup files - do we really want this? */
227 unlink(backupname);
228 if ((fp_out = fopen(backupname, "w")) == NULL) {
229 fclose(fp_in);
230 return ret;
231 }
232
233 if ((fd = fileno(fp_in)) != -1) {
234 if (!fstat(fd, &statbuf))
235 mode = statbuf.st_mode;
236 }
237
238 ret = copy_fp(fp_in, fp_out);
239
240 if ((fd = fileno(fp_out)) != -1)
241#ifdef HAVE_FCHMOD
242 fchmod(fd, mode);
243#else
244# ifdef HAVE_CHMOD
245 chmod(backupname, mode);
246# endif /* HAVE_CHMOD */
247#endif /* HAVE_FCHMOD */
248
249 fclose(fp_out);
250 fclose(fp_in);
251 return ret;
252}
253
254
255/*
256 * copy the body of articles with given file pointers,
257 * prefix (= quote_chars), initials of the articles author
258 * with_sig is set if the signature should be quoted
259 *
260 * TODO: rewrite from scratch, the code is awful.
261 */
262void
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 */
360const char *
362 const char *env, /* Environment variable we're looking for */
363 const char *def) /* Default value if no environ value found */
364{
365 const char *ptr;
366
367 return ((ptr = getenv(env)) != NULL ? ptr : def);
368}
369
370
371/*
372 * IMHO it's not tins job to take care about dumb editor backupfiles
373 * otherwise BACKUP_FILE_EXT should be configurable via 'M'enu
374 */
375#define BACKUP_FILE_EXT ".b"
376t_bool
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
420t_bool
421invoke_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) {
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
495void
497 void)
498{
499 char *p, *tmp;
500 char shell[LEN];
501
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)
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);
525
526 (void) invoke_cmd(p);
527
528# ifndef USE_CURSES
529 EndWin();
530 Raw(FALSE);
531# endif /* !USE_CURSES */
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 */
546void
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
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
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 */
594 for (i = 0; i < selmenu.max; i++) {
595 group = &active[my_group[i]];
596 if (group->read_during_session) {
597 if (ask) {
599 ask = FALSE;
600 tinrc.thread_articles = THREAD_NONE; /* speeds up index loading */
601 } else
602 break;
603 }
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)
621 break;
622 }
623
624 if (wrote_newsrc_lines < read_newsrc_lines) {
625 /* FIXME: prompt for retry? (i.e. remove break) */
627 (read_newsrc_lines - wrote_newsrc_lines),
628 PLURAL(read_newsrc_lines - wrote_newsrc_lines, txt_group),
630 if (!batch_mode)
632 break;
633 }
634
635 if (!batch_mode) {
636 if (prompt_yn(_(txt_newsrc_again), TRUE) <= 0)
637 break;
638 }
639 }
640
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
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 */
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
716int
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
741void
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 */
812t_bool
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 }
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
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)
850
851 return success;
852#endif /* IGNORE_SYSTEM_STATUS */
853}
854
855
856/*
857 * grab file portion of fullpath
858 */
859void
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 */
880void
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"
902t_bool
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 */
948const char *
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)
977int
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 */
1010void
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
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
1049void
1051 void)
1052{
1055#ifndef USE_INVERSE_HACK
1056# if 0
1057 else
1059# endif /* 0 */
1060#endif /* !USE_INVERSE_HACK */
1061}
1062
1063
1064void
1066 void)
1067{
1069}
1070
1071
1072#ifdef HAVE_COLOR
1073t_bool
1074toggle_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
1103void
1104show_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 */
1118void
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 */
1167int
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))) {
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 */
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 }
1311out:
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 */
1326static int
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 }
1422out:
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 */
1436static char *
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 */
1481static int
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;
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 */
1698int
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 */
1724char *
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 */
1792int
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 */
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 */
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 */
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 }
1980out:
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 */
1992int
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
2045void
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 */
2077void
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 */
2097void
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 */
2115void
2117 void)
2118{
2119 /*
2120 * only required if update_index == TRUE, but update_index is
2121 * unknown here
2122 */
2123 if (batch_mode)
2125}
2126
2127
2128/*
2129 * returns filesize in bytes
2130 * -1 in case of an error (file not found, or !S_IFREG)
2131 */
2132long /* we use long here as off_t might be unsigned on some systems */
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 */
2146long /* we use long (not time_t) here */
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 */
2159char *
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
2198void
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)
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
2252static void
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 */
2269
2270 if ((fp = fopen(file_tmp, "w")) == NULL) {
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)) {
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
2316
2317 umask(mask);
2318 free(file_tmp); /* free memory for tmp-filename */
2319}
2320
2321
2322/*
2323 * quotes wildcards * ? \ [ ] with \
2324 */
2325char *
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 */
2361char *
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 */
2389void
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
2401static t_bool
2402buffer_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] */
2570t_bool
2571buffer_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
2631char *
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 */
2655void
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 */
2757static 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 */
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 */
2808static 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 */
2832const char *
2834 int errcode)
2835{
2836 const char *message;
2837
2838 switch (errcode) {
2840 message = _(txt_error_gnksa_internal);
2841 break;
2842
2844 message = txt_error_gnksa_langle;
2845 break;
2846
2848 message = _(txt_error_gnksa_lparen);
2849 break;
2850
2852 message = _(txt_error_gnksa_rparen);
2853 break;
2854
2856 message = _(txt_error_gnksa_atsign);
2857 break;
2858
2860 message = _(txt_error_gnksa_sgl_domain);
2861 break;
2862
2864 message = _(txt_error_gnksa_inv_domain);
2865 break;
2866
2868 message = _(txt_error_gnksa_ill_domain);
2869 break;
2870
2872 message = _(txt_error_gnksa_unk_domain);
2873 break;
2874
2876 message = _(txt_error_gnksa_fqdn);
2877 break;
2878
2880 message = _(txt_error_gnksa_zero);
2881 break;
2882
2884 message = _(txt_error_gnksa_length);
2885 break;
2886
2888 message = _(txt_error_gnksa_hyphen);
2889 break;
2890
2892 message = _(txt_error_gnksa_begnum);
2893 break;
2894
2896 message = _(txt_error_gnksa_bad_lit);
2897 break;
2898
2900 message = _(txt_error_gnksa_local_lit);
2901 break;
2902
2904 message = _(txt_error_gnksa_rbracket);
2905 break;
2906
2908 message = _(txt_error_gnksa_lp_missing);
2909 break;
2910
2912 message = _(txt_error_gnksa_lp_invalid);
2913 break;
2914
2916 message = _(txt_error_gnksa_lp_zero);
2917 break;
2918
2920 message = _(txt_error_gnksa_rn_unq);
2921 break;
2922
2924 message = _(txt_error_gnksa_rn_qtd);
2925 break;
2926
2928 message = _(txt_error_gnksa_rn_enc);
2929 break;
2930
2932 message = _(txt_error_gnksa_rn_encsyn);
2933 break;
2934
2936 message = _(txt_error_gnksa_rn_paren);
2937 break;
2938
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 */
2959static int
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)
2972
2973 /* initialize state machine */
2974 switch (addrtype) {
2976 initialstate = 0;
2977 break;
2978
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])
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 ']':
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 '\\':
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 '=':
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 '=':
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
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 '\\':
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 */
3201static int
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)
3217
3218 if (term != ']')
3220
3221 } else { /* literal not bracketed */
3222#ifdef REQUIRE_BRACKETS_IN_DOMAIN_LITERAL
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)
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))
3235
3236 /* check for private ip or localhost - see RFC 5735, RFC 5737 */
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 */
3247
3248 return GNKSA_OK;
3249}
3250
3251
3252static int
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) == '.'))
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 }
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)
3329
3330 if (*(aux + 1) == '.')
3332
3333 if ((*(aux + 1) == '-') || (*(aux - 1) == '-'))
3335
3336#ifdef ENFORCE_RFC1034
3337 if ((*(aux + 1) >= '0') && (*(aux + 1) <= '9'))
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])
3348 }
3349
3350 return GNKSA_OK;
3351}
3352
3353
3354/*
3355 * check localpart of address
3356 */
3357static int
3359 const char *localpart)
3360{
3361 const char *aux;
3362
3363 /* no localpart at all? */
3364 if (!*localpart)
3366
3367 /* check for zero-length domain parts */
3368 if ((*localpart == '.') || (*(localpart + strlen(localpart) - 1) == '.'))
3370
3371 for (aux = localpart; *aux; aux++) {
3372 if ((*aux == '.') && (*(aux + 1) == '.'))
3374 }
3375
3376 /* check for illegal characters in FQDN */
3377 for (aux = localpart; *aux; aux++) {
3378 if (!gnksa_legal_localpart_chars[(unsigned char) *aux])
3380 }
3381
3382 return GNKSA_OK;
3383}
3384
3385
3386/*
3387 * split mail address into realname and address parts
3388 */
3389int
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)
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))
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 */
3528int
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 */
3615int
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 */
3630int
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 */
3643char *
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 */
3680char *
3681utf8_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
3809char *
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
3926int
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"),
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
4178void
4180 int i)
4181{
4182#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
4183 int j;
4184#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
4185
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 */
4195#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
4196 EndInverse(); /* ToggleInverse() doesn't work correct with ncurses4.x */
4197}
4198
4199
4200int
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(&gt, 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 */
4235static t_bool
4236stat_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 */
unsigned t_bool
Definition: bool.h:77
#define bool_not(b)
Definition: bool.h:81
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
static t_openartinfo * art
Definition: cook.c:78
#define DEBUG_MISC
Definition: debug.h:54
constext txt_error_gnksa_fqdn[]
Definition: lang.c:207
int this_resp
Definition: page.c:71
char local_input_history_file[PATH_LEN]
Definition: init.c:85
#define GNKSA_ZERO_LENGTH_LABEL
Definition: extern.h:1604
constext txt_inverse_on[]
Definition: lang.c:591
constext txt_shell_command[]
Definition: lang.c:1219
@ HIST_SHELL_COMMAND
Definition: extern.h:1573
#define GNKSA_ILLEGAL_PAREN_CHAR
Definition: extern.h:1620
int verbose
Definition: init.c:154
constext txt_newsrc_again[]
Definition: lang.c:673
constext txt_shell_escape[]
Definition: lang.c:1220
#define GNKSA_INVALID_LOCALPART
Definition: extern.h:1613
constext txt_warn_newsrc[]
Definition: lang.c:985
int hist_pos[HIST_URL+1]
Definition: init.c:120
constext txt_newsrc_saved[]
Definition: lang.c:675
char * input_history[HIST_URL+1][15+1]
Definition: init.c:169
constext txt_error_gnksa_rn_invalid[]
Definition: lang.c:223
#define GNKSA_BAD_DOMAIN_LITERAL
Definition: extern.h:1608
constext txt_filesystem_full_backup[]
Definition: lang.c:276
int * my_group
Definition: memory.c:64
t_openartinfo pgart
Definition: page.c:63
constext txt_error_gnksa_internal[]
Definition: lang.c:198
constext txt_error_gnksa_ill_domain[]
Definition: lang.c:205
constext txt_rename_error[]
Definition: lang.c:790
#define GNKSA_SINGLE_DOMAIN
Definition: extern.h:1599
constext txt_error_gnksa_inv_domain[]
Definition: lang.c:204
char homedir[PATH_LEN]
Definition: init.c:78
struct regex_cache strip_re_regex
signed long int read_newsrc_lines
Definition: main.c:51
#define GNKSA_LANGLE_MISSING
Definition: extern.h:1593
#define GNKSA_UNKNOWN_DOMAIN
Definition: extern.h:1602
t_bool cmd_line
Definition: init.c:129
#define GNKSA_ILLEGAL_LABEL_HYPHEN
Definition: extern.h:1606
#define GNKSA_ZERO_LENGTH_LOCAL_WORD
Definition: extern.h:1614
constext txt_error_gnksa_rn_unq[]
Definition: lang.c:218
t_menu selmenu
Definition: select.c:84
pid_t process_id
Definition: init.c:125
constext txt_error_gnksa_rn_encsyn[]
Definition: lang.c:221
#define GNKSA_INTERNAL_ERROR
Definition: extern.h:1591
#define GNKSA_BAD_ENCODE_SYNTAX
Definition: extern.h:1619
#define GNKSA_ILLEGAL_LABEL_BEGNUM
Definition: extern.h:1607
struct t_article * arts
Definition: memory.c:69
constext txt_error_gnksa_hyphen[]
Definition: lang.c:210
constext txt_error_gnksa_rn_enc[]
Definition: lang.c:220
#define HIST_MAXNUM
Definition: extern.h:1579
#define GNKSA_OK
Definition: extern.h:1590
#define GNKSA_LPAREN_MISSING
Definition: extern.h:1594
int art_mark_width
Definition: init.c:118
constext txt_error_gnksa_local_lit[]
Definition: lang.c:213
constext txt_error_gnksa_rparen[]
Definition: lang.c:201
constext txt_error_gnksa_rn_paren[]
Definition: lang.c:222
constext txt_error_gnksa_unk_domain[]
Definition: lang.c:206
int need_resize
Definition: signal.c:107
#define GNKSA_ILLEGAL_QUOTED_CHAR
Definition: extern.h:1617
#define GNKSA_INVALID_DOMAIN
Definition: extern.h:1600
#define GNKSA_ILLEGAL_LABEL_LENGTH
Definition: extern.h:1605
constext txt_error_gnksa_lparen[]
Definition: lang.c:200
constext txt_error_gnksa_zero[]
Definition: lang.c:208
int system_status
Definition: init.c:122
constext txt_catchup_all_read_groups[]
Definition: lang.c:138
constext txt_group[]
Definition: lang.c:1230
constext txt_error_copy_fp[]
Definition: lang.c:193
constext txt_error_gnksa_lp_invalid[]
Definition: lang.c:216
constext txt_cannot_open[]
Definition: lang.c:128
#define GNKSA_ADDRTYPE_ROUTE
Definition: extern.h:1625
constext txt_error_gnksa_atsign[]
Definition: lang.c:202
#define GNKSA_LOCALPART_MISSING
Definition: extern.h:1612
#define HIST_SIZE
Definition: extern.h:1581
char userid[PATH_LEN]
Definition: init.c:107
#define GNKSA_INVALID_REALNAME
Definition: extern.h:1621
constext txt_error_gnksa_lp_missing[]
Definition: lang.c:215
t_bool show_subject
Definition: thread.c:54
int signal_context
Definition: signal.c:105
constext txt_filesystem_full[]
Definition: lang.c:275
int mark_offset
Definition: screen.c:48
char lock_file[PATH_LEN]
Definition: init.c:88
#define GNKSA_INVALID_FQDN_CHAR
Definition: extern.h:1603
t_bool disable_gnksa_domain_check
Definition: init.c:132
constext txt_error_gnksa_langle[]
Definition: lang.c:199
t_menu * currmenu
Definition: init.c:166
constext txt_command_failed[]
Definition: lang.c:150
constext txt_error_gnksa_sgl_domain[]
Definition: lang.c:203
char newsrc[PATH_LEN]
Definition: init.c:96
#define GNKSA_ILLEGAL_UNQUOTED_CHAR
Definition: extern.h:1616
constext txt_error_gnksa_rbracket[]
Definition: lang.c:214
constext txt_catchup_group[]
Definition: lang.c:137
#define GNKSA_ADDRTYPE_OLDSTYLE
Definition: extern.h:1626
#define GNKSA_RBRACKET_MISSING
Definition: extern.h:1610
char * tin_progname
Definition: init.c:105
constext txt_error_gnksa_rn_qtd[]
Definition: lang.c:219
constext txt_error_gnksa_lp_zero[]
Definition: lang.c:217
constext txt_reading_input_history_file[]
Definition: lang.c:778
#define GNKSA_ILLEGAL_ENCODED_CHAR
Definition: extern.h:1618
int hist_last[HIST_URL+1]
Definition: init.c:119
struct t_group * curr_group
Definition: group.c:55
struct t_config tinrc
Definition: init.c:192
#define GNKSA_ILLEGAL_DOMAIN
Definition: extern.h:1601
unsigned short debug
Definition: debug.c:51
int iso2asc_supported
Definition: init.c:121
#define GNKSA_RPAREN_MISSING
Definition: extern.h:1595
constext txt_inverse_off[]
Definition: lang.c:590
constext txt_error_gnksa_begnum[]
Definition: lang.c:211
t_bool no_write
Definition: init.c:145
struct t_cmdlineopts cmdline
Definition: init.c:190
#define GNKSA_LOCAL_DOMAIN_LITERAL
Definition: extern.h:1609
t_bool read_news_via_nntp
Definition: init.c:151
constext txt_error_asfail[]
Definition: lang.c:182
#define GNKSA_RANGLE_MISSING
Definition: extern.h:1597
constext txt_error_gnksa_length[]
Definition: lang.c:209
#define GNKSA_ATSIGN_MISSING
Definition: extern.h:1596
struct t_group * active
Definition: memory.c:66
struct regex_cache strip_was_regex
#define GNKSA_MISSING_REALNAME
Definition: extern.h:1622
constext txt_error_gnksa_bad_lit[]
Definition: lang.c:212
t_bool batch_mode
Definition: init.c:127
static t_bool is_mailbox
Definition: feed.c:50
static char buf[16]
Definition: langinfo.c:50
void dir_name(const char *fullpath, char *dir)
Definition: misc.c:881
void read_input_history_file(void)
Definition: misc.c:2199
static char gnksa_legal_fqdn_chars[256]
Definition: misc.c:2757
void make_base_group_path(const char *base_dir, const char *group_name, char *group_path, size_t group_path_len)
Definition: misc.c:2098
char * idna_decode(char *in)
Definition: misc.c:3810
const char * gnksa_strerror(int errcode)
Definition: misc.c:2833
void asfail(const char *file, int line, const char *cond)
Definition: misc.c:142
int strfmailer(const char *mail_prog, char *subject, char *to, const char *filename, char *dest, size_t maxsize, const char *format)
Definition: misc.c:1793
void tin_done(int ret, const char *fmt,...)
Definition: misc.c:562
void make_group_path(const char *name, char *path)
Definition: misc.c:2078
char * quote_wild_whitespace(char *str)
Definition: misc.c:2362
static int _strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all)
Definition: misc.c:1482
int get_initials(struct t_article *art, char *s, int maxsize)
Definition: misc.c:1993
static void write_input_history_file(void)
Definition: misc.c:2253
int tin_version_info(FILE *fp)
Definition: misc.c:3927
void process_charsets(char **line, size_t *max_line_len, const char *network_charset, const char *local_charset, t_bool conv_tex2iso)
Definition: misc.c:2656
void base_name(const char *fullpath, char *file)
Definition: misc.c:860
#define MAILDIR_NEW
Definition: misc.c:901
const char * eat_re(char *s, t_bool eat_was)
Definition: misc.c:949
void append_file(char *old_filename, char *new_filename)
Definition: misc.c:120
static char gnksa_legal_localpart_chars[256]
Definition: misc.c:2782
void copy_body(FILE *fp_ip, FILE *fp_op, char *prefix, char *initl, t_bool raw_data)
Definition: misc.c:263
void get_author(t_bool thread, struct t_article *art, char *str, size_t len)
Definition: misc.c:1011
char * strip_line(char *line)
Definition: misc.c:3644
static char * strfpath_cp(char *str, char *tbuf, const char *endp)
Definition: misc.c:1437
const char * get_val(const char *env, const char *def)
Definition: misc.c:361
void do_shell_escape(void)
Definition: misc.c:547
static int gnksa_dequote_plainphrase(char *realname, char *decoded, int addrtype)
Definition: misc.c:2960
long file_mtime(const char *file)
Definition: misc.c:2147
t_bool mail_check(const char *mailbox_name)
Definition: misc.c:903
static int gnksa_check_domain(char *domain)
Definition: misc.c:3253
void toggle_inverse_video(void)
Definition: misc.c:1050
int gnksa_check_from(char *from)
Definition: misc.c:3616
t_bool invoke_cmd(const char *nam)
Definition: misc.c:813
void show_inverse_video_status(void)
Definition: misc.c:1065
t_bool copy_fp(FILE *fp_ip, FILE *fp_op)
Definition: misc.c:179
int tin_gettime(struct t_tintime *tt)
Definition: misc.c:4201
void shell_escape(void)
Definition: misc.c:496
void create_index_lock_file(char *the_lock_file)
Definition: misc.c:1119
t_bool backup_file(const char *filename, const char *backupname)
Definition: misc.c:213
void cleanup_tmp_files(void)
Definition: misc.c:2116
int gnksa_do_check_from(const char *from, char *address, char *realname)
Definition: misc.c:3529
void draw_mark_selected(int i)
Definition: misc.c:4179
t_bool invoke_editor(const char *filename, int lineno, struct t_group *group)
Definition: misc.c:377
void rename_file(const char *old_filename, const char *new_filename)
Definition: misc.c:742
int my_mkdir(char *path, mode_t mode)
Definition: misc.c:717
int gnksa_split_from(const char *from, char *address, char *realname, int *addrtype)
Definition: misc.c:3390
static int gnksa_check_domain_literal(char *domain)
Definition: misc.c:3202
int strfquote(const char *group, int respnum, char *s, size_t maxsize, char *format)
Definition: misc.c:1168
char * random_organization(char *in_org)
Definition: misc.c:2160
char * quote_wild(char *str)
Definition: misc.c:2326
void get_cwd(char *buf)
Definition: misc.c:2046
int strfpath(const char *format, char *str, size_t maxsize, struct t_group *group, t_bool expand_all)
Definition: misc.c:1699
static int strfeditor(char *editor, int linenum, const char *filename, char *s, size_t maxsize, char *format)
Definition: misc.c:1327
static int gnksa_check_localpart(const char *localpart)
Definition: misc.c:3358
char * escape_shell_meta(const char *source, int quote_area)
Definition: misc.c:1725
#define BACKUP_FILE_EXT
Definition: misc.c:375
char * buffer_to_ascii(char *c)
Definition: misc.c:2632
void strip_name(const char *from, char *address)
Definition: misc.c:2390
int my_isprint(int c)
Definition: misc.c:978
static char gnksa_legal_realname_chars[256]
Definition: misc.c:2808
char * get_tmpfilename(const char *filename)
Definition: misc.c:101
long file_size(const char *file)
Definition: misc.c:2133
int parse_from(const char *from, char *address, char *realname)
Definition: misc.c:3631
#define NNTP_STRLEN
Definition: nntplib.h:155
#define OK_NOTEXT
Definition: nntplib.h:104
int errno
static const char * gnksa_domain_list[]
Definition: policy.h:278
static char gnksa_country_codes[26 *26]
Definition: policy.h:243
void grp_mark_read(struct t_group *group, struct t_article *art)
Definition: newsrc.c:724
RETSIGTYPE(*)(SIG_ARGS) sigdisp(int signum, RETSIGTYPE(*func)(SIG_ARGS))
char * strpbrk(const char *str1, const char *str2)
Definition: string.c:312
void center_line(int line, t_bool inverse, const char *str)
Definition: screen.c:298
void prompt_continue(void)
Definition: prompt.c:803
void StartInverse(void)
Definition: curses.c:540
void convert_iso2asc(char *iso, char **asc_buffer, size_t *max_line_len, int t)
Definition: charset.c:170
int atoi(const char *s)
size_t my_strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr)
Definition: strftime.c:64
#define strrstr(s, p)
Definition: proto.h:704
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:224
void perror_message(const char *fmt,...)
Definition: screen.c:260
char * rfc1522_encode(char *s, const char *charset, t_bool ismail)
Definition: rfc2047.c:855
char * strstr(const char *text, const char *pattern)
Definition: string.c:334
int sh_format(char *dst, size_t len, const char *fmt,...)
Definition: string.c:677
void ClearScreen(void)
Definition: curses.c:410
t_bool prompt_string(const char *prompt, char *buf, int which_hist)
Definition: prompt.c:93
void info_message(const char *fmt,...)
Definition: screen.c:102
void str_lwr(char *str)
Definition: string.c:291
void EndWin(void)
Definition: curses.c:368
void free_all_arrays(void)
Definition: memory.c:237
void set_signal_catcher(int flag)
Definition: signal.c:527
void joinpath(char *result, size_t result_size, const char *dir, const char *file)
Definition: joinpath.c:50
char * my_strdup(const char *str)
Definition: string.c:139
void MoveCursor(int row, int col)
Definition: curses.c:441
void Raw(int state)
Definition: curses.c:624
void InitWin(void)
Definition: curses.c:355
char * fmt_message(const char *fmt, va_list ap)
Definition: screen.c:73
signed long int write_newsrc(void)
Definition: newsrc.c:201
void cursoron(void)
Definition: curses.c:712
void wait_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:133
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:196
void nntp_close(t_bool send_no_quit)
Definition: nntplib.c:1724
int strcasecmp(const char *p, const char *q)
Definition: string.c:475
void convert_tex2iso(char *from, char *to)
Definition: charset.c:270
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
char * fmt_string(const char *fmt,...)
Definition: string.c:1386
#define strerror(n)
Definition: proto.h:700
_Noreturn void giveup(void)
Definition: main.c:1058
void EndInverse(void)
Definition: curses.c:564
state
Definition: save.c:56
int code
Definition: signal.c:116
const char * name
Definition: signal.c:117
struct t_header hdr
Definition: rfc2046.h:185
pcre_extra * extra
Definition: tin.h:1963
pcre * re
Definition: tin.h:1962
Definition: tin.h:1533
char * maildir
Definition: tin.h:1613
char * date_format
Definition: tin.h:1619
char * savedir
Definition: tin.h:1614
char * editor_format
Definition: tin.h:1620
unsigned show_author
Definition: tin.h:1680
char savedir[255]
Definition: tin.h:1502
unsigned int args
Definition: tin.h:1503
char maildir[255]
Definition: tin.h:1500
t_bool inverse_okay
Definition: tinrc.h:227
t_bool draw_arrow
Definition: tinrc.h:223
t_bool catchup_read_groups
Definition: tinrc.h:221
char art_marked_selected
Definition: tinrc.h:73
int quote_style
Definition: tinrc.h:241
int interactive_mailer
Definition: tinrc.h:266
char default_shell_command[LEN]
Definition: tinrc.h:104
int thread_articles
Definition: tinrc.h:164
int wildcard
Definition: tinrc.h:167
char mm_charset[LEN]
Definition: tinrc.h:111
char mm_local_charset[LEN]
Definition: tinrc.h:115
char editor_format[PATH_LEN]
Definition: tinrc.h:80
Definition: tin.h:1816
t_bool read_during_session
Definition: tin.h:1827
struct t_attribute * attribute
Definition: tin.h:1834
char * name
Definition: tin.h:1817
char * date
Definition: rfc2046.h:132
char * messageid
Definition: rfc2046.h:137
void(* redraw)(void)
Definition: tin.h:2059
int max
Definition: tin.h:2057
time_t tv_sec
Definition: tin.h:2085
long tv_nsec
Definition: tin.h:2086
#define my_flush()
Definition: tcurses.h:177
#define my_fprintf
Definition: tcurses.h:176
#define cCRLF
Definition: tcurses.h:156
#define my_fflush(stream)
Definition: tcurses.h:178
#define my_fputs(str, stream)
Definition: tcurses.h:159
#define my_fputc(ch, stream)
Definition: tcurses.h:158
#define PLURAL(x, y)
Definition: tin.h:1064
#define LEN
Definition: tin.h:860
long t_artnum
Definition: tin.h:229
#define CMDLINE_MAILDIR
Definition: tin.h:1103
#define THREAD_NONE
Definition: tin.h:1139
#define SHOW_FROM_NAME
Definition: tin.h:1155
#define S_IRWXG
Definition: tin.h:2189
@ cMain
Definition: tin.h:107
#define TIN_EDITOR_FMT
Definition: tin.h:2168
#define IS_LOCAL_CHARSET(c)
Definition: tin.h:782
#define QUOTE_EMPTY
Definition: tin.h:1226
#define ENV_VAR_SHELL
Definition: tin.h:2167
#define DIRSEP
Definition: tin.h:2154
#define STRCPY(dst, src)
Definition: tin.h:820
#define OLDNEWSRC_FILE
Definition: tin.h:738
#define INDEX2LNUM(i)
Definition: tin.h:1020
#define EOF
Definition: tin.h:2516
#define my_malloc(size)
Definition: tin.h:2245
#define unlink(file)
Definition: tin.h:387
#define S_IRWXO
Definition: tin.h:2194
#define NNTP_ERROR_EXIT
Definition: tin.h:1306
#define ARRAY_SIZE(array)
Definition: tin.h:2250
#define FreeIfNeeded(p)
Definition: tin.h:2252
#define CURR_GROUP
Definition: tin.h:1054
#define WEXITSTATUS(status)
Definition: tin.h:330
#define SHOW_FROM_ADDR
Definition: tin.h:1154
#define SHOW_FROM_NONE
Definition: tin.h:1153
#define S_IRUSR
Definition: tin.h:2185
#define PATH_ISPELL
Definition: tin.h:566
#define _(Text)
Definition: tin.h:94
#define QUOTE_SIGS
Definition: tin.h:1225
@ sgl_quote
Definition: tin.h:1259
@ dbl_quote
Definition: tin.h:1258
@ no_quote
Definition: tin.h:1257
#define CMDLINE_SAVEDIR
Definition: tin.h:1105
#define PATH_LEN
Definition: tin.h:843
@ INTERACTIVE_NONE
Definition: tin.h:1169
@ cYes
Definition: tin.h:109
#define snprintf
Definition: tin.h:2464
#define WIFEXITED(status)
Definition: tin.h:334
#define SIGDASHES
Definition: tin.h:749
#define GROUP_TYPE_NEWS
Definition: tin.h:1070
#define S_IFMT
Definition: tin.h:2202
#define HEADER_LEN
Definition: tin.h:863
#define T_ARTNUM_PFMT
Definition: tin.h:230
#define INDEX_TOP
Definition: tin.h:1019
#define S_ISREG(m)
Definition: tin.h:2180
#define QUOTE_COMPRESS
Definition: tin.h:1224
#define my_realloc(ptr, size)
Definition: tin.h:2247
#define S_IWUSR
Definition: tin.h:2186
#define DEFAULT_EDITOR
Definition: tin.h:551
#define DIR_BUF
Definition: tin.h:383
#define CLOSEDIR(DIR)
Definition: tin.h:2398
#define BlankIfNull(p)
Definition: tin.h:2255
#define _Noreturn
Definition: tin.h:2495
#define SHOW_FROM_BOTH
Definition: tin.h:1156
char * _nc_visbuf(const char *s)
Definition: trace.c:80
#define TRACE(p)
Definition: trace.h:65
#define VERSION
Definition: version.h:46
#define PRODUCT
Definition: version.h:44
#define RELEASENAME
Definition: version.h:49
#define RELEASEDATE
Definition: version.h:48