"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.2/src/mail.c" (9 Dec 2022, 17179 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "mail.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
2.6.1_vs_2.6.2.
1 /*
2 * Project : tin - a Usenet reader
3 * Module : mail.c
4 * Author : I. Lea
5 * Created : 1992-10-02
6 * Updated : 2022-02-19
7 * Notes : Mail handling routines for creating pseudo newsgroups
8 *
9 * Copyright (c) 1992-2023 Iain Lea <iain@bricbrac.de>
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 TCURSES_H
45 # include "tcurses.h"
46 #endif /* !TCURSES_H */
47 #ifdef NNTP_ABLE
48 # ifndef TNNTP_H
49 # include "tnntp.h"
50 # endif /* !TNNTP_H */
51 #endif /* NNTP_ABLE */
52 /*
53 * local prototypes
54 */
55 static FILE *open_newsgroups_fp(void);
56 static void read_groups_descriptions(FILE *fp, FILE *fp_save);
57 static void read_newsgroups_file(t_bool verb);
58 #ifdef HAVE_MH_MAIL_HANDLING
59 static FILE *open_mail_active_fp(const char *mode);
60 static FILE *open_mailgroups_fp(void);
61 static void read_mailgroups_file(t_bool verb);
62 #endif /* HAVE_MH_MAIL_HANDLING */
63
64
65 #ifdef HAVE_MH_MAIL_HANDLING
66 /*
67 * Open the mail active file locally
68 */
69 static FILE *
70 open_mail_active_fp(
71 const char *mode)
72 {
73 return fopen(mail_active_file, mode);
74 }
75
76
77 /*
78 * Open mail groups description file locally
79 */
80 static FILE *
81 open_mailgroups_fp(
82 void)
83 {
84 return fopen(mailgroups_file, "r");
85 }
86
87
88 /*
89 * Load the mail active file into active[]
90 */
91 void
92 read_mail_active_file(
93 void)
94 {
95 FILE *fp;
96 char *buf;
97 char my_spooldir[PATH_LEN];
98 char buf2[PATH_LEN];
99 t_artnum min, max;
100 struct t_group *ptr;
101
102 if (!batch_mode)
103 wait_message(0, _(txt_reading_mail_active_file));
104
105 /*
106 * Open the mail active file
107 */
108 if ((fp = open_mail_active_fp("r")) == NULL) {
109 if (cmd_line)
110 my_fputc('\n', stderr);
111
112 if (!created_rcdir) /* no error on first start */
113 error_message(2, _(txt_cannot_open), mail_active_file);
114 /*
115 * TODO: do an autoscan of maildir, create & reopen?
116 */
117 write_mail_active_file();
118 return;
119 }
120
121 while ((buf = tin_fgets(fp, FALSE))) {
122 if (!parse_active_line(buf, &max, &min, my_spooldir) || *buf == '\0')
123 continue;
124
125 /*
126 * Update mailgroup info
127 */
128 if ((ptr = group_find(buf, FALSE)) != NULL) {
129 if (strcmp(ptr->spooldir, my_spooldir) != 0) {
130 free(ptr->spooldir);
131 strfpath(my_spooldir, buf2, sizeof(buf2) - 1, ptr, FALSE);
132 ptr->spooldir = my_strdup(buf2);
133 }
134 ptr->xmax = max;
135 ptr->xmin = min;
136 continue;
137 }
138
139 /*
140 * Load mailgroup into group hash table
141 */
142 if ((ptr = group_add(buf)) == NULL)
143 continue;
144
145 /*
146 * Load group info. TODO: integrate with active_add()
147 */
148 strfpath(my_spooldir, buf2, sizeof(buf2) - 1, ptr, FALSE);
149 ptr->spooldir = my_strdup(buf2);
150 group_get_art_info(ptr->spooldir, buf, GROUP_TYPE_MAIL, &ptr->count, &ptr->xmax, &ptr->xmin);
151 ptr->aliasedto = NULL;
152 ptr->description = NULL;
153 ptr->moderated = 'y';
154 ptr->type = GROUP_TYPE_MAIL;
155 ptr->inrange = FALSE;
156 ptr->read_during_session = FALSE;
157 ptr->art_was_posted = FALSE;
158 ptr->subscribed = FALSE; /* not in my_group[] yet */
159 ptr->newgroup = FALSE;
160 ptr->bogus = FALSE;
161 ptr->next = -1; /* hash chaining */
162 ptr->newsrc.xbitmap = (t_bitmap *) 0;
163 ptr->attribute = (struct t_attribute *) 0;
164 ptr->glob_filter = &glob_filter;
165 set_default_bitmap(ptr);
166 }
167 fclose(fp);
168
169 if (!batch_mode)
170 my_fputs("\n", stdout);
171 }
172
173
174 /*
175 * Write out mailgroups from active[] to ~/.tin/active.mail
176 */
177 void
178 write_mail_active_file(
179 void)
180 {
181 FILE *fp;
182 char *file_tmp;
183 char group_path[PATH_LEN];
184 int i;
185 struct t_group *group;
186
187 if (no_write && file_size(mail_active_file) != -1L)
188 return;
189
190 /* generate tmp-filename */
191 file_tmp = get_tmpfilename(mail_active_file);
192
193 if (!backup_file(mail_active_file, file_tmp)) {
194 error_message(2, _(txt_filesystem_full_backup), mail_active_file);
195 /* free memory for tmp-filename */
196 free(file_tmp);
197 return;
198 }
199
200 print_active_head(mail_active_file);
201
202 if ((fp = open_mail_active_fp("a+")) != NULL) {
203 for_each_group(i) {
204 group = &active[i];
205 if (group->type == GROUP_TYPE_MAIL) {
206 make_base_group_path(group->spooldir, group->name, group_path, sizeof(group_path));
207 find_art_max_min(group_path, &group->xmax, &group->xmin);
208 print_group_line(fp, group->name, group->xmax, group->xmin, group->spooldir);
209 }
210 }
211 if ((i = ferror(fp)) || fclose(fp)) {
212 error_message(2, _(txt_filesystem_full), mail_active_file);
213 if (i) {
214 clearerr(fp);
215 fclose(fp);
216 }
217 i = rename(file_tmp, mail_active_file);
218 # ifdef DEBUG
219 if ((debug & DEBUG_MISC) && i) /* TODO: is this the right debug-level? */
220 perror_message(_(txt_rename_error), file_tmp, mail_active_file);
221 # endif /* DEBUG */
222 } else
223 unlink(file_tmp);
224 }
225
226 /* free memory for tmp-filename */
227 free(file_tmp);
228 }
229
230
231 /*
232 * Load the text description from ~/.tin/mailgroups for each mail group into
233 * the active[] array.
234 */
235 static void
236 read_mailgroups_file(
237 t_bool verb)
238 {
239 FILE *fp;
240
241 if ((fp = open_mailgroups_fp()) != NULL) {
242 if (!batch_mode && verb)
243 wait_message(0, _(txt_reading_mailgroups_file));
244
245 read_groups_descriptions(fp, (FILE *) 0);
246
247 fclose(fp);
248
249 if (!batch_mode && verb) {
250 my_fputs("\n", stdout);
251 cursoroff();
252 }
253 }
254 }
255 #endif /* HAVE_MH_MAIL_HANDLING */
256
257
258 /*
259 * If reading via NNTP the newsgroups file will be saved to
260 * ~/.tin/$NNTPSERVER/newsgroups so that any subsequent rereads on the
261 * active file will not have to waste net bandwidth and the local copy
262 * of the newsgroups file can be accessed.
263 *
264 * in the newsrc_active case (-n cmd-line switch) we use "LIST NEWSGROUPS grp"
265 * instead of "LIST NEWSGROUPS" if we have just a few groups in the newsrc,
266 * due to pipelining the code is a bit complex.
267 */
268 static FILE *
269 open_newsgroups_fp(
270 void)
271 {
272 #ifdef NNTP_ABLE
273 FILE *result;
274 static int no_more_wildmat = 0;
275
276 if (read_news_via_nntp && !read_saved_news) {
277 if (read_local_newsgroups_file) {
278 if ((result = fopen(local_newsgroups_file, "r")) != NULL) {
279 struct stat buf;
280
281 # ifdef DEBUG
282 if ((debug & DEBUG_NNTP) && verbose > 1)
283 debug_print_file("NNTP", "open_newsgroups_fp Using local copy of newsgroups file");
284 # endif /* DEBUG */
285
286 if (!fstat(fileno(result), &buf)) {
287 if (buf.st_size > 0)
288 return result;
289 }
290 fclose(result);
291 unlink(local_newsgroups_file);
292 }
293 }
294 /*
295 * TODO: test me, find a useful limit,
296 * optimize more than n groups (e.g. 5) of the same
297 * subhierarchy to a wildmat?
298 */
299 if (((nntp_caps.type == CAPABILITIES && nntp_caps.list_newsgroups) || nntp_caps.type != CAPABILITIES) && newsrc_active && !list_active && !no_more_wildmat && (PIPELINE_LIMIT > MAX(1, num_active))) {
300 char *ptr;
301 char buff[NNTP_STRLEN];
302 char line[NNTP_STRLEN];
303 char file[PATH_LEN];
304 char serverdir[PATH_LEN];
305 struct t_group *group;
306 int resp, i, j = 0;
307
308 if (nntp_tcp_port != IPPORT_NNTP)
309 snprintf(file, sizeof(file), "%s:%u", nntp_server, nntp_tcp_port);
310 else
311 STRCPY(file, quote_space_to_dash(nntp_server));
312
313 joinpath(serverdir, sizeof(serverdir), rcdir, file);
314 joinpath(file, sizeof(file), serverdir, NEWSGROUPS_FILE".tmp");
315 *buff = '\0';
316 if ((result = fopen(file, "w")) != NULL) {
317 for_each_group(i) {
318 if ((group = group_find(active[i].name, FALSE)) != NULL) {
319 if (group->type == GROUP_TYPE_NEWS) {
320 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_newsgroups) {
321 if (*buff) {
322 if (strlen(buff) + strlen(active[i].name) + 1 < NNTP_GRPLEN) {
323 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", active[i].name);
324 continue;
325 } else {
326 put_server(buff);
327 *buff = '\0';
328 j++;
329 }
330 }
331 if (!*buff) {
332 snprintf(buff, sizeof(buff), "LIST NEWSGROUPS %s", active[i].name);
333 continue;
334 }
335 } else
336 snprintf(buff, sizeof(buff), "LIST NEWSGROUPS %s", active[i].name);
337 # ifdef DISABLE_PIPELINING
338 if ((resp = new_nntp_command(buff, OK_GROUPS, line, sizeof(line))) != OK_GROUPS) {
339 no_more_wildmat = resp;
340 *buff = '\0';
341 break;
342 }
343 while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
344 # ifdef DEBUG
345 if ((debug & DEBUG_NNTP) && verbose)
346 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
347 # endif /* DEBUG */
348 fprintf(result, "%s\n", str_trim(ptr));
349 }
350 # else
351 put_server(buff);
352 *buff = '\0';
353 j++;
354 # endif /* DISABLE_PIPELINING */
355 }
356 }
357 }
358 if (*buff) {
359 put_server(buff);
360 j++;
361 }
362 # ifndef DISABLE_PIPELINING
363 while (j--) {
364 if ((resp = get_only_respcode(line, sizeof(line))) != OK_GROUPS) {
365 if (!no_more_wildmat)
366 no_more_wildmat = resp;
367 continue;
368 }
369 while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
370 # ifdef DEBUG
371 if ((debug & DEBUG_NNTP) && verbose)
372 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
373 # endif /* DEBUG */
374 fprintf(result, "%s\n", str_trim(ptr));
375 }
376 }
377 /* TODO: add 483 (RFC 3977) support */
378 if (no_more_wildmat == ERR_NOAUTH || no_more_wildmat == NEED_AUTHINFO) {
379 if (!authenticate(nntp_server, userid, FALSE))
380 tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
381 }
382 # endif /* !DISABLE_PIPELINING */
383 fclose(result);
384 result = fopen(file, "r");
385 unlink(file); /* unlink on close */
386 }
387
388 if (result != NULL) {
389 if (!no_more_wildmat)
390 return result;
391 else /* AUTH request while pipelining or some error */
392 fclose(result);
393 }
394 }
395 return (nntp_command("LIST NEWSGROUPS", OK_GROUPS, NULL, 0));
396 }
397 #endif /* NNTP_ABLE */
398 return fopen(newsgroups_file, "r");
399 }
400
401
402 /*
403 * Load the text description from NEWSLIBDIR/newsgroups for each group into the
404 * active[] array. Save a copy locally if reading via NNTP to save bandwidth.
405 */
406 static void
407 read_newsgroups_file(
408 t_bool verb)
409 {
410 FILE *fp;
411 FILE *fp_save = (FILE *) 0;
412
413 if ((fp = open_newsgroups_fp()) != NULL) {
414 if (!batch_mode && verb)
415 wait_message(0, _(txt_reading_newsgroups_file));
416
417 if (read_news_via_nntp && !no_write && !read_local_newsgroups_file)
418 fp_save = fopen(local_newsgroups_file, "w");
419
420 read_groups_descriptions(fp, fp_save);
421
422 if (fp_save != NULL) {
423 fclose(fp_save);
424 read_local_newsgroups_file = TRUE;
425 }
426
427 TIN_FCLOSE(fp);
428
429 if (!batch_mode && verb) {
430 my_fputs("\n", stdout);
431 cursoroff();
432 }
433 }
434 }
435
436
437 /*
438 * read group descriptions for news (and mailgroups)
439 */
440 void
441 read_descriptions(
442 t_bool verb)
443 {
444 #ifdef HAVE_MH_MAIL_HANDLING
445 read_mailgroups_file(verb);
446 #endif /* HAVE_MH_MAIL_HANDLING */
447 read_newsgroups_file(verb);
448 }
449
450
451 /*
452 * Read groups descriptions from opened file & make local backup copy
453 * of all groups if reading groups of type GROUP_TYPE_NEWS.
454 * Aborting this early won't have any adverse affects, just some missing
455 * descriptions.
456 */
457 static void
458 read_groups_descriptions(
459 FILE *fp,
460 FILE *fp_save)
461 {
462 char *p, *q, *ptr;
463 char *groupname = NULL;
464 int count = 0;
465 size_t space = 0;
466 struct t_group *group;
467
468 while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
469 #ifdef DEBUG
470 if ((debug & DEBUG_NNTP) && fp == FAKE_NNTP_FP && verbose)
471 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
472 #endif /* DEBUG */
473 if (*ptr == '#' || *ptr == '\0')
474 continue;
475
476 /*
477 * This was moved from below and simplified. We can't test here for
478 * the type of group being read, because that requires having found
479 * the group in the active file, and that truncates the local copy
480 * of the newsgroups file to only subscribed-to groups when tin is
481 * called with the "-q" option.
482 */
483 if ((fp_save != NULL) && read_news_via_nntp)
484 fprintf(fp_save, "%s\n", str_trim(ptr));
485
486 if (!space) { /* initial malloc */
487 space = strlen(ptr) + 1;
488 groupname = my_malloc(space);
489 } else {
490 while (space < strlen(ptr) + 1) { /* realloc needed? */
491 space <<= 1; /* double size */
492 groupname = my_realloc(groupname, space);
493 }
494 }
495
496 for (p = ptr, q = groupname; *p && *p != ' ' && *p != '\t'; p++, q++)
497 *q = *p;
498
499 *q = '\0';
500
501 while (*p == '\t' || *p == ' ')
502 p++;
503
504 group = group_find(groupname, FALSE);
505
506 if (group != NULL && group->description == NULL) {
507 char *r;
508 size_t r_len;
509
510 q = p;
511 while ((q = strchr(q, '\t')) != NULL)
512 *q = ' ';
513
514 r = my_strdup(p);
515 r_len = strlen(r);
516 /*
517 * Protect against invalid character sequences.
518 */
519 process_charsets(&r, &r_len, "UTF-8", tinrc.mm_local_charset, FALSE);
520 group->description = convert_to_printable(r, FALSE);
521 }
522
523 if (++count % 100 == 0)
524 spin_cursor();
525 }
526 FreeIfNeeded(groupname);
527 }
528
529
530 void
531 print_active_head(
532 const char *active_file)
533 {
534 FILE *fp;
535
536 if (no_write && file_size(active_file) != -1L)
537 return;
538
539 if ((fp = fopen(active_file, "w")) != NULL) {
540 fprintf(fp, "%s", _(txt_mail_save_active_head));
541 fclose(fp);
542 }
543 }
544
545
546 void
547 find_art_max_min(
548 const char *group_path,
549 t_artnum *art_max,
550 t_artnum *art_min)
551 {
552 DIR *dir;
553 DIR_BUF *direntry;
554 t_artnum art_num;
555
556 *art_min = *art_max = T_ARTNUM_CONST(0);
557
558 if ((dir = opendir(group_path)) != NULL) {
559 while ((direntry = readdir(dir)) != NULL) {
560 art_num = atoartnum(direntry->d_name);
561 if (art_num >= T_ARTNUM_CONST(1)) {
562 if (art_num > *art_max) {
563 *art_max = art_num;
564 if (*art_min == T_ARTNUM_CONST(0))
565 *art_min = art_num;
566 } else if (art_num < *art_min)
567 *art_min = art_num;
568 }
569 }
570 CLOSEDIR(dir);
571 }
572 if (*art_min == T_ARTNUM_CONST(0))
573 *art_min = T_ARTNUM_CONST(1);
574 }
575
576
577 void
578 print_group_line(
579 FILE *fp,
580 const char *group_name,
581 t_artnum art_max,
582 t_artnum art_min,
583 const char *base_dir)
584 {
585 fprintf(fp, "%s %05"T_ARTNUM_PFMT" %05"T_ARTNUM_PFMT" %s\n",
586 group_name, art_max, art_min, base_dir);
587 }
588
589
590 void
591 grp_del_mail_art(
592 struct t_article *article)
593 {
594 if (article->delete_it)
595 info_message(_(txt_art_undeleted));
596 else
597 info_message(_(txt_art_deleted));
598
599 article->delete_it = bool_not(article->delete_it);
600 }
601
602
603 void
604 grp_del_mail_arts(
605 struct t_group *group)
606 {
607 char article_filename[PATH_LEN];
608 char group_path[PATH_LEN];
609 char artnum[LEN];
610 int i;
611 struct t_article *article;
612
613 if (group->type == GROUP_TYPE_MAIL || group->type == GROUP_TYPE_SAVE) {
614 /*
615 * at least for GROUP_TYPE_SAVE a wait is annoying - nuke the message?
616 */
617 wait_message(0, (group->type == GROUP_TYPE_MAIL) ? _(txt_processing_mail_arts) : _(txt_processing_saved_arts));
618 cursoroff();
619 make_base_group_path(group->spooldir, group->name, group_path, sizeof(group_path));
620 for_each_art(i) {
621 article = &arts[i];
622 if (article->delete_it) {
623 snprintf(artnum, sizeof(artnum), "%"T_ARTNUM_PFMT, article->artnum);
624 joinpath(article_filename, sizeof(article_filename), group_path, artnum);
625 unlink(article_filename);
626 article->thread = ART_EXPIRED;
627 }
628 }
629
630 /*
631 * current tin's build_references() is changed to free msgid and
632 * refs, therefore we cannot call write_overview after it. NovFile
633 * will update at next time.
634 */
635 }
636 }
637
638
639 t_bool
640 art_edit(
641 struct t_group *group,
642 struct t_article *article)
643 {
644 char article_filename[PATH_LEN];
645 char temp_filename[PATH_LEN];
646 char buf[PATH_LEN];
647
648 /*
649 * Check if news / mail group
650 */
651 if (group->type != GROUP_TYPE_MAIL)
652 return FALSE;
653
654 make_base_group_path(group->spooldir, group->name, temp_filename, sizeof(temp_filename));
655 snprintf(buf, sizeof(buf), "%"T_ARTNUM_PFMT, article->artnum);
656 joinpath(article_filename, sizeof(article_filename), temp_filename, buf);
657 snprintf(buf, sizeof(buf), "%ld.art", (long) process_id);
658 joinpath(temp_filename, sizeof(temp_filename), tmpdir, buf);
659
660 if (!backup_file(article_filename, temp_filename))
661 return FALSE;
662
663 if (!invoke_editor(temp_filename, 1, group)) {
664 unlink(temp_filename);
665 return FALSE;
666 }
667
668 rename_file(temp_filename, article_filename);
669 return TRUE;
670 }