1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	The nn user interface main program
6  */
7 
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include <sys/types.h>
12 #include "config.h"
13 #include "global.h"
14 #include "admin.h"
15 #include "answer.h"
16 #include "articles.h"
17 #include "db.h"
18 #include "execute.h"
19 #include "folder.h"
20 #include "group.h"
21 #include "init.h"
22 #include "keymap.h"
23 #include "kill.h"
24 #include "libnov.h"
25 #include "macro.h"
26 #include "match.h"
27 #include "menu.h"
28 #include "newsrc.h"
29 #include "nn.h"
30 #include "nntp.h"
31 #include "options.h"
32 #include "proto.h"
33 #include "sequence.h"
34 #include "nn_term.h"
35 
36 #ifdef USE_MALLOC_H
37 #include <malloc.h>
38 #endif
39 
40 /* nn.c */
41 
42 static int      nn_locked(void);
43 static group_header *last_group_maint(group_header * last, int upd);
44 static int      read_news(flag_type access_mode, char *mask);
45 static void     catch_up(void);
46 static int      do_nnspew(void);
47 static void     prt_version(void);
48 static void     do_db_check(void);
49 
50 extern char    *bin_directory;
51 
52 extern int
53                 seq_cross_filtering,	/* articles */
54                 dont_sort_folders,	/* folder.c */
55                 dont_split_digests, dont_sort_articles, also_unsub_groups,	/* group.c */
56                 also_cross_postings, case_fold_search,	/* match.c */
57                 preview_window, fmt_linenum, fmt_rptsubj,	/* menu.c */
58                 show_article_date, first_page_lines,	/* more.c */
59                 keep_rc_backup, no_update,	/* newsrc.c */
60                 hex_group_args,	/* sequence */
61                 show_current_time, conf_dont_sleep;	/* term.c */
62 
63 int
64                 article_limit = -1, also_read_articles = 0,
65                 also_full_digest = 1, batch_mode = 0, conf_auto_quit = 0,
66                 do_kill_handling = 1, merged_menu = 0, prev_also_read = 1,
67                 repeat_group_query = 0, report_cost_on_exit = 1,
68                 show_motd_on_entry = 1, silent = 0, verbose = 0, Debug = 0;
69 
70 int             check_db_update = 12 /* HOURS */ ;
71 
72 extern char     proto_host[];
73 extern int      newsrc_update_freq, novice;
74 extern int      seq_cross_filtering;
75 extern char    *news_active;
76 extern long     unread_articles;
77 extern long     initial_memory_break;
78 extern int      first_time_user;
79 extern int      also_cross_postings;
80 
81 static int
82                 group_name_args = 0, nngrab_mode = 0, prompt_for_group = 0;
83 
84 static char
85                *match_subject = NULL, *match_sender = NULL, *init_file = NULL;
86 
87 static
Option_Description(nn_options)88 Option_Description(nn_options)
89 {
90     'a', Int_Option(article_limit),
91     'B', Bool_Option(keep_rc_backup),
92     'd', Bool_Option(dont_split_digests),
93     'f', Bool_Option(dont_sort_folders),
94     'g', Bool_Option(prompt_for_group),
95     'G', Bool_Option(nngrab_mode),
96     'i', Bool_Option(case_fold_search),
97     'I', String_Option_Optional(init_file, NULL),
98     'k', Bool_Option(do_kill_handling),
99     'l', Int_Option(first_page_lines),
100     'L', Int_Option_Optional(fmt_linenum, 3),
101     'm', Bool_Option(merged_menu),
102     'n', String_Option(match_sender),
103     'N', Bool_Option(no_update),
104     'q', Bool_Option(dont_sort_articles),
105     'Q', Bool_Option(silent),
106     'r', Bool_Option(repeat_group_query),
107     's', String_Option(match_subject),
108     'S', Bool_Option(fmt_rptsubj),
109     'T', Bool_Option(show_current_time),
110     'w', Int_Option_Optional(preview_window, 5),
111     'W', Bool_Option(conf_dont_sleep),
112     'x', Int_Option_Optional(also_read_articles, -1),
113     'X', Bool_Option(also_unsub_groups),
114     'Z', Int_Option(Debug),
115     '\0',
116 };
117 
118 
119 static int
120                 report_number_of_articles = 0;
121 static char
122                *check_message_format = NULL;
123 extern int
124                 quick_unread_count;
125 
126 static
Option_Description(check_options)127 Option_Description(check_options)
128 {
129     'Q', Bool_Option(silent),
130     'r', Bool_Option(report_number_of_articles),
131     'f', String_Option(check_message_format),
132     't', Bool_Option(verbose),
133     'v', Bool_Option(verbose),
134     'c', Bool_Option(quick_unread_count),
135     '\0',
136 };
137 
138 /* program name == argv[0] without path */
139 
140 char           *pname;
141 
142 static int      must_unlock = 0;
143 
144 static int
nn_locked(void)145 nn_locked(void)
146 {
147     if (no_update)
148 	return 0;
149 
150     switch (who_am_i) {
151 	case I_AM_ADMIN:
152 	case I_AM_CHECK:
153 	case I_AM_POST:
154 	case I_AM_GREP:
155 	case I_AM_SPEW:
156 	case I_AM_VIEW:
157 	    return 0;		/* will not update .newsrc */
158     }
159 
160     if (proto_lock(I_AM_NN, PL_SET)) {
161 
162 	if (proto_host[0])
163 	    nn_exitmsg(1, "\nnn is running on host %s\nor %s/LOCK should be removed.\n\n", proto_host, nn_directory);
164 	else
165 	    nn_exitmsg(1, "\nAnother nn process is already running\n\n");
166     }
167     must_unlock = 1;
168     return 0;
169 }
170 
171 #define setup_access()	ACC_PARSE_VARIABLES
172 
173 flag_type
parse_access_flags(void)174 parse_access_flags(void)
175 {
176     flag_type       access_mode = 0;
177 
178     if (do_kill_handling)
179 	access_mode |= ACC_DO_KILL;
180     if (also_cross_postings)
181 	access_mode |= ACC_ALSO_CROSS_POSTINGS;
182     if (also_read_articles)
183 	access_mode |= ACC_ALSO_READ_ARTICLES;
184     if (dont_split_digests)
185 	access_mode |= ACC_DONT_SPLIT_DIGESTS;
186     if (also_full_digest)
187 	access_mode |= ACC_ALSO_FULL_DIGEST;
188     if (dont_sort_articles)
189 	access_mode |= ACC_DONT_SORT_ARTICLES;
190 
191     return access_mode;
192 }
193 
194 group_header   *jump_to_group = NULL;
195 int             enter_last_read_mode = 1;
196 
197 static group_header *
last_group_maint(group_header * last,int upd)198 last_group_maint(group_header * last, int upd)
199 {
200     group_header   *gh;
201     FILE           *f;
202     char           *lg_file;
203     char            buf[256], *cp;
204 
205     lg_file = relative(nn_directory, "NEXTG");
206     if (upd) {
207 	if (last == NULL)
208 	    unlink(lg_file);
209 	else {
210 	    f = open_file(lg_file, OPEN_CREATE | MUST_EXIST);
211 	    fprintf(f, "%s\n", last->group_name);
212 	    fclose(f);
213 	}
214 	return NULL;
215     }
216     if (enter_last_read_mode == 0)
217 	return last;
218 
219     f = open_file(lg_file, OPEN_READ);
220     if (f == NULL)
221 	return last;
222 
223     gh = NULL;
224     if (fgets(buf, 256, f)) {
225 	if ((cp = strchr(buf, NL)))
226 	    *cp = NUL;
227 	gh = lookup(buf);
228     }
229     fclose(f);
230 
231     if (gh == NULL || gh == last || !(gh->group_flag & G_SEQUENCE))
232 	return last;
233 
234     switch (enter_last_read_mode) {
235 	case 1:		/* confirm if unread, skip if read */
236 	    if (gh->unread_count == 0)
237 		return last;
238 	    /* FALLTHROUGH */
239 	case 2:		/* confirm any case */
240 	    prompt_line = Lines - 1;
241 	    prompt("Enter %s (%ld unread)? ", gh->group_name, gh->unread_count);
242 	    if (!yes(0))
243 		return last;
244 	    break;
245 	case 3:		/* enter uncond if unread */
246 	    if (gh->unread_count == 0)
247 		return last;
248 	    /* FALLTHROUGH */
249 	case 4:		/* enter uncond */
250 	    break;
251     }
252     return gh;
253 }
254 
255 static int
read_news(flag_type access_mode,char * mask)256 read_news(flag_type access_mode, char *mask)
257 {
258     register group_header *gh, *prev, *tmp, *after_loop;
259     flag_type       group_mode;
260     int             menu_cmd;
261     int             must_clear = 0, did_jump = 0;
262     group_header   *last_group_read = NULL;
263 
264     prev = group_sequence;
265     gh = group_sequence;
266     after_loop = NULL;
267 
268     if (access_mode == 0 && !also_read_articles) {
269 	gh = last_group_maint(gh, 0);
270 	did_jump = gh != group_sequence;
271 	m_invoke(-2);
272     }
273     for (;;) {
274 	group_mode = access_mode;
275 
276 	if (s_hangup)
277 	    break;
278 
279 	if (gh == NULL) {
280 	    if (after_loop != NULL) {
281 		gh = after_loop;
282 		after_loop = NULL;
283 		if (gh->unread_count <= 0)
284 		    group_mode |= ACC_ORIG_NEWSRC;
285 		goto enter_direct;
286 	    }
287 	    if (did_jump) {
288 		did_jump = 0;
289 		gh = group_sequence;
290 		continue;
291 	    }
292 	    if (must_clear && conf_auto_quit) {
293 		prompt("\1LAST GROUP READ.  QUIT NOW?\1");
294 		switch (yes(1)) {
295 		    case 1:
296 			break;
297 		    case 0:
298 			gh = group_sequence;
299 		    default:
300 			after_loop = prev;
301 			continue;
302 		}
303 	    }
304 	    last_group_read = NULL;
305 	    break;
306 	}
307 	if (gh->group_flag & G_UNSUBSCRIBED) {
308 	    if (!also_unsub_groups) {
309 		gh = gh->next_group;
310 		continue;
311 	    }
312 	} else if (!also_read_articles && gh->unread_count <= 0) {
313 	    gh = gh->next_group;
314 	    continue;
315 	}
316 enter_direct:
317 
318 	free_memory();
319 
320 	if (gh->group_flag & G_FOLDER) {
321 	    menu_cmd = folder_menu(gh->group_name, 0);
322 	    if (menu_cmd == ME_NO_REDRAW) {
323 		menu_cmd = ME_NO_ARTICLES;
324 		gh->last_db_article = 0;
325 	    }
326 	} else {
327 	    group_mode |= setup_access();
328 	    if (group_mode & ACC_ORIG_NEWSRC)
329 		gh->last_article = gh->first_article;
330 
331 	    menu_cmd = group_menu(gh, (article_number) (-1), group_mode, mask, menu);
332 	    group_mode = access_mode;
333 	}
334 
335 	if (menu_cmd != ME_NO_ARTICLES) {
336 	    last_group_read = gh;
337 	    after_loop = NULL;
338 	    must_clear++;
339 	}
340 	switch (menu_cmd) {
341 
342 	    case ME_QUIT:	/* or jump */
343 		if (!jump_to_group)
344 		    goto out;
345 
346 		prev = jump_to_group;
347 		jump_to_group = NULL;
348 		did_jump = 1;
349 		/* FALLTHROUGH */
350 
351 	    case ME_PREV:
352 		tmp = gh;
353 		gh = prev;
354 		prev = tmp;
355 		if (gh->unread_count <= 0)
356 		    group_mode |= ACC_ORIG_NEWSRC;
357 		else if (prev_also_read)
358 		    group_mode |= ACC_MERGED_NEWSRC;
359 		goto enter_direct;
360 
361 	    case ME_NEXT:
362 		prev = gh;
363 		/* FALLTHROUGH */
364 
365 	    case ME_NO_ARTICLES:
366 		if (gh->group_flag & G_MERGE_HEAD) {
367 		    do
368 			gh = gh->next_group;
369 		    while (gh && (gh->group_flag & G_MERGE_SUB));
370 		} else
371 		    gh = gh->next_group;
372 		continue;
373 	}
374     }
375 
376 out:
377     if (access_mode == 0 && !also_read_articles)
378 	last_group_maint(last_group_read, 1);
379 
380     return must_clear;
381 }
382 
383 
384 static void
catch_up(void)385 catch_up(void)
386 {
387     register group_header *gh;
388     char            answer1[50];
389     int             mode, must_help;
390     flag_type       access_mode;
391 
392     access_mode = setup_access();
393 
394     prt_unread("\nCatch-up on %u ? (auto)matically (i)nteractive ");
395     fl;
396     mode = 0;
397 
398     if (fgets(answer1, sizeof(answer1), stdin)) {
399 	if (strncmp(answer1, "auto", 4) == 0) {
400 	    tprintf("\nUPDATING .newsrc FILE....");
401 	    fl;
402 	    mode = -1;
403 	} else if (*answer1 == 'i')
404 	    mode = 1;
405     }
406     if (mode == 0) {
407 	tprintf("\nNO UPDATE\n");
408 	return;
409     }
410     newsrc_update_freq = 9999;
411     must_help = novice;
412 
413     Loop_Groups_Sequence(gh) {
414 
415 	if ((gh->group_flag & G_COUNTED) == 0)
416 	    continue;
417 
418 	if (mode < 0) {
419 	    update_rc_all(gh, 0);
420 	    continue;
421 	}
422 again:
423 	if (must_help) {
424 	    tprintf("\n  y - mark all articles as read in current group\n");
425 	    tprintf("  n - do not update group\n");
426 	    tprintf("  r - read the group now\n");
427 	    tprintf("  U - unsubscribe to current group\n");
428 	    tprintf("  ? - this message\n");
429 	    tprintf("  q - quit\n\n");
430 
431 	    must_help = 0;
432 	}
433 	tprintf("\rUpdate %s (%ld)? (ynrU?q) n\b", gh->group_name,
434 		(long) (gh->last_db_article - gh->last_article));
435 	fl;
436 
437 	if (fgets(answer1, sizeof(answer1), stdin) == NULL || s_keyboard)
438 	    *answer1 = 'q';
439 
440 	switch (*answer1) {
441 
442 	    case 'q':
443 		tputc(NL);
444 		tprintf("Update rest? (yn) n\b");
445 		fl;
446 		if (fgets(answer1, sizeof(answer1), stdin) == NULL || *answer1 != 'y')
447 		    return;
448 
449 		mode = -1;
450 		break;
451 
452 	    case NUL:
453 	    case 'n':
454 		continue;
455 
456 	    case 'r':
457 		nn_raw();
458 		group_menu(gh, (article_number) (-1), access_mode, (char *) NULL, menu);
459 		unset_raw();
460 		clrdisp();
461 		if (gh->unread_count > 0)
462 		    goto again;
463 		continue;
464 
465 	    case 'U':
466 		update_rc_all(gh, 1);
467 		continue;
468 
469 	    case 'y':
470 		break;
471 
472 	    default:
473 		must_help = 1;
474 		goto again;
475 	}
476 
477 	update_rc_all(gh, 0);
478     }
479 
480     tprintf("DONE\n");
481 
482     flush_newsrc();
483 }
484 
485 char           *mail_box = NULL;
486 
487 int
unread_mail(time_t t)488 unread_mail(time_t t)
489 {
490     struct stat     st;
491     static time_t   next = 0;
492     static int      any = 0;
493 
494     if (next > t)
495 	return any;
496 
497     next = t + 60;
498     any = 0;
499 
500     if (mail_box == NULL ||
501 	stat(mail_box, &st) != 0 ||
502 	st.st_size == 0 ||
503 	st.st_mtime < st.st_atime)
504 	return 0;
505 
506     any = 1;
507 
508     return 1;
509 }
510 
511 #ifdef ACCOUNTING
512 
513 #ifndef STATISTICS
514 #define STATISTICS 1
515 #endif
516 
517 #define NEED_ACCOUNT
518 #else
519 
520 #ifdef AUTHORIZE
521 #define NEED_ACCOUNT
522 #endif
523 
524 #endif
525 
526 #ifdef STATISTICS
527 static time_t   usage_time = 0;
528 static time_t   last_tick = 0;
529 
530 void
stop_usage(void)531 stop_usage(void)
532 {
533     last_tick = 0;
534 }
535 
536 time_t
tick_usage(void)537 tick_usage(void)
538 {
539     register time_t now;
540 
541     now = cur_time();
542 
543     /*
544      * We ignore ticks > 2 minutes because the user has probably just left
545      * the terminal inside nn and done something else
546      */
547     if (last_tick > (now - 120))
548 	usage_time += now - last_tick;
549 
550     last_tick = now;
551     return now;
552 }
553 
554 static void
log_usage(void)555 log_usage(void)
556 {
557 
558 #ifdef ACCOUNTING
559     account('U', report_cost_on_exit);
560 #else
561     if (usage_time < (STATISTICS * 60))
562 	return;			/* don't log short sessions */
563 
564     usage_time /= 60;
565     log_entry('U', "USAGE %d.%02d", usage_time / 60, usage_time % 60);
566 #endif
567 }
568 
569 #else
570 void
stop_usage(void)571 stop_usage(void)
572 {
573 }
574 
575 time_t
tick_usage(void)576 tick_usage(void)
577 {
578     return cur_time();
579 }
580 
581 static void
log_usage(void)582 log_usage(void)
583 {
584 }
585 
586 #endif
587 
588 #ifdef NEED_ACCOUNT
589 int
account(char option,int report)590 account(char option, int report)
591 {
592     char           *args[10], **ap;
593     char            buf1[16], buf2[16];
594     int             ok;
595 
596     if (who_am_i != I_AM_NN && who_am_i != I_AM_POST)
597 	return 0;
598 
599     if (report) {
600 	tputc(CR);
601 	clrline();
602 	fl;
603     }
604     ap = args;
605     *ap++ = "nnacct";
606     if (report)
607 	*ap++ = "-r";
608 
609 #ifdef STATISTICS
610     sprintf(buf1, "-%c%ld", option, (long) usage_time / 60);
611 #else
612     sprintf(buf1, "-%c0", option);
613 #endif
614 
615     *ap++ = buf1;
616     sprintf(buf2, "-W%d", who_am_i);
617     *ap++ = buf2;
618     *ap++ = NULL;
619     ok = execute(relative(bin_directory, "nnacct"), args, 0);
620     if (option == 'U' && report)
621 	tputc(NL);
622     return ok;
623 }
624 
625 #endif
626 
627 /* this will go into emacs_mode.c when it is completed someday */
628 
629 static void
emacs_mode(void)630 emacs_mode(void)
631 {
632     tprintf("EMACS MODE IS NOT SUPPORTED YET.\n");
633 }
634 
635 
636 static int
do_nnspew(void)637 do_nnspew(void)
638 {
639     register group_header *gh;
640     int             ix;
641 
642     ix = 0;
643     Loop_Groups_Header(gh) {
644 	if (gh->master_flag & M_IGNORE_GROUP)
645 	    continue;
646 	gh->preseq_index = ++ix;
647     }
648 
649     /* Only output each article once */
650     /* Must also use this mode when using nngrab! */
651 
652     seq_cross_filtering = 1;
653 
654     Loop_Groups_Header(gh) {
655 	if (s_hangup)
656 	    return 1;
657 	if (gh->master_flag & M_IGNORE_GROUP)
658 	    continue;
659 	access_group(gh, gh->first_db_article, gh->last_db_article,
660 		     ACC_DONT_SORT_ARTICLES | ACC_ALSO_FULL_DIGEST |
661 		     ACC_SPEW_MODE, (char *) NULL);
662     }
663     return 0;
664 }
665 
666 static void
prt_version(void)667 prt_version(void)
668 {
669     clrdisp();
670     tprintf("Release %s\n  by Kim F. Storm, Peter Wemm, and Michael T Pins, 2005\n\n", version_id);
671 }
672 
673 int
display_motd(int check)674 display_motd(int check)
675 {
676     time_t          last_motd, cur_motd;
677     char           *dot_motd, motd[FILENAME];
678 
679     strcpy(motd, relative(lib_directory, "motd"));
680     cur_motd = file_exist(motd, (char *) NULL);
681     if (cur_motd == 0)
682 	return 0;
683 
684     if (!check) {
685 	display_file(motd, CLEAR_DISPLAY | CONFIRMATION);
686 	return 1;
687     }
688     dot_motd = relative(nn_directory, ".motd");
689     last_motd = file_exist(dot_motd, (char *) NULL);
690 
691     if (last_motd >= cur_motd)
692 	return 0;
693 
694     unlink(dot_motd);
695     fclose(open_file(dot_motd, OPEN_CREATE | MUST_EXIST));
696 
697     clrdisp();
698 
699     so_printxy(0, 0, "Message of the day");
700     gotoxy(0, 2);
701     prt_version();
702 
703     display_file(motd, CONFIRMATION);
704     return 1;
705 }
706 
707 static void
do_db_check(void)708 do_db_check(void)
709 {
710     time_t          last_upd;
711 
712 #ifdef NOV
713     time_t          active_time;
714 
715     if (use_nntp)
716 	return;
717 
718     active_time = file_exist(news_active, (char *) NULL);
719     if (active_time == 0)
720 	return;			/* cannot stat/read/etc... */
721 
722     last_upd = (cur_time() - active_time) / (60 * 60);
723     if (last_upd < check_db_update)
724 	return;
725     tprintf("Notice: no news has arrived for the last %ld hours\n", last_upd);
726 
727 #else				/* NOV */
728 
729     last_upd = (cur_time() - master.last_scan) / (60 * 60);
730     if (last_upd < check_db_update)
731 	return;
732     /* too old - but is nnmaster the culprit? */
733     if (master.last_scan == file_exist(news_active, (char *) NULL))
734 	tprintf("Notice: no news has arrived for the last %ld hours\n", last_upd);
735     else
736 	tprintf("Notice: nnmaster has not updated database in %ld hours\n", last_upd);
737 #endif				/* NOV */
738 
739     sleep(3);
740 }
741 
742 int
main(int argc,char * argv[])743 main(int argc, char *argv[])
744 {
745     int             say_welcome = 0, cmd;
746     flag_type       access_mode = 0;
747     char           *mask = NULL;
748     initial_memory_break = (long) sbrk(0);
749 
750 #ifdef USE_MALLOC_H
751 
752 #ifdef MALLOC_MAXFAST
753     mallopt(M_MXFAST, MALLOC_MAXFAST);
754 #endif
755 
756 #ifdef MALLOC_FASTBLOCKS
757     mallopt(M_NLBLKS, MALLOC_FASTBLOCKS);
758 #endif
759 
760 #ifdef MALLOC_GRAIN
761     mallopt(M_GRAIN, MALLOC_GRAIN);
762 #endif
763 
764 #endif
765 
766 #ifdef MALDEBUG
767     mal_debug(getenv("MALDEBUG") ? atoi(getenv("MALDEBUG")) : 0);
768 #endif
769 
770     pname = program_name(argv);
771 
772     if (strcmp(pname, "nnadmin") == 0) {
773 	who_am_i = I_AM_ADMIN;
774     } else if (strcmp(pname, "nnemacs") == 0) {
775 	who_am_i = I_AM_EMACS;
776     } else if (strcmp(pname, "nncheck") == 0) {
777 	who_am_i = I_AM_CHECK;
778     } else if (strcmp(pname, "nntidy") == 0) {
779 	who_am_i = I_AM_TIDY;
780     } else if (strcmp(pname, "nngoback") == 0) {
781 	who_am_i = I_AM_GOBACK;
782     } else if (strcmp(pname, "nngrep") == 0) {
783 	who_am_i = I_AM_GREP;
784     } else if (strcmp(pname, "nnpost") == 0) {
785 	who_am_i = I_AM_POST;
786     } else if (strcmp(pname, "nnview") == 0) {
787 	who_am_i = I_AM_VIEW;
788     } else if (strcmp(pname, "nnbatch") == 0) {
789 	who_am_i = I_AM_NN;
790 	batch_mode = 1;
791     } else {
792 	who_am_i = I_AM_NN;
793     }
794 
795 #ifdef NOV
796 #if defined(NOV_DIRECTORY) || defined(NOV_FILENAME)
797     if (who_am_i != I_AM_VIEW) {
798 
799 #ifdef NOV_DIRECTORY
800 	novartdir(NOV_DIRECTORY);
801 #endif				/* NOV_DIRECTORY */
802 
803 #ifdef NOV_FILENAME
804 	novfilename(NOV_FILENAME);
805 #endif				/* NOV_FILENAME */
806     }
807 #endif				/* NOV_DIRECTORY || NOV_FILENAME */
808 #endif				/* NOV */
809 
810     if (who_am_i == I_AM_NN || (who_am_i == I_AM_ADMIN && argc == 1)) {
811 	if (!batch_mode && !isatty(0)) {
812 	    if (who_am_i == I_AM_NN && argc == 2) {
813 		if (strcmp(argv[1], "-SPEW") == 0) {
814 		    who_am_i = I_AM_SPEW;
815 		    init_global();
816 		    goto nnspew;
817 		}
818 	    }
819 	    fprintf(stderr, "Input is not a tty\n");
820 	    exit(1);
821 	}
822     }
823 
824 #ifdef AUTHORIZE
825     init_execute();
826     if ((cmd = account('P', 0))) {
827 	switch (cmd) {
828 	    case 1:
829 		if (who_am_i == I_AM_POST) {
830 		    tprintf("You are not authorized to post news\n");
831 		    exit(41);
832 		}
833 		/* ok_to_post = 0; */
834 		cmd = 0;
835 		break;
836 	    case 2:
837 		tprintf("You are not authorized to read news\n");
838 		break;
839 	    case 3:
840 		tprintf("You cannot read news at this time\n");
841 		break;
842 	    case 4:
843 		tprintf("Your news reading quota is exceeded\n");
844 		break;
845 	    default:
846 		tprintf("Bad authorization -- reason %d\n", cmd);
847 		break;
848 	}
849 	if (cmd)
850 	    exit(40 + cmd);
851     }
852 #endif
853 
854     /*
855      * do this before nntp_check so nntp_server can be set on cmdline as nn
856      * -someoptions nntp_server=[somehost OR /somefile] -moreoptionsmaybe
857      *
858      * You really want to do that if you are using multiple servers and/or
859      * .newsrc files.  The downside is that you can't override an init file
860      * setting on the command line, but this is something I very rarely have
861      * seen a need for.
862      */
863 
864     if ((say_welcome = init_global()) < 0)
865 	nn_exitmsg(1, "%s: nn has not been invoked to initialize user files", pname);
866     parseargv_variables(argv, argc);
867     if ((say_welcome = init_global2()) < 0)
868 	nn_exitmsg(1, "%s: nn has not been invoked to initialize user files", pname);
869 
870     init_key_map();
871 
872 #ifndef AUTHORIZE
873     init_execute();
874 #endif
875 
876     init_macro();
877 
878     switch (who_am_i) {
879 	case I_AM_VIEW:
880 	    /* FALLTHROUGH */
881 	case I_AM_NN:
882 	    init_term(1);
883 	    visit_init_file(0, argv[1]);
884 	    if (!silent)
885 		prt_version();
886 	    break;
887 
888 	case I_AM_ADMIN:
889 	    if (argc == 1) {
890 		init_term(1);
891 		visit_init_file(0, (char *) NULL);
892 	    }
893 	    break;
894 
895 	case I_AM_CHECK:
896 	    /* FALLTHROUGH */
897 	case I_AM_TIDY:
898 	    /* FALLTHROUGH */
899 	case I_AM_GOBACK:
900 	    /* FALLTHROUGH */
901 	case I_AM_GREP:
902 	    init_term(0);
903 	    visit_init_file(0, (char *) NULL);
904 	    if (!silent && who_am_i != I_AM_CHECK)
905 		prt_version();
906 	    break;
907     }
908 
909     if (nn_locked())
910 	nn_exit(2);
911 
912 #ifdef NNTP
913     if (who_am_i != I_AM_VIEW) {
914 	nntp_check();
915     }
916 #endif
917 
918 nnspew:
919 
920     if (who_am_i != I_AM_VIEW)
921 	open_master(OPEN_READ);
922 
923     switch (who_am_i) {
924 
925 	case I_AM_VIEW:
926 	    /* FALLTHROUGH */
927 	case I_AM_NN:
928 	    if (say_welcome) {
929 		first_time_user = 1;
930 		display_file("adm.welcome", CLEAR_DISPLAY | CONFIRMATION);
931 		clrdisp();
932 	    }
933 	    if (show_motd_on_entry)
934 		if (display_motd(1))
935 		    silent = 1;
936 
937 	    group_name_args =
938 		parse_options(argc, argv, (char *) NULL, nn_options,
939 			      " [group | [+]folder]...");
940 
941 	    if (also_read_articles) {
942 		if (article_limit < 0)
943 		    article_limit = also_read_articles;
944 		do_kill_handling = 0;
945 		no_update++;
946 	    }
947 	    if (match_subject) {
948 		mask = match_subject;
949 		access_mode = ACC_ON_SUBJECT;
950 	    } else if (match_sender) {
951 		mask = match_sender;
952 		access_mode = ACC_ON_SENDER;
953 	    } else {
954 		mask = NULL;
955 		access_mode = 0;
956 	    }
957 
958 	    if (mask) {
959 		if (case_fold_search)
960 		    fold_string(mask);
961 		no_update++;
962 		do_kill_handling = 0;
963 	    }
964 	    if (nngrab_mode) {
965 		seq_cross_filtering = 1;
966 		hex_group_args = 1;
967 		no_update = 1;
968 	    }
969 	    if (merged_menu) {
970 		no_update++;
971 	    }
972 	    if (!no_update && group_name_args > 0)
973 		no_update = only_folder_args(argv + 1);
974 
975 	    break;
976 
977 	case I_AM_ADMIN:
978 	    admin_mode(argv[1]);
979 	    nn_exit(0);
980 	    break;
981 
982 	case I_AM_CHECK:
983 	    silent = 0;		/* override setting in init file */
984 	    group_name_args =
985 		parse_options(argc, argv, (char *) NULL, check_options,
986 			      " [group]...");
987 	    no_update = 1;	/* don't update .newsrc and LAST */
988 	    break;
989 
990 	case I_AM_EMACS:
991 	    silent = 1;
992 	    break;
993 
994 	case I_AM_TIDY:
995 	    group_name_args = opt_nntidy(argc, argv);
996 	    break;
997 
998 	case I_AM_GOBACK:
999 	    group_name_args = opt_nngoback(argc, &argv);
1000 	    break;
1001 
1002 	case I_AM_GREP:
1003 	    opt_nngrep(argc, argv);
1004 	    silent = 1;
1005 	    no_update = 1;
1006 	    break;
1007 
1008 	case I_AM_POST:
1009 	    do_nnpost(argc, argv);
1010 	    nn_exit(0);
1011 	    break;
1012 
1013 	case I_AM_SPEW:
1014 	    if (proto_lock(I_AM_SPEW, PL_SET) == 0) {
1015 		cmd = do_nnspew();
1016 		proto_lock(I_AM_SPEW, PL_CLEAR);
1017 	    } else
1018 		cmd = 1;	/* don't interfere with other nnspew */
1019 	    nn_exit(cmd);
1020 	    break;
1021     }
1022 
1023     if (who_am_i == I_AM_VIEW) {
1024 	named_group_sequence(argv + 1);
1025 	count_unread_articles();
1026 	if (read_news(access_mode, mask))
1027 	    clrdisp();
1028 	nn_exit(0);
1029     }
1030 
1031     /*
1032      * at one point, the command line variable settings were handled in the
1033      * next routine (named_group_sequence()), which meant you couldn't change
1034      * the .newsrc file.  I moved that processing into parseargv_variables(),
1035      * which is called quite early.  Olson, 11/99
1036      */
1037 
1038     visit_rc_file();
1039 
1040     if (group_name_args > 0)
1041 	named_group_sequence(argv + 1);
1042     else
1043 	normal_group_sequence();
1044 
1045     if (who_am_i != I_AM_TIDY && who_am_i != I_AM_GOBACK)
1046 	count_unread_articles();
1047 
1048     switch (who_am_i) {
1049 
1050 	case I_AM_EMACS:
1051 	    prt_unread("%U %G\n");
1052 	    emacs_mode();
1053 	    break;
1054 
1055 	case I_AM_CHECK:
1056 	    if (report_number_of_articles) {
1057 		prt_unread("%U");
1058 		break;
1059 	    }
1060 	    if (check_message_format) {
1061 		if (unread_articles || !silent)
1062 		    prt_unread(check_message_format);
1063 	    } else if (!silent) {
1064 		if (unread_articles || report_number_of_articles)
1065 		    prt_unread("There %i %u in %g\n");
1066 		else
1067 		    prt_unread((char *) NULL);
1068 	    }
1069 	    nn_exit(unread_articles ? 0 : 99);
1070 	    /* NOTREACHED */
1071 
1072 	case I_AM_TIDY:
1073 	    do_tidy_newsrc();
1074 	    break;
1075 
1076 	case I_AM_GOBACK:
1077 	    do_goback();
1078 	    break;
1079 
1080 	case I_AM_NN:
1081 	    if (check_db_update)
1082 		do_db_check();
1083 
1084 	    if (unread_articles == 0 &&
1085 		group_name_args == 0 &&
1086 		!also_read_articles &&
1087 		!prompt_for_group) {
1088 		if (!silent)
1089 		    prt_unread((char *) NULL);
1090 		break;
1091 	    }
1092 	    if (do_kill_handling)
1093 		do_kill_handling = init_kill();
1094 
1095 	    if (prompt_for_group) {
1096 
1097 		if (mask != NULL)
1098 		    nn_exitmsg(1, "Cannot use -s/-n with -g\n\r");
1099 
1100 		also_cross_postings = 1;
1101 		do {
1102 		    nn_raw();
1103 		    clrdisp();
1104 		    current_group = NULL;
1105 		    prompt_line = 2;
1106 		    cmd = goto_group(K_GOTO_GROUP, (article_header *) NULL, setup_access());
1107 
1108 		    if (cmd == ME_NO_REDRAW)
1109 			sleep(2);
1110 		} while (repeat_group_query && cmd != ME_QUIT && cmd != ME_NO_REDRAW);
1111 		clrdisp();
1112 		unset_raw();
1113 		break;
1114 	    }
1115 	    if (!no_update && article_limit == 0) {
1116 		catch_up();
1117 		break;
1118 	    }
1119 	    if (merged_menu) {
1120 		merge_and_read(access_mode | setup_access(), mask);
1121 		clrdisp();
1122 		break;
1123 	    }
1124 	    if (read_news(access_mode, mask)) {
1125 		clrdisp();
1126 
1127 		if (master.db_lock[0])
1128 		    tprintf("Database has been locked:\n%s\n", master.db_lock);
1129 
1130 		if (!also_read_articles &&
1131 		    unread_articles > 0 &&
1132 		    !silent && group_name_args == 0)
1133 		    prt_unread("There %i still %u in %g\n\n\r");
1134 		break;
1135 	    }
1136 	    gotoxy(0, Lines - 1);
1137 	    if (group_name_args == 0) {
1138 		clrdisp();
1139 		tprintf("No News (is good news)\n");
1140 	    } else
1141 		tprintf("\r\n");
1142 	    break;
1143 
1144 	case I_AM_GREP:
1145 	    do_grep(argv + 1);
1146 	    break;
1147     }
1148 
1149     nn_exit(0);
1150     /* NOTREACHED */
1151     return 0;
1152 }
1153 
1154 /*
1155  * nn_exit() --- called whenever a program exits.
1156  */
1157 
1158 void
nn_exit(int n)1159 nn_exit(int n)
1160 {
1161     static int      loop = 0;
1162 
1163     if (loop)
1164 	exit(n);
1165     loop++;
1166 
1167     visual_off();
1168 
1169 #ifdef NNTP
1170     nntp_cleanup();
1171 #endif				/* NNTP */
1172 
1173     close_master();
1174     flush_newsrc();
1175 
1176     if (who_am_i == I_AM_NN)
1177 	log_usage();
1178 
1179     if (must_unlock)
1180 	proto_lock(I_AM_NN, PL_CLEAR);
1181 
1182 #if 0
1183     malloc_verify(0);
1184     malloc_shutdown();
1185 #endif
1186 
1187     exit(n);
1188 }
1189