1 /*
2  *  Project   : tin - a Usenet reader
3  *  Module    : newsrc.c
4  *  Author    : I. Lea & R. Skrenta
5  *  Created   : 1991-04-01
6  *  Updated   : 2020-05-09
7  *  Notes     : ArtCount = (ArtMax - ArtMin) + 1  [could have holes]
8  *
9  * Copyright (c) 1991-2021 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 TCURSES_H
45 #	include "tcurses.h"
46 #endif /* !TCURSES_H */
47 #ifndef TNNTP_H
48 #	include "tnntp.h"
49 #endif /* !TNNTP_H */
50 #ifndef NEWSRC_H
51 #	include "newsrc.h"
52 #endif /* !NEWSRC_H */
53 
54 static mode_t newsrc_mode = 0;
55 
56 /*
57  * Local prototypes
58  */
59 static FILE *open_subscription_fp(void);
60 static char *parse_newsrc_line(char *line, int *sub);
61 static char *parse_subseq(struct t_group *group, char *seq, t_artnum *low, t_artnum *high, t_artnum *sum);
62 static char *parse_get_seq(char *seq, t_artnum *low, t_artnum *high);
63 static int write_newsrc_line(FILE *fp, char *line);
64 static t_bool create_newsrc(char *newsrc_file);
65 static void auto_subscribe_groups(char *newsrc_file);
66 static void get_subscribe_info(struct t_group *grp);
67 static void parse_bitmap_seq(struct t_group *group, char *seq);
68 static void print_bitmap_seq(FILE *fp, struct t_group *group);
69 
70 
71 /*
72  * Read .newsrc into my_group[]. my_group[] ints point to active[] entries.
73  * If allgroups is set, then my_group[] is completely overwritten,
74  * otherwise, groups are appended. Any bogus groups will be handled
75  * accordingly. Bogus groups will _not_ be subscribed to as a design
76  * principle.
77  *
78  * Returns the number of lines read(useful for a check newsrc >= oldnewsrc)
79  * 	< 0 error
80  * 	>=0 number of lines read
81  */
82 signed long int
read_newsrc(char * newsrc_file,t_bool allgroups)83 read_newsrc(
84 	char *newsrc_file,
85 	t_bool allgroups)
86 {
87 	FILE *fp;
88 	char *grp, *seq;
89 	int sub, i;
90 	signed long line_count = 0;
91 	struct stat statbuf;
92 
93 	if (allgroups)
94 		selmenu.max = skip_newgroups();
95 
96 	/*
97 	 * make a .newsrc if none exist & auto subscribe to set groups
98 	 */
99 	if (stat(newsrc_file, &statbuf) == -1) {
100 		if (!create_newsrc(newsrc_file))
101 			return -1L; /* ouch */
102 		auto_subscribe_groups(newsrc_file);
103 	} else
104 		newsrc_mode = statbuf.st_mode;
105 
106 	if ((fp = fopen(newsrc_file, "r")) != NULL) {
107 		if (!batch_mode || verbose)
108 			wait_message(0, _(txt_reading_newsrc));
109 
110 		while ((grp = tin_fgets(fp, FALSE)) != NULL) {
111 			seq = parse_newsrc_line(grp, &sub);
112 			line_count++;
113 
114 			if (sub == SUBSCRIBED) {
115 				if ((i = my_group_add(grp, FALSE)) >= 0) {
116 					if (!active[my_group[i]].bogus) {
117 						active[my_group[i]].subscribed = SUB_BOOL(sub);
118 						parse_bitmap_seq(&active[my_group[i]], seq);
119 					}
120 				} else
121 					process_bogus(grp);
122 			}
123 		}
124 		fclose(fp);
125 		/* If you aborted with 'q', then you get what you get. */
126 
127 		if (!batch_mode || verbose)
128 			my_fputc('\n', stdout);
129 
130 		if (!cmd_line && !batch_mode)
131 			clear_message();
132 	}
133 	return line_count;
134 }
135 
136 
137 /*
138  * Parse a line from the newsrc file and write it back out with updated
139  * sequence information. Return number of lines written (ie, 0 or 1)
140  */
141 static int
write_newsrc_line(FILE * fp,char * line)142 write_newsrc_line(
143 	FILE *fp,
144 	char *line)
145 {
146 	char *seq;
147 	int sub;
148 	struct t_group *group;
149 
150 	seq = parse_newsrc_line(line, &sub);
151 
152 	if (line[0] == '\0' || sub == 0)		/* Insurance against blank line */
153 		return 0;
154 
155 	if (seq == NULL) {		/* line has no ':' or '!' in it */
156 		if (tinrc.strip_bogus == BOGUS_REMOVE)
157 			wait_message(2, _(txt_remove_bogus), line);
158 		return 0;
159 	}
160 
161 	/*
162 	 * Find the group in active. If we cannot, then junk it if bogus groups
163 	 * are set to auto removal. Also check for bogus flag just in case
164 	 * strip_bogus was changed since tin started
165 	 */
166 	group = group_find(line, FALSE);
167 
168 	if (tinrc.strip_bogus == BOGUS_REMOVE) {
169 		if (group == NULL || group->bogus) { /* group doesn't exist */
170 			wait_message(2, _(txt_remove_bogus), line);
171 			return 0;
172 		}
173 	}
174 
175 	if ((group && group->newsrc.present) && (group->subscribed || !tinrc.strip_newsrc)) {
176 		fprintf(fp, "%s%c ", group->name, SUB_CHAR(group->subscribed));
177 		print_bitmap_seq(fp, group);
178 		return 1;
179 	} else {
180 		if (sub == SUBSCRIBED || !tinrc.strip_newsrc) {
181 			fprintf(fp, "%s%c %s\n", line, sub, seq);
182 			return 1;
183 		}
184 	}
185 	return 0;
186 }
187 
188 
189 /*
190  * Read in the users newsrc file and write a new file with all the changes
191  * changes from the current session. If this works, replace the original
192  * newsrc file.
193  * Returns < 0 on error
194  *         >=0 number of lines written
195  */
196 signed long int
write_newsrc(void)197 write_newsrc(
198 	void)
199 {
200 	FILE *fp_ip;
201 	FILE *fp_op;
202 	char *line;
203 	signed long int tot = 0L;
204 	struct stat note_stat_newsrc;
205 	t_bool write_ok = FALSE;
206 	int err;
207 
208 	if (no_write)
209 		return 0L;
210 
211 	if ((fp_ip = fopen(newsrc, "r")) == NULL)
212 		return -1L; /* can't open newsrc */
213 
214 	/* get size of original newsrc */
215 	if (fstat(fileno(fp_ip), &note_stat_newsrc) != 0) {
216 		fclose(fp_ip);
217 		return -1L; /* can't access newsrc */
218 	}
219 
220 	if (!note_stat_newsrc.st_size) {
221 		fclose(fp_ip);
222 		return 0L; /* newsrc is empty */
223 	}
224 
225 	if ((fp_op = fopen(newnewsrc, "w")) != NULL) {
226 		if (newsrc_mode)
227 #ifdef HAVE_FCHMOD
228 			fchmod(fileno(fp_op), newsrc_mode);
229 #else
230 #	ifdef HAVE_CHMOD
231 			chmod(newnewsrc, newsrc_mode);
232 #	endif /* HAVE_CHMOD */
233 #endif /* HAVE_FCHMOD */
234 
235 		while ((line = tin_fgets(fp_ip, FALSE)) != NULL)
236 			tot += write_newsrc_line(fp_op, line);
237 
238 		/*
239 		 * Don't rename if either fclose() fails or ferror() is set
240 		 */
241 		if ((err = ferror(fp_op)) || fclose(fp_op)) {
242 			error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
243 			unlink(newnewsrc);
244 			if (err) {
245 				clearerr(fp_op);
246 				fclose(fp_op);
247 			}
248 		} else
249 			write_ok = TRUE;
250 	}
251 
252 	fclose(fp_ip);
253 
254 	if (tot < 1) {
255 		error_message(2, _(txt_newsrc_nogroups));
256 		unlink(newnewsrc);
257 		return 0L;		/* So we don't get prompted to try again */
258 	}
259 
260 	if (write_ok)
261 		rename_file(newnewsrc, newsrc);
262 
263 	return tot;
264 }
265 
266 
267 /*
268  * Create a newsrc from active[] groups. Subscribe to all groups.
269  */
270 static t_bool
create_newsrc(char * newsrc_file)271 create_newsrc(
272 	char *newsrc_file)
273 {
274 	FILE *fp;
275 	int i;
276 
277 	if ((fp = fopen(newsrc_file, "w")) != NULL) {
278 		wait_message(0, _(txt_creating_newsrc));
279 
280 		for_each_group(i)
281 			fprintf(fp, "%s!\n", active[i].name);
282 
283 		if ((i = ferror(fp)) || fclose(fp)) {
284 			error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
285 			if (i) {
286 				clearerr(fp);
287 				fclose(fp);
288 			}
289 			return FALSE;
290 		}
291 		return TRUE; /* newsrc created */
292 	}
293 	return FALSE;
294 }
295 
296 
297 /*
298  * Get a list of default groups to subscribe to
299  */
300 static FILE *
open_subscription_fp(void)301 open_subscription_fp(
302 	void)
303 {
304 	if (!read_saved_news) {
305 #ifdef NNTP_ABLE
306 		if (read_news_via_nntp) {
307 			/* RFC 6048 2.6 */
308 			if (nntp_caps.type == CAPABILITIES && !nntp_caps.list_subscriptions)
309 				return NULL;
310 			else
311 				return (nntp_command("LIST SUBSCRIPTIONS", OK_GROUPS, NULL, 0));
312 		} else
313 #endif /* NNTP_ABLE */
314 			return (fopen(subscriptions_file, "r"));
315 	} else
316 		return NULL;
317 }
318 
319 
320 /*
321  * Automatically subscribe user to newsgroups specified in
322  * NEWSLIBDIR/subscriptions (locally) or same file but from NNTP
323  * server (LIST SUBSCRIPTIONS) and create .newsrc
324  */
325 static void
auto_subscribe_groups(char * newsrc_file)326 auto_subscribe_groups(
327 	char *newsrc_file)
328 {
329 	FILE *fp_newsrc;
330 	FILE *fp_subs;
331 	char *ptr;
332 	int err;
333 
334 	/*
335 	 * If subscription file exists then first unsubscribe to all groups
336 	 * and then subscribe to just the auto specified groups.
337 	 */
338 	if ((fp_subs = open_subscription_fp()) == NULL)
339 		return;
340 
341 	if (!batch_mode)
342 		wait_message(0, _(txt_autosubscribing_groups));
343 
344 	if ((fp_newsrc = fopen(newsrc_file, "w")) == NULL) {
345 		TIN_FCLOSE(fp_subs);
346 		return;
347 	}
348 
349 	if (newsrc_mode)
350 #ifdef HAVE_FCHMOD
351 		fchmod(fileno(fp_newsrc), newsrc_mode);
352 #else
353 #	ifdef HAVE_CHMOD
354 		chmod(newsrc_file, newsrc_mode);
355 #	endif /* HAVE_CHMOD */
356 #endif /* HAVE_FCHMOD */
357 
358 	/* TODO: test me! */
359 	while ((ptr = tin_fgets(fp_subs, FALSE)) != NULL) {
360 		if (ptr[0] != '#') {
361 			if (group_find(ptr, FALSE) != NULL)
362 				fprintf(fp_newsrc, "%s:\n", ptr);
363 		}
364 	}
365 
366 	/* We ignore user 'q'uits here. They will get them next time in any case */
367 
368 	if ((err = ferror(fp_newsrc)) || fclose(fp_newsrc)) {
369 		error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
370 		if (err) {
371 			clearerr(fp_newsrc);
372 			fclose(fp_newsrc);
373 		}
374 	}
375 
376 	TIN_FCLOSE(fp_subs);
377 }
378 
379 
380 /*
381  * make a backup of users .newsrc in case of the bogie man
382  */
383 void
backup_newsrc(void)384 backup_newsrc(
385 	void)
386 {
387 	char dirbuf[PATH_LEN];
388 	char filebuf[PATH_LEN];
389 	struct stat statbuf;
390 
391 #ifdef NNTP_ABLE
392 	if (read_news_via_nntp && !read_saved_news && nntp_tcp_port != IPPORT_NNTP)
393 		snprintf(filebuf, sizeof(filebuf), "%s:%u", nntp_server, nntp_tcp_port);
394 	else
395 #endif /* NNTP_ABLE */
396 	{
397 		STRCPY(filebuf, quote_space_to_dash(nntp_server));
398 	}
399 	joinpath(dirbuf, sizeof(dirbuf), rcdir, filebuf);
400 	joinpath(filebuf, sizeof(filebuf), dirbuf, OLDNEWSRC_FILE);
401 
402 	if (stat(dirbuf, &statbuf) == -1) {
403 		if (my_mkdir(dirbuf, (mode_t) (S_IRWXU)) == -1)
404 			/* Can't create directory: Fall back on Homedir */
405 			joinpath(filebuf, sizeof(filebuf), homedir, OLDNEWSRC_FILE);
406 	}
407 
408 	if (!backup_file(newsrc, filebuf))
409 		error_message(2, _(txt_filesystem_full_backup), NEWSRC_FILE);
410 }
411 
412 
413 /*
414  * Find the total, max & min articles number for specified group
415  * Use nntp GROUP command or read local spool
416  * Return 0, or -error
417  */
418 int
group_get_art_info(char * tin_spooldir,char * groupname,int grouptype,t_artnum * art_count,t_artnum * art_max,t_artnum * art_min)419 group_get_art_info(
420 	char *tin_spooldir,
421 	char *groupname,
422 	int grouptype,
423 	t_artnum *art_count,
424 	t_artnum *art_max,
425 	t_artnum *art_min)
426 {
427 	DIR *dir;
428 	DIR_BUF *direntry;
429 	t_artnum artnum;
430 
431 	if (read_news_via_nntp && grouptype == GROUP_TYPE_NEWS) {
432 #ifdef NNTP_ABLE
433 		char line[NNTP_STRLEN];
434 
435 		snprintf(line, sizeof(line), "GROUP %s", groupname);
436 #	ifdef DEBUG
437 		if ((debug & DEBUG_NNTP) && verbose > 1)
438 			debug_print_file("NNTP", "group_get_art_info %s", line);
439 #	endif /* DEBUG */
440 		put_server(line);
441 
442 		switch (get_respcode(line, sizeof(line))) {
443 			case OK_GROUP:
444 				if (sscanf(line, "%"T_ARTNUM_SFMT" %"T_ARTNUM_SFMT" %"T_ARTNUM_SFMT, art_count, art_min, art_max) != 3) {
445 #	ifdef DEBUG
446 					if ((debug & DEBUG_NNTP) && verbose > 1)
447 						debug_print_file("NNTP", "Invalid response to GROUP command, %s", line);
448 #	endif /* DEBUG */
449 				}
450 				break;
451 
452 			case ERR_NOGROUP:
453 				*art_count = T_ARTNUM_CONST(0);
454 				*art_min = T_ARTNUM_CONST(1);
455 				*art_max = T_ARTNUM_CONST(0);
456 				return -ERR_NOGROUP;
457 
458 			case ERR_ACCESS:
459 				tin_done(NNTP_ERROR_EXIT, "%s", line);
460 				/* keep lint quiet: */
461 				/* NOTREACHED */
462 				break;
463 
464 			default:
465 #	ifdef DEBUG
466 				if ((debug & DEBUG_NNTP) && verbose > 1)
467 					debug_print_file("NNTP", "NOT_OK %s", line);
468 #	endif /* DEBUG */
469 				return -1;
470 		}
471 #else
472 		my_fprintf(stderr, _("Unreachable?\n")); /* TODO: -> lang.c */
473 		return 0;
474 #endif /* NNTP_ABLE */
475 	} else {
476 		char group_path[PATH_LEN];
477 		*art_count = T_ARTNUM_CONST(0);
478 		*art_min = T_ARTNUM_CONST(0);
479 		*art_max = T_ARTNUM_CONST(0);
480 
481 		make_base_group_path(tin_spooldir, groupname, group_path, sizeof(group_path));
482 
483 		if ((dir = opendir(group_path)) != NULL) {
484 			while ((direntry = readdir(dir)) != NULL) {
485 				artnum = atoartnum(direntry->d_name); /* should be '\0' terminated... */
486 				if (artnum >= T_ARTNUM_CONST(1)) {
487 					if (artnum > *art_max) {
488 						*art_max = artnum;
489 						if (*art_min == T_ARTNUM_CONST(0))
490 							*art_min = artnum;
491 					} else if (artnum < *art_min)
492 						*art_min = artnum;
493 					(*art_count)++;
494 				}
495 			}
496 			CLOSEDIR(dir);
497 			if (*art_min == T_ARTNUM_CONST(0))
498 				*art_min = T_ARTNUM_CONST(1);
499 		} else {
500 			*art_min = T_ARTNUM_CONST(1);
501 			return -1;
502 		}
503 	}
504 
505 	return 0;
506 }
507 
508 
509 /*
510  * Get and fixup (if needed) the counters for a newly subscribed group
511  */
512 static void
get_subscribe_info(struct t_group * grp)513 get_subscribe_info(
514 	struct t_group *grp)
515 {
516 	t_artnum oldmin = grp->xmin;
517 	t_artnum oldmax = grp->xmax;
518 
519 	group_get_art_info(grp->spooldir, grp->name, grp->type, &grp->count, &grp->xmax, &grp->xmin);
520 
521 	if (grp->newsrc.num_unread > grp->count) {
522 #ifdef DEBUG
523 		if (debug & DEBUG_NEWSRC) { /* TODO: is this the right debug-level? */
524 			my_printf(cCRLF "Unread WRONG %s unread=[%"T_ARTNUM_PFMT"] count=[%"T_ARTNUM_PFMT"]", grp->name, grp->newsrc.num_unread, grp->count);
525 			my_flush();
526 		}
527 #endif /* DEBUG */
528 		grp->newsrc.num_unread = grp->count;
529 	}
530 
531 	if (grp->xmin != oldmin || grp->xmax != oldmax) {
532 		expand_bitmap(grp, 0);
533 #ifdef DEBUG
534 		if (debug & DEBUG_NEWSRC) { /* TODO: is this the right debug-level? */
535 			my_printf(cCRLF "Min/Max DIFF %s old=[%"T_ARTNUM_PFMT"-%"T_ARTNUM_PFMT"] new=[%"T_ARTNUM_PFMT"-%"T_ARTNUM_PFMT"]", grp->name, oldmin, oldmax, grp->xmin, grp->xmax);
536 			my_flush();
537 		}
538 #endif /* DEBUG */
539 	}
540 }
541 
542 
543 /*
544  * Subscribe/unsubscribe to a group in .newsrc.
545  * This involves rewriting the .newsrc with the new info
546  * If get_info is set we are allowed to issue NNTP commands if needed
547  */
548 void
subscribe(struct t_group * group,int sub_state,t_bool get_info)549 subscribe(
550 	struct t_group *group,
551 	int sub_state,
552 	t_bool get_info)
553 {
554 	FILE *fp;
555 	FILE *newfp;
556 	char *line;
557 	char *seq;
558 	int sub;
559 	t_bool found = FALSE;
560 
561 	if (no_write)
562 		return;
563 
564 	if ((newfp = fopen(newnewsrc, "w")) == NULL)
565 		return;
566 
567 	if (newsrc_mode)
568 #ifdef HAVE_FCHMOD
569 		fchmod(fileno(newfp), newsrc_mode);
570 #else
571 #	ifdef HAVE_CHMOD
572 		chmod(newnewsrc, newsrc_mode);
573 #	endif /* HAVE_CHMOD */
574 #endif /* HAVE_FCHMOD */
575 
576 	if ((fp = fopen(newsrc, "r")) != NULL) {
577 		while ((line = tin_fgets(fp, FALSE)) != NULL) {
578 			if ((seq = parse_newsrc_line(line, &sub))) {
579 				if (STRCMPEQ(line, group->name)) {
580 					fprintf(newfp, "%s%c %s\n", line, sub_state, seq);
581 					group->subscribed = SUB_BOOL(sub_state);
582 
583 					/* If previously subscribed to in .newsrc, load up any existing information */
584 					if (sub_state == SUBSCRIBED)
585 						parse_bitmap_seq(group, seq);
586 
587 					found = TRUE;
588 				} else
589 					fprintf(newfp, "%s%c %s\n", line, sub, seq);
590 			}
591 		}
592 
593 		fclose(fp);
594 
595 		if (!found) {
596 			wait_message(0, _(txt_subscribing));
597 			group->subscribed = SUB_BOOL(sub_state);
598 			if (sub_state == SUBSCRIBED) {
599 				fprintf(newfp, "%s%c ", group->name, sub_state);
600 				if (get_info) {
601 					get_subscribe_info(group);
602 					print_bitmap_seq(newfp, group);
603 				} else /* we are not allowed to issue NNTP cmds during AUTOSUBSCRIBE loop */
604 					fprintf(newfp, "1\n");
605 			} else
606 				fprintf(newfp, "%s%c\n", group->name, sub_state);
607 		}
608 	}
609 
610 	if ((sub = ferror(newfp)) || fclose(newfp)) {
611 		error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
612 		if (sub) {
613 			clearerr(newfp);
614 			fclose(newfp);
615 		}
616 		unlink(newnewsrc);
617 	} else
618 		rename_file(newnewsrc, newsrc);
619 }
620 
621 
622 void
reset_newsrc(void)623 reset_newsrc(
624 	void)
625 {
626 	FILE *fp;
627 	FILE *newfp;
628 	char *line;
629 	int sub, i;
630 
631 	if (!no_write && (newfp = fopen(newnewsrc, "w")) != NULL) {
632 		if (newsrc_mode)
633 #ifdef HAVE_FCHMOD
634 			fchmod(fileno(newfp), newsrc_mode);
635 #else
636 #	ifdef HAVE_CHMOD
637 			chmod(newnewsrc, newsrc_mode);
638 #	endif /* HAVE_CHMOD */
639 #endif /* HAVE_FCHMOD */
640 
641 		if ((fp = fopen(newsrc, "r")) != NULL) {
642 			while ((line = tin_fgets(fp, FALSE)) != NULL) {
643 				(void) parse_newsrc_line(line, &sub);
644 				fprintf(newfp, "%s%c\n", line, sub);
645 			}
646 			fclose(fp);
647 		}
648 		if ((sub = ferror(newfp)) || fclose(newfp)) {
649 			error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
650 			if (sub) {
651 				clearerr(newfp);
652 				fclose(newfp);
653 			}
654 			unlink(newnewsrc);
655 		} else
656 			rename_file(newnewsrc, newsrc);
657 	}
658 
659 	for (i = 0; i < selmenu.max; i++)
660 		set_default_bitmap(&active[my_group[i]]);
661 }
662 
663 
664 /*
665  * Rewrite the newsrc file, without the specified group
666  */
667 void
delete_group(char * group)668 delete_group(
669 	char *group)
670 {
671 	FILE *fp;
672 	FILE *newfp;
673 	char *line;
674 	char *seq;
675 	int sub;
676 
677 	if (no_write)
678 		return;
679 
680 	if ((newfp = fopen(newnewsrc, "w")) != NULL) {
681 		if (newsrc_mode)
682 #ifdef HAVE_FCHMOD
683 			fchmod(fileno(newfp), newsrc_mode);
684 #else
685 #	ifdef HAVE_CHMOD
686 			chmod(newnewsrc, newsrc_mode);
687 #	endif /* HAVE_CHMOD */
688 #endif /* HAVE_FCHMOD */
689 
690 		if ((fp = fopen(newsrc, "r")) != NULL) {
691 			while ((line = tin_fgets(fp, FALSE)) != NULL) {
692 				if ((seq = parse_newsrc_line(line, &sub))) {
693 					if (!STRCMPEQ(line, group))
694 						fprintf(newfp, "%s%c %s\n", line, sub, seq);
695 				}
696 			}
697 			fclose(fp);
698 		}
699 
700 		if ((sub = ferror(newfp)) || fclose(newfp)) {
701 			error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
702 			if (sub) {
703 				clearerr(newfp);
704 				fclose(newfp);
705 			}
706 			unlink(newnewsrc);
707 		} else
708 			rename_file(newnewsrc, newsrc);
709 	}
710 }
711 
712 
713 /*
714  * Mark a group as read
715  * If art != NULL then we explicitly process each article thus
716  * catching crossposts as well, otherwise we simply scrub the
717  * bitmap and adjust the highwater mark.
718  */
719 void
grp_mark_read(struct t_group * group,struct t_article * art)720 grp_mark_read(
721 	struct t_group *group,
722 	struct t_article *art)
723 {
724 	int i;
725 
726 #ifdef DEBUG
727 	if (debug & DEBUG_NEWSRC)
728 		debug_print_comment("c/C command");
729 #endif /* DEBUG */
730 
731 	if (art != NULL) {
732 		for_each_art(i)
733 			art_mark(group, &art[i], ART_READ);
734 	} else {
735 		FreeAndNull(group->newsrc.xbitmap);
736 		group->newsrc.xbitlen = 0;
737 		if (group->xmax > group->newsrc.xmax)
738 			group->newsrc.xmax = group->xmax;
739 		group->newsrc.xmin = group->newsrc.xmax + 1;
740 		group->newsrc.num_unread = 0;
741 	}
742 }
743 
744 
745 void
grp_mark_unread(struct t_group * group)746 grp_mark_unread(
747 	struct t_group *group)
748 {
749 	t_artnum bitlength;
750 	t_bitmap *newbitmap = (t_bitmap *) 0;
751 
752 #ifdef DEBUG
753 	if (debug & DEBUG_NEWSRC)
754 		debug_print_comment("Z command");
755 #endif /* DEBUG */
756 
757 	group_get_art_info(group->spooldir, group->name, group->type, &group->count, &group->xmax, &group->xmin);
758 
759 	group->newsrc.num_unread = group->count;
760 	if (group->xmax > group->newsrc.xmax)
761 		group->newsrc.xmax = group->xmax;
762 	if (group->xmin > 0)
763 		group->newsrc.xmin = group->xmin;
764 
765 	bitlength = MAX(0, group->newsrc.xmax - group->newsrc.xmin + 1);
766 
767 	if (bitlength > 0)
768 		newbitmap = my_malloc(BITS_TO_BYTES(bitlength));
769 
770 	FreeIfNeeded(group->newsrc.xbitmap);
771 	group->newsrc.xbitmap = newbitmap;
772 	group->newsrc.xbitlen = bitlength;
773 
774 	if (bitlength)
775 		NSETRNG1(group->newsrc.xbitmap, T_ARTNUM_CONST(0), bitlength - T_ARTNUM_CONST(1));
776 
777 #ifdef DEBUG
778 	if (debug & DEBUG_NEWSRC)
779 		debug_print_bitmap(group, NULL);
780 #endif /* DEBUG */
781 }
782 
783 
784 void
thd_mark_read(struct t_group * group,long thread)785 thd_mark_read(
786 	struct t_group *group,
787 	long thread)
788 {
789 	int i;
790 
791 #ifdef DEBUG
792 	if (debug & DEBUG_NEWSRC)
793 		debug_print_comment("Mark thread read K command");
794 #endif /* DEBUG */
795 
796 	for (i = (int) thread; i >= 0; i = arts[i].thread)
797 		art_mark(group, &arts[i], ART_READ);
798 }
799 
800 
801 void
thd_mark_unread(struct t_group * group,long thread)802 thd_mark_unread(
803 	struct t_group *group,
804 	long thread)
805 {
806 	int i;
807 
808 #ifdef DEBUG
809 	if (debug & DEBUG_NEWSRC)
810 		debug_print_comment("Mark thread unread Z command");
811 #endif /* DEBUG */
812 
813 	for (i = (int) thread; i >= 0; i = arts[i].thread)
814 		art_mark(group, &arts[i], ART_WILL_RETURN);
815 }
816 
817 
818 /*
819  * Parse the newsrc sequence for the specified group
820  */
821 static void
parse_bitmap_seq(struct t_group * group,char * seq)822 parse_bitmap_seq(
823 	struct t_group *group,
824 	char *seq)
825 {
826 	char *ptr;
827 	t_artnum sum = T_ARTNUM_CONST(0);
828 	t_artnum low = T_ARTNUM_CONST(0);
829 	t_artnum high = T_ARTNUM_CONST(0);
830 	t_artnum min, max;
831 	t_bool gotseq = FALSE;
832 
833 	/*
834 	 * Skip possible non-numeric prefix
835 	 */
836 	ptr = seq;
837 	while (ptr && *ptr && (*ptr < '0' || *ptr > '9'))
838 		ptr++;
839 
840 #ifdef DEBUG
841 	if (debug & DEBUG_NEWSRC) {
842 		char buf[NEWSRC_LINE];
843 
844 		snprintf(buf, sizeof(buf), "Parsing [%s%c %.*s]", group->name, SUB_CHAR(group->subscribed), (int) (NEWSRC_LINE - strlen(group->name) - 14), BlankIfNull(ptr));
845 		debug_print_comment(buf);
846 		debug_print_bitmap(group, NULL);
847 	}
848 #endif /* DEBUG */
849 
850 	if (ptr) {
851 		gotseq = TRUE;
852 		ptr = parse_get_seq(ptr, &low, &high);
853 
854 		if (high < group->xmin - 1)
855 			high = group->xmin - 1;
856 
857 		min = ((low <= 1) ? (high + 1) : 1);
858 
859 		if (group->xmin > min)
860 			min = group->xmin;
861 
862 		if (group->xmax > high)
863 			max = group->xmax;
864 		else
865 			max = high;		/* trust newsrc's max */
866 
867 		FreeAndNull(group->newsrc.xbitmap);
868 		group->newsrc.xmax = max;
869 		group->newsrc.xmin = min;
870 		group->newsrc.xbitlen = (max - min) + 1;
871 		if (group->newsrc.xbitlen > 0) {
872 			group->newsrc.xbitmap = my_malloc(BITS_TO_BYTES(group->newsrc.xbitlen));
873 			NSETRNG1(group->newsrc.xbitmap, T_ARTNUM_CONST(0), group->newsrc.xbitlen - T_ARTNUM_CONST(1));
874 		}
875 
876 		if (min <= high) {
877 			if (low > min)
878 				sum = low - min;
879 			else
880 				low = min;
881 			NSETRNG0(group->newsrc.xbitmap, low - min, high - min);
882 		}
883 
884 		/*
885 		 * Pick up any additional articles/ranges after the first
886 		 */
887 		while (*ptr)
888 			ptr = parse_subseq(group, ptr, &low, &high, &sum);
889 	} else {
890 		FreeAndNull(group->newsrc.xbitmap);
891 		group->newsrc.xmax = group->xmax;
892 		if (group->xmin > 0)
893 			group->newsrc.xmin = group->xmin;
894 		else
895 			group->newsrc.xmin = 1;
896 		group->newsrc.xbitlen = (group->newsrc.xmax - group->newsrc.xmin) + 1;
897 		if (group->newsrc.xbitlen > 0) {
898 			group->newsrc.xbitmap = my_malloc(BITS_TO_BYTES(group->newsrc.xbitlen));
899 			NSETRNG1(group->newsrc.xbitmap, T_ARTNUM_CONST(0), group->newsrc.xbitlen - T_ARTNUM_CONST(1));
900 		}
901 /*
902 wait_message(2, "BITMAP Grp=[%s] MinMax=[%"T_ARTNUM_PFMT"-%"T_ARTNUM_PFMT"] Len=[%"T_ARTNUM_PFMT"]\n",
903 	group->name, group->xmin, group->xmax, group->newsrc.xbitlen);
904 */
905 	}
906 
907 	group->newsrc.present = TRUE;
908 
909 	if (gotseq) {
910 		if (group->newsrc.xmax > high)
911 			sum += group->newsrc.xmax - high;
912 	} else
913 		sum = (group->count >= 0) ? group->count : ((group->newsrc.xmax - group->newsrc.xmin) + 1);
914 
915 	group->newsrc.num_unread = sum;
916 #ifdef DEBUG
917 	if (debug & DEBUG_NEWSRC)
918 		debug_print_bitmap(group, NULL);
919 #endif /* DEBUG */
920 }
921 
922 
923 /*
924  * Parse a subsection of the newsrc sequencer ie., 1-34,23-90,93,97-99
925  * would parse the sequence if called in a loop in the following way:
926  *   1st call would parse  1-34 and return 23-90,93,97-99
927  *   2nd call would parse 23-90 and return 93,97-99
928  *   3rd call would parse    93 and return 97-99
929  *   4th call would parse 97-99 and return NULL
930  */
931 static char *
parse_subseq(struct t_group * group,char * seq,t_artnum * low,t_artnum * high,t_artnum * sum)932 parse_subseq(
933 	struct t_group *group,
934 	char *seq,
935 	t_artnum *low,
936 	t_artnum *high,
937 	t_artnum *sum)
938 {
939 	t_artnum bitmin;
940 	t_artnum bitmax;
941 	t_artnum last_high = *high;
942 
943 	seq = parse_get_seq(seq, low, high);
944 
945 	/*
946 	 * Bitmap index
947 	 */
948 	bitmin = *low - group->newsrc.xmin;
949 
950 	/*
951 	 * check that seq is not out of order
952 	 */
953 	if (*low > last_high)
954 		*sum += (*low - last_high) - 1;
955 
956 	if (*high == *low) {
957 		if (bitmin >= 0) {
958 			if (*high > group->newsrc.xmax) {
959 				/* We trust .newsrc's max. */
960 				t_artnum bitlen;
961 				t_bitmap *newbitmap;
962 
963 				group->newsrc.xmax = *high;
964 				bitlen = group->newsrc.xmax - group->newsrc.xmin + 1;
965 				newbitmap = my_malloc(BITS_TO_BYTES(bitlen));
966 
967 				/* Copy over old bitmap */
968 				memcpy(newbitmap, group->newsrc.xbitmap, BITS_TO_BYTES(group->newsrc.xbitlen));
969 
970 				/* Mark high numbered articles as unread */
971 				NSETRNG1(newbitmap, group->newsrc.xbitlen, bitlen - 1);
972 
973 				free(group->newsrc.xbitmap);
974 				group->newsrc.xbitmap = newbitmap;
975 				group->newsrc.xbitlen = bitlen;
976 			}
977 			NSET0(group->newsrc.xbitmap, bitmin);
978 		}
979 	} else if ((*low < *high) && (*high >= group->newsrc.xmin)) {
980 		/*
981 		 * Restrict the range to min..max
982 		 */
983 		if (bitmin < 0)
984 			bitmin = 0;
985 
986 		bitmax = *high;
987 
988 		if (bitmax > group->newsrc.xmax) {
989 			/* We trust .newsrc's max. */
990 			t_artnum bitlen;
991 			t_bitmap *newbitmap;
992 
993 			group->newsrc.xmax = bitmax;
994 			bitlen = group->newsrc.xmax - group->newsrc.xmin + 1;
995 			newbitmap = my_malloc(BITS_TO_BYTES(bitlen));
996 
997 			/* Copy over old bitmap */
998 			memcpy(newbitmap, group->newsrc.xbitmap, BITS_TO_BYTES(group->newsrc.xbitlen));
999 
1000 			/* Mark high numbered articles as unread */
1001 			NSETRNG1(newbitmap, group->newsrc.xbitlen, bitlen - 1);
1002 
1003 			free(group->newsrc.xbitmap);
1004 			group->newsrc.xbitmap = newbitmap;
1005 			group->newsrc.xbitlen = bitlen;
1006 		}
1007 
1008 		bitmax -= group->newsrc.xmin;
1009 
1010 		/*
1011 		 * Fill in the whole range as read
1012 		 */
1013 		NSETRNG0(group->newsrc.xbitmap, bitmin, bitmax);
1014 	}
1015 	return seq;
1016 }
1017 
1018 
1019 static char *
parse_get_seq(char * seq,t_artnum * low,t_artnum * high)1020 parse_get_seq(
1021 	char *seq,
1022 	t_artnum *low,
1023 	t_artnum *high)
1024 {
1025 	*low = strtoartnum(seq, &seq, 10);
1026 
1027 	if (*seq == '-') {	/* Range of articles */
1028 		seq++;
1029 		*high = strtoartnum(seq, &seq, 10);
1030 	} else	/* Single article */
1031 		*high = *low;
1032 
1033 	while (*seq && (*seq < '0' || *seq > '9'))
1034 		seq++;
1035 
1036 	return seq;
1037 }
1038 
1039 
1040 /*
1041  * Loop through arts[] array marking state of each article READ/UNREAD
1042  */
1043 void
parse_unread_arts(struct t_group * group,t_artnum min)1044 parse_unread_arts(
1045 	struct t_group *group,
1046 	t_artnum min)
1047 {
1048 	int i;
1049 	t_artnum unread = T_ARTNUM_CONST(0);
1050 	t_artnum bitmin, bitmax;
1051 	t_bitmap *newbitmap = (t_bitmap *) 0;
1052 
1053 	bitmin = group->newsrc.xmin;
1054 	bitmax = group->newsrc.xmax;
1055 
1056 	/*
1057 	 * TODO
1058 	 * what about group->newsrc.xmax > group->xmax?
1059 	 * that should indicate an artnum 'reset' on the server
1060 	 * (or using the "wrong" newsrc for that server)
1061 	 */
1062 
1063 	if (group->xmax > group->newsrc.xmax)
1064 		group->newsrc.xmax = group->xmax;
1065 
1066 	if (group->newsrc.xmax >= bitmin) {
1067 		newbitmap = my_malloc(BITS_TO_BYTES(group->newsrc.xmax - bitmin + 1));
1068 		NSETRNG0(newbitmap, T_ARTNUM_CONST(0), group->newsrc.xmax - bitmin);
1069 	}
1070 
1071 	/*
1072 	 * if getart_limit > 0 preserve read/unread state
1073 	 * of all articles below the new minimum
1074 	 */
1075 	if (min > 0 && newbitmap) {
1076 		t_artnum j, tmp_bitmax;
1077 
1078 		tmp_bitmax = (bitmax < min) ? bitmax : min;
1079 		for (j = bitmin; j < tmp_bitmax; j++) {
1080 			if (NTEST(group->newsrc.xbitmap, j - bitmin) != ART_READ)
1081 				NSET1(newbitmap, j - bitmin);
1082 		}
1083 		while (j < min) {
1084 			NSET1(newbitmap, j - bitmin);
1085 			j++;
1086 		}
1087 	}
1088 
1089 	for_each_art(i) {
1090 		if (arts[i].artnum < bitmin)
1091 			arts[i].status = ART_READ;
1092 		else if (arts[i].artnum > bitmax)
1093 			arts[i].status = ART_UNREAD;
1094 		else if (NTEST(group->newsrc.xbitmap, arts[i].artnum - bitmin) == ART_READ)
1095 			arts[i].status = ART_READ;
1096 		else
1097 			arts[i].status = ART_UNREAD;
1098 
1099 		/* TODO: logic correct? */
1100 		if (newbitmap != NULL && arts[i].status == ART_UNREAD && arts[i].artnum >= bitmin) {
1101 #if 0
1102 		/*
1103 		 * check for wrong article numbers in the overview
1104 		 *
1105 		 * TODO: check disabled as we currently catch the artnum > high_mark
1106 		 *       case in read_overview() where we might be able to
1107 		 *       fix the broken artnum (via xref:-parsing). currently
1108 		 *       we just skip the art there.
1109 		 */
1110 			if (arts[i].artnum <= group->xmax)
1111 #endif /* 0 */
1112 				NSET1(newbitmap, arts[i].artnum - bitmin);
1113 			unread++;
1114 		}
1115 	}
1116 
1117 	group->newsrc.xbitlen = group->newsrc.xmax - bitmin + 1;
1118 
1119 	FreeIfNeeded(group->newsrc.xbitmap);
1120 
1121 	group->newsrc.xbitmap = newbitmap;
1122 	group->newsrc.num_unread = unread;
1123 }
1124 
1125 
1126 static void
print_bitmap_seq(FILE * fp,struct t_group * group)1127 print_bitmap_seq(
1128 	FILE *fp,
1129 	struct t_group *group)
1130 {
1131 	t_artnum artnum;
1132 	t_artnum i;
1133 	t_bool flag = FALSE;
1134 
1135 #ifdef DEBUG
1136 	if (debug & DEBUG_NEWSRC) {
1137 		debug_print_comment("print_bitmap_seq()");
1138 		debug_print_bitmap(group, NULL);
1139 	}
1140 #endif /* DEBUG */
1141 
1142 	if (group->count == 0 || group->xmin > group->xmax) {
1143 		if (group->newsrc.xmax > 1)
1144 			fprintf(fp, "1-%"T_ARTNUM_PFMT, group->newsrc.xmax);
1145 
1146 		fprintf(fp, "\n");
1147 		fflush(fp);
1148 #ifdef DEBUG
1149 		if (debug & DEBUG_NEWSRC)
1150 			debug_print_comment("print_bitmap_seq(): group->count == 0");
1151 #endif /* DEBUG */
1152 		return;
1153 	}
1154 
1155 	i = group->newsrc.xmin;
1156 	if (i <= group->newsrc.xmax) {
1157 		forever {
1158 			if (group->newsrc.xbitmap && NTEST(group->newsrc.xbitmap, i - group->newsrc.xmin) == ART_READ) {
1159 				if (flag) {
1160 					artnum = i;
1161 					fprintf(fp, ",%"T_ARTNUM_PFMT, i);
1162 				} else {
1163 					artnum = 1;
1164 					flag = TRUE;
1165 					fprintf(fp, "1");
1166 				}
1167 				while (i < group->newsrc.xmax && NTEST(group->newsrc.xbitmap, (i + 1) - group->newsrc.xmin) == ART_READ)
1168 					i++;
1169 
1170 				if (artnum != i)
1171 					fprintf(fp, "-%"T_ARTNUM_PFMT, i);
1172 
1173 			} else if (!flag) {
1174 				flag = TRUE;
1175 				if (group->newsrc.xmin > 1) {
1176 					fprintf(fp, "1");
1177 
1178 					if (group->newsrc.xmin > 2)
1179 						fprintf(fp, "-%"T_ARTNUM_PFMT, group->newsrc.xmin - 1);
1180 
1181 				}
1182 			}
1183 			if (group->newsrc.xmax == i)
1184 				break;
1185 
1186 			i++;
1187 		}
1188 	}
1189 
1190 	if (!flag && group->newsrc.xmin > 1) {
1191 		fprintf(fp, "1");
1192 
1193 		if (group->newsrc.xmin > 2)
1194 			fprintf(fp, "-%"T_ARTNUM_PFMT, group->newsrc.xmin - 1);
1195 
1196 #ifdef DEBUG
1197 		if (debug & DEBUG_NEWSRC)
1198 			debug_print_comment("print_bitmap_seq(): !flag && group->newsrc.xmin > 1");
1199 #endif /* DEBUG */
1200 	}
1201 
1202 	fprintf(fp, "\n");
1203 	fflush(fp);
1204 }
1205 
1206 
1207 /*
1208  * rewrite .newsrc and position group at specified position
1209  */
1210 t_bool
pos_group_in_newsrc(struct t_group * group,int pos)1211 pos_group_in_newsrc(
1212 	struct t_group *group,
1213 	int pos)
1214 {
1215 	FILE *fp_in = NULL, *fp_out = NULL;
1216 	FILE *fp_sub = NULL, *fp_unsub = NULL;
1217 	char *newsgroup = NULL;
1218 	char *line;
1219 	char filename[PATH_LEN];
1220 	char sub[PATH_LEN];
1221 	char unsub[PATH_LEN];
1222 	int subscribed_pos = 1;
1223 	int err;
1224 	size_t group_len;
1225 	t_bool found = FALSE;
1226 	t_bool newnewsrc_created = FALSE;
1227 	t_bool option_line = FALSE;
1228 	t_bool repositioned = FALSE;
1229 	t_bool ret_code = FALSE;
1230 	t_bool sub_created = FALSE;
1231 	t_bool unsub_created = FALSE;
1232 	t_bool fs_error = FALSE;
1233 
1234 	if (no_write)
1235 		goto rewrite_group_done;
1236 
1237 	if ((fp_in = fopen(newsrc, "r")) == NULL)
1238 		goto rewrite_group_done;
1239 
1240 	if ((fp_out = fopen(newnewsrc, "w")) == NULL)
1241 		goto rewrite_group_done;
1242 
1243 	newnewsrc_created = TRUE;
1244 
1245 	if (newsrc_mode)
1246 #ifdef HAVE_FCHMOD
1247 		fchmod(fileno(fp_out), newsrc_mode);
1248 #else
1249 #	ifdef HAVE_CHMOD
1250 		chmod(newnewsrc, newsrc_mode);
1251 #	endif /* HAVE_CHMOD */
1252 #endif /* HAVE_FCHMOD */
1253 
1254 	joinpath(filename, sizeof(filename), TMPDIR, ".subrc");
1255 	snprintf(sub, sizeof(sub), "%s.%ld", filename, (long) process_id);
1256 
1257 	joinpath(filename, sizeof(filename), TMPDIR, ".unsubrc");
1258 	snprintf(unsub, sizeof(unsub), "%s.%ld", filename, (long) process_id);
1259 
1260 	if ((fp_sub = fopen(sub, "w")) == NULL)
1261 		goto rewrite_group_done;
1262 
1263 	sub_created = TRUE;
1264 
1265 	if ((fp_unsub = fopen(unsub, "w")) == NULL)
1266 		goto rewrite_group_done;
1267 
1268 	unsub_created = TRUE;
1269 
1270 	/*
1271 	 * split newsrc into subscribed and unsubscribed to files
1272 	 */
1273 	group_len = strlen(group->name);
1274 
1275 	while ((line = tin_fgets(fp_in, FALSE)) != NULL) {
1276 		if (STRNCMPEQ(group->name, line, group_len) && line[group_len] == SUBSCRIBED) {
1277 			FreeIfNeeded(newsgroup);
1278 			newsgroup = my_strdup(line);		/* Take a copy of this line */
1279 			found = TRUE;
1280 			continue;
1281 		} else if (strchr(line, SUBSCRIBED) != NULL) {
1282 			write_newsrc_line(fp_sub, line);
1283 		} else if (strchr(line, UNSUBSCRIBED) != NULL) {
1284 			write_newsrc_line(fp_unsub, line);
1285 		} else {								/* options line at beginning of .newsrc */
1286 			fprintf(fp_sub, "%s\n", line);
1287 			option_line = TRUE;
1288 		}
1289 	}
1290 
1291 	if ((err = ferror(fp_sub)) || fclose(fp_sub)) {
1292 		error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
1293 		if (err) {
1294 			clearerr(fp_sub);
1295 			fclose(fp_sub);
1296 		}
1297 		fs_error = TRUE;
1298 	}
1299 	if ((err = ferror(fp_unsub)) || fclose(fp_unsub)) {
1300 		if (!fs_error) /* avoid repeatd error message */
1301 			error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
1302 		if (err) {
1303 			clearerr(fp_unsub);
1304 			fclose(fp_unsub);
1305 		}
1306 		fs_error = TRUE;
1307 	}
1308 	fp_sub = fp_unsub = NULL;
1309 
1310 	if (fs_error)
1311 		goto rewrite_group_done;
1312 
1313 	fclose(fp_in);
1314 	fp_in = NULL;
1315 
1316 	/*
1317 	 * The group to be moved cannot be found, so give up now
1318 	 */
1319 	if (!found)
1320 		goto rewrite_group_done;
1321 
1322 	/*
1323 	 * write subscribed groups & repositioned group to newnewsrc
1324 	 */
1325 	if ((fp_sub = fopen(sub, "r")) == NULL)
1326 		goto rewrite_group_done;
1327 
1328 	while ((line = tin_fgets(fp_sub, FALSE)) != NULL) {
1329 		if (option_line) {
1330 			if (strchr(line, SUBSCRIBED) == NULL && strchr(line, UNSUBSCRIBED) == NULL) {
1331 				fprintf(fp_out, "%s\n", line);
1332 				continue;
1333 			} else
1334 				option_line = FALSE;
1335 		}
1336 
1337 		if (pos == subscribed_pos) {
1338 			write_newsrc_line(fp_out, newsgroup);
1339 			repositioned = TRUE;
1340 		}
1341 
1342 		fprintf(fp_out, "%s\n", line);
1343 
1344 		subscribed_pos++;
1345 	}
1346 
1347 	if (!repositioned)
1348 		write_newsrc_line(fp_out, newsgroup);
1349 
1350 	/*
1351 	 * append unsubscribed groups file to newnewsrc
1352 	 */
1353 	if ((fp_unsub = fopen(unsub, "r")) == NULL)
1354 		goto rewrite_group_done;
1355 
1356 	while ((line = tin_fgets(fp_unsub, FALSE)) != NULL)
1357 		fprintf(fp_out, "%s\n", line);
1358 
1359 	/*
1360 	 * Try and cleanly close out the newnewsrc file
1361 	 */
1362 	if ((err = ferror(fp_out)) || fclose(fp_out)) {
1363 		error_message(2, _(txt_filesystem_full), NEWSRC_FILE);
1364 		if (err) {
1365 			clearerr(fp_out);
1366 			fclose(fp_out);
1367 		}
1368 	} else {
1369 		rename_file(newnewsrc, newsrc);
1370 		ret_code = TRUE;
1371 	}
1372 	fp_out = NULL;
1373 	newnewsrc_created = FALSE;
1374 
1375 rewrite_group_done:
1376 	if (fp_in != NULL)
1377 		fclose(fp_in);
1378 
1379 	if (fp_out != NULL)
1380 		fclose(fp_out);
1381 
1382 	if (fp_sub != NULL)
1383 		fclose(fp_sub);
1384 
1385 	if (fp_unsub != NULL)
1386 		fclose(fp_unsub);
1387 
1388 	if (newnewsrc_created)
1389 		unlink(newnewsrc);
1390 
1391 	if (sub_created)
1392 		unlink(sub);
1393 
1394 	if (unsub_created)
1395 		unlink(unsub);
1396 
1397 	FreeIfNeeded(newsgroup);
1398 
1399 	return ret_code;
1400 }
1401 
1402 
1403 /*
1404  * catchup all groups in .newsrc
1405  */
1406 void
catchup_newsrc_file(void)1407 catchup_newsrc_file(
1408 	void)
1409 {
1410 	int i;
1411 	struct t_group *group;
1412 
1413 	for (i = 0; i < selmenu.max; i++) {
1414 		group = &active[my_group[i]];
1415 		group->newsrc.present = TRUE;
1416 		FreeAndNull(group->newsrc.xbitmap);
1417 		if (group->xmax > group->newsrc.xmax)
1418 			group->newsrc.xmax = group->xmax;
1419 		group->newsrc.xmin = group->newsrc.xmax + 1;
1420 		group->newsrc.num_unread = 0;
1421 		group->newsrc.xbitlen = 0;
1422 	}
1423 }
1424 
1425 
1426 /*
1427  * Break down a line of .newsrc file
1428  * The sequence information [ eg; 1-3,10,12 ] is returned, line is truncated to
1429  * just the group name and the subscription flag is copied to sub.
1430  */
1431 static char *
parse_newsrc_line(char * line,int * sub)1432 parse_newsrc_line(
1433 	char *line,
1434 	int *sub)
1435 {
1436 	char *ptr, *tmp;
1437 
1438 	*sub = UNSUBSCRIBED;				/* Default to no entry */
1439 
1440 	if ((ptr = strpbrk(line, "!:")) == NULL)			/* space|SUBSCRIBED|UNSUBSCRIBED */
1441 		return NULL;
1442 
1443 	*sub = *ptr;						/* Save the subscription status */
1444 	tmp = ptr;							/* Keep this blank for later */
1445 	*(ptr++) = '\0';					/* Terminate the group name */
1446 
1447 #if 0
1448 	if (ptr == NULL)					/* No seq info, so return a blank */
1449 		return tmp;
1450 #endif /* 0 */
1451 
1452 	if ((ptr = strpbrk(ptr, " \t")) == NULL)
1453 		return tmp;
1454 
1455 	return (ptr + 1);	/* Return pointer to sequence info. At worst this will be \0 */
1456 }
1457 
1458 
1459 /*
1460  * expand group->newsrc information if group->xmax is larger than
1461  * group->newsrc.xmax or min is smaller than group->newsrc.xmin.
1462  */
1463 void
expand_bitmap(struct t_group * group,t_artnum min)1464 expand_bitmap(
1465 	struct t_group *group,
1466 	t_artnum min)
1467 {
1468 	t_artnum bitlen;
1469 	t_artnum first;
1470 	t_artnum tmp;
1471 	t_artnum max;
1472 	t_bool need_full_copy = FALSE;
1473 
1474 	/* calculate new max */
1475 	if (group->newsrc.xmax > group->xmax)
1476 		max = group->newsrc.xmax;
1477 	else
1478 		max = group->xmax;
1479 
1480 	/* adjust min */
1481 	if (!min)
1482 		min = group->newsrc.xmin;
1483 
1484 	/* calculate first */
1485 	if (min >= group->newsrc.xmin)
1486 		first = group->newsrc.xmin;
1487 	else
1488 		first = group->newsrc.xmin - ((group->newsrc.xmin - min + (NBITS - 1)) & ~(NBITS - 1));
1489 
1490 	/* adjust first */
1491 	if (first > group->newsrc.xmax + 1)
1492 		first = first - ((first - (group->newsrc.xmax + 1) + (NBITS - 1)) & ~(NBITS - 1));
1493 
1494 	/* check first */
1495 	if (first < 1) {
1496 		need_full_copy = TRUE;
1497 		first = 1;
1498 	}
1499 
1500 	bitlen = max - first + 1;
1501 
1502 	if (bitlen <= 0) {
1503 		bitlen = 0;
1504 		FreeIfNeeded(group->newsrc.xbitmap);
1505 		group->newsrc.xbitmap = (t_bitmap *) 0;
1506 #ifdef DEBUG
1507 		if (debug & DEBUG_NEWSRC)
1508 			debug_print_comment("expand_bitmap: group->newsrc.bitlen == 0");
1509 #endif /* DEBUG */
1510 	} else if (group->newsrc.xbitmap == NULL) {
1511 		group->newsrc.xbitmap = my_malloc(BITS_TO_BYTES(bitlen));
1512 		if (group->newsrc.xmin > first)
1513 			NSETRNG0(group->newsrc.xbitmap, T_ARTNUM_CONST(0), group->newsrc.xmin - first - T_ARTNUM_CONST(1));
1514 		if (bitlen > group->newsrc.xmin - first)
1515 			NSETRNG1(group->newsrc.xbitmap, group->newsrc.xmin - first, bitlen - 1);
1516 #ifdef DEBUG
1517 		if (debug & DEBUG_NEWSRC)
1518 			debug_print_comment("expand_bitmap: group->newsrc.xbitmap == NULL");
1519 #endif /* DEBUG */
1520 	} else if (need_full_copy) {
1521 		t_bitmap *newbitmap = my_malloc(BITS_TO_BYTES(bitlen));
1522 
1523 		/* Copy over old bitmap */
1524 		/* TODO: change to use shift */
1525 		for (tmp = group->newsrc.xmin; tmp <= group->newsrc.xmax; tmp++) {
1526 			if (NTEST(group->newsrc.xbitmap, tmp - group->newsrc.xmin) == ART_READ)
1527 				NSET0(newbitmap, tmp - first);
1528 			else
1529 				NSET1(newbitmap, tmp - first);
1530 		}
1531 
1532 		/* Mark earlier articles as read, updating num_unread */
1533 
1534 		if (first < group->newsrc.xmin) {
1535 			NSETRNG0(newbitmap, T_ARTNUM_CONST(0), group->newsrc.xmin - first - T_ARTNUM_CONST(1));
1536 		}
1537 
1538 		for (tmp = group->newsrc.xmin; tmp < min; tmp++) {
1539 			if (NTEST(newbitmap, tmp - first) != ART_READ) {
1540 				NSET0(newbitmap, tmp - first);
1541 				if (group->newsrc.num_unread)
1542 					group->newsrc.num_unread--;
1543 			}
1544 		}
1545 
1546 		/* Mark high numbered articles as unread */
1547 
1548 		if (group->newsrc.xmin - first + group->newsrc.xbitlen < bitlen) {
1549 			tmp = group->newsrc.xmin - first + group->newsrc.xbitlen;
1550 			NSETRNG1(newbitmap, tmp, bitlen - 1);
1551 		}
1552 
1553 		free(group->newsrc.xbitmap);
1554 		group->newsrc.xbitmap = newbitmap;
1555 #ifdef DEBUG
1556 		if (debug & DEBUG_NEWSRC)
1557 			debug_print_comment("expand_bitmap: group->newsrc.bitlen != (group->max-group->min)+1 and need full copy");
1558 #endif /* DEBUG */
1559 	} else if (max != group->newsrc.xmax || first != group->newsrc.xmin) {
1560 		t_bitmap *newbitmap;
1561 		newbitmap = my_malloc(BITS_TO_BYTES(bitlen));
1562 
1563 		/* Copy over old bitmap */
1564 
1565 		assert((group->newsrc.xmin - first) / NBITS + BITS_TO_BYTES(group->newsrc.xbitlen) <= BITS_TO_BYTES(bitlen));
1566 
1567 		memcpy(newbitmap + (group->newsrc.xmin - first) / NBITS, group->newsrc.xbitmap, BITS_TO_BYTES(group->newsrc.xbitlen));
1568 
1569 		/* Mark earlier articles as read, updating num_unread */
1570 
1571 		if (first < group->newsrc.xmin) {
1572 			NSETRNG0(newbitmap, T_ARTNUM_CONST(0), group->newsrc.xmin - first - T_ARTNUM_CONST(1));
1573 		}
1574 
1575 		for (tmp = group->newsrc.xmin; tmp < min; tmp++) {
1576 			if (NTEST(newbitmap, tmp - first) != ART_READ) {
1577 				NSET0(newbitmap, tmp - first);
1578 				if (group->newsrc.num_unread)
1579 					group->newsrc.num_unread--;
1580 			}
1581 		}
1582 
1583 		/* Mark high numbered articles as unread */
1584 
1585 		if (group->newsrc.xmin - first + group->newsrc.xbitlen < bitlen) {
1586 			tmp = group->newsrc.xmin - first + group->newsrc.xbitlen;
1587 			NSETRNG1(newbitmap, tmp, bitlen - 1);
1588 		}
1589 
1590 		free(group->newsrc.xbitmap);
1591 		group->newsrc.xbitmap = newbitmap;
1592 #ifdef DEBUG
1593 		if (debug & DEBUG_NEWSRC)
1594 			debug_print_comment("expand_bitmap: group->newsrc.bitlen != (group->max-group->min)+1");
1595 #endif /* DEBUG */
1596 	}
1597 	group->newsrc.xmin = first;
1598 	if (group->newsrc.xmax < max)
1599 		group->newsrc.num_unread += max - group->newsrc.xmax;
1600 	group->newsrc.xmax = max;
1601 	group->newsrc.xbitlen = bitlen;
1602 	group->newsrc.present = TRUE;
1603 }
1604 
1605 
1606 void
art_mark(struct t_group * group,struct t_article * art,int flag)1607 art_mark(
1608 	struct t_group *group,
1609 	struct t_article *art,
1610 	int flag)
1611 {
1612 	if (art == NULL)
1613 		return;
1614 
1615 	switch (flag) {
1616 		case ART_READ:
1617 			if (group != NULL) {
1618 				if (art->artnum >= group->newsrc.xmin && art->artnum <= group->newsrc.xmax)
1619 					NSET0(group->newsrc.xbitmap, art->artnum - group->newsrc.xmin);
1620 #ifdef DEBUG
1621 				if (debug & DEBUG_NEWSRC)
1622 					debug_print_bitmap(group, art);
1623 #endif /* DEBUG */
1624 			}
1625 			if ((art->status == ART_UNREAD) || (art->status == ART_WILL_RETURN)) {
1626 				art_mark_xref_read(art);
1627 
1628 				if (group != NULL) {
1629 					if (group->newsrc.num_unread)
1630 						group->newsrc.num_unread--;
1631 
1632 					if (group->attribute->show_only_unread_arts)
1633 						art->keep_in_base = TRUE;
1634 				}
1635 
1636 				art->status = ART_READ;
1637 			}
1638 			break;
1639 
1640 		case ART_UNREAD:
1641 		case ART_WILL_RETURN:
1642 			if (art->status == ART_READ) {
1643 				if (group != NULL) {
1644 					group->newsrc.num_unread++;
1645 
1646 					if (group->attribute->show_only_unread_arts)
1647 						art->keep_in_base = FALSE;
1648 				}
1649 
1650 				art->status = flag;
1651 			}
1652 			if (group != NULL) {
1653 				if (art->artnum < group->newsrc.xmin)
1654 					expand_bitmap(group, art->artnum);
1655 				else {
1656 					NSET1(group->newsrc.xbitmap, art->artnum - group->newsrc.xmin);
1657 #ifdef DEBUG
1658 					if (debug & DEBUG_NEWSRC)
1659 						debug_print_bitmap(group, art);
1660 #endif /* DEBUG */
1661 				}
1662 			}
1663 			break;
1664 
1665 		default:
1666 			break;
1667 	}
1668 }
1669 
1670 
1671 void
set_default_bitmap(struct t_group * group)1672 set_default_bitmap(
1673 	struct t_group *group)
1674 {
1675 	if (group != NULL) {
1676 		group->newsrc.num_unread = 0;
1677 		group->newsrc.present = FALSE;
1678 
1679 		FreeIfNeeded(group->newsrc.xbitmap);
1680 
1681 		group->newsrc.xbitmap = (t_bitmap *) 0;
1682 		group->newsrc.xbitlen = 0;
1683 		if (group->xmin > 0)
1684 			group->newsrc.xmin = group->xmin;
1685 		else
1686 			group->newsrc.xmin = 1;
1687 		group->newsrc.xmax = group->newsrc.xmin - 1;
1688 	}
1689 }
1690