"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.2/src/active.c" (23 Dec 2022, 36419 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 "active.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 : active.c
4 * Author : I. Lea
5 * Created : 1992-02-16
6 * Updated : 2022-12-23
7 * Notes :
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
48 /*
49 * List of allowed separator chars in active file
50 * unused in parse_active_line()
51 */
52 #define ACTIVE_SEP " \n"
53
54 #ifdef NNTP_ABLE
55 # ifdef DISABLE_PIPELINING
56 # define NUM_SIMULTANEOUS_GROUP_COMMAND 1
57 # else
58 # define NUM_SIMULTANEOUS_GROUP_COMMAND 50
59 # endif /* DISABLE_PIPELINING */
60 #endif /* NNTP_ABLE */
61
62 t_bool force_reread_active_file = FALSE;
63 static time_t active_timestamp; /* time active file read (local) */
64
65
66 /*
67 * Local prototypes
68 */
69 static FILE *open_newgroups_fp(int idx);
70 static FILE *open_news_active_fp(void);
71 static int check_for_any_new_groups(void);
72 static void active_add(struct t_group *ptr, t_artnum count, t_artnum max, t_artnum min, const char *moderated);
73 static void append_group_line(char *active_file, char *group_path, t_artnum art_max, t_artnum art_min, char *base_dir);
74 static void make_group_list(char *active_file, char *base_dir, char *fixed_base, char *group_path);
75 static void read_active_file(void);
76 static void read_newsrc_active_file(void);
77 static void subscribe_new_group(char *group, char *autosubscribe, char *autounsubscribe);
78 #ifdef NNTP_ABLE
79 static t_bool do_read_newsrc_active_file(FILE *fp);
80 static t_bool parse_count_line(char *line, t_artnum *max, t_artnum *min, t_artnum *count, char *moderated);
81 static void read_active_counts(void);
82 #else
83 static void do_read_newsrc_active_file(FILE *fp);
84 #endif /* NNTP_ABLE */
85
86
87 t_bool
88 need_reread_active_file(
89 void)
90 {
91 return (force_reread_active_file || (tinrc.reread_active_file_secs != 0 &&
92 (int) (time(NULL) - active_timestamp) >= tinrc.reread_active_file_secs));
93 }
94
95
96 /*
97 * Resync active file when reread_active_file_secs have passed or
98 * force_reread_active_file is set.
99 * Return TRUE if a reread was performed
100 */
101 t_bool
102 resync_active_file(
103 void)
104 {
105 char *old_group = NULL;
106 t_bool command_line = FALSE;
107
108 if (!need_reread_active_file())
109 return FALSE;
110
111 reread_active_for_posted_arts = FALSE;
112
113 if (selmenu.curr >= 0 && selmenu.max)
114 old_group = my_strdup(CURR_GROUP.name);
115
116 write_newsrc();
117 read_news_active_file();
118
119 #ifdef HAVE_MH_MAIL_HANDLING
120 read_mail_active_file();
121 #endif /* HAVE_MH_MAIL_HANDLING */
122
123 if (read_cmd_line_groups())
124 command_line = TRUE;
125
126 read_newsrc(newsrc, bool_not(command_line));
127
128 if (command_line) /* Can't show only unread groups with cmd line groups */
129 tinrc.show_only_unread_groups = FALSE;
130 else
131 toggle_my_groups(old_group);
132
133 FreeIfNeeded(old_group);
134 show_selection_page();
135
136 return TRUE;
137 }
138
139
140 /*
141 * Populate a slot in the active[] array
142 * TODO: 1) Have a preinitialised default slot and block assign it for speed
143 * TODO: 2) Lump count/max/min/moderat into a t_active, big patch but much cleaner throughout tin
144 */
145 static void
146 active_add(
147 struct t_group *ptr,
148 t_artnum count,
149 t_artnum max,
150 t_artnum min,
151 const char *moderated)
152 {
153 /* name - pre-initialised when group is made */
154 ptr->aliasedto = ((moderated[0] == '=') ? my_strdup(moderated + 1) : NULL);
155 ptr->description = NULL;
156 /* spool - see below */
157 ptr->moderated = moderated[0];
158 ptr->count = count;
159 ptr->xmax = max;
160 ptr->xmin = min;
161 /* type - see below */
162 ptr->inrange = FALSE;
163 ptr->read_during_session = FALSE;
164 ptr->art_was_posted = FALSE;
165 ptr->subscribed = FALSE; /* not in my_group[] yet */
166 ptr->newgroup = FALSE;
167 ptr->bogus = FALSE;
168 ptr->next = -1; /* hash chain */
169 ptr->newsrc.xbitmap = (t_bitmap *) 0;
170 ptr->attribute = (struct t_attribute *) 0;
171 ptr->glob_filter = &glob_filter;
172 set_default_bitmap(ptr);
173
174 if (moderated[0] == '/') {
175 ptr->type = GROUP_TYPE_SAVE;
176 ptr->spooldir = my_strdup(moderated); /* TODO: Unix'ism, other OSs need transformation */
177 } else {
178 ptr->type = GROUP_TYPE_NEWS;
179 ptr->spooldir = spooldir; /* another global - sigh */
180 }
181 }
182
183
184 /*
185 * Decide how to handle a bogus groupname.
186 * If we process them interactively, create an empty active[] for this
187 * group and mark it bogus for display in the group selection page
188 * Otherwise, bogus groups are not displayed and are dealt with when newsrc
189 * is written.
190 */
191 t_bool
192 process_bogus(
193 char *name) /* return value is always ignored */
194 {
195 struct t_group *ptr;
196
197 if (read_saved_news || tinrc.strip_bogus != BOGUS_SHOW)
198 return FALSE;
199
200 if ((ptr = group_add(name)) == NULL)
201 return FALSE;
202
203 active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "n");
204 ptr->bogus = TRUE; /* Mark it bogus */
205 /*
206 * Set pointer to default attributes
207 */
208 if (ptr->attribute && !ptr->attribute->global)
209 free(ptr->attribute);
210 ptr->attribute = scopes[0].attribute;
211
212 if (my_group_add(name, FALSE) < 0)
213 return TRUE;
214
215 return FALSE; /* Nothing was printed yet */
216 }
217
218
219 /*
220 * Parse line from news or mail active files
221 */
222 t_bool
223 parse_active_line(
224 char *line,
225 t_artnum *max,
226 t_artnum *min,
227 char *moderated)
228 {
229 char *p = NULL, *q = NULL, *r = NULL;
230 #ifdef DEBUG
231 char *l;
232 #endif /* DEBUG */
233 t_bool lineok = FALSE;
234
235 if (line[0] == '#' || line[0] == '\0')
236 return lineok;
237
238 #ifdef DEBUG
239 l = my_strdup(line);
240 #endif /* DEBUG */
241
242 if (strtok(line, ACTIVE_SEP)) { /* skip group name */
243 if ((p = strtok(NULL, ACTIVE_SEP))) { /* group max count */
244 if ((q = strtok(NULL, ACTIVE_SEP))) { /* group min count */
245 if ((r = strtok(NULL, ACTIVE_SEP))) /* mod status or path to mailgroup */
246 lineok = TRUE;
247 }
248 }
249 }
250
251 if (!lineok) {
252 #ifdef DEBUG
253 /* TODO: This also logs broken non NNTP active lines (i.e. mail.active) to NNTP */
254 if ((debug & DEBUG_NNTP) && verbose > 1)
255 debug_print_file("NNTP", "Active file corrupt - %s", l);
256
257 free(l);
258 #endif /* DEBUG */
259 if (!p || !q) {
260 return lineok;
261 }
262 }
263
264 *max = atoartnum(p);
265 *min = atoartnum(q);
266
267 if (!lineok) { /* missing moderation flag - seen on usenet.farm */
268 strcpy(moderated, "y"); /* guess posting is fine */
269 lineok = TRUE;
270 } else {
271 #ifdef DEBUG
272 free(l);
273 #endif /* DEBUG */
274 strcpy(moderated, r);
275 }
276
277 return lineok;
278 }
279
280
281 #ifdef NNTP_ABLE
282 /*
283 * Parse line from "LIST COUNTS" (RFC 6048 2.2.)
284 * group high low count status
285 */
286 static t_bool
287 parse_count_line(
288 char *line,
289 t_artnum *max,
290 t_artnum *min,
291 t_artnum *count,
292 char *moderated)
293 {
294 char *p = NULL, *q = NULL, *r = NULL, *s = NULL;
295 t_bool lineok = FALSE;
296
297 if (line[0] == '#' || line[0] == '\0')
298 return FALSE;
299
300 if (strtok(line, ACTIVE_SEP)) { /* skip group name */
301 if ((p = strtok(NULL, ACTIVE_SEP))) { /* group max */
302 if ((q = strtok(NULL, ACTIVE_SEP))) { /* group min */
303 if ((r = strtok(NULL, ACTIVE_SEP))) { /* group count */
304 s = strtok(NULL, ACTIVE_SEP); /* mod status or path to mailgroup */
305 lineok = TRUE;
306 }
307 }
308 }
309 }
310
311 if (!p || !q || !r || !s || !lineok) {
312 # ifdef DEBUG
313 if ((debug & DEBUG_NNTP) && verbose > 1)
314 debug_print_file("NNTP", "unparsable \"LIST COUNTS\" line: \"%s\"", line);
315 # endif /* DEBUG */
316 return FALSE;
317 }
318
319 *max = atoartnum(p);
320 *min = atoartnum(q);
321 *count = atoartnum(r);
322 strcpy(moderated, s);
323
324 return TRUE;
325 }
326 #endif /* NNTP_ABLE */
327
328
329 /*
330 * Load the active information into active[] by counting the min/max/count
331 * for each news group.
332 * Parse a line from the .newsrc file
333 * Send GROUP command to NNTP server directly to keep window.
334 * We can't know the 'moderator' status and always return 'y'
335 * But we don't change if the 'moderator' status is already checked by
336 * read_active_file()
337 * Returns TRUE if NNTP is enabled and authentication is needed
338 */
339 #ifdef NNTP_ABLE
340 static t_bool
341 #else
342 static void
343 #endif /* NNTP_ABLE */
344 do_read_newsrc_active_file(
345 FILE *fp)
346 {
347 char *ptr;
348 char *p;
349 char moderated[PATH_LEN];
350 int window = 0;
351 long processed = 0L;
352 t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
353 static char ngname[NNTP_GRPLEN + 1]; /* RFC 3977 3.1 limits group names to 497 octets */
354 struct t_group *grpptr;
355 #ifdef NNTP_ABLE
356 t_bool need_auth = FALSE;
357 char *ngnames[NUM_SIMULTANEOUS_GROUP_COMMAND];
358 int index_i = 0;
359 int index_o = 0;
360 #endif /* NNTP_ABLE */
361
362 rewind(fp);
363
364 if (!batch_mode || verbose)
365 wait_message(0, _(txt_reading_news_newsrc_file));
366
367 while ((ptr = tin_fgets(fp, FALSE)) != NULL || window != 0) {
368 if (ptr) {
369 p = strpbrk(ptr, ":!");
370
371 if (!p || *p != SUBSCRIBED) /* Invalid line or unsubscribed */
372 continue;
373 *p = '\0'; /* Now ptr is the group name */
374
375 /*
376 * 128 should be enough for a groupname, >256 and we overflow buffers
377 * later on
378 * TODO: check RFCs for possible max. size
379 */
380 my_strncpy(ngname, ptr, 128);
381 ptr = ngname;
382 }
383
384 if (read_news_via_nntp && !read_saved_news) {
385 #ifdef NNTP_ABLE
386 char buf[NNTP_STRLEN];
387 char line[NNTP_STRLEN];
388
389 if (window < NUM_SIMULTANEOUS_GROUP_COMMAND && ptr && (!list_active || (newsrc_active && list_active && group_find(ptr, FALSE)))) {
390 ngnames[index_i] = my_strdup(ptr);
391 snprintf(buf, sizeof(buf), "GROUP %s", ngnames[index_i]);
392 # ifdef DEBUG
393 if ((debug & DEBUG_NNTP) && verbose > 1)
394 debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
395 # endif /* DEBUG */
396 put_server(buf);
397 index_i = (index_i + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
398 window++;
399 }
400 if (window == NUM_SIMULTANEOUS_GROUP_COMMAND || ptr == NULL) {
401 int respcode = get_only_respcode(line, sizeof(line));
402
403 if (reconnected_in_last_get_server) {
404 /*
405 * If tin reconnected, last output is resended to server.
406 * So received data is for ngnames[last window_i].
407 * We resend all buffered command except for last window_i.
408 * And rotate buffer to use data received.
409 */
410 int i;
411 int j = index_o;
412 for (i = 0; i < window - 1; i++) {
413 snprintf(buf, sizeof(buf), "GROUP %s", ngnames[j]);
414 # ifdef DEBUG
415 if ((debug & DEBUG_NNTP) && verbose > 1)
416 debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
417 # endif /* DEBUG */
418 put_server(buf);
419 j = (j + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
420 }
421 if (--index_o < 0)
422 index_o = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
423 if (--index_i < 0)
424 index_i = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
425 if (index_i != index_o)
426 ngnames[index_o] = ngnames[index_i];
427 }
428
429 switch (respcode) {
430
431 case OK_GROUP:
432 {
433 char fmt[25];
434
435 snprintf(fmt, sizeof(fmt), "%%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%%ds", NNTP_GRPLEN);
436 if (sscanf(line, fmt, &count, &min, &max, ngname) != 4) {
437 # ifdef DEBUG
438 if ((debug & DEBUG_NNTP) && verbose > 1)
439 debug_print_file("NNTP", "Invalid response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
440 # endif /* DEBUG */
441 }
442 if (strcmp(ngname, ngnames[index_o]) != 0) {
443 # ifdef DEBUG
444 if ((debug & DEBUG_NNTP) && verbose > 1)
445 debug_print_file("NNTP", "Groupname mismatch in response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
446 # endif /* DEBUG */
447 }
448 ptr = ngname;
449 free(ngnames[index_o]);
450 index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
451 window--;
452 break;
453 }
454
455 case ERR_NOAUTH:
456 case NEED_AUTHINFO:
457 need_auth = TRUE; /* delay auth till end of loop */
458 /* keep lint quiet: */
459 /* FALLTHROUGH */
460
461 case ERR_NOGROUP:
462 free(ngnames[index_o]);
463 index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
464 window--;
465 continue;
466
467 case ERR_ACCESS:
468 tin_done(NNTP_ERROR_EXIT, "%s", line);
469 /* keep lint quiet: */
470 /* FALLTHROUGH */
471
472 default:
473 # ifdef DEBUG
474 if ((debug & DEBUG_NNTP) && verbose > 1)
475 debug_print_file("NNTP", "NOT_OK %s", line);
476 # endif /* DEBUG */
477 free(ngnames[index_o]);
478 index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
479 window--;
480 continue;
481 }
482 } else
483 continue;
484 #endif /* NNTP_ABLE */
485 } else {
486 if (group_get_art_info(spooldir, ptr, GROUP_TYPE_NEWS, &count, &max, &min))
487 continue;
488 }
489
490 strcpy(moderated, "y");
491
492 if (++processed % 5 == 0)
493 spin_cursor();
494
495 /*
496 * Load group into group hash table
497 * NULL means group already present, so we just fixup the counters
498 * This call may implicitly ++num_active
499 */
500 if ((grpptr = group_add(ptr)) == NULL) {
501 t_bool changed = FALSE;
502
503 if ((grpptr = group_find(ptr, FALSE)) == NULL)
504 continue;
505
506 if (max > grpptr->xmax) {
507 grpptr->xmax = max;
508 changed = TRUE;
509 }
510 if (min > grpptr->xmin) {
511 grpptr->xmin = min;
512 changed = TRUE;
513 }
514 if (changed) {
515 grpptr->count = count;
516 expand_bitmap(grpptr, 0); /* TODO: expand_bitmap(grpptr,grpptr->xmin) should be enough */
517 }
518 continue;
519 }
520
521 /*
522 * Load the new group in active[]
523 */
524 active_add(grpptr, count, max, min, moderated);
525 }
526 #ifdef NNTP_ABLE
527 return need_auth;
528 #endif /* NNTP_ABLE */
529 }
530
531
532 /*
533 * Wrapper for do_read_newsrc_active_file() to handle
534 * missing authentication
535 */
536 static void
537 read_newsrc_active_file(
538 void)
539 {
540 FILE *fp;
541 #ifdef NNTP_ABLE
542 t_bool need_auth;
543 #endif /* NNTP_ABLE */
544
545 /*
546 * return immediately if no .newsrc can be found or .newsrc is empty
547 * when function asked to use .newsrc
548 */
549 if ((fp = fopen(newsrc, "r")) == NULL)
550 return;
551
552 if (file_size(newsrc) <= 0L) {
553 fclose(fp);
554 return;
555 }
556
557 #ifdef NNTP_ABLE
558 need_auth = do_read_newsrc_active_file(fp);
559 #else
560 do_read_newsrc_active_file(fp);
561 #endif /* NNTP_ABLE */
562
563 #ifdef NNTP_ABLE
564 if (need_auth) { /* delayed auth */
565 if (!authenticate(nntp_server, userid, FALSE))
566 tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
567 # if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
568 set_maxartnum(FALSE);
569 # endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
570 if (do_read_newsrc_active_file(fp))
571 tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
572 }
573 #endif /* NNTP_ABLE */
574
575 fclose(fp);
576
577 /*
578 * Exit if active file wasn't read correctly or is empty
579 */
580 if (tin_errno || !num_active) {
581 if (newsrc_active && !num_active)
582 tin_done(EXIT_FAILURE, _(txt_error_server_has_no_listed_groups), newsrc);
583 else
584 tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
585 }
586
587 if (!batch_mode || verbose)
588 my_fputc('\n', stdout);
589 }
590
591
592 /*
593 * Open the news active file locally or send the LIST command
594 */
595 static FILE *
596 open_news_active_fp(
597 void)
598 {
599 #ifdef NNTP_ABLE
600 if (read_news_via_nntp && !read_saved_news)
601 return (nntp_command("LIST", OK_GROUPS, NULL, 0));
602 #endif /* NNTP_ABLE */
603 return (fopen(news_active_file, "r"));
604 }
605
606
607 /*
608 * Load the active file into active[]
609 */
610 static void
611 read_active_file(
612 void)
613 {
614 FILE *fp;
615 char *ptr;
616 char moderated[PATH_LEN];
617 long processed = 0L;
618 struct t_group *grpptr;
619 t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
620
621 if (!batch_mode || verbose)
622 wait_message(0, _(txt_reading_news_active_file));
623
624 if ((fp = open_news_active_fp()) == NULL) {
625 if (cmd_line && !batch_mode)
626 my_fputc('\n', stderr);
627
628 #ifdef NNTP_ABLE
629 if (read_news_via_nntp)
630 tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
631 # ifndef NNTP_ONLY
632 else
633 tin_done(EXIT_FAILURE, _(txt_cannot_open_active_file), news_active_file, tin_progname);
634 # endif /* !NNTP_ONLY */
635 #else
636 tin_done(EXIT_FAILURE, _(txt_cannot_open), news_active_file);
637 #endif /* NNTP_ABLE */
638 }
639
640 while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
641 #if defined(DEBUG) && defined(NNTP_ABLE)
642 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
643 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
644 #endif /* DEBUG && NNTP_ABLE */
645
646 if (++processed % MODULO_COUNT_NUM == 0)
647 spin_cursor();
648
649 if (!parse_active_line(ptr, &max, &min, moderated))
650 continue;
651
652 /*
653 * Load group into group hash table
654 * NULL means group already present, so we just fixup the counters
655 * This call may implicitly ++num_active
656 */
657 if ((grpptr = group_add(ptr)) == NULL) {
658 if ((grpptr = group_find(ptr, FALSE)) == NULL)
659 continue;
660
661 if (max > grpptr->xmax) {
662 grpptr->xmax = max;
663 grpptr->count = count;
664 }
665
666 if (min > grpptr->xmin) {
667 grpptr->xmin = min;
668 grpptr->count = count;
669 }
670
671 continue;
672 }
673
674 /*
675 * Load the new group in active[]
676 */
677 active_add(grpptr, count, max, min, moderated);
678 }
679
680 TIN_FCLOSE(fp);
681
682 /*
683 * Exit if active file wasn't read correctly or is empty
684 */
685 if (tin_errno || !num_active)
686 tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
687
688 if (!batch_mode || verbose)
689 my_fputc('\n', stdout);
690 }
691
692
693 #ifdef NNTP_ABLE
694 /*
695 * Load the active file into active[] via LIST COUNTS
696 */
697 static void
698 read_active_counts(
699 void)
700 {
701 FILE *fp;
702 char *ptr;
703 char moderated[PATH_LEN];
704 long processed = 0L;
705 struct t_group *grpptr;
706 t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
707
708 if (!batch_mode || verbose)
709 wait_message(0, _(txt_reading_news_active_file));
710
711 if ((fp = nntp_command("LIST COUNTS", OK_GROUPS, NULL, 0)) == NULL) {
712 if (cmd_line && !batch_mode)
713 my_fputc('\n', stderr);
714
715 tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
716 }
717
718 while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
719 # ifdef DEBUG
720 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
721 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
722 # endif /* DEBUG */
723
724 if (++processed % MODULO_COUNT_NUM == 0)
725 spin_cursor();
726
727 if (!parse_count_line(ptr, &max, &min, &count, moderated))
728 continue;
729
730 /*
731 * Load group into group hash table
732 * NULL means group already present, so we just fixup the counters
733 * This call may implicitly ++num_active
734 */
735 if ((grpptr = group_add(ptr)) == NULL) {
736 if ((grpptr = group_find(ptr, FALSE)) == NULL)
737 continue;
738
739 if (max > grpptr->xmax) {
740 grpptr->xmax = max;
741 grpptr->count = count;
742 }
743
744 if (min > grpptr->xmin) {
745 grpptr->xmin = min;
746 grpptr->count = count;
747 }
748
749 continue;
750 }
751
752 /*
753 * Load the new group in active[]
754 */
755 active_add(grpptr, count, max, min, moderated);
756 }
757
758 /*
759 * Exit if active file wasn't read correctly or is empty
760 */
761 if (tin_errno || !num_active)
762 tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), _(txt_servers_active));
763
764 if (!batch_mode || verbose)
765 my_fputc('\n', stdout);
766 }
767 #endif /* NNTP_ABLE */
768
769
770 /*
771 * Load the active file into active[]
772 * Check and preload any new newgroups into my_group[]
773 */
774 int
775 read_news_active_file(
776 void)
777 {
778 FILE *fp;
779 int newgrps = 0;
780 t_bool do_group_cmds = !nntp_caps.list_counts;
781 #if defined(NNTP_ABLE) && !defined(DISABLE_PIPELINING)
782 t_bool did_list_cmd = FALSE;
783 #endif /* NNTP_ABLE && !DISABLE_PIPELINING */
784
785 /*
786 * Ignore -n if no .newsrc can be found or .newsrc is empty
787 */
788 if (newsrc_active) {
789 if ((fp = fopen(newsrc, "r")) == NULL) {
790 list_active = TRUE;
791 newsrc_active = FALSE;
792 } else {
793 fclose(fp);
794 if (file_size(newsrc) <= 0L) {
795 list_active = TRUE;
796 newsrc_active = FALSE;
797 }
798 }
799 }
800
801 /* Read an active file if it is allowed */
802 if (list_active) {
803 #ifdef NNTP_ABLE
804 # ifndef DISABLE_PIPELINING
805 did_list_cmd = TRUE;
806 # endif /* !DISABLE_PIPELINING */
807 if (read_news_via_nntp && nntp_caps.list_counts)
808 read_active_counts();
809 else
810 #endif /* NNTP_ABLE */
811 read_active_file();
812 }
813
814 /* Read .newsrc and check each group */
815 if (newsrc_active) {
816 #ifdef NNTP_ABLE
817 # ifndef DISABLE_PIPELINING
818 /*
819 * prefer LIST COUNTS, otherwise use LIST ACTIVE (-l) or GROUP (-n)
820 * or both (-ln); LIST COUNTS/ACTIVE grplist is used up to
821 * PIPELINE_LIMIT groups in newsrc
822 */
823 if (read_news_via_nntp && (list_active || nntp_caps.list_counts) && !did_list_cmd) {
824 char buff[NNTP_STRLEN];
825 char *ptr, *q;
826 char moderated[PATH_LEN];
827 int r = 0, j = 0;
828 int i;
829 struct t_group *grpptr;
830 t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
831 t_bool need_auth = FALSE;
832
833 *buff = '\0';
834 /* we can't use for_each_group(i) yet, so we have to parse the newsrc */
835 if ((fp = fopen(newsrc, "r")) != NULL) {
836 while (tin_fgets(fp, FALSE) != NULL)
837 j++;
838 rewind(fp);
839 if (j < PIPELINE_LIMIT) {
840 while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
841 if (!(q = strpbrk(ptr, ":!")))
842 continue;
843 *q = '\0';
844 if (nntp_caps.type == CAPABILITIES && (nntp_caps.list_active || nntp_caps.list_counts)) {
845 /* LIST ACTIVE or LIST COUNTS takes wildmats */
846 if (*buff && ((strlen(buff) + strlen(ptr)) < (NNTP_GRPLEN - 1))) { /* append group name */
847 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", ptr);
848 } else {
849 if (*buff) {
850 put_server(buff);
851 r++;
852 }
853 snprintf(buff, sizeof(buff), "LIST %s %s", nntp_caps.list_counts ? "COUNTS" : "ACTIVE", ptr);
854 }
855 continue;
856 } else
857 snprintf(buff, sizeof(buff), "LIST ACTIVE %s", ptr);
858 put_server(buff);
859 r++;
860 *buff = '\0';
861 }
862 if (*buff) {
863 put_server(buff);
864 r++;
865 }
866 } else
867 do_group_cmds = TRUE;
868
869 fclose(fp);
870
871 if (j < PIPELINE_LIMIT) {
872 for (i = 0; i < r && !did_reconnect; i++) {
873 if ((j = get_only_respcode(buff, sizeof(buff))) != OK_GROUPS) {
874 /* TODO: add 483 (RFC 3977) code */
875 if (j == ERR_NOAUTH || j == NEED_AUTHINFO)
876 need_auth = TRUE;
877 # if 0 /* do we need something like this? */
878 if (j == ERR_CMDSYN)
879 list_active = TRUE;
880 # endif /* 0 */
881 continue;
882 } else {
883 while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
884 # ifdef DEBUG
885 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
886 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
887 # endif /* DEBUG */
888 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_counts) {
889 if (!parse_count_line(ptr, &max, &min, &count, moderated))
890 continue;
891 } else {
892 if (!parse_active_line(ptr, &max, &min, moderated))
893 continue;
894 }
895
896 if ((grpptr = group_add(ptr)) == NULL) {
897 if ((grpptr = group_find(ptr, FALSE)) == NULL)
898 continue;
899
900 if (max > grpptr->xmax) {
901 grpptr->xmax = max;
902 grpptr->count = count;
903 }
904 if (min > grpptr->xmin) {
905 grpptr->xmin = min;
906 grpptr->count = count;
907 }
908 continue;
909 }
910 active_add(grpptr, count, max, min, moderated);
911 }
912 }
913 }
914 if (need_auth) { /* retry after auth is overkill here, so just auth */
915 if (!authenticate(nntp_server, userid, FALSE))
916 tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
917 /* no set_maxartnum() here after auth as we're pipelining ... */
918 }
919 }
920 did_reconnect = FALSE;
921 }
922 }
923 # endif /* !DISABLE_PIPELINING */
924 #endif /* NNTP_ABLE */
925 if (!nntp_caps.list_counts || do_group_cmds)
926 read_newsrc_active_file();
927 }
928
929 (void) time(&active_timestamp);
930 force_reread_active_file = FALSE;
931
932 /*
933 * check_for_any_new_groups() also does $AUTOSUBSCRIBE
934 */
935 if (check_for_new_newsgroups)
936 newgrps = check_for_any_new_groups();
937
938 /*
939 * finally we have a list of all groups and can set the attributes
940 */
941 assign_attributes_to_groups();
942
943 return newgrps;
944 }
945
946
947 /*
948 * Open the active.times file locally or send the NEWGROUPS command
949 * "NEWGROUPS yymmdd hhmmss"
950 */
951 static FILE *
952 open_newgroups_fp(
953 int idx)
954 {
955 #ifdef NNTP_ABLE
956 char line[NNTP_STRLEN];
957 struct tm *ngtm;
958
959 if (read_news_via_nntp && !read_saved_news) {
960 /*
961 * not checking for caps_type == CAPABILITIES && reader as some
962 * servers do not support it even if advertising READER so we must
963 * handle errors anyway and just issue the cmd.
964 */
965 if (idx == -1 || ((ngtm = localtime(&newnews[idx].time)) == NULL))
966 return (FILE *) 0;
967
968 /*
969 * RFC 3977 states that we SHOULD use 4 digit year but some servers
970 * still do not support it.
971 */
972 snprintf(line, sizeof(line), "NEWGROUPS %02d%02d%02d %02d%02d%02d",
973 ngtm->tm_year % 100, ngtm->tm_mon + 1, ngtm->tm_mday,
974 ngtm->tm_hour, ngtm->tm_min, ngtm->tm_sec);
975
976 return (nntp_command(line, OK_NEWGROUPS, NULL, 0));
977 }
978 #endif /* NNTP_ABLE */
979 return (fopen(active_times_file, "r"));
980 }
981
982
983 /*
984 * Check for any newly created newsgroups.
985 *
986 * If reading news locally check the NEWSLIBDIR/active.times file.
987 * Format: Groupname Seconds Creator
988 *
989 * If reading news via NNTP issue a NEWGROUPS command.
990 * Format: (as active file) Groupname Maxart Minart moderated
991 */
992 static int
993 check_for_any_new_groups(
994 void)
995 {
996 FILE *fp;
997 char *autosubscribe, *autounsubscribe;
998 char *ptr, *line, buf[NNTP_STRLEN];
999 char old_newnews_host[PATH_LEN];
1000 int newnews_index;
1001 int newgrps = 0;
1002 time_t old_newnews_time;
1003 time_t new_newnews_time;
1004
1005 if (!batch_mode /* || verbose */)
1006 wait_message(0, _(txt_checking_new_groups));
1007
1008 (void) time(&new_newnews_time);
1009
1010 /*
1011 * find out if we have read news from here before otherwise -1
1012 */
1013 if ((newnews_index = find_newnews_index(nntp_server)) >= 0) {
1014 STRCPY(old_newnews_host, newnews[newnews_index].host);
1015 old_newnews_time = newnews[newnews_index].time;
1016 } else {
1017 STRCPY(old_newnews_host, "UNKNOWN");
1018 old_newnews_time = (time_t) 0;
1019 }
1020
1021 #ifdef DEBUG
1022 if ((debug & DEBUG_NNTP) && verbose > 1)
1023 debug_print_file("NNTP", "Newnews old=[%lu] new=[%lu]", (unsigned long int) old_newnews_time, (unsigned long int) new_newnews_time);
1024 #endif /* DEBUG */
1025
1026 if ((fp = open_newgroups_fp(newnews_index)) != NULL) {
1027 /*
1028 * Need these later. They list user-defined groups to be
1029 * automatically subscribed or unsubscribed.
1030 */
1031 autosubscribe = getenv("AUTOSUBSCRIBE");
1032 autounsubscribe = getenv("AUTOUNSUBSCRIBE");
1033
1034 while ((line = tin_fgets(fp, FALSE)) != NULL) {
1035 /*
1036 * Split the group name off and subscribe. If we're reading local,
1037 * we must check the creation date manually
1038 */
1039 if ((ptr = strchr(line, ' ')) != NULL) {
1040 if (!read_news_via_nntp && ((time_t) atol(ptr) < old_newnews_time || old_newnews_time == (time_t) 0))
1041 continue;
1042
1043 *ptr = '\0';
1044 }
1045 subscribe_new_group(line, autosubscribe, autounsubscribe);
1046 newgrps++;
1047 }
1048 TIN_FCLOSE(fp);
1049
1050 if (tin_errno)
1051 return 0; /* Don't update the time if we quit */
1052 }
1053
1054 /*
1055 * Update (if already existing) or create (if new) the in-memory
1056 * 'last time newgroups checked' slot for this server. It will be written
1057 * out as part of tinrc.
1058 */
1059 if (newnews_index >= 0)
1060 newnews[newnews_index].time = new_newnews_time;
1061 else {
1062 snprintf(buf, sizeof(buf), "%s %lu", nntp_server, (unsigned long int) new_newnews_time);
1063 load_newnews_info(buf);
1064 }
1065
1066 if (!batch_mode)
1067 my_fputc('\n', stdout);
1068
1069 return newgrps;
1070 }
1071
1072
1073 /*
1074 * Subscribe to a new news group:
1075 * Handle the AUTOSUBSCRIBE/AUTOUNSUBSCRIBE env vars
1076 * They hold a wildcard list of groups that should be automatically
1077 * (un)subscribed when a new group is found
1078 * If a group is autounsubscribed, completely ignore it
1079 * If a group is autosubscribed, subscribe to it
1080 * Otherwise, mark it as New for inclusion in selection screen
1081 */
1082 static void
1083 subscribe_new_group(
1084 char *group,
1085 char *autosubscribe,
1086 char *autounsubscribe)
1087 {
1088 int idx;
1089 struct t_group *ptr;
1090
1091 /*
1092 * If we explicitly don't auto subscribe to this group, then don't bother going on
1093 */
1094 if ((autounsubscribe != NULL) && match_group_list(group, autounsubscribe))
1095 return;
1096
1097 /*
1098 * Try to add the group to our selection list. If this fails, we're
1099 * probably using -n, so we fake an entry with no counts. The count will
1100 * be properly updated when we enter the group. Otherwise there is some
1101 * mismatch in the active.times data and we ignore the newgroup.
1102 */
1103 if ((idx = my_group_add(group, FALSE)) < 0) {
1104 if (list_active) {
1105 /* my_fprintf(stderr, "subscribe_new_group: %s not in active[] && list_active\n", group); */
1106 return;
1107 }
1108
1109 if ((ptr = group_add(group)) != NULL)
1110 active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "y");
1111
1112 if ((idx = my_group_add(group, FALSE)) < 0)
1113 return;
1114 }
1115
1116 if (!no_write && (autosubscribe != NULL) && match_group_list(group, autosubscribe)) {
1117 if (!batch_mode || verbose)
1118 my_printf(_(txt_autosubscribed), group);
1119
1120 /*
1121 * as subscribe_new_group() is called from check_for_any_new_groups()
1122 * which has pending data on the socket if reading via NNTP we are not
1123 * allowed to issue any NNTP commands yet
1124 */
1125 subscribe(&active[my_group[idx]], SUBSCRIBED, bool_not(read_news_via_nntp));
1126 /*
1127 * Bad kluge to stop group later appearing in New newsgroups. This
1128 * effectively loses the group, and it has now been subscribed to and
1129 * so will be reread later by read_newsrc()
1130 */
1131 selmenu.max--;
1132 } else
1133 active[my_group[idx]].newgroup = TRUE;
1134 }
1135
1136
1137 /*
1138 * See if group is a member of group_list, returning a boolean.
1139 * group_list is a comma separated list of newsgroups, ! implies NOT
1140 * The same degree of wildcarding as used elsewhere in tin is allowed
1141 */
1142 t_bool
1143 match_group_list(
1144 const char *group,
1145 const char *group_list)
1146 {
1147 char *separator;
1148 char pattern[HEADER_LEN];
1149 size_t group_len, list_len;
1150 t_bool negate, accept = FALSE;
1151
1152 list_len = strlen(group_list);
1153 /*
1154 * walk through comma-separated entries in list
1155 */
1156 while (list_len != 0) {
1157 /*
1158 * find end/length of this entry
1159 */
1160 separator = strchr(group_list, ',');
1161 group_len = MIN(((separator == NULL) ? list_len : (size_t) (separator - group_list)), sizeof(pattern) - 1);
1162
1163 if ((negate = (*group_list == '!'))) {
1164 /*
1165 * a '!' before the pattern inverts sense of match
1166 */
1167 group_list++;
1168 group_len--;
1169 list_len--;
1170 }
1171 /*
1172 * copy out the entry and terminate it properly
1173 */
1174 strncpy(pattern, group_list, group_len);
1175 pattern[group_len] = '\0';
1176 /*
1177 * case-insensitive wildcard match
1178 */
1179 if (GROUP_MATCH(group, pattern, TRUE))
1180 accept = bool_not(negate); /* matched! */
1181
1182 /*
1183 * now examine next entry if any
1184 */
1185 if (group_list[group_len] != '\0')
1186 group_len++; /* skip the separator */
1187
1188 group_list += group_len;
1189 list_len -= group_len;
1190 }
1191 return accept;
1192 }
1193
1194
1195 /*
1196 * Add or update an entry to the in-memory newnews[] array (The times newgroups
1197 * were last checked for a particular news server)
1198 * If this is first time we've been called, zero out the array.
1199 *
1200 * Side effects:
1201 * 'info' is modified. Caller should not depend on it.
1202 */
1203 void
1204 load_newnews_info(
1205 char *info)
1206 {
1207 char *ptr;
1208 int i;
1209 time_t new_time;
1210
1211 /*
1212 * initialize newnews[] if no entries
1213 */
1214 if (!num_newnews) {
1215 for (i = 0; i < max_newnews; i++) {
1216 newnews[i].host = NULL;
1217 newnews[i].time = (time_t) 0;
1218 }
1219 }
1220
1221 /*
1222 * Split 'info' into hostname and time
1223 */
1224 if ((ptr = strchr(info, ' ')) == NULL)
1225 return;
1226
1227 *ptr++ = '\0';
1228 new_time = (time_t) atol(ptr);
1229
1230 /*
1231 * If this is a new host entry, set it up
1232 */
1233 if ((i = find_newnews_index(info)) == -1) {
1234 i = num_newnews++;
1235
1236 if (i >= max_newnews)
1237 expand_newnews();
1238 newnews[i].host = my_strdup(info);
1239 }
1240
1241 newnews[i].time = new_time;
1242
1243 #ifdef DEBUG
1244 if ((debug & DEBUG_NNTP) && verbose > 1)
1245 debug_print_file("NNTP", "ACTIVE host=[%s] time=[%lu]", newnews[i].host, (unsigned long int) newnews[i].time);
1246 #endif /* DEBUG */
1247 }
1248
1249
1250 /*
1251 * Return the index of cur_newnews_host in newnews[] or -1 if not found
1252 */
1253 int
1254 find_newnews_index(
1255 const char *cur_newnews_host)
1256 {
1257 int i;
1258
1259 for (i = 0; i < num_newnews; i++) {
1260 if (STRCMPEQ(cur_newnews_host, newnews[i].host))
1261 return i;
1262 }
1263
1264 return -1;
1265 }
1266
1267
1268 /*
1269 * Get a single status char from the moderated field. Used on selection screen
1270 * and in header of group screen
1271 */
1272 char
1273 group_flag(
1274 char ch)
1275 {
1276 switch (ch) {
1277 case 'm':
1278 return 'M';
1279
1280 case 'x':
1281 case 'n':
1282 case 'j':
1283 return 'X';
1284
1285 case '=':
1286 return '=';
1287
1288 default:
1289 return ' ';
1290 }
1291 }
1292
1293
1294 void
1295 create_save_active_file(
1296 void)
1297 {
1298 char *fb;
1299 char group_path[PATH_LEN];
1300 char local_save_active_file[PATH_LEN];
1301
1302 joinpath(local_save_active_file, sizeof(local_save_active_file), rcdir, ACTIVE_SAVE_FILE);
1303
1304 if (no_write && file_size(local_save_active_file) != -1L)
1305 return;
1306
1307 if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, group_path, sizeof(group_path), NULL, FALSE)) {
1308 wait_message(0, _(txt_creating_active));
1309 print_active_head(local_save_active_file);
1310
1311 while (strlen(group_path) && group_path[strlen(group_path) - 1] == '/')
1312 group_path[strlen(group_path) - 1] = '\0';
1313
1314 fb = my_strdup(group_path);
1315 make_group_list(local_save_active_file, (cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, fb, group_path);
1316 free(fb);
1317 }
1318 }
1319
1320
1321 static void
1322 make_group_list(
1323 char *active_file,
1324 char *base_dir,
1325 char *fixed_base,
1326 char *group_path)
1327 {
1328 DIR *dir;
1329 DIR_BUF *direntry;
1330 char *ptr;
1331 char filename[PATH_LEN];
1332 char path[PATH_LEN];
1333 t_artnum art_max;
1334 t_artnum art_min;
1335 struct stat stat_info;
1336 t_bool is_dir;
1337
1338 if ((dir = opendir(group_path)) != NULL) {
1339 is_dir = FALSE;
1340 while ((direntry = readdir(dir)) != NULL) {
1341 STRCPY(filename, direntry->d_name);
1342 joinpath(path, sizeof(path), group_path, filename);
1343 if (!(filename[0] == '.' && filename[1] == '\0') &&
1344 !(filename[0] == '.' && filename[1] == '.' && filename[2] == '\0')) {
1345 if (stat(path, &stat_info) != -1) {
1346 if (S_ISDIR(stat_info.st_mode))
1347 is_dir = TRUE;
1348 }
1349 }
1350 if (is_dir) {
1351 is_dir = FALSE;
1352 strcpy(group_path, path);
1353
1354 make_group_list(active_file, base_dir, fixed_base, group_path);
1355 find_art_max_min(group_path, &art_max, &art_min);
1356 append_group_line(active_file, group_path + strlen(fixed_base) + 1, art_max, art_min, fixed_base);
1357 if ((ptr = strrchr(group_path, '/')) != NULL)
1358 *ptr = '\0';
1359 }
1360 }
1361 CLOSEDIR(dir);
1362 }
1363 }
1364
1365
1366 static void
1367 append_group_line(
1368 char *active_file,
1369 char *group_path,
1370 t_artnum art_max,
1371 t_artnum art_min,
1372 char *base_dir)
1373 {
1374 FILE *fp;
1375 char *file_tmp;
1376
1377 if (art_max == 0 && art_min == 1)
1378 return;
1379
1380 file_tmp = get_tmpfilename(active_file);
1381
1382 if (!backup_file(active_file, file_tmp)) {
1383 free(file_tmp);
1384 return;
1385 }
1386
1387 if ((fp = fopen(active_file, "a+")) != NULL) {
1388 char *ptr;
1389 char *group_name;
1390 int err;
1391
1392 ptr = group_name = my_strdup(group_path);
1393 ptr++;
1394 while ((ptr = strchr(ptr, '/')) != NULL)
1395 *ptr = '.';
1396
1397 wait_message(0, "Appending=[%s %"T_ARTNUM_PFMT" %"T_ARTNUM_PFMT" %s]\n", group_name, art_max, art_min, base_dir);
1398 print_group_line(fp, group_name, art_max, art_min, base_dir);
1399 if ((err = ferror(fp)) || fclose(fp)) { /* TODO: issue warning? */
1400 if (err) {
1401 clearerr(fp);
1402 fclose(fp);
1403 }
1404 err = rename(file_tmp, active_file);
1405 #ifdef DEBUG
1406 if ((debug & DEBUG_MISC) && err) /* TODO: is this the right debug-level? */
1407 perror_message(_(txt_rename_error), file_tmp, active_file);
1408 #endif /* DEBUG */
1409 }
1410 free(group_name);
1411 }
1412 unlink(file_tmp);
1413 free(file_tmp);
1414 }