nn-6.7.4/0000755000175000017500000000000015214615172011555 5ustar mtpinsmtpinsnn-6.7.4/back_act.sh0000644000175000017500000000113605750347714013651 0ustar mtpinsmtpins# prefix is inserted above by make # # back_act will maintain a set of `old' active files # in the DB directory where they can be used by nngoback # to backtrack the rc file a number of days. # # It should be invoked by cron every day at midnight. # It should run as user `news'! # # Call: back_act [days] # Default: keep copy of active file for the last 14 days. cd $DB || exit 1 p=${1-15} l="" while [ "$p" -gt 0 ] do i="`expr $p - 1`" if [ -f active.$i ] then mv active.$i active.$p l=$p elif [ -n "$l" ] then ln active.$l active.$p fi p=$i done cp $ACTIVE active.0 chmod 644 active.0 nn-6.7.4/patchlevel.h0000644000175000017500000000433715213127033014055 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Modification history: * * 1988-07-20: Release 6.0beta (Denmark) * 1988-11-01: Release 6.1 (Europe) * 1989-03-21: Release 6.2beta (FTP) * 1989-05-30: Release 6.3 (comp.sources.unix) * 1989-09-08: Release 6.3.7 (FTP) * 1990-03-03: Release 6.4beta (FTP) * 1990-05-07: Release 6.4 (comp.sources.unix) * * 1990-05-10: Patch #1 (6.4.1) - HIGH * 1990-05-15: Patch #2 (6.4.2) - HIGH * 1990-05-21: Patch #3 (6.4.3) - HIGH * 1990-05-29: Patch #4 (6.4.4) - HIGH * 1990-06-11: Patch #5 (6.4.5) - MEDIUM * 1990-06-25: Patch #6 (6.4.6) - MEDIUM * 1990-07-09: Patch #7 (6.4.7) - LOW * 1990-07-16: Patch #8 (6.4.8) - HIGH * 1990-07-19: Patch #9 (6.4.9) - MEDIUM * 1990-09-18: Patch #10 (6.4.10) - HIGH * 1990-10-05: Patch #11 (6.4.11) - HIGH * 1990-11-07: Patch #12 (6.4.12) - LOW * 1991-02-06: Patch #13 (6.4.13) - MEDIUM * 1991-03-22: Patch #14 (6.4.14) - MEDIUM * 1991-04-02: Patch #15 (6.4.15) - LOW * 1991-04-18: Patch #16 (6.4.16) - HIGH * * Released by Larry Bouzane * 1991-11-19: Patch #17 (6.4.17) - MEDIUM * 1991-12-01: Patch #18 (6.4.18) - MEDIUM * * Released by Peter Wemm * 1993-09-01: Release 6.5.0.b3 (FTP) * * Released by Michael T Pins * 1996-07-28: Release 6.5.0.b4 * 1996-08-04: Release 6.5.0.b5 * 1996-08-11: Release 6.5.0.b6 * 1996-08-14: Release 6.5.0.b7 * 1996-08-19: Release 6.5.0 * 1996-08-25: Patch #1 (6.5.1) * 1998-06-26: Patch #2 (6.5.2) * 1998-07-15: Patch #3 (6.5.3) * 1999-06-09: Patch #4 (6.5.4) * 2000-01-04: Patch #5 (6.5.5) * 2000-02-05: Patch #6 (6.5.6) * * 2001-09-18: Release 6.6 * 2001-10-06: Patch #1 (6.6.1) * 2001-10-15: Patch #2 (6.6.2) * 2002-02-12: Patch #3 (6.6.3) * 2002-06-25: Patch #4 (6.6.4) * 2003-01-25: Patch #5 (6.6.5) * * 2005-03-24: 6.7 alpha * 2005-04-07: 6.7 beta * 2005-04-21: Release 6.7.0 * 2005-06-14: Patch #1 (6.7.1) * 2005-07-21: Patch #2 (6.7.2) * 2005-11-15: Patch #3 (6.7.3) * 2026-06-12: Patch #4 (6.7.4) */ #ifndef _PATCHLEVEL_H #define _PATCHLEVEL_H 1 #define RELEASE "6.7" #define PATCHLEVEL "4" #endif /* _PATCHLEVEL_H */ nn-6.7.4/pack_date.h0000644000175000017500000000015407751420631013642 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ time_stamp pack_date(char *); nn-6.7.4/nnspew.sh0000644000175000017500000000106405750350011013414 0ustar mtpinsmtpins# nnspew -- generate subject line database # # From: James A. Woods (ames!jaw), NASA Ames Research Center # # Updates the auxiliary 'subjects' file. It must be activated regularly # via a system-wide 'cron' or 'at' command, e.g. # # 2 6,9,12,15,18,21 * * * root /bin/nice /usr/lib/nn/nnspew trap "rm -f $TMP/nnsubj$$* ; exit" 0 1 2 15 # We use a "secret" 'nn -SPEW' call to do the hard part: if $BIN/nn -SPEW < /dev/null > $TMP/nnsubj$$a then if sort -u $TMP/nnsubj$$a > $TMP/nnsubj$$b then mv $TMP/nnsubj$$b $DB/subjects chmod 644 $DB/subjects fi fi nn-6.7.4/menu.c0000644000175000017500000015427610230572260012675 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * selection mode menu */ #include #include #include "config.h" #include "global.h" #include "articles.h" #include "answer.h" #include "db.h" #include "execute.h" #include "folder.h" #include "group.h" #include "init.h" #include "keymap.h" #include "kill.h" #include "macro.h" #include "match.h" #include "menu.h" #include "more.h" #include "newsrc.h" #include "regexp.h" #include "save.h" #include "sort.h" #include "nn_term.h" /* menu.c */ struct menu_info; static article_number root_article(register article_number root); static article_number next_root_article(register article_number root); static void set_root_if_closed(void); static article_number thread_counters(article_number art); static void cursor_at_id(void); static attr_type closed_attr(register struct menu_info * mi, char *cbuf); static void mark(void); static void toggle(void); static int do_auto_kill(void); static int do_auto_select(regexp * re, int mode); static int quit_preview(int cmd); static void count_selected_articles(void); static int show_articles(void); static int get_k_cmd_1(void); static int get_k_cmd(void); static int repl_attr(register article_number first, register article_number last, register attr_type old, register attr_type new, int update); static int repl_attr_subject(attr_type old, attr_type new, int update); static int repl_attr_all(attr_type old, attr_type new, int update); static void get_purpose(char *purpose); static int do_consolidation(void); int echo_prefix_key = 1; /* echo prefix keys */ int preview_window = 0; /* size of preview window */ int fmt_linenum = 1;/* menu line format */ int fmt_rptsubj = 0;/* repeat identical subjects if !0 */ int novice = 1; /* novice mode -- use extended prompts */ int long_menu = 0; /* don't put empty lines around menu lines */ int delay_redraw = 0; /* prompt again if :-command clears * screen */ int slow_mode = 0; /* mark selected articles with *s */ int re_layout = 0; /* Re: format presentation on menus */ int collapse_subject = 25; /* collapse long subjects at position */ int conf_group_entry = 0; /* ask whether group should be * entered */ int conf_entry_limit = 0; /* ask only if more than .. unread */ int mark_read_skip = 4; /* effect of X command */ int mark_read_return = 0; /* effect of Z command */ int mark_next_group = 0; /* effect of N command */ int show_purpose_mode = 1; /* 0: never, 1: new, 2: always */ int read_ret_next_page = 0; /* Z returns to next page */ int consolidated_menu = 0; /* show only root articles */ int save_closed_mode = 13; /* ask how to save closed subj, dflt * all */ int auto_select_rw = 0; /* select subject read or written */ int auto_select_closed = 1; /* select all in closed subject */ int menu_spacing = 0; /* number of screen lines per menu * line */ char *counter_delim_left = "["; char *counter_delim_right = "] "; int counter_padding = 5; /* counters are padded to align * subjects */ int auto_preview_mode = 0; /* preview rather than select */ int preview_continuation = 12; /* what to do after preview */ int preview_mark_read = 1; /* previewed articles are A_READ */ int select_on_sender = 0; /* find command selects on sender */ int auto_select_subject = 0; /* auto select articles with * same subj. */ int auto_read_limit = 0; /* ignore auto_read_mode if less * articles */ char delayed_msg[100] = ""; /* give to msg() after redraw */ int flush_typeahead = 0; extern int also_read_articles; extern int merged_menu; extern int case_fold_search; extern int ignore_fancy_select; extern int kill_file_loaded; extern int new_read_prompt; extern int any_message; extern int enable_stop; extern int alt_cmd_key, in_menu_mode; extern int mouse_y; /* mouse event position */ extern int mouse_state; extern key_type erase_key; extern int get_from_macro; static regexp *regular_expr = NULL; static int firstl; /* first menu line */ static article_number firsta; /* first article on menu (0 based) */ static article_number nexta; /* first article on next menu */ static article_number cura; /* current article */ static article_number next_cura; /* article to become cura if >= 0 */ static article_number numa; /* no of articles on menu - 1 */ static attr_type last_attr; #define INTERVAL1 ('z' - 'a' + 1) #define INTERVAL2 ('9' - '0' + 1) static char ident[] = "abcdefghijklmnopqrstuvwxyz0123456789"; char attributes[30] = " .,+=#! **"; /* Corresponds to A_XXXX in * data.h */ static int menu_length; /* current no of line on menu */ static int menu_articles; /* current no of articles on menu */ static struct menu_info { /* info for each menu line */ int mi_cura; /* cura corresponding to this menu line */ int mi_total; /* total number of articles with this subject */ int mi_unread; /* no of unread articles with this subject */ int mi_selected;/* no of selected articles with this subject */ int mi_left; /* no of articles marked for later viewing */ int mi_art_id; /* article id (for mark()) */ } menu_info[INTERVAL1 + INTERVAL2]; static struct menu_info *art_id_to_mi[INTERVAL1 + INTERVAL2]; #define IS_VISIBLE(ah) (((ah)->flag & (A_CLOSED | A_ROOT_ART)) != A_CLOSED) int prt_replies(int level) { int re; if (level == 0) return 0; re = level & 0x80; level &= 0x7f; switch (re_layout) { case 1: if (!re) return 0; so_printf(">"); return 1; case 2: switch (level) { case 0: return 0; case 1: so_printf(">"); return 1; default: so_printf("%d>", level); return level < 10 ? 2 : 3; } case 3: so_printf("Re: "); return 4; case 4: if (level == 0 && re) level++; break; } if (level < 10) { so_printf("%-.*s", level, ">>>>>>>>>"); return level; } so_printf(">>>%3d >>>>", level); return 11; } static article_number root_article(register article_number root) { register article_header *ah = articles[root]; if (ah->flag & A_ROOT_ART) return root; while (root > 0) { if (articles[root]->flag & A_ROOT_ART) break; root--; } return root; } static article_number next_root_article(register article_number root) { while (++root < n_articles) if (articles[root]->flag & A_ROOT_ART) break; return root; } static void set_root_if_closed(void) { if (articles[firsta + cura]->flag & A_CLOSED) cura = root_article(firsta + cura) - firsta; } /* * count info for thread containing article #art (must be on menu!) * returns article number for next root article */ static article_number thread_counters(article_number art) { register struct menu_info *mi; register article_number n; int total, unread, selected, left; if (!(articles[art]->flag & A_CLOSED)) return art + 1; total = unread = selected = left = 0; n = art = root_article(art); while (n < n_articles) { if (articles[n]->attr == 0) unread++; else if (articles[n]->attr & A_SELECT) selected++; else if (articles[n]->attr == A_LEAVE_NEXT) left++; total++; if (++n == n_articles) break; if (articles[n]->flag & A_ROOT_ART) break; } unread += selected; mi = menu_info + articles[art]->menu_line; mi->mi_total = total; mi->mi_unread = unread; mi->mi_selected = selected; mi->mi_left = left; return n; } static void cursor_at_id(void) { gotoxy(0, firstl + articles[firsta + cura]->menu_line); fl; /* place cursor at current article id */ save_xy(); } static attr_type closed_attr(register struct menu_info * mi, char *cbuf) { char lft[10], sel[10], unr[10]; attr_type cattr; if (mi->mi_total == mi->mi_left) cattr = A_LEAVE_NEXT; else if (mi->mi_unread == 0) cattr = A_READ; else if (mi->mi_total == mi->mi_selected) cattr = A_SELECT; else if (auto_select_closed == 1 && mi->mi_unread == mi->mi_selected) cattr = A_SELECT; else if (mi->mi_selected) cattr = A_KILL; /* pseudo flag -> highlight cbuf */ else cattr = 0; lft[0] = sel[0] = unr[0] = NUL; if (mi->mi_left && mi->mi_left < mi->mi_unread) sprintf(lft, "%d,", mi->mi_left); if (mi->mi_selected && mi->mi_selected < mi->mi_unread) sprintf(sel, "%d/", mi->mi_selected); if (mi->mi_unread && mi->mi_unread < mi->mi_total) sprintf(unr, "%d:", mi->mi_unread); sprintf(cbuf, "%s%s%s%d", lft, sel, unr, mi->mi_total); return cattr; } static int subj_indent; static void mark(void) { register article_header *ah; register struct menu_info *mi; int lno, lnum, lsubj, lname; int pad; char cbuf[80]; attr_type cattr = 0; ah = articles[firsta + cura]; if (!IS_VISIBLE(ah)) return; last_attr = ah->attr; if (ah->disp_attr == A_NOT_DISPLAYED) { mi = &menu_info[ah->menu_line]; lno = firstl + ah->menu_line; gotoxy(0, lno); tputc(ident[mi->mi_art_id]); cattr = closed_attr(mi, cbuf); goto print_line; } if (cura < 0 || cura > numa) return; lno = firstl + ah->menu_line; if (ah->flag & A_CLOSED) { struct menu_info old; char oldctr[80]; mi = &menu_info[ah->menu_line]; old = *mi; thread_counters(firsta + cura); if (old.mi_total == mi->mi_total && old.mi_selected == mi->mi_selected && old.mi_left == mi->mi_left && old.mi_unread == mi->mi_unread) return; cattr = closed_attr(mi, cbuf); if (!slow_mode) goto print_line; closed_attr(&old, oldctr); if (strcmp(cbuf, oldctr)) goto print_line; last_attr = cattr; } if (last_attr == ah->disp_attr) return; /* A_AUTO_SELECT will not occur here! */ if (!slow_mode) { if (last_attr == A_SELECT) { if ((ah->disp_attr & A_SELECT) == 0) { goto print_line; } } else { if (ah->disp_attr & A_SELECT) { goto print_line; } } } gotoxy(1, lno); tputc(attributes[ah->attr]); goto out; print_line: /* menu line formats: */ /* 1 3 8 10 20 22 xx */ /* : : : : : : : */ /* -1 id name:8 subject */ /* 0 id name subject +lines */ /* 1 id name lines subject */ /* 2 id lines subject */ /* 3 id subject */ /* 4 id subject (or as 1 if short subject) */ if ((ah->flag & A_CLOSED) == 0) { cattr = ah->attr; cbuf[0] = NUL; } if (fmt_linenum > 4) fmt_linenum = 1; if (!slow_mode && (cattr & A_SELECT)) { if (so_gotoxy(1, lno, 1) == 0) tputc(attributes[A_SELECT]); } else { gotoxy(1, lno); tputc(cattr == A_KILL ? ' ' : attributes[cattr]); } if (ah->lines < 10) lnum = 1; else if (ah->lines < 100) lnum = 2; else if (ah->lines < 1000) lnum = 3; else if (ah->lines < 10000) lnum = 4; else lnum = 5; lsubj = Columns - cookie_size - 2; /* ident char + space */ switch (fmt_linenum) { case -1: lsubj -= 9; so_printf("%-8.8s ", ah->sender); goto no_counters; case 0: lsubj -= Name_Length + 1 + 2 + lnum; /* name. .subj. +.lines */ so_printf("%-*s ", Name_Length, ah->sender); break; case 4: if ((int) ah->subj_length > (lsubj - Name_Length - 5)) if (fmt_rptsubj || lno == firstl || (ah->flag & A_SAME) == 0) { so_printf(" "); lsubj -= 2; break; } /* else use layout 1, so fall thru */ /* FALLTHROUGH */ case 1: lsubj -= Name_Length + 5; /* name.lines. .subj (name may be shortened) */ lname = Name_Length + 2 - lnum; so_printf("%-*.*s ", lname, lname, ah->sender); so_printf(ah->lines >= 0 ? "%d " : "? ", ah->lines); break; case 2: lsubj -= 6; so_printf(ah->lines >= 0 ? "%5d " : " ? ", ah->lines); break; case 3: break; } if (cbuf[0]) { so_printf("%s", counter_delim_left); if (cattr == A_KILL) so_gotoxy(-1, -1, 0); so_printf("%s", cbuf); if (cattr == A_KILL) so_end(); so_printf("%s", counter_delim_right); pad = strlen(cbuf) + strlen(counter_delim_left) + strlen(counter_delim_right); if (cattr == A_KILL) pad += cookie_size * 2; lsubj -= pad > subj_indent ? pad : subj_indent; pad = subj_indent - pad; } else { lsubj -= subj_indent; pad = subj_indent; } if (pad > 0) { if (pad > 20) pad = 20; so_printf(" " + 20 - pad); } no_counters: if (!fmt_rptsubj && lno > firstl && ah->flag & A_SAME) { if (ah->replies == 0 || prt_replies(ah->replies) == 0) so_printf("-"); } else { lsubj -= prt_replies(ah->replies); if (lsubj >= (int) ah->subj_length) so_printf("%s", ah->subject); else if (collapse_subject < 0) so_printf("%-.*s", lsubj, ah->subject); else { if (collapse_subject > 0) so_printf("%-.*s", collapse_subject, ah->subject); so_printf("<>%s", ah->subject + ah->subj_length - lsubj + collapse_subject + 2); } } if (fmt_linenum == 0) so_printf(ah->lines >= 0 ? " +%d" : " +?", ah->lines); so_end(); if ((ah->flag & A_CLOSED) && lsubj > (int) ah->subj_length) clrline_noflush(); out: ah->disp_attr = last_attr; return; } static void toggle(void) { last_attr = articles[firsta + cura]->attr = articles[firsta + cura]->attr & A_SELECT ? 0 : A_SELECT; } static int do_auto_kill(void) { register article_number i; register article_header *ah, **ahp; int any = 0; for (i = 0, ahp = articles; i < n_articles; i++, ahp++) { ah = *ahp; if (auto_select_article(ah, 0)) { ah->attr = A_KILL; any = 1; } } return any; } /* * perform auto selections that are not already selected * if article is in range firsta..firsta+numa (incl) mark article */ static int do_auto_select(regexp * re, int mode) { register article_number i, o_cura; register article_header *ah, **ahp; int count = 0, should_mark; if (mode == 1 && re == NULL) if (!kill_file_loaded && !init_kill()) return 0; o_cura = cura; should_mark = 0; /* * note: this code assumes that a visible article will be found before * anything is marked */ for (i = 0, ahp = articles; i < n_articles; i++, ahp++) { ah = *ahp; if (IS_VISIBLE(ah)) { if (should_mark) { mark(); should_mark = 0; } cura = i - firsta; } if (re != NULL) { if (!regexec_cf(re, select_on_sender ? ah->sender : ah->subject)) continue; } else if (!auto_select_article(ah, mode)) continue; count++; if (ah->attr & A_SELECT) continue; ah->attr = A_SELECT; if (firsta <= i && i <= (firsta + numa)) should_mark = 1; } if (should_mark) mark(); if (count) msg("Selected %d article%s", count, plural((long) count)); else msg("No selections"); cura = o_cura; return 0; } static int quit_preview(int cmd) { int op; if ((firsta + cura) >= n_articles) return 1; op = preview_continuation; if (cmd == MC_PREVIEW_NEXT) op /= 10; op %= 10; switch (op) { case 0: return 1; case 1: return 0; case 2: return articles[firsta + cura]->flag & A_ROOT_ART; } return 0; } long n_selected; int show_art_next_invalid; static void count_selected_articles(void) { register article_number cur; n_selected = 0; for (cur = 0; cur < n_articles; cur++) { if (articles[cur]->attr & A_SELECT) n_selected++; } } static int show_articles(void) { register article_number cur, next, prev = -1; register article_header *ah; article_number elim_list[1]; register int mode; int cmd, again; attr_type o_attr; do { for (cur = 0; cur < n_articles; cur++) { if (articles[cur]->attr & A_SELECT) break; } while (cur < n_articles) { for (next = cur + 1; next < n_articles; next++) { if (articles[next]->attr & A_SELECT) break; } show_art_next_invalid = 0; show: ah = articles[cur]; o_attr = ah->attr; ah->attr = 0; if (auto_select_rw && !ignore_fancy_select && !auto_select_article(ah, 1)) enter_kill_file(current_group, ah->subject, 6, 30); if (new_read_prompt) count_selected_articles(); mode = 0; if (prev >= 0) mode |= MM_PREVIOUS; if (next == n_articles) mode |= MM_LAST_SELECTED; if ((cur + 1) >= n_articles) mode |= MM_LAST_ARTICLE; if (cur == 0) mode |= MM_FIRST_ARTICLE; cmd = more(ah, mode, 0); switch (cmd) { case MC_DO_KILL: if (do_auto_kill()) { elim_list[0] = cur; elim_articles(elim_list, 1); cur = elim_list[0]; if (cur >= n_articles || cur < 0) cur = n_articles; else { for (prev = cur; prev >= 0; --prev) if ((articles[prev]->attr & A_READ) != 0) break; for (; cur < n_articles; ++cur) if ((articles[cur]->attr & A_SELECT) != 0) break; } continue; } break; case MC_DO_SELECT: break; case MC_PREV: ah->attr = o_attr; if (prev == next) break; next = cur; cur = prev; prev = next; goto show; case MC_NEXTSUBJ: ah->attr = A_READ; for (next = cur + 1; next < n_articles; next++) { if ((ah = articles[next])->flag & A_ROOT_ART) break; ah->attr = A_READ; } for (; next < n_articles; next++) { if (articles[next]->attr & A_SELECT) break; } break; case MC_ALLSUBJ: ah->attr = A_READ; for (next = cur + 1; next < n_articles; next++) { ah = articles[next]; if (ah->flag & A_ROOT_ART) break; ah->attr = A_SELECT; } for (next = cur + 1; next < n_articles; next++) if (articles[next]->attr & A_SELECT) break; break; case MC_MENU: ah->attr = o_attr; if (nexta - firsta < n_articles) { /* Keep a little article context by making the */ /* current article go on menu line 6 if possible */ if (IS_VISIBLE(articles[cur])) firsta = cur; else firsta = root_article(cur); for (next = 0; firsta > 0 && next < 5; next++) { firsta--; if (!IS_VISIBLE(articles[firsta])) firsta = root_article(firsta); } } next_cura = cur - firsta; return MC_MENU; case MC_NEXT: if (ah->attr == 0) /* Not set by more (sufficient ?!?!) */ ah->attr = A_READ; break; case MC_BACK_ART: ah->attr = o_attr ? o_attr : A_SEEN; next = cur - 1; break; case MC_FORW_ART: ah->attr = o_attr ? o_attr : A_SEEN; next = cur + 1; break; case MC_NEXTGROUP: case MC_REENTER_GROUP: case MC_QUIT: ah->attr = o_attr; return cmd; case MC_READGROUP: for (cur = 0; cur < n_articles; cur++) { ah = articles[cur]; if (ah->attr == 0 || (ah->attr & A_SELECT)) ah->attr = A_READ; } return MC_NEXTGROUP; } if (show_art_next_invalid) for (next = cur + 1; next < n_articles; next++) { if (articles[next]->attr & A_SELECT) break; } prev = cur; cur = next; } for (cur = 0; cur < n_articles; cur++) if (articles[cur]->attr & A_SELECT) break; again = 0; if (cur < n_articles) continue; for (cur = 0; cur < n_articles; cur++) { ah = articles[cur]; if (ah->attr == A_LEAVE) { if (again == 0) { prompt("Show left over articles again now? "); if (yes(0) <= 0) break; } ah->attr = A_SELECT; again++; } } if (again > 1) sprintf(delayed_msg, "Showing %d articles again", again); } while (again); return MC_READGROUP; } static article_number article_id; static key_type cur_key; static int is_k_select; /* set when K_ARTICLE_ID was really K_SELECT */ #define MOUSE (mouse_y >= 0) static int mouse_map(int map) { /* remap mouse functions back into normal functions */ if (map == K_M_SELECT) { map = K_ARTICLE_ID; } else if (map == K_M_PREVIEW) { map = K_PREVIEW; } else if (map == K_M_SELECT_SUBJECT) { map = K_SELECT_SUBJECT; } else if (map == K_M_SELECT_RANGE) { map = K_SELECT_RANGE; } else return map; /* deal with mouse article selection */ if (MOUSE) { if ((mouse_y >= firstl) && (mouse_y <= firstl + menu_length - 1)) { article_id = mouse_y - firstl; article_id = art_id_to_mi[article_id]->mi_cura; } } return map; } static int get_k_cmd_1(void) { register int c, map; int *key_map = menu_key_map; if (flush_typeahead) flush_input(); loop: article_id = -1; if ((c = get_c()) & GETC_COMMAND) { cur_key = K_interrupt; map = c & ~GETC_COMMAND; } else { cur_key = c; map = key_map[c]; } if (s_hangup) map = K_QUIT; if (map & K_PREFIX_KEY) { key_map = keymaps[map & ~K_PREFIX_KEY].km_map; if (echo_prefix_key) msg("%s", key_name(cur_key)); goto loop; } is_k_select = 0; if (map == K_SELECT) { map = K_ARTICLE_ID; article_id = cura; is_k_select = 1; } else if (map & K_ARTICLE_ID) { article_id = map & ~K_ARTICLE_ID; map = K_ARTICLE_ID; if (article_id < 0 || article_id >= menu_articles) { ding(); goto loop; } article_id = art_id_to_mi[article_id]->mi_cura; } else map = mouse_map(map); if (any_message && map != K_LAST_MESSAGE) clrmsg(-1); return map; } static int get_k_cmd(void) { register int map; map = get_k_cmd_1(); if (map & K_MACRO) map = orig_menu_map[cur_key]; return map; } char * pct(long start, long end, long first, long last) { long n = end - start; static char buf[16]; char *fmt; if (first <= start || n <= 0) { if (last >= end || n <= 0) return "All"; else fmt = "Top %d%%"; } else if (last >= end) { return "Bot"; } else { fmt = "%d%%"; } sprintf(buf, fmt, ((last - start) * 100) / n); return buf; } static int repl_attr(register article_number first, register article_number last, register attr_type old, register attr_type new, int update) { int any; register article_header *ah; article_number ocura = cura; if (new == old) return 0; if (new == A_KILL) update = 0; any = 0; cura = -1; while (first < last) { ah = articles[first]; if (cura >= 0 && ah->flag & A_ROOT_ART) { set_root_if_closed(); mark(); cura = -1; } if (old == A_KILL || ah->attr == old) { ah->attr = new; if (update && first >= firsta && first < nexta) { cura = first - firsta; if ((ah->flag & A_CLOSED) == 0) { mark(); cura = -1; } } any = 1; } first++; } if (cura >= 0) { set_root_if_closed(); mark(); } cura = update ? last - firsta : ocura; return any; } static int repl_attr_subject(attr_type old, attr_type new, int update) { int f, l; f = root_article(firsta + cura); l = next_root_article(firsta + cura); if (old == A_SELECT) (void) repl_attr(f, l, A_AUTO_SELECT, A_SELECT, update); return repl_attr(f, l, old, new, update); } static int repl_attr_all(attr_type old, attr_type new, int update) { return repl_attr((article_number) 0, n_articles, old, new, update); } static void get_purpose(char *purpose) { #ifdef CACHE_PURPOSE register char *cp; cp = purp_lookup(current_group->group_name); strncpy(purpose, cp, 76); return; #else FILE *f; char line[256], *group; register char *cp, *pp; register int len; if (current_group == NULL) return; if ((current_group->master_flag & M_VALID) == 0) return; if (current_group->group_flag & G_FAKED) return; if ((f = open_purpose_file()) == NULL) return; group = current_group->group_name; len = current_group->group_name_length; while (fgets(line, 256, f) != NULL) { if (!isascii(line[len]) || !isspace(line[len])) continue; if (strncmp(line, group, len)) continue; cp = line + len; while (*cp && isspace(*cp)) cp++; for (pp = purpose, len = 76; --len >= 0 && *cp && *cp != NL;) *pp++ = *cp++; *pp = NUL; } #endif /* CACHE_PURPOSE */ } /* * bypass_consolidation may be set to temporarily overrule the global * consolidated_menu variable: * 1: don't consolidate (e.g. for G=... ) * 2: do consolidate * 3: use consolidated_mode */ int bypass_consolidation = 0; static int cur_bypass = 0; /* current bypass status (see below) */ static int do_consolidation(void) { int consolidate; switch (bypass_consolidation) { case 0: break; case 1: cur_bypass = -1; /* no consolidation */ break; case 2: cur_bypass = 1; /* force consolidation */ break; case 3: cur_bypass = 0; /* reset bypass to use consolidated_menu */ break; } bypass_consolidation = 0; if (cur_bypass) consolidate = cur_bypass > 0; else consolidate = consolidated_menu; if (consolidate) for (nexta = 0; nexta < n_articles; nexta++) articles[nexta]->flag |= A_CLOSED; else for (nexta = 0; nexta < n_articles; nexta++) articles[nexta]->flag &= ~A_CLOSED; return consolidate; } int menu(fct_type print_header) { register int k_cmd, cur_k_cmd; register article_header *ah; register struct menu_info *mi; int consolidate, o_bypass; int last_k_cmd; int menu_cmd = 0, temp; int save_selected; article_number last_save; attr_type orig_attr, junk_attr; int doing_unshar, did_unshar, junk_prompt; char *fname, *savemode; int maxa; /* max no of articles per menu page */ article_number o_firsta, temp1 = 0, temp2; int o_mode; /* for recursive calls */ static int menu_level = 0; char purpose[80], pr_fmt[60]; article_number elim_list[3]; int entry_check; int auto_read; long o_selected; #define menu_return(cmd) \ { menu_cmd = (cmd); goto menu_exit; } flush_input(); o_firsta = firsta; o_mode = in_menu_mode; o_selected = n_selected; in_menu_mode = 1; menu_level++; if (menu_level == 1) { if ((current_group->group_flag & G_COUNTED) && n_articles != current_group->unread_count) { add_unread(current_group, -1); current_group->unread_count = n_articles; add_unread(current_group, 0); } entry_check = conf_group_entry && n_articles > conf_entry_limit; auto_read = auto_read_limit < 0 || n_articles <= auto_read_limit; } else { entry_check = 0; auto_read = 0; } sprintf(pr_fmt, menu_level == 1 ? "\1\2-- SELECT %s-----%%s-----\1" : "\1\2-- SELECT %s-----%%s-----<%s%d>--\1", novice ? "-- help:? " : "", novice ? "level " : "", menu_level); purpose[0] = NUL; if (!merged_menu) switch (show_purpose_mode) { case 0: break; case 1: if ((current_group->group_flag & G_NEW) == 0) break; /* FALLTHROUGH */ case 2: get_purpose(purpose); if (purpose[0]) strcpy(delayed_msg, purpose); } o_bypass = cur_bypass; cur_bypass = 0; consolidate = do_consolidation(); firsta = 0; while (firsta < n_articles && articles[firsta]->attr == A_SEEN) firsta++; if (firsta == n_articles) firsta = 0; next_cura = -1; cur_k_cmd = K_UNBOUND; #ifdef HAVE_JOBCONTROL #define REDRAW_CHECK if (s_redraw) goto do_redraw do_redraw: /* safe to clear here, because we are going to redraw anyway */ s_redraw = 0; #else #define REDRAW_CHECK #endif redraw: s_keyboard = 0; empty_menu_hack: /* do: "s_keyboard=1; goto empty_menu_hack;" */ if (!slow_mode) s_keyboard = 0; nexta = firsta; clrdisp(); if (entry_check) { prompt_line = firstl = CALL(print_header) (); prompt("\1Enter?\1 "); if ((temp = yes(0)) <= 0) { if (temp < 0) { prompt("\1Mark as read?\1 "); if ((temp = yes(0)) < 0) menu_return(ME_QUIT); if (temp > 0) repl_attr_all(A_KILL, A_READ, 0); } menu_return(ME_NEXT); } gotoxy(0, firstl); clrline(); } if (auto_read) { auto_read = entry_check = 0; if (repl_attr_all(0, A_AUTO_SELECT, 0)) { k_cmd = K_READ_GROUP_UPDATE; if (purpose[0]) strcpy(delayed_msg, purpose); else sprintf(delayed_msg, "Entering %s, %ld articles", current_group->group_name, (long) n_articles); goto do_auto_read; } } if (!entry_check) firstl = CALL(print_header) (); entry_check = 0; maxa = Lines - preview_window - firstl - 2; if (!long_menu) firstl++, maxa -= 2; if (maxa > (INTERVAL1 + INTERVAL2)) maxa = INTERVAL1 + INTERVAL2; nextmenu: no_raw(); gotoxy(0, firstl); clrpage(); if (articles[nexta]->flag & A_CLOSED) nexta = root_article(nexta); firsta = nexta; cura = 0; REDRAW_CHECK; menu_length = 0; menu_articles = 0; goto draw_menu; partial_redraw: next_cura = cura; partial_redraw_nc: nexta = firsta + cura; menu_length = articles[nexta]->menu_line; menu_articles = menu_info[menu_length].mi_art_id; gotoxy(0, firstl + menu_length); clrpage(); draw_menu: subj_indent = consolidate ? counter_padding : 0; if (!s_keyboard) { int first_menu_line = menu_length; mi = menu_info + menu_length; while (nexta < n_articles) { REDRAW_CHECK; ah = articles[nexta]; if (ah->flag & A_HIDE) { ah->menu_line = menu_length; /* just in case.... */ continue; } if (menu_length > first_menu_line) { switch (menu_spacing) { case 0: break; case 1: if ((ah->flag & A_ROOT_ART) == 0) break; /* XXX: bug? Another fall? */ case 2: menu_length++; mi++; break; } if (menu_length >= maxa) break; } ah->menu_line = menu_length; art_id_to_mi[menu_articles] = mi; mi->mi_art_id = menu_articles++; mi->mi_cura = cura = nexta - firsta; if (ah->flag & A_CLOSED) { /* skip rest of thread */ nexta = thread_counters(nexta); if (nexta == firsta + cura + 1) ah->flag &= ~A_CLOSED; else { article_number tmpa = firsta + cura; while (++tmpa < nexta) articles[tmpa]->menu_line = menu_length; } } else nexta++; ah->disp_attr = A_NOT_DISPLAYED; mark(); if (++menu_length >= maxa) break; mi++; } } if (menu_length > maxa) menu_length = maxa; fl; s_keyboard = 0; prompt_line = firstl + menu_length; if (!long_menu || menu_length < maxa) prompt_line++; numa = nexta - firsta - 1; if (numa < 0) prompt_line++; /* Changed by Stefan Schwarz (stefans@bauv.unibw-muenchen.de Nov. 17, 1992 */ if (firsta + next_cura >= nexta) next_cura = -1; /* End of changes*/ if (next_cura >= 0) { cura = next_cura; set_root_if_closed(); next_cura = -1; } else { cura = 0; for (article_id = firsta; cura < numa; article_id++, cura++) { if ((articles[article_id]->attr & A_SELECT) == 0) break; /* ??? */ if (!IS_VISIBLE(articles[article_id])) continue; } if (!IS_VISIBLE(articles[article_id])) cura = root_article(article_id) - firsta; } build_prompt: nn_raw(); Prompt: prompt(pr_fmt, pct(0L, (long) (n_articles - 1), (long) firsta, (long) (firsta + numa))); if (delayed_msg[0] != NUL) { msg(delayed_msg); delayed_msg[0] = NUL; } same_prompt: if (cura < 0 || cura > numa) cura = 0; if (!IS_VISIBLE(articles[firsta + cura])) { cura = next_root_article(firsta + cura) - firsta; if (cura > numa) cura = 0; } if (numa >= 0) { cursor_at_id(); } last_k_cmd = cur_k_cmd; get_next_k_cmd: k_cmd = get_k_cmd_1(); alt_key: if (k_cmd & K_MACRO) { m_invoke(k_cmd & ~K_MACRO); goto get_next_k_cmd; } #define STATE(state) {k_cmd = state; goto new_state;} /* mouse clicks on top or bottom prompt bars */ #define MOUSE_MENU \ if (MOUSE) { \ if (mouse_y > firstl + menu_length ) { \ STATE(K_CONTINUE); \ } else if (mouse_y == firstl + menu_length ) { \ STATE(K_UNBOUND); \ } else if (mouse_y < firstl - 1) { \ if (firsta > 0) { \ STATE(K_PREV_PAGE); \ } else { \ STATE(K_PREVIOUS); \ } \ } else if (mouse_y == firstl - 1) { \ STATE(K_UNBOUND); \ } \ } new_state: switch (cur_k_cmd = k_cmd) { case K_UNBOUND: ding(); flush_input(); /* FALLTHROUGH */ case K_INVALID: goto same_prompt; case K_REDRAW: next_cura = cura; goto redraw; case K_LAST_MESSAGE: msg((char *) NULL); goto same_prompt; case K_HELP: if (numa < 0) goto nextmenu; /* give specific help here */ display_help("menu"); goto redraw; case K_SHELL: if (group_file_name) *group_file_name = NUL; if (shell_escape()) goto redraw; goto Prompt; case K_VERSION: prompt(P_VERSION); goto same_prompt; case K_EXTENDED_CMD: temp = consolidated_menu; switch (alt_command()) { case AC_UNCHANGED: goto same_prompt; case AC_QUIT: menu_return(ME_QUIT); break; /* for lint. we can't actually get here */ case AC_PROMPT: goto Prompt; case AC_REORDER: firsta = 0; consolidate = do_consolidation(); goto redraw; case AC_REDRAW: if (temp != consolidated_menu) consolidate = do_consolidation(); goto redraw; case AC_KEYCMD: k_cmd = alt_cmd_key; goto alt_key; case AC_REENTER_GROUP: menu_return(ME_REENTER_GROUP); } /* XXX: bug? fall */ case K_QUIT: menu_return(ME_QUIT); break; /* for lint. we can't actually get here */ case K_M_TOGGLE: if (mouse_state) { xterm_mouse_off(); mouse_state = 0; } else { xterm_mouse_on(); mouse_state = 1; } goto Prompt; case K_CANCEL: savemode = "Cancel"; fname = ""; goto cancel1; case K_SAVE_NO_HEADER: case K_SAVE_SHORT_HEADER: case K_SAVE_FULL_HEADER: case K_SAVE_HEADER_ONLY: case K_PRINT: case K_UNSHAR: case K_PATCH: case K_UUDECODE: if (numa < 0) goto nextmenu; fname = init_save(k_cmd, &savemode); if (fname == NULL) goto Prompt; cancel1: enable_stop = 0; save_selected = 0; doing_unshar = k_cmd == K_UNSHAR || k_cmd == K_PATCH; did_unshar = 0; m_startinput(); if (novice) msg(" * selected articles on this page, + all selected articles"); while (!save_selected && !did_unshar) { prompt("\1%s\1 %.*s Article (* +): ", savemode, Columns - 25, fname); k_cmd = get_k_cmd(); if (k_cmd == K_SELECT_SUBJECT) { save_selected = 1; cura = 0; article_id = firsta; last_save = firsta + numa; } else if (k_cmd == K_AUTO_SELECT) { save_selected = 1; cura = -firsta; article_id = 0; last_save = n_articles - 1; } else if (k_cmd == K_ARTICLE_ID) { cura = article_id; article_id += firsta; last_save = article_id; if (articles[article_id]->flag & A_CLOSED) { int n = save_closed_mode % 10; if (article_id == n_articles - 1) goto save_it; if (articles[article_id + 1]->flag & A_ROOT_ART) goto save_it; if (save_closed_mode >= 10) { prompt("\1%s thread\1 (r)oot (s)elected (u)nread (b)oth (a)ll (%c)", savemode, "rsuba"[n]); switch (get_c()) { case 'r': n = 0; break; case 's': n = 1; break; case 'u': n = 2; break; case 'b': n = 3; break; case 'a': n = 4; break; case SP: case CR: case NL: break; default: ding(); goto Prompt; } } switch (n) { case 0: /* save root only */ break; case 1: /* save all selected */ save_selected = 1; break; case 2: /* save all unread */ save_selected = 2; break; case 3: /* save all selected + unread */ save_selected = 3; break; case 4: /* save all articles */ break; } if (n) last_save = next_root_article(article_id) - 1; save_selected |= 8; /* closed subject */ temp1 = cura; } } else break; save_it: for (; article_id <= last_save; article_id++, cura++) { ah = articles[article_id]; switch (save_selected & 0x3) { case 0: break; case 3: if (ah->attr == 0) break; /* XXX: check fall */ case 1: if ((ah->attr & A_SELECT) == 0) continue; break; case 2: if (ah->attr == 0) break; continue; } if (cur_k_cmd == K_CANCEL) { if (current_group->group_flag & G_FOLDER) { fcancel(ah); } else switch (cancel(ah)) { case -1: did_unshar = 1; continue; case 0: ah->attr = A_CANCEL; break; default: continue; } if (!did_unshar) mark(); continue; } if (doing_unshar) { did_unshar++; } else if (ah->subject != NULL) prompt("Processing '%.50s'...", ah->subject); else if (cura >= 0 && cura <= numa) prompt("Processing %c...", ident[cura]); else prompt("Processing entry %d...", article_id); if (save(ah)) { ah->attr = A_READ; if (doing_unshar) continue; if (cura >= 0 && cura <= numa) mark(); } } if (save_selected & 8) { save_selected = 0; /* select closed */ cura = temp1; /* mark(); */ } } if (save_selected) cura = 0; m_endinput(); enable_stop = 1; if (cur_k_cmd != K_CANCEL) end_save(); if (did_unshar) { tprintf("\r\n"); any_key(0); goto redraw; } goto Prompt; case K_FOLLOW_UP: case K_REPLY: if (numa < 0) goto nextmenu; prompt(k_cmd == K_REPLY ? "\1Reply to author\1 of article: " : "\1Follow Up\1 to article: "); if (get_k_cmd() == K_ARTICLE_ID) if (answer(articles[firsta + article_id], k_cmd, -1)) goto redraw; goto Prompt; case K_POST: if (post_menu()) goto redraw; goto Prompt; case K_MAIL_OR_FORWARD: if (numa < 0) goto nextmenu; prompt("\1Article to be forwarded\1 (SP if none): "); if ((k_cmd = get_k_cmd()) == K_ARTICLE_ID) { if (answer(articles[firsta + article_id], K_MAIL_OR_FORWARD, 1)) goto redraw; } else if (k_cmd == K_CONTINUE) if (answer((article_header *) NULL, K_MAIL_OR_FORWARD, 0)) goto redraw; goto Prompt; /* case K_CANCEL: if (numa < 0) goto nextmenu; if (current_group->group_flag & G_FOLDER) { prompt("\1Cancel Folder\1 Article: "); if (get_k_cmd() == K_ARTICLE_ID) { cura = article_id; fcancel(articles[firsta+article_id]); mark(); } goto Prompt; } prompt("\1Cancel\1 Article: "); if (get_k_cmd() == K_ARTICLE_ID) if (cancel(articles[firsta+article_id]) & 1) goto redraw; goto Prompt; */ case K_UNSUBSCRIBE: if (unsubscribe(current_group)) { if (current_group->group_flag & G_UNSUBSCRIBED) menu_return(ME_NEXT); home(); CALL(print_header) (); } goto Prompt; case K_GROUP_OVERVIEW: group_overview(-1); goto redraw; case K_KILL_HANDLING: switch (kill_menu((article_header *) NULL)) { case 0: /* select */ do_auto_select((regexp *) NULL, 2); break; case 1: /* kill */ if (!do_auto_kill()) break; goto junk_killed_articles; default: break; } goto Prompt; case K_CONTINUE: /* goto next menu page or show the articles */ repl_attr(firsta, nexta, 0, A_SEEN, 0); /* FALLTHROUGH */ case K_CONTINUE_NO_MARK: /* but don't mark unselected articles */ if (nexta < n_articles) goto nextmenu; break; case K_READ_GROUP_THEN_SAME: if ((temp = mark_read_return)) goto do_marked_by; break; case K_NEXT_GROUP_NO_UPDATE: if ((temp = mark_next_group)) goto do_marked_by; menu_return(ME_NEXT); break; /* for lint. we can't actually get here */ case K_READ_GROUP_UPDATE: if ((temp = mark_read_skip) == 0) break; do_marked_by: temp1 = 0; temp2 = n_articles; switch (temp) { case 1: temp1 = firsta; /* FALLTHROUGH */ case 3: temp2 = nexta; break; case 2: temp2 = firsta - 1; break; case 4: break; } repl_attr(temp1, temp2, 0, A_SEEN, 0); if (k_cmd != K_NEXT_GROUP_NO_UPDATE) break; menu_return(ME_NEXT); break; /* for lint. we can't actually get here */ case K_PREVIOUS: menu_return(ME_PREV); break; /* for lint. we can't actually get here */ case K_ADVANCE_GROUP: case K_BACK_GROUP: if (merged_menu) { msg("No possible on merged menu"); goto same_prompt; } /* FALLTHROUGH */ case K_GOTO_GROUP: temp1 = n_articles; switch (goto_group(k_cmd, (article_header *) NULL, (flag_type) 0)) { case ME_REDRAW: firsta = 0; if (temp1 != n_articles && consolidate) consolidate = do_consolidation(); goto redraw; case ME_NO_ARTICLES: msg("No selections made."); /* FALLTHROUGH */ case ME_NO_REDRAW: goto Prompt; case ME_QUIT: menu_return(ME_QUIT); break; /* for lint. we can't actually get here */ case ME_PREV: goto redraw; case ME_NEXT: s_keyboard = 1; goto empty_menu_hack; } /* XXX: Fall ? */ case K_OPEN_SUBJECT: if (numa < 0) goto nextmenu; prompt("\1Open subject\1"); k_cmd = get_k_cmd(); if (k_cmd != K_ARTICLE_ID) { if (k_cmd != cur_k_cmd) goto Prompt; article_id = cura; } open_subject: ah = articles[firsta + article_id]; if (!(ah->flag & A_CLOSED) || (ah->flag & (A_ROOT_ART | A_NEXT_SAME)) == A_ROOT_ART) goto Prompt; cura = article_id = root_article(firsta + article_id) - firsta; while (cura + firsta < n_articles) { ah = articles[firsta + cura]; if (cura > article_id && ah->flag & A_ROOT_ART) break; ah->flag &= ~A_CLOSED; cura++; } cura = article_id; goto partial_redraw; case K_CLOSE_SUBJECT: if (numa < 0) goto nextmenu; prompt("\1Close subject\1"); k_cmd = get_k_cmd(); if (k_cmd != K_ARTICLE_ID) { if (k_cmd != cur_k_cmd) goto Prompt; article_id = cura; } ah = articles[firsta + article_id]; if ((ah->flag & A_CLOSED) || (ah->flag & (A_ROOT_ART | A_NEXT_SAME)) == A_ROOT_ART) { cura = next_root_article(firsta + cura) - firsta; goto Prompt; } cura = article_id = root_article(firsta + article_id) - firsta; while (cura + firsta < n_articles) { ah = articles[firsta + cura]; if (cura > article_id && ah->flag & A_ROOT_ART) break; ah->flag |= A_CLOSED; cura++; } cura = article_id; next_cura = next_root_article(firsta + cura) - firsta; if (next_cura < 0 || next_cura > numa) next_cura = 0; if (cura >= 0) goto partial_redraw_nc; articles[cura + firsta]->menu_line = articles[firsta]->menu_line; firsta += cura; goto redraw; case K_LEAVE_NEXT: case K_JUNK_ARTICLES: junk_prompt = cur_k_cmd == K_JUNK_ARTICLES ? 1 : 5; for (;;) { switch (junk_prompt) { case 1: if (novice) msg("Use J repeatedly to select other functions"); prompt("\1Mark read\1 S)een U)nmarked A)ll *+)selected a-z . [LN]"); junk_attr = A_READ; break; case 2: prompt("\1Unmark\1 S)een R)ead a-z [*+LAN.J] "); junk_attr = 0; break; case 3: prompt("\1Select\1 L)eft-over, N(leave-next) [USRa-z.J]"); junk_attr = A_SELECT; break; case 4: prompt("\1Kill\1 R)ead S)een [LANU*+a-z.J]"); junk_attr = A_KILL; break; case 5: prompt("\1Leave\1 a-z .,/ * + U)nmarked [LANRSJ]"); junk_attr = A_LEAVE_NEXT; break; default: junk_prompt = 1; continue; } junk_another: if (cura < 0 || cura > numa) cura = 0; cursor_at_id(); switch (get_k_cmd()) { case K_JUNK_ARTICLES: junk_prompt++; /* can be 0 */ continue; case K_ARTICLE_ID: cura = article_id; if (junk_attr == A_KILL) junk_attr = A_READ; if (auto_select_closed > 0 && articles[firsta + cura]->flag & A_CLOSED) repl_attr_subject(A_KILL, junk_attr, 1); else { articles[firsta + cura]->attr = junk_attr; mark(); cura++; } goto junk_another; case K_NEXT_LINE: cura++; goto junk_another; case K_PREV_LINE: --cura; goto junk_another; case K_SELECT_SUBJECT: if (junk_attr == A_KILL) junk_attr = A_READ; repl_attr(firsta, nexta, A_AUTO_SELECT, A_SELECT, 0); repl_attr(firsta, nexta, A_SELECT, junk_attr, 1); goto Prompt; case K_AUTO_SELECT: repl_attr_all(A_AUTO_SELECT, A_SELECT, 0); orig_attr = A_SELECT; break; default: switch (cur_key) { case 'S': orig_attr = A_SEEN; break; case 'U': orig_attr = 0; break; case 'L': if (junk_attr == A_KILL) junk_attr = A_READ; orig_attr = A_LEAVE; break; case 'A': orig_attr = A_KILL; break; case 'N': orig_attr = A_LEAVE_NEXT; break; case 'R': /* kill read articles */ orig_attr = A_READ; break; default: goto Prompt; } break; } break; } if (nexta - firsta < n_articles) { prompt("On all menu pages? "); switch (yes(1)) { case -1: goto Prompt; case 0: if (!repl_attr(firsta, nexta, orig_attr, junk_attr, 1)) goto Prompt; break; case 1: if (!repl_attr_all(orig_attr, junk_attr, 1)) goto Prompt; break; } } else if (!repl_attr(firsta, nexta, orig_attr, junk_attr, 1)) goto Prompt; if (junk_attr != A_KILL) goto Prompt; junk_killed_articles: elim_list[0] = firsta; elim_list[1] = firsta + cura; elim_list[2] = nexta; if (elim_articles(elim_list, 3)) { firsta = elim_list[0]; goto redraw; } firsta = elim_list[0]; cura = elim_list[1] - firsta; nexta = elim_list[2]; goto Prompt; case K_ARTICLE_ID: if (numa < 0) goto nextmenu; MOUSE_MENU; if (!is_k_select && auto_preview_mode) goto auto_preview; cura = article_id; if (!auto_select_closed || !(articles[firsta + cura]->flag & A_CLOSED)) { toggle(); if (!is_k_select && auto_select_subject) goto select_subject; mark(); cura++; goto same_prompt; } if (auto_select_closed < 0) { article_id = cura; goto open_subject; } mi = menu_info + articles[firsta + cura]->menu_line; if (mi->mi_unread == 0) repl_attr_subject(A_KILL, A_SELECT, 1); else if (mi->mi_selected == mi->mi_unread) repl_attr_subject(auto_select_closed == 2 ? A_KILL : A_SELECT, 0, 1); else repl_attr_subject(auto_select_closed == 2 ? A_KILL : 0, A_SELECT, 1); goto same_prompt; case K_SELECT_INVERT: if (numa < 0) goto nextmenu; temp = cura; no_raw(); /* for x-on/x-off */ for (cura = 0; cura <= numa; cura++) { toggle(); } for (cura = 0; cura <= numa; cura++) { if (IS_VISIBLE(articles[firsta + cura])) mark(); } fl; REDRAW_CHECK; nn_raw(); cura = temp; goto same_prompt; case K_UNSELECT_ALL: if (last_k_cmd == K_UNSELECT_ALL) repl_attr_all(A_SELECT, 0, 1); else repl_attr_all(A_AUTO_SELECT, 0, 1); fl; cura = 0; goto same_prompt; case K_NEXT_LINE: if (numa < 0) goto nextmenu; cura++; goto same_prompt; case K_PREV_LINE: if (numa < 0) goto nextmenu; if (--cura < 0) cura = numa; set_root_if_closed(); goto same_prompt; case K_SELECT_SUBJECT: if (numa < 0) goto nextmenu; MOUSE_MENU; if (MOUSE) cura = article_id; if (last_k_cmd != K_ARTICLE_ID) toggle(); select_subject: repl_attr_subject(A_KILL, last_attr, 1); goto same_prompt; case K_SELECT_RANGE: if (numa < 0) goto nextmenu; if (last_k_cmd == K_ARTICLE_ID) { cura--; if (cura < 0) cura = numa; } else last_attr = (articles[firsta + cura]->attr & A_SELECT) ? 0 : A_SELECT; range_again: if (MOUSE) { if ((mouse_y < firstl) || (mouse_y > firstl + menu_length - 1)) goto same_prompt; /* stop selection when down click changes groups */ if (last_k_cmd == K_INVALID) goto same_prompt; } else { prompt("\1%select range\1 %c-", last_attr ? "S" : "Des", ident[cura]); k_cmd = get_k_cmd(); if (k_cmd == K_SELECT_RANGE) { last_attr = last_attr ? 0 : A_SELECT; goto range_again; } if (k_cmd != K_ARTICLE_ID) goto Prompt; } if (article_id > cura) { article_number tmp = cura; cura = article_id; article_id = tmp; } repl_attr(firsta + article_id, firsta + cura + 1, A_KILL, last_attr, 1); goto Prompt; case K_AUTO_SELECT: do_auto_select((regexp *) NULL, 1); goto same_prompt; case K_GOTO_MATCH: prompt("\1Select regexp\1 "); if ((fname = get_s(NONE, NONE, NONE, NULL_FCT)) == NULL) goto Prompt; if (*fname != NUL) { if (regular_expr) freeobj(regular_expr); if (case_fold_search) fold_string(fname); regular_expr = regcomp(fname); } if (regular_expr == NULL) msg("No previous expression"); else do_auto_select(regular_expr, 2); goto Prompt; case K_NEXT_PAGE: if (nexta < n_articles) goto nextmenu; if (firsta == 0) goto same_prompt; nexta = 0; goto nextmenu; case K_PREV_PAGE: if (firsta == 0 && nexta == n_articles) goto same_prompt; prevmenu: nexta = (firsta > 0 ? firsta : n_articles); for (menu_length = maxa; menu_length > 0 && --nexta >= 0;) { if (!IS_VISIBLE(articles[nexta])) continue; if (--menu_length > 0) { switch (menu_spacing) { case 0: break; case 1: if ((articles[nexta]->flag & A_ROOT_ART) == 0) break; /* XXX: fall? */ case 2: --menu_length; break; } } } if (nexta < 0) nexta = 0; goto nextmenu; case K_FIRST_PAGE: if (firsta == 0) goto same_prompt; nexta = 0; goto nextmenu; case K_LAST_PAGE: if (nexta == n_articles) goto same_prompt; firsta = 0; goto prevmenu; case K_PREVIEW: if (numa < 0) goto nextmenu; preview_other: MOUSE_MENU; if (!MOUSE) { prompt("\1Preview article\1"); k_cmd = get_k_cmd(); if (k_cmd != K_ARTICLE_ID) { if (k_cmd != K_PREVIEW) goto Prompt; article_id = cura; } } auto_preview: temp = prompt_line; preview_next: cura = article_id; ah = articles[firsta + cura]; no_raw(); orig_attr = ah->attr; ah->attr = 0; menu_cmd = more(ah, MM_PREVIEW, prompt_line); if (menu_cmd == MC_MENU) { if (ah->attr == 0) ah->attr = orig_attr; if (prompt_line < 0) { next_cura = cura; goto redraw; } mark(); prompt_line = temp; next_cura = -1; goto build_prompt; } if (ah->attr == 0) ah->attr = preview_mark_read ? A_READ : orig_attr; if (prompt_line >= 0) mark(); next_cura = ++cura; switch (menu_cmd) { case MC_DO_KILL: if (!do_auto_kill()) break; elim_list[0] = firsta; elim_list[1] = firsta + cura; elim_articles(elim_list, 2); firsta = elim_list[0]; next_cura = elim_list[1] - firsta; goto redraw; case MC_DO_SELECT: if (prompt_line >= 0) { /* not redrawn */ do_auto_select((regexp *) NULL, 2); break; } numa = -1; do_auto_select((regexp *) NULL, 2); /* FALLTHROUGH */ case MC_QUIT: menu_return(ME_QUIT); break; /* for lint. we can't actually get here */ case MC_REENTER_GROUP: menu_return(ME_REENTER_GROUP); break; /* for lint, we can't actually get here */ case MC_NEXT: case MC_PREVIEW_NEXT: if (prompt_line < 0) { /* redrawn screen ! */ if (quit_preview(menu_cmd)) goto redraw; prompt_line = Lines; } else { if (quit_preview(menu_cmd)) { next_cura = -1; break; } prompt_line = temp; } article_id = cura; goto preview_next; case MC_PREVIEW_OTHER: prompt_line = temp; nn_raw(); goto preview_other; default: if (prompt_line < 0) goto redraw; break; } prompt_line = temp; goto build_prompt; case K_LAYOUT: if (++fmt_linenum > 4) fmt_linenum = 0; goto redraw; default: msg("Command %d not supported", k_cmd); goto same_prompt; } no_raw(); do_auto_read: switch (show_articles()) { case MC_MENU: goto redraw; case MC_READGROUP: if (k_cmd == K_READ_GROUP_THEN_SAME) { if (read_ret_next_page && nexta < n_articles) firsta = nexta; goto redraw; } /* XXX: fall? */ case MC_NEXTGROUP: menu_cmd = ME_NEXT; break; case MC_REENTER_GROUP: menu_cmd = ME_REENTER_GROUP; break; case MC_QUIT: menu_cmd = ME_QUIT; break; default: sys_error("show_articles returned improper value"); } menu_exit: cur_bypass = o_bypass; n_selected = o_selected; firsta = o_firsta; in_menu_mode = o_mode; menu_level--; no_raw(); return menu_cmd; } /* * return article header for article on menu */ article_header * get_menu_article(void) { register article_header *ah; tprintf(" from article: "); fl; if (get_k_cmd() == K_ARTICLE_ID) { ah = articles[firsta + article_id]; if (ah->a_group) init_group(ah->a_group); return ah; } return NULL; } /* * read command from command line */ int alt_command(void) { int ok_val, macro_cmd; char *cmd, brkchars[10]; if (get_from_macro) ok_val = AC_UNCHANGED; else { prompt(":"); ok_val = AC_PROMPT; } again: sprintf(brkchars, "?%c ", erase_key); cmd = get_s(NONE, NONE, brkchars, alt_completion); if (cmd == NULL || *cmd == NUL || *cmd == SP || (key_type) * cmd == erase_key) return ok_val; macro_cmd = get_from_macro; if (*cmd == '?') { display_file("help.extended", CLEAR_DISPLAY); ok_val = AC_REDRAW; goto new_prompt; } ok_val = parse_command(cmd, ok_val, (FILE *) NULL); if (ok_val != AC_REDRAW || !delay_redraw) return ok_val; new_prompt: if (macro_cmd) return ok_val; prompt_line = -1; tprintf("\n\r:"); fl; goto again; } nn-6.7.4/proto.c0000644000175000017500000001254110222574201013056 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Master/slave communication and locking. */ #include #include #include #include #include #include #include "config.h" #include "global.h" #include "hostname.h" #include "proto.h" /* proto.c */ static void write_lock(char *lock, char *operation); static int read_lock(char *lock); #ifndef ACCOUNTING #ifdef AUTHORIZE #define ACCOUNTING #endif #endif extern char *master_directory, *db_directory; #define HOSTBUF 80 char proto_host[HOSTBUF]; /* host having the lock */ /* * When setting a lock, we must check a little later that * we really got the lock set, i.e. that another process * didn't set it at the same time! */ #define LOCK_SAFETY 5 /* seconds */ /* * proto_lock(program, mode) * * Returns: * -1 Not running. * 0 Running, no permission (PL_WAKEUP_SOFT) * 0 Lock set (PL_SET) * 1 Lock not set (PL_SET) (another is running) * 1 Locked and running (PL_WAKEUP) * pid Locked and running (PL_CHECK) */ static void write_lock(char *lock, char *operation) { FILE *m_pid; char host[HOSTBUF]; m_pid = open_file(lock, OPEN_CREATE); if (m_pid == NULL) sys_error("Cannot %s lock file: %s", operation, lock); nn_gethostname(host, HOSTBUF); fprintf(m_pid, "%d\n%s\n", process_id, host); fclose(m_pid); } static int read_lock(char *lock) { FILE *m_pid; char host[HOSTBUF]; char pid[10]; pid[0] = NUL; proto_host[0] = NUL; m_pid = open_file(lock, OPEN_READ); if (m_pid == NULL) return -2; /* no lock */ fgets(pid, 10, m_pid); fgets(proto_host, HOSTBUF, m_pid); fclose(m_pid); if (pid[0] == NUL) return 0; /* corrupted lock */ if (proto_host[0] != NUL) { substchr(proto_host, NL, NUL); nn_gethostname(host, HOSTBUF); if (strncmp(proto_host, host, HOSTBUF) != 0) return -1; /* locked by another host */ proto_host[0] = NUL; } return atoi(pid); } int proto_lock(int prog, int command) { int try, pid; char *lock = NULL; switch (prog) { case I_AM_MASTER: case I_AM_EXPIRE: lock = relative(master_directory, "MPID"); break; case I_AM_SPEW: lock = relative(master_directory, "WPID"); break; #ifdef ACCOUNTING case I_AM_ACCT: lock = relative(db_directory, "LCK..acct"); break; #endif case I_AM_NN: lock = relative(nn_directory, "LOCK"); break; default: sys_error("Invalid LOCK prog"); } if (command == PL_TRANSFER) { write_lock(lock, "transfer"); return 1; } if (command == PL_CLEAR) goto rm_lock; try = 1; again: switch (pid = read_lock(lock)) { case -2: goto no_lock; case -1: /* wrong host */ return 1; case 0: case 1: case 2: /* corrupted lock file */ if (who_am_i == I_AM_NN) goto rm_lock; if (--try < 0) goto rm_lock; sleep(LOCK_SAFETY); /* maybe it is being written */ goto again; default: break; } if (kill(pid, command == PL_TERMINATE ? SIGHUP : SIGALRM) == 0) { switch (command) { case PL_SET_QUICK: sleep(1); goto again; case PL_SET_WAIT: case PL_CLEAR_WAIT: sleep(30); goto again; case PL_CHECK: return pid; default: return 1; } } if (command == PL_CHECK) return (errno == EPERM) ? pid : -1; if (command == PL_WAKEUP_SOFT) return (errno == EPERM) ? 0 : -1; /* lock file contains a non-existing process, or a process with */ /* wrong owner, ie. neither master or expire, so remove it */ rm_lock: unlink(lock); no_lock: if (command != PL_SET && command != PL_SET_QUICK && command != PL_SET_WAIT) return -1; write_lock(lock, "create"); /* a user will not start nn twice at the exact same time! */ if (who_am_i == I_AM_NN || command == PL_SET_QUICK) return 0; sleep(LOCK_SAFETY); if (read_lock(lock) != process_id) return 1; /* somebody stole the lock file */ return 0; /* lock is set */ } FILE * open_gate_file(int mode) { char *gate, *err; FILE *gf; gate = relative(master_directory, "GATE"); gf = open_file(gate, mode); err = NULL; switch (mode) { case OPEN_READ: if (gf != NULL) { if (unlink(gate) == 0) break; err = "unlink"; break; } if (errno != ENOENT) err = "read"; break; default: if (gf != NULL) chmod(gate, 0644); /* override restrictive umask */ /* caller must complain if cannot open! */ break; } if (err != NULL) { sys_warning("Cannot %s %s (err=%d). Check modes!!", err, gate, errno); if (gf != NULL) fclose(gf); return NULL; } return gf; } void send_master(char command, group_header * gh, int opt, long arg) { FILE *gate; gate = open_gate_file(OPEN_APPEND); if (gate == NULL) { printf("Cannot send to master (check GATE file)\n"); return; } fprintf(gate, "%c;%ld;%c;%ld;%s %s;\n", command, gh == NULL ? -1L : gh->group_num, opt, arg, user_name(), date_time((time_t) 0)); fclose(gate); log_entry('A', "SEND %c %s %c %ld", command, gh == NULL ? "(all)" : gh->group_name, opt, arg); if (who_am_i == I_AM_ADMIN) proto_lock(I_AM_MASTER, PL_WAKEUP_SOFT); } nn-6.7.4/keymap.c0000644000175000017500000005676410231320326013215 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Keyboard (re)mapping */ #include #include #include "config.h" #include "global.h" #include "init.h" #include "keymap.h" #include "nn_term.h" extern int data_bits; /* * KEY MAP LAYOUT: * 128 normal ASCII chars * 0200 -- unused * 4 arrow keys #up/down/left/right * 10 multi keys #0-#9 * 17 spare keys * 95 national 8-bit characters (8859/x) * 0377 is unused (since we must be able to test c */ K_UNBOUND, /* ? */ K_HELP, /* @ */ K_UNBOUND, /* A */ K_UNBOUND, /* B */ K_UNBOUND, /* C */ K_CANCEL, /* D */ K_ROT13, /* E */ K_SAVE_HEADER_ONLY, /* F */ K_FOLLOW_UP, /* G */ K_GOTO_GROUP, /* H */ K_FULL_DIGEST, /* I */ K_UNBOUND, /* J */ K_UNBOUND, /* K */ K_KILL_HANDLING, /* L */ K_LEAVE_NEXT, /* M */ K_MAIL_OR_FORWARD, /* N */ K_NEXT_GROUP_NO_UPDATE, /* O */ K_SAVE_SHORT_HEADER, /* P */ K_PRINT, /* Q */ K_QUIT, /* R */ K_REPLY, /* S */ K_SAVE_FULL_HEADER, /* T */ K_UNBOUND, /* U */ K_UNSUBSCRIBE, /* V */ K_VERSION, /* W */ K_SAVE_NO_HEADER, /* X */ K_READ_GROUP_UPDATE, /* Y */ K_GROUP_OVERVIEW, /* Z */ K_BACK_TO_MENU, /* [ */ K_UNBOUND, /* \ */ K_UNBOUND, /* ] */ K_UNBOUND, /* ^ */ K_FIRST_PAGE, /* _ */ K_UNBOUND, /* ` */ K_UNBOUND, /* a */ K_FORW_ARTICLE, /* b */ K_BACK_ARTICLE, /* c */ K_COMPRESS, /* d */ K_NEXT_HALF_PAGE, /* e */ K_SAVE_HEADER_ONLY, /* f */ K_FOLLOW_UP, /* g */ K_GOTO_LINE, /* h */ K_HEADER_PAGE, /* i */ K_UNBOUND, /* j */ K_UNBOUND, /* k */ K_NEXT_SUBJECT, /* l */ K_LEAVE_ARTICLE, /* m */ K_MAIL_OR_FORWARD, /* n */ K_NEXT_ARTICLE, /* o */ K_SAVE_SHORT_HEADER, /* p */ K_PREVIOUS /* article */ , /* q */ K_NEXT_GROUP_NO_UPDATE, /* r */ K_REPLY, /* s */ K_SAVE_FULL_HEADER, /* t */ K_FIRST_PAGE, /* u */ K_PREV_HALF_PAGE, /* v */ K_UNBOUND, /* w */ K_SAVE_NO_HEADER, /* x */ K_UNBOUND, /* y */ K_UNBOUND, /* z */ K_UNBOUND, /* { */ K_UNBOUND, /* | */ K_UNBOUND, /* } */ K_UNBOUND, /* ~ */ K_UNBOUND, /* DEL */ K_PREV_PAGE, /* 200 */ K_UNBOUND, /* up */ K_PREV_PAGE, /* down */ K_NEXT_PAGE, /* left */ K_UNBOUND, /* right */ K_UNBOUND, /* #0 */ K_UNBOUND, /* #1 */ K_UNBOUND, /* #2 */ K_UNBOUND, /* #3 */ K_UNBOUND, /* #4 */ K_UNBOUND, /* #5 */ K_UNBOUND, /* #6 */ K_UNBOUND, /* #7 */ K_UNBOUND, /* #8 */ K_UNBOUND, /* #9 */ K_UNBOUND, /* mouse d1 */ K_M_CONTINUE, /* mouse d2 */ K_NEXT_SUBJECT, /* mouse d3 */ K_BACK_TO_MENU, /* mouse u1 */ K_INVALID, /* mouse u2 */ K_INVALID, /* mouse u3 */ K_INVALID }; /* * standard keyboard mappings for menu() * * illegal command * redraw ^L, ^R * continue space * continue no mark return, newline * repeat message ^P * help ? * shell escape ! * version V * alternative commands : * quit Q * * save S, O * save, no header W * reply R * follow up F * mail (forward) M * cancel C * unsubscribe U * group overview Y * kill handling K * junk articles J * * read, then next X * read, then same Z * no update, next N * prev group P * goto group G * advance group A * back group B * * article identifier a-z 0-9 * inverse @ * select current, next . * next , (down arrow) * prev / (up arrow) * select subject * * range - * auto select + * * next page > * prev page < * first page ^ * last page $ * * pre-view article % * * layout L */ int menu_key_map[KEY_MAP_SIZE] = { /* NUL ^@ */ K_UNBOUND, /* SOH ^A */ K_UNBOUND, /* STX ^B */ K_UNBOUND, /* ETX ^C */ K_UNBOUND, /* EOT ^D */ K_UNBOUND, /* ENQ ^E */ K_UNBOUND, /* ACK ^F */ K_UNBOUND, /* BEL ^G */ K_INVALID, /* BS ^H */ K_PREV_LINE, /* TAB ^I */ K_UNBOUND, /* NL ^J */ K_CONTINUE_NO_MARK, /* VT ^K */ K_UNBOUND, /* FF ^L */ K_REDRAW, /* CR ^M */ K_CONTINUE_NO_MARK, /* SO ^N */ K_UNBOUND, /* SI ^O */ K_UNBOUND, /* DLE ^P */ K_LAST_MESSAGE, /* DC1 ^Q */ K_UNBOUND, /* DC2 ^R */ K_REDRAW, /* DC3 ^S */ K_UNBOUND, /* DC4 ^T */ K_UNBOUND, /* NAK ^U */ K_UNBOUND, /* SYN ^V */ K_NEXT_PAGE, /* ETB ^W */ K_UNBOUND, /* CAN ^X */ K_UNBOUND, /* EM ^Y */ K_UNBOUND, /* SUB ^Z */ K_UNBOUND, /* ESC ^[ */ K_UNBOUND, /* FS ^\ */ K_UNBOUND, /* GS ^] */ K_UNBOUND, /* RS ^^ */ K_UNBOUND, /* US ^_ */ K_UNBOUND, /* SP */ K_CONTINUE, /* ! */ K_SHELL, /* " */ K_LAYOUT, /* # */ K_UNBOUND, /* $ */ K_LAST_PAGE, /* % */ K_PREVIEW, /* & */ K_UNBOUND, /* ' */ K_UNBOUND, /* ( */ K_OPEN_SUBJECT, /* ) */ K_CLOSE_SUBJECT, /* * */ K_SELECT_SUBJECT, /* + */ K_AUTO_SELECT, /* , */ K_NEXT_LINE, /* - */ K_SELECT_RANGE, /* . */ K_SELECT, /* / */ K_PREV_LINE, /* 0 */ K_ARTICLE_ID + 26, /* 1 */ K_ARTICLE_ID + 27, /* 2 */ K_ARTICLE_ID + 28, /* 3 */ K_ARTICLE_ID + 29, /* 4 */ K_ARTICLE_ID + 30, /* 5 */ K_ARTICLE_ID + 31, /* 6 */ K_ARTICLE_ID + 32, /* 7 */ K_ARTICLE_ID + 33, /* 8 */ K_ARTICLE_ID + 34, /* 9 */ K_ARTICLE_ID + 35, /* : */ K_EXTENDED_CMD, /* ; */ K_UNBOUND, /* < */ K_PREV_PAGE, /* = */ K_GOTO_MATCH, /* > */ K_NEXT_PAGE, /* ? */ K_HELP, /* @ */ K_SELECT_INVERT, /* A */ K_ADVANCE_GROUP, /* B */ K_BACK_GROUP, /* C */ K_CANCEL, /* D */ K_UNBOUND, /* E */ K_SAVE_HEADER_ONLY, /* F */ K_FOLLOW_UP, /* G */ K_GOTO_GROUP, /* H */ K_UNBOUND, /* I */ K_UNBOUND, /* J */ K_JUNK_ARTICLES, /* K */ K_KILL_HANDLING, /* L */ K_LEAVE_NEXT, /* M */ K_MAIL_OR_FORWARD, /* N */ K_NEXT_GROUP_NO_UPDATE, /* O */ K_SAVE_SHORT_HEADER, /* P */ K_PREVIOUS /* group */ , /* Q */ K_QUIT, /* R */ K_REPLY, /* S */ K_SAVE_FULL_HEADER, /* T */ K_UNBOUND, /* U */ K_UNSUBSCRIBE, /* V */ K_VERSION, /* W */ K_SAVE_NO_HEADER, /* X */ K_READ_GROUP_UPDATE, /* Y */ K_GROUP_OVERVIEW, /* Z */ K_READ_GROUP_THEN_SAME, /* [ */ K_UNBOUND, /* \ */ K_UNBOUND, /* ] */ K_UNBOUND, /* ^ */ K_FIRST_PAGE, /* _ */ K_UNBOUND, /* ` */ K_UNBOUND, /* a */ K_ARTICLE_ID + 0, /* b */ K_ARTICLE_ID + 1, /* c */ K_ARTICLE_ID + 2, /* d */ K_ARTICLE_ID + 3, /* e */ K_ARTICLE_ID + 4, /* f */ K_ARTICLE_ID + 5, /* g */ K_ARTICLE_ID + 6, /* h */ K_ARTICLE_ID + 7, /* i */ K_ARTICLE_ID + 8, /* j */ K_ARTICLE_ID + 9, /* k */ K_ARTICLE_ID + 10, /* l */ K_ARTICLE_ID + 11, /* m */ K_ARTICLE_ID + 12, /* n */ K_ARTICLE_ID + 13, /* o */ K_ARTICLE_ID + 14, /* p */ K_ARTICLE_ID + 15, /* q */ K_ARTICLE_ID + 16, /* r */ K_ARTICLE_ID + 17, /* s */ K_ARTICLE_ID + 18, /* t */ K_ARTICLE_ID + 19, /* u */ K_ARTICLE_ID + 20, /* v */ K_ARTICLE_ID + 21, /* w */ K_ARTICLE_ID + 22, /* x */ K_ARTICLE_ID + 23, /* y */ K_ARTICLE_ID + 24, /* z */ K_ARTICLE_ID + 25, /* { */ K_UNBOUND, /* | */ K_UNBOUND, /* } */ K_UNBOUND, /* ~ */ K_UNSELECT_ALL, /* DEL */ K_PREV_LINE, /* 200 */ K_UNBOUND, /* up */ K_PREV_LINE, /* down */ K_NEXT_LINE, /* left */ K_UNBOUND, /* right */ K_UNBOUND, /* #0 */ K_UNBOUND, /* #1 */ K_UNBOUND, /* #2 */ K_UNBOUND, /* #3 */ K_UNBOUND, /* #4 */ K_UNBOUND, /* #5 */ K_UNBOUND, /* #6 */ K_UNBOUND, /* #7 */ K_UNBOUND, /* #8 */ K_UNBOUND, /* #9 */ K_UNBOUND, /* mouse d1 */ K_M_SELECT, /* mouse d2 */ K_M_PREVIEW, /* mouse d3 */ K_NEXT_GROUP_NO_UPDATE, /* mouse u1 */ K_M_SELECT_RANGE, /* mouse u2 */ K_INVALID, /* mouse u3 */ K_INVALID }; int orig_menu_map[KEY_MAP_SIZE]; /* initially empty */ static struct command_name_map { char *cmd_name; int cmd_code; int cmd_restriction; } command_name_map[] = { "advance-article", K_FORW_ARTICLE, K_ONLY_MORE, "advance-group", K_ADVANCE_GROUP, 0, "article", K_ARTICLE_ID, K_ONLY_MENU, "as", K_EQUAL_KEY, 0, "back-article", K_BACK_ARTICLE, K_ONLY_MORE, "back-group", K_BACK_GROUP, 0, "cancel", K_CANCEL, 0, "close-subject", K_CLOSE_SUBJECT, K_ONLY_MENU, "command", K_EXTENDED_CMD, 0, "compress", K_COMPRESS, K_ONLY_MORE, "continue", K_CONTINUE, 0, "continue-no-mark", K_CONTINUE_NO_MARK, K_ONLY_MENU, "decode", K_UUDECODE, 0, "find", K_GOTO_MATCH, 0, "find-next", K_NEXT_MATCH, K_ONLY_MORE, "follow", K_FOLLOW_UP, 0, "full-digest", K_FULL_DIGEST, K_ONLY_MORE, "goto-group", K_GOTO_GROUP, 0, "goto-menu", K_BACK_TO_MENU, K_ONLY_MORE, "help", K_HELP, 0, "junk-articles", K_JUNK_ARTICLES, K_ONLY_MENU, "kill-select", K_KILL_HANDLING, 0, "layout", K_LAYOUT, K_ONLY_MENU, "leave-article", K_LEAVE_ARTICLE, K_ONLY_MORE, "leave-next", K_LEAVE_NEXT, K_ONLY_MORE, "line+1", K_NEXT_LINE, 0, "line-1", K_PREV_LINE, K_ONLY_MENU, "line=@", K_GOTO_LINE, K_ONLY_MORE, "macro", K_MACRO, 0, "mail", K_MAIL_OR_FORWARD, 0, "message", K_LAST_MESSAGE, 0, "mouse-continue", K_M_CONTINUE, K_ONLY_MORE, "mouse-preview", K_M_PREVIEW, K_ONLY_MENU, "mouse-select", K_M_SELECT, K_ONLY_MENU, "mouse-select-range", K_M_SELECT_RANGE, K_ONLY_MENU, "mouse-select-subject", K_M_SELECT_SUBJECT, K_ONLY_MENU, "mouse-toggle", K_M_TOGGLE, 0, "next-article", K_NEXT_ARTICLE, K_ONLY_MORE, "next-group", K_NEXT_GROUP_NO_UPDATE, 0, "next-subject", K_NEXT_SUBJECT, K_ONLY_MORE, "nil", K_UNBOUND, 0, "open-subject", K_OPEN_SUBJECT, K_ONLY_MENU, "overview", K_GROUP_OVERVIEW, 0, "page+1", K_NEXT_PAGE, 0, "page+1/2", K_NEXT_HALF_PAGE, K_ONLY_MORE, "page-1", K_PREV_PAGE, 0, "page-1/2", K_PREV_HALF_PAGE, K_ONLY_MORE, "page=$", K_LAST_PAGE, 0, "page=0", K_HEADER_PAGE, 0, "page=1", K_FIRST_PAGE, 0, "page=@", K_GOTO_PAGE, K_ONLY_MORE, "patch", K_PATCH, 0, "post", K_POST, 0, "prefix", K_PREFIX_KEY, 0, "preview", K_PREVIEW, 0, "previous", K_PREVIOUS, 0, "print", K_PRINT, 0, "quit", K_QUIT, 0, "read-return", K_READ_GROUP_THEN_SAME, K_ONLY_MENU, "read-skip", K_READ_GROUP_UPDATE, 0, "redraw", K_REDRAW, 0, "reply", K_REPLY, 0, "rot13", K_ROT13, K_ONLY_MORE, "save-body", K_SAVE_NO_HEADER, 0, "save-full", K_SAVE_FULL_HEADER, 0, "save-short", K_SAVE_SHORT_HEADER, 0, "save-hdr-only", K_SAVE_HEADER_ONLY, 0, "select", K_SELECT, K_ONLY_MENU, "select-auto", K_AUTO_SELECT, K_ONLY_MENU, "select-invert", K_SELECT_INVERT, K_ONLY_MENU, "select-range", K_SELECT_RANGE, K_ONLY_MENU, "select-subject", K_SELECT_SUBJECT, 0, "shell", K_SHELL, 0, "skip-lines", K_SKIP_LINES, 0, "unselect-all", K_UNSELECT_ALL, K_ONLY_MENU, "unshar", K_UNSHAR, 0, "unsub", K_UNSUBSCRIBE, 0, "version", K_VERSION, 0, (char *) NULL, 0, 0 }; static int name_map_size; #define max_cmd_name_length 16 /* recalculate if table is changed */ key_type global_key_map[KEY_MAP_SIZE]; void init_key_map(void) { register int c; register struct command_name_map *cnmp; for (c = 0; c < KEY_MAP_SIZE; c++) global_key_map[c] = c; for (c = NORMAL_KEYS + ARROW_KEYS + MULTI_KEYS; c < KEY_MAP_SIZE; c++) { menu_key_map[c] = K_UNBOUND; more_key_map[c] = K_UNBOUND; } for (cnmp = command_name_map; cnmp->cmd_name; cnmp++); name_map_size = cnmp - command_name_map; } int lookup_command(char *command, int restriction) { register struct command_name_map *cnmp; register int i, j, k, t; i = 0; j = name_map_size - 1; while (i <= j) { k = (i + j) / 2; cnmp = &command_name_map[k]; if ((t = strcmp(command, cnmp->cmd_name)) > 0) i = k + 1; else if (t < 0) j = k - 1; else { switch (restriction) { case K_ONLY_MENU: case K_ONLY_MORE: if (cnmp->cmd_restriction == restriction) break; /* FALLTHRU */ case 0: if (cnmp->cmd_restriction == 0) break; return K_INVALID - 1; default: break; } return cnmp->cmd_code; } } return K_INVALID; } int cmd_completion(char *path, int index) { static char *head, *tail = NULL; static int len; static struct command_name_map *cmd, *help_cmd; if (index < 0) return 0; if (path) { head = path; tail = path + index; while (*head && isspace(*head)) head++; help_cmd = cmd = command_name_map; len = tail - head; return 1; } if (index) { list_completion((char *) NULL); if (help_cmd->cmd_name == NULL) help_cmd = command_name_map; for (; help_cmd->cmd_name; help_cmd++) { index = strncmp(help_cmd->cmd_name, head, len); if (index < 0) continue; if (index > 0) { help_cmd = command_name_map; break; } if (list_completion(help_cmd->cmd_name) == 0) break; } fl; return 1; } for (; cmd->cmd_name; cmd++) { if (len == 0) index = 0; else index = strncmp(cmd->cmd_name, head, len); if (index < 0) continue; if (index > 0) break; if (cmd->cmd_code == K_MACRO || cmd->cmd_code == K_ARTICLE_ID || cmd->cmd_code == K_PREFIX_KEY || cmd->cmd_code == K_EQUAL_KEY) sprintf(tail, "%s ", cmd->cmd_name + len); else strcpy(tail, cmd->cmd_name + len); cmd++; return 1; } return 0; } char * command_name(int cmd) { register struct command_name_map *cnmp; cmd &= ~GETC_COMMAND; for (cnmp = command_name_map; cnmp->cmd_name; cnmp++) if (cnmp->cmd_code == cmd) return cnmp->cmd_name; return "unknown"; } /* * convert key name into ascii code * * key names are: * c character c * ^C control-C * 0xNN hex value (0..0x7f) * 0NNN octal value (0..0177) * NNN decimal value (0..127) * up, down, left, rigth arrow keys * #0..#9 function keys (initially undefined) */ key_type parse_key(char *str) { int x; if (str[1] == NUL) return (data_bits < 8) ? (str[0] & 0177) : str[0]; if (str[0] == '^') { if (str[1] == '?') return 0177; else return CONTROL_(str[1]); } if (isdigit(str[0])) { if (str[0] == '0') { if (str[1] == 'x') sscanf(str + 2, "%d", &x); else sscanf(str + 1, "%d", &x); } else { sscanf(str, "%d", &x); } return x; } if (str[0] == '#' && isdigit(str[1])) return K_function(str[1] - '0'); if (str[0] == '#') str++; if (strcmp(str, "up") == 0) return K_up_arrow; if (strcmp(str, "down") == 0) return K_down_arrow; if (strcmp(str, "left") == 0) return K_left_arrow; if (strcmp(str, "right") == 0) return K_right_arrow; if (strcmp(str, "mouse_d1") == 0) /* thp */ return K_m_d1; if (strcmp(str, "mouse_d2") == 0) /* thp */ return K_m_d2; if (strcmp(str, "mouse_d3") == 0) /* thp */ return K_m_d3; if (strcmp(str, "mouse_u1") == 0) /* thp */ return K_m_u1; if (strcmp(str, "mouse_u2") == 0) /* thp */ return K_m_u2; if (strcmp(str, "mouse_u3") == 0) /* thp */ return K_m_u3; init_message("unknown key: %s", str); return 0200; } char * key_name(key_type c) { static char buf[10]; if (c >= NORMAL_KEYS && c <= (key_type) (KEY_MAP_SIZE - NATIONAL_KEYS)) { switch (c) { case K_up_arrow: return "up"; case K_down_arrow: return "down"; case K_left_arrow: return "left"; case K_right_arrow: return "right"; case K_m_d1: return "mouse_d1"; case K_m_d2: return "mouse_d2"; case K_m_d3: return "mouse_d3"; case K_m_u1: return "mouse_u1"; case K_m_u2: return "mouse_u2"; case K_m_u3: return "mouse_u3"; default: buf[0] = '#'; buf[1] = (c - K_function(0)) + (c >= (key_type) K_function(MULTI_KEYS) ? 'A' - K_function(MULTI_KEYS) : '0'); buf[2] = NUL; goto out; } } if (c == SP) return "space"; if (c < SP) { buf[0] = '^'; buf[1] = c + '@'; buf[2] = NUL; goto out; } if (c == 0177) { strcpy(buf, "^?"); goto out; } if (data_bits < 8 && c >= NORMAL_KEYS) { sprintf(buf, "0x%02x", (uint)c); goto out; } buf[0] = c; buf[1] = NUL; out: return buf; } void dump_global_map(void) { register key_type c; clrdisp(); so_printf("\1REMAPPED KEYS\1\n\n"); pg_init(2, 4); for (c = 0; c < KEY_MAP_SIZE; c++) if (c != global_key_map[c]) { if (pg_next() < 0) break; tprintf("%s", key_name(c)); pg_indent(6); tprintf("-> %s", key_name(global_key_map[c])); } pg_end(); } int dump_key_map(char *where) { register struct command_name_map *cnmp; register key_type c; register int code, first_prt; int *map, restriction; if ((code = lookup_keymap(where)) < 0) return -1; map = keymaps[code].km_map; restriction = keymaps[code].km_flag & (K_ONLY_MENU | K_ONLY_MORE); clrdisp(); so_printf("\1KEY BINDINGS (%s)\1\n\n", where); if (restriction == K_ONLY_MENU) { tprintf("\rarticle: "); for (c = 0; c < KEY_MAP_SIZE; c++) if (map[c] & K_ARTICLE_ID) tprintf("%s", key_name(c)); } pg_init(4, 2); for (cnmp = command_name_map; cnmp->cmd_name; cnmp++) { if (cnmp->cmd_restriction && cnmp->cmd_restriction != restriction) continue; if (cnmp->cmd_code == K_UNBOUND) continue; if (cnmp->cmd_code == K_MACRO) continue; code = cnmp->cmd_code; first_prt = 1; for (c = 0; c < KEY_MAP_SIZE; c++) if (map[c] == code) { if (first_prt) { if (pg_next() < 0) goto out; tprintf("%s", cnmp->cmd_name); pg_indent(max_cmd_name_length); first_prt = 0; } tprintf(" %s", key_name(c)); } } for (c = 0; c < KEY_MAP_SIZE; c++) if (map[c] & K_MACRO) { if (pg_next() < 0) goto out; tprintf("macro %d: %s", (map[c] & ~K_MACRO), key_name(c)); if (map == menu_key_map && orig_menu_map[c] != K_UNBOUND) tprintf(" (%s)", command_name(orig_menu_map[c])); } else if (map[c] & K_PREFIX_KEY) { if (pg_next() < 0) goto out; tprintf("prefix %s: %s", keymaps[(map[c] & ~K_PREFIX_KEY)].km_name, key_name(c)); } out: pg_end(); return 0; } #define MAX_KEYMAPS 17 struct key_map_def keymaps[MAX_KEYMAPS + 1] = { "#", NULL, K_MULTI_KEY_MAP, "key", NULL, K_GLOBAL_KEY_MAP, "menu", menu_key_map, K_BIND_ORIG | K_ONLY_MENU, "show", more_key_map, K_ONLY_MORE, "both", menu_key_map, K_BOTH_MAPS | K_BIND_ORIG, "more", more_key_map, K_ONLY_MORE, "read", more_key_map, K_ONLY_MORE, NULL, }; int lookup_keymap(char *name) { register struct key_map_def *m; if (name[0] == '#') return 0; for (m = keymaps; m->km_name; m++) { if (strcmp(name, m->km_name) == 0) return m - keymaps; } return keymaps - m; } int make_keymap(char *name) { register struct key_map_def *m; register int *kp; int ix; ix = lookup_keymap(name); if (ix >= 0) return -1; ix = -ix; if (ix == MAX_KEYMAPS) return -2; m = &keymaps[ix]; m->km_name = copy_str(name); m->km_map = newobj(int, KEY_MAP_SIZE); m->km_flag = K_ONLY_MORE | K_ONLY_MENU; for (kp = m->km_map; kp < &(m->km_map)[KEY_MAP_SIZE];) *kp++ = K_UNBOUND; return ix; } int keymap_completion(char *buf, int ix) { static char *head, *tail = NULL; static int len; static struct key_map_def *map, *help_map; if (ix < 0) return 0; if (buf) { head = buf; tail = buf + ix; while (*head && isspace(*head)) head++; help_map = map = keymaps; len = tail - head; return 1; } if (ix) { list_completion((char *) NULL); if (help_map->km_name == NULL) help_map = keymaps; for (; help_map->km_name; help_map++) { if (strncmp(help_map->km_name, head, len)) continue; if (list_completion(help_map->km_name) == 0) break; } fl; return 1; } for (; map->km_name; map++) { if (len && strncmp(map->km_name, head, len)) continue; strcpy(tail, map->km_name + len); if (map != keymaps) strcat(tail, " "); map++; return 1; } return 0; } nn-6.7.4/nnusage.sh0000644000175000017500000000210005750350012013533 0ustar mtpinsmtpins# CONFIG file is inserted here OPT="" SORTMODE="" ALL=false LOOP=true while $LOOP do case "$1" in -a) OPT="$OPT -a" ALL=true shift ;; -t) SORTMODE="+1nr" shift ;; -at) OPT="$OPT -a" ALL=true SORTMODE="+1nr" shift ;; -*) echo "$0: unknown option: $1" exit 1 ;; *) LOOP=false ;; esac done if [ -f $DB/acct -a -f $BIN/nnacct ] ; then if $AUTH ; then echo "USER USAGE QUOTA LAST_ACTIVE COST/PERIOD POLICY" else echo "USER USAGE QUOTA LAST_ACTIVE COST/PERIOD" fi $BIN/nnacct -r $OPT $@ | sed -e 1d | sort $SORTMODE exit fi OLDLOG=${LOG}.old if [ ! -s ${OLDLOG} ] then OLDLOG="" fi cat $OLDLOG $LOG | if $ALL then grep '^U:' else grep "^U:.*(${LOGNAME-$USER})" fi | ${AWK} ' BEGIN { any=0 } NF == 7 { if (split($7, t, ".") == 2) { u[$5] += t[1] * 60 + t[2] if (any == 0) printf("Usage since %s %d, %s\n", $2, $3, $4) any=1 } } END { if (!any) { printf("No usage statistics\n") exit } for (n in u) { name=substr(n, 2, length(n)-3) printf("%-10.10s%8d.%02d\n", name, u[n]/60, u[n]%60); } }' | sort $SORTMODE nn-6.7.4/split.c0000644000175000017500000002227707751421413013066 0ustar mtpinsmtpins/*******************WARNING********************* This is a *MODIFIED* version of Geoff Coller's proof-of-concept NOV implementation. It has been modified to support threading directly from a file handle to a NNTP server without a temporary file. This is not a complete distribution. We have only distributed enough to support NN's needs. The original version came from world.std.com:/src/news/nov.dist.tar.Z and was dated 11 Aug 1993. In any case, bugs you find here are probably my fault, as I've trimmed a fair bit of unused code. -Peter Wemm */ /* * Copyright (c) Geoffrey Collyer 1992, 1993. * All rights reserved. * Written by Geoffrey Collyer. * Thanks to UUNET Communications Services Inc for financial support. * * This software is not subject to any license of the American Telephone * and Telegraph Company, the Regents of the University of California, or * the Free Software Foundation. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * * 4. This notice may not be removed or altered. */ #include "config.h" /* * split - divide a string into fields, like awk split() */ int /* number of fields, including overflow */ split(char *string, char *fields[], int nfields, char *sep) /* fields list is not NULL-terminated */ /* nfields number of entries available in fields[] */ /* sep "" white, "c" single char, "ab" [ab]+ */ { register char *p = string; register char c; /* latest character */ register char sepc = sep[0]; register char sepc2; register int fn; register char **fp = fields; register char *sepp; register int trimtrail; /* white space */ if (sepc == '\0') { while ((c = *p++) == ' ' || c == '\t') continue; p--; trimtrail = 1; sep = " \t"; /* note, code below knows this is 2 long */ sepc = ' '; } else trimtrail = 0; sepc2 = sep[1]; /* now we can safely pick this up */ /* catch empties */ if (*p == '\0') return (0); /* single separator */ if (sepc2 == '\0') { fn = nfields; for (;;) { *fp++ = p; fn--; if (fn == 0) break; while ((c = *p++) != sepc) if (c == '\0') return (nfields - fn); *(p - 1) = '\0'; } /* we have overflowed the fields vector -- just count them */ fn = nfields; for (;;) { while ((c = *p++) != sepc) if (c == '\0') return (fn); fn++; } /* not reached */ } /* two separators */ if (sep[2] == '\0') { fn = nfields; for (;;) { *fp++ = p; fn--; while ((c = *p++) != sepc && c != sepc2) if (c == '\0') { if (trimtrail && **(fp - 1) == '\0') fn++; return (nfields - fn); } if (fn == 0) break; *(p - 1) = '\0'; while ((c = *p++) == sepc || c == sepc2) continue; p--; } /* we have overflowed the fields vector -- just count them */ fn = nfields; while (c != '\0') { while ((c = *p++) == sepc || c == sepc2) continue; p--; fn++; while ((c = *p++) != '\0' && c != sepc && c != sepc2) continue; } /* might have to trim trailing white space */ if (trimtrail) { p--; while ((c = *--p) == sepc || c == sepc2) continue; p++; if (*p != '\0') { if (fn == nfields + 1) *p = '\0'; fn--; } } return (fn); } /* n separators */ fn = 0; for (;;) { if (fn < nfields) *fp++ = p; fn++; for (;;) { c = *p++; if (c == '\0') return (fn); sepp = sep; while ((sepc = *sepp++) != '\0' && sepc != c) continue; if (sepc != '\0') /* it was a separator */ break; } if (fn < nfields) *(p - 1) = '\0'; for (;;) { c = *p++; sepp = sep; while ((sepc = *sepp++) != '\0' && sepc != c) continue; if (sepc == '\0') /* it wasn't a separator */ break; } p--; } /* not reached */ } #ifdef TEST_SPLIT /* * test program * pgm runs regression * pgm sep splits stdin lines by sep * pgm str sep splits str by sep * pgm str sep n splits str by sep n times */ int main(int argc, char *argv[]) { char buf[512]; register int n; #define MNF 10 char *fields[MNF]; if (argc > 4) for (n = atoi(argv[3]); n > 0; n--) { (void) strcpy(buf, argv[1]); } else if (argc > 3) for (n = atoi(argv[3]); n > 0; n--) { (void) strcpy(buf, argv[1]); (void) split(buf, fields, MNF, argv[2]); } else if (argc > 2) dosplit(argv[1], argv[2]); else if (argc > 1) while (fgets(buf, sizeof(buf), stdin) != NULL) { buf[strlen(buf) - 1] = '\0'; /* stomp newline */ dosplit(buf, argv[1]); } else regress(); exit(0); } dosplit(char *string, char *seps) { #define NF 5 char *fields[NF]; register int nf; nf = split(string, fields, NF, seps); print(nf, NF, fields); } print(int nf, int nfp, char *fields[]) { register int fn; register int bound; bound = (nf > nfp) ? nfp : nf; printf("%d:\t", nf); for (fn = 0; fn < bound; fn++) printf("\"%s\"%s", fields[fn], (fn + 1 < nf) ? ", " : "\n"); } #define RNF 5 /* some table entries know this */ struct { char *str; char *seps; int nf; char *fi[RNF]; } tests[] = { "", " ", 0, { "" }, " ", " ", 2, { "", "" }, "x", " ", 1, { "x" }, "xy", " ", 1, { "xy" }, "x y", " ", 2, { "x", "y" }, "abc def g ", " ", 5, { "abc", "def", "", "g", "" }, " a bcd", " ", 4, { "", "", "a", "bcd" }, "a b c d e f", " ", 6, { "a", "b", "c", "d", "e f" }, " a b c d ", " ", 6, { "", "a", "b", "c", "d " }, "", " _", 0, { "" }, " ", " _", 2, { "", "" }, "x", " _", 1, { "x" }, "x y", " _", 2, { "x", "y" }, "ab _ cd", " _", 2, { "ab", "cd" }, " a_b c ", " _", 5, { "", "a", "b", "c", "" }, "a b c_d e f", " _", 6, { "a", "b", "c", "d", "e f" }, " a b c d ", " _", 6, { "", "a", "b", "c", "d " }, "", " _~", 0, { "" }, " ", " _~", 2, { "", "" }, "x", " _~", 1, { "x" }, "x y", " _~", 2, { "x", "y" }, "ab _~ cd", " _~", 2, { "ab", "cd" }, " a_b c~", " _~", 5, { "", "a", "b", "c", "" }, "a b_c d~e f", " _~", 6, { "a", "b", "c", "d", "e f" }, "~a b c d ", " _~", 6, { "", "a", "b", "c", "d " }, "", " _~-", 0, { "" }, " ", " _~-", 2, { "", "" }, "x", " _~-", 1, { "x" }, "x y", " _~-", 2, { "x", "y" }, "ab _~- cd", " _~-", 2, { "ab", "cd" }, " a_b c~", " _~-", 5, { "", "a", "b", "c", "" }, "a b_c-d~e f", " _~-", 6, { "a", "b", "c", "d", "e f" }, "~a-b c d ", " _~-", 6, { "", "a", "b", "c", "d " }, "", " ", 0, { "" }, " ", " ", 2, { "", "" }, "x", " ", 1, { "x" }, "xy", " ", 1, { "xy" }, "x y", " ", 2, { "x", "y" }, "abc def g ", " ", 4, { "abc", "def", "g", "" }, " a bcd", " ", 3, { "", "a", "bcd" }, "a b c d e f", " ", 6, { "a", "b", "c", "d", "e f" }, " a b c d ", " ", 6, { "", "a", "b", "c", "d " }, "", "", 0, { "" }, " ", "", 0, { "" }, "x", "", 1, { "x" }, "xy", "", 1, { "xy" }, "x y", "", 2, { "x", "y" }, "abc def g ", "", 3, { "abc", "def", "g" }, "\t a bcd", "", 2, { "a", "bcd" }, " a \tb\t c ", "", 3, { "a", "b", "c" }, "a b c d e ", "", 5, { "a", "b", "c", "d", "e" }, "a b\tc d e f", "", 6, { "a", "b", "c", "d", "e f" }, " a b c d e f ", "", 6, { "a", "b", "c", "d", "e f " }, NULL, NULL, 0, { NULL }, }; regress(void) { char buf[512]; register int n; char *fields[RNF + 1]; register int nf; register int i; register int printit; register char *f; for (n = 0; tests[n].str != NULL; n++) { (void) strcpy(buf, tests[n].str); fields[RNF] = NULL; nf = split(buf, fields, RNF, tests[n].seps); printit = 0; if (nf != tests[n].nf) { printf("split `%s' by `%s' gave %d fields, not %d\n", tests[n].str, tests[n].seps, nf, tests[n].nf); printit = 1; } else if (fields[RNF] != NULL) { printf("split() went beyond array end\n"); printit = 1; } else { for (i = 0; i < nf && i < RNF; i++) { f = fields[i]; if (f == NULL) f = "(NULL)"; if (strcmp(f, tests[n].fi[i]) != 0) { printf("split `%s' by `%s', field %d is `%s', not `%s'\n", tests[n].str, tests[n].seps, i, fields[i], tests[n].fi[i]); printit = 1; } } } if (printit) print(nf, RNF, fields); } } #endif nn-6.7.4/upgrade_rc.sh0000644000175000017500000000141005750350025014215 0ustar mtpinsmtpins# Upgrade from release 6.3 # # Convert old rc file to .newsrc cd if [ ! -d .nn ] then echo "No .nn directory" exit 1 fi if [ ! -f .nn/rc ] then echo "No rc file -- upgrade not possible" exit 2 fi if [ x"$1" = "xn" ] then echo "Using existing .newsrc" else if [ -f .newsrc ] then rm -f .newsrc.old mv .newsrc .newsrc.old echo "Old .newsrc saved in .newsrc.old" fi echo "Creating .newsrc" ${AWK} ' NF != 3 { next } $1 == "+" || $1 == "!" { if ($1 == "+") printf("%s:", $3) else printf("%s!", $3) if ($2+0 > 1) printf(" 1-%d\n", $2+0) else if ($2 == 1) printf(" 1\n") else printf("\n") }' < .nn/rc > .newsrc fi cd .nn rm -f rc-6.3 S.[0-9]* mv rc rc-6.3 echo "Old rc file saved in rc-6.3" echo "Upgrade completed" exit 0 nn-6.7.4/variable.c0000644000175000017500000010314315213127105013501 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Variable setting and display */ #include #include #include #include "config.h" #include "global.h" #include "chset.h" #include "folder.h" #include "init.h" #include "keymap.h" #include "regexp.h" #include "sort.h" #include "nn_term.h" /* variable.c */ static struct variable_defs *lookup_variable(char *variable); static char escaped_char(char c); static void adjust(register char *str); static char *var_value(register struct variable_defs * var, char *tag); static int var_on_stack(register struct variable_defs * var); extern int prompt_length, prompt_line; extern regexp *pg_regexp; extern int pg_new_regexp; /* * Variable types and variants * * Boolean: type 'int'. * * BOOL 0 Ordinary boolean variable. * BOOL 1 As 0 + redraw screen on return. * BOOL 2 Special: reorder menu according to new value. * BOOL 4 Inverse boolean variable ("set" clears internal var). * * Numeric: type 'int'. * * INT 0 Ordinary numeric variable ("unset" set var to 0). * INT 1 As 0 + redraw screen on return. * INT 2 As 0, but "unset" set var to -1. * INT 3 As 2 + redraw screen on return. * * Strings: type 'char *' * * STR 0 Ordinary string ("unset" set var to NULL). * STR 2 (home relative) file name or full path. * Automatically expanded. ("unset" set var to NULL). * STR 3 Ordinary string, but cannot be "unset". * STR 4 (Expanded) file name - cannot be unset. * STR 5 Ordinary string ("unset" set var to ""). * * CODES n String initialized by list of key names. * A maximum of 16 CODES variables (n = 0 - 15) exist. * * Strings: type 'char []' * * STR 1 Ordinary string saved in static array. * "unset" set array to empty string. * Warning: no bounds checking! * Notice: Variable address is "(char **)var" (no &). * * Keys: type 'key_type' * * KEY 0 Ordinary key. * * Pseudo variables: * * SPEC n Treated by V_SPECIAL 'case n'. * * Modifiers: * * INIT Can only be set in init file. * SAFE Cannot be changed if "shell-restrictions" is set. */ extern int in_init; extern char /* string variables */ *backup_folder_path, *bak_suffix, *bug_address, *counter_delim_left, *counter_delim_right, *decode_header_file, *default_distribution, *default_save_file, *distribution_follow, *distribution_post, *editor_program, *extra_mail_headers, *extra_news_headers, *folder_save_file, *header_lines, *folder_directory, included_mark[], *inews_program, *initial_newsrc_path, *mail_alias_expander, *mail_box, *mail_record, *mail_script, *mailer_program, attributes[], *newsrc_file, *news_record, *news_script, *pager, patch_command[], printer[], *print_header_lines, *response_dflt_answer, *save_counter_format, *save_header_lines, *saved_header_escape, *shade_on_attr, *shade_off_attr, *spell_checker, *sign_type, *trusted_escapes, unshar_command[], *unshar_header_file, *user_shell; extern int /* boolean variables */ also_cross_postings, also_full_digest, also_subgroups, append_sig_mail, append_sig_post, auto_junk_seen, auto_select_rw, auto_select_subject, auto_preview_mode, body_search_header, case_fold_search, check_group_access, compress_mode, conf_append, conf_auto_quit, conf_create, conf_dont_sleep, conf_group_entry, conf_junk_seen, consolidated_manual, consolidated_menu, counter_padding, decode_keep, delay_redraw, dflt_kill_select, do_kill_handling, dont_sort_articles, dont_sort_folders, dont_split_digests, echo_prefix_key, edit_patch_command, edit_print_command, edit_unshar_command, empty_answer_check, enter_last_read_mode, flow_control, flush_typeahead, fmt_rptsubj, folder_format_check, folder_rewrite_trace, guard_double_slash, ignore_formfeed, ignore_xon_xoff, ignore_re, include_art_id, include_full_header, include_mark_blanks, inews_pipe_input, keep_backup_folder, keep_rc_backup, keep_unsubscribed, keep_unsub_long, long_menu, macro_debug, mailer_pipe_input, mark_overlap, mark_overlap_shading, match_parts_equal, match_parts_begin, monitor_mode, new_read_prompt, novice, old_packname, prev_also_read, preview_mark_read, query_signature, quick_save, quick_unread_count, read_ret_next_page, repeat_group_query, report_cost_on_exit, retain_seen_status, save_report, scroll_clear_page, select_leave_next, select_on_sender, seq_cross_filtering, shell_restrictions, show_article_date, show_current_time, show_motd_on_entry, silent, slow_mode, suggest_save_file, tidy_newsrc, use_ed_line, use_mail_folders, use_mmdf_folders, use_path_in_from, use_selections, use_visible_bell; extern int /* integer variables */ also_read_articles, article_limit, auto_read_limit, auto_select_closed, check_db_update, conf_entry_limit, collapse_subject, Columns, cross_post_limit, data_bits, Debug, decode_skip_prefix, entry_message_limit, expired_msg_delay, first_page_lines, fmt_linenum, kill_debug, kill_ref_count, Lines, match_skip_prefix, mark_next_group, mark_read_return, mark_read_skip, menu_spacing, merge_report_rate, message_history, min_pv_window, mouse_usage, multi_key_guard_time, new_group_action, newsrc_update_freq, orig_to_include_mask, overlap, preview_continuation, preview_window, print_header_type, re_layout, re_layout_more, response_check_pause, retry_on_error, save_closed_mode, save_counter_offset, scroll_last_lines, show_purpose_mode, slow_speed, strict_from_parse, sort_mode, subject_match_limit, wrap_headers; #ifdef NNTP extern char *nn_directory; extern char *nntp_cache_dir, *nntp_server; extern char *nntp_user, *nntp_password; extern int nntp_cache_size, nntp_debug, nntp_forceauth; #endif extern key_type /* key strokes */ comp1_key, comp2_key, help_key, erase_key, delword_key, kill_key; #undef STR #undef BOOL #undef INT #undef KEY #undef SPEC #undef SAFE #undef INIT #undef CODES #define V_STRING 0x1000 #define V_BOOLEAN 0x2000 #define V_INTEGER 0x3000 #define V_KEY 0x4000 #define V_SPECIAL 0x5000 #define V_CODES 0x6000 #define V_SAFE 0x0100 #define V_INIT 0x0200 #define V_LOCKED 0x0800 #define V_MODIFIED 0x8000 #define STR V_STRING | #define BOOL V_BOOLEAN | #define INT V_INTEGER | #define KEY V_KEY | #define SPEC V_SPECIAL | #define CODES V_CODES | #define SAFE V_SAFE | #define INIT V_INIT | static char *code_strings[16] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static struct variable_defs { char *var_name; int var_flags; char **var_addr; } variables[] = { "also-full-digest", BOOL 0, (char **) &also_full_digest, "also-subgroups", BOOL INIT 0, (char **) &also_subgroups, "append-signature-mail", BOOL 0, (char **) &append_sig_mail, "append-signature-post", BOOL 0, (char **) &append_sig_post, "attributes", STR 1, (char **) attributes, "auto-junk-seen", BOOL 0, (char **) &auto_junk_seen, "auto-preview-mode", BOOL 0, (char **) &auto_preview_mode, "auto-read-mode-limit", INT 0, (char **) &auto_read_limit, "auto-select-closed", INT 0, (char **) &auto_select_closed, "auto-select-rw", BOOL 0, (char **) &auto_select_rw, "auto-select-subject", BOOL 0, (char **) &auto_select_subject, "backup", BOOL 0, (char **) &keep_rc_backup, "backup-folder-path", STR 4, (char **) &backup_folder_path, "backup-suffix", STR 0, (char **) &bak_suffix, "body-search-header", BOOL 0, (char **) &body_search_header, "bug-report-address", STR 0, (char **) &bug_address, "case-fold-search", BOOL 0, (char **) &case_fold_search, "charset", SPEC 3, (char **) NULL, "check-db-update-time", INT 0, (char **) &check_db_update, "check-group-access", BOOL 0, (char **) &check_group_access, "collapse-subject", INT 3, (char **) &collapse_subject, "columns", INT 1, (char **) &Columns, "comp1-key", KEY 0, (char **) &comp1_key, "comp2-key", KEY 0, (char **) &comp2_key, "compress", BOOL 0, (char **) &compress_mode, "confirm-append", BOOL 0, (char **) &conf_append, "confirm-auto-quit", BOOL 0, (char **) &conf_auto_quit, "confirm-create", BOOL 0, (char **) &conf_create, "confirm-entry", BOOL 0, (char **) &conf_group_entry, "confirm-entry-limit", INT 0, (char **) &conf_entry_limit, "confirm-junk-seen", BOOL 0, (char **) &conf_junk_seen, "confirm-messages", BOOL 0, (char **) &conf_dont_sleep, "consolidated-manual", BOOL 0, (char **) &consolidated_manual, "consolidated-menu", BOOL 1, (char **) &consolidated_menu, "counter-delim-left", STR 5, (char **) &counter_delim_left, "counter-delim-right", STR 5, (char **) &counter_delim_right, "counter-padding", INT 1, (char **) &counter_padding, "cross-filter-seq", BOOL 0, (char **) &seq_cross_filtering, "cross-post", BOOL 0, (char **) &also_cross_postings, "cross-post-limit", INT 0, (char **) &cross_post_limit, "data-bits", INT 0, (char **) &data_bits, "date", BOOL 0, (char **) &show_article_date, "debug", INT 0, (char **) &Debug, "decode-header-file", STR 0, (char **) &decode_header_file, "decode-keep", INT 0, (char **) &decode_keep, "decode-skip-prefix", INT 0, (char **) &decode_skip_prefix, "default-distribution", STR 0, (char **) &default_distribution, "default-kill-select", INT 0, (char **) &dflt_kill_select, "default-save-file", STR 3, (char **) &default_save_file, "delay-redraw", BOOL 0, (char **) &delay_redraw, "echo-prefix-key", BOOL 0, (char **) &echo_prefix_key, "edit-patch-command", BOOL 0, (char **) &edit_patch_command, "edit-print-command", BOOL 0, (char **) &edit_print_command, "edit-response-check", BOOL 0, (char **) &empty_answer_check, "edit-unshar-command", BOOL 0, (char **) &edit_unshar_command, "editor", STR 0, (char **) &editor_program, "embedded-header-escape", STR 0, (char **) &saved_header_escape, "enter-last-read-mode", INT 0, (char **) &enter_last_read_mode, "entry-report-limit", INT 0, (char **) &entry_message_limit, "erase-key", KEY 0, (char **) &erase_key, "expert", BOOL 4, (char **) &novice, "expired-message-delay", INT 0, (char **) &expired_msg_delay, "flow-control", BOOL 0, (char **) &flow_control, "flush-typeahead", BOOL 0, (char **) &flush_typeahead, "folder", STR 2, (char **) &folder_directory, "folder-format-check", BOOL 0, (char **) &folder_format_check, "folder-save-file", STR 3, (char **) &folder_save_file, "follow-distribution", STR 0, (char **) &distribution_follow, "from-line-parsing", INT 0, (char **) &strict_from_parse, "fsort", BOOL 2, (char **) &dont_sort_folders, "guard-double-slash", BOOL 0, (char **) &guard_double_slash, "header-lines", STR 0, (char **) &header_lines, "help-key", KEY 0, (char **) &help_key, "ignore-formfeed", BOOL 0, (char **) &ignore_formfeed, "ignore-re", BOOL 0, (char **) &ignore_re, "ignore-xon-xoff", BOOL 0, (char **) &ignore_xon_xoff, "include-art-id", BOOL 0, (char **) &include_art_id, "include-full-header", BOOL 0, (char **) &include_full_header, "include-mark-blank-lines", BOOL 0, (char **) &include_mark_blanks, "included-mark", STR 1, (char **) included_mark, "inews", STR 0, (char **) &inews_program, "inews-pipe-input", BOOL 0, (char **) &inews_pipe_input, "initial-newsrc-file", STR 0, (char **) &initial_newsrc_path, "keep-backup-folder", BOOL 0, (char **) &keep_backup_folder, "keep-unsubscribed", BOOL 0, (char **) &keep_unsubscribed, "kill", BOOL 0, (char **) &do_kill_handling, "kill-debug", BOOL 0, (char **) &kill_debug, "kill-key", KEY 0, (char **) &kill_key, "kill-reference-count", INT 0, (char **) &kill_ref_count, "layout", INT 1, (char **) &fmt_linenum, "limit", INT 2, (char **) &article_limit, "lines", INT 1, (char **) &Lines, "long-menu", BOOL 1, (char **) &long_menu, "macro-debug", BOOL 0, (char **) ¯o_debug, "mail", STR 2, (char **) &mail_box, "mail-alias-expander", STR 0, (char **) &mail_alias_expander, "mail-format", BOOL 0, (char **) &use_mail_folders, "mail-header", STR 0, (char **) &extra_mail_headers, "mail-record", STR 2, (char **) &mail_record, "mail-script", STR SAFE 2, (char **) &mail_script, "mailer", STR 0, (char **) &mailer_program, "mailer-pipe-input", BOOL 0, (char **) &mailer_pipe_input, "mark-overlap", BOOL 0, (char **) &mark_overlap, "mark-overlap-shading", BOOL 0, (char **) &mark_overlap_shading, "marked-by-next-group", INT 0, (char **) &mark_next_group, "marked-by-read-return", INT 0, (char **) &mark_read_return, "marked-by-read-skip", INT 0, (char **) &mark_read_skip, "menu-spacing", INT 1, (char **) &menu_spacing, "merge-report-rate", INT 0, (char **) &merge_report_rate, "message-history", INT 0, (char **) &message_history, "min-window", INT 1, (char **) &min_pv_window, "mmdf-format", BOOL 0, (char **) &use_mmdf_folders, "monitor", BOOL 0, (char **) &monitor_mode, "motd", BOOL 0, (char **) &show_motd_on_entry, "mouse-usage", INT 0, (char **) &mouse_usage, "multi-key-guard-time", INT 0, (char **) &multi_key_guard_time, "new-group-action", INT 0, (char **) &new_group_action, "new-style-read-prompt", BOOL 0, (char **) &new_read_prompt, "news-header", STR 0, (char **) &extra_news_headers, "news-record", STR 2, (char **) &news_record, "news-script", STR SAFE 2, (char **) &news_script, "newsrc", STR 2, (char **) &newsrc_file, "nn-directory", STR 2, (char **) &nn_directory, #ifdef NNTP "nntp-cache-dir", STR INIT 0, (char **) &nntp_cache_dir, "nntp-cache-size", INT INIT 0, (char **) &nntp_cache_size, "nntp-debug", BOOL 0, (char **) &nntp_debug, "nntp-forceauth", BOOL 0, (char **) &nntp_forceauth, "nntp-password", STR 3, (char **) &nntp_password, "nntp-server", STR 3, (char **) &nntp_server, "nntp-user", STR 3, (char **) &nntp_user, #endif /* "no....." -- cannot have variable names starting with "no" */ "old", SPEC 2, (char **) NULL, "old-packname", BOOL 0, (char **) &old_packname, "orig-to-include-mask", INT 0, (char **) &orig_to_include_mask, "overlap", INT 0, (char **) &overlap, "pager", STR SAFE 3, (char **) &pager, "patch-command", STR SAFE 1, (char **) patch_command, "post-distribution", STR 0, (char **) &distribution_post, "preview-continuation", INT 0, (char **) &preview_continuation, "preview-mark-read", BOOL 0, (char **) &preview_mark_read, "previous-also-read", BOOL 0, (char **) &prev_also_read, "print-header-lines", STR 0, (char **) &print_header_lines, "print-header-type", INT 0, (char **) &print_header_type, "printer", STR SAFE 1, (char **) printer, "query-signature", BOOL 0, (char **) &query_signature, "quick-count", BOOL 0, (char **) &quick_unread_count, "quick-save", BOOL 0, (char **) &quick_save, "re-layout", INT 0, (char **) &re_layout, "re-layout-read", INT 0, (char **) &re_layout_more, "read-return-next-page", BOOL 0, (char **) &read_ret_next_page, "record", SPEC 1, (char **) NULL, "repeat", BOOL 0, (char **) &fmt_rptsubj, "repeat-group-query", BOOL 0, (char **) &repeat_group_query, "report-cost", BOOL 0, (char **) &report_cost_on_exit, "response-check-pause", INT 0, (char **) &response_check_pause, "response-default-answer", STR 0, (char **) &response_dflt_answer, "retain-seen-status", BOOL 0, (char **) &retain_seen_status, "retry-on-error", INT 0, (char **) &retry_on_error, "save-closed-mode", INT 0, (char **) &save_closed_mode, "save-counter", STR 3, (char **) &save_counter_format, "save-counter-offset", INT 0, (char **) &save_counter_offset, "save-header-lines", STR 0, (char **) &save_header_lines, "save-report", BOOL 0, (char **) &save_report, "scroll-clear-page", BOOL 0, (char **) &scroll_clear_page, "scroll-last-lines", INT 0, (char **) &scroll_last_lines, "select-leave-next", BOOL 0, (char **) &select_leave_next, "select-on-sender", BOOL 0, (char **) &select_on_sender, "shading-off", CODES 0, (char **) &shade_off_attr, "shading-on", CODES 1, (char **) &shade_on_attr, "shell", STR SAFE 0, (char **) &user_shell, "shell-restrictions", BOOL INIT 0, (char **) &shell_restrictions, "show-purpose-mode", INT 0, (char **) &show_purpose_mode, "sign-type", STR 0, (char **) &sign_type, "silent", BOOL 0, (char **) &silent, "slow-mode", BOOL 0, (char **) &slow_mode, "slow-speed", INT 0, (char **) &slow_speed, "sort", BOOL 2, (char **) &dont_sort_articles, "sort-mode", INT 0, (char **) &sort_mode, "spell-checker", STR 0, (char **) &spell_checker, "split", BOOL 4, (char **) &dont_split_digests, "stop", INT 0, (char **) &first_page_lines, "subject-match-limit", INT 0, (char **) &subject_match_limit, "subject-match-minimum", INT 0, (char **) &match_parts_begin, "subject-match-offset", INT 0, (char **) &match_skip_prefix, "subject-match-parts", BOOL 0, (char **) &match_parts_equal, "suggest-default-save", BOOL 0, (char **) &suggest_save_file, "tidy-newsrc", BOOL 0, (char **) &tidy_newsrc, "time", BOOL 0, (char **) &show_current_time, "trace-folder-packing", BOOL 0, (char **) &folder_rewrite_trace, "trusted-escape-codes", STR 0, (char **) &trusted_escapes, "unshar-command", STR SAFE 1, (char **) unshar_command, "unshar-header-file", STR 0, (char **) &unshar_header_file, "unsubscribe-mark-read", BOOL 4, (char **) &keep_unsub_long, "update-frequency", INT 0, (char **) &newsrc_update_freq, "use-editor-line", BOOL 0, (char **) &use_ed_line, "use-path-in-from", BOOL 0, (char **) &use_path_in_from, "use-selections", BOOL 0, (char **) &use_selections, "visible-bell", BOOL 0, (char **) &use_visible_bell, "window", INT 1, (char **) &preview_window, "word-key", KEY 0, (char **) &delword_key, "wrap-header-margin", INT 2, (char **) &wrap_headers }; #define TABLE_SIZE (sizeof(variables)/sizeof(struct variable_defs)) #define INT_VAR (*((int *)(var->var_addr))) #define BOOL_VAR (*((int *)(var->var_addr))) #define STR_VAR (*(var->var_addr)) #define CBUF_VAR ((char *)(var->var_addr)) #define KEY_VAR (*((key_type *)(var->var_addr))) #define VAR_TYPE (var->var_flags & 0x7000) #define VAR_OP (var->var_flags & 0x000f) static struct variable_defs * lookup_variable(char *variable) { register struct variable_defs *var; register int i, j, k, t; i = 0; j = TABLE_SIZE - 1; while (i <= j) { k = (i + j) / 2; var = &variables[k]; if ((t = strcmp(variable, var->var_name)) > 0) i = k + 1; else if (t < 0) j = k - 1; else return var; } init_message("unknown variable: %s", variable); return NULL; } static char escaped_char(char c) { switch (c) { case 'a': return 007; case 'b': return BS; case 'e': return 033; case 'f': return '\f'; case 'n': return NL; case 'r': return CR; case 't': return TAB; } return c; } static void adjust(register char *str) { register char *s, *t; if ((s = t = str) == NULL) return; while (*s && *s != '#') { if (*s == '\\' && s[1] != NUL) { s++; *str++ = escaped_char(*s++); } else if (str == s) { str++; if (isspace(*s++)) continue; } else if (isspace(*str++ = *s++)) continue; t = str; } *t = NUL; } int set_variable(char *variable, int on, char *val_string) { int value; register struct variable_defs *var; if (strncmp(variable, "no", 2) == 0) { on = !on; variable += 2; if (variable[0] == '-') variable++; } if ((var = lookup_variable(variable)) == NULL) return 0; if (!in_init && (var->var_flags & (V_INIT | V_SAFE))) { if (var->var_flags & V_INIT) { msg("'%s' can only be set in the init file", variable); return 0; } if (shell_restrictions) { msg("Restricted operation - cannot change"); return 0; } } if (var->var_flags & V_LOCKED) { msg("Variable '%s' is locked", variable); return 0; } if (!on || val_string == NULL) value = 0; else value = atoi(val_string); var->var_flags |= V_MODIFIED; switch (VAR_TYPE) { case V_STRING: if (on) adjust(val_string); switch (VAR_OP) { case 0: STR_VAR = (on && val_string) ? copy_str(val_string) : (char *) NULL; break; case 1: strcpy(CBUF_VAR, (on && val_string) ? val_string : ""); break; case 2: if (on) { char exp_buf[FILENAME]; if (val_string) { if (expand_file_name(exp_buf, val_string, 1)) STR_VAR = home_relative(exp_buf); } } else STR_VAR = (char *) NULL; break; case 3: case 4: if (!on || val_string == NULL) { msg("Cannot unset string `%s'", variable); break; } if (VAR_OP == 4) { char exp_buf[FILENAME]; if (expand_file_name(exp_buf, val_string, 1)) { STR_VAR = copy_str(exp_buf); break; } } STR_VAR = copy_str(val_string); break; case 5: STR_VAR = (on && val_string) ? copy_str(val_string) : ""; break; } break; case V_BOOLEAN: adjust(val_string); if (val_string && *val_string != NUL) { if (val_string[0] == 'o') on = val_string[1] == 'n'; /* on */ else on = val_string[0] == 't'; /* true */ } switch (VAR_OP) { case 0: BOOL_VAR = on; break; case 1: BOOL_VAR = on; return 1; case 2: if (BOOL_VAR) { /* don't change if already ok */ if (!on) break; } else if (on) break; BOOL_VAR = !on; if (!in_init) { sort_articles(BOOL_VAR ? 0 : -1); return 1; } break; case 4: BOOL_VAR = !on; break; } break; case V_INTEGER: switch (VAR_OP) { case 0: case 1: INT_VAR = value; break; case 2: case 3: if (!on) value = -1; INT_VAR = value; break; } return (VAR_OP & 1); case V_KEY: switch (VAR_OP) { case 0: if (val_string) { if (*val_string) adjust(val_string + 1); /* #N is valid */ KEY_VAR = parse_key(val_string); } break; } break; case V_SPECIAL: switch (VAR_OP) { case 1: if (val_string) { adjust(val_string); news_record = home_relative(val_string); mail_record = news_record; var->var_flags &= ~V_MODIFIED; lookup_variable("mail-record")->var_flags |= V_MODIFIED; lookup_variable("news-record")->var_flags |= V_MODIFIED; } break; case 2: also_read_articles = on; article_limit = (on && value > 0) ? value : -1; break; case 3: { struct chset *csp; struct variable_defs *dbvar; dbvar = lookup_variable("data-bits"); if (on && val_string) { if ((csp = getchset(val_string)) == NULL) msg("Illegal value for `%s' variable", variable); else { curchset = csp; data_bits = csp->cs_width ? csp->cs_width : 7; dbvar->var_flags &= ~V_MODIFIED; } } else msg("Cannot unset special `%s' variable", variable); } break; } break; case V_CODES: { char codes[80], code[16], *sp, *cp, *vs; if (val_string == NULL) on = 0; if (on) { adjust(val_string); if (val_string[0] == NUL) on = 0; } if (on) { sp = codes; vs = val_string; while (*vs) { while (*vs && (!isascii(*vs) || isspace(*vs))) vs++; if (*vs == NUL) break; cp = code; while (*vs && isascii(*vs) && !isspace(*vs)) *cp++ = *vs++; *cp = NUL; *sp++ = parse_key(code); } *sp = NUL; if (codes[0] == NUL) on = 0; } freeobj(code_strings[VAR_OP]); code_strings[VAR_OP] = on ? copy_str(val_string) : NULL; STR_VAR = on ? copy_str(codes) : (char *) NULL; break; } } return 0; } void toggle_variable(char *variable) { register struct variable_defs *var; if ((var = lookup_variable(variable)) == NULL) return; if (VAR_TYPE != V_BOOLEAN) { init_message("variable %s is not boolean", variable); return; } BOOL_VAR = !BOOL_VAR; } void lock_variable(char *variable) { register struct variable_defs *var; if ((var = lookup_variable(variable)) != NULL) var->var_flags |= V_LOCKED; } static char * var_value(register struct variable_defs * var, char *tag) { static char ival[16]; register char *str = NULL; register int b; if (tag != NULL) *tag = var_on_stack(var) ? '>' : (var->var_flags & V_LOCKED) ? '!' : (var->var_flags & V_MODIFIED) ? '*' : ' '; switch (VAR_TYPE) { case V_STRING: str = (VAR_OP == 1) ? CBUF_VAR : STR_VAR; break; case V_BOOLEAN: b = BOOL_VAR; if (VAR_OP == 2 || VAR_OP == 4) b = !b; str = b ? "on" : "off"; break; case V_INTEGER: sprintf(ival, "%d", INT_VAR); str = ival; break; case V_KEY: str = key_name(KEY_VAR); break; case V_SPECIAL: str = "UNDEF"; switch (VAR_OP) { case 2: if (!also_read_articles) break; sprintf(ival, "%d", article_limit); str = ival; break; case 3: str = curchset->cs_name; break; } break; case V_CODES: str = code_strings[VAR_OP]; break; } if (str == NULL) str = "NULL"; return str; } int test_variable(char *expr) { char *variable; register struct variable_defs *var; int res = -1; variable = expr; if ((expr = strchr(variable, '=')) == NULL) goto err; *expr++ = NUL; if ((var = lookup_variable(variable)) == NULL) { msg("testing unknown variable %s=%s", variable, expr); goto out; } switch (VAR_TYPE) { case V_BOOLEAN: res = BOOL_VAR; if (strcmp(expr, "on") == 0 || strcmp(expr, "true") == 0) break; if (strcmp(expr, "off") == 0 || strcmp(expr, "false") == 0) { res = !res; break; } msg("boolean variables must be tested =on or =off"); break; case V_INTEGER: res = (INT_VAR == atoi(expr)) ? 1 : 0; break; default: msg("%s: cannot only test boolean and integer variables", variable); break; } out: *--expr = '='; err: return res; } static int vc_column; void var_compl_opts(int col) { vc_column = col; } int var_completion(char *path, int index) { static char *head, *tail = NULL; static int len; static struct variable_defs *var, *help_var; if (index < 0) { clrmsg(-(index + 1)); return 1; } if (path) { head = path; tail = path + index; while (*head && isspace(*head)) head++; if (strncmp(head, "no", 2) == 0) { head += 2; if (*head == '-') head++; } help_var = var = variables; len = tail - head; return 1; } if (index) { list_completion((char *) NULL); for (;; help_var++) { if (help_var >= &variables[TABLE_SIZE]) { help_var = variables; break; } index = strncmp(help_var->var_name, head, len); if (index < 0) continue; if (index > 0) { help_var = variables; break; } if (list_completion(help_var->var_name) == 0) break; } fl; return 1; } for (; var < &variables[TABLE_SIZE]; var++) { if (len == 0) index = 0; else index = strncmp(var->var_name, head, len); if (index < 0) continue; if (index > 0) break; sprintf(tail, "%s ", var->var_name + len); msg("%.70s", var_value(var, (char *) NULL)); gotoxy(prompt_length + vc_column, prompt_line); var++; return 1; } clrmsg(vc_column); return 0; } static struct var_stack { struct var_stack *next; struct variable_defs *v; int mod_flag; union { int ivar; int bool; char key; char *str; } value; } *var_stack = NULL, *vs_pool = NULL; void mark_var_stack(void) { register struct var_stack *vs; if (vs_pool) { vs = vs_pool; vs_pool = vs->next; } else vs = newobj(struct var_stack, 1); vs->next = var_stack; var_stack = vs; vs->v = NULL; } int push_variable(char *variable) { register struct variable_defs *var; register struct var_stack *vs; if (strncmp(variable, "no", 2) == 0) { variable += 2; if (variable[0] == '-') variable++; } if ((var = lookup_variable(variable)) == NULL) { msg("pushing unknown variable %s", variable); return 0; } mark_var_stack(); vs = var_stack; vs->v = var; vs->mod_flag = var->var_flags & V_MODIFIED; switch (VAR_TYPE) { case V_STRING: switch (VAR_OP) { case 0: /* if we update one of these variables, */ case 2: /* new storage will be allocated for it */ case 3: /* so it is ok just to save the pointer */ case 4: vs->value.str = STR_VAR; break; case 1: /* we free this memory when restored */ vs->value.str = copy_str(CBUF_VAR); break; } break; case V_BOOLEAN: vs->value.bool = BOOL_VAR; break; case V_INTEGER: vs->value.ivar = INT_VAR; break; case V_KEY: vs->value.key = KEY_VAR; break; case V_SPECIAL: msg("Cannot push pseudo variable %s", var->var_name); break; case V_CODES: msg("Cannot push code string variable %s", var->var_name); break; } return 1; } void restore_variables(void) { register struct variable_defs *var; register struct var_stack *vs, *vs1; vs = var_stack; while (vs != NULL) { if ((var = vs->v) == NULL) { var_stack = vs->next; vs->next = vs_pool; vs_pool = vs; return; } var->var_flags &= ~V_MODIFIED; var->var_flags |= vs->mod_flag; switch (VAR_TYPE) { case V_STRING: switch (VAR_OP) { case 0: /* only restore the string if changed; then * we */ case 2: /* can also free the memory occupied by the */ case 3: /* 'new' value (if not NULL) */ case 4: if (STR_VAR != vs->value.str) { if (STR_VAR != NULL) freeobj(STR_VAR); STR_VAR = vs->value.str; } break; case 1: /* it fitted before, so it will fit again */ strcpy(CBUF_VAR, vs->value.str); freeobj(vs->value.str); break; } break; case V_BOOLEAN: BOOL_VAR = vs->value.bool; break; case V_INTEGER: INT_VAR = vs->value.ivar; break; case V_KEY: KEY_VAR = vs->value.key; break; case V_SPECIAL: /* these are not saved, so... */ break; case V_CODES: break; } vs1 = vs->next; vs->next = vs_pool; vs_pool = vs; vs = vs1; } var_stack = NULL; } static int var_on_stack(register struct variable_defs * var) { register struct var_stack *vs; for (vs = var_stack; vs; vs = vs->next) if (vs->v == var) return 1; return 0; } void disp_variables(int all, char *rexp) { char *str, pushed; register struct variable_defs *var; if (in_init) return; clrdisp(); if (novice && !all && rexp == NULL) { msg("Use `:set all' to see all variable settings"); home(); } so_printxy(0, 0, "Variable settings"); pg_init(1, 1); if (rexp) { pg_regexp = regcomp(rexp + 1); all = 1; } for (var = variables; var < &variables[TABLE_SIZE]; var++) { if (pg_regexp != NULL && regexec(pg_regexp, var->var_name) == 0) continue; str = var_value(var, &pushed); if (!all && pushed == ' ') continue; if (pg_next() < 0) break; if (pg_new_regexp) { pg_new_regexp = 0; var = variables; var--; continue; } if (VAR_TYPE == V_STRING) tprintf("%c %-25.25s = \"%s\"\n", pushed, var->var_name, str); else tprintf("%c %-25.25s = %s\n", pushed, var->var_name, str); } pg_end(); } void print_variable_config(FILE * f, int all) { register struct variable_defs *var; char *str, tag[2]; tag[1] = NUL; for (var = variables; var < &variables[TABLE_SIZE]; var++) { if (!all && (var->var_flags & V_MODIFIED) == 0) continue; str = var_value(var, tag); if (var->var_name == "nntp-password" || var->var_name == "nntp-user") str = "XXXXXXXX"; fprintf(f, "%s%s='%s'\n", all ? tag : "", var->var_name, str); } } /* * var_options(string_var *, options, result *) * * test whether "string_var" contains any of the options * listed in "options" (NUL-separated list of names ended with * double NUL). On return, string_var is advanced over all * recognized options and the result will have the bits corresponding * to the recognized options set. */ void var_options(char **str, register char *options, flag_type * res) { char word[128]; char *optab[32]; register char **op, *wp, c; register int n; for (op = optab; *options != NUL; op++, options++) { *op = options; while (*options) options++; } *op = NULL; *res = 0; options = *str; while (*options) { for (wp = word; (c = *options) != NUL; *wp++ = c, options++) if (isascii(c) && isspace(c)) break; while (((c = *options)) && isascii(c) && isspace(c)) options++; *wp = NUL; for (op = optab, n = 1; *op != NULL; op++, n++) { if (strcmp(word, *op)) continue; *res |= FLAG(n); *str = options; break; } if (*op == NULL) break; } } nn-6.7.4/save.c0000644000175000017500000004613115213127113012654 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Save (or print) articles */ #include #include #include #include #include #include #include "config.h" #include "global.h" #include "db.h" #include "decode.h" #include "digest.h" #include "folder.h" #include "more.h" #include "news.h" #include "save.h" #include "nn_term.h" #include "unshar.h" /* save.c */ static char *ckdir_path(char *name); static int mkdirs_in_path(char *name, char *start); char *default_save_file = "+$F"; char *folder_save_file = NULL; int suggest_save_file = 1; char *unshar_header_file = "Unshar.Headers"; int use_mail_folders = 0; int use_path_in_from = 0; int use_mmdf_folders = 0; int save_report = 1; int quick_save = 0; int conf_append = 0; int conf_create = 1; int folder_format_check = 1; char *saved_header_escape = "~"; char *print_header_lines = "FDGS"; char *save_header_lines = "FDNS"; static char *short_header_lines; char *save_counter_format = "%d"; /* format of save counter */ int save_counter_offset = 0; char printer[FILENAME] = DEFAULT_PRINTER; int edit_print_command = 1; char patch_command[FILENAME] = "patch -p0"; int edit_patch_command = 1; char unshar_command[FILENAME] = SHELL; int edit_unshar_command = 0; extern char *temp_file; extern int shell_restrictions; extern char delayed_msg[]; extern int current_folder_type; extern int rot13_active; extern char *ctime(const time_t *); static int save_mode; static char *unshar_cmd; #define HEADER_HANDLING 0x0f /* what should we do with the header */ #define NO_HEADER 0 /* save without a header */ #define SHORT_HEADER 1 /* save with partial header */ #define FULL_HEADER 2 /* save with full header */ #define SHORT_HEADER_DG 3 /* save with partial header (digest) */ #define HEADER_ONLY 4 /* save with *only* the header */ int print_header_type = SHORT_HEADER; #define SEPARATE_FILES 0x0100 /* save as separate files */ #define UNIQUE_FILES 0x0200 /* save in unique files */ #define FILE_IS_NEW 0x0400 /* this is a new file */ #define APPEND_ARTNUM 0x0800 /* append article number to file name */ #define IS_PIPE 0x1000 /* output is on pipe */ #define DO_UNSHAR 0x2000 /* unshar article (or patch) */ #define DO_PATCH 0x4000 /* patch article */ #define DO_DECODE 0x8000 /* uudecode article */ /* open modes for open_news_article for the various HEADER_HANDLINGs */ static int open_mode[] = { SKIP_HEADER, FILL_NEWS_HEADER | SKIP_HEADER | GET_ALL_FIELDS, 0, FILL_NEWS_HEADER | FILL_DIGEST_HEADER | SKIP_HEADER | GET_ALL_FIELDS, FILL_NEWS_HEADER }; static FILE *save_file; /* output stream for saved files */ static char *save_name; /* save file name */ static int uniq_counter; /* separate files counter */ static char uniq_format[FILENAME]; /* sprintf format for '*' expansion */ static char last_dir[FILENAME] = ""; /* * return pointer to first path name component, that does not exist */ static char * ckdir_path(char *name) { char *slash; char *component; component = name; while ((slash = strchr(component, '/'))) { if (slash == component) { /* ...//... */ component++; continue; } *slash = NUL; if (file_exist(name, "drx")) { *slash++ = '/'; component = slash; continue; } if (file_exist(name, (char *) NULL)) { *slash = '/'; return NULL; } *slash = '/'; break; } return component; } /* * create directories in path name, starting from start */ static int mkdirs_in_path(char *name, char *start) { char *slash; char *component; component = start; while ((slash = strchr(component, '/'))) { if (slash == component) { /* ...//... */ component++; continue; } *slash = NUL; if (mkdir(name, 0755)) { msg("Cannot make %s/", name); *slash = '/'; return 0; } *slash++ = '/'; component = slash; } return 1; } static void set_folder_type(char *name) { FILE *f; current_folder_type = -1; if (!folder_format_check) return; if ((f = open_file(name, OPEN_READ)) != NULL) { get_folder_type(f); fclose(f); } } char * init_save(int command, char **mode_textp) { char *mode_text; static char last_input[FILENAME] = ""; static char name_buf[512]; /* buffer for file name expansion */ char *start, *last, *np; uniq_counter = 0; short_header_lines = save_header_lines; switch (command) { case K_SAVE_FULL_HEADER: save_mode = FULL_HEADER; mode_text = "Save"; goto cont_save; case K_SAVE_SHORT_HEADER: save_mode = SHORT_HEADER; mode_text = "Output"; goto cont_save; case K_SAVE_HEADER_ONLY: save_mode = HEADER_ONLY; mode_text = "Header_save"; goto cont_save; case K_SAVE_NO_HEADER: save_mode = NO_HEADER; mode_text = "Write"; cont_save: if (quick_save && (current_group->group_flag & G_FOLDER) == 0) { if (current_group->save_file) save_name = current_group->save_file; else save_name = default_save_file; strcpy(last_input, save_name); save_name = last_input; } else { prompt("\1%s on\1 (+~|) ", mode_text); save_name = current_group->save_file; if (save_name == NULL && suggest_save_file) save_name = (current_group->group_flag & G_FOLDER) ? folder_save_file : default_save_file; if (save_name != NULL) { if (!expand_file_name(name_buf, save_name, 2)) return NULL; save_name = name_buf; } save_name = get_s(last_input, save_name, NONE, file_completion); if (save_name == NULL || *save_name == NUL) return NULL; if (save_name[1] == NUL && save_name[0] == '+') save_name = (current_group->group_flag & G_FOLDER) ? folder_save_file : default_save_file; else if (current_group->save_file == NULL || strcmp(save_name, current_group->save_file)) strcpy(last_input, save_name); if (save_name == NULL || *save_name == NUL) return NULL; } if (*save_name == '|') { if (shell_restrictions) { msg("Restricted operation - pipes not allowed"); return NULL; } mode_text = "Pipe"; save_name++; save_mode |= IS_PIPE; if (*save_name == '|') { save_mode |= SEPARATE_FILES; save_name++; } } if (!expand_file_name(name_buf, save_name, (save_mode & IS_PIPE) ? 11 : 3)) return NULL; save_name = name_buf; if ((save_mode & IS_PIPE) == 0) { np = strrchr(save_name, '*'); if (np != NULL) { *np = NUL; sprintf(uniq_format, "%s%s%s", save_name, save_counter_format, np + 1); *np = '*'; save_mode |= SEPARATE_FILES | UNIQUE_FILES; } else { np = strrchr(save_name, '$'); if (np != NULL && np[1] == 'N') { if (current_group->group_flag & G_FOLDER) { msg("$N is not defined in a folder"); return NULL; } *np = NUL; sprintf(uniq_format, "%s%%ld%s", save_name, np + 2); *np = '$'; save_mode |= SEPARATE_FILES | APPEND_ARTNUM; } } } break; case K_UUDECODE: save_mode = NO_HEADER | DO_UNSHAR | DO_DECODE; mode_text = "Decode"; goto unshar1; case K_PATCH: save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR | DO_PATCH; mode_text = "Patch"; if (!shell_restrictions && edit_patch_command) { prompt("\1Patch command:\1 "); save_name = get_s(NONE, patch_command, NONE, NULL_FCT); if (save_name == NULL || *save_name == NUL) return NULL; strcpy(patch_command, save_name); } unshar_cmd = patch_command; goto unshar1; case K_UNSHAR: save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR; mode_text = "Unshar"; if (!shell_restrictions && edit_unshar_command) { prompt("\1Unshar command:\1 "); save_name = get_s(NONE, unshar_command, NONE, NULL_FCT); if (save_name == NULL || *save_name == NUL) return NULL; strcpy(unshar_command, save_name); } unshar_cmd = unshar_command; unshar1: prompt("\1%s Directory:\1 ", mode_text); save_name = current_group->save_file; if (save_name != NULL) { if (!expand_file_name(name_buf, save_name, 10)) return NULL; save_name = name_buf; } save_name = get_s(last_dir, save_name, NONE, file_completion); if (save_name == NULL || *save_name == NUL) return NULL; strcpy(last_dir, save_name); if (!expand_file_name(name_buf, save_name, 1)) return NULL; save_name = name_buf; break; case K_PRINT: if (print_header_type < NO_HEADER || print_header_type > FULL_HEADER) { msg("Invalid 'print-header-type' value %d", print_header_type); print_header_type = NO_HEADER; } short_header_lines = print_header_lines; save_mode = print_header_type | IS_PIPE; if (!shell_restrictions && edit_print_command) { prompt("\1Print command:\1 "); save_name = get_s(NONE, printer, NONE, NULL_FCT); if (save_name == NULL || *save_name == NUL) return NULL; strcpy(printer, save_name); } if (!expand_file_name(name_buf, printer, 1)) return NULL; save_name = name_buf; mode_text = "Print"; break; default: msg("Illegal save command: %d", command); return NULL; } if (save_name == NULL) return NULL; if (!(save_mode & IS_PIPE)) { if (file_exist(save_name, (save_mode & DO_UNSHAR) ? "wd" : "wf")) { if (save_mode & DO_UNSHAR) { int len = strlen(save_name); if (save_name[len - 1] != '/') strcpy(save_name + len, "/"); } else if (conf_append) { tprintf("\rAppend to: %s ? ", save_name); clrline(); if (!yes(0)) return NULL; } } else { if (errno != ENOENT) { msg("Cannot access %s", save_name); return NULL; } if (save_mode & DO_UNSHAR) { int len = strlen(save_name); if (save_name[len - 1] != '/') strcpy(save_name + len, "/"); } start = ckdir_path(save_name); if (start == NULL) { msg("No permission"); return NULL; } last = strrchr(start, '/'); /* last != NULL => non-existing directories */ if (conf_create && (!(save_mode & SEPARATE_FILES) || last)) { tprintf("\rCreate "); for (np = save_name; *np; np++) { if (np == start) tputc('\"'); tputc(*np); if ((save_mode & SEPARATE_FILES) && np == last) break; } tprintf("\" ?"); clrline(); if (yes(last != NULL) <= 0) return NULL; } if (last && !mkdirs_in_path(save_name, start)) return NULL; } } if (mode_textp) *mode_textp = mode_text; save_mode |= FILE_IS_NEW; /* so save() will open it */ if (save_mode & DO_DECODE) { uud_start(save_name); save_mode &= ~DO_UNSHAR; } return save_name; } int save(article_header * ah) { register FILE *art; register int c, lcount = 0, mode; news_header_buffer hdrbuf; news_header_buffer dghdrbuf; int was_raw = 0, set_visual = 0; char copybuf[FILENAME * 4], uniqbuf[FILENAME]; flag_type nn_st_flag = A_ST_FILED; int with_header; if (ah->a_group) init_group(ah->a_group); mode = save_mode & HEADER_HANDLING; if (mode == SHORT_HEADER && ah->flag & A_DIGEST) mode = SHORT_HEADER_DG; c = open_mode[mode]; if (use_mail_folders && use_path_in_from && (c & FILL_NEWS_HEADER) == 0) c |= FILL_NEWS_HEADER | GET_ALL_FIELDS; art = open_news_article(ah, c, hdrbuf, dghdrbuf); if (art == NULL) { msg("Cannot read %s", group_path_name); return 0; } if (save_mode & DO_DECODE) { save_file = NULL; c = uudecode(ah, art); fclose(art); return (c < 0) ? 0 : 1; } if (save_mode & UNIQUE_FILES) { uniqbuf[0] = NUL; do { strcpy(copybuf, uniqbuf); uniq_counter++; sprintf(uniqbuf, uniq_format, uniq_counter + save_counter_offset); if (strcmp(uniqbuf, copybuf) == 0) { msg("save-counter \"%s\" does not generate unique file names", save_counter_format); goto fatal; } } while (file_exist(uniqbuf, (char *) NULL)); save_name = uniqbuf; save_mode |= FILE_IS_NEW; } else if (save_mode & APPEND_ARTNUM) { sprintf(uniqbuf, uniq_format, (long) (ah->a_number)); save_name = uniqbuf; } if (save_mode & FILE_IS_NEW) { if (save_mode & (IS_PIPE | DO_UNSHAR)) { set_visual = 1; was_raw = visual_off(); } if (save_mode & IS_PIPE) { if ((save_file = popen(save_name, "w")) == NULL) { msg("Cannot pipe to %s", save_name); goto fatal; } } else if (save_mode & DO_UNSHAR) { if ((save_mode & DO_PATCH) == 0) { if (!unshar_position(art)) goto fatal; if (unshar_header_file) store_header(ah, art, save_name, unshar_header_file); } new_temp_file(); sprintf(copybuf, "cd %s && { %s 2>&1 ; } | tee %s ; cat %s >> %s.Result ; rm %s", save_name != NULL ? save_name : ".", unshar_cmd, temp_file, temp_file, (save_mode & DO_PATCH) ? "Patch" : "Unshar", temp_file); save_file = popen(copybuf, "w"); if (save_file == NULL) { msg("Cannot exec: '%s'", copybuf); goto fatal; } tprintf("\r\n%s %s\r\n", save_mode & DO_PATCH ? "PATCHING FROM" : "UNPACKING", ah->subject ? ah->subject : "ARTICLE"); fl; } else { if ((save_file = open_file(save_name, OPEN_APPEND)) == NULL) { msg("Cannot write %s", save_name); fclose(art); return 0; } current_folder_type = -1; if (ftell(save_file) != (off_t) 0) { if (mode != NO_HEADER) set_folder_type(save_name); save_mode &= ~FILE_IS_NEW; } } } clrline(); s_pipe = 0; with_header = mode != NO_HEADER && (save_mode & (IS_PIPE | DO_UNSHAR)) == 0; if (with_header) mailbox_format(save_file, 1); if (mode == FULL_HEADER || mode == HEADER_ONLY) { off_t cnt = ah->fpos - ah->hpos; while (--cnt >= 0) { if ((c = getc(art)) == EOF) break; putc(c, save_file); } } else if (mode == SHORT_HEADER || mode == SHORT_HEADER_DG) { char *name, *val; scan_header_fields(short_header_lines, ah); while (next_header_field(&name, &val, (fct_type *) NULL)) { if (name == NULL) continue; fprintf(save_file, "%s: %s\n", name, val); } fputc(NL, save_file); } fflush(save_file); if (s_pipe) goto broken_pipe; if (mode != HEADER_ONLY) { lcount = 0; while (ftell(art) < ah->lpos && fgets(copybuf, 512, art)) { lcount++; if (rot13_active) rot13_line(copybuf); if (saved_header_escape && with_header && is_header_line(copybuf)) fputs(saved_header_escape, save_file); fputs(copybuf, save_file); if (s_pipe) goto broken_pipe; } } if (with_header) lcount += mailbox_format(save_file, 0); broken_pipe: fclose(art); if (save_mode & DO_UNSHAR) { if ((c = pclose(save_file)) != 0) { sprintf(delayed_msg, "Save command failed; exit = %d", c); nn_st_flag = 0; } save_file = NULL; } else { if (s_pipe) msg("Command did not read complete article!"); else if (save_report) msg((save_mode & IS_PIPE) ? "%s: %d lines piped" : (save_mode & FILE_IS_NEW) ? "%s created: %d lines written" : "%s: %d lines appended", save_name, lcount); if (s_pipe || (save_mode & SEPARATE_FILES)) { if (end_save() == 0) goto err; } else save_mode &= ~FILE_IS_NEW; } #ifdef MAIL_READING if (mail_auto_delete && (save_mode & IS_PIPE) == 0) if (current_group->group_flag & G_MAILBOX) if (ah->attr != A_CANCEL) fcancel(ah); #endif if (set_visual) visual_on(); if (was_raw) nn_raw(); ah->flag |= nn_st_flag; return !s_pipe || (save_mode & SEPARATE_FILES); fatal: fclose(art); err: if (set_visual) visual_on(); if (was_raw) nn_raw(); return 0; } int end_save(void) { int c; FILE *sf; sf = save_file; save_file = NULL; if (sf) { if (save_mode & (IS_PIPE | DO_UNSHAR)) { if ((c = pclose(sf)) != 0) { if (c >= 256) c >>= 8; /* HACK */ sprintf(delayed_msg, "Save command failed; exit = %d", c); return 0; } } else if (fclose(sf) == EOF) { sprintf(delayed_msg, "Save failed (disk full?)"); return 0; } } if (save_mode & DO_DECODE) { uud_end(); } if (save_mode & DO_UNSHAR) sprintf(delayed_msg, "Output is saved in %s/%s.Result", save_name != NULL ? save_name : ".", (save_mode & DO_PATCH) ? "Patch" : "Unshar"); return 1; } void store_header(article_header * ah, FILE * f, char *dir, char *file) { register int c; off_t endpos; FILE *h; if (dir != (char *) NULL && file[0] != '/') file = relative(dir, file); if ((h = open_file(file, OPEN_APPEND)) == NULL) { msg("Cannot open %s", file); return; } fseek(h, 0, 2); if (ftell(h) > 0) set_folder_type(file); else current_folder_type = -1; if (!use_mmdf_folders && ftell(h) > 0) putc(NL, h); /* just in case */ mailbox_format(h, 1); endpos = ftell(f) - ah->hpos; fseek(f, ah->hpos, 0); while (--endpos >= 0 && (c = getc(f)) != EOF) putc(c, h); mailbox_format(h, 0); fclose(h); } int mailbox_format(FILE * f, int top) { time_t now; int do_mmdf, do_mail; do_mmdf = do_mail = 0; switch (current_folder_type) { case 0: break; case 1: do_mail = 1; break; case 2: do_mmdf = 1; break; default: do_mmdf = use_mmdf_folders; do_mail = use_mail_folders; break; } if (do_mmdf) { fprintf(f, "\001\001\001\001\n"); return 0; } if (top == 0) { fputc(NL, f); return 1; } if (top > 0 && do_mail) { now = cur_time(); fprintf(f, "From %s %s", (use_path_in_from && news.ng_path) ? news.ng_path : current_group->group_name, ctime(&now)); return 1; } return 0; } char * run_mkdir(char *dir, char *name_buf) { if (dir == NULL) { prompt("\1Directory: \1"); dir = get_s(last_dir, NONE, NONE, file_completion); if (dir == NULL || *dir == NUL) return NULL; strcpy(last_dir, dir); } if (*dir == '+' || *dir == '~') { if (!expand_file_name(name_buf, dir, 1)) return NULL; dir = name_buf; } if (file_exist(dir, (char *) NULL)) { msg("Directory %s already exists", dir); return NULL; } if (mkdir(dir, 0755)) { msg("Cannot make %s", dir); return NULL; } return dir; } int change_dir(char *dir, int in_init) { char dir_buf[FILENAME]; if (dir == NULL) { prompt("\1Directory: \1"); dir = get_s(last_dir, NONE, NONE, file_completion); } if (dir == NULL || *dir == NUL) return 0; strcpy(last_dir, dir); if (*dir == '+' || *dir == '~') { if (!expand_file_name(dir_buf, dir, 1)) return in_init; dir = dir_buf; } if (chdir(dir) == 0) { if (!in_init) msg("Directory: %s", dir); return 0; } if (in_init) return 1; if (errno == EACCES) msg("Cannot access directory %s", dir); else { /* should ask and create directory here */ msg("Directory not found: %s", dir); } return 0; } nn-6.7.4/db.c0000644000175000017500000013413315213127117012307 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Database access and update */ #include #include #include #include #include #include #include "config.h" #include "global.h" #include "answer.h" #include "articles.h" #include "db.h" #include "libnov.h" #include "nntp.h" #include "nn_term.h" #include "pack_date.h" #include "pack_name.h" #include "pack_subject.h" #ifdef NOV #include "hash.h" #include "newsoverview.h" #endif /* NOV */ /* db.c */ #ifndef NOV static char *mk_archive_file(register group_header *, register char *, int); static void db_read_master(void); static void db_read_group(register group_header *); #endif /* !NOV */ static int sort_gh(group_header ** g1, group_header ** g2); struct novgroup *nntp_get_overview(group_header *, article_number, article_number); #ifdef NOV static void db_init_group(group_header * gh, int num); static void db_init_active(group_header * gh); static void readtimfile(void); static void readactfile(void); static void db_fixup_cross_postings(data_header * dhp, data_dynamic_data * ddp, struct novart * artp); static struct novgroup *ngovp; static struct novart *allarts; static char **actlist, **grplist; static HASHTABLE *timtbl; #endif /* NOV */ #ifdef CACHE_PURPOSE struct purp_list { char *group_name; char *purpose; } *purp_list; static void cache_purpose(void); static int purp_cnt; #endif /* CACHE_PURPOSE */ /* * String pool allocation */ /* Badly broken. Fix ASAP */ typedef struct stlisthdr { struct stlist *next; } stlisthdr_t; typedef struct stlist { stlisthdr_t n; char str[1]; } stlist_t; #if defined(NOV) || defined(CACHE_PURPOSE) static char *strkeep(char *s, int hdr, int poolid); static char *pmalloc(int size, int poolid); static void pfree(int poolid); #endif #define POOL_GRP 0 #define POOL_PUR 1 #define POOL_ACT 2 #define POOL_TIM 3 #define POOL_MAX 3 #define POOL_TMP 4 #define RCX_NEVER 0 extern char *master_directory, *db_directory, *db_data_directory, *news_directory, *news_lib_directory; extern int db_data_subdirs, new_group_action; master_header master; #ifdef MALLOC_64K_LIMITATION group_header **active_groups = NULL; #else group_header *active_groups = NULL; #endif /* MALLOC_64K_LIMITATION */ group_header **sorted_groups = NULL; int reread_groups_file = 0; /* nnmaster -G */ int check_group_access = 0; data_header db_hdr; data_dynamic_data db_data; int32 db_read_counter = 0; /* articles read by db_read_art */ int32 db_write_counter = 0; /* articles written by db_write_art */ /* * Init access to a group */ group_header *current_group = NULL; char group_path_name[FILENAME]; char *group_file_name = NULL; static char *group_position = NULL; static article_number current_digest_article = 0; int l_g_index, s_g_first; int init_group(register group_header * gh) { register char *p, *q; current_digest_article = 0; if (gh == NULL) return 0; /* if (gh->master_flag & M_IGNORE_GROUP) return 0; *//* OBS */ if (gh == current_group) return 1; current_group = gh; if (gh->group_flag & G_FOLDER) { group_position = NULL; group_file_name = NULL; strcpy(group_path_name, gh->archive_file); return 1; } #ifdef NNTP if (use_nntp && nntp_set_group(gh) < 0) return 0; #endif /* NNTP */ if (group_position == NULL) { if (who_am_i == I_AM_MASTER || who_am_i == I_AM_EXPIRE) { group_position = group_path_name; } else { strcpy(group_path_name, news_directory); group_position = group_path_name + strlen(group_path_name); *group_position++ = '/'; } } for (p = group_position, q = gh->group_name; *q; q++) *p++ = (*q == '.') ? '/' : *q; if (who_am_i == I_AM_MASTER) { /* The master will chdir to the group's directory to get */ /* better performance (can use relative path names). */ *p++ = NUL; if (!use_nntp) { if (chdir(news_directory) < 0) sys_error(news_directory); if (chdir(group_path_name) < 0) return 0; } group_file_name = group_path_name; return 1; } /* client */ if (gh->master_flag & M_NO_DIRECTORY) return 0; if (check_group_access && !use_nntp) { *p = NUL; if (file_exist(group_path_name, "dxr") == 0) return 0; } *p++ = '/'; *p = NUL; group_file_name = p; return 1; } #ifndef NOV /* * Open master & group file; read it in if first open. * * GROUPS file format is: * * One line per group: * [] [] * If is omitted, a timestamp of 0 is used (very old group). * If is "@", the master entry is ignored. * * : * D Digest all articles in the group * N Never digest articles * @ Bogus group, ignore completely * ! Don't collect this group * * Do not edit the GROUPS file while nnmaster is running. * After editing the groups file (options), run nnmaster -G. */ static FILE *group_file = NULL; int open_groups(int mode) { group_file = open_file(relative(db_directory, "GROUPS"), mode); if (group_file != NULL && (mode & OPEN_CREATE)) { fprintf(group_file, "#\n#\tNEVER MODIFY THIS FILE WHILE nnmaster IS RUNNING\n"); fprintf(group_file, "#\n#\tRUN 'nnmaster -G' AFTER MODIFYING THIS FILE\n"); fprintf(group_file, "#\n#\tDO NOT REMOVE OR REORDER ANY LINES IN THIS FILE\n#\n"); } return group_file != NULL; } void close_groups(void) { if (group_file != NULL) { fclose(group_file); group_file = NULL; } } static void db_append_group(register group_header * gh) { char flags[16], *fp; if (gh->group_name[0] == NUL) { fputc('@', group_file); goto out; } fprintf(group_file, "%s", gh->group_name); if (gh->creation_time > 0) fprintf(group_file, " %ld", (long) (gh->creation_time)); fp = flags; if (gh->master_flag & M_IGNORE_G) *fp++ = '!'; if (gh->master_flag & M_ALWAYS_DIGEST) *fp++ = 'D'; if (gh->master_flag & M_NEVER_DIGEST) *fp++ = 'N'; if (gh->master_flag & M_INCLUDE_OLD) *fp++ = 'O'; if (gh->master_flag & M_AUTO_RECOLLECT) *fp++ = 'R'; if (gh->archive_file != NULL) *fp++ = '>'; if (fp != flags) { *fp++ = NUL; fprintf(group_file, " %s%s", flags, gh->archive_file != NULL ? gh->archive_file : ""); } out: fputc(NL, group_file); } void db_rewrite_groups(int save_groups, group_header * group_list) { register group_header *gh; if (save_groups) if (save_old_file(relative(db_directory, "GROUPS"), "~") < 0) sys_error("Cannot rename GROUPS file"); open_groups(OPEN_CREATE | MUST_EXIST); if (group_list != NULL) { for (gh = group_list->next_group; gh != NULL; gh = gh->next_group) db_append_group(gh); } else { Loop_Groups_Header(gh) { db_append_group(gh); } } close_groups(); } static char * mk_archive_file(register group_header * gh, register char *cp, int logerr) { char *name; while (*cp && (*cp == '>' || isspace(*cp))) cp++; name = cp; while (*cp && !isspace(*cp)) cp++; if (*cp) *cp++ = NUL; if (*name) { gh->archive_file = copy_str(name); if (*name == '/') gh->master_flag |= M_AUTO_ARCHIVE; else { gh->master_flag &= ~M_AUTO_ARCHIVE; if (who_am_i == I_AM_MASTER) { if (logerr) log_entry('E', "GROUPS %s >%s: Full path required", gh->group_name, name); else printf("Error in GROUPS: %s >%s: Full path required\n", gh->group_name, name); } } } return cp; } int db_parse_group(register group_header * gh, int trust_master) /* trust_master trust what is in the master file */ { char line[256]; register char *cp, *name; int ignore; do { if (fgets(line, 256, group_file) == NULL) return 0; for (cp = line; *cp && isspace(*cp); cp++); } while (*cp == NUL || *cp == '#'); gh->archive_file = NULL; name = cp; if (trust_master) { if (gh->group_name_length == 0) { gh->group_name = ""; return 1; } cp = name + gh->group_name_length; if (*cp == NUL || !isspace(*cp)) sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line); } else { /* parse GROUPS line */ if (*cp == '@') { ignore = 1; gh->group_name_length = 0; gh->group_name = ""; goto ignore_group; } if (gh->group_name_length == 0) { while (*cp && !isspace(*cp)) cp++; gh->group_name_length = cp - name; } else { cp = name + gh->group_name_length; if (*cp == NUL || !isspace(*cp)) { sys_error("MASTER/GROUPS conflict: %d/%s", gh->group_num, line); } } } if (*cp) *cp++ = NUL; if (gh->group_name_length > 0) gh->group_name = copy_str(name); else gh->group_name = ""; if (trust_master) { if (gh->master_flag & M_AUTO_ARCHIVE) { while (*cp && *cp != '>') cp++; if (*cp == '>') mk_archive_file(gh, cp, 1); } return 1; } while (*cp && isspace(*cp)) cp++; if (*cp && isdigit(*cp)) { gh->creation_time = atol(cp); while (*cp && isdigit(*cp)) cp++; } else gh->creation_time = 0; while (*cp && isspace(*cp)) cp++; ignore = 0; gh->master_flag &= ~(M_ALWAYS_DIGEST | M_NEVER_DIGEST | M_AUTO_RECOLLECT | M_AUTO_ARCHIVE); while (*cp) { switch (*cp++) { case ' ': case '\t': case NL: case CR: continue; case 'D': /* Collect this group, digest all articles */ gh->master_flag |= M_ALWAYS_DIGEST; continue; case 'N': /* Collect this group, never digest articles */ gh->master_flag |= M_NEVER_DIGEST; continue; case 'O': /* Ignore -O option for this group */ gh->master_flag |= M_INCLUDE_OLD; continue; case 'R': /* Recollect this group when new articles * arrive */ gh->master_flag |= M_AUTO_RECOLLECT; continue; case '>': /* Archive all new articles in * gh->archive_file */ cp = mk_archive_file(gh, cp, 0); continue; case '@': /* Bogus GROUP -- ignore completely */ ignore = 1; gh->group_name_length = 0; break; case '!': /* Do not collect this group */ case 'X': ignore = 1; break; case '#': /* comment */ *cp = NUL; break; default: printf("Bad GROUPS flag for %s: `%c'\n", gh->group_name, *--cp); break; } break; } ignore_group: /* G_DONE indicates to master that the group must be cleaned */ if (ignore) { if ((gh->master_flag & M_IGNORE_GROUP) == 0) { gh->master_flag |= M_MUST_CLEAN; log_entry('X', "Group %s ignored", gh->group_name); } gh->master_flag |= M_IGNORE_G; } else { /* was group ignored in GROUPS, but not * active before? */ if ((gh->master_flag & M_IGNORE_GROUP) == M_IGNORE_G) { gh->master_flag &= ~M_NO_DIRECTORY; log_entry('X', "Group %s activated", gh->group_name); } gh->master_flag &= ~M_IGNORE_G; } return 1; } static FILE *master_file = NULL; static int db_sequential = 0; #ifdef APOLLO_DOMAIN_OS /* make copy of master file to CLIENT, but only once every 100 passes or */ /* if forced */ static int make_master_copy(int force_copy) { char client_path[FILENAME]; static int pass_count = 0; int n; if (!force_copy) { /* if not forced copy, only do copy every * %100 passes */ pass_count++; if (pass_count < 100) { return; } } pass_count = 0; strcpy(client_path, relative(db_directory, "CLIENT")); unlink(client_path); if ((n = copy_file(relative(db_directory, "MASTER"), client_path, 0)) < 0) log_entry('R', "Copy of MASTER to CLIENT failed (err=%d)", n); } #endif /* APOLLO_DOMAIN_OS */ #endif /* !NOV */ #ifdef NOV /* * Init the groups data from active file. */ void open_master(int mode) { register group_header *gh; freeobj(sorted_groups); freeobj(active_groups); active_groups = NULL; sorted_groups = NULL; readactfile(); /* Read in the active file - count groups */ readtimfile(); /* Read the newsgroup creation time file */ db_expand_master(); /* uses count from readact() call! */ Loop_Groups_Header(gh) { /* db_read_group opens a file per call; use db_init_group instead */ db_init_group(gh, l_g_index); } sort_groups(); Loop_Groups_Header(gh) { db_init_active(gh); } /* * Free actlist and timtbl space. Don't free grplist, because * active_groups[] has pointers thereto. */ pfree(POOL_ACT); free(actlist); pfree(POOL_TIM); free(timtbl); #ifdef CACHE_PURPOSE cache_purpose(); /* cache sorted newsgroups and descriptions */ #endif /* CACHE_PURPOSE */ } #else /* NOV */ /* * Open master & group files; read then in if first open. */ void open_master(int mode) { register group_header *gh; int trust_master; close_master(); #ifdef APOLLO_DOMAIN_OS if (who_am_i != I_AM_MASTER && who_am_i != I_AM_ADMIN) master_file = open_file(relative(db_directory, "CLIENT"), mode | MUST_EXIST); else #endif /* APOLLO_DOMAIN_OS */ master_file = open_file(relative(db_directory, "MASTER"), mode | MUST_EXIST); db_sequential = 0; if (mode == OPEN_CREATE) db_sequential = 1; if (mode != OPEN_READ) return; db_read_master(); if (who_am_i != I_AM_MASTER && master.db_lock[0]) nn_exitmsg(88, "DATABASE LOCKED.\n%s\n", master.db_lock); freeobj(sorted_groups); #ifdef MALLOC_64K_LIMITATION if (active_groups) Loop_Groups_Header(gh) freeobj(gh); #endif /* MALLOC_64K_LIMITATION */ freeobj(active_groups); active_groups = NULL; sorted_groups = NULL; db_expand_master(); open_groups(OPEN_READ | MUST_EXIST); trust_master = (who_am_i != I_AM_MASTER || !reread_groups_file); db_sequential = 1; #ifdef MALLOC_64K_LIMITATION Loop_Groups_Number(l_g_index) { gh = newobj(group_header, 1); active_groups[l_g_index] = gh; #else Loop_Groups_Header(gh) { #endif /* MALLOC_64K_LIMITATION */ gh->group_num = l_g_index; db_read_group(gh); db_parse_group(gh, trust_master); } db_sequential = 0; close_groups(); sort_groups(); #ifdef CACHE_PURPOSE cache_purpose(); /* cache sorted newsgroups and descriptions */ #endif /* CACHE_PURPOSE */ } #endif /* NOV */ void db_expand_master(void) { master.free_groups = 20; #ifndef NOV #ifdef MALLOC_64K_LIMITATION active_groups = resizeobj(active_groups, group_header *, master.number_of_groups + master.free_groups); #else /* MALLOC_64K_LIMITATION */ active_groups = resizeobj(active_groups, group_header, master.number_of_groups + master.free_groups); clearobj(active_groups + master.number_of_groups, group_header, master.free_groups); #endif /* MALLOC_64K_LIMITATION */ #else /* !NOV */ active_groups = resizeobj(active_groups, group_header, master.number_of_groups + master.free_groups); clearobj(active_groups + master.number_of_groups, group_header, master.free_groups); #endif /* !NOV */ sorted_groups = resizeobj(sorted_groups, group_header *, master.number_of_groups + master.free_groups); } #if 0 #ifdef NOV static void freeup(char *key, char *data, char *hook) { hashdelete((HASHTABLE *) data, key); free(key); free(data); } #endif /* NOV */ #endif /* 0 */ void close_master(void) { #ifdef NOV #if 0 if (ngovp) { novclose(ngovp); ngovp = NULL; } #endif /* 0 */ #else /* NOV */ if (master_file != NULL) { fclose(master_file); master_file = NULL; #ifdef APOLLO_DOMAIN_OS if (who_am_i == I_AM_MASTER) make_master_copy(1); #endif /* APOLLO_DOMAIN_OS */ } #endif /* NOV */ } int update_group(group_header * gh) { #ifndef NOV group_number numg; numg = master.number_of_groups; db_read_master(); master.number_of_groups = numg; if (master.db_lock[0]) return -3; #endif /* !NOV */ #ifndef NOV db_read_group(gh); #else db_read_group(gh, gh->first_a_article, gh->last_a_article); /* XXX */ #endif /* !NOV */ if (gh->master_flag & M_IGNORE_GROUP) return 0; if (gh->master_flag & M_BLOCKED) return -1; return 1; } static int sort_gh(group_header ** g1, group_header ** g2) { return strcmp((*g1)->group_name, (*g2)->group_name); } void sort_groups(void) { register group_header *gh; Loop_Groups_Header(gh) sorted_groups[l_g_index] = gh; quicksort(sorted_groups, master.number_of_groups, group_header *, sort_gh); s_g_first = 0; Loop_Groups_Sorted(gh) if (gh->group_name[0] != NUL) { s_g_first = l_g_index; break; } } group_header * lookup_no_alias(char *name) { register int i, j, k, t; i = s_g_first; j = master.number_of_groups - 1; while (i <= j) { k = (i + j) / 2; if ((t = strcmp(name, sorted_groups[k]->group_name)) > 0) i = k + 1; else if (t < 0) j = k - 1; else return sorted_groups[k]; } return NULL; } group_header * lookup(char *name) { register group_header *gh; group_header *gh_na; register int32 n, x; gh = lookup_no_alias(name); if (gh == NULL || (gh->master_flag & M_ALIASED) == 0) return gh; gh_na = gh; x = 16; do { if (--x == 0) { log_entry('R', "Possible alias loop: %s", name); return gh_na; } n = (int32) gh->data_write_offset; /* * if alias info is unreliable, return original group which will be * ignored anyway */ if (n < 0 || n >= master.number_of_groups) { log_entry('R', "Bad aliasing of %s -> %d", gh->group_name, n); return gh_na; } gh = ACTIVE_GROUP(n); } while (gh->master_flag & M_ALIASED); return gh; } int art_collected(group_header * gh, article_number art_num) { return gh->first_db_article <= art_num && gh->last_db_article >= art_num; } #ifndef NOV char * db_data_path(char *namebuf, group_header * gh, char d_or_x) { register char *cp, *np; if (db_data_directory != NULL) { #ifdef DB_LONG_NAMES sprintf(namebuf, "%s/%s.%c", db_data_directory, gh->group_name, d_or_x); #else if (db_data_subdirs) sprintf(namebuf, "%s/%ld/%ld.%c", db_data_directory, gh->group_num / 100, gh->group_num, d_or_x); else sprintf(namebuf, "%s/%ld.%c", db_data_directory, gh->group_num, d_or_x); #endif /* DB_LONG_NAMES */ } else { np = namebuf; /* master chdir to the group's directory */ if (who_am_i != I_AM_MASTER) { for (cp = news_directory; (*np = *cp++); np++); *np++ = '/'; for (cp = gh->group_name; *cp; cp++) *np++ = *cp == '.' ? '/' : *cp; *np++ = '/'; } *np++ = '.'; *np++ = 'n'; *np++ = 'n'; *np++ = d_or_x; *np++ = NUL; } return namebuf; } /* STUB */ FILE * open_data_file(group_header * gh, char d_or_x, int mode) { FILE *f; char data_file[FILENAME]; db_data_path(data_file, gh, d_or_x); if (mode == -1) { if (unlink(data_file) < 0 && errno != ENOTDIR && errno != ENOENT) log_entry('E', "Cannot unlink %s (errno=%d)", data_file, errno); f = NULL; } else { again: f = open_file(data_file, (mode & ~MUST_EXIST)); if (f != NULL) return f; #ifndef DB_LONG_NAMES if (db_data_subdirs && (mode & 0xf) == OPEN_CREATE && errno == ENOENT) { char *s; s = strrchr(data_file, '/'); *s = NUL; if (!file_exist(data_file, "dx")) { if (mkdir(data_file, 0755) < 0) sys_error("Cannot create directory %s", data_file); log_entry('C', "Created directory %s", data_file); *s = '/'; goto again; } *s = '/'; errno = ENOENT; } #endif /* DB_LONG_NAMES */ if (mode & MUST_EXIST) sys_error("%s (%d): cannot open '%c' file (mode=%x, errno=%d)", gh->group_name, (int) (gh->group_num), d_or_x, mode, errno); } return f; } #ifdef NETWORK_DATABASE #define MASTER_FIELDS 5 /* + DB_LOCK_MESSAGE bytes */ #define GROUP_FIELDS 9 #define ARTICLE_FIELDS 10 typedef int32 net_long; #ifdef NETWORK_BYTE_ORDER #define net_to_host(buf, n) #define host_to_net(buf, n) #else static int net_to_host(register net_long * buf, int lgt) { while (--lgt >= 0) { *buf = ntohl(*buf); buf++; } } static int host_to_net(register net_long * buf, int lgt) { while (--lgt >= 0) { *buf = htonl(*buf); buf++; } } #endif /* not NETWORK_BYTE_ORDER */ #endif /* NETWORK_DATABASE */ #define NWDB_MAGIC 0x00190000 /* NN#n <-> NW#n */ static void db_read_master(void) { #ifdef NETWORK_DATABASE net_long buf[MASTER_FIELDS]; rewind(master_file); if (fread((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file) != MASTER_FIELDS) goto err; if (fread(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file) != DB_LOCK_MESSAGE) goto err; net_to_host(buf, MASTER_FIELDS); master.db_magic = buf[0] ^ NWDB_MAGIC; master.last_scan = buf[1]; master.last_size = buf[2]; master.number_of_groups = buf[3]; master.db_created = buf[4]; #else rewind(master_file); if (fread((char *) &master, sizeof(master_header), 1, master_file) != 1) goto err; #endif /* NETWORK_DATABASE */ if (master.db_magic != NNDB_MAGIC) sys_error("Database magic number mismatch"); return; err: sys_error("Incomplete MASTER file"); } #endif /* NOV */ #ifdef NOV static void readactfile(void) { char actline[512]; int count = 0; int i; FILE *actfp; stlist_t *sthead, *stp = NULL; if (actlist != NULL) return; #ifdef NNTP if (use_nntp) { actfp = nntp_fopen_list("LIST"); } else #endif /* NNTP */ actfp = fopen(relative(news_lib_directory, "active"), "r"); if (actfp == NULL) { nn_exitmsg(1, "could not fetch active file\n"); } /* * Snarf all of active up in first pass. This gives us a count of the * groups we can use for internal tables. */ sthead = NULL; #ifdef NNTP while (use_nntp ? nntp_fgets(actline, sizeof actline) : fgets(actline, sizeof actline, actfp)) #else while (fgets(actline, sizeof actline, actfp)) #endif /* NNTP */ { stlist_t *stnew = (stlist_t *) strkeep(actline, sizeof(stlisthdr_t), POOL_ACT); if (stnew == NULL) { nn_exitmsg(1, "out of mem for active file (at line %d)\n", count + 1); } if (sthead != NULL) { stp->n.next = stnew; stp = stnew; } else { sthead = stnew; stp = sthead; } count++; } stp->n.next = NULL; if (!use_nntp) (void) fclose(actfp); actlist = (char **) calloc(count + 1, sizeof(char *)); grplist = (char **) calloc(count + 1, sizeof(char *)); if (grplist == NULL) { nn_exitmsg(1, "can't create active or group list (%d entries)\n", count + 1); } /* * Second pass (in core): Put active lines and group names into string * arrays. */ for (i = 0, stp = sthead; stp && i < count; stp = stp->n.next, i++) { char *p = strchr(stp->str, ' '); if (p == NULL) actlist[i] = NULL; else { *p++ = NUL; actlist[i] = p; } grplist[i] = strkeep(stp->str, 0, POOL_GRP); } actlist[count] = NULL; grplist[count] = NULL; /* init the master struct */ clearobj(&master, sizeof(master), 1); master.number_of_groups = count; } #endif /* NOV */ #ifndef NOV void db_write_master(void) { #ifdef NETWORK_DATABASE net_long buf[MASTER_FIELDS]; buf[0] = master.db_magic ^ NWDB_MAGIC; buf[1] = master.last_scan; buf[2] = master.last_size; buf[3] = master.number_of_groups; buf[4] = master.db_created; host_to_net(buf, MASTER_FIELDS); rewind(master_file); if (fwrite((char *) buf, sizeof(net_long), MASTER_FIELDS, master_file) != MASTER_FIELDS) goto err; if (fwrite(master.db_lock, sizeof(char), DB_LOCK_MESSAGE, master_file) != DB_LOCK_MESSAGE) goto err; #else rewind(master_file); if (fwrite((char *) &master, sizeof(master_header), 1, master_file) != 1) goto err; #endif /* NETWORK_DATABASE */ fflush(master_file); #ifdef APOLLO_DOMAIN_OS if (who_am_i == I_AM_MASTER) make_master_copy(0); #endif /* APOLLO_DOMAIN_OS */ return; err: sys_error("Write to MASTER failed"); } static void db_read_group(register group_header * gh) { #ifdef NETWORK_DATABASE net_long buf[GROUP_FIELDS]; if (!db_sequential) fseek(master_file, (off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE + GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0); if (fread((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS) goto err; net_to_host(buf, GROUP_FIELDS); gh->first_db_article = buf[0]; gh->last_db_article = buf[1]; gh->index_write_offset = buf[2]; gh->data_write_offset = buf[3]; gh->group_name_length = buf[4]; gh->master_flag = buf[5]; gh->first_a_article = buf[6]; gh->last_a_article = buf[7]; gh->creation_time = buf[8]; #else if (!db_sequential) fseek(master_file, sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0); if (fread((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1) goto err; #endif /* NETWORK_DATABASE */ return; err: sys_error("Read GROUPS failed"); } void db_write_group(register group_header * gh) { #ifdef NETWORK_DATABASE net_long buf[GROUP_FIELDS]; if (!db_sequential) fseek(master_file, (off_t) (MASTER_FIELDS * sizeof(net_long) + DB_LOCK_MESSAGE + GROUP_FIELDS * sizeof(net_long) * gh->group_num), 0); buf[0] = gh->first_db_article; buf[1] = gh->last_db_article; buf[2] = gh->index_write_offset; buf[3] = gh->data_write_offset; buf[4] = gh->group_name_length; buf[5] = gh->master_flag; buf[6] = gh->first_a_article; buf[7] = gh->last_a_article; buf[8] = gh->creation_time; host_to_net(buf, GROUP_FIELDS); if (fwrite((char *) buf, sizeof(net_long), GROUP_FIELDS, master_file) != GROUP_FIELDS) goto err; #else /* NETWORK_DATABASE */ if (!db_sequential) fseek(master_file, sizeof(master_header) + SAVED_GROUP_HEADER_SIZE(*gh) * gh->group_num, 0); if (fwrite((char *) gh, SAVED_GROUP_HEADER_SIZE(*gh), 1, master_file) != 1) goto err; #endif /* NETWORK_DATABASE */ fflush(master_file); #ifdef APOLLO_DOMAIN_OS if (who_am_i == I_AM_MASTER) make_master_copy(0); #endif /* APOLLO_DOMAIN_OS */ return; err: sys_error("Write GROUPS failed"); } #endif /* !NOV */ #ifdef NOV static void readtimfile(void) { char timline[512]; FILE *timfp; unsigned hsize; if (timtbl != NULL) return; hsize = master.number_of_groups | 0x1ff; timtbl = hashcreate(hsize, (unsigned (*) ()) NULL); if (timtbl == NULL) { nn_exitmsg(1, "can't create time hash (%d entries)\n", hsize); } if (new_group_action == RCX_NEVER) return; /* we don't need it */ #ifdef NNTP if (use_nntp) timfp = nntp_fopen_list("LIST active.times"); else #endif /* NNTP */ timfp = fopen(relative(news_lib_directory, "active.times"), "r"); if (timfp == NULL) return; /* no great shakes if its missing */ /* alt.fan.marla-thrift 736668095 netnews@ccc.amdahl.com */ #ifdef NNTP while (use_nntp ? nntp_fgets(timline, sizeof timline) : fgets(timline, sizeof timline, timfp)) #else /* NNTP */ while (fgets(timline, sizeof timline, timfp)) #endif /* NNTP */ { char *line = strkeep(timline, 0, POOL_TIM); char *p = strchr(line, ' '); if (p == NULL) continue; *p++ = NUL; if (!hashstore(timtbl, line, p)) { nn_exitmsg(1, "nn: time hashstore failed\n"); } } if (!use_nntp) (void) fclose(timfp); } #endif /* NOV */ #ifdef CACHE_PURPOSE static int purpcmp(const void *p0, const void *p1) { return strcmp(((struct purp_list *) p0)->group_name, ((struct purp_list *) p1)->group_name); } /* * Open purpose file and cache sorted list of group purposes */ static void cache_purpose(void) { char buf[512]; register char *p; FILE *fp; int i, ngrp; stlist_t *sthead, *stp, *stnew; struct purp_list *plp; if ((fp = open_purpose_file()) == NULL || (ngrp = master.number_of_groups) == 0 || purp_list != NULL) { return; } ngrp = 0; stp = sthead = NULL; while (fgets(buf, sizeof(buf), fp) != NULL) { if ((p = strchr(buf, NL)) != NULL) { *p = NUL; } stnew = (stlist_t *) strkeep(buf, sizeof(stlisthdr_t), POOL_PUR); if (stnew == NULL) { /* tough cookies. we'll just do without. */ pfree(POOL_PUR); return; } if (sthead != NULL) { stp->n.next = stnew; stp = stnew; } else { sthead = stnew; stp = sthead; } stnew->n.next = NULL; ngrp++; } purp_list = (struct purp_list *) calloc(ngrp + 1, sizeof(struct purp_list)); if (purp_list == NULL) { pfree(POOL_PUR); return; } for (i = 0, plp = purp_list, stp = sthead; stp; stp = stp->n.next) { p = stp->str; while (!isspace(*p)) { /* skip newsgroup name */ if (*++p == NUL) goto next; } *p++ = NUL; while (isspace(*p)) { /* skip to group description */ if (*++p == NUL) goto next; } plp->group_name = stp->str; plp->purpose = p; plp++; i++; next: continue; } plp->group_name = NULL; plp->purpose = NULL; purp_cnt = i; qsort(purp_list, purp_cnt, sizeof(struct purp_list), purpcmp); } char * purp_lookup(char *group) { register int i, j, k, t; i = 0; j = purp_cnt - 1; while (i <= j) { k = (i + j) / 2; if ((t = strcmp(group, purp_list[k].group_name)) > 0) i = k + 1; else if (t < 0) j = k - 1; else return purp_list[k].purpose; } return ""; } #endif /* CACHE_PURPOSE */ #ifndef NOV off_t db_read_art(FILE * f) { off_t bytes; #ifdef NETWORK_DATABASE net_long buf[ARTICLE_FIELDS]; if (fread((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS) return 0; bytes = sizeof(net_long) * ARTICLE_FIELDS; net_to_host(buf, ARTICLE_FIELDS); db_hdr.dh_number = buf[0]; db_hdr.dh_date = buf[1]; db_hdr.dh_hpos = buf[2]; db_hdr.dh_lpos = buf[3]; db_hdr.dh_fpos = buf[4]; db_hdr.dh_lines = buf[5]; db_hdr.dh_replies = buf[6]; db_hdr.dh_cross_postings = buf[7]; db_hdr.dh_subject_length = buf[8]; db_hdr.dh_sender_length = buf[9]; #else /* NETWORK_DATABASE */ if (fread((char *) &db_hdr, sizeof(data_header), 1, f) != 1) return 0; bytes = sizeof(data_header); #endif /* NETWORK_DATABASE */ if (db_hdr.dh_number < 0) { current_digest_article = db_hdr.dh_number = -db_hdr.dh_number; db_data.dh_type = DH_DIGEST_HEADER; } else if (db_hdr.dh_number == 0) { db_hdr.dh_number = current_digest_article; db_data.dh_type = DH_SUB_DIGEST; } else { current_digest_article = 0; db_data.dh_type = DH_NORMAL; } if (db_hdr.dh_cross_postings) { if (fread((char *) db_data.dh_cross, sizeof(cross_post_number), (int) db_hdr.dh_cross_postings, f) != (int) db_hdr.dh_cross_postings) return -1; bytes += sizeof(cross_post_number) * (int) db_hdr.dh_cross_postings; } if (db_hdr.dh_sender_length) { if (fread(db_data.dh_sender, sizeof(char), (int) db_hdr.dh_sender_length, f) != db_hdr.dh_sender_length) return -1; bytes += sizeof(char) * (int) db_hdr.dh_sender_length; } db_data.dh_sender[db_hdr.dh_sender_length] = NUL; if (db_hdr.dh_subject_length) { if (fread(db_data.dh_subject, sizeof(char), (int) db_hdr.dh_subject_length, f) != db_hdr.dh_subject_length) return -1; bytes += sizeof(char) * (int) db_hdr.dh_subject_length; } db_data.dh_subject[db_hdr.dh_subject_length] = NUL; db_read_counter++; return bytes; } #endif /* !NOV */ #ifdef NOV /* * initialise *gh; this is much cheaper than calling db_read_group. */ static void db_init_group(register group_header * gh, int num) { register char *p; /* tidy up the struct */ clearobj(gh, sizeof(struct group_header), 1); gh->group_num = num; if (gh->group_name == NULL) { gh->group_name = grplist[num]; if (gh->group_name == NULL) { nn_exitmsg(1, "can't map group %d to name\n", num); } gh->group_name_length = strlen(gh->group_name); } gh->master_flag = M_VALID; /* control.newgrp, etc are control groups */ if (strncmp(gh->group_name, "control", 7) == 0) gh->master_flag |= M_CONTROL; /* these next two are subtle and we need to lie below */ /* gh->first_db_article = 0; *//* lowest # in ov. data */ /* gh->last_db_article = 0; *//* highest # in ov. data */ gh->first_a_article = 1; /* lowest # in active */ /* gh->last_a_article = 0; *//* highest number in active */ /* gh->index_write_offset = 0; *//* dunno */ /* gh->data_write_offset = 0; *//* dunno */ /* set the creation time */ gh->creation_time = 1; /* group creation date (~epoch) */ p = hashfetch(timtbl, gh->group_name); if (p != NULL) { gh->creation_time = atol(p); } } /* * further initialise *gh; to be done after sort_groups(void) */ static void db_init_active(register group_header * gh) { group_header *gh1; register char *p; p = actlist[gh->group_num]; if (p != NULL) { while (isspace(*p)) ++p; gh->last_a_article = atol(p); p = strchr(p, ' '); if (p == NULL) return; gh->first_a_article = atol(++p); p = strchr(p, ' '); if (*++p == '=') { /* an alias */ gh1 = lookup_no_alias(++p); if (gh1 == NULL) { log_entry('R', "Group %s aliased to unknown group (%s)", gh->group_name, p); /* ah well! leave it be */ } else { gh->master_flag |= M_ALIASED; gh->data_write_offset = (long) gh1->group_num; } } } gh->first_db_article = gh->first_a_article; /* lowest # in ov. data */ gh->last_db_article = gh->last_a_article; /* highest # in ov. data */ } /* * slurp up the overview data for this group into *gh. * this costs a file open and so should not be done frivolously. */ void db_read_group(register group_header * gh, article_number first, article_number last) { register struct novart *artp, *lastartp; /* db_init_group(gh, group_num?? ); already done early at init time */ if (ngovp != NULL) novclose(ngovp); /* trash last group's data */ #ifdef NNTP if (use_nntp) { ngovp = nntp_get_overview(gh, first, last); } else #endif /* NNTP */ ngovp = novopen(gh->group_name); if (ngovp == NULL) { printf("no overview data for group `%s'\n", gh->group_name); return; } allarts = novall(ngovp, first, last); if (allarts == NULL) { /* * printf("overview data inaccessible for group `%s'\n", * gh->group_name); */ return; } if (!use_nntp || first == gh->first_a_article) gh->first_db_article = atol(allarts->a_num); /* lowest # */ if (!use_nntp || last == gh->last_a_article) { lastartp = allarts; for (artp = allarts; artp != NULL; artp = artp->a_nxtnum) if (atol(artp->a_num) != 0) lastartp = artp; gh->last_db_article = atol(lastartp->a_num); /* highest # */ } /* Handle Digest flag */ if (gh->first_db_article < 0) gh->first_db_article = -gh->first_db_article; if (gh->last_db_article < 0) gh->last_db_article = -gh->last_db_article; } /* * fill in db_hdr and db_data from the overview data for the next * article in this group. does weirdo nn encodings of header fields. */ off_t db_read_art(FILE * f) { register data_header *dhp = &db_hdr; register data_dynamic_data *ddp = &db_data; register struct novart *artp; int recnt = 0; if (ngovp == NULL || ngovp->g_first == NULL) return 0; if (ngovp->g_first == NULL) /* XXX */ return 0; artp = novnext(ngovp); if (artp == NULL) return 0; /* group exhausted */ dhp->dh_number = atol(artp->a_num); /* printf("article #%ld\n", dhp->dh_number); *//* DEBUG */ dhp->dh_date = pack_date(artp->a_date); /* "encoded Date: filed" */ dhp->dh_hpos = 0; /* 1st hdr byte */ dhp->dh_lpos = 1L << 30; /* last article byte */ dhp->dh_fpos = 0; /* 1st article text byte */ dhp->dh_lines = -1; /* -1 == "unknown" */ if (isascii(artp->a_lines[0]) && isdigit(artp->a_lines[0])) dhp->dh_lines = atoi(artp->a_lines); dhp->dh_replies = 0; /* # of References: */ if (artp->a_refs != NULL) { register char *p; for (p = artp->a_refs; *p != '\0'; p++) if (*p == '<') dhp->dh_replies++; } db_fixup_cross_postings(dhp, ddp, artp); if (dhp->dh_number < 0) { current_digest_article = dhp->dh_number = -dhp->dh_number; ddp->dh_type = DH_DIGEST_HEADER; } else if (dhp->dh_number == 0) { #ifdef DO_NOV_DIGEST char *cp; if (artp->a_bytes && (cp = strchr(artp->a_bytes, ':'))) { dhp->dh_hpos = atol(++cp); if ((cp = strchr(cp, ':')) != NULL) { dhp->dh_fpos = atol(++cp); if ((cp = strchr(cp, ':')) != NULL) { dhp->dh_lpos = atol(++cp); } } } #endif /* DO_NOV_DIGEST */ dhp->dh_number = current_digest_article; ddp->dh_type = DH_SUB_DIGEST; } else { current_digest_article = 0; ddp->dh_type = DH_NORMAL; } dhp->dh_sender_length = pack_name(ddp->dh_sender, artp->a_from, Name_Length); dhp->dh_subject_length = pack_subject(ddp->dh_subject, artp->a_subj, &recnt, DBUF_SIZE); if (recnt) /* 5/3/93 wolfgang@wsrcc.com */ dhp->dh_replies |= 0x80; db_read_counter++; return 1; } static void db_fixup_cross_postings(data_header * dhp, data_dynamic_data * ddp, struct novart * artp) { char *curg, *tmp; int numgrps = 0; dhp->dh_cross_postings = 0; /* assume none as default until we can show * otherwise */ /* * If no "other" header lines are in NOV database, we're out of luck, can * only assume no crosspostings, so return. */ if ((artp->a_others) == NULL) return; /* Scan until we find a Xref: header line. */ for (curg = artp->a_others;; ++curg) { if (strncmp("Xref: ", curg, 6) == 0 || strncmp("xref: ", curg, 6) == 0) { break; } curg = strchr(curg, '\t'); /* Not this header, skip to the next */ if (curg == NULL) return; } curg += 6; /* Skip over "Xref: " */ while (*curg == ' ') ++curg; /* Skip to the hostname field after Xref: */ /* Skip over the hostname to the space following hostname */ if ((curg = strchr(curg, ' ')) == NULL) { return; /* header is malformed, punt. */ } /* * Start reading the entries one at a time. Each entry is of the form * "newsgroup:number", and entries are separated by spaces. Algorithm * loosely based on the orignal one in collect.c for setting up the * crosspost information. */ while (*curg == ' ' && numgrps < DBUF_SIZE) { group_header *gh; while (*curg == ' ') ++curg; /* Skip spaces to the next entry */ /* Zap colon at end of current entry. */ for (tmp = curg;; ++tmp) { if (*tmp == ':' || *tmp == '\0' || *tmp == '\t') break; } if (*tmp != ':') break; /* malformed entry, punt. */ *tmp = '\0'; /* Find gh struct for the group. */ if ((gh = lookup(curg)) != NULL) { /* and add group number to the crosspost list. */ ddp->dh_cross[numgrps++] = gh->group_num; } curg = tmp + 1; while (isdigit(*curg)) ++curg; /* Skip over the article number */ } if (numgrps > 1) { /* * Note: if # of groups is only 1, we leave dh_cross_postings at its * original value of zero. */ dhp->dh_cross_postings = numgrps; } return; } #endif /* NOV */ #ifndef NOV int db_write_art(FILE * f) { #ifdef NETWORK_DATABASE net_long buf[ARTICLE_FIELDS]; #endif /* NETWORK_DATABASE */ article_number art_num = db_hdr.dh_number; switch (db_data.dh_type) { case DH_NORMAL: break; case DH_SUB_DIGEST: db_hdr.dh_number = 0; break; case DH_DIGEST_HEADER: db_hdr.dh_number = -art_num; break; } #ifdef NETWORK_DATABASE buf[0] = db_hdr.dh_number; buf[1] = db_hdr.dh_date; buf[2] = db_hdr.dh_hpos; buf[3] = db_hdr.dh_lpos; buf[4] = db_hdr.dh_fpos; buf[5] = db_hdr.dh_lines; buf[6] = db_hdr.dh_replies; buf[7] = db_hdr.dh_cross_postings; buf[8] = db_hdr.dh_subject_length; buf[9] = db_hdr.dh_sender_length; host_to_net(buf, ARTICLE_FIELDS); if (fwrite((char *) buf, sizeof(net_long), ARTICLE_FIELDS, f) != ARTICLE_FIELDS) return -1; #else /* NETWORK_DATABASE */ if (fwrite((char *) &db_hdr, sizeof(data_header), 1, f) != 1) return -1; #endif /* NETWORK_DATABASE */ if (db_hdr.dh_cross_postings) if (fwrite((char *) db_data.dh_cross, sizeof(cross_post_number), (int) db_hdr.dh_cross_postings, f) != (int) db_hdr.dh_cross_postings) return -1; if (db_hdr.dh_sender_length) if (fwrite(db_data.dh_sender, sizeof(char), (int) db_hdr.dh_sender_length, f) != db_hdr.dh_sender_length) return -1; if (db_hdr.dh_subject_length) if (fwrite(db_data.dh_subject, sizeof(char), (int) db_hdr.dh_subject_length, f) != db_hdr.dh_subject_length) return -1; db_hdr.dh_number = art_num; db_write_counter++; return 1; } long get_index_offset(group_header * gh, article_number art_num) { #ifdef NETWORK_DATABASE return (off_t) ((art_num - gh->first_db_article) * sizeof(net_long)); #else /* NETWORK_DATABASE */ return (off_t) ((art_num - gh->first_db_article) * sizeof(off_t)); #endif /* NETWORK_DATABASE */ } long get_data_offset(group_header * gh, article_number art_num) { FILE *index; long data_offset; if (gh->first_db_article == art_num) return (off_t) 0; index = open_data_file(gh, 'x', OPEN_READ); if (index == NULL) return (off_t) (-1); fseek(index, get_index_offset(gh, art_num), 0); if (!db_read_offset(index, &data_offset)) data_offset = -1; fclose(index); return data_offset; } int db_read_offset(FILE * f, long *offset) { #ifdef NETWORK_DATABASE net_long temp; if (fread((char *) &temp, sizeof(net_long), 1, f) != 1) return 0; #ifndef NETWORK_BYTE_ORDER temp = ntohl(temp); #endif /* !NETWORK_BYTE_ORDER */ *offset = temp; #else /* NETWORK_DATABASE */ if (fread((char *) offset, sizeof(off_t), 1, f) != 1) return 0; #endif /* NETWORK_DATABASE */ return 1; } int db_write_offset(FILE * f, long *offset) { #ifdef NETWORK_DATABASE net_long temp; temp = *offset; #ifndef NETWORK_BYTE_ORDER temp = htonl(temp); #endif /* !NETWORK_BYTE_ORDER */ if (fwrite((char *) &temp, sizeof(net_long), 1, f) != 1) return 0; #else /* NETWORK_DATABASE */ if (fwrite((char *) offset, sizeof(off_t), 1, f) != 1) return 0; #endif /* NETWORK_DATABASE */ return 1; } #endif /* !NOV */ #ifdef NOV /* These are strictly temporary. They will go away. */ char * db_data_path(char *namebuf, group_header * gh, char d_or_x) { nn_exitmsg(50, "STUB ROUTINE CALLED: db_data_path\n"); return NULL; } int db_read_offset(FILE * f, long *offset) { nn_exitmsg(50, "STUB ROUTINE CALLED: db_read_offset\n"); return -1; } void db_write_group(group_header * gh) { nn_exitmsg(50, "STUB ROUTINE CALLED: db_write_group\n"); return; } FILE * open_data_file(group_header * gh, char d_or_x, int mode) { nn_exitmsg(50, "STUB ROUTINE CALLED: open_data_dile\n"); return NULL; } long get_index_offset(group_header * gh, article_number art_num) { nn_exitmsg(50, "STUB ROUTINE CALLED: get_index_offset\n"); return -1; } #endif /* NOV */ #if defined(NOV) || defined(CACHE_PURPOSE) /* * pmalloc()/pfree(): * A scheme to avoid malloc()/free() overhead; handles memory in * STRCHUNK increments (deliberately same as STR_THUNK_SIZE). * Unlike mark_str()/alloc_str()/release_str(), pfree()'d memory * returns to the malloc() pool, which is arguably more social. * More important, the alloc_str() family assumes only one active * use at a time; interleaving uses or a misplaced release_str() has * the potential to leak memory or corrupt the current_str_t pool. * * Perhaps should be globally available; move to global.c? */ #define STRCHUNK ((1<<14) - 32) /* leave room for malloc header */ typedef struct stpool { stlist_t *sthead; char *pool; int pfree; int pslop; } stpool_t; static stpool_t stpool[POOL_MAX + 1]; static char * pmalloc(int size, int poolid) { register stpool_t *pp; register stlist_t *stnew; register char *ret; if (poolid < 0 || poolid > POOL_MAX) return NULL; pp = &stpool[poolid]; if (size <= pp->pfree) { /* Usually short; fits into current chunk */ ret = pp->pool; } else if (size <= STRCHUNK) { /* Sometimes chunk is exhausted; chain new one to pool */ stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + STRCHUNK); if (stnew == NULL) return NULL; pp->pslop += pp->pfree; stnew->n.next = pp->sthead; pp->sthead = stnew; pp->pfree = STRCHUNK; ret = stnew->str; } else { /* * Last resort: allocate oversize chunk and chain to pool behind * current chunk. */ stnew = (stlist_t *) malloc(sizeof(stlisthdr_t) + size); if (stnew == NULL) return NULL; if (pp->sthead != NULL) { stnew->n.next = pp->sthead->n.next; pp->sthead->n.next = stnew; } else { stnew->n.next = NULL; pp->sthead = stnew; } return stnew->str; /* NOTREACHED */ } pp->pool = ret + size; pp->pfree -= size; return ret; } static void pfree(int poolid) { register stpool_t *pp; register stlist_t *stp, *stnext; if (poolid < 0 || poolid > POOL_MAX) return; pp = &stpool[poolid]; for (stp = pp->sthead; stp; stp = stnext) { stnext = stp->n.next; free(stp); } pp->sthead = NULL; pp->pool = NULL; pp->pfree = 0; pp->pslop = 0; } /* * strkeep(void) * Save a string, allowing space for a header. */ static char * strkeep(char *s, int hdr, int poolid) { register int size; register char *ret; if (poolid == POOL_TMP) { size = hdr + strlen(s); ret = alloc_str(size); } else { size = (hdr + strlen(s) + sizeof(long)) & ~(sizeof(long) - 1); ret = pmalloc(size, poolid); } if (ret) #ifdef NO_MEMMOVE bcopy(s, ret + hdr, size - hdr); #else memmove(ret + hdr, s, size - hdr); #endif /* NO_MEMMOVE */ return ret; } #endif /* NOV || CACHE_PURPOSE */ nn-6.7.4/sort.h0000644000175000017500000000032407751420632012716 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ void sort_articles(int); void no_sort_articles(void); int elim_articles(register article_number *, int); nn-6.7.4/hostname.c0000644000175000017500000000502410222443031013523 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * "generic" gethostname() emulation: * * Possibilities are used in following order: * * HAVE_GETHOSTNAME -- use gethostname() * HAVE_UNAME -- use sysV uname().nodename * HOSTNAME_FILE "/.../..." -- read hostname from file * HOSTNAME_WHOAMI -- use sysname from whoami.h * HOSTNAME "host" -- hard-coded hostname * You lose! */ #include #include #include "config.h" #undef DONE #ifdef HAVE_GETHOSTNAME /* * Easy -- we already got it */ void nn_gethostname(char *name, int length) { gethostname(name, length); } #define DONE #endif #ifndef DONE #ifdef HAVE_UNAME /* * System V uname() is available. Use nodename field. */ #include void nn_gethostname(char *name, int length) { struct utsname un; uname(&un); strncpy(name, un.nodename, length); } #define DONE #endif #endif #ifndef DONE #ifdef HOSTNAME_FILE /* * Hostname is available in a file. * The name of the file is defined in HOSTNAME_FILE. * This is not intended to fail (or exit would have been via nn_exit) */ void nn_gethostname(char *name, int length) { FILE *f; register char *p; f = fopen(HOSTNAME_FILE, "r"); /* Generic code -- don't use * open_file */ if (f == NULL) goto err; if (fgets(name, length, f) == NULL) goto err; if ((p = strchr(name, NL)) != NULL) *p = NUL; fclose(f); return; err: fprintf(stderr, "HOSTNAME NOT FOUND IN %s\n", HOSTNAME_FILE); exit(77); } #define DONE #endif #endif #ifndef DONE #ifdef HOSTNAME_WHOAMI /* * Hostname is found in whoami.h */ void nn_gethostname(char *name, int length) { FILE *f; char buf[512]; register char *p, *q; f = fopen("/usr/include/whoami.h", "r"); if (f == NULL) goto err; while (fgets(buf, 512, f) != NULL) { if (buf[0] != '#') continue; if ((p = strchr(buf, '"')) == NULL) continue; *p++ = NUL; if (strncmp(buf, "#define sysname", 15)) continue; if ((q = strchr(p, '"')) == NULL) break; *q = NUL; strncpy(name, p, length); return; } err: fprintf(stderr, "HOSTNAME (sysname) NOT DEFINED IN whoami.h\n"); exit(77); } #define DONE #endif #endif #ifndef DONE #ifdef HOSTNAME void nn_gethostname(char *name, int length) { strncpy(name, HOSTNAME, length); } #define DONE #endif #endif #ifndef DONE YOU LOSE ON GETHOSTNAME-- DEFINE HOSTNAME IN CONFIG.H #endif nn-6.7.4/global.h0000644000175000017500000001413115213127122013156 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Global declarations and definitions. */ #ifndef _NN_GLOBAL_H #define _NN_GLOBAL_H 1 #include /* * Various constants and types */ typedef int8 attr_type; typedef int32 flag_type; #ifdef I286_BUG #define FLAG(n) (1L<<(n-1)) #else #define FLAG(n) (((flag_type)1)<<(n-1)) #endif typedef long article_number; typedef int32 group_number; typedef uint32 time_stamp; typedef int (*fct_type) (); #define CALL(fct) (* (fct)) #define NULL_FCT (fct_type)NULL /* frequently used characters */ #define NUL '\0' #define TAB '\t' #define NL '\n' #define CR '\r' #define BS '\b' #define SP ' ' /* misc macros */ #define fl fflush(stdout) #ifdef CONTROL_ #undef CONTROL_ #endif #define CONTROL_(c) (c&037) #ifndef HAVE_STRCHR #define strrchr rindex #define strchr index #endif #ifdef SIGNAL_HANDLERS_ARE_VOID #define sig_type void #else #define sig_type int #endif /* define types of own functions */ #define OPEN_READ 0 /* open for reading */ #define OPEN_UPDATE 1 /* open/create for update */ #define OPEN_CREATE 2 /* create/truncate for write */ #define OPEN_APPEND 3 /* open for append */ #define OPEN_CREATE_RW 4 /* create for read/write */ #define DONT_CREATE 0x40 /* return if file does not exist */ #define MUST_EXIST 0x80 /* fatal error if cannot open */ #define OPEN_UNLINK 0x100 /* unlink after open (not OPEN_UPDATE) */ /* * Other external definitions * * NOTICE: the distinction between pointers and arrays is important * here (they are global variables - not function arguments) */ extern char *nn_directory, *lib_directory, version_id[]; extern int use_nntp; /* 1 iff we are using nntp */ extern int s_hangup, /* hangup signal */ s_keyboard, /* keyboard signal */ s_pipe, /* broken pipe */ s_redraw; /* continue signal after stop */ extern int who_am_i; #define I_AM_MASTER 0 #define I_AM_NN 1 #define I_AM_ADMIN 2 #define I_AM_CHECK 3 #define I_AM_TIDY 4 #define I_AM_EMACS 5 #define I_AM_GOBACK 6 #define I_AM_POST 7 #define I_AM_GREP 8 #define I_AM_DAILY 9 #define I_AM_SPEW 10 #define I_AM_EXPIRE 11 #define I_AM_ACCT 12 #define I_AM_VIEW 13 extern uid_t user_id; extern int process_id; extern int errno; /* * Storage management */ #define newobj(type, nelt) \ (type *)mem_obj(sizeof(type), (int32)(nelt)) #define newstr(nelt) \ mem_str((int32)(nelt)) #define resizeobj(obj, type, nelt) \ (type *)mem_resize((char *)(obj), sizeof(type), (int32)(nelt)) #define clearobj(obj, type, nelt) \ mem_clear((char *)(obj), sizeof(type), (int32)(nelt)) #define freeobj(obj) mem_free((char *)(obj)) #define quicksort(a,n,t,f) qsort((char *)(a), (long)(n), sizeof(t), (int (*)(const void *,const void *)) f) #include "data.h" /* * db external data */ extern master_header master; /* group headers */ #ifdef MALLOC_64K_LIMITATION extern group_header **active_groups, **sorted_groups; #else extern group_header *active_groups, **sorted_groups; #endif /* current group information */ extern char group_path_name[]; extern char *group_file_name; extern group_header *current_group, *group_sequence, *rc_sequence; extern int l_g_index, s_g_first; #define Loop_Groups_Number(num) \ for (num = 0; num < master.number_of_groups; num++) #ifdef MALLOC_64K_LIMITATION #define Loop_Groups_Header(gh) \ for (l_g_index = 0; \ (l_g_index < master.number_of_groups) && \ (gh = active_groups[l_g_index]) ;\ l_g_index++) #else #define Loop_Groups_Header(gh) \ for (l_g_index = 0, gh=active_groups; \ l_g_index < master.number_of_groups; \ l_g_index++, gh++) #endif #define Loop_Groups_Sorted(gh) \ for (l_g_index = s_g_first; \ (l_g_index < master.number_of_groups) && \ (gh = sorted_groups[l_g_index]) ;\ l_g_index++) #define Loop_Groups_Sequence(gh) \ for (gh = group_sequence; gh; gh = gh->next_group) #define Loop_Groups_Newsrc(gh) \ for (gh = rc_sequence; gh; gh = gh->newsrc_seq) #ifdef MALLOC_64K_LIMITATION #define ACTIVE_GROUP(n) active_groups[n] #else #define ACTIVE_GROUP(n) &active_groups[n] #endif /* 8 bit support (ISO 8859/...) */ #ifdef HAVE_8BIT_CTYPE #ifdef isascii #undef isascii #endif #define isascii(c) 1 #define iso8859(c) (isprint(c)) #else #define iso8859(c) ((c) & 0x60) #endif /* * Some systems don't define these in */ #ifndef S_IFMT #define S_IFMT 0170000 /* type of file */ #define S_IFDIR 0040000 /* directory */ #define S_IFREG 0100000 /* regular */ #endif /* global.c */ sig_type catch_hangup(int); int init_global(void); int init_global2(void); void new_temp_file(void); FILE *open_file(char *, int); FILE *open_file_search_path(char *, int); int fgets_multi(char *, int, FILE *); char *relative(char *, char *); char *mk_file_name(char *, char *); char *home_relative(char *); char *substchr(char *, char, char); char *copy_str(char *); time_t m_time(FILE *); time_t file_exist(char *, char *); int cmp_file(char *, char *); int copy_file(char *, char *, int); int move_file(char *, char *, int); int save_old_file(char *, char *); void sys_error(char *,...); int sys_warning(char *,...); int log_entry(int, char *,...); char *user_name(void); time_t cur_time(void); char *date_time(time_t); char *plural(long); char *mem_obj(unsigned, int32); char *mem_str(int32); char *mem_resize(char *, unsigned, int32); void mem_clear(char *, unsigned, int32); void mem_free(char *); int nn_truncate(char *, off_t); char *strsave(char *); char *str3save(char *, char *, char *); char *fgetstr(FILE *); int nn_getline(char *, int); extern char *tmp_directory; extern char *nntp_cache_dir; #endif /* _NN_GLOBAL_H */ nn-6.7.4/digest.c0000644000175000017500000003223715213127126013203 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Digest article handling * * The code to do the selective parsing of mail and mmdf formats, * mail from lines and determining folder types is based on patches * contributed by Bernd Wechner (bernd@bhpcpd.kembla.oz.au). */ #include #include #include #include #include "config.h" #include "global.h" #include "debug.h" #include "digest.h" #include "news.h" #include "pack_name.h" #include "nn_term.h" /* digest.c */ static char **dg_hdr_field(register char *lp, int all); struct digest_header digest; int strict_from_parse = 2; #ifdef DG_TEST #define TEST0(fmt) #define TEST1(fmt, x) if (Debug & DG_TEST) printf(fmt, x) #define TEST2(fmt, x, y) if (Debug & DG_TEST) printf(fmt, x, y) #else #define TEST0(fmt, x) #define TEST1(fmt, x) #define TEST2(fmt, x, y) #endif #define UNIFY 040 static char digest_pattern[] = "igest"; void init_digest_parsing(void) { register char *m; for (m = digest_pattern; *m; m++) *m |= UNIFY; } int is_digest(register char *subject) { register char c, *q, *m; if (subject == NULL) return 0; while ((c = *subject++)) { if ((c | UNIFY) != ('d' | UNIFY)) continue; q = subject; m = digest_pattern; while ((c = *m++) && (*q++ | UNIFY) == c); if (c == NUL) return 1; } return 0; } /* * is_mail_from_line - Is this a legal unix mail "From " line? * * Given a line of input will check to see if it matches the standard * unix mail "from " header format. Returns 0 if it does and <0 if not. * * The check may be very lax or very strict depending upon * the value of "strict-mail-from-parse": * * 0 - Lax, checks only for the string "From ". * 1 - Strict, checks that the correct number of fields are present. * 2 - Very strict, also checks that each field contains a legal value. * * Assumptions: Not having the definitive unix mailbox reference I have * assumed that unix mailbox headers follow this format: * * From * * Where is the address of the sender, being an ordinary * string with no white space imbedded in it, and is the date of * posting, in ctime(3C) format. * * This would, on the face of it, seem valid. I (Bernd) have yet to find a * unix mailbox header which doesn't follow this format. * * From: Bernd Wechner (bernd@bhpcpd.kembla.oz.au) * Obfuscated by: KFS (as usual) */ #define MAX_FIELDS 10 static char legal_day[] = "SunMonTueWedThuFriSat"; static char legal_month[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; static int legal_numbers[] = {1, 31, 0, 23, 0, 59, 0, 60, 1969, 2199}; static int is_mail_from_line(char *line, char *namebuf) /* line Line of text to be checked */ /* namebuf Optional buffer to place packed sender info */ { char *fields[MAX_FIELDS]; char *sender_tail = NULL; register char *lp, **fp; register int n, i; if (strncmp(line, "From ", 5)) return -100; if (strict_from_parse == 0) return 0; lp = line + 5; /* sender day mon dd hh:mm:ss year */ for (n = 0, fp = fields; n < MAX_FIELDS; n++) { while (*lp && *lp != NL && isascii(*lp) && isspace(*lp)) lp++; if (*lp == NUL || *lp == NL) break; *fp++ = lp; while (*lp && isascii(*lp) && !isspace(*lp)) if (*lp++ == ':' && (n == 4 || n == 5)) break; if (n == 0) sender_tail = lp; } if (n < 8) return -200 - n; fp = fields; if (n > 8 && !isdigit(fp[7][0])) fp[7] = fp[8]; /* ... TZ year */ if (n > 9 && !isdigit(fp[7][0])) fp[7] = fp[9]; /* ... TZ DST year */ if (namebuf != NULL) { char x = *sender_tail; *sender_tail = NUL; pack_name(namebuf, *fp, Name_Length); *sender_tail = x; } if (strict_from_parse == 1) return 0; fp++; for (i = 0; i < 21; i += 3) if (strncmp(*fp, &legal_day[i], 3) == 0) break; if (i == 21) return -1; fp++; for (i = 0; i < 36; i += 3) if (strncmp(*fp, &legal_month[i], 3) == 0) break; if (i == 36) return -2; for (i = 0; i < 10; i += 2) { lp = *++fp; if (!isdigit(*lp)) return -20 - i; n = atoi(lp); if (n < legal_numbers[i] || legal_numbers[i + 1] < n) return -10 - i; } return 0; } /* * expect that f is positioned at header of an article */ static int is_mmdf_folder = 0; static int is_mail_folder = 0; /* * get_folder_type * * Given a file descriptor f, will check what type of folder it is. * Must be called at zero offset and caller must reposition if necessary. * Side-effects: sets is_mail_folder, is_mmdf_folder, and current_folder_type. * Return values: * -1: folder is empty, * 0: normal digest, * 1: UNIX mail format * 2: MMDF format */ int current_folder_type; int get_folder_type(FILE * f) { char line[1024]; is_mail_folder = 0; is_mmdf_folder = 0; if (fgets(line, 1024, f) == NULL) return current_folder_type = -1; if (strncmp(line, "\001\001\001\001\n", 5) == 0) { is_mmdf_folder = 1; return current_folder_type = 2; } if (is_mail_from_line(line, (char *) NULL) == 0) { is_mail_folder = 1; return current_folder_type = 1; } return current_folder_type = 0; } int get_digest_article(FILE * f, news_header_buffer hdrbuf) { int cont; digest.dg_hpos = ftell(f); TEST1("GET DIGEST hp=%ld\n", (long) digest.dg_hpos); do { if (!parse_digest_header(f, 0, hdrbuf)) return -1; digest.dg_fpos = ftell(f); TEST2("END HEADER hp=%ld fp=%ld\n", (long) digest.dg_hpos, (long) digest.dg_fpos); } while ((cont = skip_digest_body(f)) < 0); TEST2("END BODY lp=%ld next=%ld\n", (long) digest.dg_lpos, ftell(f)); return cont; } #define BACKUP_LINES 50 /* remember class + offset for parsed lines */ #define LN_BLANK 0x01 /* blank line */ #define LN_DASHED 0x02 /* dash line */ #define LN_HEADER 0x04 /* (possible) header line */ #define LN_ASTERISK 0x08 /* asterisk line (near end) */ #define LN_END_OF 0x10 /* End of ... line */ #define LN_TEXT 0x20 /* unclassified line */ /* * skip until 'Subject: ' (or End of digest) line is found * then backup till start of header */ /* * Tuning parameters: * * MIN_HEADER_LINES: number of known header lines that must * be found in a block to identify a new * header * * MAX_BLANKS_DASH max no of blanks on a 'dash line' * * MIN_DASHES min no of dashes on a 'dash line' * * MAX_BLANKS_ASTERISKS max no of blanks on an 'asterisk line' * * MIN_ASTERISKS min no of asterisks on an 'asterisk line' * * MAX_BLANKS_END_OF max no of blanks before "End of " */ #define MIN_HEADER_LINES 2 #define MAX_BLANKS_DASH 3 #define MIN_DASHES 16 #define MAX_BLANKS_ASTERISK 1 #define MIN_ASTERISKS 10 #define MAX_BLANKS_END_OF 1 int skip_digest_body(register FILE * f) { long backup_p[BACKUP_LINES]; int line_type[BACKUP_LINES]; register int backup_index, backup_count; int more_header_lines, end_or_asterisks, blanks; int colon_lines; char line[1024]; register char *cp; #define decrease_index() \ if (--backup_index < 0) backup_index = BACKUP_LINES - 1 backup_index = -1; backup_count = 0; end_or_asterisks = 0; digest.dg_lines = 0; next_line: more_header_lines = 0; colon_lines = 0; next_possible_header_line: digest.dg_lines++; if (++backup_index == BACKUP_LINES) backup_index = 0; if (backup_count < BACKUP_LINES) backup_count++; backup_p[backup_index] = ftell(f); line_type[backup_index] = LN_TEXT; if (fgets(line, 1024, f) == NULL) { TEST2("end_of_file, bc=%d, lines=%d\n", backup_count, digest.dg_lines); if (is_mmdf_folder) { digest.dg_lpos = backup_p[backup_index]; is_mmdf_folder = 0; return 0; } /* end of file => look for "****" or "End of" line */ if (end_or_asterisks) while (--backup_count >= 0) { --digest.dg_lines; decrease_index(); if (line_type[backup_index] & (LN_ASTERISK | LN_END_OF)) break; } digest.dg_lpos = backup_p[backup_index]; if (digest.dg_lines == 0) return 0; while (--backup_count >= 0) { --digest.dg_lines; digest.dg_lpos = backup_p[backup_index]; decrease_index(); if ((line_type[backup_index] & (LN_ASTERISK | LN_END_OF | LN_BLANK | LN_DASHED)) == 0) break; } return 0; /* no article follows */ } TEST1("\n>>%-.50s ==>>", line); if (is_mmdf_folder) { /* in an mmdf folder we simply look for the next ^A^A^A^A line */ if (line[0] != '\001' || strcmp(line, "\001\001\001\001\n")) goto next_line; digest.dg_lpos = backup_p[backup_index]; --digest.dg_lines; return (digest.dg_lines <= 0) ? -1 : 1; } for (cp = line; *cp && isascii(*cp) && isspace(*cp); cp++); if (*cp == NUL) { TEST0("BLANK"); line_type[backup_index] = LN_BLANK; goto next_line; } if (is_mail_folder) { /* in a mail folder we simply look for the next "From " line */ if (line[0] != 'F' || is_mail_from_line(line, (char *) NULL) < 0) goto next_line; line_type[backup_index] = LN_HEADER; fseek(f, backup_p[backup_index], 0); goto found_mail_header; } blanks = cp - line; if (*cp == '-') { if (blanks > MAX_BLANKS_DASH) goto next_line; while (*cp == '-') cp++; if (cp - line - blanks > MIN_DASHES) { while (*cp && (*cp == '-' || (isascii(*cp) && isspace(*cp)))) cp++; if (*cp == NUL) { TEST0("DASHED"); line_type[backup_index] = LN_DASHED; } } goto next_line; } if (*cp == '*') { if (blanks > MAX_BLANKS_ASTERISK) goto next_line; while (*cp == '*') cp++; if (cp - line - blanks > MIN_ASTERISKS) { while (*cp && (*cp == '*' || (isascii(*cp) && isspace(*cp)))) cp++; if (*cp == NUL) { TEST0("ASTERISK"); line_type[backup_index] = LN_ASTERISK; end_or_asterisks++; } } goto next_line; } if (blanks <= MAX_BLANKS_END_OF && *cp == 'E' && strncmp(cp, "End of ", 7) == 0) { TEST0("END_OF_"); line_type[backup_index] = LN_END_OF; end_or_asterisks++; goto next_line; } if (blanks) goto next_possible_header_line; /* must be able to handle continued lines in sub-digest headers... goto next_line; */ if (!dg_hdr_field(line, 0)) { char *colon; if ((colon = strchr(line, ':'))) { for (cp = line; cp < colon; cp++) if (!isascii(*cp) || isspace(*cp)) break; if (cp == colon) { TEST0("COLON"); colon_lines++; line_type[backup_index] = LN_HEADER; goto next_possible_header_line; } } if (is_mail_from_line(line, (char *) NULL) == 0) { TEST0("FROM_"); colon_lines++; line_type[backup_index] = LN_HEADER; } goto next_possible_header_line; } TEST0("HEADER"); line_type[backup_index] = LN_HEADER; if (++more_header_lines < MIN_HEADER_LINES) goto next_possible_header_line; /* found block with MIN_HEADER_LINES */ TEST0("\nSearch for start of header\n"); colon_lines += more_header_lines; for (;;) { fseek(f, backup_p[backup_index], 0); if (line_type[backup_index] == LN_HEADER) if (--colon_lines <= 0) break; --digest.dg_lines; if (--backup_count == 0) break; decrease_index(); if ((line_type[backup_index] & (LN_HEADER | LN_TEXT)) == 0) break; } if (digest.dg_lines == 0) { TEST0("Skipped empty article\n"); return -1; } found_mail_header: for (;;) { digest.dg_lpos = backup_p[backup_index]; if (--backup_count < 0) break; decrease_index(); if ((line_type[backup_index] & (LN_BLANK | LN_DASHED)) == 0) break; --digest.dg_lines; } return (digest.dg_lines == 0) ? -1 : 1; } int parse_digest_header(FILE * f, int all, news_header_buffer hdrbuf) { digest.dg_date = digest.dg_from = digest.dg_subj = digest.dg_to = NULL; parse_header(f, dg_hdr_field, all, hdrbuf); return digest.dg_from || digest.dg_subj; } static char ** dg_hdr_field(register char *lp, int all) { static char *dummy; static char namebuf[33]; #define check(name, lgt, field) \ if (isascii(lp[lgt]) && isspace(lp[lgt]) \ && strncasecmp(name, lp, lgt) == 0) {\ TEST0("MATCH: " #field " "); \ return &digest.field; \ } TEST1("\nPARSE[%.20s] ==>> ", lp); switch (*lp++) { case '\001': /* In an mmdf folder ^A^A^A^A is skipped at beginning of header */ if (!is_mmdf_folder) break; if (strncmp(lp, "\001\001\001\n", 4)) break; digest.dg_hpos += 5; return NULL; case 'D': case 'd': check("ate:", 4, dg_date); break; case 'F': case 'f': check("rom:", 4, dg_from); if (!is_mail_folder) break; if (*--lp != 'F') break; if (is_mail_from_line(lp, namebuf) < 0) break; /* Store packed sender in dg_from here and return dummy to parser */ if (digest.dg_from == NULL) digest.dg_from = namebuf; return &dummy; case 'R': case 'r': if (!all) break; check("e:", 2, dg_subj); break; case 'S': case 's': check("ubject:", 7, dg_subj); check("ubject", 6, dg_subj); break; case 'T': case 't': check("itle:", 5, dg_subj); if (!all) break; check("o:", 2, dg_to); break; } #undef check TEST0("NOT MATCHED "); return NULL; } nn-6.7.4/nn.h0000644000175000017500000000054010217216212012326 0ustar mtpinsmtpins/* * Copyright (c) 2003-2005 Michael T Pins. All rights reserved. */ flag_type parse_access_flags(void); int unread_mail(time_t); void stop_usage(void); time_t tick_usage(void); int display_motd(int); void nn_exit(int); #ifdef ACCOUNTING int account(char, int); #endif nn-6.7.4/articles.c0000644000175000017500000003540315213127131013524 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Basic article access and management */ #include #include #include "config.h" #include "global.h" #include "articles.h" #include "db.h" #include "kill.h" #include "match.h" #include "news.h" #include "newsrc.h" #include "regexp.h" #include "sort.h" #include "nn_term.h" /* articles.c */ static void new_thunk(thunk * t, char *ptr, long size); #ifdef ART_GREP static int grep_article(group_header * gh, article_header * ah, char *pattern, int (*fcn) ()); #endif /* ART_GREP */ int seq_cross_filtering = 1; int select_leave_next = 0; /* ask to select left over art. */ int ignore_re = 0; int body_search_header = 0; int cross_post_limit = 0; article_number n_articles; article_header **articles; extern int ignore_fancy_select; extern int killed_articles; extern attr_type test_article(article_header *); /* * memory management */ static thunk dummy_str_t = { NULL, NULL, 0L }, dummy_art_t = { NULL, NULL, 0L }; static thunk *first_str_t = &dummy_str_t; static thunk *current_str_t = &dummy_str_t; static thunk *first_art_t = &dummy_art_t; static thunk *current_art_t = &dummy_art_t; static long cur_str_size = 0, cur_art_size = 0; static char *next_str; static article_header *next_art; static article_header **art_array = NULL; static article_number max_articles = 0, mem_offset = 0; /* * allocate one article header */ #ifndef ART_THUNK_SIZE #define ART_THUNK_SIZE 127 #endif static void new_thunk(thunk * t, char *ptr, long size) { thunk *new; new = newobj(thunk, 1); new->next_thunk = t->next_thunk; t->next_thunk = new; new->this_thunk = ptr; new->thunk_size = size; } article_header * alloc_art(void) { if (cur_art_size == 0) { if (current_art_t->next_thunk == NULL) new_thunk(current_art_t, (char *) newobj(article_header, ART_THUNK_SIZE), (long) ART_THUNK_SIZE); current_art_t = current_art_t->next_thunk; next_art = (article_header *) current_art_t->this_thunk; cur_art_size = current_art_t->thunk_size; } cur_art_size--; return next_art++; } /* * allocate a string of length 'len' */ #ifndef STR_THUNK_SIZE #define STR_THUNK_SIZE ((1<<14) - 32) /* leave room for malloc header */ #endif char * alloc_str(int len) { /* allow space for '\0', and align on word boundary */ int size = (len + sizeof(int)) & ~(sizeof(int) - 1); char *ret; if (cur_str_size < size) { if (current_str_t->next_thunk == NULL) new_thunk(current_str_t, newstr(STR_THUNK_SIZE), (long) STR_THUNK_SIZE); current_str_t = current_str_t->next_thunk; next_str = current_str_t->this_thunk; cur_str_size = current_str_t->thunk_size; } /* XXX else should do something reasonable */ ret = next_str; cur_str_size -= size; next_str += size; ret[len] = NUL; /* ensure string is null terminated */ return ret; } /* * "free" the allocated memory */ void free_memory(void) { current_str_t = first_str_t; current_art_t = first_art_t; cur_str_size = 0; cur_art_size = 0; n_articles = 0; } /* * mark/release memory */ void mark_str(string_marker * str_marker) { str_marker->sm_cur_t = current_str_t; str_marker->sm_size = cur_str_size; str_marker->sm_next = next_str; } void release_str(string_marker * str_marker) { current_str_t = str_marker->sm_cur_t; cur_str_size = str_marker->sm_size; next_str = str_marker->sm_next; } void mark_memory(memory_marker * mem_marker) { mark_str(&(mem_marker->mm_string)); mem_marker->mm_cur_t = current_art_t; mem_marker->mm_size = cur_art_size; mem_marker->mm_next = next_art; mem_marker->mm_nart = n_articles; mem_offset += n_articles; n_articles = 0; articles = art_array + mem_offset; } void release_memory(memory_marker * mem_marker) { release_str(&(mem_marker->mm_string)); current_art_t = mem_marker->mm_cur_t; cur_art_size = mem_marker->mm_size; next_art = mem_marker->mm_next; n_articles = mem_marker->mm_nart; mem_offset -= n_articles; articles = art_array + mem_offset; } /* * merge all memory chunks into one. */ void merge_memory(void) { n_articles += mem_offset; mem_offset = 0; articles = art_array; } /* * save article header in 'articles' array * 'articles' is enlarged if too small */ #define FIRST_ART_ARRAY_SIZE 500 /* malloc header */ #define NEXT_ART_ARRAY_SIZE 512 void add_article(article_header * art) { if ((n_articles + mem_offset) == max_articles) { /* must increase size of 'articles' */ if (max_articles == 0) { /* allocate initial 'articles' array */ max_articles = FIRST_ART_ARRAY_SIZE; } else { max_articles += NEXT_ART_ARRAY_SIZE; } art_array = resizeobj(art_array, article_header *, max_articles); articles = art_array + mem_offset; } articles[n_articles] = art; n_articles++; } int access_group(register group_header * gh, article_number first_article, article_number last_article, register flag_type flags, char *mask) { register article_header *ah; int skip_digest, n; group_header *cpgh; #ifdef NOV int dbstatus; #else /* NOV */ FILE *data; long data_offset, data_size; #endif /* NOV */ cross_post_number cross_post; attr_type leave_attr; memory_marker mem_marker; static regexp *rexp = NULL; static char subptext[80]; if (first_article < gh->first_db_article) first_article = gh->first_db_article; if (last_article > gh->last_db_article) last_article = gh->last_db_article; if (last_article == 0 || first_article > last_article) return 0; #ifdef NOV db_read_group(gh, first_article, last_article); #else data = open_data_file(gh, 'd', OPEN_READ); if (data == NULL) return -10; if ((data_offset = get_data_offset(gh, first_article)) == -1) return -11; #endif /* NOV */ if (mask == NULL) { if (rexp != NULL) { freeobj(rexp); rexp = NULL; } } else { if (*mask == '/') { mask++; if (rexp != NULL) { if (strncmp(mask, subptext, 80) != 0) { freeobj(rexp); rexp = NULL; } } if (rexp == NULL) { strncpy(subptext, mask, 80); rexp = regcomp(mask); if (rexp == NULL) return -1; } /* notice mask is still non-NULL */ } } if ((flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES))) leave_attr = 0; else if (select_leave_next) leave_attr = A_READ; /* will prompt */ else leave_attr = A_LEAVE_NEXT; if (!(flags & ACC_SPEW_MODE)) use_newsrc(gh, (flags & ACC_MERGED_NEWSRC) ? 2 : (flags & ACC_ORIG_NEWSRC) ? 1 : 0); if ((flags & ACC_EXTRA_ARTICLES) == 0) mark_memory(&mem_marker); ah = alloc_art(); skip_digest = 0; #ifdef NOV /* XXX: db_read_art takes a FILE * in nnmaster version */ while ((dbstatus = db_read_art((FILE *) - 1)) > 0) { #ifdef ART_GREP if (s_keyboard || s_hangup) break; /* add to allow interrupt of GREP */ #endif if (db_hdr.dh_number < first_article) continue; #else fseek(data, data_offset, 0); while (data_offset < gh->data_write_offset) { #ifdef ART_GREP if (s_keyboard || s_hangup) break; /* add to allow interrupt of GREP */ #endif data_size = db_read_art(data); if (data_size <= 0) { fclose(data); if ((flags & ACC_EXTRA_ARTICLES) == 0) release_memory(&mem_marker); return -2; } data_offset += data_size; #endif /* NOV */ if (db_hdr.dh_lpos == (off_t) 0) continue; /* article not accessible */ if (db_hdr.dh_number > gh->last_db_article || db_hdr.dh_number < gh->first_db_article) goto data_error; if (skip_digest && db_data.dh_type == DH_SUB_DIGEST) continue; skip_digest = 0; if (cross_post_limit && db_hdr.dh_cross_postings >= cross_post_limit) continue; if (db_hdr.dh_cross_postings && !(flags & ACC_ALSO_CROSS_POSTINGS)) { for (n = 0; n < (int) db_hdr.dh_cross_postings; n++) { cross_post = NETW_CROSS_INT(db_data.dh_cross[n]); if (cross_post < 0 || cross_post >= master.number_of_groups) continue; cpgh = ACTIVE_GROUP(cross_post); if (cpgh == gh) { if (seq_cross_filtering) continue; n = db_hdr.dh_cross_postings; break; } if (!(flags & ACC_ALSO_UNSUB_GROUPS)) if (cpgh->group_flag & G_UNSUBSCRIBED) continue; if (!seq_cross_filtering) break; if (cpgh->preseq_index > 0 && cpgh->preseq_index < gh->preseq_index) break; } if (n < (int) db_hdr.dh_cross_postings) { if (db_data.dh_type == DH_DIGEST_HEADER) skip_digest++; continue; } } ah->flag = 0; switch (db_data.dh_type) { case DH_DIGEST_HEADER: if (flags & ACC_DONT_SPLIT_DIGESTS) skip_digest++; else if ((flags & ACC_ALSO_FULL_DIGEST) == 0) continue; /* don't want the full digest when split */ ah->flag |= A_FULL_DIGEST; break; case DH_SUB_DIGEST: ah->flag |= A_DIGEST; break; } ah->a_number = db_hdr.dh_number; if (ah->a_number > last_article) break; if (flags & ACC_SPEW_MODE) { fold_string(db_data.dh_subject); tprintf("%x:%s\n", (int) (gh->group_num), db_data.dh_subject); continue; } ah->hpos = db_hdr.dh_hpos; ah->fpos = ah->hpos + (off_t) (db_hdr.dh_fpos); ah->lpos = db_hdr.dh_lpos; ah->attr = test_article(ah); if (flags & ACC_MERGED_NEWSRC) { if (ah->attr == A_KILL) continue; } else if (ah->attr != A_READ && (flags & ACC_ONLY_READ_ARTICLES)) continue; #ifdef ART_GREP if ((flags & ACC_ON_GREP_UNREAD) && (ah->attr == A_READ)) continue; #endif /* ART_GREP */ if (rexp != NULL) { if (flags & ACC_ON_SUBJECT) if (regexec_cf(rexp, db_data.dh_subject)) goto match_ok; if (flags & ACC_ON_SENDER) if (regexec_cf(rexp, db_data.dh_sender)) goto match_ok; #ifdef ART_GREP if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL)) if (grep_article(gh, ah, (char *) rexp, regexec_cf)) goto match_ok; #endif /* ART_GREP */ continue; } else if (mask != NULL) { if (flags & ACC_ON_SUBJECT) if (strmatch_cf(mask, db_data.dh_subject)) goto match_ok; if (flags & ACC_ON_SENDER) if (strmatch_cf(mask, db_data.dh_sender)) goto match_ok; #ifdef ART_GREP if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL)) if (grep_article(gh, ah, mask, strmatch_cf)) goto match_ok; #endif /* ART_GREP */ continue; } match_ok: attr_again: switch (ah->attr) { case A_LEAVE: if (mask) { ah->attr = 0; break; } if (leave_attr == A_READ) { clrdisp(); prompt("Select left over articles in group %s? ", gh->group_name); leave_attr = yes(0) > 0 ? A_SELECT : A_LEAVE_NEXT; } ah->attr = leave_attr; goto attr_again; case A_SELECT: if (mask) ah->attr = 0; break; case A_READ: if (flags & ACC_MERGED_NEWSRC) break; if (!(flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES))) if (ah->a_number > gh->last_article) continue; /* FALLTHROUGH */ case A_SEEN: case 0: if (flags & ACC_DO_KILL) { ah->replies = db_hdr.dh_replies; ah->sender = db_data.dh_sender; ah->subject = db_data.dh_subject; if (kill_article(ah)) continue; } /* The 'P' command to a read group must show articles as read */ if (gh->unread_count <= 0) ah->attr = A_READ; break; default: break; } if (db_hdr.dh_sender_length) { ah->name_length = db_hdr.dh_sender_length; ah->sender = alloc_str((int) db_hdr.dh_sender_length); strcpy(ah->sender, db_data.dh_sender); } else ah->sender = ""; if (db_hdr.dh_subject_length) { ah->subj_length = db_hdr.dh_subject_length; ah->subject = alloc_str((int) db_hdr.dh_subject_length); strcpy(ah->subject, db_data.dh_subject); } else ah->subject = ""; ah->replies = db_hdr.dh_replies; ah->lines = db_hdr.dh_lines; ah->t_stamp = db_hdr.dh_date; ah->a_group = (flags & ACC_MERGED_MENU) ? current_group : NULL; add_article(ah); ah = alloc_art(); } #ifdef NOV if (dbstatus < 0) { /* Error reading DB? */ if ((flags & ACC_EXTRA_ARTICLES) == 0) release_memory(&mem_marker); return -2; } /* else EOF reading DB */ #else fclose(data); #endif /* NOV */ if ((flags & ACC_DONT_SORT_ARTICLES) == 0) sort_articles(-1); else no_sort_articles(); if (ignore_re && !ignore_fancy_select && !(flags & ACC_ALSO_READ_ARTICLES)) { int nexta, roota, killa, newa; for (nexta = killa = newa = roota = 0; nexta < n_articles; nexta++) { ah = articles[nexta]; if (ah->flag & A_ROOT_ART) roota = ah->replies & 127; if (roota && ah->replies && !auto_select_article(ah, 1)) killa++; else articles[newa++] = ah; } n_articles -= killa; killed_articles += killa; } return n_articles > 0 ? 1 : 0; data_error: log_entry('E', "%s: data inconsistency", gh->group_name); #ifndef NOV fclose(data); #endif /* NOV */ if ((flags & ACC_EXTRA_ARTICLES) == 0) release_memory(&mem_marker); return -12; } #ifdef ART_GREP /* GREP ARTICLE -- search the article for the pattern using the specified function Return 1 if pattern occurs in the article else 0 Also return 0 if any malloc or file open or read error occurs Global data_header db_hdr is set by caller todo: doesn't work for folders (hangs) */ static int grep_article(group_header * gh, article_header * ah, char *pattern, int (*fcn) ()) { char *line, *end; FILE *art; news_header_buffer buffer1, buffer2; static char *buf; static int bufsize, count; size_t size; count++; art = open_news_article(ah, FILL_OFFSETS | (body_search_header ? 0 : SKIP_HEADER), buffer1, buffer2); if (!art) { /* msg("Cannot open article"); */ return 0; } size = ah->lpos - ftell(art) + 1; if (bufsize < size) { if (buf) free(buf); buf = (char *) malloc(size + 10); if (!buf) { msg("Cannot malloc %d bytes", size); bufsize = 0; return 0; } bufsize = size; } if (fread(buf, size - 1, 1, art) != 1) { fclose(art); /* msg("Cannot read article"); */ return 0; } fclose(art); /* print status message every so often */ if (!(count % 27)) msg("Searching %d of %d", ah->a_number - gh->first_db_article, gh->last_db_article - gh->first_db_article); buf[size] = 0; /* make buf a giant string so strchr below * terminates */ line = buf; while ((end = strchr(line, '\n'))) { *end++ = 0; /* make the line a nul terminated string */ if ((*fcn) (pattern, line)) return 1; line = end; } return 0; } #endif /* ART_GREP */ nn-6.7.4/macro.h0000644000175000017500000000076507751420627013045 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ void init_macro(void); char *m_define(char *, FILE *); char *parse_enter_macro(FILE *, register int); void m_invoke(int); void m_startinput(void); void m_endinput(void); void m_advinput(void); void m_break_entry(void); void m_break(void); int m_getc(int *); int m_gets(char *); int m_yes(void); nn-6.7.4/execute.h0000644000175000017500000000041707751420624013375 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ void init_execute(void); int execute(char *, char **, int); int shell_escape(void); int run_shell(char *, int, int); int suspend_nn(void); nn-6.7.4/sort.c0000644000175000017500000002363615213127135012716 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Article sorting. */ #include #include #include #include "config.h" #include "global.h" #include "articles.h" /* sort.c */ static int order_subj_date(article_header ** ah1, article_header ** ah2); static int order_date_subj_date(article_header ** ah1, article_header ** ah2); static int order_arrival(article_header ** a, article_header ** b); static int order_date(register article_header ** ah1, register article_header ** ah2); static int order_from_date(register article_header ** ah1, register article_header ** ah2); static flag_type article_equal(article_header ** ah1, article_header ** ah2); #ifdef BAD_ORDER_SUBJ_DATE /* If one article's subject is identical to the first part of another article, the two subjects will still be considered identical if the length of the shorter subject is at least the limit set by this variable. */ int subject_match_limit = 20; /* "strncmp" limit for * subjects */ #else /* Subjects are considered identical if their first s-m-l characters match */ int subject_match_limit = 256; /* "strncmp" limit for * subjects */ #endif int match_skip_prefix = 0; /* skip first N bytes in matches */ int match_parts_equal = 0; /* match digits as equal */ int match_parts_begin = 4; /* require N characters for part * match */ int sort_mode = 1; /* default sort mode */ extern int bypass_consolidation; /* * String matching macroes. * * MATCH_DROP(t, a) and MATCH_DROP(t, b) must both be proven false * before MATCH_?? (t, a, b) is used. */ #define MATCH_DROP(table, c) ( c & 0200 || table[c] == 0 ) #define MATCH_EQ(table, a, b) ( a == b || table[a] == table[b] ) #define MATCH_LS_EQ(table, a, b) ( a <= b || table[a] <= table[b] ) #define MATCH_LS(table, a, b) ( a < b || table[a] < table[b] ) #define MATCH_CMP(table, a, b) (table[a] - table[b]) static char match_subject[128] = { /* NUL SOH STX ETX EOT ENQ ACK BEL BS TAB NL VT FF CR SO SI */ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US */ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* SP ! " # $ % & ' ( ) * + , - . / */ 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 99, 00, 00, 00, 00, /* ^^ */ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 00, 00, 00, 00, 00, 00, /* @ A B C D E F G H I J K L M N O */ 00, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* P Q R S T U V W X Y Z [ \ ] ^ _ */ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 00, 00, /* ` a b c d e f g h i j k l m n o */ 00, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* p q r s t u v w x y z { | } ~ DEL */ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 00, 00 }; static int order_subj_date(article_header ** ah1, article_header ** ah2) { register char *a = (**ah1).subject, *b = (**ah2).subject; register char ca, cb; register int p, len; if (match_skip_prefix) { if ((int) (**ah1).subj_length > match_skip_prefix) { if ((int) (**ah2).subj_length > match_skip_prefix) { a += match_skip_prefix; b += match_skip_prefix; } else return 1; } else { if ((int) (**ah2).subj_length > match_skip_prefix) { return -1; } } } #ifdef BAD_ORDER_SUBJ_DATE for (len = 0;; len++, a++, b++) { #else for (len = subject_match_limit; --len >= 0; a++, b++) { #endif while ((ca = *a) && MATCH_DROP(match_subject, ((int8) ca))) a++; while ((cb = *b) && MATCH_DROP(match_subject, ((int8) cb))) b++; if (ca == NUL) { if (cb == NUL) break; #ifdef BAD_ORDER_SUBJ_DATE if (len >= subject_match_limit) break; #endif return -1; } if (cb == NUL) { #ifdef BAD_ORDER_SUBJ_DATE if (len >= subject_match_limit) break; #endif return 1; } if ((p = MATCH_CMP(match_subject, (int8) ca, (int8) cb))) return p; } if ((**ah1).t_stamp > (**ah2).t_stamp) return 1; if ((**ah1).t_stamp < (**ah2).t_stamp) return -1; return order_arrival(ah1, ah2); } /* data_subj_date can only be used after root_t_stamp is set */ static int order_date_subj_date(article_header ** ah1, article_header ** ah2) { register time_stamp t1 = (**ah1).root_t_stamp; register time_stamp t2 = (**ah2).root_t_stamp; if (t1 > t2) return 1; if (t1 < t2) return -1; return order_subj_date(ah1, ah2); /* get original order */ } static int order_arrival(article_header ** a, article_header ** b) { register long i; if ((i = ((*a)->a_number - (*b)->a_number)) == 0) i = (*a)->fpos - (*b)->fpos; return (i > 0) ? 1 : (i < 0) ? -1 : 0; } static int order_date(article_header ** ah1, article_header ** ah2) { if ((**ah1).t_stamp > (**ah2).t_stamp) return 1; if ((**ah1).t_stamp < (**ah2).t_stamp) return -1; return order_arrival(ah1, ah2); } static int order_from_date(register article_header ** ah1, register article_header ** ah2) { register int i; if ((i = strcmp((**ah1).sender, (**ah2).sender))) return i; return order_date(ah1, ah2); } static flag_type article_equal(article_header ** ah1, article_header ** ah2) /* ah1.hdr == ah2.hdr */ { register char *a = (**ah1).subject, *b = (**ah2).subject; register int len; if (match_skip_prefix) { if ((int) (**ah1).subj_length > match_skip_prefix) { if ((int) (**ah2).subj_length > match_skip_prefix) { a += match_skip_prefix; b += match_skip_prefix; } } } for (len = 0;; len++, a++, b++) { while (*a && MATCH_DROP(match_subject, (int8) * a)) a++; while (*b && MATCH_DROP(match_subject, (int8) * b)) b++; if (*a == NUL) { if (*b == NUL) break; if (len >= subject_match_limit) return A_ALMOST_SAME; return 0; } if (*b == NUL) { if (len >= subject_match_limit) return A_ALMOST_SAME; return 0; } if (!MATCH_EQ(match_subject, (int8) * a, (int8) * b)) { if (len >= subject_match_limit) return A_ALMOST_SAME; if (len >= match_parts_begin && match_parts_equal && isdigit(*a) && isdigit(*b)) return A_ALMOST_SAME; return 0; } } return A_SAME; } void sort_articles(int mode) { register article_header **app; register long n; register flag_type same; fct_type cmp; if (n_articles <= 0) return; for (n = n_articles; --n >= 0;) articles[n]->flag &= ~(A_SAME | A_ALMOST_SAME | A_NEXT_SAME | A_ROOT_ART); if (n_articles == 1) { articles[0]->flag |= A_ROOT_ART; return; } if (mode == -1) mode = sort_mode; switch (mode) { case -2: /* restore original ordering for update */ cmp = order_arrival; break; default: case 0: /* arrival (no sort) */ cmp = order_arrival; bypass_consolidation = 1; break; case 1: /* date-subject-date */ case 2: /* subject-date */ cmp = order_subj_date; break; case 3: /* date only */ cmp = order_date; bypass_consolidation = 1; break; case 4: /* sender-date */ cmp = order_from_date; bypass_consolidation = 1; break; } quicksort(articles, n_articles, article_header *, cmp); articles[0]->root_t_stamp = articles[0]->t_stamp; articles[0]->flag |= A_ROOT_ART; for (n = n_articles - 1, app = articles + 1; --n >= 0; app++) { if ((same = article_equal(app, app - 1))) { app[0]->root_t_stamp = app[-1]->root_t_stamp; app[0]->flag |= same; app[-1]->flag |= A_NEXT_SAME; } else { app[0]->root_t_stamp = app[0]->t_stamp; app[0]->flag |= A_ROOT_ART; } } if (mode == 1) quicksort(articles, n_articles, article_header *, order_date_subj_date); } /* * If articles are not sorted via sort_articles, they must still be * marked with proper attributes (e.g. A_ROOT_ART) via no_sort_articles. */ void no_sort_articles(void) { register article_number n; register article_header *ah; for (n = n_articles; --n >= 0;) { ah = articles[n]; ah->flag &= ~(A_SAME | A_ALMOST_SAME | A_NEXT_SAME | A_ROOT_ART); ah->flag |= A_ROOT_ART; } bypass_consolidation = 1; } /* * Eliminate articles with the A_KILL flag set preserving the present ordering. * This will only release the last entries in the articles array. * Neither strings nor articles headers are released. */ int elim_articles(register article_number * list, int list_lgt) { register article_header **srca, **desta; register article_number n, count; register flag_type same; int changed, llen; count = 0; changed = 0, llen = 0; for (n = 0, srca = desta = articles; n < n_articles; n++, srca++) { if ((*srca)->attr == A_KILL) { if (list_lgt > 0) { if (n < *list) { if (llen) changed = 1; } else if (n == *list) { if (llen) { llen++; list_lgt--; *list++ = -1; } else ++(*list); changed = 1; } } continue; } if (list_lgt > 0 && n == *list) { *list++ = count; list_lgt--; llen++; } count++; *desta++ = *srca; } if (list_lgt > 0) { if (!llen) *list = 0; changed = 1; } n_articles = count; if (changed && n_articles > 0) { srca = articles; srca[0]->flag &= ~(A_SAME | A_ALMOST_SAME | A_NEXT_SAME); srca[0]->flag |= A_ROOT_ART; for (n = n_articles - 1, srca++; --n >= 0; srca++) { srca[0]->flag &= ~(A_SAME | A_ALMOST_SAME | A_NEXT_SAME | A_ROOT_ART); if ((same = article_equal(srca, srca - 1))) { srca[0]->flag |= same; srca[-1]->flag |= A_NEXT_SAME; } else srca[0]->flag |= A_ROOT_ART; } } return changed; } nn-6.7.4/aux.h0000644000175000017500000000025207751420622012523 0ustar mtpinsmtpins/* * Copyright (c) 2001-2003 Michael T Pins. All rights reserved. */ int aux_sh(article_header *, char *, char *, char *, char *, char *, int, int); nn-6.7.4/init.c0000644000175000017500000007144215213127140012664 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * .nn/init file handling */ #include #include #include #include #include #include #include "config.h" #include "global.h" #include "articles.h" #include "admin.h" #include "answer.h" #include "db.h" #include "execute.h" #include "folder.h" #include "group.h" #include "hostname.h" #include "init.h" #include "keymap.h" #include "kill.h" #include "macro.h" #include "menu.h" #include "newsrc.h" #include "nn.h" #include "printconf.h" #include "save.h" #include "sequence.h" #include "sort.h" #include "nn_term.h" #include "variable.h" #ifdef USE_MALLOC_H #include #endif /* init.c */ static char *strip_str(register char *cmd); static int split_command(register char *cmd); static char *argv(int i); static int is_sequence(char *cmd); static void load_init_file(char *name, FILE ** seq_hook_ptr, int only_seq); static void print_debug_info(void); static void print_command(char *str); static int do_show(char *table, int mode_arg); static int do_map(FILE * initf); static void parse_on_to_end(FILE * f); extern char *help_directory, *lib_directory; int in_init = 0; /* true when parsing init file */ int alt_cmd_key; /* K_ when parse_command returns AC_KEYCMD */ #ifndef __FreeBSD__ long initial_memory_break; /* for :debug statistics */ #endif int first_time_user = 0; static int init_err = 0; /* errors in init file */ extern char *term_name; extern FILE *loc_seq_hook, *glob_seq_hook; extern int list_offset; extern int in_menu_mode; extern char *dflt_enter_macro; /* extern char *dflt_exit_macro; */ extern int terminal_speed, slow_speed; extern char *pname; extern char *start_up_macro; extern char *newsrc_file; extern int in_menu_mode; extern int do_kill_handling; extern char printer[]; extern char *mail_box; void init_message(char *fmt,...) { va_list ap; va_start(ap, fmt); if (in_init) { visual_off(); printf("init error: "); vprintf(fmt, ap); putchar(NL); init_err++; } else vmsg(fmt, ap); va_end(ap); } #define MAXARG 10 static char *argvec[MAXARG + 2]; static int argc; static char * strip_str(register char *cmd) { if (cmd == NULL) return cmd; while (*cmd && isascii(*cmd) && isspace(*cmd)) cmd++; if (*cmd == NUL || *cmd == NL) return NULL; return cmd; } static int split_command(register char *cmd) { /* split command string */ for (argc = 0; argc < MAXARG + 2; argc++) argvec[argc] = NULL; strip_more: if ((cmd = strip_str(cmd)) == NULL || *cmd == '#') return 0; if (*cmd == ':') { cmd++; goto strip_more; } argc = 0; argvec[0] = cmd; return 1; } static char * argv(int i) { register char *cmd; if (i > MAXARG) return NULL; if (argc <= i) { if (argvec[argc]) { cmd = argvec[argc]; while (argc <= i) { while (*cmd && (!isascii(*cmd) || !isspace(*cmd))) cmd++; if (*cmd == NUL) { argc = MAXARG; break; } *cmd++ = NUL; if ((cmd = strip_str(cmd)) == NULL) { argc = MAXARG; break; } argvec[++argc] = cmd; } } else { argc = MAXARG; } } return argvec[i]; } static int is_sequence(char *cmd) { if (!split_command(cmd)) return 0; if ((cmd = argv(0)) == NULL) return 0; return strcmp(cmd, "sequence") == 0; } #define START_SEQUENCE 555 #define CHAIN_FILE 556 /* chain file */ #define STOP_FILE 557 /* stop */ static void load_init_file(char *name, FILE ** seq_hook_ptr, int only_seq) { FILE *init; char cmdbuf[1024], *cmd, *term; /* use cmdbuf temporarily (to handle @ expansion) */ for (cmd = cmdbuf; *name; name++) if (*name == '@') { term = term_name; while (term && *term) *cmd++ = *term++; } else *cmd++ = *name; *cmd = NUL; name = cmdbuf; chain_file: if (strchr(name, '/') == NULL) name = relative(nn_directory, name); init = open_file(name, OPEN_READ); if (init == NULL) return; while (fgets_multi(cmdbuf, 1024, init)) { if (only_seq) { if (!is_sequence(cmdbuf)) continue; *seq_hook_ptr = init; return; } /* we use AC_REDRAW to avoid !-commands clear the screen */ switch (parse_command(cmdbuf, AC_REDRAW, init)) { case CHAIN_FILE: fclose(init); name = argvec[argc]; /* ARGTAIL */ if (name == NULL) return; goto chain_file; case STOP_FILE: fclose(init); return; case START_SEQUENCE: if (seq_hook_ptr) { *seq_hook_ptr = init; return; /* no close !! */ } else { init_message("load file contains 'sequence'"); fclose(init); return; } } } fclose(init); } static char dflt_init_files[] = ",init"; void visit_init_file(int only_seq, char *first_arg) { char *next_arg; in_init = 1; load_init_file(relative(lib_directory, "setup"), (FILE **) NULL, 0); in_init = 0; if (first_arg && strncmp(first_arg, "-I", 2) == 0) { if (first_arg[2] == NUL) return; first_arg += 2; } else first_arg = dflt_init_files; in_init = 1; while (first_arg) { next_arg = strchr(first_arg, ','); if (next_arg) *next_arg++ = NUL; if (*first_arg == NUL) { if (glob_seq_hook == NULL) load_init_file(relative(lib_directory, "init"), &glob_seq_hook, only_seq); } else { if (loc_seq_hook != NULL) { fclose(loc_seq_hook); loc_seq_hook = NULL; } load_init_file(first_arg, &loc_seq_hook, only_seq); } first_arg = next_arg; } if (init_err) nn_exit(1); in_init = 0; } /* * parse a command (also :-commands) */ static char *sw_string; static int sw_loop_once; #define SWITCH(str) \ for (sw_string = str, sw_loop_once = 1; --sw_loop_once == 0; ) #define CASE(str) \ if (strcmp(sw_string, str) == 0) #define ARG(i, str) (argv(i) && strcmp(argv(i), str) == 0) #define ARGVAL(i) atol(argv(i)) #define ARGTAIL argvec[argc] static struct alt_commands { char *alt_name; int alt_len; int alt_type; } alt_commands[] = { "admin", 5, 0, "bug", 3, 0, "cd", 2, 1, "compile", 7, 0, "coredump", 8, 0, "cost", 4, 0, "decode", 6, 0, "define", 6, 0, "help", 4, 2, "load", 4, 0, "local", 5, 3, "lock", 4, 3, "make", 4, -1, "make map ", 9, -2, "man", 3, 0, "map", 3, 4, "mkdir", 5, 1, "motd", 4, 0, "patch", 5, 0, /* QUICK HACK */ "post", 4, 0, /* QUICK HACK */ "print", 5, -3, /* QUICK HACK */ "print-variables", 15, 0, "pwd", 3, 0, "rmail", 5, 0, "set", 3, 3, "show", 4, -1, "show config", 11, 0, "show groups", 11, -1, "show groups all", 15, 0, "show groups subscr", 18, 0, "show groups total", 17, 0, "show groups unsub", 17, 0, "show kill", 9, 0, "show map", 8, -1, "show map #", 10, 0, "show map key", 12, 0, "show map menu", 13, 0, "show map show", 13, 0, "show rc ", 8, 0, "sort", 4, -1, "sort arrival", 12, 0, "sort date", 9, 0, "sort lexical", 12, 0, "sort sender", 11, 0, "sort subject", 12, 0, "toggle", 6, 3, "unread", 6, 0, "unset", 5, 3, "unshar", 6, 0, /* QUICK HACK */ NULL, 0, 0 }; int alt_completion(char *buf, int index) { static char *head, *tail = NULL, buffer[FILENAME]; static int len; static struct alt_commands *alt, *help_alt; static fct_type other_compl; int temp; register char *p, *q; if (other_compl) { temp = CALL(other_compl) (buf, index); if (index == 0 && temp == 1 && tail) strcpy(tail, head); if (index < 0 || (index == 0 && temp == 0)) { other_compl = NULL; list_offset = 0; } return temp; } if (index < 0) return 0; if (buf) { if (index >= 1 && buf[0] == '!') return -1; /* :! is special */ head = buf; tail = buf + index; alt = help_alt = alt_commands; len = tail - head; other_compl = NULL; for (; alt->alt_name; alt++) { if (len <= alt->alt_len) continue; if (head[alt->alt_len] != SP) { if (alt->alt_type != -2) continue; if (strncmp(alt->alt_name, head, alt->alt_len)) continue; return -1; } index = strncmp(alt->alt_name, head, alt->alt_len); if (index < 0) continue; if (index > 0) break; if (alt->alt_type < 0) { if (len > alt->alt_len) continue; break; } if (alt->alt_type == 0) return -1; /* cannot be further compl */ head += alt->alt_len; while (*head && *head == SP) head++; len = tail - head; temp = -1; switch (alt->alt_type) { case 1: other_compl = file_completion; tail = NULL; temp = file_completion(head, len); break; case 2: other_compl = file_completion; sprintf(buffer, "%s.%s", relative(help_directory, "help"), head); len = strlen(buffer); head = buffer + len; list_offset = 5; temp = file_completion(buffer, len); break; case 3: /* [set ]variable[ value] */ for (p = head; *p;) if (*p++ == SP) return -1; other_compl = var_completion; var_compl_opts((int) (tail - buf)); tail = NULL; temp = var_completion(head, len); break; case 4: /* [map XXX ]Y command[ N] */ if (*head == '#') return -1; for (p = head, temp = 0; *p;) if (*p++ == SP) { while (*p && *p == SP) p++; head = p; temp++; } if (temp == 0) other_compl = keymap_completion; else if (temp == 2) other_compl = cmd_completion; else return -1; tail = NULL; len = p - head; temp = CALL(other_compl) (head, len); break; } if (temp <= 0) other_compl = NULL; return temp; } alt = alt_commands; return 1; } if (index) { list_completion((char *) NULL); if (help_alt->alt_name == NULL) help_alt = alt_commands; list_offset = 0; if ((p = strrchr(head, ' '))) list_offset = p - head; while (help_alt->alt_name) { if (len > help_alt->alt_len || (index = strncmp(help_alt->alt_name, head, len)) < 0) { help_alt++; continue; } if (index > 0) { help_alt = alt_commands; break; } p = help_alt->alt_name; if (list_completion(p) == 0) break; temp = help_alt->alt_len; if (help_alt->alt_type == -3) { help_alt++; continue; } do help_alt++; while (((q = help_alt->alt_name)) && help_alt->alt_len > temp && strncmp(p, q, temp) == 0); } fl; list_offset = 0; return 1; } for (; alt->alt_name; alt++) { if (len == 0) index = 0; else index = strncmp(alt->alt_name, head, len); if (index < 0) continue; if (index > 0) break; p = alt->alt_name; sprintf(tail, "%s%s", p + len, alt->alt_type <= -2 ? "" : " "); temp = alt->alt_len; if (alt->alt_type == -3) { alt++; return 1; } do alt++; while (((q = alt->alt_name)) && alt->alt_len > temp && strncmp(p, q, temp) == 0); return 1; } cmd_completion(head, len); if ((temp = cmd_completion((char *) NULL, 0))) { other_compl = cmd_completion; tail = NULL; } return temp; } static void print_debug_info(void) { #ifdef USE_MALLOC_H struct mallinfo mallinfo(), mi; #endif #ifndef __FreeBSD__ static long prev_mem = 0; long cur_mem; clrdisp(); tprintf("group=%s, nart=%ld\n\r", current_group->group_name, n_articles); cur_mem = (((long) sbrk(0)) - initial_memory_break) / 1024; tprintf("\nMemory usage: %ldk, previous: %ldk, change: %ldk\n\r", cur_mem, prev_mem, cur_mem - prev_mem); prev_mem = cur_mem; #endif #ifdef USE_MALLOC_H mi = mallinfo(); tprintf("\nMalloc info. Total allocation: %d\n\r", mi.arena); tprintf("Ordinary blocks: %d, space in use: %d, space free: %d\n\r", mi.ordblks, mi.uordblks, mi.fordblks); tprintf("Small blocks: %d, space in use: %d, space free: %d\n\r", mi.smblks, mi.usmblks, mi.fsmblks); tprintf("Holding blocks: %d, space in headers: %d\n\r", mi.hblks, mi.hblkhd); #endif any_key(0); } static void print_command(char *str) { char **av; char buf[1024]; if (!in_init) { msg(str); return; } buf[0] = NUL; for (av = argvec; *av; av++) { strcat(buf, " "); strcat(buf, *av); } init_message("%s: %s", str, buf); } static int do_show(char *table, int mode_arg) { register group_header *gh; int ret = 1; if (in_init || table == NULL) return 0; no_raw(); SWITCH(table) { CASE("config") { clrdisp(); print_config(stdout); any_key(0); break; } CASE("kill") { clrdisp(); dump_kill_list(); break; } CASE("groups") { clrdisp(); if ARG (mode_arg, "all") group_overview(1); else if ARG (mode_arg, "total") group_overview(2); else if ARG (mode_arg, "unsub") group_overview(3); else if ARG (mode_arg, "subscr") group_overview(4); else if ARG (mode_arg, "sequence") group_overview(-1); else group_overview(0); break; } CASE("map") { char *name; if ((name = argv(mode_arg)) == NULL) name = in_menu_mode ? "menu" : "show"; if (name[0] == '#') { clrdisp(); dump_multi_keys(); break; } SWITCH(name) { CASE("key") { clrdisp(); dump_global_map(); break; } if (dump_key_map(name) >= 0) break; init_message("unknown map '%s'", argv(mode_arg)); ret = 0; /* break; goto err */ } break; } CASE("rc") { if (argv(2)) { gh = lookup(argv(2)); if (gh == NULL) { msg("Unknown: %s", argv(2)); break; } } else gh = current_group; if (gh->group_flag & G_FAKED) break; clrdisp(); tprintf("Available: %ld - %ld (unread %ld)\n\n\r", (long) (gh->first_db_article), (long) (gh->last_db_article), (long) (gh->unread_count)); tprintf(".newsrc:\n\r>%s\r<%s\n\rselect:\n\r>%s\r<%s\n\r", gh->newsrc_line ? gh->newsrc_line : "(null)\n", gh->newsrc_orig == gh->newsrc_line ? "(same)\n" : gh->newsrc_orig ? gh->newsrc_orig : "(null)\n", gh->select_line ? gh->select_line : "(null)\n", gh->select_orig == gh->select_line ? "(same)\n" : gh->select_orig ? gh->select_orig : "(null)\n"); any_key(0); break; } init_message("unknown table '%s'", table); ret = 0; /* break; */ /* goto err; */ /* NOTREACHED */ } nn_raw(); return ret; /* err: nn_raw(); return 0; */ } static int do_map(FILE * initf) { int code, map_menu, map_show, must_redraw = 0; key_type bind_to; register struct key_map_def *map_def; register int *map; code = lookup_keymap(argv(1)); if (code < 0) { print_command("unknown map"); goto out; } map_def = &keymaps[code]; if (map_def->km_flag & K_GLOBAL_KEY_MAP) { if (argv(3) == NULL) goto mac_err; if (argv(2) == NULL) { dump_global_map(); return 1; } global_key_map[parse_key(argv(2))] = parse_key(argv(3)); return 0; } if (map_def->km_flag & K_MULTI_KEY_MAP) { key_type multi_buffer[16], *mb; int i; if (argv(1)[1] == NUL) { dump_multi_keys(); return 1; } if (isdigit(argv(1)[1])) bind_to = K_function(argv(1)[1] - '0'); else { bind_to = parse_key(argv(1) + 1); if (bind_to < K_up_arrow || bind_to > K_right_arrow) goto mac_err; } for (i = 2, mb = multi_buffer; argv(i); i++) *mb++ = parse_key(argv(i)); *mb = NUL; enter_multi_key(bind_to, (key_type *) copy_str((char *) multi_buffer)); return 0; } code = K_UNBOUND; map = map_def->km_map; map_show = map_def->km_flag & K_BOTH_MAPS; map_menu = map_def->km_flag & K_BIND_ORIG; if (ARG(3, "(")) { code = (int) m_define("-2", initf); must_redraw = 1; if (code == K_UNBOUND) goto mac_err; } if (code == K_UNBOUND && argv(3)) code = lookup_command(argv(3), map_def->km_flag & (K_ONLY_MENU | K_ONLY_MORE)); switch (code) { case K_INVALID - 1: init_message("Cannot bind '%s' in '%s' map", argv(3), argv(1)); goto out; case K_EQUAL_KEY: if (argv(4) == NULL) goto mac_err; code = map[parse_key(argv(4))]; break; case K_MACRO: case K_ARTICLE_ID: if (argv(4) == NULL) goto mac_err; code |= atoi(argv(4)); break; case K_PREFIX_KEY: if (argv(4) == NULL) goto mac_err; code = lookup_keymap(argv(4)); if (code < 0) { print_command("unknown prefix map"); goto out; } code |= K_PREFIX_KEY; break; } if (code == K_INVALID) { init_message("unknown key command: %s", argv(3)); goto out; } bind_to = parse_key(argv(2)); if (map_menu) { if (code & K_MACRO && orig_menu_map[bind_to] == 0) orig_menu_map[bind_to] = map[bind_to]; } map[bind_to] = code; if (map_show) more_key_map[bind_to] = code; goto out; mac_err: print_command("map argument missing"); out: return must_redraw; } static void parse_on_to_end(FILE * f) { register char *cp; char buf[1024]; static char *last_cmd_res = NULL; int i; int err_flag = 0; if (ARGTAIL == NULL || *ARGTAIL == NUL) goto on_err; cp = NULL; switch (*ARGTAIL) { case '#': /* on #... end: skipped (+hack for else) */ goto skip_to_end; case '`': /* on `shell command` str1 str2 ... */ { FILE *p; char *cmd = ARGTAIL + 1, *t; if ((cp = strrchr(cmd, '`')) == NULL) goto syntax_err; if ((t = strip_str(cp + 1)) == NULL) goto syntax_err; *cp = NUL; ARGTAIL = t; if (cmd[0]) { buf[0] = NUL; if ((p = popen(cmd, "r"))) { if (fgets(buf, 1024, p)) buf[strlen(buf) - 1] = NUL; pclose(p); } if (last_cmd_res != NULL && strcmp(last_cmd_res, buf)) { free(last_cmd_res); last_cmd_res = NULL; } if (buf[0] == NUL) goto skip_to_end; last_cmd_res = copy_str(buf); } for (i = 1; argv(i) != NULL; i++) if (strcmp(argv(i), last_cmd_res) == 0) return; } goto skip_to_end; case '$': /* on $VAR [ a b c ... ] */ cp = argv(1); if ((cp = getenv(cp + 1)) == NULL) goto skip_to_end; if (ARGTAIL == NULL) return; for (i = 2; argv(i) != NULL; i++) if (strcmp(argv(i), cp) == 0) return; goto skip_to_end; case '!': /* on !shell-command */ cp = ARGTAIL + 1; break; case '[': /* on [ test ] */ cp = ARGTAIL + strlen(ARGTAIL) - 1; if (*cp != ']') goto syntax_err; cp = ARGTAIL; break; default: break; } if (cp) { if (run_shell(cp, -2, 1) == 0) return; goto skip_to_end; } if (argv(1) == NULL) goto on_err; SWITCH(argv(1)) { CASE("entry") { group_header *gh; char *macro; int ii; macro = parse_enter_macro(f, NL); if (ARGTAIL) { for (ii = 2; argv(ii); ii++) { start_group_search(argv(ii)); while ((gh = get_group_search())) gh->enter_macro = macro; } } else dflt_enter_macro = macro; return; } /* CASE( "exit" ) { dflt_exit_macro = parse_enter_macro(f, NL); return; } */ CASE("slow") { if (terminal_speed <= (slow_speed / 10)) return; break; } CASE("fast") { if (terminal_speed > (slow_speed / 10)) return; break; } CASE("term") { int ii; for (ii = 2; argv(ii) != NULL; ii++) if (strcmp(argv(ii), term_name) == 0) return; break; } CASE("host") { char local_host[100]; int ii; nn_gethostname(local_host, 100); for (ii = 2; argv(ii) != NULL; ii++) if (strcmp(argv(ii), local_host) == 0) return; break; } CASE("program") { char *pname1; int ii; for (pname1 = pname; *pname1 == 'n'; pname1++); for (ii = 2; argv(ii) != NULL; ii++) { if (strcmp(argv(ii), pname) == 0) return; if (pname1[0] && strcmp(argv(ii), pname1) == 0) return; } break; } CASE("start-up") { start_up_macro = parse_enter_macro(f, NL); return; } CASE("first-use") { if (!first_time_user) break; if (argv(2) == NULL || ARG(2, "all")) return; if (newsrc_file == NULL) /* == code from visit_rc_file == */ newsrc_file = home_relative(".newsrc"); if (ARG(2, "old") && file_exist(newsrc_file, (char *) NULL)) return; if (ARG(2, "new") && !file_exist(newsrc_file, (char *) NULL)) return; break; } err_flag = 1; /* goto on_err; */ } if (err_flag == 1) goto on_err; skip_to_end: while (fgets_multi(buf, 1024, f)) { for (cp = buf; *cp && isascii(*cp) && isspace(*cp); cp++); if (*cp != 'e') continue; if (strncmp(cp, "end", 3) == 0) return; if (strncmp(cp, "else", 4) == 0) return; } init_message("end missing (on %s)", argv(1)); return; on_err: init_message("on `what'?"); return; syntax_err: init_message("syntax error: on %s", ARGTAIL); } int parse_command(char *cmd, int ok_val, FILE * initf) { if (!split_command(cmd)) return ok_val; if (*ARGTAIL == '!') { if (ok_val == AC_UNCHANGED) { /* in macro */ if (ARGTAIL[1] == '!') /* !!cmd => guarantee no output! */ run_shell(ARGTAIL + 2, -2, 1); else run_shell(ARGTAIL + 1, -1, 1); return ok_val; } if (run_shell(ARGTAIL + 1, ok_val == AC_PROMPT ? 1 : 0, in_init) >= 0) { any_key(0); return AC_REDRAW; } return ok_val; } SWITCH(argv(0)) { CASE("unset") { if (argv(1) == NULL) goto stx_err; if (set_variable(argv(1), 0, (char *) NULL)) return AC_REDRAW; else return ok_val; } CASE("local") { if (ARGTAIL == NULL) goto stx_err; cmd = argv(1); if (!push_variable(cmd)) return ok_val; if (ARGTAIL && set_variable(cmd, 1, ARGTAIL)) return AC_REDRAW; else return ok_val; } CASE("set") { if (ARGTAIL == NULL || ARGTAIL[0] == '/') { disp_variables(0, ARGTAIL); return AC_REDRAW; } cmd = argv(1); /* get ARGTAIL right */ if (cmd != NULL && strcmp(cmd, "all") == 0) { disp_variables(1, (char *) NULL); return AC_REDRAW; } if (set_variable(cmd, 1, ARGTAIL)) return AC_REDRAW; else return ok_val; } CASE("toggle") { if (argv(1) == NULL) goto stx_err; toggle_variable(argv(1)); break; } CASE("lock") { if (argv(1) == NULL) goto stx_err; lock_variable(argv(1)); break; } CASE("define") { if (in_init) { if (argv(1) == NULL) { init_message("macro number missing"); break; } m_define(argv(1), initf); } else if (m_define(argv(1), (FILE *) NULL)) return AC_REDRAW; break; } CASE("map") { if (argv(2) == NULL) { if (do_show("map", 1)) return AC_REDRAW; break; } if (do_map(initf)) return AC_REDRAW; break; } CASE("make") { if (ARG(1, "map") && argv(2)) { switch (make_keymap(argv(2))) { case -1: init_message("map %s already exists", argv(2)); break; case -2: init_message("cannot make %s: too many maps", argv(2)); break; } break; } print_command("invalid make command"); break; } CASE("cd") { if (change_dir(argv(1), in_init)) init_message("chdir %s FAILED", argv(1)); break; } CASE("clear") { clrdisp(); break; } if (in_init) { CASE("load") { if (argv(1)) load_init_file(argv(1), (FILE **) NULL, 0); break; } CASE("on") { parse_on_to_end(initf); break; } CASE("else") { ARGTAIL = "#"; /* skip to end */ parse_on_to_end(initf); break; } CASE("end") { break; } CASE("echo") { tprintf("\r%s\n\r", ARGTAIL); break; } CASE("error") { nn_exitmsg(1, "%s\n", ARGTAIL); } CASE("exit") { nn_exit(ARGTAIL != NULL ? atoi(ARGTAIL) : 0); } CASE("chain") { return CHAIN_FILE; } CASE("stop") { return STOP_FILE; } CASE("sequence") { return START_SEQUENCE; } CASE("save-files") { parse_save_files(initf); break; } print_command("unknown command"); break; } /* * commands only available from : command line */ if (ok_val != AC_REDRAW) { alt_cmd_key = lookup_command(sw_string, in_menu_mode ? K_ONLY_MENU : K_ONLY_MORE); if (alt_cmd_key > K_INVALID && alt_cmd_key != K_HELP) { if (alt_cmd_key == K_MACRO) { if (ARGTAIL == NULL) break; alt_cmd_key |= atoi(ARGTAIL); } return AC_KEYCMD; } } CASE("load") { clrdisp(); in_init = 1; init_err = 0; load_init_file(argv(1) ? argv(1) : "init", (FILE **) NULL, 0); in_init = 0; if (init_err) any_key(0); return AC_REDRAW; } CASE("q") { break; } CASE("Q") { return AC_QUIT; } CASE("q!") { if (restore_bak()) return AC_QUIT; break; } CASE("x") { update_rc_all(current_group, 0); return AC_QUIT; } CASE("help") { if (argv(1) == NULL) display_help("help"); else display_help(argv(1)); return AC_REDRAW; } CASE("man") { char *manual; group_header *orig_group; manual = relative(help_directory, "Manual"); if (!file_exist(manual, "fr")) { manual = relative(lib_directory, "Manual"); if (!file_exist(manual, "fr")) { msg("Online manual is not available"); break; } } orig_group = current_group; folder_menu(manual, 1); init_group(orig_group); return AC_REDRAW; } CASE("motd") { if (display_motd(0)) return AC_REDRAW; msg("no motd file"); break; } CASE("sort") { if (argv(1) == NULL) sort_articles(-1); else if (ARG(1, "no") || ARG(1, "arrival")) sort_articles(0); else if ARG (1, "subject") sort_articles(1); else if ARG (1, "lexical") sort_articles(2); else if (ARG(1, "date") || ARG(1, "age")) sort_articles(3); else if (ARG(1, "sender") || ARG(1, "from")) sort_articles(4); else { msg("Unknown sort mode '%s'", argv(1)); break; } return AC_REORDER; } CASE("unread") { group_header *gh; int ix; if (argv(1) && (gh = lookup(argv(1))) != NULL) ix = 2; else { ix = 1; gh = current_group; } if (gh == current_group) return AC_REENTER_GROUP; if (argv(ix)) { if (!restore_rc(gh, gh->last_db_article - (article_number) ARGVAL(ix))) break; } else if (!restore_unread(gh)) break; break; } CASE("dump") { if (do_show(argv(1), 2)) return AC_REDRAW; break; } CASE("show") { if (do_show(argv(1), 2)) return AC_REDRAW; break; } CASE("compile") { clrdisp(); rm_kill_file(); free_kill_entries(); do_kill_handling = init_kill() && do_kill_handling; return AC_REDRAW; } CASE("print-variables") { FILE *p; if ((p = popen(ARGTAIL ? ARGTAIL : printer, "w"))) { print_variable_config(p, 1); pclose(p); msg("Variables printed"); } break; } CASE("pwd") { FILE *p = popen("exec pwd", "r"); char dir[FILENAME]; if (p) { if (fgets(dir, FILENAME, p)) { dir[strlen(dir) - 1] = NUL; msg("%s", dir); } pclose(p); } break; } CASE("rmail") { group_header *orig_group; int rv; if (mail_box == NULL) { msg("'mail' path not defined"); break; } orig_group = current_group; rv = folder_menu(mail_box, 2); init_group(orig_group); return rv == ME_NO_REDRAW ? ok_val : AC_REDRAW; } CASE("mkdir") { char *dir; char name_buf[FILENAME]; if ((dir = run_mkdir(argv(1), name_buf))) { prompt("Change to %s", dir); if (yes(0)) change_dir(dir, 0); } break; } CASE("sh") { suspend_nn(); s_redraw = 0; return AC_REDRAW; } CASE("admin") { group_header *cur_group; cur_group = current_group; no_raw(); clrdisp(); tprintf("\n\n\n\rADMINISTRATION MODE\r\n\n\n"); admin_mode((char *) NULL); clrdisp(); nn_raw(); init_group(cur_group); return AC_REDRAW; } CASE("cost") { #ifdef ACCOUNTING gotoxy(0, Lines - 1); clrline(); account('C', 1); #else msg("No accounting"); #endif break; } CASE("bug") { if (answer((article_header *) NULL, K_BUG_REPORT, 0)) return AC_REDRAW; break; } CASE("debug") { print_debug_info(); return AC_REDRAW; } CASE("coredump") { unset_raw(); abort(); } msg("unknown command: \"%s\"", argv(0)); } return ok_val; stx_err: print_command("syntax error"); return ok_val; } void display_help(char *subject) { char file[FILENAME]; strcpy(file, "help."); strcpy(file + 5, subject); display_file(file, CLEAR_DISPLAY | CONFIRMATION); } nn-6.7.4/nntp.h0000644000175000017500000000664110217207150012703 0ustar mtpinsmtpins/* * Response codes for NNTP server * * @(#)nntp.h 1.7 (Berkeley) 1/11/88 * * First digit: * * 1xx Informative message * 2xx Command ok * 3xx Command ok so far, continue * 4xx Command was correct, but couldn't be performed * for some specified reason. * 5xx Command unimplemented, incorrect, or a * program error has occured. * * Second digit: * * x0x Connection, setup, miscellaneous * x1x Newsgroup selection * x2x Article selection * x3x Distribution * x4x Posting */ #ifndef _NN_NNTP_H #define _NN_NNTP_H 1 #define CHAR_INF '1' #define CHAR_OK '2' #define CHAR_CONT '3' #define CHAR_ERR '4' #define CHAR_FATAL '5' #define INF_HELP 100 /* Help text on way */ #define INF_DEBUG 199 /* Debug output */ #define OK_CANPOST 200 /* Hello; you can post */ #define OK_NOPOST 201 /* Hello; you can't post */ #define OK_SLAVE 202 /* Slave status noted */ #define OK_GOODBYE 205 /* Closing connection */ #define OK_GROUP 211 /* Group selected */ #define OK_GROUPS 215 /* Newsgroups follow */ #define OK_ARTICLE 220 /* Article (head & body) follows */ #define OK_HEAD 221 /* Head follows */ #define OK_BODY 222 /* Body follows */ #define OK_NOTEXT 223 /* No text sent -- stat, next, last */ #define OK_NOV 224 /* Nov data follows */ #define OK_NEWNEWS 230 /* New articles by message-id follow */ #define OK_NEWGROUPS 231 /* New newsgroups follow */ #define OK_XFERED 235 /* Article transferred successfully */ #define OK_POSTED 240 /* Article posted successfully */ #define OK_AUTH 281 /* Authentication completed OK */ #define CONT_XFER 335 /* Continue to send article */ #define CONT_POST 340 /* Continue to post article */ #define OK_NEEDPASS 381 /* Authentication username ok, need passwd */ #define ERR_GOODBYE 400 /* Have to hang up for some reason */ #define ERR_NOGROUP 411 /* No such newsgroup */ #define ERR_NCING 412 /* Not currently in newsgroup */ #define ERR_NOCRNT 420 /* No current article selected */ #define ERR_NONEXT 421 /* No next article in this group */ #define ERR_NOPREV 422 /* No previous article in this group */ #define ERR_NOARTIG 423 /* No such article in this group */ #define ERR_NOART 430 /* No such article at all */ #define ERR_GOTIT 435 /* Already got that article, don't send */ #define ERR_XFERFAIL 436 /* Transfer failed */ #define ERR_XFERRJCT 437 /* Article rejected, don't resend */ #define ERR_NOPOST 440 /* Posting not allowed */ #define ERR_POSTFAIL 441 /* Posting failed */ #define ERR_NEEDAUTH 480 /* need to authenticate with "authinfo" */ #define ERR_COMMAND 500 /* Command not recognized */ #define ERR_CMDSYN 501 /* Command syntax error */ #define ERR_ACCESS 502 /* Access to server denied */ #define ERR_FAULT 503 /* Program fault, command not performed */ /* RFC 977 defines this; don't change it. */ #define NNTP_STRLEN 512 void nntp_check(void); int nntp_set_group(group_header *); FILE *nntp_get_newsgroups(void); FILE *nntp_get_article(article_number, int); char *nntp_get_filename(article_number, group_header *); void nntp_close_server(void); void nntp_cleanup(void); int nntp_post(char *temp_file); #ifdef NOV struct novgroup *nntp_get_overview(group_header *, article_number, article_number); FILE *nntp_fopen_list(char *); char *nntp_fgets(char *, int); #else int nntp_get_active(void); article_number *nntp_get_article_list(group_header *); #endif #endif /* _NN_NNTP_H */ nn-6.7.4/README0000644000175000017500000000344310225401464012433 0ustar mtpinsmtpins Read INSTALLATION for instructions on configuring/compiling/installing nn. Major differences from nn-6.6.5: The name length displayed is now based upon the width of the screen. nn will now correctly parse From: lines that contain () For example: From: "Jesse James (home)" used to display as "home", the new behavior will display it as "Jesse James". However, this may affect killfiles that depend upon the old behavior. To use the old behavior, set the new run-time variable 'old-packname' to true. Header check macro is now case insensitive for both news and folders. Added GPG support, which comes with a new run-time variable 'sign-type', and a compile-time variable SIGN_TYPE to set the default to either pgp or gpg. nn will now look in /etc/resolv.conf for a domain if DOMAIN isn't defined and hostname isn't a FQDN. new command ":show config" will show the compile-time definitions. nntp-user and nntp-password are now be settable in the init file. Note that this may have serious security ramifications if the nntp-password and the users password are identical. DISTRIBUTION: The current version of nn is available via anonymous ftp from ftp.nndev.org BUG REPORTS and SUGGESTIONS: Please send bug reports (and fixes) to the following address: nn-bugs@nndev.org You may also use nn-bugs for suggestions for improvements. The easiest way to send a bug report is by using the :bug command in nn. NN HAS ITS OWN NEWS GROUP: There is an unmoderated news group dedicated to nn: news.software.nn The news.software.nn group is used for discussion on all subjects related to the nn news reader. This includes, but is not limited to, questions, answers, ideas, hints, information from the developer, patches, etc. nn-6.7.4/usercheck.c0000644000175000017500000000041410233745620013672 0ustar mtpinsmtpins#include #include #include /* check for whether caller is a specific uid (numeric) */ int main(int argc, char *argv[]) { int uid = 0; if (argc > 1) uid = atoi(argv[1]); exit(getuid() == uid ? 0 : 1); } nn-6.7.4/regexp.c0000644000175000017500000007236310223157714013224 0ustar mtpinsmtpins/* * @(#)regexp.c 1.3 of 18 April 87 * * Copyright (c) 1986 by University of Toronto. * Written by Henry Spencer. Not derived from licensed software. * * Permission is granted to anyone to use this software for any * purpose on any computer system, and to redistribute it freely, * subject to the following restrictions: * * 1. The author is not responsible for the consequences of use of * this software, no matter how awful, even if they arise * from defects in it. * * 2. The origin of this software must not be misrepresented, either * by explicit claim or by omission. * * 3. Altered versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * Beware that some of this code is subtly aware of the way operator * precedence is structured in regular expressions. Serious changes in * regular-expression syntax might require a total rethink. */ /* * NOTICE: THIS CODE HAS BEEN MODIFIED TO FIT THE NN ENVIRONMENT. * * modified by Kim Fabricius Storm * modified by Michael T. Pins */ #include #include #include #include #include "config.h" #include "global.h" #include "regexp.h" #include "nn_term.h" /* * The "internal use only" fields in regexp.h are present to pass info from * compile to execute that permits the execute phase to run lots faster on * simple cases. They are: * * regstart char that must begin a match; '\0' if none obvious * reganch is the match anchored (at beginning-of-line only)? * regmust string (pointer into program) that match must include, or NULL * regmlen length of regmust string * * Regstart and reganch permit very fast decisions on suitable starting points * for a match, cutting down the work a lot. Regmust permits fast rejection * of lines that cannot possibly match. The regmust tests are costly enough * that regcomp() supplies a regmust only if the r.e. contains something * potentially expensive (at present, the only such thing detected is * or + * at the start of the r.e., which can involve a lot of backup). Regmlen is * supplied because the test in regexec() needs it and regcomp() is computing * it anyway. */ /* * Structure for regexp "program". This is essentially a linear encoding * of a nondeterministic finite-state machine (aka syntax charts or * "railroad normal form" in parsing technology). Each node is an opcode * plus a "next" pointer, possibly plus an operand. "Next" pointers of * all nodes except BRANCH implement concatenation; a "next" pointer with * a BRANCH on both ends of it is connecting two alternatives. (Here we * have one of the subtle syntax dependencies: an individual BRANCH (as * opposed to a collection of them) is never concatenated with anything * because of operator precedence.) The operand of some types of node is * a literal string; for others, it is a node leading into a sub-FSM. In * particular, the operand of a BRANCH node is the first node of the branch. * (NB this is *not* a tree structure: the tail of the branch connects * to the thing following the set of BRANCHes.) The opcodes are: */ /* definition number opnd? meaning */ #define END 0 /* no End of program. */ #define BOL 1 /* no Match "" at beginning of line. */ #define EOL 2 /* no Match "" at end of line. */ #define ANY 3 /* no Match any one character. */ #define ANYOF 4 /* str Match any character in this string. */ #define ANYBUT 5 /* str Match any character not in this * string. */ #define BRANCH 6 /* node Match this alternative, or the * next... */ #define BACK 7 /* no Match "", "next" ptr points backward. */ #define EXACTLY 8 /* str Match this string. */ #define NOTHING 9 /* no Match empty string. */ #define STAR 10 /* node Match this (simple) thing 0 or more * times. */ #define PLUS 11 /* node Match this (simple) thing 1 or more * times. */ #define W_START 12 /* no Match beginning of word */ #define W_END 13 /* no Match end of word */ #define OPEN 20 /* no Mark this point in input as start of * #n. */ /* OPEN+1 is number 1, etc. */ #define CLOSE 30 /* no Analogous to OPEN. */ /* * Opcode notes: * * BRANCH The set of branches constituting a single choice are hooked * together with their "next" pointers, since precedence prevents * anything being concatenated to any individual branch. The * "next" pointer of the last BRANCH in a choice points to the * thing following the whole choice. This is also where the * final "next" pointer of each individual branch points; each * branch starts with the operand node of a BRANCH node. * * BACK Normal "next" pointers all implicitly point forward; BACK * exists to make loop structures possible. * * STAR,PLUS '?', and complex '*' and '+', are implemented as circular * BRANCH structures using BACK. Simple cases (one character * per match) are implemented with STAR and PLUS for speed * and to minimize recursive plunges. * * OPEN,CLOSE ...are numbered at compile time. */ /* * A node is one char of opcode followed by two chars of "next" pointer. * "Next" pointers are stored as two 8-bit pieces, high order first. The * value is a positive offset from the opcode of the node containing it. * An operand, if any, simply follows the node. (Note that much of the * code generation knows about this implicit relationship.) * * Using two bytes for the "next" pointer is vast overkill for most things, * but allows patterns to get big without disasters. */ #define OP(p) (*(p)) #define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377)) #define OPERAND(p) ((p) + 3) /* * Utility definitions. */ #ifndef CHARBITS #define UCHARAT(p) ((int)*(unsigned char *)(p)) #else #define UCHARAT(p) ((int)*(p)&CHARBITS) #endif #define FAIL(m) { regerror(m); return(NULL); } #define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?') #define META "^$.[()|?+*\\" /* * Flags to be passed up and down. */ #define HASWIDTH 01 /* Known never to match null string. */ #define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */ #define SPSTART 04 /* Starts with * or +. */ #define WORST 0 /* Worst case. */ /* * Global work variables for regcomp(). */ static char *regparse; /* Input-scan pointer. */ static int regnpar; /* () count. */ static char regdummy; static char *regcode; /* Code-emit pointer; ®dummy = don't. */ static long regsize; /* Code size. */ /* * Forward declarations for regcomp()'s friends. */ #ifndef STATIC #define STATIC static #endif STATIC char *reg(int, int *); STATIC char *regbranch(int *); STATIC char *regpiece(int *); STATIC char *regatom(int *); STATIC char *regnode(char); STATIC char *regnext(register char *); STATIC void regc(char); STATIC void reginsert(char, char *); STATIC void regtail(char *, char *); STATIC void regoptail(char *, char *); STATIC void regerror(char *); #ifdef STRCSPN STATIC int strcspn(char *, char *); #endif /* - regcomp - compile a regular expression into internal code * * We can't allocate space until we know how big the compiled form will be, * but we can't compile it (and thus know how big it is) until we've got a * place to put the code. So we cheat: we compile it twice, once with code * generation turned off and size counting turned on, and once "for real". * This also means that we don't allocate space until we are sure that the * thing really will compile successfully, and we never have to move the * code and thus invalidate pointers into it. (Note that it has to be in * one piece because free() must be able to free it all.) * * Beware that the optimization-preparation code in here knows about some * of the structure of the compiled regexp. */ regexp * regcomp(char *exp) { register regexp *r; register char *scan; register char *longest; register int len; int flags; if (exp == NULL) FAIL("NULL argument"); /* First pass: determine size, legality. */ regparse = exp; regnpar = 1; regsize = 0L; regcode = ®dummy; regc((char) MAGIC); if (reg(0, &flags) == NULL) return (NULL); /* Small enough for pointer-storage convention? */ if (regsize >= 32767L) /* Probably could be 65535L. */ FAIL("regexp too big"); /* Allocate space. */ r = (regexp *) malloc(sizeof(regexp) + (unsigned) regsize); if (r == NULL) FAIL("out of space"); /* Second pass: emit code. */ regparse = exp; regnpar = 1; regcode = r->program; regc((char) MAGIC); if (reg(0, &flags) == NULL) return (NULL); /* Dig out information for optimizations. */ r->regstart = '\0'; /* Worst-case defaults. */ r->reganch = 0; r->regmust = NULL; r->regmlen = 0; scan = r->program + 1; /* First BRANCH. */ if (OP(regnext(scan)) == END) { /* Only one top-level choice. */ scan = OPERAND(scan); /* Starting-point info. */ if (OP(scan) == EXACTLY) r->regstart = *OPERAND(scan); else if (OP(scan) == BOL) r->reganch++; /* * If there's something expensive in the r.e., find the longest * literal string that must appear and make it the regmust. Resolve * ties in favor of later strings, since the regstart check works * with the beginning of the r.e. and avoiding duplication * strengthens checking. Not a strong reason, but sufficient in the * absence of others. */ if (flags & SPSTART) { longest = NULL; len = 0; for (; scan != NULL; scan = regnext(scan)) if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) { longest = OPERAND(scan); len = strlen(OPERAND(scan)); } r->regmust = longest; r->regmlen = len; } } return (r); } /* - reg - regular expression, i.e. main body or parenthesized thing * * Caller must absorb opening parenthesis. * * Combining parenthesis handling with the base level of regular expression * is a trifle forced, but the need to tie the tails of the branches to what * follows makes it hard to avoid. * * paren: Parenthesized? */ static char * reg(int paren, int *flagp) { register char *ret; register char *br; register char *ender; register int parno = 0; int flags; *flagp = HASWIDTH; /* Tentatively. */ /* Make an OPEN node, if parenthesized. */ if (paren) { if (regnpar >= NSUBEXP) FAIL("too many ()"); parno = regnpar; regnpar++; ret = regnode(OPEN + parno); } else ret = NULL; /* Pick up the branches, linking them together. */ br = regbranch(&flags); if (br == NULL) return (NULL); if (ret != NULL) regtail(ret, br); /* OPEN -> first. */ else ret = br; if (!(flags & HASWIDTH)) *flagp &= ~HASWIDTH; *flagp |= flags & SPSTART; while (*regparse == '|') { regparse++; br = regbranch(&flags); if (br == NULL) return (NULL); regtail(ret, br); /* BRANCH -> BRANCH. */ if (!(flags & HASWIDTH)) *flagp &= ~HASWIDTH; *flagp |= flags & SPSTART; } /* Make a closing node, and hook it on the end. */ ender = regnode((paren) ? CLOSE + parno : END); regtail(ret, ender); /* Hook the tails of the branches to the closing node. */ for (br = ret; br != NULL; br = regnext(br)) regoptail(br, ender); /* Check for proper termination. */ if (paren && *regparse++ != ')') { FAIL("unmatched ()"); } else if (!paren && *regparse != '\0') { if (*regparse == ')') { FAIL("unmatched ()"); } else FAIL("junk on end");/* "Can't happen". */ /* NOTREACHED */ } return (ret); } /* - regbranch - one alternative of an | operator * * Implements the concatenation operator. */ static char * regbranch(int *flagp) { register char *ret; register char *chain; register char *latest; int flags; *flagp = WORST; /* Tentatively. */ ret = regnode(BRANCH); chain = NULL; while (*regparse != '\0' && *regparse != '|' && *regparse != ')') { latest = regpiece(&flags); if (latest == NULL) return (NULL); *flagp |= flags & HASWIDTH; if (chain == NULL) /* First piece. */ *flagp |= flags & SPSTART; else regtail(chain, latest); chain = latest; } if (chain == NULL) /* Loop ran zero times. */ (void) regnode(NOTHING); return (ret); } /* - regpiece - something followed by possible [*+?] * * Note that the branching code sequences used for ? and the general cases * of * and + are somewhat optimized: they use the same NOTHING node as * both the endmarker for their branch list and the body of the last branch. * It might seem that this node could be dispensed with entirely, but the * endmarker role is not redundant. */ static char * regpiece(int *flagp) { register char *ret; register char op; register char *next; int flags; ret = regatom(&flags); if (ret == NULL) return (NULL); op = *regparse; if (!ISMULT(op)) { *flagp = flags; return (ret); } if (!(flags & HASWIDTH) && op != '?') FAIL("*+ operand could be empty"); *flagp = (op != '+') ? (WORST | SPSTART) : (WORST | HASWIDTH); if (op == '*' && (flags & SIMPLE)) reginsert(STAR, ret); else if (op == '*') { /* Emit x* as (x&|), where & means "self". */ reginsert(BRANCH, ret); /* Either x */ regoptail(ret, regnode(BACK)); /* and loop */ regoptail(ret, ret); /* back */ regtail(ret, regnode(BRANCH)); /* or */ regtail(ret, regnode(NOTHING)); /* null. */ } else if (op == '+' && (flags & SIMPLE)) reginsert(PLUS, ret); else if (op == '+') { /* Emit x+ as x(&|), where & means "self". */ next = regnode(BRANCH); /* Either */ regtail(ret, next); regtail(regnode(BACK), ret); /* loop back */ regtail(next, regnode(BRANCH)); /* or */ regtail(ret, regnode(NOTHING)); /* null. */ } else if (op == '?') { /* Emit x? as (x|) */ reginsert(BRANCH, ret); /* Either x */ regtail(ret, regnode(BRANCH)); /* or */ next = regnode(NOTHING);/* null. */ regtail(ret, next); regoptail(ret, next); } regparse++; if (ISMULT(*regparse)) FAIL("nested *?+"); return (ret); } /* - regatom - the lowest level * * Optimization: gobbles an entire sequence of ordinary characters so that * it can turn them into a single node, which is smaller to store and * faster to run. Backslashed characters are exceptions, each becoming a * separate node; the code is simpler that way and it's not worth fixing. */ static char * regatom(int *flagp) { register char *ret; int flags; *flagp = WORST; /* Tentatively. */ switch (*regparse++) { case '^': ret = regnode(BOL); break; case '$': ret = regnode(EOL); break; case '.': ret = regnode(ANY); *flagp |= HASWIDTH | SIMPLE; break; case '[':{ register int class; register int classend; if (*regparse == '^') { /* Complement of range. */ ret = regnode(ANYBUT); regparse++; } else ret = regnode(ANYOF); if (*regparse == ']' || *regparse == '-') regc(*regparse++); while (*regparse != '\0' && *regparse != ']') { if (*regparse == '-') { regparse++; if (*regparse == ']' || *regparse == '\0') regc('-'); else { class = UCHARAT(regparse - 2) + 1; classend = UCHARAT(regparse); if (class > classend + 1) FAIL("invalid [] range"); for (; class <= classend; class++) regc(class); regparse++; } } else regc(*regparse++); } regc('\0'); if (*regparse != ']') FAIL("unmatched []"); regparse++; *flagp |= HASWIDTH | SIMPLE; } break; case '(': ret = reg(1, &flags); if (ret == NULL) return (NULL); *flagp |= flags & (HASWIDTH | SPSTART); break; case '\0': case '|': case ')': FAIL("internal urp"); /* Supposed to be caught earlier. */ break; case '?': case '+': case '*': FAIL("?+* follows nothing"); break; case '\\': switch (*regparse++) { case '\0': FAIL("trailing \\"); break; /* for lint, we can't actually get here */ case '<': ret = regnode(W_START); break; case '>': ret = regnode(W_END); break; default: ret = regnode(EXACTLY); regc(*regparse); regc('\0'); *flagp |= HASWIDTH | SIMPLE; break; } break; default:{ register int len; register char ender; regparse--; len = strcspn(regparse, META); if (len <= 0) FAIL("internal disaster"); ender = *(regparse + len); if (len > 1 && ISMULT(ender)) len--; /* Back off clear of ?+* operand. */ *flagp |= HASWIDTH; if (len == 1) *flagp |= SIMPLE; ret = regnode(EXACTLY); while (len > 0) { regc(*regparse++); len--; } regc('\0'); } break; } return (ret); } /* - regnode - emit a node */ static char * /* Location. */ regnode(char op) { register char *ret; register char *ptr; ret = regcode; if (ret == ®dummy) { regsize += 3; return (ret); } ptr = ret; *ptr++ = op; *ptr++ = '\0'; /* Null "next" pointer. */ *ptr++ = '\0'; regcode = ptr; return (ret); } /* - regc - emit (if appropriate) a byte of code */ static void regc(char b) { if (regcode != ®dummy) *regcode++ = b; else regsize++; } /* - reginsert - insert an operator in front of already-emitted operand * * Means relocating the operand. */ static void reginsert(char op, char *opnd) { register char *src; register char *dst; register char *place; if (regcode == ®dummy) { regsize += 3; return; } src = regcode; regcode += 3; dst = regcode; while (src > opnd) *--dst = *--src; place = opnd; /* Op node, where operand used to be. */ *place++ = op; *place++ = '\0'; *place++ = '\0'; } /* - regtail - set the next-pointer at the end of a node chain */ static void regtail(char *p, char *val) { register char *scan; register char *temp; register int offset; if (p == ®dummy) return; /* Find last node. */ scan = p; for (;;) { temp = regnext(scan); if (temp == NULL) break; scan = temp; } if (OP(scan) == BACK) offset = scan - val; else offset = val - scan; *(scan + 1) = (offset >> 8) & 0377; *(scan + 2) = offset & 0377; } /* - regoptail - regtail on operand of first argument; nop if operandless */ static void regoptail(char *p, char *val) { /* "Operandless" and "op != BRANCH" are synonymous in practice. */ if (p == NULL || p == ®dummy || OP(p) != BRANCH) return; regtail(OPERAND(p), val); } /* * regexec and friends */ /* * Global work variables for regexec(). */ static char *reginput; /* String-input pointer. */ static char *regbol; /* Beginning of input, for ^ check. */ static char **regstartp; /* Pointer to startp array. */ static char **regendp; /* Ditto for endp. */ /* * Forwards. */ STATIC int regtry(regexp *, char *); STATIC int regmatch(char *); STATIC int regrepeat(char *); #ifdef DEBUG int regnarrate = 0; void regdump(regexp *); STATIC char *regprop(char *); #endif /* - regexec - match a regexp against a string */ int regexec(register regexp * prog, register char *string) { register char *s; /* Be paranoid... */ if (prog == NULL || string == NULL) { regerror("NULL parameter"); return (0); } /* Check validity of program. */ if (UCHARAT(prog->program) != MAGIC) { regerror("corrupted program"); return (0); } /* If there is a "must appear" string, look for it. */ if (prog->regmust != NULL) { s = string; while ((s = strchr(s, prog->regmust[0])) != NULL) { if (strncmp(s, prog->regmust, prog->regmlen) == 0) break; /* Found it. */ s++; } if (s == NULL) /* Not present. */ return (0); } /* Mark beginning of line for ^ . */ regbol = string; /* Simplest case: anchored match need be tried only once. */ if (prog->reganch) return (regtry(prog, string)); /* Messy cases: unanchored match. */ s = string; if (prog->regstart != '\0') /* We know what char it must start with. */ while ((s = strchr(s, prog->regstart)) != NULL) { if (regtry(prog, s)) return (1); s++; } else /* We don't -- general case. */ do { if (regtry(prog, s)) return (1); } while (*s++ != '\0'); /* Failure. */ return (0); } /* - regtry - try match at specific point */ static int /* 0 failure, 1 success */ regtry(regexp * prog, char *string) { register int i; register char **sp; register char **ep; reginput = string; regstartp = prog->startp; regendp = prog->endp; sp = prog->startp; ep = prog->endp; for (i = NSUBEXP; i > 0; i--) { *sp++ = NULL; *ep++ = NULL; } if (regmatch(prog->program + 1)) { prog->startp[0] = string; prog->endp[0] = reginput; return (1); } else return (0); } /* - regmatch - main matching routine * * Conceptually the strategy is simple: check to see whether the current * node matches, call self recursively to see whether the rest matches, * and then act accordingly. In practice we make some effort to avoid * recursion, in particular by going through "ordinary" nodes (that don't * need to know whether the rest of the match failed) by a loop instead of * by recursion. */ static int /* 0 failure, 1 success */ regmatch(char *prog) { register char *scan; /* Current node. */ char *next; /* Next node. */ scan = prog; #ifdef DEBUG if (scan != NULL && regnarrate) fprintf(stderr, "%s(\n", regprop(scan)); #endif while (scan != NULL) { #ifdef DEBUG if (regnarrate) fprintf(stderr, "%s...\n", regprop(scan)); #endif next = regnext(scan); switch (OP(scan)) { case BOL: if (reginput != regbol) return (0); break; case EOL: if (*reginput != '\0') return (0); break; case W_START: if (*reginput == '\0' || isspace(*reginput)) return (0); if (reginput != regbol && !isspace(*(reginput - 1))) return (0); break; case W_END: if (*reginput != '\0' && !isspace(*reginput)) return (0); if (reginput == regbol || isspace(*(reginput - 1))) return (0); break; case ANY: if (*reginput == '\0') return (0); reginput++; break; case EXACTLY:{ register int len; register char *opnd; opnd = OPERAND(scan); /* Inline the first character, for speed. */ if (*opnd != *reginput) return (0); len = strlen(opnd); if (len > 1 && strncmp(opnd, reginput, len) != 0) return (0); reginput += len; } break; case ANYOF: if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) == NULL) return (0); reginput++; break; case ANYBUT: if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) != NULL) return (0); reginput++; break; case NOTHING: break; case BACK: break; case OPEN + 1: case OPEN + 2: case OPEN + 3: case OPEN + 4: case OPEN + 5: case OPEN + 6: case OPEN + 7: case OPEN + 8: case OPEN + 9:{ register int no; register char *save; no = OP(scan) - OPEN; save = reginput; if (regmatch(next)) { /* * Don't set startp if some later invocation of the * same parentheses already has. */ if (regstartp[no] == NULL) regstartp[no] = save; return (1); } else return (0); } break; case CLOSE + 1: case CLOSE + 2: case CLOSE + 3: case CLOSE + 4: case CLOSE + 5: case CLOSE + 6: case CLOSE + 7: case CLOSE + 8: case CLOSE + 9:{ register int no; register char *save; no = OP(scan) - CLOSE; save = reginput; if (regmatch(next)) { /* * Don't set endp if some later invocation of the * same parentheses already has. */ if (regendp[no] == NULL) regendp[no] = save; return (1); } else return (0); } break; case BRANCH:{ register char *save; if (OP(next) != BRANCH) /* No choice. */ next = OPERAND(scan); /* Avoid recursion. */ else { do { save = reginput; if (regmatch(OPERAND(scan))) return (1); reginput = save; scan = regnext(scan); } while (scan != NULL && OP(scan) == BRANCH); return (0); /* NOTREACHED */ } } break; case STAR: case PLUS:{ register char nextch; register int no; register char *save; register int min; /* * Lookahead to avoid useless match attempts when we know * what character comes next. */ nextch = '\0'; if (OP(next) == EXACTLY) nextch = *OPERAND(next); min = (OP(scan) == STAR) ? 0 : 1; save = reginput; no = regrepeat(OPERAND(scan)); while (no >= min) { /* If it could work, try it. */ if (nextch == '\0' || *reginput == nextch) if (regmatch(next)) return (1); /* Couldn't or didn't -- back up. */ no--; reginput = save + no; } return (0); } break; case END: return (1); /* Success! */ break; default: regerror("memory corruption"); return (0); break; } scan = next; } /* * We get here only if there's trouble -- normally "case END" is the * terminating point. */ regerror("corrupted pointers"); return (0); } /* - regrepeat - repeatedly match something simple, report how many */ static int regrepeat(char *p) { register int count = 0; register char *scan; register char *opnd; scan = reginput; opnd = OPERAND(p); switch (OP(p)) { case ANY: count = strlen(scan); scan += count; break; case EXACTLY: while (*opnd == *scan) { count++; scan++; } break; case ANYOF: while (*scan != '\0' && strchr(opnd, *scan) != NULL) { count++; scan++; } break; case ANYBUT: while (*scan != '\0' && strchr(opnd, *scan) == NULL) { count++; scan++; } break; default: /* Oh dear. Called inappropriately. */ regerror("internal foulup"); count = 0; /* Best compromise. */ break; } reginput = scan; return (count); } /* - regnext - dig the "next" pointer out of a node */ static char * regnext(register char *p) { register int offset; if (p == ®dummy) return (NULL); offset = NEXT(p); if (offset == 0) return (NULL); if (OP(p) == BACK) return (p - offset); else return (p + offset); } #ifdef DEBUG STATIC char *regprop(char *); /* - regdump - dump a regexp onto stdout in vaguely comprehensible form */ void regdump(regexp * r) { register char *s; register char op = EXACTLY; /* Arbitrary non-END op. */ register char *next; s = r->program + 1; while (op != END) { /* While that wasn't END last time... */ op = OP(s); printf("%2d%s", s - r->program, regprop(s)); /* Where, what. */ next = regnext(s); if (next == NULL) /* Next ptr. */ printf("(0)"); else printf("(%d)", (s - r->program) + (next - s)); s += 3; if (op == ANYOF || op == ANYBUT || op == EXACTLY) { /* Literal string, where present. */ while (*s != '\0') { putchar(*s); s++; } s++; } putchar('\n'); } /* Header fields of interest. */ if (r->regstart != '\0') printf("start `%c' ", r->regstart); if (r->reganch) printf("anchored "); if (r->regmust != NULL) printf("must have \"%s\"", r->regmust); printf("\n"); } /* - regprop - printable representation of opcode */ static char * regprop(char *op) { register char *p; static char buf[50]; (void) strcpy(buf, ":"); switch (OP(op)) { case BOL: p = "BOL"; break; case EOL: p = "EOL"; break; case W_START: p = "WSTART"; break; case W_END: p = "WEND"; break; case ANY: p = "ANY"; break; case ANYOF: p = "ANYOF"; break; case ANYBUT: p = "ANYBUT"; break; case BRANCH: p = "BRANCH"; break; case EXACTLY: p = "EXACTLY"; break; case NOTHING: p = "NOTHING"; break; case BACK: p = "BACK"; break; case END: p = "END"; break; case OPEN + 1: case OPEN + 2: case OPEN + 3: case OPEN + 4: case OPEN + 5: case OPEN + 6: case OPEN + 7: case OPEN + 8: case OPEN + 9: sprintf(buf + strlen(buf), "OPEN%d", OP(op) - OPEN); p = NULL; break; case CLOSE + 1: case CLOSE + 2: case CLOSE + 3: case CLOSE + 4: case CLOSE + 5: case CLOSE + 6: case CLOSE + 7: case CLOSE + 8: case CLOSE + 9: sprintf(buf + strlen(buf), "CLOSE%d", OP(op) - CLOSE); p = NULL; break; case STAR: p = "STAR"; break; case PLUS: p = "PLUS"; break; default: regerror("corrupted opcode"); break; } if (p != NULL) (void) strcat(buf, p); return (buf); } #endif /* * The following is provided for those people who do not have strcspn() in * their C libraries. They should get off their butts and do something * about it; at least one public-domain implementation of those (highly * useful) string routines has been published on Usenet. */ #ifdef STRCSPN /* * strcspn - find length of initial segment of s1 consisting entirely * of characters not from s2 */ static int strcspn(char *s1, char *s2) { register char *scan1; register char *scan2; register int count; count = 0; for (scan1 = s1; *scan1 != '\0'; scan1++) { for (scan2 = s2; *scan2 != '\0';) /* ++ moved down. */ if (*scan1 == *scan2++) return (count); count++; } return (count); } #endif void regerror(char *s) { msg("regexp: %s", s); /* NOTREACHED */ } nn-6.7.4/cvt-help.c0000644000175000017500000000116710233745604013450 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2003 Michael T Pins. All rights reserved. * * Convert help-files to binary. * ;:X -> ^X (control X) */ #include #include int main(void) { register int c; while ((c = getchar()) != EOF) { if (c == ';') { c = getchar(); if (c == ':') { c = getchar(); putchar(c & 0xf); continue; } putchar(';'); putchar(c); continue; } if (c >= 1 && c <= 7) { putchar(';'); putchar(':'); putchar(c | 0x40); continue; } putchar(c); } exit(0); } nn-6.7.4/answer.h0000644000175000017500000000044010220205322013203 0ustar mtpinsmtpins/* * Copyright (c) 2003-2005 Michael T Pins. All rights reserved. */ FILE *open_purpose_file(void); int answer(article_header *, int, int); int cancel(article_header *); void do_nnpost(int, char *[]); int post_menu(void); nn-6.7.4/inst.sh0000644000175000017500000002142715212307611013066 0ustar mtpinsmtpins # (Large) prefix inserted above by Make # BSD systems keep chown in /etc PATH="$PATH:/etc" case "$1" in mkdir) if [ -n "$2" -a ! -d "$2"/. ] then mkdir $2 if [ ! -d "$2" ] ; then echo "Cannot create directory $2" exit 1 fi chmod $3 $2 if ./usercheck 0 ; then chgrp $GROUP $2 chown $OWNER $2 fi echo Created directory $2 fi exit 0 ;; copy) cp $4 $3 chmod $2 $3/$4 if ./usercheck 0 ; then chgrp $GROUP $3/$4 chown $OWNER $3/$4 fi echo "$4 -> $3/$4" exit 0 ;; chmod) chmod $2 $3 if ./usercheck 0 ; then chgrp $GROUP $3 chown $OWNER $3 fi exit 0 ;; esac set -u ( if $NNTP then : else if [ ! -d "$SPOOL"/. ] then echo Error: News spool directory $SPOOL not found. fi if [ ! -d "$NLIB"/. ] then echo Error: News lib directory $NLIB not found. fi fi set $RECMAIL if [ ! -f "$1" ] then echo ERROR: Mailer program $RECMAIL not found. fi ) > ErrorCheck if [ -s ErrorCheck ] then cat ErrorCheck echo "Hit return to continue" read X fi rm -f ErrorCheck LOOP=true while $LOOP do if [ $# -ge 1 ] then OPT="$1" shift if [ $# -eq 0 ] then LOOP=false fi PAUSE=false else PAUSE=true cat <<'EOF' INSTALLATION 1) Master programs (machine dependent) 2) User programs (machine dependent, shareable) 3) auxiliary programs (configuration dependent, shareable) 4) Documentation (shareable) 5) Help files (shareable) 6) Online manual (shareable with 5) INIT) Initialize database s) Server installation: 1 + 2 + 3 + 4 + 5 + 6 n) Network installation: 2 + 3 + 4 + 5 + 6 h) Shared installation: 2 + 3 m) Master installation: 1 c) Client installation: 2 u) Update after patch q) Quit EOF if ./usercheck 0 ; then : else echo "Warning: not running as super user" echo "" fi ${AWK} 'BEGIN{printf "Select option: "}' < /dev/null read OPT echo fi case $OPT in s*|a*) OPT="master bin aux help online man" ;; u*) OPT="" if [ -f "$MASTER/nnmaster" ] then OPT="$OPT master" fi if $DBSHORTNAME then if [ -n "$DBDATA" -a -d "$DBDATA" -a ! -d "$DBDATA/0" ] then OPT="$OPT splitdb" fi fi if [ -f "$BIN/nn" ] then OPT="$OPT bin" fi if [ -f "$LIB/aux" ] then OPT="$OPT aux" fi if [ -d "$HELP" ] then OPT="$OPT help" fi if [ -f "$DMAN_DIR/nnmaster.$DMAN_SECT" ] then OPT="$OPT man" fi if [ -f "$HELP/Manual" ] then OPT="$OPT online" fi ;; 1|m) OPT=master ;; n) OPT="bin aux help online man" ;; 2|c) OPT=bin ;; h) OPT="bin aux" ;; 3) OPT="aux" ;; 4) OPT="man" ;; 5) OPT="help" ;; 6) OPT="online" ;; INIT) if $NOV then echo "The NOV version of NN does not *have* it's own database!" else OPT=init fi ;; q*|"") if [ -f $MASTER/nnmaster -a ! -f $MASTER/MPID -a ! $NOV ] then echo "Remember to restart $MASTER/nnmaster" fi exit 0 ;; *) echo "Unrecognized option: $OPT" exit 1 ;; esac for OP in $OPT do case "$OP" in master) ./inst mkdir $MASTER 755 || exit 1 if [ -f $MASTER/nnmaster ] then if [ -f $MASTER/MPID ] then echo "Stopping running master..." if $MASTER/nnmaster -k ; then echo "Stopped." else exit 1 fi fi mv $MASTER/nnmaster $MASTER/nnmaster.old fi echo Installing master in $MASTER for prog in $MASTER_PROG do ./inst copy 755 $MASTER $prog done if [ -f $MASTER/nnmaster ] then chmod 6750 $MASTER/nnmaster fi ;; bin) echo if [ ! -d "$BIN"/. ] then echo Directory $BIN does not found! exit 1 fi echo Installing user programs in $BIN if [ -f $BIN/nn ] then ( cd $BIN mv nn nn.old rm -f $BIN_PROG $BIN_LINK ) fi for prog in $BIN_PROG do ./inst copy 755 $BIN $prog done for link in $BIN_LINK do ln $BIN/nn $BIN/$link echo $link linked to nn done if [ -f $BIN/nnacct ] ; then chmod 4755 $BIN/nnacct echo nnacct is setuid ${OWNER}. fi ;; aux) echo ./inst mkdir $LIB 755 || exit 1 echo Installing auxiliary programs in $LIB for prog in $LIB_PROG do ./inst copy 755 $LIB $prog done ./mkprefix conf > ${LIB}/conf grep "^#" config.h | sed -e '/_MAN_/d' -e 's/[ ]*\/\*.*$//' >> ${LIB}/conf ./inst chmod 644 ${LIB}/conf ;; help) ./inst mkdir $HELP 755 || exit 1 echo echo Installing help files in $HELP cd help for h in * do cd .. ./cvt-help < help/$h > $HELP/$h ./inst chmod 644 $HELP/$h echo $h cd help done cd .. ;; man) echo echo Installing manuals PL="`echo $VERSION | sed -e 's/ .*$//'`" { echo $UMAN_DIR $UMAN_SECT .1 echo $SMAN_DIR $SMAN_SECT .1m echo $DMAN_DIR $DMAN_SECT .8 } | while read DIR SECT SRC do if [ -d "$DIR"/. ] then for i in man/*$SRC do MAN=`basename ${i} $SRC` NEW=$DIR/${MAN}.$SECT sed -e '/^\.TH /s/6.7/'${PL}'/' $i > $NEW ./inst chmod 644 $NEW echo $MAN in $NEW done else echo $DIR not found or not writeable fi done ;; online) ./inst mkdir $HELP 755 || exit 1 MAN=$HELP/Manual echo echo "Formatting online manual $MAN" echo ".... (continues in background) ...." rm -f $MAN ( sed -e 's/\\f[BPI]//g' \ -e 's/\\-/-/g' -e 's/\\&//g' -e 's/\\e/\\/g' \ -e '/^\.\\"ta/p' -e '/^\.\\"/d' \ -e '/^\.nf/d' -e '/^\.fi/d' \ -e '/^\.if/d' -e '/^\.ta/d' -e '/^\.nr/d' \ -e '/^\.in/d' -e 's/^\.[BI] //' \ `ls -1 man/*.? man/*.??` | ${AWK} -f format.awk - > $MAN ./inst chmod 644 $MAN ) & ;; splitdb) ( echo echo "Rearranging $DBDATA directory for better performance." echo "Notice: If interrupted, the database must be rebuilt!" echo "Be patient. This may take a while...." echo $MASTER/nnmaster -l "DATABASE UPGRADE IN PROGRESS" OLDDB="${DBDATA}-old" mv ${DBDATA} ${OLDDB} || exit 1 ./inst mkdir "$DBDATA" 755 || exit 1 Ngrp="`cat ${DB}/GROUPS | wc -l`" Ngrp="`expr $Ngrp + 1`" Ndir="`expr $Ngrp / 100`" i=0 while [ $i -le $Ndir ] do ./inst mkdir "${DBDATA}/${i}" 755 || exit 1 i="`expr $i + 1`" done cd ${OLDDB} i=$Ndir while [ $i -ge 1 ] do if [ "`echo ${i}[0-9][0-9].[dx]`" != "${i}[0-9][0-9].[dx]" ] then echo "Moving groups ${i}00-${i}99 to ${DBDATA}/${i}" mv ${i}[0-9][0-9].[dx] ${DBDATA}/${i} fi i="`expr $i - 1`" done if [ "`echo *.[dx]`" != '*.[dx]' ] then echo "Moving groups 0-99 to ${DBDATA}/${i}" mv *.[dx] ${DBDATA}/0 fi cd /tmp rm -r ${OLDDB} $MASTER/nnmaster -l echo "Database reorganization completed." echo ) ;; init) echo ./inst mkdir "$DB" 755 || exit 1 if [ -n "$DBDATA" ] ; then if [ -d "$DBDATA" -a "$DBDATA" = "${DB}/DATA" ] then echo "Removing old data files" ( cd /tmp && rm -r "$DBDATA" ) fi ./inst mkdir "$DBDATA" 755 || exit 1 if $DBSHORTNAME then ./inst mkdir "$DBDATA/0" 755 || exit 1 fi fi if $NNTP ; then if [ x"$NNTPCACHE" != "x" ] ; then ./inst mkdir "$NNTPCACHE" 777 || exit 1 fi ILIMIT=50 DFLT=50 cat <<'EOF' When nnmaster is started the first time after initializing nn's database, it will attempt to fetch all the articles from the nntp server. It does this by successively requesting each article in the range min..last obtained from the NNTP server. Often the 'min' number is unreliable or even zero (Cnews doesn't maintain it). This means that the nnmaster will request a lot of non-existing articles from the server, causing a lot of network traffic. To limit this activity, nn will normally only attempt to fetch the fifty newest articles in each group. This shouldn't really be a problem since that will give you enough news to start with, and the older articles will probably be expired in a few days anyway. You can change this limit if you like. Or you can disable this limitation completely if you trust the min field by giving a 0 limit. EOF else ILIMIT="" DFLT="none" cat <<'EOF' If the 'min' field in your active file is not reliable, nnmaster can waste a lot of time trying to locate non-existing articles in the news groups when it is collecting the available articles the first time it is started after the database is initialized. This is especially true with Cnews where the min field is not normally maintained. To limit the efforts during the initial collection, you can set a limit on the number of articles in each group which nnmaster should try to locate in each group. This may get you running faster, and it shouldn't matter much anyway since the articles that may be ignored will be the oldest articles in the group, and they will probably be expired soon anyway. A value in the range 100-500 should be more than enough. If you don't specify a limit, all articles will be collected, but it may take quite some time if the min fields are unreliable. EOF fi ${AWK} 'END{printf "Initial article limit ('"$DFLT"') "}' < /dev/null read L if [ -n "$L" ] ; then ILIMIT="$L" fi echo Running nnmaster -I $ILIMIT to initialize database.... echo $MASTER/nnmaster -I $ILIMIT echo echo "Now start $MASTER/nnmaster [ -D ] [ -r ]" ;; esac done if [ -f $LOG ] then chmod 644 $LOG fi if $PAUSE then ${AWK} 'BEGIN{printf("\nHit return to continue....")}' < /dev/null read X fi done nn-6.7.4/unshar.h0000644000175000017500000000016207751420632013227 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int unshar_position(FILE *); nn-6.7.4/sequence.c0000644000175000017500000003727010222576460013542 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Read presentation sequence file */ #include #include #include #include "config.h" #include "global.h" #include "db.h" #include "debug.h" #include "macro.h" #include "nn.h" #include "sequence.h" #include "nn_term.h" #include "variable.h" /* sequence.c */ static int enter_sequence(int mode, group_header * gh); static void faked_entry(char *name, flag_type flag); static void end_sequence(void); static int visit_presentation_file(char *directory, char *seqfile, FILE * hook); group_header *group_sequence; char *read_mail = NULL; int also_subgroups = 1; int hex_group_args = 0; static int seq_break_enabled = 1; /* !! enabled */ static int ignore_done_flag = 0; /* % toggle */ static group_header *tail_sequence = NULL; static group_header *final_sequence = NULL; static int gs_more_groups; extern group_header *rc_sequence; int only_folder_args(char **args) { register char *arg; while ((arg = *args++)) { if (*arg == '+' || *arg == '~' || *arg == '/') continue; if (file_exist(arg, "fr")) continue; return 0; } return 1; } #define SHOW_NORMAL 0 /* : put this in at current pos */ #define SHOW_FIRST 1 /* < : show these groups first */ #define SHOW_LAST 2 /* > : show this as late as possible */ #define IGNORE_ALWAYS 3 /* ! : ignore these groups completely */ #define IGN_UNLESS_RC 4 /* !:X ignore these groups unless in rc */ #define IGN_UNLESS_NEW 5 /* !:O ignore these groups unless new */ #define IGN_UNL_RC_NEW 6 /* !:U ignore unsubscribed */ #define IGN_IF_NEW 7 /* !:N ignore these groups if new */ #define SHOW_MODES " <>!-?*" static int enter_sequence(int mode, group_header * gh) { #ifdef SEQ_TEST if (Debug & SEQ_TEST && mode != SHOW_NORMAL) tprintf("SEQ(%c), %s\n", SHOW_MODES[mode], gh->group_name); #endif if (gh->master_flag & M_IGNORE_GROUP) return 0; if (ignore_done_flag) { if (gh->group_flag & G_SEQUENCE) return 0; } else if (gh->group_flag & G_DONE) return 0; switch (mode) { case IGN_UNLESS_NEW: if ((gh->group_flag & G_NEW) == 0) gh->group_flag |= G_DONE; return 0; case IGN_IF_NEW: if (gh->group_flag & G_NEW) gh->group_flag |= G_DONE; return 0; case IGN_UNL_RC_NEW: if (gh->group_flag & G_NEW) return 0; if (gh->newsrc_line == NULL || (gh->group_flag & G_UNSUBSCRIBED)) gh->group_flag |= G_DONE; return 0; case IGN_UNLESS_RC: if (gh->newsrc_line == NULL || (gh->group_flag & (G_UNSUBSCRIBED | G_NEW))) gh->group_flag |= G_DONE; return 0; case IGNORE_ALWAYS: gh->group_flag |= G_DONE; return 0; default: gh->group_flag |= G_DONE; break; } gh->group_flag |= G_SEQUENCE; if (gh->master_flag & M_NO_DIRECTORY) return 0; /* for nntidy -s */ switch (mode) { case SHOW_FIRST: if (tail_sequence) { gh->next_group = group_sequence; group_sequence = gh; break; } /* FALLTHRU */ case SHOW_NORMAL: if (tail_sequence) tail_sequence->next_group = gh; else group_sequence = gh; tail_sequence = gh; break; case SHOW_LAST: gh->next_group = final_sequence; final_sequence = gh; break; } return 1; } static void faked_entry(char *name, flag_type flag) { group_header *gh; gh = newobj(group_header, 1); gh->group_name = name; gh->group_flag = flag | G_FAKED; gh->master_flag = 0; /* "invent" an unread article for read_news */ gh->last_article = 1; gh->last_db_article = 2; enter_sequence(SHOW_NORMAL, gh); } static void end_sequence(void) { register group_header *gh, *backp; register int seq_ix; if (tail_sequence) tail_sequence->next_group = NULL; /* set up backward pointers */ backp = NULL; seq_ix = 0; Loop_Groups_Sequence(gh) { gh->preseq_index = ++seq_ix; gh->prev_group = backp; backp = gh; } #ifdef SEQ_DUMP if (Debug & SEQ_DUMP) { for (gh = group_sequence; gh; gh = gh->next_group) tprintf("%s\n", gh->group_name); tputc(NL); nn_exit(0); } #endif } #ifdef MAIL_READING static int mail_check(void) { static group_header mail_group; struct stat st; if (read_mail == NULL) return; if (stat(read_mail, &st) < 0) return; if (st.st_size == 0 || st.st_mtime < st.st_atime) return; mail_group.group_name = read_mail; gh->group_flag = G_FOLDER | G_MAILBOX | G_FAKED; gh->master_flag = 0; /* "invent" an unread article for read_news */ gh->last_article = 1; gh->last_db_article = 2; if (tail_sequence) { mail_group.next_group = group_sequence; group_sequence = mail_group; } else enter_sequence(SHOW_NORMAL, &mail_group); } #endif static int visit_presentation_file(char *directory, char *seqfile, FILE * hook) { register FILE *sf; register int c; register group_header *gh; group_header *mp_group; char group[FILENAME], *gname; char savefile[FILENAME], *dflt_save, *enter_macro; register char *gp; int mode, merge_groups; if (gs_more_groups == 0) return 0; if (hook != NULL) sf = hook; /* hook to init file */ else if ((sf = open_file(relative(directory, seqfile), OPEN_READ)) == NULL) return 0; #ifdef SEQ_TEST if (Debug & SEQ_TEST) tprintf("Sequence file %s/%s\n", directory, seqfile); #endif mode = SHOW_NORMAL; savefile[0] = NUL; dflt_save = NULL; while (gs_more_groups) { if ((c = getc(sf)) == EOF) break; if (!isascii(c) || isspace(c)) continue; switch (c) { case '!': mode = IGNORE_ALWAYS; if ((c = getc(sf)) == EOF) continue; if (c == '!') { if (seq_break_enabled) { fclose(sf); return 1; } mode = SHOW_NORMAL; continue; } if (c == ':') { if ((c = getc(sf)) == EOF) continue; if (!isascii(c) || isspace(c) || !isupper(c)) continue; switch (c) { case 'O': mode = IGN_UNLESS_NEW; continue; case 'N': mode = IGN_IF_NEW; continue; case 'U': mode = IGN_UNL_RC_NEW; continue; case 'X': mode = IGN_UNLESS_RC; continue; default: /* should give error here */ mode = SHOW_NORMAL; continue; } } ungetc(c, sf); continue; case '<': mode = SHOW_FIRST; continue; case '>': mode = SHOW_LAST; continue; case '%': ignore_done_flag = !ignore_done_flag; continue; case '@': seq_break_enabled = 0; mode = SHOW_NORMAL; continue; case '#': do c = getc(sf); while (c != EOF && c != NL); mode = SHOW_NORMAL; continue; } gp = group; merge_groups = 0; do { *gp++ = c; if (c == ',') merge_groups = 1; c = getc(sf); } while (c != EOF && isascii(c) && !isspace(c)); *gp = NUL; while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf); if (c == '+' || c == '~' || c == '/') { gp = savefile; if (c == '+') { c = getc(sf); if (c == EOF || (isascii(c) && isspace(c))) goto use_same_savefile; *gp++ = '+'; } do { *gp++ = c; c = getc(sf); } while (c != EOF && isascii(c) && !isspace(c)); *gp = NUL; dflt_save = savefile[0] ? copy_str(savefile) : NULL; } else dflt_save = NULL; use_same_savefile: while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf); if (c == '(') { enter_macro = parse_enter_macro(sf, getc(sf)); } else { enter_macro = NULL; if (c != EOF) ungetc(c, sf); } mp_group = NULL; for (gp = group; *gp;) { gname = gp; if (merge_groups) { while (*gp && *gp != ',') gp++; if (*gp) *gp++ = NUL; } start_group_search(gname); while ((gh = get_group_search())) { if (merge_groups && gh->group_flag & G_UNSUBSCRIBED) continue; if (!enter_sequence(mode, gh)) continue; if (merge_groups) { if (mp_group == NULL) { gh->group_flag |= G_MERGE_HEAD; } else { mp_group->merge_with = gh; gh->group_flag |= G_MERGE_SUB; } mp_group = gh; } if (gh->save_file == NULL) /* not set by "save-files" */ gh->save_file = dflt_save; if (gh->enter_macro == NULL) /* not set by "on entry" */ gh->enter_macro = enter_macro; } if (!merge_groups) *gp = NUL; } if (merge_groups && mp_group != NULL) mp_group->merge_with = NULL; mode = SHOW_NORMAL; } fclose(sf); return 0; } void parse_save_files(register FILE * sf) { register int c; register group_header *gh; char group[FILENAME]; char *savefile = NULL; char namebuf[FILENAME]; register char *gp; for (;;) { if ((c = getc(sf)) == EOF) break; if (!isascii(c) || isspace(c)) continue; if (c == '#') { do c = getc(sf); while (c != EOF && c != NL); continue; } gp = group; do { *gp++ = c; c = getc(sf); } while (c != EOF && isascii(c) && !isspace(c)); *gp = NUL; if (strcmp(group, "end") == 0) break; while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf); gp = namebuf; do { *gp++ = c; c = getc(sf); } while (c != EOF && isascii(c) && !isspace(c)); *gp = NUL; if (namebuf[0] == NUL) break; if (strcmp(namebuf, "+")) savefile = copy_str(namebuf); start_group_search(group); while ((gh = get_group_search())) gh->save_file = savefile; } } void named_group_sequence(char **groups) { register group_header *gh; register char *group; register char *value; int non_vars; int found, errors, gnum; group_sequence = NULL; also_subgroups = 0; errors = 0; non_vars = 0; while ((group = *groups++)) { non_vars++; if (hex_group_args) { sscanf(group, "%d", &gnum); if (gnum < 0 || gnum >= master.number_of_groups) continue; gh = ACTIVE_GROUP(gnum); enter_sequence(SHOW_NORMAL, gh); continue; } if ((gh = lookup(group))) { enter_sequence(SHOW_NORMAL, gh); continue; } if ((value = strchr(group, '='))) { *value++ = NUL; set_variable(group, 1, value); non_vars--; continue; } if (*group == '+' || *group == '~' || file_exist(group, "fr")) { faked_entry(group, G_FOLDER); continue; } found = 0; start_group_search(group); while ((gh = get_group_search())) { found++; enter_sequence(SHOW_NORMAL, gh); } if (!found) { tprintf("Group %s not found\n", group); fl; errors++; } } if (non_vars == 0) { normal_group_sequence(); return; } end_sequence(); if (errors) user_delay(2); return; } FILE *loc_seq_hook = NULL; /* sequence in local "init" file */ FILE *glob_seq_hook = NULL; /* sequence in global "init" file */ void normal_group_sequence(void) { register group_header *gh; group_sequence = NULL; gs_more_groups = 1; /* visit_p_f returns non-zero if terminated by !! */ if (visit_presentation_file(nn_directory, "seq", loc_seq_hook)) goto final; if (visit_presentation_file(lib_directory, "sequence", glob_seq_hook)) goto final; Loop_Groups_Sorted(gh) { enter_sequence(SHOW_NORMAL, gh); } final: if (final_sequence) { if (tail_sequence) { tail_sequence->next_group = final_sequence; tail_sequence = NULL; } else { group_sequence = final_sequence; } } #ifdef MAIL_READING mail_check(); #endif end_sequence(); } static char *gs_group; static int gs_length, gs_index, gs_mode; static group_header *gs_only_group = NULL; #define GS_PREFIX0 0 /* group (or group*) */ #define GS_PREFIX 1 /* group. */ #define GS_SUFFIX 2 /* .group */ #define GS_INFIX 3 /* .group. */ #define GS_NEW_GROUP 4 /* new group */ #define GS_ALL 5 /* all / . */ #define GS_NEWSRC 6 /* RC */ void start_group_search(char *group) { char *dot; int last; gs_index = master.number_of_groups; /* loop will fail */ if ((last = strlen(group) - 1) < 0) return; if (group[last] == '*') group[last] = NUL; else if (!also_subgroups && (gs_only_group = lookup(group)) != NULL) return; gs_index = 0; gs_more_groups = 0; gs_length = 0; gs_group = NULL; if (strcmp(group, "NEW") == 0) { gs_mode = GS_NEW_GROUP; return; } if (strncmp(group, "RC", 2) == 0) { gs_mode = GS_NEWSRC; gs_only_group = rc_sequence; gs_more_groups = 1; /* we just can't know! */ if (group[2] != ':') return; if (isdigit(group[3])) gs_index = atoi(group + 3); else { gs_group = group + 3; gs_length = strlen(gs_group); } return; } if (strcmp(group, "all") == 0 || strcmp(group, ".") == 0) { gs_mode = GS_ALL; return; } gs_mode = GS_PREFIX0; if (strncmp(group, "all.", 4) == 0) group += 3; if (*group == '.') gs_mode = GS_SUFFIX; if ((dot = strrchr(group, '.')) != NULL && dot != group) { if (dot[1] == NUL || strcmp(dot + 1, "all") == 0) { dot[1] = NUL; gs_mode = (gs_mode == GS_SUFFIX) ? GS_INFIX : GS_PREFIX; } } gs_length = strlen(group); gs_group = group; } #define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, (int)(n)) == 0) group_header * get_group_search(void) { register group_header *gh, **ghp; register int gsidx = gs_index; /* cached gs_index (RW) */ register int mstngrps = master.number_of_groups; /* cached (RO) */ register int gdonemask = (ignore_done_flag ? 0 : G_DONE); register int tail; register int gslen = gs_length; /* cached (RO) */ register int gsmgrps = gs_more_groups; /* cached (RW) */ register char *ghgrpnm; register char *gsgrp = gs_group; /* cached (RO) */ register int gsmode = gs_mode; /* cached (RO) */ register group_header *gsog = gs_only_group; /* cached (RW) */ /* most of start up CPU time goes here */ if (gsmode == GS_NEWSRC) { /* gsgrp is usually "" */ #ifdef notdef /* seemed like a good idea at the time; * actually slower */ if (gslen == 0) { /* common case */ if (gsidx != 0) --gs_index; gs_only_group = NULL; return NULL; } #endif do { gh = gsog; if (gh == NULL) break; if (gsidx && --gsidx == 0) gsog = NULL; else if (gsgrp && gh->group_name_length >= gslen && strncmp(gh->group_name, gsgrp, gslen) == 0) { gsog = NULL; } else gsog = gh->newsrc_seq; } while (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP); gs_only_group = gsog; gs_index = gsidx; return gh; } if (gsog != NULL) { gh = gsog; gs_only_group = NULL; if (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP) return NULL; return gh; } for (ghp = &sorted_groups[gsidx]; gsidx < mstngrps; ++ghp) { gh = *ghp; gsidx++; if (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP) continue; gsmgrps++; if ((tail = gh->group_name_length - gslen) < 0) continue; ghgrpnm = gh->group_name; if (gsmode == GS_PREFIX0) { /* the big winner */ if (((tail = ghgrpnm[gslen]) != NUL && tail != '.') || !STREQN(ghgrpnm, gsgrp, gslen)) continue; } else if (gsmode == GS_PREFIX) { if (!STREQN(ghgrpnm, gsgrp, gslen)) continue; } else switch (gsmode) { case GS_NEW_GROUP: if ((gh->group_flag & G_NEW) == 0 || gh->group_flag & G_UNSUBSCRIBED) continue; break; case GS_SUFFIX: if (strcmp(ghgrpnm + tail, gsgrp) != 0) continue; break; case GS_INFIX: nn_exitmsg(1, ".name. notation not supported (yet)"); break; case GS_ALL: break; } gsmgrps--; gs_more_groups = gsmgrps; gs_only_group = gsog; gs_index = gsidx; return gh; } gs_more_groups = gsmgrps; gs_only_group = gsog; gs_index = gsidx; return NULL; } nn-6.7.4/master.h0000644000175000017500000000036610217216333013220 0ustar mtpinsmtpins/* * Copyright (c) 2003-2005 Michael T Pins. All rights reserved. */ #ifndef NOV void nn_exit(int); void clean_group(group_header *); group_header *add_new_group(char *); void write_error(void); #endif nn-6.7.4/prefix.c0000644000175000017500000000650310223324157013215 0ustar mtpinsmtpins#include #include "config.h" #include "global.h" #include "execute.h" /* prefix.c */ static void make_nn_prefix(int flag, FILE * f); extern char *home_directory; extern char *news_directory; extern char *news_lib_directory; extern char *master_directory; extern char *help_directory; extern char *bin_directory; extern char *db_directory; extern char *db_data_directory; extern char *tmp_directory; extern char *log_file; #define SHELL_PREFIX 0x01 #define FULL_PREFIX 0x02 #define CONF_PREFIX 0x04 static void make_nn_prefix(int flag, FILE * f) { char nl; nl = (flag & CONF_PREFIX) ? TAB : NL; if (flag & SHELL_PREFIX) { #ifdef AVOID_SHELL_EXEC fprintf(f, ":\n"); #else fprintf(f, "#!%s\n", SHELL); #endif #ifdef PUT_TIMESTAMP_IN_SCRIPTS fprintf(f, "\n# Generated by nn release %s at %s\n\n", version_id, date_time((time_t) 0)); #else fprintf(f, "\n# Generated by nn release %s\n\n", version_id); #endif } fprintf(f, "VERSION=\"%s\"\n", version_id); #ifndef OLD_AWK #define OLD_AWK "awk" #endif fprintf(f, "AWK=\"%s\"\n", OLD_AWK); #ifdef NOV fprintf(f, "NOV=true%c", nl); #else fprintf(f, "NOV=false%c", nl); #endif #ifdef NNTP fprintf(f, "NNTP=true%c", nl); fprintf(f, "ACTIVE=%s/ACTIVE\n", db_directory); #ifndef CACHE_DIRECTORY #define CACHE_DIRECTORY "" #endif fprintf(f, "NNTPCACHE=%s\n", CACHE_DIRECTORY); #else fprintf(f, "NNTP=false%c", nl); fprintf(f, "ACTIVE=%s/active\n", news_lib_directory); #endif fprintf(f, "LOG=%s\n", log_file); fprintf(f, "TMP=${TMPDIR-%s}\n", tmp_directory); fprintf(f, "DB=%s\n", db_directory); fprintf(f, "BIN=%s\n", bin_directory); fprintf(f, "LIB=%s\n", lib_directory); if (flag & (FULL_PREFIX | CONF_PREFIX)) { fprintf(f, "RECMAIL=\"%s\"\n", REC_MAIL); fprintf(f, "SPOOL=%s\n", news_directory); fprintf(f, "NLIB=%s\n", news_lib_directory); fprintf(f, "MASTER=%s\n", master_directory); fprintf(f, "HELP=%s\n", help_directory); fprintf(f, "DBDATA=\"%s\"\n", db_data_directory ? db_data_directory : ""); #ifdef DB_LONG_NAMES fprintf(f, "DBSHORTNAME=false\n"); #else fprintf(f, "DBSHORTNAME=true\n"); #endif fprintf(f, "OWNER=%s%c", OWNER, nl); fprintf(f, "GROUP=%s\n", GROUP); } #ifdef AUTHORIZE fprintf(f, "AUTH=true\n"); #else fprintf(f, "AUTH=false\n"); #endif if (flag & FULL_PREFIX) { fprintf(f, "UMAN_DIR=\"%s\"\n", USER_MAN_DIR); fprintf(f, "UMAN_SECT=\"%s\"\n", USER_MAN_SECTION); fprintf(f, "SMAN_DIR=\"%s\"\n", SYS_MAN_DIR); fprintf(f, "SMAN_SECT=\"%s\"\n", SYS_MAN_SECTION); fprintf(f, "DMAN_DIR=\"%s\"\n", DAEMON_MAN_DIR); fprintf(f, "DMAN_SECT=\"%s\"\n", DAEMON_MAN_SECTION); } if (flag & SHELL_PREFIX) fprintf(f, "\n# ---- end of prefix\n\n"); } int main(int argc, char *argv[]) { if (argc != 2) exit(2); /* init_global(I_AM_MASTER); */ init_global(); switch (argv[1][0]) { case 'f': make_nn_prefix(SHELL_PREFIX | FULL_PREFIX, stdout); break; case 'p': make_nn_prefix(SHELL_PREFIX, stdout); break; case 'c': make_nn_prefix(CONF_PREFIX, stdout); break; default: exit(1); } exit(0); } void nn_exit(int n) { exit(n); } void nn_exitmsg(void) { exit(0); } #ifdef HAVE_JOBCONTROL int suspend_nn(void) { return 0; } #endif /* dummy function */ void msg(void) { } nn-6.7.4/collect.c0000644000175000017500000002747410222720707013357 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Collect and save article information in database. */ #include #include #include #include "config.h" #include "global.h" #include "db.h" #include "digest.h" #include "master.h" #include "news.h" #include "nntp.h" #include "pack_date.h" #include "pack_name.h" #include "pack_subject.h" /* collect.c */ static void do_auto_archive(group_header * gh, register FILE * f, article_number num); static void build_hdr(int type); static int collect_article(register group_header * gh, article_number art_num); static long collect_group(register group_header * gh); #define COUNT_RE_REFERENCES /* no of >>> depends on Reference: line */ int ignore_bad_articles = 1; /* no Newsgroups: line */ int remove_bad_articles = 0; time_t max_article_age = 0; extern int trace, debug_mode; #ifdef NNTP extern int nntp_failed; #endif static long bad_count; static FILE *ix, *data; static void do_auto_archive(group_header * gh, register FILE * f, article_number num) { char line[200]; article_number last; register FILE *arc; register int c; long start; static char *arc_header = "Archived-Last: "; /* Header format: Archived-Last: 88888888 group.name */ /* Fixed constants length == 15 and offset == 24 are used below */ arc = open_file(gh->archive_file, OPEN_READ); last = 0; start = 0; if (arc != NULL) { while (fgets(line, 200, arc) != NULL) { if (strncmp(line, arc_header, 15)) { log_entry('E', "%s not archive for %s\n", gh->archive_file, gh->group_name); gh->master_flag &= ~M_AUTO_ARCHIVE; fclose(arc); return; } if (strncmp(line + 24, gh->group_name, gh->group_name_length)) { start = ftell(arc); continue; } last = atol(line + 15); break; } fclose(arc); } if (last >= num) return; arc = open_file(gh->archive_file, last > 0 ? OPEN_UPDATE : OPEN_CREATE); if (arc == NULL) { log_entry('E', "Cannot create archive file: %s\n", gh->archive_file); gh->master_flag &= ~M_AUTO_ARCHIVE; return; } fseek(arc, start, 0); fprintf(arc, "%s%8ld %s\n", arc_header, (long) num, gh->group_name); fseek(arc, 0, 2); fseek(f, 0, 0); while ((c = getc(f)) != EOF) putc(c, arc); putc(NL, arc); fclose(arc); } static void build_hdr(int type) { register char *name, *subj; int re; db_data.dh_type = type; if (type == DH_SUB_DIGEST) { name = digest.dg_from; subj = digest.dg_subj; db_hdr.dh_lines = digest.dg_lines; db_hdr.dh_hpos = digest.dg_hpos; db_hdr.dh_fpos = (int16) (digest.dg_fpos - db_hdr.dh_hpos); db_hdr.dh_lpos = digest.dg_lpos; db_hdr.dh_date = pack_date(digest.dg_date ? digest.dg_date : news.ng_date); } else { if (!news.ng_from) news.ng_from = news.ng_reply; name = news.ng_from; subj = news.ng_subj; db_hdr.dh_lines = news.ng_lines; db_hdr.dh_hpos = 0; db_hdr.dh_fpos = (int16) (news.ng_fpos); db_hdr.dh_lpos = news.ng_lpos; db_hdr.dh_date = pack_date(news.ng_date); } if (name) { db_hdr.dh_sender_length = pack_name(db_data.dh_sender, name, NAME_LENGTH); } else db_hdr.dh_sender_length = 0; if (type == DH_DIGEST_HEADER) { db_hdr.dh_subject_length = 1; db_data.dh_subject[0] = '@'; } else db_hdr.dh_subject_length = 0; db_hdr.dh_subject_length += pack_subject(db_data.dh_subject + db_hdr.dh_subject_length, subj, &re, DBUF_SIZE); #ifdef COUNT_RE_REFERENCES if (re) re = 0x80; if (news.ng_ref) { for (name = news.ng_ref; *name; name++) { if ((re & 0x7f) == 0x7f) break; if (*name == '<') re++; } } #endif db_hdr.dh_replies = re; if (db_write_art(data) < 0) write_error(); } static int collect_article(register group_header * gh, article_number art_num) { FILE *art_file; news_header_buffer nhbuf, dgbuf; article_header art_hdr; int mode, count; cross_post_number *cp_ptr; long age; count = 0; db_hdr.dh_number = art_num; /* get article header */ art_hdr.a_number = art_num; art_hdr.hpos = 0; art_hdr.lpos = (off_t) 0; art_hdr.flag = 0; mode = FILL_NEWS_HEADER | FILL_OFFSETS | SKIP_HEADER; if ((gh->master_flag & (M_CONTROL | M_NEVER_DIGEST | M_ALWAYS_DIGEST)) == 0) mode |= DIGEST_CHECK; #ifdef NNTP if ((gh->master_flag & M_ALWAYS_DIGEST) == 0) mode |= LAZY_BODY; #endif if ((art_file = open_news_article(&art_hdr, mode, nhbuf, (char *) NULL)) == NULL) { #ifdef NNTP if (nntp_failed) { /* * connection to nntp_server is broken stop collection of * articles immediately */ return -1; } #endif /* * it is not really necessary to save anything in the data file we * simply use the index file to get the *first* available article */ return 0; } if (art_file == (FILE *) 1) { /* empty file */ if (!ignore_bad_articles) return 0; news.ng_groups = NULL; art_file = NULL; } else if (max_article_age && /* == 0 if use_nntp */ (gh->master_flag & M_INCLUDE_OLD) == 0 && (age = m_time(art_file)) < max_article_age) { if (remove_bad_articles) unlink(group_path_name); log_entry('O', "%sold article (%ld days): %s/%ld", remove_bad_articles ? "removed " : "", (cur_time() - age) / (24 * 60 * 60), current_group->group_name, (long) art_num); bad_count++; fclose(art_file); return 0; } if (ignore_bad_articles && news.ng_groups == NULL) { char *rem = ""; if (!use_nntp && remove_bad_articles) { unlink(group_path_name); rem = "removed "; } log_entry('B', "%sbad article: %s/%ld", rem, current_group->group_name, (long) art_num); if (art_file != NULL) fclose(art_file); bad_count++; return 0; } /* map cross-postings into a list of group numbers */ db_hdr.dh_cross_postings = 0; if (gh->master_flag & M_CONTROL) { /* we cannot trust the Newsgroups: line in the control group */ /* so we simply ignore it (i.e. use "Newsgroups: control") */ goto dont_digest; } if (news.ng_groups) { char *curg, *nextg; group_header *gh1; for (nextg = news.ng_groups, cp_ptr = db_data.dh_cross; *nextg;) { curg = nextg; if ((nextg = strchr(curg, ','))) *nextg++ = NUL; else nextg = ""; if (strcmp(gh->group_name, curg) == 0) gh1 = gh; else if ((gh1 = lookup(curg)) == NULL) continue; *cp_ptr++ = NETW_CROSS_EXT(gh1->group_num); if (++db_hdr.dh_cross_postings == DBUF_SIZE) break; } } if (db_hdr.dh_cross_postings == 1) db_hdr.dh_cross_postings = 0; /* only current group */ if (gh->master_flag & M_NEVER_DIGEST) goto dont_digest; /* split digest */ if ((gh->master_flag & M_ALWAYS_DIGEST) || (news.ng_flag & N_DIGEST)) { int any = 0, cont = 1; skip_digest_body(art_file); while (cont && (cont = get_digest_article(art_file, dgbuf)) >= 0) { if (any == 0) { build_hdr(DH_DIGEST_HEADER); /* write DIGEST_HEADER */ count++; db_hdr.dh_cross_postings = 0; /* no cross post in sub */ any++; } build_hdr(DH_SUB_DIGEST); /* write SUB_DIGEST */ count++; } if (any) goto finish; } /* not a digest */ dont_digest: build_hdr(DH_NORMAL); /* normal article */ count++; finish: if (gh->master_flag & M_AUTO_ARCHIVE) { #ifdef NNTP FILE *f; f = nntp_get_article(art_num, 0); do_auto_archive(gh, f, art_num); fclose(f); #else do_auto_archive(gh, art_file, art_num); #endif /* NNTP */ } fclose(art_file); return count; } /* * Collect unread articles in current group * * On entry, init_group has been called to setup the proper environment */ static long collect_group(register group_header * gh) { long article_count, temp, obad; article_number start_collect; if (gh->last_db_article == 0) { gh->first_db_article = gh->first_a_article; gh->last_db_article = gh->first_db_article - 1; } if (gh->last_db_article >= gh->last_a_article) return 0; if (gh->index_write_offset) { ix = open_data_file(gh, 'x', OPEN_UPDATE | MUST_EXIST); fseek(ix, gh->index_write_offset, 0); } else ix = open_data_file(gh, 'x', OPEN_CREATE | MUST_EXIST); if (gh->data_write_offset) { data = open_data_file(gh, 'd', OPEN_UPDATE | MUST_EXIST); fseek(data, gh->data_write_offset, 0); } else data = open_data_file(gh, 'd', OPEN_CREATE | MUST_EXIST); article_count = 0; start_collect = gh->last_db_article + 1; if (debug_mode) { printf("\t\t%s (%ld..%ld)\r", gh->group_name, start_collect, gh->last_a_article); fl; } bad_count = obad = 0; while (gh->last_db_article < gh->last_a_article) { if (s_hangup) break; gh->last_db_article++; if (debug_mode) { printf("\r%ld", gh->last_db_article); if (obad != bad_count) printf("\t%ld", bad_count); obad = bad_count; fl; } gh->data_write_offset = ftell(data); #ifdef NNTP gh->index_write_offset = ftell(ix); #endif temp = collect_article(gh, gh->last_db_article); #ifdef NNTP if (temp < 0) { /* connection failed, current article is not collected */ gh->last_db_article--; article_count = -1; goto out; } #endif #ifndef RENUMBER_DANGER if (temp == 0 && gh->data_write_offset == 0) { gh->first_db_article = gh->last_db_article + 1; continue; } #endif if (!db_write_offset(ix, &(gh->data_write_offset))) write_error(); article_count += temp; } if (start_collect < gh->first_db_article) start_collect = gh->first_db_article; if (trace && start_collect <= gh->last_db_article) log_entry('T', "Col %s (%d to %d) %d", gh->group_name, start_collect, gh->last_db_article, article_count); if (debug_mode) printf("\nCol %s (%ld to %ld) %ld", gh->group_name, start_collect, gh->last_db_article, article_count); gh->data_write_offset = ftell(data); gh->index_write_offset = ftell(ix); #ifdef NNTP out: #endif fclose(data); fclose(ix); if (debug_mode) putchar(NL); return article_count; } int do_collect(void) { register group_header *gh; long col_article_count, temp; int col_group_count; time_t start_time; start_time = cur_time(); col_article_count = col_group_count = 0; current_group = NULL; /* for init_group */ temp = 0; Loop_Groups_Header(gh) { if (s_hangup) { temp = -1; break; } if (gh->master_flag & M_IGNORE_GROUP) continue; if (gh->master_flag & M_MUST_CLEAN) clean_group(gh); if (gh->last_db_article == gh->last_a_article) { if (gh->master_flag & M_BLOCKED) goto unblock_group; continue; } if (!init_group(gh)) { if ((gh->master_flag & M_NO_DIRECTORY) == 0) { log_entry('R', "%s: no directory", gh->group_name); gh->master_flag |= M_NO_DIRECTORY; } gh->last_db_article = gh->last_a_article; gh->first_db_article = gh->last_a_article; /* OBS: not first */ gh->master_flag &= ~(M_EXPIRE | M_BLOCKED); db_write_group(gh); continue; } if (gh->master_flag & M_NO_DIRECTORY) { /* The directory has been created now */ gh->master_flag &= ~M_NO_DIRECTORY; clean_group(gh); } temp = collect_group(gh); #ifdef NNTP if (temp < 0) { /* connection broken */ gh->master_flag &= ~M_EXPIRE; /* remains blocked */ db_write_group(gh); break; } #endif if (temp > 0) { col_article_count += temp; col_group_count++; } unblock_group: gh->master_flag &= ~(M_EXPIRE | M_BLOCKED); db_write_group(gh); } if (col_article_count > 0) log_entry('C', "Collect: %ld art, %d gr, %ld s", col_article_count, col_group_count, cur_time() - start_time); return temp > 0; /* return true IF we got articles */ } nn-6.7.4/dir.h0000644000175000017500000000212707751420624012511 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2003 Michael T Pins. All rights reserved. * * Directory access routines (faked ones). * * If HAVE_DIRECTORY is not defined, traditional sysV directory * structure is assumed unless NOT_SYS5_DIRECTORY is defined (in * which case a shell command is used to list a directory). */ #ifndef _NN_DIR_H #define _NN_DIR_H 1 #ifndef HAVE_DIRECTORY #ifndef NOT_SYS5_DIRECTORY static struct dir_entry { short int d_ino; char d_name[15]; } dirbuf; #define Direntry struct dir_entry #define DIR FILE #define opendir(name) fopen(name, "r") #define readdir(dirp) (fread(&dirbuf, 16, 1, dirp) == 1 ? &dirbuf : NULL) #undef rewinddir #define rewinddir(dirp) rewind(dirp) #define closedir(dirp) fclose(dirp) #define HAVE_DIRECTORY #define FAKED_DIRECTORY #endif #endif int list_directory(char *, char *); int next_directory(register char *, int); int compl_help_directory(void); void close_directory(void); #endif /* _NN_DIR_H */ nn-6.7.4/group.h0000644000175000017500000000073207751420625013070 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int group_completion(char *, int); int group_menu(register group_header *, article_number, register flag_type, char *, fct_type); group_header *lookup_regexp(char *, char *, int); int goto_group(int, article_header *, flag_type); void merge_and_read(flag_type, char *); int unsubscribe(group_header *); void group_overview(int); nn-6.7.4/kill.c0000644000175000017500000006062610231314755012663 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Kill file handling */ #include #include #include #include #include "config.h" #include "global.h" #include "db.h" #include "kill.h" #include "match.h" #include "menu.h" #include "regexp.h" #include "nn_term.h" /* prototypes further down */ int killed_articles; int dflt_kill_select = 30; int kill_file_loaded = 0; int kill_debug = 0; int kill_ref_count = 0; extern char *temp_file; extern char delayed_msg[]; static char KILL_FILE[] = "kill"; static char COMPILED_KILL[] = "KILL.COMP"; #define COMP_KILL_MAGIC 0x4b694c6f /* KiLo */ /* * kill flags */ #define COMP_KILL_ENTRY 0x80000000 #define GROUP_REGEXP 0x01000000 #define GROUP_REGEXP_HDR 0x02000000 #define AND_MATCH 0x00020000 #define OR_MATCH 0x00010000 #define KILL_CASE_MATCH 0x00000100 #define KILL_ON_REGEXP 0x00000200 #define KILL_UNLESS_MATCH 0x00000400 #define AUTO_KILL 0x00000001 #define AUTO_SELECT 0x00000002 #define ON_SUBJECT 0x00000004 #define ON_SENDER 0x00000008 #define ON_FOLLOW_UP 0x00000010 #define ON_ANY_REFERENCES 0x00000020 #define ON_NOT_FOLLOW_UP 0x00000040 /* * external flag representation */ #define EXT_AUTO_KILL '!' #define EXT_AUTO_SELECT '+' #define EXT_KILL_UNLESS_MATCH '~' #define EXT_ON_FOLLOW_UP '>' #define EXT_ON_NOT_FOLLOW_UP '<' #define EXT_ON_ANY_REFERENCES 'a' #define EXT_ON_SUBJECT 's' #define EXT_ON_SENDER 'n' #define EXT_KILL_CASE_MATCH '=' #define EXT_KILL_ON_REGEXP '/' #define EXT_AND_MATCH '&' #define EXT_OR_MATCH '|' /* * period = nnn DAYS */ #define DAYS * 24 * 60 * 60 /* * kill_article * * return 1 to kill article, 0 to include it */ typedef struct kill_list_entry { flag_type kill_flag; char *kill_pattern; regexp *kill_regexp; struct kill_list_entry *next_kill; } kill_list_entry; static kill_list_entry *kill_tab; static char *kill_patterns; static kill_list_entry *global_kill_list = NULL; static kill_list_entry latest_kl_entry; typedef struct { regexp *group_regexp; kill_list_entry *kill_entry; } kill_group_regexp; static kill_group_regexp *group_regexp_table = NULL; static int regexp_table_size = 0; static kill_list_entry *regexp_kill_list = NULL; static group_header *current_kill_group = NULL; /* kill.c */ static void build_regexp_kill(void); static kill_list_entry *exec_kill(register kill_list_entry * kl, register article_header * ah, int *unlessp, int do_kill, int do_select); static void fput_pattern(register char *p, register FILE * f); static char *get_pattern(register char *p, int *lenp, int more); static int compile_kill_file(void); static void free_kill_list(register kill_list_entry * kl); static int print_kill(register kill_list_entry * kl); /* * Build regexp_kill_list for current_group */ static void build_regexp_kill(void) { register kill_group_regexp *tb; register int n, used_last; register char *name; regexp_kill_list = NULL; current_kill_group = current_group; name = current_group->group_name; used_last = 0; /* get AND_MATCH/OR_MATCH for free */ for (n = regexp_table_size, tb = group_regexp_table; --n >= 0; tb++) { if (tb->group_regexp != NULL) { used_last = 0; if (!regexec(tb->group_regexp, name)) continue; } else if (!used_last) continue; tb->kill_entry->next_kill = regexp_kill_list; regexp_kill_list = tb->kill_entry; used_last = 1; } } /* * execute kill patterns on article */ static kill_list_entry * exec_kill(register kill_list_entry * kl, register article_header * ah, int *unlessp, int do_kill, int do_select) { register flag_type flag; register char *string; for (; kl != NULL; kl = kl->next_kill) { flag = kl->kill_flag; if (do_select && (flag & AUTO_SELECT) == 0) goto failed; if (do_kill && (flag & AUTO_KILL) == 0) goto failed; if (kill_debug && print_kill(kl) < 0) kill_debug = 0; if (flag & KILL_UNLESS_MATCH) *unlessp = 1; if (flag & ON_ANY_REFERENCES) { if (ah->replies & 0x7f) goto match; goto failed; } if (flag & ON_SUBJECT) { if (flag & ON_FOLLOW_UP) { if ((ah->replies & 0x80) == 0) goto failed; } else if (flag & ON_NOT_FOLLOW_UP) { if (ah->replies & 0x80) goto failed; } string = ah->subject; } else string = ah->sender; if (flag & KILL_CASE_MATCH) { if (flag & KILL_ON_REGEXP) { if (regexec(kl->kill_regexp, string)) goto match; } else if (strcmp(kl->kill_pattern, string) == 0) goto match; } else if (flag & KILL_ON_REGEXP) { if (regexec_fold(kl->kill_regexp, string)) goto match; } else if (strmatch_fold(kl->kill_pattern, string)) goto match; failed: if ((flag & AND_MATCH) == 0) continue; do /* skip next */ kl = kl->next_kill; while (kl && (kl->kill_flag & AND_MATCH)); if (kl) continue; break; match: if (kill_debug) { pg_next(); tprintf("%sMATCH\n", flag & AND_MATCH ? "PARTIAL " : ""); } if (flag & AND_MATCH) continue; break; } return kl; } int kill_article(article_header * ah) { register kill_list_entry *kl; int unless_match = 0; if (kill_debug) { clrdisp(); pg_init(0, 1); pg_next(); so_printf("\1KILL: %s: %s%-.40s (%d)\1", ah->sender, ah->replies & 0x80 ? "Re: " : "", ah->subject, ah->replies & 0x7f); } kl = exec_kill((kill_list_entry *) (current_group->kill_list), ah, &unless_match, 0, 0); if (kl == NULL && group_regexp_table != NULL) { if (current_kill_group != current_group) build_regexp_kill(); kl = exec_kill(regexp_kill_list, ah, &unless_match, 0, 0); } if (kl == NULL) kl = exec_kill(global_kill_list, ah, &unless_match, 0, 0); if (kl != NULL) { if (kl->kill_flag & AUTO_KILL) goto did_kill; if (kl->kill_flag & AUTO_SELECT) { ah->attr = A_AUTO_SELECT; goto did_select; } goto no_kill; } if (unless_match) goto did_kill; no_kill: if (kill_ref_count && (int) (ah->replies & 0x7f) >= kill_ref_count) { if (kill_debug) { pg_next(); tprintf("REFERENCE COUNT (%d) >= %d\n", ah->replies & 0x7f, kill_ref_count); } goto did_kill; } did_select: if (kill_debug && pg_end() < 0) kill_debug = 0; return 0; did_kill: if (kill_debug && pg_end() < 0) kill_debug = 0; killed_articles++; return 1; } int auto_select_article(article_header * ah, int do_select) { register kill_list_entry *kl; int dummy; if (do_select == 1) { kl = ah->a_group ? (kill_list_entry *) (ah->a_group->kill_list) : (kill_list_entry *) (current_group->kill_list); kl = exec_kill(kl, ah, &dummy, !do_select, do_select); if (kl == NULL && group_regexp_table != NULL) { if (current_kill_group != current_group) build_regexp_kill(); kl = exec_kill(regexp_kill_list, ah, &dummy, !do_select, do_select); } if (kl == NULL) kl = exec_kill(global_kill_list, ah, &dummy, !do_select, do_select); } else { kl = exec_kill(&latest_kl_entry, ah, &dummy, !do_select, do_select); } if (kl == NULL) return 0; if (!do_select) killed_articles++; return 1; } static void fput_pattern(register char *p, register FILE * f) { register char c; while ((c = *p++)) { if (c == ':' || c == '\\') putc('\\', f); putc(c, f); } } static char * get_pattern(register char *p, int *lenp, int more) { register char c, *q, *start; start = q = p; while ((c = *p++)) { if (c == '\\') { c = *p++; if (c != ':' && c != '\\') *q++ = '\\'; *q++ = c; continue; } if (more) { if (c == ':') break; if (c == NL) return NULL; } else if (c == NL) break; *q++ = c; } if (c == NUL) return NULL; *q++ = NUL; *lenp = q - start; return p; } void enter_kill_file(group_header * gh, char *pattern, register flag_type flag, int days) { FILE *killf; register kill_list_entry *kl; regexp *re; char *str; str = copy_str(pattern); if ((flag & KILL_CASE_MATCH) == 0) fold_string(str); if (flag & KILL_ON_REGEXP) { re = regcomp(pattern); if (re == NULL) return; } else re = NULL; killf = open_file(relative(nn_directory, KILL_FILE), OPEN_APPEND); if (killf == NULL) { msg("cannot create kill file"); return; } if (days >= 0) { if (days == 0) days = 30; fprintf(killf, "%ld:", (long) (cur_time() + days DAYS)); } if (gh) fputs(gh->group_name, killf); fputc(':', killf); if (flag & KILL_UNLESS_MATCH) fputc(EXT_KILL_UNLESS_MATCH, killf); if (flag & AUTO_KILL) fputc(EXT_AUTO_KILL, killf); if (flag & AUTO_SELECT) fputc(EXT_AUTO_SELECT, killf); if (flag & ON_FOLLOW_UP) fputc(EXT_ON_FOLLOW_UP, killf); if (flag & ON_NOT_FOLLOW_UP) fputc(EXT_ON_NOT_FOLLOW_UP, killf); if (flag & ON_ANY_REFERENCES) fputc(EXT_ON_ANY_REFERENCES, killf); if (flag & ON_SENDER) fputc(EXT_ON_SENDER, killf); if (flag & ON_SUBJECT) fputc(EXT_ON_SUBJECT, killf); if (flag & KILL_CASE_MATCH) fputc(EXT_KILL_CASE_MATCH, killf); if (flag & KILL_ON_REGEXP) fputc(EXT_KILL_ON_REGEXP, killf); fputc(':', killf); fput_pattern(pattern, killf); fputc(NL, killf); fclose(killf); rm_kill_file(); kl = newobj(kill_list_entry, 1); latest_kl_entry.kill_pattern = kl->kill_pattern = str; latest_kl_entry.kill_regexp = kl->kill_regexp = re; latest_kl_entry.kill_flag = kl->kill_flag = flag; latest_kl_entry.next_kill = NULL; if (gh) { kl->next_kill = (kill_list_entry *) (gh->kill_list); gh->kill_list = (char *) kl; } else { kl->next_kill = global_kill_list; global_kill_list = kl; } } typedef struct { group_number ck_group; flag_type ck_flag; long ck_pattern_index; } comp_kill_entry; typedef struct { long ckh_magic; time_t ckh_db_check; off_t ckh_pattern_offset; long ckh_pattern_size; long ckh_entries; long ckh_regexp_size; } comp_kill_header; int kill_menu(article_header * ah) { int days; register flag_type flag; char *mode1, *mode2; char *pattern, *dflt, *days_str, buffer[512]; group_header *gh; days = dflt_kill_select % 100; flag = (dflt_kill_select / 100) ? AUTO_SELECT : AUTO_KILL; prompt("\1AUTO\1 (k)ill or (s)elect (CR => %s subject %d days) ", flag == AUTO_KILL ? "Kill" : "Select", days); switch (get_c()) { case CR: case NL: if (ah == NULL) { ah = get_menu_article(); if (ah == NULL) return -1; } strcpy(buffer, ah->subject); enter_kill_file(current_group, buffer, flag | ON_SUBJECT | KILL_CASE_MATCH, days); msg("DONE"); return 1; case 'k': case 'K': case '!': flag = AUTO_KILL; mode1 = "KILL"; break; case 's': case 'S': case '+': flag = AUTO_SELECT; mode1 = "SELECT"; break; default: return -1; } prompt("\1AUTO %s\1 on (s)ubject or (n)ame (s)", mode1); dflt = NULL; switch (get_c()) { case 'n': case 'N': flag |= ON_SENDER; if (ah) dflt = ah->sender; mode2 = "Name"; break; case 's': case 'S': case SP: case CR: case NL: flag |= ON_SUBJECT; if (ah) dflt = ah->subject; mode2 = "Subject"; break; default: return -1; } prompt("\1%s %s:\1 (%=/) ", mode1, mode2); pattern = get_s(dflt, NONE, "%=/", NULL_FCT); if (pattern == NULL) return -1; if (*pattern == NUL || *pattern == '%' || *pattern == '=') { if (dflt && *dflt) pattern = dflt; else { if ((ah = get_menu_article()) == NULL) return -1; pattern = (flag & ON_SUBJECT) ? ah->subject : ah->sender; } flag |= KILL_CASE_MATCH; } else if (*pattern == '/') { prompt("\1%s %s\1 (regexp): ", mode1, mode2); pattern = get_s(NONE, NONE, NONE, NULL_FCT); if (pattern == NULL || *pattern == NUL) return -1; flag |= KILL_ON_REGEXP; } strcpy(buffer, pattern); pattern = buffer; prompt("\1%s\1 in (g)roup '%s' or in (a)ll groups (g)", mode1, current_group->group_name); switch (get_c()) { case 'g': case 'G': case SP: case CR: case NL: gh = current_group; break; case 'A': case 'a': gh = NULL; break; default: return -1; } prompt("\1Lifetime of entry in days\1 (p)ermanent (30) "); days_str = get_s(" 30 days", NONE, "pP", NULL_FCT); if (days_str == NULL) return -1; if (*days_str == NUL) { days_str = "30 days"; days = 30; } else if (*days_str == 'p' || *days_str == 'P') { days_str = "perm"; days = -1; } else if (isdigit(*days_str)) { days = atoi(days_str); sprintf(days_str, "%d days", days); } else { ding(); return -1; } prompt("\1CONFIRM\1 %s %s %s%s: %-.35s%s ", mode1, mode2, days_str, (flag & KILL_CASE_MATCH) ? " exact" : (flag & KILL_ON_REGEXP) ? " regexp" : "", pattern, (int) strlen(pattern) > 35 ? "..." : ""); if (yes(0) <= 0) return -1; enter_kill_file(gh, pattern, flag, days); return (flag & AUTO_KILL) ? 1 : 0; } static int compile_kill_file(void) { FILE *killf, *compf, *patternf, *dropf; comp_kill_header header; comp_kill_entry entry; time_t now, age; long cur_line_start; char line[512]; register char *cp, *np; register int c; group_header *gh; flag_type flag, fields[10]; int any_errors, nfield, nf, len; any_errors = 0; header.ckh_entries = header.ckh_regexp_size = 0; killf = open_file(relative(nn_directory, KILL_FILE), OPEN_READ | DONT_CREATE); if (killf == NULL) return 0; dropf = NULL; compf = open_file(relative(nn_directory, COMPILED_KILL), OPEN_CREATE); if (compf == NULL) goto err1; new_temp_file(); if ((patternf = open_file(temp_file, OPEN_CREATE)) == NULL) goto err2; msg("Compiling kill file"); fseek(compf, sizeof(header), 0); now = cur_time(); next_entry: for (;;) { cur_line_start = ftell(killf); if (fgets(line, 512, killf) == NULL) break; cp = line; while (*cp && isascii(*cp) && isspace(*cp)) cp++; if (*cp == NUL || *cp == '#' || !isascii(*cp)) continue; if ((np = strchr(cp, ':')) == NULL) goto bad_entry; /* optional "age:" */ if (np != cp && isdigit(*cp)) { *np++ = NUL; age = (time_t) atol(cp); if (age < now) goto drop_entry; cp = np; if ((np = strchr(cp, ':')) == NULL) goto bad_entry; } /* "group-name:" or "/regexp:" or ":" for all groups */ flag = COMP_KILL_ENTRY; if (np == cp) { entry.ck_group = -1; np++; } else { *np++ = NUL; if (*cp == '/') { entry.ck_group = (long) ftell(patternf); cp++; len = strlen(cp) + 1; if (fwrite(cp, sizeof(char), len, patternf) != len) goto err3; flag |= GROUP_REGEXP | GROUP_REGEXP_HDR; } else { if ((gh = lookup(cp)) == NULL || (gh->master_flag & M_IGNORE_GROUP)) { tprintf("Unknown/ignored group in kill file: %s\n", cp); any_errors++; goto drop_entry; } entry.ck_group = gh->group_num; } } /* flags */ cp = np; nfield = 0; for (;;) { switch (*cp++) { case EXT_AND_MATCH: case EXT_OR_MATCH: fields[nfield++] = flag; flag &= ~(AND_MATCH | ON_SUBJECT | ON_SENDER | KILL_CASE_MATCH | KILL_ON_REGEXP | GROUP_REGEXP_HDR); flag |= (cp[-1] == EXT_AND_MATCH) ? AND_MATCH : OR_MATCH; continue; case EXT_AUTO_KILL: flag |= AUTO_KILL; continue; case EXT_AUTO_SELECT: flag |= AUTO_SELECT; continue; case EXT_ON_FOLLOW_UP: flag |= ON_FOLLOW_UP; continue; case EXT_ON_NOT_FOLLOW_UP: flag |= ON_NOT_FOLLOW_UP; continue; case EXT_ON_ANY_REFERENCES: flag |= ON_ANY_REFERENCES; continue; case EXT_ON_SUBJECT: flag |= ON_SUBJECT; continue; case EXT_ON_SENDER: flag |= ON_SENDER; continue; case EXT_KILL_CASE_MATCH: flag |= KILL_CASE_MATCH; continue; case EXT_KILL_UNLESS_MATCH: flag |= KILL_UNLESS_MATCH; continue; case EXT_KILL_ON_REGEXP: flag |= KILL_ON_REGEXP; continue; case ':': break; case NL: goto bad_entry; default: tprintf("Ignored flag '%c' in kill file\n", cp[-1]); any_errors++; continue; } break; } fields[nfield++] = flag; for (nf = 0; --nfield >= 0; nf++) { entry.ck_flag = flag = fields[nf]; np = cp; if ((cp = get_pattern(np, &len, nfield)) == NULL) goto bad_entry; if ((flag & KILL_CASE_MATCH) == 0) fold_string(np); entry.ck_pattern_index = ftell(patternf); if (fwrite((char *) &entry, sizeof(entry), 1, compf) != 1) goto err3; if (fwrite(np, sizeof(char), len, patternf) != len) goto err3; header.ckh_entries++; if (flag & GROUP_REGEXP) header.ckh_regexp_size++; } } header.ckh_pattern_size = ftell(patternf); fclose(patternf); patternf = open_file(temp_file, OPEN_READ | OPEN_UNLINK); if (patternf == NULL) goto err2; header.ckh_pattern_offset = ftell(compf); while ((c = getc(patternf)) != EOF) putc(c, compf); fclose(patternf); rewind(compf); header.ckh_magic = COMP_KILL_MAGIC; header.ckh_db_check = master.db_created; if (fwrite((char *) &header, sizeof(header), 1, compf) != 1) goto err2; fclose(compf); fclose(killf); if (dropf != NULL) fclose(dropf); if (any_errors) { tputc(NL); any_key(0); } return 1; bad_entry: tprintf("Incomplete kill file entry:\n%s", line); fl; any_errors++; drop_entry: if (dropf == NULL) { dropf = open_file(relative(nn_directory, KILL_FILE), OPEN_UPDATE | DONT_CREATE); if (dropf == NULL) goto next_entry; } fseek(dropf, cur_line_start, 0); fwrite("# ", sizeof(char), 2, dropf); goto next_entry; err3: fclose(patternf); unlink(temp_file); err2: fclose(compf); rm_kill_file(); err1: fclose(killf); if (dropf != NULL) fclose(dropf); msg("cannot compile kill file"); return 0; } int init_kill(void) { FILE *killf; comp_kill_header header; comp_kill_entry entry; register kill_list_entry *kl; register kill_group_regexp *tb; register group_header *gh; time_t kill_age, comp_age; register long n; int first_try = 1; Loop_Groups_Header(gh) gh->kill_list = NULL; kill_age = file_exist(relative(nn_directory, KILL_FILE), "frw"); if (kill_age == 0) return 0; comp_age = file_exist(relative(nn_directory, COMPILED_KILL), "fr"); again: if (comp_age < kill_age && !compile_kill_file()) return 0; kill_tab = NULL; kill_patterns = NULL; group_regexp_table = NULL; regexp_table_size = 0; killf = open_file(relative(nn_directory, COMPILED_KILL), OPEN_READ); if (killf == NULL) return 0; if (fread((char *) &header, sizeof(header), 1, killf) != 1) goto err; /* MAGIC check: format changed or using different hardware */ if (header.ckh_magic != COMP_KILL_MAGIC) goto err; #ifndef NOV /* DB check: if database is rebuilt, group numbers may change */ if (header.ckh_db_check != master.db_created) goto err; #else /* ugly hack for NOV as there isn't a master to check */ if (first_try) goto err; #endif if (header.ckh_entries == 0) { fclose(killf); kill_file_loaded = 1; return 0; } if (header.ckh_pattern_size > 0) { kill_patterns = newstr(header.ckh_pattern_size); fseek(killf, header.ckh_entries * sizeof(entry), 1); if (fread(kill_patterns, sizeof(char), (int) header.ckh_pattern_size, killf) != header.ckh_pattern_size) goto err; } else kill_patterns = newstr(1); kill_tab = newobj(kill_list_entry, header.ckh_entries); if ((regexp_table_size = header.ckh_regexp_size)) group_regexp_table = newobj(kill_group_regexp, header.ckh_regexp_size); tb = group_regexp_table; fseek(killf, sizeof(header), 0); for (n = header.ckh_entries, kl = kill_tab; --n >= 0; kl++) { if (fread((char *) &entry, sizeof(entry), 1, killf) != 1) goto err; if (header.ckh_pattern_size <= entry.ck_pattern_index || entry.ck_pattern_index < 0) goto err; kl->kill_pattern = kill_patterns + entry.ck_pattern_index; kl->kill_flag = entry.ck_flag; if (kl->kill_flag & KILL_ON_REGEXP) kl->kill_regexp = regcomp(kl->kill_pattern); else kl->kill_regexp = NULL; if (kl->kill_flag & GROUP_REGEXP) { if (kl->kill_flag & GROUP_REGEXP_HDR) { if (header.ckh_pattern_size <= entry.ck_group || entry.ck_group < 0) goto err; tb->group_regexp = regcomp(kill_patterns + entry.ck_group); } else tb->group_regexp = NULL; tb->kill_entry = kl; tb++; } else if (entry.ck_group >= 0) { gh = ACTIVE_GROUP(entry.ck_group); kl->next_kill = (kill_list_entry *) (gh->kill_list); gh->kill_list = (char *) kl; } else { kl->next_kill = global_kill_list; global_kill_list = kl; } } fclose(killf); kill_file_loaded = 1; return 1; err: if (group_regexp_table != NULL) freeobj(group_regexp_table); if (kill_patterns != NULL) freeobj(kill_patterns); if (kill_tab != NULL) freeobj(kill_tab); fclose(killf); rm_kill_file(); if (first_try) { first_try = 0; comp_age = 0; goto again; } strcpy(delayed_msg, "Error in compiled kill file (ignored)"); Loop_Groups_Header(gh) gh->kill_list = NULL; global_kill_list = NULL; group_regexp_table = NULL; return 0; } void rm_kill_file(void) { unlink(relative(nn_directory, COMPILED_KILL)); } static void free_kill_list(register kill_list_entry * kl) { register kill_list_entry *nxt; while (kl) { nxt = kl->next_kill; if (kl->kill_regexp != NULL) freeobj(kl->kill_regexp); if ((kl->kill_flag & COMP_KILL_ENTRY) == 0) { if (kl->kill_pattern != NULL) freeobj(kl->kill_pattern); freeobj(kl); } kl = nxt; } } void free_kill_entries(void) { register group_header *gh; register kill_group_regexp *tb; register int n; Loop_Groups_Header(gh) if (gh->kill_list) { free_kill_list((kill_list_entry *) (gh->kill_list)); gh->kill_list = NULL; } free_kill_list(global_kill_list); global_kill_list = NULL; if ((tb = group_regexp_table)) { for (n = regexp_table_size; --n >= 0; tb++) if (tb->group_regexp != NULL) freeobj(tb->group_regexp); freeobj(group_regexp_table); group_regexp_table = NULL; } if (kill_patterns != NULL) freeobj(kill_patterns); if (kill_tab != NULL) freeobj(kill_tab); kill_file_loaded = 0; } static flag_type pk_prev_and; static int print_kill(register kill_list_entry * kl) { register flag_type flag = kl->kill_flag; if (pg_next() < 0) return -1; if (pk_prev_and) tprintf("\r AND "); else tprintf("\r%s%s ON ", flag & AUTO_KILL ? "AUTO KILL" : flag & AUTO_SELECT ? "AUTO SELECT" : "", (flag & KILL_UNLESS_MATCH) == 0 ? "" : flag & AUTO_KILL ? " UNLESS" : flag & AUTO_SELECT ? "" : "KEEP"); tprintf("%s '%s%.35s'%s\n", flag & ON_SUBJECT ? "SUBJECT" : flag & ON_SENDER ? "NAME" : flag & ON_ANY_REFERENCES ? "ANY REF" : "????", flag & ON_NOT_FOLLOW_UP ? "!Re: " : flag & ON_FOLLOW_UP ? "Re: " : "", kl->kill_pattern, flag & KILL_CASE_MATCH ? (flag & KILL_ON_REGEXP ? " (re exact)" : " (exact)") : (flag & KILL_ON_REGEXP ? " (re fold)" : "")); pk_prev_and = flag & AND_MATCH; return 0; } void dump_kill_list(void) { register kill_list_entry *kl; pg_init(0, 1); pg_next(); kl = (kill_list_entry *) (current_group->kill_list); if (current_kill_group != current_group) build_regexp_kill(); if (kl == NULL && regexp_kill_list == NULL) { tprintf("No kill entries for %s", current_group->group_name); } else { so_printf("\1GROUP %s kill list entries\1", current_group->group_name); pk_prev_and = 0; for (; kl; kl = kl->next_kill) if (print_kill(kl) < 0) goto out; pk_prev_and = 0; for (kl = regexp_kill_list; kl; kl = kl->next_kill) if (print_kill(kl) < 0) goto out; if (pg_next() < 0) goto out; } if (pg_next() < 0) goto out; so_printf("\1GLOBAL kill list entries:\1"); pk_prev_and = 0; for (kl = global_kill_list; kl != NULL; kl = kl->next_kill) if (print_kill(kl) < 0) goto out; out: pg_end(); } nn-6.7.4/execute.c0000644000175000017500000000721510267553602013372 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * UNIX command execution. */ #include #include #include #include #include #include #include #include "config.h" #include "global.h" #include "execute.h" #include "folder.h" #include "nn_term.h" /* execute.c */ static int shell_check(void); int shell_restrictions = 0; /* disable shell escapes */ static char *init_shell = SHELL; char *user_shell; char *exec_chdir_to = NULL; extern int errno; static int shell_check(void) { if (shell_restrictions) { msg("Restricted operation - not allowed"); return -1; } return 0; } void init_execute(void) { if ((user_shell = getenv("SHELL")) == NULL) user_shell = SHELL; } int execute(char *path, char **args, int toggle_visual) { int was_raw, pid, i, status; sig_type(*quit) (), (*intr) (), (*tstp) (); was_raw = toggle_visual ? visual_off() : unset_raw(); while ((pid = fork()) == -1) sleep(1); if (pid == 0) { for (i = 3; i < 20; i++) close(i); if (exec_chdir_to != NULL) chdir(exec_chdir_to); if (execvp(path, args)) { msg("%s: %s", path, strerror(errno)); user_delay(2); exit(20); } } quit = signal(SIGQUIT, SIG_IGN); intr = signal(SIGINT, SIG_IGN); #ifdef HAVE_JOBCONTROL tstp = signal(SIGTSTP, SIG_DFL); #endif while ((i = wait(&status)) != pid && (i != -1 || errno == EINTR)); signal(SIGQUIT, quit); signal(SIGINT, intr); #ifdef HAVE_JOBCONTROL signal(SIGTSTP, tstp); #endif if (toggle_visual) { visual_on(); if (toggle_visual == 2) s_redraw++; } if (was_raw) nn_raw(); return (status & 0xff) ? 0x100 : (status >> 8); } int shell_escape(void) { static char command[FILENAME] = ""; char *cmd; int first = 1; if (shell_check()) return 0; for (;;) { prompt("\1!\1"); cmd = get_s(command, NONE, NONE, NULL_FCT); if (cmd == NULL) return !first; if (*cmd == NUL) { if (first) run_shell((char *) NULL, 1, 0); return 1; } strcpy(command, cmd); if (run_shell(command, first, 0) < 0) return !first; first = 0; prompt_line = -1; } } static char *exec_sh_args[] = { "nnsh", (char *) NULL, /* "-c" or "-i" */ (char *) NULL, /* cmdstring */ (char *) NULL }; int run_shell(char *command, int clear, int init_sh) /* * clear -2 => no command output (:!!command) - keep visual, output * before command: -1 => none, 0 => CR/NL, 1 => clear */ /* init_sh 0 => use user_shell, else use init_shell */ { char cmdstring[512]; if (shell_check()) return -1; if (command != NULL) { if (!expand_file_name(cmdstring, command, 1)) return -1; exec_sh_args[1] = "-c"; exec_sh_args[2] = cmdstring; } else { exec_sh_args[1] = "-i"; exec_sh_args[2] = NULL; } if (clear > 0) { clrdisp(); fl; } else if (clear == 0) { tputc(CR); tputc(NL); } return execute(init_sh ? init_shell : user_shell, exec_sh_args, clear == -2 ? 0 : 1); } #ifndef HAVE_JOBCONTROL static char *exec_suspend_args[] = { "nnsh", "-i", (char *) NULL }; #endif int suspend_nn(void) { int was_raw; if (shell_check()) return 0; gotoxy(0, Lines - 1); clrline(); #ifdef HAVE_JOBCONTROL was_raw = visual_off(); kill(process_id, SIGSTOP); visual_on(); s_redraw++; if (was_raw) nn_raw(); #else execute(user_shell, exec_suspend_args, 2); #endif return 1; } nn-6.7.4/reroute.h0000644000175000017500000000016207751420632013414 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int reroute(char *, char *); nn-6.7.4/variable.h0000644000175000017500000000114007751420633013512 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int set_variable(char *, int, char *); void toggle_variable(char *); void lock_variable(char *); int test_variable(char *); void var_compl_opts(int); int var_completion(char *, int); void mark_var_stack(void); int push_variable(char *); void restore_variables(void); void disp_variables(int, char *); void print_variable_config(FILE *, int); void var_options(char **, register char *, flag_type *); nn-6.7.4/awksplit.c0000644000175000017500000000514107751524306013565 0ustar mtpinsmtpins/*******************WARNING********************* This is a *MODIFIED* version of Geoff Coller's proof-of-concept NOV implementation. It has been modified to support threading directly from a file handle to a NNTP server without a temporary file. This is not a complete distribution. We have only distributed enough to support NN's needs. The original version came from world.std.com:/src/news/nov.dist.tar.Z and was dated 11 Aug 1993. In any case, bugs you find here are probably my fault, as I've trimmed a fair bit of unused code. -Peter Wemm */ /* * Copyright (c) Geoffrey Collyer 1992, 1993. * All rights reserved. * Written by Geoffrey Collyer. * Thanks to UUNET Communications Services Inc for financial support. * * This software is not subject to any license of the American Telephone * and Telegraph Company, the Regents of the University of California, or * the Free Software Foundation. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * * 4. This notice may not be removed or altered. */ /* * awksplit - awk-like split(3) that handles memory allocation automatically */ #include #include "config.h" #include "split.h" int /* number of fields, including overflow */ awksplit(char *string, register char ***fieldsp, register int nfields, char *sep) /* fieldsp; list is not NULL-terminated */ /* nfields; number of entries available in fields[] */ /* sep; "" white, "c" single char, "ab" [ab]+ */ { register int nf; nf = split(string, *fieldsp, nfields, sep); if (nf > nfields) { /* too many fields to fieldsp? */ register char **array = (char **) malloc((unsigned) (nf * sizeof(char *))); register int i; if (array == NULL) *fieldsp = NULL; /* you lose */ else { for (i = 0; i < nfields; i++) array[i] = (*fieldsp)[i]; *fieldsp = array; (void) split(array[nfields - 1], &array[nfields], nf - nfields, sep); } } return nf; } nn-6.7.4/kill.h0000644000175000017500000000071007751420626012664 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int kill_article(article_header *); int auto_select_article(article_header *, int); void enter_kill_file(group_header *, char *, register flag_type, int); int kill_menu(article_header *); int init_kill(void); void rm_kill_file(void); void free_kill_entries(void); void dump_kill_list(void); nn-6.7.4/db.h0000644000175000017500000001035610217206250012307 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * DATABASE ORGANIZATION: * * The central nn information is contained in following files: * DB_DIRECTORY/MASTER * DB_DIRECTORY/GROUPS * DB_DIRECTORY/DATA/nnn.x * DB_DIRECTORY/DATA/nnn.d * * The MASTER file consists of a header and one entry for each news * group. The sequence of the group headers defines the group * number associated with the group. * * The GROUPS file contains the names of the news groups; the names * occur in the same sequence as in the MASTER file. * * For each news group, the DATA directory contains two files whose * name is constructed from the group number 'nnn': * * nnn.x Index file * nnn.d Data file * * The index file provides a a mapping from article numbers to offsets * in the data file. * * The data file contains the actual header data. Each article is * represented by a Header, an array of Cross Postings, and the * strings representing the sender name and the article subject: * * header * group_number 1 [ if cross posted ] * group_number 2 * ... * sender name (null terminated) [if sender_length > 0] * subject (null terminated) [if subject_length > 0] * * For a digest, cross posted groups are only specified for the * first entry (the header entry). * * On disk, the article_number is negative for digest article * header and zero for following sub articles. * * The format of the index and data files are specified below. * * Unless NETWORK_DATABASE is defined, the database will * will contain machine dependent binary data. */ #ifndef _NN_DB_H #define _NN_DB_H 1 #ifdef NETWORK_DATABASE typedef int32 cross_post_number; #ifdef NETWORK_BYTE_ORDER #define NETW_CROSS_INT(cp) cp #define NETW_CROSS_EXT(cp) cp #else #define NETW_CROSS_INT(cp) ntohl(cp) #define NETW_CROSS_EXT(cp) htonl(cp) #endif #else typedef group_number cross_post_number; #define NETW_CROSS_INT(cp) cp #define NETW_CROSS_EXT(cp) cp #endif typedef struct { article_number dh_number; time_stamp dh_date; /* encoded Date: filed (not a time_t value!!) */ off_t dh_hpos; /* absolute offset for first byte of header */ off_t dh_lpos; /* absolute offset for last byte of article */ int16 dh_fpos; /* relative offset for first byte in article * text */ int16 dh_lines; int8 dh_replies; int8 dh_cross_postings; int8 dh_subject_length; int8 dh_sender_length; } data_header; #define DBUF_SIZE 255 typedef struct { int dh_type; #define DH_NORMAL 0 #define DH_DIGEST_HEADER 1 #define DH_SUB_DIGEST 2 cross_post_number dh_cross[DBUF_SIZE + 1]; char dh_sender[DBUF_SIZE + 1]; char dh_subject[DBUF_SIZE + 1]; } data_dynamic_data; #ifndef NOV int open_groups(int); void close_groups(void); void db_rewrite_groups(int, group_header *); int db_parse_group(register group_header *, int); void db_write_master(void); int db_write_art(FILE * f); long get_data_offset(group_header *, article_number); int db_write_offset(FILE *, long *); #endif int init_group(register group_header *); void open_master(int); void db_expand_master(void); void close_master(void); int update_group(group_header *); void sort_groups(void); group_header *lookup_no_alias(char *); group_header *lookup(char *); int art_collected(group_header *, article_number); char *db_data_path(char *, group_header *, char); FILE *open_data_file(group_header *, char, int); void db_write_group(register group_header *); off_t db_read_art(FILE *); long get_index_offset(group_header *, article_number); int db_read_offset(FILE *, long *); #ifdef CACHE_PURPOSE char *purp_lookup(char *); #endif #ifdef NOV void db_read_group(register group_header *, article_number, article_number); #endif extern data_header db_hdr; extern data_dynamic_data db_data; #endif /* _NN_DB_H */ nn-6.7.4/nnstats.sh0000644000175000017500000000571505750350011013603 0ustar mtpinsmtpins# --- prefix is inserted above by make --- # # Extract collection & expiration statistics from Log file. # # From: Mark Moraes # # Heavily modified by Kim F. Storm: # Added expiration statistics # Will now make daily statistics & grand summary # Split into two awk scripts to fit in argument list space # (and as a side-effect to be able to use functions in old awk!!!) CHECK="0>0" LONG=0 SUMMARY=1 debug=false LOOP=true while $LOOP; do case "$1" in -l) LONG=1 shift;; -d) CHECK="\$2!=\"$2\" || \$3!=\"$3\"" shift; shift; shift;; -m) CHECK="\$2!=\"$2\"" shift; shift;; -t) SUMMARY=0 shift;; -D) debug=true shift;; -*) echo "unknown option: $1" exit 1;; *) LOOP=false ;; esac done grep '^[CX]:' `ls -tr ${@-$LOG}` | ${AWK} 'BEGIN{ day="0"; l='$LONG'; t='$SUMMARY' f=" %d %d %d %d %d %d %d\n" } '"$CHECK"' { next } day!=$3{ if(day!="0")print "d " date if(l && day!="0" && (run>0 || xrun>0)){ print "h" printf "c" f,run,art,Mart,gr,Mgr,sec,Msec printf "e" f,xrun,xart,xMart,xgr,xMgr,xsec,xMsec } day=$3 date=$2 " " $3 run=art=gr=sec=0 Mart=Mgr=Msec=0 xrun=xart=xgr=xsec=0 xMart=xMgr=xMsec=0 } $6=="Collect:" { if ($7 <= 0) next run++; art+=$7; gr+=$9; sec+=$11 Trun++; Tart+=$7; Tgr+=$9; Tsec+=$11 if ($7 > Mart) Mart=$7; if ($9 > Mgr) Mgr=$9; if ($11 > Msec) Msec=$11; if ($11 > 0) A=$7 / $11; else A=$7; if (A > Mps) Mps=A; next } $6=="Expire:" { if ($7 <= 0) next xrun++; xart+=$7; xgr+=$9; xsec+=$11 xTrun++; xTart+=$7; xTgr+=$9; xTsec+=$11 if ($7 > xMart) xMart=$7 if ($9 > xMgr) xMgr=$9 if ($11 > xMsec) xMsec=$11 if ($11 > 0) A=$7 / $11; else A=$7 if (A > xMps) xMps=A next } END{ if (day == "0") { print "Z"; exit } print "d " date if (l && (run > 0 || xrun > 0)) { print "h" printf "c" f,run,art,Mart,gr,Mgr,sec,Msec printf "e" f,xrun,xart,xMart,xgr,xMgr,xsec,xMsec } if (t) { print "H" printf "C %d %d %d %d\n",Trun,Tart,Tsec,Mps printf "E %d %d %d %d\n",xTrun,xTart,xTsec,xMps } }' | if $debug ; then cat else ${AWK} 'BEGIN{ first="" } /^d/{ last=$2 " " $3 if (first == "") first=last next } /^h/{ printf "\nStatistics for %s\n", last next } /^H/{ tostr="" if (first != last) tostr=" to " last printf "\nSummary for %s%s:\n", first, tostr next } /^[cC]/{tp="Collection"} /^[eE]/{tp="Expiration"} /^[ce]/{ if ($2 == 0) next printf "\n %s runs: %d\n", tp, $2 printf "%10d articles, average of %5d per run, maximum %6d\n", $3, $3/$2, $4 printf "%10d groups, average of %5d per run, maximum %6d\n", $5, $5/$2, $6 printf "%10d seconds, average of %5d per run, maximum %6d\n", $7, $7/$2, $8 next } /^[CE]/{ printf "\n %s runs: %d\n", tp, $2 if ($2 == 0) next printf "%10d articles, average of %5d per run\n", $3, $3/$2 printf "%10d seconds, average of %5d per run\n", $4, $4/$2 if ($4>0) avg=$3/$4; else avg=$3 printf " average of %d articles per second, maximum %d\n", avg, $5 } /^Z/{ printf "Log is empty\n" }' fi nn-6.7.4/newsrc.h0000644000175000017500000000176310220663777013244 0ustar mtpinsmtpins/* * Copyright (c) 2003-2005 Michael T Pins. All rights reserved. */ void visit_rc_file(void); void use_newsrc(register group_header *, int); attr_type test_article(article_header *); void flush_newsrc(void); int restore_bak(void); void update_rc(register group_header *); void update_rc_all(register group_header *, int); void add_to_newsrc(group_header *); article_number restore_rc(register group_header *, article_number); int restore_unread(register group_header *); void count_unread_articles(void); void prt_unread(register char *); article_number add_unread(group_header *, int); void opt_nngrep(int, char *[]); void do_grep(char **); int opt_nntidy(int, char *[]); void do_tidy_newsrc(void); int opt_nngoback(int, char ***); void do_goback(void); #ifdef NOV group_header *add_new_group(char *); #endif nn-6.7.4/options.h0000644000175000017500000000520107751420630013417 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2003 Michael T Pins. All rights reserved. * * Include file for generic option parsing */ /* * To use this routine, you must a table called an Option_Description. * Each element in this table describes one possible option: * Its option letter * Its argument type (if any) * Whether an argument is mandatory or optional * The address of the variable holding the option value * The defualt value if argument is optional * * Example: * * A program accepts the following options: * -a [no value] * -b N [a numeric value] * -p [N] [an optional numeric value] * -t S [a string value] * * The corresponding option description table would then look like: * * #include * int a_flg = 1, b_value = 0, p_value = 0; * char *t_string = "default"; * * Option_Description( options ) { * 'a', Bool_Option(a_flg), * 'b', Int_Option(b_value), * 'p', Int_Option_Optional(p_value, -1), * 't', String_Option(t_string), * '\0', * } * To parse the argument list - and the contents of the environment variable * XXINIT, all that has to be done is to issue the following call: * * files = parse_options(argc, argv, "XXINIT", options, NULL); * * If no environment variable is associated with the program, use NULL as * the third parameter. * * Upon return, the elements argv[1] .. argv[files] will contain * the file names (and other 'non-options') that occur in the argument list. * * The last NULL argument may be replaced by your own 'usage routine' * which will be called in the following way: * * usage(pname) * char *pname; /+ argv[0] without path +/ * * * char *program_name(argv) * * return a pointer to the last component of argv[0] (the program name with * with the path deleted). * */ #ifndef _NN_OPTIONS_H #define _NN_OPTIONS_H 1 struct option_descr { char option_letter; char option_type; char **option_address; char *option_default; }; #define Option_Description(name) \ struct option_descr name[] = #define Bool_Option(addr) \ 1, (char **)(&addr), (char *)0 #define String_Option(addr) \ 2, &addr, (char *)0 #define String_Option_Optional(addr, default) \ 3, &addr, default #define Int_Option(addr) \ 4, (char **)(&addr), (char *)0 #define Int_Option_Optional(addr, default) \ 5, (char **)(&addr), (char *)default char *program_name(char **); void parseargv_variables(char **, int); int parse_options(int, char **, char *, struct option_descr[], char *); #endif /* _NN_OPTIONS_H */ nn-6.7.4/regexp.h0000644000175000017500000000157710223055055013223 0ustar mtpinsmtpins/* * Definitions etc. for regexp(3) routines. * * Caveat: this is V8 regexp(3) [actually, a reimplementation thereof], * not the System V one. */ #ifndef REGEXP_H #define REGEXP_H #define NSUBEXP 10 typedef struct regexp { char *startp[NSUBEXP]; char *endp[NSUBEXP]; char regstart; /* Internal use only. */ char reganch; /* Internal use only. */ char *regmust; /* Internal use only. */ int regmlen; /* Internal use only. */ char program[1]; /* Unwarranted chumminess with compiler. */ } regexp; /* * The first byte of the regexp internal "program" is actually this magic * number; the start node begins in the second byte. */ #define MAGIC 0234 extern regexp *regcomp(char *); extern int regexec(register regexp *, register char *); #endif /* REGEXP_H */ nn-6.7.4/expire.h0000644000175000017500000000015207751420624013223 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int do_expire(void); nn-6.7.4/libnov.h0000644000175000017500000000066110230567062013217 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ void novartdir(char *); void novfilename(char *); struct novgroup *novopen(char *); struct novgroup *novstream(register FILE *); struct novart *novall(register struct novgroup *, register article_number, register article_number); struct novart *novnext(register struct novgroup *); void novclose(register struct novgroup *); nn-6.7.4/split.h0000644000175000017500000000017707751420632013070 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int split(char *, char *[], int, char *); nn-6.7.4/digest.h0000644000175000017500000000062507751420624013213 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ #include "news.h" void init_digest_parsing(void); int is_digest(register char *); int get_folder_type(FILE *); int get_digest_article(FILE *, news_header_buffer); int skip_digest_body(register FILE *); int parse_digest_header(FILE *, int, news_header_buffer); nn-6.7.4/nn_term.h0000644000175000017500000000570015213127154013367 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Terminal interface definitions. */ #ifndef _NN_TERM_H #define _NN_TERM_H 1 #include extern int Lines, Columns, Name_Length; extern int cookie_size; extern int STANDOUT; #define NONE (char *)NULL /* no default string etc. */ #define GET_S_BUFFER 256 /* if caller want to reuse get_s buffer */ /* special keys returned by get_c() */ #define K_interrupt CONTROL_('G') #define K_up_arrow 0x0081 #define K_down_arrow 0x0082 #define K_left_arrow 0x0083 #define K_right_arrow 0x0084 #define K_function(n) (0x0085 + n) #define K_m_d1 0x008f #define K_m_d2 0x0090 #define K_m_d3 0x0091 #define K_m_u1 0x0092 #define K_m_u2 0x0093 #define K_m_u3 0x0094 #define GETC_COMMAND 0x4000 /* bit set by get_c to return a command */ /* * prompt_line = ... * prompt( [P_COMMAND], ] [ format [, arg1 ... , arg4] ] ); * * P_MOVE: just move to prompt line * P_REDRAW: redraw prompt * P_VERSION: print version on prompt line */ extern int prompt_line; /* prompt line */ #define P_MOVE (char *)1 #define P_REDRAW (char *)5 #define P_VERSION (char *)9 #define P_SAVE (char *)13 #define P_RESTORE (char *)17 #define CLEAR_DISPLAY 0x01 #define CONFIRMATION 0x02 #include "keymap.h" void enter_multi_key(int, key_type *); void dump_multi_keys(void); void init_term(int); void home(void); void save_xy(void); void restore_xy(void); void gotoxy(int, int); void clrdisp(void); void clrline(void); void clrline_noflush(void); void clrpage(void); void tprintf(char *,...); void tputc(int); int so_gotoxy(int, int, int); void so_printf(char *,...); void so_end(void); void so_printxy(int, int, char *,...); int underline(int); int highlight(int); int shadeline(int); void xterm_mouse_on(void); void xterm_mouse_off(void); void visual_on(void); int visual_off(void); void nn_raw(void); int no_raw(void); int unset_raw(void); void flush_input(void); int get_c(void); char *get_s(char *, char *, char *, fct_type); int list_completion(char *); int yes(int); void ding(void); void display_file(char *, int); void nn_exitmsg(int, char *,...); void vmsg(char *, va_list); void msg(char *,...); void clrmsg(int); void prompt(char *,...); int any_key(int); void pg_init(int, int); int pg_scroll(int); int pg_next(void); void pg_indent(int); int pg_end(void); void user_delay(int); #endif /* _NN_TERM_H */ nn-6.7.4/sequence.h0000644000175000017500000000052707751420632013544 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int only_folder_args(char **); void parse_save_files(register FILE *); void named_group_sequence(char **); void normal_group_sequence(void); void start_group_search(char *); group_header *get_group_search(void); nn-6.7.4/term.c0000644000175000017500000013735715213134115012700 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Terminal interface. */ #define raw __curses__raw__ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "global.h" #include "execute.h" #include "folder.h" #include "keymap.h" #include "macro.h" #include "nn.h" #include "regexp.h" #include "nn_term.h" #if !defined(__FreeBSD__) && !(__NetBSD__) && !defined(NeXT) && !defined(__linux__) #include #else #include #endif #ifdef RESIZING #include /* for TIOCGWINSZ */ extern int s_resized; #endif /* RESIZING */ #ifdef FAKE_INTERRUPT #include #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef USE_TERMINFO #include #ifndef VINTR #include /* some systems don't include this in * curses.h */ #endif /* VINTR */ #ifndef auto_left_margin #include #endif /* !auto_left_margin */ #else #define USE_TERMCAP #if !defined(SUNOS4) && !defined(NeXT) #include #endif /* SUNOS4 */ #endif #ifdef HAVE_TERMIO_H #ifdef USE_TERMCAP #include #endif /* USE_TERMCAP */ #else #ifndef __FreeBSD__ #include #endif /* __FreeBSD__ */ #endif #ifdef SYSV_RESIZING #include #include #endif /* SYSV_RESIZING */ /* SYSV curses.h clash */ #undef raw /* AIX term.h clash */ #ifdef _AIX #undef Lines #undef Columns #endif /* _AIX */ #ifdef __osf__ void cfmakeraw(struct termios * t); #endif /* term.c */ #ifdef HAVE_TERMIOS_H static int outc(int c); #else static int outc(char c); #endif static void set_term_speed(register unsigned long sp); static void raw_not_ok_error(void); static sig_type rd_timeout(int n); static int read_char_kbd(int tmo); static void unread_char(int c); static int nnstandout(int); static sig_type catch_winch(int n); void visual_on(void); void xterm_mouse_on(void); void xterm_mouse_off(void); extern int data_bits; extern int batch_mode; extern char *help_directory; extern void ding(); extern void clrmsg(int); extern void gotoxy(int, int); struct msg_list { char *buf; struct msg_list *prev; }; static struct msg_list *msg_stack = NULL, *msg_ptr = NULL; int message_history = 15; char *term_name = NULL; int show_current_time = 1; int conf_dont_sleep = 0; int prompt_length; int prompt_line; int terminal_speed = 0; int slow_speed = 1200; int any_message = 0; int flow_control = 1; int use_visible_bell = 1; /* if supported by terminal */ int ignore_xon_xoff = 1; int multi_key_guard_time = 2; /* tenths of a second */ int guard_double_slash = 0; /* need /// or //+ to clear line */ char *shade_on_attr = NULL; char *shade_off_attr = NULL; int mouse_state; /* if xterm is in mouse state */ int mouse_usage; /* 0 don't set mouse, 1 only if xterm, 2 set * mouse */ key_type help_key = '?'; key_type comp1_key = SP; key_type comp2_key = TAB; key_type erase_key, kill_key; key_type delword_key = CONTROL_('W'); static char bell_str[256] = "\007"; static int appl_keypad_mode = 0; #ifdef USE_TERMINFO #define HAS_CAP(str) (str && *str) extern char *tgoto(); /* some systems don't have this in term.h */ #else char *tgoto (const char *, int, int); char PC; char *BC, *UP; char nnspeed; static char XBC[64], XUP[64]; static char enter_ca_mode[64], exit_ca_mode[64]; static char cursor_home[64]; static char cursor_address[128]; static char clear_screen[64]; static char clr_eol[64]; static char clr_eos[64]; static char enter_standout_mode[64], exit_standout_mode[64]; static char enter_underline_mode[64], exit_underline_mode[64]; static char key_down[64], key_up[64], key_right[64], key_left[64]; static char keypad_local[64], keypad_xmit[64]; int magic_cookie_glitch; /* magic cookie size */ int ceol_standout_glitch; /* hp brain damage! */ int auto_right_margin; /* automatic right margin */ int eat_newline_glitch; /* newline ignored at right margin */ #define putp(str) tputs(str, 1, outc) #define HAS_CAP(str) (*str) #endif /* USE_TERMCAP */ static char key_mouse_d1[64] = "\33[M "; static char key_mouse_d2[64] = "\33[M!"; static char key_mouse_d3[64] = "\33[M\""; static char key_mouse_u1[64] = "\33[M#"; /* * Compute the greatest multiple of p not greater than x. * p must be a power of 2. * x must be nonnegative, for portability to non-2's-complement hosts. */ #define DISCARD_REMAINDER(x,p) ((x) & ~((p)-1)) #ifdef HAVE_TERMIOS_H static int outc(int c) #else static int outc(char c) #endif { putchar(c); return 0; /* XXX What is it supposed to return? */ } #define putpc(str, cnt) tputs(str, cnt, outc) int Lines, Columns; /* screen size */ int Name_Length; /* length of displayed name */ int cookie_size; /* size of magic cookie */ static int two_cookies; /* space needed to enter&exit standout mode */ int STANDOUT; /* terminal got standout mode */ static int curxy_c = 0, curxy_l = -1, savxy_c = 0, savxy_l = -1; static int just_sent_cr = 0; /* just sent \r to avoid 'xn' term * attribute */ static int *nonsp; /* number of non-space characters on line */ #ifdef TERM_DEBUG static char *term_debug = NULL; extern char *getenv(); #define curxy_nonsp (curxy_l < 0 ? -1 : nonsp[curxy_l]) #endif #ifdef FAKE_INTERRUPT extern jmp_buf fake_keyb_sig; extern int arm_fake_keyb_sig; extern char fake_keyb_siglist[]; #endif /* FAKE_INTERRUPT */ #if defined(HAVE_TERMIO_H) || defined(HAVE_TERMIOS_H) /* This used to be 50, but there are some rather complex bugs in the SYSV */ /* TERMIO driver... */ #define KEY_BURST 2 /* read bursts of 1 char (or timeout after * 100 ms) */ #undef CBREAK #ifdef HAVE_TERMIOS_H static struct termios norm_tty, raw_tty; #else static struct termio norm_tty, raw_tty; #endif #define IntrC ((key_type) norm_tty.c_cc[VINTR]) #define EraseC ((key_type) norm_tty.c_cc[VERASE]) #define KillC ((key_type) norm_tty.c_cc[VKILL]) #ifdef HAVE_TERMIOS_H #define SuspC ((key_type) norm_tty.c_cc[VSUSP]) #else #define SuspC ((key_type) CONTROL_('Z')) /* norm_tty.c_cc[SWTCH] */ #endif #else /* V7/BSD TTY DRIVER */ static struct sgttyb norm_tty, raw_tty; static struct tchars norm_chars; #define IntrC ((key_type) norm_chars.t_intrc) #define EraseC ((key_type) norm_tty.sg_erase) #define KillC ((key_type) norm_tty.sg_kill) #ifdef TIOCGLTC static struct ltchars spec_chars; #define SuspC ((key_type) spec_chars.t_suspc) #else #define SuspC ((key_type) CONTROL_('Z')) #endif #endif #ifdef USE_TERMCAP static int opt_cap(char *cap, char *buf) { #ifndef __APPLE__ char *tgetstr (const char *, char **); #else NCURSES_EXPORT(char *) tgetstr (NCURSES_CONST char *, char **); #endif *buf = NUL; return tgetstr(cap, &buf) != NULL; } static void get_cap(char *cap, char *buf) { if (!opt_cap(cap, buf)) nn_exitmsg(1, "TERMCAP entry for %s has no '%s' capability", term_name, cap); } #endif /* USE_TERMCAP */ /* * timeout in n/10 seconds via SIGALRM */ static void micro_alarm(int n) { #ifdef HAVE_UALARM ualarm(n <= 1 ? 100000 : n * 100000, 0); /* 4.3 BSD ualarm() */ #else #ifdef MICRO_ALARM if (n <= 0) n = 1; MICRO_ALARM(n); /* System specific timeout */ #else alarm(n <= 10 ? 1 : (n + 9) / 10); /* Standard alarm() call */ #endif /* MICRO_ALARM */ #endif /* HAVE_UALARM */ } static int multi_keys = 0; static struct multi_key { key_type *cur_key; key_type *keys; key_type code; } multi_key_list[MULTI_KEYS]; void enter_multi_key(int code, key_type * keys) { register int i; if (strlen((char *) keys) == 1) /* will ignore arrow keys overlaying these keys */ if (*keys == NL || *keys == CR || *keys == erase_key || *keys == kill_key || *keys == IntrC) return; /* lookup code to see if it is already defined... */ for (i = 0; i < multi_keys; i++) if (multi_key_list[i].code == (key_type) code) goto replace_key; i = multi_keys++; /* now i points to matching or empty slot */ if (i >= MULTI_KEYS) { /* should never happen */ log_entry('E', "too many multi keys"); return; } replace_key: multi_key_list[i].keys = keys; multi_key_list[i].code = code; } void dump_multi_keys(void) { register int i; register key_type *cp; clrdisp(); pg_init(0, 1); for (i = 0; i < multi_keys; i++) { if (pg_next() < 0) break; tprintf("%d\t%s\t", i, key_name(multi_key_list[i].code)); for (cp = multi_key_list[i].keys; *cp; cp++) tprintf(" %s", key_name(*cp)); } pg_end(); } #ifdef RESIZING static sig_type catch_winch(int n) { struct winsize winsize; int i; signal(SIGWINCH, catch_winch); if (ioctl(0, TIOCGWINSZ, &winsize) >= 0 && (winsize.ws_row != Lines || winsize.ws_col != Columns)) { nonsp = resizeobj(nonsp, int, winsize.ws_row); if ((int) winsize.ws_row > Lines) for (i = Lines; i < (int) winsize.ws_row; i++) nonsp[i] = winsize.ws_col; if ((int) winsize.ws_col < Columns) for (i = 0; i < (int) winsize.ws_row; i++) if (nonsp[i] > (int) winsize.ws_col) nonsp[i] = winsize.ws_col; Lines = winsize.ws_row; Columns = winsize.ws_col; Name_Length = Columns / 5; if (Name_Length < NAME_LENGTH) Name_Length = NAME_LENGTH; curxy_l = -1; s_redraw = 1; s_resized = 1; } #ifdef FAKE_INTERRUPT if (fake_keyb_siglist[n] && arm_fake_keyb_sig) longjmp(fake_keyb_sig, 1); #endif /* FAKE_INTERRUPT */ } #endif /* RESIZING */ #ifdef SV_INTERRUPT #ifdef NO_SIGINTERRUPT static int siginterrupt(signo, on) { struct sigvec sv; sv.sv_handler = signal(signo, SIG_DFL); sv.sv_mask = 0; sv.sv_flags = on ? SV_INTERRUPT : 0; sigvec(signo, &sv, 0); } #endif /* NO_SIGINTERRUPT */ #endif /* SV_INTERRUPT */ #ifdef FAKE_INTERRUPT #define SV_INTERRUPT static int siginterrupt(signo, on) { fake_keyb_siglist[signo] = on; } #endif /* FAKE_INTERRUPT */ static unsigned sp_table[] = { B9600, 960, #ifdef B19200 B19200, 1920, #else #ifdef EXTA EXTA, 1920, #endif /* EXTA */ #endif /* B19200 */ #ifdef B38400 B38400, 3840, #else #ifdef EXTB EXTB, 3840, #endif /* EXTB */ #endif /* B38400 */ B1200, 120, B2400, 240, B4800, 480, B300, 30, 0, 0 }; static void set_term_speed(register unsigned long sp) { register unsigned *tp; for (tp = sp_table; *tp; tp += 2) if (*tp == sp) { terminal_speed = tp[1]; return; } terminal_speed = 30; } static void raw_not_ok_error(void) { if (batch_mode) return; nn_exitmsg(1, "Not prepared for terminal i/o"); /* NOTREACHED */ } #define RAW_CHECK if (terminal_speed == 0) {raw_not_ok_error(); return 0;} #define RAW_CHECK_V if (terminal_speed == 0) {raw_not_ok_error(); return;} #define BATCH_CHECK if (terminal_speed == 0) return 0 #define BATCH_CHECK_V if (terminal_speed == 0) return void init_term(int full) { #ifdef USE_TERMCAP char tbuf[1024]; #endif int i; #ifdef TERM_DEBUG term_debug = getenv("TERM_DEBUG"); if (term_debug) fprintf(stderr, "init_term(%d)\n", full); #endif if (batch_mode) { term_name = "batch"; close(0); open("/dev/null", 0); STANDOUT = 0; cookie_size = 1; return; } if ((term_name = getenv("TERM")) == NULL) { if (full) nn_exitmsg(1, "No TERM variable in environment"); else term_name = "unknown"; } if (!full) return; #ifdef HAVE_TERMIO_H ioctl(0, TCGETA, &norm_tty); #else #ifdef HAVE_TERMIOS_H tcgetattr(0, &norm_tty); #else ioctl(0, TIOCGETP, &norm_tty); #endif /* HAVE_TERMIOS_H */ #endif /* HAVE_TERMIO_H */ #ifdef USE_TERMINFO setupterm((char *) NULL, 1, (int *) NULL); Columns = columns; Lines = lines; if (use_visible_bell && HAS_CAP(flash_screen)) strcpy(bell_str, flash_screen); else if (HAS_CAP(bell)) strcpy(bell_str, bell); if (!HAS_CAP(cursor_home)) cursor_home = copy_str(tgoto(cursor_address, 0, 0)); #else if (tgetent(tbuf, term_name) <= 0) nn_exitmsg(1, "Unknown terminal type: %s", term_name); if (opt_cap("bc", XBC)) BC = XBC; if (opt_cap("up", XUP)) UP = XUP; opt_cap("pc", cursor_address); /* temp. usage */ PC = cursor_address[0]; get_cap("cm", cursor_address); if (!opt_cap("ho", cursor_home)) strcpy(cursor_home, tgoto(cursor_address, 0, 0)); get_cap("cl", clear_screen); opt_cap("ce", clr_eol); opt_cap("cd", clr_eos); Lines = tgetnum("li"); Columns = tgetnum("co"); opt_cap("so", enter_standout_mode); opt_cap("se", exit_standout_mode); opt_cap("us", enter_underline_mode); opt_cap("ue", exit_underline_mode); opt_cap("kd", key_down); opt_cap("ku", key_up); opt_cap("kr", key_right); opt_cap("kl", key_left); magic_cookie_glitch = tgetnum("sg"); ceol_standout_glitch = tgetflag("xs"); auto_right_margin = tgetflag("am"); eat_newline_glitch = tgetflag("xn"); opt_cap("ti", enter_ca_mode); opt_cap("te", exit_ca_mode); opt_cap("ks", keypad_xmit); /* used to turn "application cursor */ opt_cap("ke", keypad_local);/* key" mode on and off (sometimes) */ if (!use_visible_bell || !opt_cap("vb", bell_str)) if (!opt_cap("bl", bell_str)) strcpy(bell_str, "\007"); #endif /* USE_TERMINFO */ #ifdef RESIZING { struct winsize winsize; if (ioctl(0, TIOCGWINSZ, &winsize) >= 0 && winsize.ws_row != 0 && winsize.ws_col != 0) { Lines = winsize.ws_row; Columns = winsize.ws_col; signal(SIGWINCH, catch_winch); #ifdef SV_INTERRUPT siginterrupt(SIGWINCH, 1); /* make read from tty interruptable */ #endif /* SV_INTERRUPT */ } } #endif /* RESIZING */ /* Stop NN from blowing up if on a *really* dumb terminal, like "dumb" */ if (Lines < 1) Lines = 24; if (Columns < 1) Columns = 80; nonsp = newobj(int, Lines); for (i = 0; i < Lines; i++) nonsp[i] = Columns; STANDOUT = HAS_CAP(enter_standout_mode); cookie_size = magic_cookie_glitch; if (STANDOUT) { if (cookie_size < 0) cookie_size = 0; two_cookies = 2 * cookie_size; } else cookie_size = two_cookies = 0; raw_tty = norm_tty; #ifdef HAVE_TERMIO_H raw_tty.c_iflag &= ~(BRKINT | INLCR | ICRNL | IGNCR | ISTRIP); raw_tty.c_iflag |= IGNBRK | IGNPAR; raw_tty.c_oflag &= ~OPOST; raw_tty.c_lflag &= ~(ISIG | ICANON | XCASE | ECHO | NOFLSH); /* read a maximum of 10 characters in one burst; timeout in 1-200 ms */ raw_tty.c_cc[VMIN] = KEY_BURST; raw_tty.c_cc[VTIME] = ((int) (raw_tty.c_cflag & CBAUD) > B1200) ? 1 : 2; set_term_speed((unsigned long) (raw_tty.c_cflag & CBAUD)); #else #ifdef HAVE_TERMIOS_H cfmakeraw(&raw_tty); /* read a maximum of 10 characters in one burst; timeout in 1-200 ms */ raw_tty.c_cc[VMIN] = KEY_BURST; #if 0 raw_tty.c_cc[VTIME] = (cfgetispeed(&raw_tty) > B1200) ? 1 : 2; #else raw_tty.c_cc[VTIME] = 1; #endif /* 0 */ set_term_speed((unsigned long) cfgetospeed(&raw_tty)); #ifdef SV_INTERRUPT siginterrupt(SIGTSTP, 1); siginterrupt(SIGALRM, 1); #endif /* SV_INTERRUPT */ #else ioctl(0, TIOCGETC, &norm_chars); #ifdef TIOCGLTC ioctl(0, TIOCGLTC, &spec_chars); #endif /* TIOCGLTC */ nnspeed = norm_tty.sg_ospeed; set_term_speed((unsigned long) nnspeed); raw_tty.sg_flags &= ~(ECHO | CRMOD); #ifdef CBREAK #ifdef SV_INTERRUPT /* make read from tty interruptable */ siginterrupt(SIGTSTP, 1); /* this is necessary to redraw screen */ #endif /* SV_INTERRUPT */ raw_tty.sg_flags |= CBREAK; #else raw_tty.sg_flags |= RAW; #endif /* CBREAK */ #ifdef SV_INTERRUPT siginterrupt(SIGALRM, 1); /* make read from tty interruptable */ #endif /* SV_INTERRUPT */ #endif /* HAVE_TERMIOS_H */ #endif /* HAVE_TERMIO_H */ Name_Length = Columns / 5; if (Name_Length < NAME_LENGTH) Name_Length = NAME_LENGTH; erase_key = EraseC; kill_key = KillC; if (HAS_CAP(key_down)) enter_multi_key(K_down_arrow, (key_type *) key_down); if (HAS_CAP(key_up)) enter_multi_key(K_up_arrow, (key_type *) key_up); if (HAS_CAP(key_right)) enter_multi_key(K_right_arrow, (key_type *) key_right); if (HAS_CAP(key_left)) enter_multi_key(K_left_arrow, (key_type *) key_left); enter_multi_key(K_m_d1, (key_type *) key_mouse_d1); enter_multi_key(K_m_d2, (key_type *) key_mouse_d2); enter_multi_key(K_m_d3, (key_type *) key_mouse_d3); enter_multi_key(K_m_u1, (key_type *) key_mouse_u1); appl_keypad_mode = (HAS_CAP(keypad_xmit) && HAS_CAP(keypad_local)); if (!HAS_CAP(key_up)) appl_keypad_mode = 0; /* no cursor keys */ if (appl_keypad_mode) { /* Use of ks/ke isn't consistent, so we must guess what to do. */ /* If termcap expects keys to send ESC [, don't switch */ appl_keypad_mode = (key_up[0] != '\033' || key_up[1] != '['); } if ((mouse_usage == 2) || ((mouse_usage == 1) && !strncmp("xterm", term_name, 5))) { mouse_state = 1; flow_control = 0; } else { mouse_state = 0; } visual_on(); } void home(void) { BATCH_CHECK_V; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "home\n"); #endif putp(cursor_home); curxy_c = curxy_l = 0; } void save_xy(void) { savxy_c = curxy_c; savxy_l = curxy_l; } void restore_xy(void) { if (savxy_l < 0) return; gotoxy(savxy_c, savxy_l); fl; } void gotoxy(int c, int l) { BATCH_CHECK_V; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "gotoxy %d %d -> %d %d\n", curxy_c, curxy_l, c, l); #endif if (Columns <= (unsigned) c || Lines <= (unsigned) l) return; if (!(c | l)) putp(cursor_home); else putp(tgoto(cursor_address, c, l)); curxy_c = c; curxy_l = l; } void clrdisp(void) { int i; BATCH_CHECK_V; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "clrdisp\n"); #endif putpc(clear_screen, Lines); curxy_c = curxy_l = 0; savxy_l = -1; for (i = 0; i < Lines; i++) nonsp[i] = 0; msg_ptr = msg_stack; } void clrline_noflush(); void clrline(void) { BATCH_CHECK_V; /* If we moved the cursor left to avoid weird effects, don't clear. */ if (just_sent_cr) return; clrline_noflush(); fl; } void clrline_noflush(void) { int oldxy_c, oldxy_l, spcnt; BATCH_CHECK_V; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "clrline %d %d [%d]", curxy_c, curxy_l, curxy_nonsp); #endif if (curxy_l < 0) return; /* remainder of line already blank? */ if (curxy_c >= nonsp[curxy_l]) { #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " ignored\n"); #endif return; } if (HAS_CAP(clr_eol)) { #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "\n"); #endif putp(clr_eol); } else { oldxy_c = curxy_c; oldxy_l = curxy_l; spcnt = nonsp[curxy_l] - curxy_c; /* guard against scroll */ if (auto_right_margin && curxy_l == Lines - 1 && curxy_c + spcnt == Columns) spcnt--; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " %d\n", spcnt); #endif /* TERM_DEBUG */ #ifdef TERM_DEBUG if (term_debug && *term_debug) { while (spcnt--) tputc(*term_debug); spcnt = 0; } #endif /* clear out line */ while (spcnt--) tputc(SP); if (curxy_c != oldxy_c || curxy_l != oldxy_l) gotoxy(oldxy_c, oldxy_l); } nonsp[curxy_l] = curxy_c; } void clrpage(void) { int oldxy_c, oldxy_l, i; BATCH_CHECK_V; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "clrpage %d %d [%d]\n", curxy_c, curxy_l, curxy_nonsp); #endif if (curxy_l < 0) return; oldxy_c = curxy_c; oldxy_l = curxy_l; /* code below only handles curxy_c == 0 */ if (curxy_c != 0) { clrline(); if (curxy_l < Lines - 1) gotoxy(0, curxy_l + 1); } /* clear to end of screen */ if (curxy_c == 0) { if (HAS_CAP(clr_eos)) { putpc(clr_eos, Lines - curxy_l); for (i = curxy_l; i < Lines; i++) nonsp[i] = 0; } else if (curxy_l == 0) { putpc(clear_screen, Lines); for (i = 0; i < Lines; i++) nonsp[i] = 0; } else { for (i = curxy_l; i < Lines; i++) { if (nonsp[i] != 0) { gotoxy(0, i); clrline_noflush(); } } } } if (curxy_c != oldxy_c || curxy_l != oldxy_l) gotoxy(oldxy_c, oldxy_l); fl; msg_ptr = msg_stack; } static char buf[512]; static void tvprintf(char *fmt, va_list ap) { char *str; vsprintf(buf, fmt, ap); for (str = buf; *str; str++) tputc(*str); } void tprintf(char *fmt,...) { va_list ap; va_start(ap, fmt); tvprintf(fmt, ap); va_end(ap); } void tputc(int c) { int i; #ifdef TERM_DEBUG if (term_debug) { fprintf(stderr, "tputc %d %d [%d] ", curxy_c, curxy_l, curxy_nonsp); if (c < ' ' || c > '~') switch (c) { case 0: fprintf(stderr, "'NUL'"); break; case 1: fprintf(stderr, "'SOH'"); break; case 2: fprintf(stderr, "'STX'"); break; case 3: fprintf(stderr, "'ETX'"); break; case 4: fprintf(stderr, "'EOT'"); break; case 5: fprintf(stderr, "'ENQ'"); break; case 6: fprintf(stderr, "'ACK'"); break; case 7: fprintf(stderr, "'BEL'"); break; case 8: fprintf(stderr, "'BS'"); break; case 9: fprintf(stderr, "'HT'"); break; case 10: fprintf(stderr, "'NL'"); break; case 11: fprintf(stderr, "'VT'"); break; case 12: fprintf(stderr, "'NP'"); break; case 13: fprintf(stderr, "'CR'"); break; case 14: fprintf(stderr, "'SP'"); break; case 15: fprintf(stderr, "'SI'"); break; case 16: fprintf(stderr, "'DLE'"); break; case 17: fprintf(stderr, "'DC1'"); break; case 18: fprintf(stderr, "'DC2'"); break; case 19: fprintf(stderr, "'DC3'"); break; case 20: fprintf(stderr, "'DC4'"); break; case 21: fprintf(stderr, "'NAK'"); break; case 22: fprintf(stderr, "'SYN'"); break; case 23: fprintf(stderr, "'ETB'"); break; case 24: fprintf(stderr, "'CAN'"); break; case 25: fprintf(stderr, "'EM'"); break; case 26: fprintf(stderr, "'SUB'"); break; case 27: fprintf(stderr, "'ESC'"); break; case 28: fprintf(stderr, "'FS'"); break; case 29: fprintf(stderr, "'GS'"); break; case 30: fprintf(stderr, "'RS'"); break; case 31: fprintf(stderr, "'US'"); break; case 32: fprintf(stderr, "'SP'"); break; default: fprintf(stderr, "out of range: ???"); break; } else fprintf(stderr, "'%c'", c); } #endif just_sent_cr = 0; putchar(c); switch (c) { case '\n': curxy_c = 0; if (curxy_l >= 0) curxy_l++; break; case '\r': curxy_c = 0; break; case '\t': curxy_c = DISCARD_REMAINDER(curxy_c + 8, 8); break; case '\b': curxy_c--; break; case ' ': curxy_c++; if (curxy_l >= 0 && nonsp[curxy_l] == curxy_c) nonsp[curxy_l]--; break; default: curxy_c++; if (curxy_l >= 0 && nonsp[curxy_l] < curxy_c) nonsp[curxy_l] = curxy_c; break; } #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " -> %d %d [%d]", curxy_c, curxy_l, curxy_nonsp); #endif /* account for right margin */ if (curxy_c == Columns) { #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " margin"); #endif if (auto_right_margin) { if (eat_newline_glitch) { putchar(CR); just_sent_cr = 1; curxy_c = 0; } else { curxy_c = 0; if (curxy_l >= 0) curxy_l++; } } else curxy_c--; } /* account for vertical scroll */ if (curxy_l == Lines) { #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " scroll"); #endif for (i = 1; i < Lines; i++) nonsp[i - 1] = nonsp[i]; nonsp[--curxy_l] = 0; } #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "\n"); #endif } static char so_buf[512], *so_p; static int so_c, so_l, so_b, so_active = 0; int so_gotoxy(int c, int l, int blank) { if (!STANDOUT && c >= 0) { if (c >= 0 && l >= 0) gotoxy(c, l); return 0; } so_active++; so_c = c; so_l = l; so_b = blank; so_p = so_buf; *so_p = NUL; return 1; /* not really true if not standout & c < 0 */ } static void so_vprintf(char *fmt, va_list ap) { if (!so_active) { if (ceol_standout_glitch) highlight(0); /* xxx why? */ tvprintf(fmt, ap); return; } vsprintf(so_p, fmt, ap); while (*so_p) so_p++; } void so_printf(char *fmt,...) { va_list ap; va_start(ap, fmt); so_vprintf(fmt, ap); va_end(ap); } void so_end(void) { int len; if (!so_active) return; if (so_l >= 0) { len = so_p - so_buf + two_cookies; if (so_c < 0) so_c = Columns - len - 2; if (so_c < 0) so_c = 0; if (len + so_c >= Columns) { len = Columns - so_c - two_cookies; so_buf[len] = NUL; } if (cookie_size) { gotoxy(so_c + len - cookie_size, so_l); nnstandout(0); } gotoxy(so_c, so_l); } if ((so_b & 1) && (!STANDOUT || !cookie_size)) tputc(SP); if (STANDOUT) { if (ceol_standout_glitch) clrline(); nnstandout(1); } tprintf("%s", so_buf); if (STANDOUT) nnstandout(0); if ((so_b & 2) && (!STANDOUT || !cookie_size)) tputc(SP); so_active = 0; } void so_printxy(int c, int l, char *fmt,...) { va_list ap; va_start(ap, fmt); so_gotoxy(c, l, 0); so_vprintf(fmt, ap); so_end(); va_end(ap); } int underline(int on) { if (cookie_size) return 0; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "underline %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on); #endif if (!HAS_CAP(enter_underline_mode)) return 0; putp(on ? enter_underline_mode : exit_underline_mode); return 1; } int highlight(int on) { if (cookie_size) return 0; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "highlight %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on); #endif if (!HAS_CAP(enter_standout_mode)) return 0; putp(on ? enter_standout_mode : exit_standout_mode); return 1; } int shadeline(int on) { if (cookie_size) return 0; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "shadeline %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on); #endif if (shade_on_attr && shade_off_attr) { putp(on ? shade_on_attr : shade_off_attr); return 1; } else return underline(1); } static int nnstandout(int on) { #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "standout %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on); #endif if (!HAS_CAP(enter_standout_mode)) return 0; putp(on ? enter_standout_mode : exit_standout_mode); curxy_c += cookie_size; if (curxy_l >= 0 && curxy_c > nonsp[curxy_l]) nonsp[curxy_l] = curxy_c; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, " -> %d %d [%d]\n", curxy_c, curxy_l, curxy_nonsp); #endif return 1; } static int is_visual = 0; static int is_raw = 0; #ifdef HAVE_TERMIO_H #define RAW_MODE_ON ioctl(0, TCSETAW, &raw_tty) #define RAW_MODE_OFF ioctl(0, TCSETAW, &norm_tty) #else #ifdef HAVE_TERMIOS_H #define RAW_MODE_ON tcsetattr(0, TCSADRAIN, &raw_tty) #define RAW_MODE_OFF tcsetattr(0, TCSADRAIN, &norm_tty) #else #define RAW_MODE_ON ioctl(0, TIOCSETP, &raw_tty) #define RAW_MODE_OFF ioctl(0, TIOCSETP, &norm_tty) #endif /* HAVE_TERMIOS_H */ #endif /* HAVE_TERMIO_H */ void xterm_mouse_on(void) { putp("\33[?1000h"); } void xterm_mouse_off(void) { putp("\33[?1000l"); } void visual_on(void) { BATCH_CHECK_V; if (terminal_speed == 0) return; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "visual_on\n"); #endif if (!is_visual) { if (HAS_CAP(enter_ca_mode)) putp(enter_ca_mode); if (appl_keypad_mode) putp(keypad_xmit); if (mouse_state) xterm_mouse_on(); fl; } is_visual = 1; } int visual_off(void) { int was_raw = is_raw; if (terminal_speed == 0) return 0; #ifdef TERM_DEBUG if (term_debug) fprintf(stderr, "visual_off\n"); #endif if (is_visual) { if (appl_keypad_mode) putp(keypad_local); if (HAS_CAP(exit_ca_mode)) putp(exit_ca_mode); if (mouse_state) xterm_mouse_off(); fl; } is_visual = 0; is_raw = 1; unset_raw(); return was_raw; } #ifdef CBREAK void nn_raw(void) { RAW_CHECK_V; if (is_raw == 1) return; is_raw = 1; RAW_MODE_ON; } int no_raw(void) { return 0; } int unset_raw(void) { if (is_raw == 0) return 0; RAW_CHECK; RAW_MODE_OFF; is_raw = 0; return 1; } #else /* not CBREAK */ static int must_set_raw = 1; #undef raw void nn_raw(void) { RAW_CHECK_V; if (!flow_control) { if (!must_set_raw) return; must_set_raw = 0; } if (is_raw) return; RAW_MODE_ON; is_raw++; } int no_raw(void) { if (!flow_control) return 0; if (!is_raw) return 0; RAW_CHECK; RAW_MODE_OFF; is_raw = 0; return 1; } int unset_raw(void) { int was_raw = is_raw; if (is_raw) { RAW_CHECK; RAW_MODE_OFF; is_raw = 0; } if (!flow_control) must_set_raw = 1; return was_raw; } #endif /* CBREAK */ #ifndef KEY_BURST #define KEY_BURST 32 #endif /* KEY_BURST */ #define RD_PUSHBACK 10 static char rd_buffer[KEY_BURST + RD_PUSHBACK]; /* Holds stuff from read */ static char *rd_ptr; static int rd_count = 0, rd_alarm = 0; #ifdef FAKE_INTERRUPT static jmp_buf fake_alarm_sig; #endif /* FAKE_INTERRUPT */ static sig_type rd_timeout(int n) { rd_alarm = 1; #ifdef FAKE_INTERRUPT longjmp(fake_alarm_sig, 1); #endif /* FAKE_INTERRUPT */ } #define RD_TIMEOUT 0x1000 #define RD_INTERRUPT 0x1001 static int read_char_kbd(int tmo) /* tmo timeout if no input arrives */ { if (rd_count <= 0) { if (tmo) { #ifdef FAKE_INTERRUPT if (setjmp(fake_alarm_sig)) goto tmout; #endif /* FAKE_INTERRUPT */ rd_alarm = 0; signal(SIGALRM, rd_timeout); micro_alarm(multi_key_guard_time); } rd_ptr = rd_buffer + RD_PUSHBACK; rd_count = read(0, rd_ptr, KEY_BURST); if (tmo) { if (rd_alarm) goto tmout; alarm(0); } if (rd_count < 0) { if (errno != EINTR) s_hangup++; return RD_INTERRUPT; } } --rd_count; return *rd_ptr++; tmout: rd_count = 0; return RD_TIMEOUT; } static void unread_char(int c) { if (rd_ptr == rd_buffer) return; rd_count++; *--rd_ptr = c; } void flush_input(void) { #ifndef HAVE_TERMIO_H #if !defined(HAVE_TERMIOS_H) && defined(FREAD) int arg; #endif #endif BATCH_CHECK_V; #ifdef HAVE_TERMIO_H ioctl(0, TCFLSH, 0); #else #ifdef HAVE_TERMIOS_H ioctl(0, TCIFLUSH); #else #ifdef FREAD arg = FREAD; ioctl(0, TIOCFLUSH, &arg); #else ioctl(0, TIOCFLUSH, 0); #endif /* FREAD */ #endif /* HAVE_TERMIOS_H */ #endif /* HAVE_TERMIO_H */ rd_count = 0; } int enable_stop = 1; static int do_macro_processing = 1; int mouse_y = -1; static int mouse_last = 0; int get_c(void) { key_type c, first_key; int key_cnt, mc, n; register struct multi_key *mk, *multi_match; register int i; multi_match = NULL; first_key = 0; mouse_y = -1; next_key: if (s_hangup) return K_interrupt; if (do_macro_processing) switch (m_getc(&mc)) { case 0: break; case 1: return mc; case 2: return K_interrupt; } #ifdef RESIZING if (s_resized) goto redraw; #endif /* RESIZING */ if (batch_mode) nn_exitmsg(1, "Attempt to read keyboard input in batch mode"); for (i = multi_keys, mk = multi_key_list; --i >= 0; mk++) mk->cur_key = mk->keys; key_cnt = 0; #ifdef FAKE_INTERRUPT if (setjmp(fake_keyb_sig)) goto intr; arm_fake_keyb_sig = 1; #endif /* FAKE_INTERRUPT */ multi: switch (n = read_char_kbd(key_cnt)) { case RD_INTERRUPT: #ifdef CBREAK if (s_redraw) goto redraw; #endif /* CBREAK */ #ifdef RESIZING if (s_resized) goto redraw; #endif /* RESIZING */ goto intr; case RD_TIMEOUT: while (--key_cnt > 0) unread_char(multi_match->keys[key_cnt]); c = first_key; goto got_char; default: c = (key_type) n; if (data_bits < 8) c &= 0x7f; if (ignore_xon_xoff) if (c == CONTROL_('Q') || c == CONTROL_('S')) goto multi; break; } multi_match = NULL; for (i = multi_keys, mk = multi_key_list; --i >= 0; mk++) { if (mk->cur_key == NUL) continue; if (*(mk->cur_key)++ != c) { mk->cur_key = NUL; continue; } if (*(mk->cur_key) == NUL) { c = mk->code; /* * xterm mouse specific code, translate cursor position into * static variables, synthesize key up events */ if ((c >= K_m_d1) && (c <= K_m_u1)) { mouse_y = read_char_kbd(key_cnt) - '!'; if (c == K_m_u1) { if (mouse_last == K_m_d2) { c = K_m_u2; } else if (mouse_last == K_m_d3) { c = K_m_u3; } } mouse_last = c; } goto got_char; } multi_match = mk; } if (multi_match) { if (key_cnt == 0) first_key = c; key_cnt++; goto multi; } if (key_cnt) { if (key_cnt == 1 && first_key == 033) { unread_char(c); c = 033; goto got_char; } ding(); flush_input(); goto next_key; } got_char: #ifdef FAKE_INTERRUPT arm_fake_keyb_sig = 0; #endif /* FAKE_INTERRUPT */ c = global_key_map[c]; #ifndef CBREAK if (c == IntrC) return K_interrupt; /* don't flush */ if (c == SuspC) { if (enable_stop && suspend_nn()) goto redraw; goto next_key; } #endif /* CBREAK */ return (int) c; intr: #ifdef FAKE_INTERRUPT arm_fake_keyb_sig = 0; #endif /* FAKE_INTERRUPT */ rd_count = 0; return K_interrupt; redraw: #ifdef RESIZING s_resized = 0; #endif /* RESIZING */ s_redraw = 0; return GETC_COMMAND | K_REDRAW; } /* * read string with completion, pre-filling, and break on first char * * dflt is a string that will be use as default value if the * space bar is hit as the first character. * * prefill pre-fill the buffer with .... and print it * * break_chars return immediately if one of these characters * is entered as the first character. * * completion is a function that will fill the buffer with a value * see the group_completion and file_completion routines * for examples. */ char * get_s(char *dflt, char *prefill, char *break_chars, fct_type completion) { static key_type lbuf[GET_S_BUFFER]; register char *cp; register int i, c, lastc; char *ret_val = (char *) lbuf; int comp_used, comp_len; int ostop, max, did_help; int hit_count; switch (m_gets((char *) lbuf)) { case 0: break; case 1: return (char *) lbuf; case 2: return NULL; } ostop = enable_stop; enable_stop = 0; do_macro_processing = 0; hit_count = 0; max = Columns - prompt_length; if (max >= FILENAME) max = FILENAME - 1; i = comp_len = comp_used = did_help = 0; if (prefill && prefill[0]) { while ((c = *prefill++)) { if (i == max) break; tputc(c); lbuf[i] = c; i++; } fl; } if (dflt && *dflt == NUL) dflt = NULL; if (break_chars && *break_chars == NUL) break_chars = NULL; c = NUL; for (;;) { lastc = c; c = get_c(); if (c & GETC_COMMAND) continue; kill_prefill_hack: hit_count++; if (i == 0) { if (c == comp1_key && dflt) { while ((c = *dflt++) != NUL && i < max) { tputc(c); lbuf[i] = c; i++; } fl; dflt = NULL; continue; } if ((cp = break_chars)) { /* Stupid ^@$# GCC!!! */ while (*cp) if (*cp++ == c) { lbuf[0] = c; lbuf[1] = NUL; goto out; } } } if (completion != NULL_FCT) { if (comp_used && c == erase_key) { if (comp_len) { i -= comp_len; while (--comp_len >= 0) tputc(BS); clrline(); } if (!CALL(completion) (lbuf, -(i + 1)) && did_help) clrmsg(i); did_help = 0; comp_len = comp_used = 0; if (lastc == help_key) goto no_completion; continue; } if (c == comp1_key || c == comp2_key || c == help_key) { if (!comp_used || c == comp2_key || (c == help_key && lastc != c)) { lbuf[i] = NUL; if ((comp_used = CALL(completion) (lbuf, i)) == 0) { ding(); continue; } if (comp_used < 0) { comp_used = 0; goto no_completion; } comp_len = 0; } if (c == help_key) { if (CALL(completion) ((char *) NULL, 1)) { gotoxy(prompt_length + i, prompt_line); fl; did_help = 1; } continue; } if (comp_len) { i -= comp_len; while (--comp_len >= 0) tputc(BS); clrline(); comp_len = 0; } switch (CALL(completion) ((char *) NULL, 0)) { case 0: /* no possible completion */ comp_used = 0; ding(); continue; case 2: /* whole new alternative */ while (--i >= 0) tputc(BS); clrline(); /* FALLTHRU */ case 1: /* completion */ comp_len = i; while ((c = lbuf[i])) { if (i == max) break; tputc(c); i++; } fl; comp_len = i - comp_len; continue; } } if (comp_used) { if (!CALL(completion) (lbuf, -(i + 1)) && did_help) clrmsg(i); did_help = 0; comp_len = comp_used = 0; } } no_completion: if (c == CR || c == NL) { lbuf[i] = NUL; break; } if (c == erase_key) { if (i <= 0) continue; i--; tputc(BS); tputc(' '); tputc(BS); fl; continue; } if (c == delword_key) { if (i <= 0) continue; lbuf[i - 1] = 'X'; while (i > 0 && isalnum(lbuf[i - 1])) { tputc(BS); i--; } clrline(); continue; } if (c == kill_key) { while (i > 0) { tputc(BS); i--; } clrline(); if (hit_count == 1 && dflt) { c = comp1_key; goto kill_prefill_hack; } continue; } if (c == K_interrupt) { ret_val = NULL; break; } if (data_bits == 8) { if (!iso8859(c)) continue; } else if (!isascii(c) || !isprint(c)) continue; if (i == max) continue; if (i > 0 && lbuf[i - 1] == '/' && (c == '/' || c == '+')) { if (c != '/' || !guard_double_slash || (i > 1 && lbuf[i - 2] == '/')) { if (completion == file_completion) { while (i > 0) { tputc(BS); i--; } clrline(); } } } tputc(c); fl; lbuf[i] = c; i++; } out: enable_stop = ostop; do_macro_processing = 1; return ret_val; } int list_offset = 0; int list_completion(char *str) { static int cols, line; if (str == NULL) { cols = Columns; line = prompt_line + 1; if (line == Lines - 1) cols--; gotoxy(0, line); clrpage(); return 1; } str += list_offset; for (;;) { cols -= strlen(str); if (cols >= 0) { tprintf("%s%s", str, cols > 0 ? " " : ""); cols--; return 1; } if (line >= Lines - 1) return 0; line++; cols = Columns; gotoxy(0, line); if (line == Lines - 1) cols--; } } int yes(int must_answer) { int c, help = 1, in_macro = 0; switch (m_yes()) { case 0: break; case 1: return 0; case 2: return 1; case 3: do_macro_processing = 0; in_macro++; break; } fl; for (;;) { if (!is_raw) { nn_raw(); c = get_c(); unset_raw(); } else c = get_c(); if (c == 'y' || c == 'Y') { c = 1; break; } if (must_answer == 0 && (c == SP || c == CR || c == NL)) { c = 1; break; } if (c == 'n' || c == 'N') { c = 0; break; } if (c == K_interrupt) { c = -1; break; } if (help) { tprintf(" y=YES n=NO"); fl; prompt_length += 11; help = 0; } } if (in_macro) { if (c < 0) m_break(); do_macro_processing = 1; } return c; } void ding(void) { BATCH_CHECK_V; putp(bell_str); fl; } void display_file(char *name, int modes) { FILE *f; register int c, stand_on; int linecnt, headln_cnt, hdline, no_conf; char headline[128]; headline[0] = 0; hdline = 0; no_conf = 0; headln_cnt = -1; if (modes & CLEAR_DISPLAY) { gotoxy(0, 0); clrdisp(); } linecnt = Lines - 1; chain: if (*name != '/') name = relative(help_directory, name); f = open_file(name, OPEN_READ); if (f == NULL) tprintf("\r\n\nFile %s is not available\n\n", name); else { stand_on = 0; while ((c = getc(f)) != EOF) { #ifdef HAVE_JOBCONTROL if (s_redraw) { no_conf = 1; break; } #endif /* HAVE_JOBCONTROL */ no_conf = 0; if (c == '\1') { if (STANDOUT) { putp(stand_on ? exit_standout_mode : enter_standout_mode); curxy_c += cookie_size; stand_on = !stand_on; } continue; } if (c == '\2') { headln_cnt = 0; continue; } if (c == '\3') { headln_cnt = 0; while ((c = getc(f)) != EOF && c != NL) headline[headln_cnt++] = c; headline[headln_cnt++] = NUL; name = headline; fclose(f); goto chain; } if (c == '\4') { tprintf("%s", version_id); continue; } if (headln_cnt >= 0) headline[headln_cnt++] = c; if (hdline) { tprintf("%s\r", headline); hdline = 0; linecnt--; } tputc(c); if (c == NL) { tputc(CR); if (headln_cnt >= 0) { headline[--headln_cnt] = 0; headln_cnt = -1; } if (--linecnt == 0) { no_conf = 1; if (any_key(0) == K_interrupt) break; linecnt = Lines - 1; if (modes & CLEAR_DISPLAY) { gotoxy(0, 0); clrdisp(); } hdline = headline[0]; } } } if (stand_on) { putp(exit_standout_mode); curxy_c += cookie_size; } fclose(f); } prompt_line = Lines - 1; /* move prompt to last line */ if (!no_conf && (modes & CONFIRMATION)) any_key(prompt_line); } void nn_exitmsg(int n, char *fmt,...) { va_list ap; if (terminal_speed != 0) { clrdisp(); visual_off(); } va_start(ap, fmt); vprintf(fmt, ap); putchar(NL); va_end(ap); nn_exit(n); /* NOTREACHED */ } static void push_msg(char *str) { register struct msg_list *mp, *newmsg; static int slots = 0; if (str != NULL) { if (slots > message_history) { for (mp = newmsg = msg_stack; mp->prev != NULL; mp = mp->prev) newmsg = mp; if (newmsg == mp) msg_stack = NULL; else { newmsg->prev = NULL; newmsg = mp; } freeobj(newmsg->buf); } else { slots++; newmsg = newobj(struct msg_list, 1); } newmsg->buf = copy_str(str); newmsg->prev = msg_stack; msg_stack = newmsg; } msg_ptr = msg_stack; } void vmsg(char *fmt, va_list ap) { char *errmsg; if (fmt) { char lbuf[512]; vsprintf(lbuf, fmt, ap); push_msg(lbuf); } if (msg_ptr) { errmsg = msg_ptr->buf; msg_ptr = msg_ptr->prev; } else { errmsg = "(no more messages)"; msg_ptr = msg_stack; } if (terminal_speed == 0) { tprintf("%s\n", errmsg); fl; return; } gotoxy(0, Lines - 1); tprintf("%s", errmsg); clrline(); any_message = 1; if (prompt_line != Lines - 1) gotoxy(prompt_length, prompt_line); fl; } void msg(char *fmt,...) { va_list ap; va_start(ap, fmt); vmsg(fmt, ap); va_end(ap); } void clrmsg(int col) { BATCH_CHECK_V; gotoxy(0, prompt_line + 1); clrpage(); if (col >= 0) gotoxy(prompt_length + col, prompt_line); fl; any_message = 0; } void prompt(char *fmt,...) { va_list ap; register char *cp; int stand_on; static char cur_p[FILENAME]; static char saved_p[FILENAME]; BATCH_CHECK_V; va_start(ap, fmt); if (fmt == P_VERSION) { gotoxy(0, prompt_line + 1); tprintf("Release %s ", version_id); clrline(); any_message++; if (prompt_line >= 0) gotoxy(prompt_length, prompt_line); goto out; } if (fmt == P_SAVE) { strcpy(saved_p, cur_p); goto out; } if (fmt == P_RESTORE) strcpy(cur_p, saved_p); if (prompt_line >= 0) gotoxy(0, prompt_line); if (fmt == P_MOVE) { clrline(); goto out; } if (fmt != P_REDRAW && fmt != P_RESTORE) vsprintf(cur_p, fmt, ap); tputc(CR); for (cp = cur_p, stand_on = 0, prompt_length = 0; *cp; cp++) { if (*cp == '\1') { if (cp[1] != '\1') { if (STANDOUT) { stand_on = !stand_on; nnstandout(stand_on); prompt_length += cookie_size; } continue; } cp++; } else if (*cp == '\2') { time_t t; char *timestr; #ifdef STATISTICS t = tick_usage(); #else t = cur_time(); #endif if (show_current_time) { timestr = ctime(&t) + 11; timestr[5] = NUL; tprintf("-- %s ", timestr); prompt_length += 9; } if (unread_mail(t)) { tprintf("Mail "); prompt_length += 5; } continue; } tputc(*cp); prompt_length++; } if (stand_on) { nnstandout(0); prompt_length += cookie_size; } clrline(); if (fmt == P_RESTORE) restore_xy(); #if notdef else curxy_c = -1; #endif out: va_end(ap); } int any_key(int line) { int was_raw, c, dmp; BATCH_CHECK; was_raw = is_raw; if (!is_raw) nn_raw(); if (line == 0) line = -1; else if (line < 0) line = Lines + line; if (line != 10000) so_printxy(0, line, "Hit any key to continue"); clrline(); dmp = do_macro_processing; do_macro_processing = 0; c = get_c(); if (c == 'q' || c == 'Q') c = K_interrupt; do_macro_processing = dmp; if (!was_raw) unset_raw(); return c; } static int pg_fline, pg_width, pg_maxw, pg_line, pg_col, pg_quit; regexp *pg_regexp = NULL; int pg_new_regexp = 0; void pg_init(int first_line, int cols) { if (pg_regexp) { freeobj(pg_regexp); pg_regexp = NULL; } pg_new_regexp = 0; pg_fline = first_line; pg_line = pg_fline - 1; pg_quit = pg_col = 0; pg_width = Columns / cols; pg_maxw = pg_width * (cols - 1); } int pg_scroll(int n) { pg_line += n; if (pg_line >= (Lines - 1)) { pg_line = 0; if (any_key(0) == K_interrupt) return 1; tputc(CR); clrline(); } return 0; } int pg_next(void) { int c; if (batch_mode) { putchar(NL); return 0; } pg_line++; if (pg_line < Lines) { gotoxy(pg_col, pg_line); if (pg_line == Lines - 1 && pg_col == pg_maxw) { c = any_key(0); if (c == '/') { char *expr; tputc(CR); tputc('/'); clrline(); expr = get_s((char *) NULL, (char *) NULL, (char *) NULL, NULL_FCT); if (expr != NULL && *expr != NUL) { freeobj(pg_regexp); pg_regexp = regcomp(expr); pg_new_regexp = 1; } } gotoxy(0, pg_fline); clrpage(); pg_col = 0; pg_line = pg_fline; if (c == K_interrupt) { pg_quit = 1; return -1; } return 1; } } else { pg_line = pg_fline; pg_col += pg_width; gotoxy(pg_col, pg_line); } return 0; } void pg_indent(int pos) { BATCH_CHECK_V; gotoxy(pg_col + pos, pg_line); } int pg_end(void) { int c; if (pg_quit == 0 && pg_next() == 0) c = any_key(0); else c = K_interrupt; if (pg_regexp) { freeobj(pg_regexp); pg_regexp = NULL; } return c == K_interrupt ? -1 : 0; } void user_delay(int ticks) { BATCH_CHECK_V; if (ticks <= 0 || conf_dont_sleep) { tprintf(" <>"); any_key(10000); } else { fl; sleep((unsigned) ticks); } } nn-6.7.4/pack_subject.h0000644000175000017500000000022507751420631014363 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int pack_subject(register char *, register char *, int *, int); nn-6.7.4/nngrab.sh0000644000175000017500000000125705750350010013354 0ustar mtpinsmtpins# nngrab -- quick news retrieval by keyword # # From: James A. Woods (ames!jaw), NASA Ames Research Center # # Naturally, you're running fast e?grep (GNU-style) or this is all for # naught. FOLDCASE="" case $1 in -c) FOLDCASE="-i" shift esac case $# in 1) ;; *) echo >&2 "usage: $0 [-c] keyword-pattern" exit 1 esac case $1 in *[A-Z]*) KW="`echo "$1" | tr '[A-Z]' '[a-z]'`";; *) KW=$1 esac if [ -s $DB/subjects ] ; then groups=` egrep "^[^:]*:.*${KW}" $DB/subjects | sed 's/:.*//' | uniq ` case $groups in '') echo >&2 "Pattern '$1' not found in any subjects" exit 1 esac groups="-G $groups" else groups=all fi exec $BIN/nn -Q -mxX $FOLDCASE -s/"$1" $groups nn-6.7.4/newsoverview.h0000644000175000017500000000500107751632107014471 0ustar mtpinsmtpins/*******************WARNING********************* This is a *MODIFIED* version of Geoff Coller's proof-of-concept NOV implementation. It has been modified to support threading directly from a file handle to a NNTP server without a temporary file. This is not a complete distribution. We have only distributed enough to support NN's needs. The original version came from world.std.com:/src/news/nov.dist.tar.Z and was dated 11 Aug 1993. In any case, bugs you find here are probably my fault, as I've trimmed a fair bit of unused code. -Peter Wemm */ /* * Copyright (c) Geoffrey Collyer 1992, 1993. * All rights reserved. * Written by Geoffrey Collyer. * Thanks to UUNET Communications Services Inc for financial support. * * This software is not subject to any license of the American Telephone * and Telegraph Company, the Regents of the University of California, or * the Free Software Foundation. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * * 4. This notice may not be removed or altered. */ /* newsreader interface to news overview data */ struct novgroup { char *g_dir; FILE *g_stream; struct novart *g_first; struct novart *g_curr; HASHTABLE *g_msgids; HASHTABLE *g_roots; }; struct novart { char *a_num; char *a_subj; char *a_from; char *a_date; char *a_msgid; char *a_refs; char *a_bytes; char *a_lines; char *a_others; /* these members are message-ids, filled in by novthread() */ char *a_parent; char *a_child1; /* first child of a chain */ char *a_sibling; /* next sibling in this chain */ /* end message-ids */ struct novart *a_nxtnum; /* next in numeric order */ }; nn-6.7.4/pack_name.h0000644000175000017500000000017107751420631013644 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int pack_name(char *, char *, int); nn-6.7.4/INSTALLATION0000644000175000017500000005234510223352604013403 0ustar mtpinsmtpins CONFIGURATION AND INSTALLATION OF NN ------------------------------------ RELEASE 6.7 This file describes the necessary steps to configure and install nn. You are advised to read this file before proceeding with the installation. If you've configured/installed nn before, you can probably ignore this file and proceed to editing config.h. Note that there are new variables in config.h, so copying one from nn-6.6 won't work. (Though it can obviously be used as a guide). The following command line prompts are used in the examples: `$' is used when no special privileges are required. `#' is used when SUPER USER privileges may be required. Note that this file still assumes in many places that you will be running nnmaster. If you are using NOV instead, you should ignore anything dealing with nnmaster and its database. If you are only installing the nn client, you can also ignore steps 3.5, 6, and 7. STEP 1 CONFIGURATION OF MAKEFILE ------------------------- Check the 'Makefile' file and supply the proper values for the following parameters: CC The command to invoke the C compiler CPP The command to invoke the C preprocessor with the result written to the standard output stream. CFLAGS Flags to the C compiler (e.g. -O or -g) LDFLAGS Additional flags to the C compiler when linking executables Notice that mandatory system specific CFLAGS and LDFLAGS are usually defined in the s- file (see below). STEP 2 CREATE CONFIGURATION FILE config.h ---------------------------------- The configuration file is distributed in the file config.h-dist. You must COPY this to config.h before proceeding. Keep the original -dist file to allow patches to be applied to it if necessary. $ cp config.h-dist config.h STEP 3 EDIT config.h TO REFLECT YOUR SYSTEM ------------------------------------ For each required configuration parameter, the config.h file contains verbose comments explaining the meaning of each parameter in the file. Read and follow these instructions carefully. If you do that, nn should compile and run without problems. Further information about the parameters can be found below in case you are in doubt. Regarding the NNTP related definitions, consult the file named olddoc/NNTP. Note that the information there is out of date. Notice that every time you edit config.h, the file update.h is modified to make a new configuration level for the version in the source directory. The current configuration is showed in the version string as #number. STEP 3.1: SPECIFY NOV AND NNTP CONFIGURATION ------------------------------------------------ If you will be connecting to a newsserver that uses NOV (and nearly all of them do these days), define NOV. If you use nn on a single system with news on its local disks, you will not have to worry the least about networking, and you simply leave NNTP undefined. STEP 3.2: SELECT SYSTEM FILE ---------------------------- A list of systems that nn runs on and their associated include files is found in the file MACHINES. If none of these can be used on your system, create your own based on the file conf/s-template.h. STEP 3.3: SELECT MACHINE FILE ----------------------------- A list of machines that nn runs on and their associated include files is found in the file MACHINES. If you cannot use one of these machine files create your own based on the file conf/m-template.h. STEP 3.4: LOCALIZE NN --------------------- You will have to specify a number of files and directories which nn has to know about to work properly. If you don't specify these, nn will in most cases use it own alternatives which will correspond to common practices on most installations. The following directories and files can be defined in config.h: BIN_DIRECTORY (mandatory) The directory where you want the user programs to be installed. The following programs will be installed in that directory: nn The news reader program nnacct Accounting, quota, and access management nnadmin The administration program (link to nn) nncheck Check for unread articles (link to nn) nngoback Mark older articles as unread (link to nn) nngrab Faster keyword search nngrep Grep for news groups (link to nn) nnpost Standalone posting program (link to nn). nnstats Collection and expiration statistics nntidy Cleans up the rc file (link to nn) nnusage Show usage statistics nnview Reads mail folders (link to nn) LIB_DIRECTORY The directory where nn's auxiliary programs and files will be installed unless another directory is specified by one of the following definitions. MASTER_DIRECTORY (optional, default = LIB_DIRECTORY) The directory containing the nnmaster daemon programs. It should not be shared in a networked environment! back_act Program to make daily copies of the active file to allow nngoback to work. nnmaster The program building and maintaining nn's database. nnspew Program to build a tertiary subject database for nngrab. MPID Exclusive lock file created by the currently running nnmaster daemon process. Accessed by nnadmin to get the daemon's process id. GATE Message file created by nnadmin and deleted by nnmaster. CLIENT_DIRECTORY (optional, default = LIB_DIRECTORY) Contains the auxiliary programs and files required by the nn program: aux The shell script used in connection with replies etc. It knows about common editors like vi and gnu emacs to have them position the cursor appropriately. To add support for your own favorite editor, you should edit the file aux.sh, not the compiled `aux' script! upgrade_rc Script used by nn to convert release 6.3 rc files to .newsrc format when first invoked after upgrade to 6.4 or later. HELP_DIRECTORY (optional, default = CLIENT_DIRECTORY/help) The directory where the help files and online manual are stored. This directory is an obvious candidate for sharing in a network. CACHE_DIRECTORY (optional, default = each user's .nn directory) Only used with NNTP!! Directory to be used as a common storage for temporary cache files when nn is used with NNTP. Using a common directory for cache files allows you to clean these out on reboot with a single "rm" command in the rc file: (cd CACHE_DIRECTORY; rm -f *) But of course this requires that you use a separate directory for the cache! TMP_DIRECTORY (optional, default = /usr/tmp) The directory to be used as temporary storage for files used when editing responses etc. LOG_FILE (optional, default = LIB_DIRECTORY/Log) The log file used by nnmaster and nn to store reports, error messages, usage statistics, etc. NEWS_DIRECTORY (optional, default = /usr/spool/news) The directory containing the news spool directories and files. It is not required when a remote NNTP server is used. NEWS_LIB_DIRECTORY (optional, default = /usr/lib/news) The directory containing the news system's active file and other related files. STEP 3.5: WHERE DO YOU WANT THE DATABASE? ----------------------------------------- The following definitions in config.h are used to control where the database maintained by nnmaster is stored. The database consists of a couple of files containing global information for all existing groups, and a pair of files for each non-empty group. The database requires some disk space to hold the necessary information. On average about 100 bytes per article in the system, or about 5% of the space allocated to the news articles. The database is intended to be shared together with the news spool directory in a networked environment provided that NETWORK_DATABASE is defined in config.h. If DB_DIRECTORY is not defined, the global database files will be located in a directory named NEWS_DIRECTORY/.nn, and the per-group files will be located in each individual news group's directory (named .nnd and .nnx). Using this strategy will normally require that nnmaster is owned "news" (OWNER in config.h). The location of the database can be changed via the following definitions in config.h: DB_DIRECTORY (optional, default = see above) The directory containing the global database information files. If you share /usr/spool/news via NFS or RFS, you can set DB to something like /usr/spool/news/.nn to share it automatically with /usr/spool/news. DB_DATA_DIRECTORY (optional, default = DB_DIRECTORY/DATA) When DB_DIRECTORY is defined, the per-group files will no longer be stored in the group directories, but in a single common directory specified by DB_DATA_DIRECTORY. The files in this directory will be named either by group number or by group name (if DB_LONG_NAMES is also defined). The files config.h and NNTP describes how to share the database between several machines (also when you don't use NNTP). STEP 4 COMPILE THE SOFTWARE -------------------- To compile the software, you just have to run one of the following make commands. If you are making a complete package with both master and client, do $ make all If you want to share a database which resides on another host (through NFS/RFS/rdist), you don't need a master on the local system, so you can do the following instead: $ make client STEP 5 INSTALLING THE SOFTWARE ----------------------- Installation of the nn software is handled entirely via a script "./inst" created during the compilation. The components of nn are split into five parts, which can be installed separately or in various combinations depending on whether you run a stand-alone system or networked systems sharing the database and other parts of the nn package. The five components are: 1) Master files and programs (machine dependent) Install the MASTER_DIRECTORY programs. 2) User files and programs (machine dependent, shareable) Install the BIN_DIRECTORY programs. 3) Auxiliary programs (configuration dependent, shareable) Install the CLIENT_DIRECTORY files and programs. 4) Documentation (shareable) Install the MAN pages in the proper directories. 5) Help files (shareable) Install the HELP_DIRECTORY files (except online manual). 6) Online manual (shareable with 5) Format and install the online manual in HELP_DIRECTORY. Unless you have defined yourself as the owner of the nn package and have write permission on all the necessary directories, you will need to be super-user to install nn, so start with $ su Now run the installation script: # ./inst The `inst' script will list a menu with the following choices: (1)-(6) Install individual parts of the package. (s) Install a complete server + client package (1-6). (c) Install a client which accesses all its support files and the database via a network (2). (h) Install a client with local auxiliary files, but shared documentation and help files (2-3). (n) Install a client accessing only the database via a network (2-6). (m) Install only the nnmaster (1). (c) Install only the client programs (2). (u) Update already installed parts of the package. This will check for the existence of the old programs, and only update programs and files already installed. You will typically use this after applying patches. You can also run the `inst' script with the choices as arguments, e.g. ./inst m c NOTICE: Since nnmaster runs setuid OWNER, all users can potentially kill the running master, initialize the database etc. if they have access to execute the master. So either restrict the permissions to execute nnmaster or the access to the directory containing it. The default installation puts modes -rwsr-s--- on nnmaster and leaves the directory "open" which may not be adequate for you. STEP 6 INITIALIZE THE DATABASE ----------------------- If you have installed the nnmaster on the current system, you now have to initialize the database: $ su -- also as superuser # ./inst INIT If something goes wrong in this step, e.g. problems with the active file, you must redo the initialization after fixing the other problems. When the INIT operation completes, a database with empty entries for all the currently existing groups will have been created. If you want some special actions to be performed for specific groups as described in the nnmaster manual, you must now edit the GROUPS file created by nnmaster in the DB_DIRECTORY. If you modify the GROUPS file, you must run the following command to register the changes to the GROUPS file. $ MASTER_DIRECTORY/nnmaster -G When you are ready, you must start nnmaster to enter all the existing articles into the database. If you use the following command, nnmaster will fork and return immediately; its background child will continue to update the database whenever new articles arrive: It will ignore articles which are more than 45 days old (-O). $ MASTER_DIRECTORY/nnmaster -r -O45 It may take quite a while before all existing articles have been entered into the database. You can use nnadmin's (U)pdate and (S)tat commands to trace the progress and the (L)og command to see if it has finished (a C entry will occur). You will see that many groups will be BLOCKED, but the number should decrease as nnmaster gets through more and more groups. You can also use the following command to do the initial collection of articles from a terminal and get a nice trace of the action: $ MASTER_DIRECTORY/nnmaster -D -O45 One or two numbers will be shown while a group is being collected. The first number is the number of the article currently being read. The (optional) second number will be the number of old (or bad) articles which nnmaster has ignored in the group so far. NOTICE: If the system file you have used does not specify how to detatch a process from its controlling terminal, the nnmaster may die when you log out. When nnmaster has finished the initial collection the articles, you can nnadmin's (V)alidate command to verify that the database build by nnmaster is consistent (restart nnadmin before verifying). STEP 7 UPDATE SYSTEM FILES ------------------- You will have to edit some of the scripts and files on your system to get the database and other support files updates automatically, also following system shutdown, crashes, etc. STEP 7.1: START NNMASTER WHEN SYSTEM IS BOOTED ---------------------------------------------- To have the database updated at all times, the nnmaster should be started when the system is booted. There will be a file named /etc/rc, a directory /etc/rc.d, or something similar on your system which contains commands that are executed when the system is booted. The following commands should be added to the boot scripts: rm -f MASTER_DIRECTORY/MPID MASTER_DIRECTORY/nnmaster -l -r -C Alternatively, you can arrange for cron to run the master regularly. In this case, you should not use the -r and -C options. Instead, you should let cron execute the command 'nnadmin Z' once a day to validate the database. For example: 0,10,20,30,40,50 * * * * MASTER/nnmaster -LM 0 0 * * * BIN/nnadmin Z WARNING: If you share the database via NFS or RFS, nnmaster should run only on the system where the database actually resides!! And preferably it should run on the host where the news spool directory is located as well. This will improve speed as well as reliability (NFS can suffer from time out problems). STEP 7.2: SETUP EXPIRE ON THE DATABASE -------------------------------------- To run expire on the database, you simply have to execute the following command (with sufficient privileges): BIN_DIRECTORY/nnadmin =EYW You should arrange for expire to be run on nn's database whenever you have run expire on the news directories. Supposing you run the expire from your crontab, you may simply add the above command to the crontab at an appropriate time when you are certain that news' expire is completed. If you run nnmaster from cron rather than in daemon mode, you can use the following command to run expire on the database. nnmaster -F -k "" This form allows you to run expire on selected groups rather than on all groups as initiated by the above nnadmin command. See the nnmaster manual for further details. STEP 7.3: SAVE A COPY OF THE ACTIVE FILE ONCE A DAY --------------------------------------------------- The `nngoback' program requires that the program `back_act' is executed once (and only once) every day to maintain suitable copies of the active files for the last 14 days. In a network environment, it should execute on the same host as the nnmaster. Simply arrange for back_act to be invoked by cron once a day (preferably just before the batch of news for `today' arrives). For example, assuming the news is received just before midnight (syntax and location of crontab entry may vary): In /usr/spool/cron/crontabs/news (System V): 00 23 * * * MASTER_DIRECTORY/back_act or in /usr/lib/crontab (BSD): 00 23 * * * su - news MASTER_DIRECTORY/back_act The default setup allows you to go 14 days back with nngoback, but if you don't keep news that long, there is no reason to keep that many copies of the active file either. In that case, you can supply the maximum number of days as an argument to back_act. Of course, you can also keep more than 14 copies of the active file to allow nngoback to go more than 14 days back by giving back_act an argument larger than 14. STEP 7.4: PREPARE FOR NNSPEW TO BE RUN REGULARLY ------------------------------------------------- The nngrab program will run faster if a dedicated subject database in addition to the normal database is available. To maintain this additional database, the program nnspew must be executed regularly, e.g. from cron. Every 3-6 hours should be sufficient to keep the database reasonably up-to-date, e.g. In /usr/spool/cron/crontabs/news (System V): 2 6,12,18 * * * MASTER_DIRECTORY/nnspew or in /usr/lib/crontab (BSD): 2 6,12,18 * * * su - news MASTER_DIRECTORY/nnspew STEP 8 INSTALL AND EDIT GLOBAL FILES ----------------------------- Depending on your needs, you should create the following files on each host running nn (you may also just share the files if you like): CLIENT_DIRECTORY/init The global init file for all users on the system. Users will be able to override the definitions in this file, but you can probably make some sensible decisions which will prevent novice users from getting into trouble, for example set the default distribution to "local" etc. You can also specify a global presentation sequence here. You may use init.sample as a starting point, but don't use it without careful examination. Especially, the sequence part is mainly an illustration of the possibilities. If you are in doubt, just delete the sequence part of the file (groups will then be presented in pure alphabetical order). CLIENT_DIRECTORY/motd Every time you change this file, it will be shown to the nn users the next time they invoke nn. This can be used to inform the users about changes in the news environment or local policies. (motd = message of the day) NNTP_SERVER Must contain the host name of the NNTP-server when NNTP is used. If you already run NNTP with your other news readers, this file does not need to be modified. STEP 9 TEST THE BASIC FUNCTIONS ------------------------ If any of the following tests fails or you see other peculiar behavior, you should consult the PROBLEMS file. You may not be the only one to have seen the problems, and there might even be a solution. First you should check that nnmaster does collect the articles it is supposed to. Here, nnadmin is a great help, since you can peek around in all the database files and see what nnmaster is doing. nnadmin takes a snap-shot of the database when it starts up, but you can take a new snap-shot at any time using the (U)pdate command. Also look at the (L)og to see that there were no problems while collecting the articles. There are a few things you should check to ensure the proper functioning of nn. 1) Backup your current .newsrc file if you have one. (Don't save it in .newsrc.bak or .newsrc.orig since nn may use these names). 2) Run `nn'. If you have upgraded from release 6.3, nn will convert your release 6.3 .nn/rc file into a .newsrc file. 3) Does nn find any news? If not, does nn -x find anything? 4) Can you send mail to yourself? Try the sequence: m (return) (return) testing (return) edit the letter s (return) If not, you should check the REC_MAIL program. 5) Can you post an article to the local test group? Try: :post (return) test (return) local (return) edit the article s (return) If not, you should check the INEWS program. ------------------------------------------- IF EVERYTHING WORKS YOU HAVE COMPLETED THE INSTALLATION ------------------------------------------- UPDATING THE SOFTWARE --------------------- Patches to this software will be distributes as context diff's which can be applied using Larry Wall's `patch' program. After applying the patches, you will need to redo the compilation and installation steps: $ patch -p0 < PATCH_FILE (or use nn's :patch command) $ make all $ su # ./inst u To be able to install a new nnmaster, the currently running master (if any) will be stopped automatically, and it has to be started manually when the installation is complete (unless it is setup to be run by cron). Notice that unless it is explicitly required in the patch, there is no need to reinitialize the database after applying the patch. nn-6.7.4/debug.h0000644000175000017500000000117710223315255013014 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Debug flags and defines * * Notice: no modules are conditioned by this file in the * makefile. touch the source file to have a change * in debugging setup to reflect the program behavior */ #ifndef _NN_DEBUG_H #define _NN_DEBUG_H 1 /* debugging */ #define RC_TEST 1 /* rc file updates */ #define DG_TEST 2 /* digest decoding */ #define SEQ_TEST 4 /* sequence file decoding */ #define SEQ_DUMP 8 /* dump sequence after read */ extern int Debug; #endif /* _NN_DEBUG_H */ nn-6.7.4/options.c0000644000175000017500000001134215213127163013412 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Generic option parsing */ #include #include #include #include "config.h" #include "global.h" #include "nn.h" #include "options.h" #include "variable.h" /* options.c */ static void dump_options(int type, char *tail); static void error(char *message, char option_letter); static char **save_argv, *usage_mesg; static struct option_descr *save_optd; extern int no_update; char * program_name(char **av) { char *cp; /* skip "/path/" part of program name */ if ((cp = strrchr(*av, '/'))) return cp + 1; else return *av; } static void dump_options(int type, char *tail) { register struct option_descr *od; int any = 0; for (od = save_optd; od->option_letter; od++) { if (od->option_type != type) continue; fprintf(stderr, any ? "%c" : " -%c", od->option_letter); any++; } if (any && tail && tail[0]) { fprintf(stderr, "%s", tail); } } static void error(char *message, char option_letter) { char *prog_name = program_name(save_argv); fprintf(stderr, "%s: ", prog_name); fprintf(stderr, message, option_letter); fputc('\n', stderr); fprintf(stderr, "usage: %s", prog_name); dump_options(1, ""); dump_options(2, " STR"); dump_options(3, " [STR]"); dump_options(4, " NUM"); dump_options(5, " [NUM]"); if (usage_mesg) fprintf(stderr, "%s", usage_mesg); fputc(NL, stderr); nn_exit(9); } /* parse variables set on command line separately from everything * else, very early. This is necessary because open_master is done * so early for nntp; don't alter the arg, so we can skip it later. */ void parseargv_variables(char **av, int ac) { char *value; while (--ac > 0) { if (**++av != '-' && (value = strchr(*av, '='))) { *value++ = NUL; set_variable(*av, 1, value); *--value = '='; /* restore for later parsing to skip it */ } } } int parse_options(int ac, char **av, char *envname, struct option_descr options[], char *usage) { register char *cp, opt; register struct option_descr *od; int files; char **names; char *envinit; save_argv = av; save_optd = options; if (options == NULL) return 0; usage_mesg = usage; --ac; names = ++av; files = 0; envinit = envname ? getenv(envname) : NULL; cp = envinit; next_option: if (envinit) { while (*cp && isspace(*cp)) cp++; if (*cp == '-') { cp++; goto next_option; } if (*cp == NUL) { envinit = NULL; goto next_option; } } else if (cp == NULL || *cp == NUL) { if ((cp = *av++) == NULL) { *names = NULL; return files; } ac--; if (*cp != '-') { if (!strchr(cp, '=')) { *names++ = cp; cp = NULL; files++; } /* * else it's a variable that was handled in parseargv_variables() * earlier, so nothing to do. */ cp = NULL; goto next_option; } cp++; /* skip - */ } opt = *cp++; for (od = options; od->option_letter; od++) { if (od->option_letter != opt) continue; switch (od->option_type) { case 1: /* BOOL_OPTION */ *((int *) (od->option_address)) = !*((int *) (od->option_address)); goto next_option; case 2: /* STRING_OPTION */ case 3: /* OPTIONAL_STRING */ /* -oSTR or -o STR */ while (*cp && isspace(*cp)) cp++; if (*cp == NUL) { if (envinit || ac == 0) { if (od->option_type == 3) goto opt_str; error("missing string argument to -%c", opt); } cp = *av++; ac--; } if (od->option_type == 3 && *cp == '-') { cp++; goto opt_str; } *(od->option_address) = cp; if (envinit) { while (*cp && !isspace(*cp)) cp++; if (*cp) *cp++ = NUL; } else cp = NULL; goto next_option; opt_str: *(od->option_address) = od->option_default; goto next_option; case 4: case 5: /* -oN or -o N */ while (*cp && isspace(*cp)) cp++; if (*cp) { if (!isdigit(*cp)) { if (od->option_type == 5) goto opt_int; error("non-numeric argument to -%c", opt); } } else { if (envinit || ac == 0 || !isdigit(**av)) { if (od->option_type == 5) goto opt_int; error("missing argument to -%c", opt); } cp = *av++; ac--; } *((int *) (od->option_address)) = atoi(cp); while (isdigit(*cp)) cp++; goto next_option; opt_int: *((int *) (od->option_address)) = (int) (od->option_default); goto next_option; } } error("unknown option '-%c'", opt); /* NOTREACHED */ return 0; } nn-6.7.4/news.c0000644000175000017500000002131715213127166012701 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Article header parsing. */ #include #include #include "config.h" #include "global.h" #include "digest.h" #include "more.h" #include "news.h" #include "nntp.h" #ifndef SUNOS4 #include #else #include #endif /* news.c */ struct news_header news; static char **art_hdr_field(register char *lp, int all); int retry_on_error = 0; char * parse_header(FILE * f, char **(*hdr_field) (), int modes, news_header_buffer hdrbuf) { register char *bp, *cp, **fptr; int siz, all, date_only; long pos; pos = ftell(f); /* read first NEWS_HEADER_BUFFER bytes (should be more than enough) */ all = modes & GET_ALL_FIELDS; date_only = modes & GET_DATE_ONLY; siz = fread(hdrbuf, sizeof(char), NEWS_HEADER_BUFFER, f); if (siz <= 0) { hdrbuf[0] = NUL; return hdrbuf; } bp = hdrbuf; bp[siz - 1] = NUL; /* decode subarticle header */ while (*bp) { if (*bp == NL) { /* empty line following header */ ++bp; fseek(f, pos + (bp - hdrbuf), 0); return bp; } if (bp[0] == SP && bp[1] == SP) { /* An ugly hack so that NN * can read */ bp += 2; /* it's own FAQ... :-) (sorry Bill) */ continue; } if (date_only && *bp != 'D') fptr = NULL; else if ((fptr = (*hdr_field) (bp, all))) { while (*bp && *bp != ':' && isascii(*bp) && !isspace(*bp)) bp++; if (*bp) bp++; while (*bp && isascii(*bp) && isspace(*bp) && *bp != NL) bp++; *fptr = bp; } #ifdef NO_HEADER_SEPARATION_HACK else { for (cp = bp; *cp && *cp != ':'; cp++) { if (!isascii(*cp)) break; if (*cp == '_' || *cp == '-') continue; if (isalnum(*cp)) continue; break; } if (*cp != ':') { *bp = NL; pos--; continue; } } #endif while (*bp && *bp != NL) bp++; /* Assume that continued lines are never empty! */ if (fptr && bp == *fptr) *fptr = NULL; while (*bp) { /* look for continued lines */ cp = bp + 1; if (!(*cp && isascii(*cp) && isspace(*cp) && *cp != NL)) { /* next line is empty or not indented */ *bp++ = NUL; break; } *bp = SP; /* substitute NL with SPACE */ bp = cp; while (*bp && *bp != NL) bp++; } } return bp; } static char ** art_hdr_field(register char *lp, int all) { #define check(name, lgt, field) \ if (isascii(lp[lgt]) && isspace(lp[lgt]) \ && strncasecmp(name, lp, lgt) == 0)\ return &news.field switch (*lp++) { case 'A': case 'a': if (!all) break; check("pproved:", 8, ng_appr); break; case 'B': case 'b': check("ack-References:", 15, ng_bref); break; case 'C': case 'c': check("ontrol:", 7, ng_control); check("omment-To:", 10, ng_comment); break; case 'D': case 'd': check("ate:", 4, ng_date); if (!all) break; check("ate-Received:", 13, ng_rdate); check("istribution:", 12, ng_dist); break; case 'F': case 'f': check("rom:", 4, ng_from); if (!all) break; check("ollowup-To:", 11, ng_follow); break; case 'K': case 'k': if (!all) break; check("eywords:", 8, ng_keyw); break; case 'L': case 'l': check("ines:", 5, ng_xlines); break; case 'M': case 'm': if (!all) break; if (strncasecmp(lp, "essage-", 7)) break; lp += 7; check("ID:", 3, ng_ident); break; case 'N': case 'n': check("ewsgroups:", 10, ng_groups); break; case 'O': case 'o': if (!all) break; check("rganization:", 12, ng_org); check("riginator:", 10, ng_origr); break; case 'P': case 'p': if (!all) break; check("ath:", 4, ng_path); break; case 'R': case 'r': check("eferences:", 10, ng_ref); check("eply-To:", 8, ng_reply); break; case 'S': case 's': check("ubject:", 7, ng_subj); check("ender:", 6, ng_sender); if (!all) break; check("ummary:", 7, ng_summ); break; case 'T': case 't': check("itle:", 5, ng_subj); break; case 'X': case 'x': check("ref:", 4, ng_xref); break; } return NULL; #undef check } int is_header_line(char *line) { return art_hdr_field(line, 0) != (char **) NULL; } FILE * open_news_article(article_header * art, int modes, news_header_buffer buffer1, news_header_buffer buffer2) { char *digest_buffer; int retry; FILE *f; struct stat statb; #ifndef DONT_COUNT_LINES int c; off_t digest_artlen = 0; #endif /* DONT_COUNT_LINES */ #ifdef NNTP int lazy = 0; #endif /* NNTP */ #ifndef DONT_COUNT_LINES #ifdef NNTP long fpos; #endif /* NNTP */ #endif /* DONT_COUNT_LINES */ if (art->flag & A_FOLDER) { f = open_file(group_path_name, OPEN_READ); if (f == NULL) return NULL; fseek(f, art->hpos, 0); #ifndef DONT_COUNT_LINES digest_artlen = art->lpos - art->fpos; #endif /* DONT_COUNT_LINES */ } #ifdef NNTP else if (use_nntp) { lazy = (current_group->master_flag & M_ALWAYS_DIGEST) == 0 && (modes & LAZY_BODY) ? 1 : 0; f = nntp_get_article(art->a_number, lazy); if (f == NULL) return NULL; } #endif /* NNTP */ else { sprintf(group_file_name, "%ld", art->a_number); retry = retry_on_error; while ((f = open_file(group_path_name, OPEN_READ)) == NULL) if (--retry < 0) return NULL; /* necessary because empty files wreak havoc */ if (fstat(fileno(f), &statb) < 0 || #ifdef NOV (art->lpos = statb.st_size, statb.st_size <= (off_t) 0)) { #else statb.st_size < art->lpos || statb.st_size <= (off_t) 0) { #endif /* NOV */ fclose(f); return who_am_i == I_AM_MASTER ? (FILE *) 1 : NULL; } } digest_buffer = buffer1; if (modes & FILL_NEWS_HEADER) { news.ng_from = NULL; news.ng_reply = NULL; news.ng_name = NULL; news.ng_subj = NULL; news.ng_groups = NULL; news.ng_ref = NULL; news.ng_bref = NULL; news.ng_sender = NULL; news.ng_xlines = NULL; news.ng_xref = NULL; if (modes & GET_ALL_FIELDS) { news.ng_path = NULL; news.ng_reply = NULL; news.ng_ident = NULL; news.ng_follow = NULL; news.ng_keyw = NULL; news.ng_dist = NULL; news.ng_org = NULL; news.ng_appr = NULL; news.ng_summ = NULL; news.ng_control = NULL; news.ng_date = NULL; news.ng_rdate = NULL; news.ng_comment = NULL; news.ng_origr = NULL; } if (modes & GET_DATE_ONLY) news.ng_date = NULL; (void) parse_header(f, art_hdr_field, modes, buffer1); if (news.ng_from == NULL) news.ng_from = news.ng_sender; #ifdef NOV /* fill in article positions.. new style.. */ if ((art->flag & (A_FOLDER | A_DIGEST)) == 0) { setpos(art, f); news.ng_fpos = art->fpos; } #else /* NOV */ if (modes & FILL_OFFSETS) /* used only by old DB code */ art->fpos = news.ng_fpos = ftell(f); #endif /* NOV */ if (news.ng_xlines) news.ng_lines = atoi(news.ng_xlines); else { #ifndef DONT_COUNT_LINES #ifdef NNTP if (use_nntp && lazy && !(art->flag & (A_DIGEST | A_FOLDER))) { fpos = ftell(f); fclose(f); f = nntp_get_article(art->a_number, 2); if (f == NULL) return NULL; lazy = 0; fseek(f, fpos, 0); } #endif /* NNTP */ news.ng_lines = 0; while ((c = getc(f)) != EOF) { if (c == '\n') news.ng_lines++; if (digest_artlen && --digest_artlen == 0) break; } #else /* DONT_COUNT_LINES */ news.ng_lines = -1; #endif /* DONT_COUNT_LINES */ } if (modes & FILL_OFFSETS) { fseek(f, 0, 2); news.ng_lpos = ftell(f); } #ifdef NNTP else if (use_nntp && (art->flag & (A_DIGEST | A_FOLDER)) == 0) { fseek(f, 0, 2); art->lpos = ftell(f); } #endif news.ng_flag = 0; if (news.ng_appr) news.ng_flag |= N_MODERATED; if (modes & DIGEST_CHECK && is_digest(news.ng_subj)) news.ng_flag |= N_DIGEST; #ifdef NNTP if (use_nntp && lazy && news.ng_flag & N_DIGEST) { fclose(f); f = nntp_get_article(art->a_number, 2); if (f == NULL) return NULL; } #endif digest_buffer = buffer2; } #ifdef NNTP else if (use_nntp && (art->flag & (A_DIGEST | A_FOLDER)) == 0) { fseek(f, 0, 2); art->lpos = ftell(f); } #endif if (modes & FILL_DIGEST_HEADER) { fseek(f, art->hpos, 0); parse_digest_header(f, modes & GET_ALL_FIELDS, digest_buffer); } #ifdef NOV else { /* fill in article positions.. new style.. */ if ((art->flag & (A_FOLDER | A_DIGEST)) == 0) { setpos(art, f); news.ng_fpos = art->fpos; } } #endif /* NOV */ fseek(f, (modes & SKIP_HEADER) ? art->fpos : art->hpos, 0); return f; } nn-6.7.4/hdbmint.h0000644000175000017500000000442207751115174013361 0ustar mtpinsmtpins/*******************WARNING********************* This is a *MODIFIED* version of Geoff Coller's proof-of-concept NOV implementation. It has been modified to support threading directly from a file handle to a NNTP server without a temporary file. This is not a complete distribution. We have only distributed enough to support NN's needs. The original version came from world.std.com:/src/news/nov.dist.tar.Z and was dated 11 Aug 1993. In any case, bugs you find here are probably my fault, as I've trimmed a fair bit of unused code. -Peter Wemm */ /* * Copyright (c) Geoffrey Collyer 1992, 1993. * All rights reserved. * Written by Geoffrey Collyer. * Thanks to UUNET Communications Services Inc for financial support. * * This software is not subject to any license of the American Telephone * and Telegraph Company, the Regents of the University of California, or * the Free Software Foundation. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * * 4. This notice may not be removed or altered. */ /* * general-purpose in-core hashing, dbm interface (internals) */ #define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) #define BADTBL(tbl) (((tbl)->ht_magic&BYTEMASK) != HASHMAG) #define HASHMAG 0257 #define BYTEMASK 0377 #define HASHENT struct hashent HASHENT { HASHENT *he_next; /* in hash chain */ HDBMDATUM he_key; /* to verify a match */ HDBMDATUM he_data; }; HASHTABLE { HASHENT **ht_addr; /* array of HASHENT pointers */ unsigned ht_size; unsigned char ht_magic; unsigned (*ht_hash) (); }; nn-6.7.4/fullname.c0000644000175000017500000000537515212306634013534 0ustar mtpinsmtpins/* * This file is derived from Bnews' fullname.c file. * Bnews is Copyright (c) 1986 by Rick Adams. * * NOTICE: THIS CODE HAS BEEN MODIFIED TO FIT THE NN ENVIRONMENT: * * The full_name function has been rewritten entirely, although * there is still some structural resemblance. * Fullname checks $NAME before looking at /etc/passwd. * The LOCALNAME alternative has been removed, because it would fit * nn very poorly. * The buildfname function is made static and moved before full_name. * * fullname.c - this file is made separate so that different local * conventions can be applied. The stock version understands two * conventions: * * (a) Berkeley finger: the gecos field in /etc/passwd begins with * the full name, terminated with comma, semicolon, or end of * field. & expands to the login name. * (b) BTL RJE: the gecos field looks like * : junk - full name ( junk : * where the "junk -" is optional. * * If you have a different local convention, modify this file accordingly. */ #ifdef SCCSID static char *SccsId = "@(#)fullname.c 1.13 11/4/87"; #endif /* SCCSID */ #include #include #include #include #include "config.h" #include "global.h" /* fullname.c */ static void buildfname(register char *p, char *login, char *buf); /* ** BUILDFNAME -- build full name from gecos style entry. ** (routine lifted from sendmail) ** ** This routine interprets the strange entry that would appear ** in the GECOS field of the password file. ** ** Parameters: ** p -- name to build. ** login -- the login name of this user (for &). ** buf -- place to put the result. ** ** Returns: ** none. ** ** Side Effects: ** none. */ static void buildfname(register char *p, char *login, char *buf) { register char *bp = buf; if (*p == '*') p++; while (*p != '\0' && *p != ',' && *p != ';' && *p != ':' && *p != '(') { if (*p == '-' && (isdigit(p[-1]) || isspace(p[-1]))) { bp = buf; p++; } else if (*p == '&') { strcpy(bp, login); if ((bp == buf || !isalpha(bp[-1])) && islower(*bp)) *bp = toupper(*bp); while (*bp != '\0') bp++; p++; } else *bp++ = *p++; } *bp = '\0'; } /* * Figure out who is sending the message and sign it. * We attempt to look up the user in the gecos field of /etc/passwd. */ char * full_name(void) { static char *fullname = NULL; char inbuf[FILENAME]; struct passwd *pw; if (fullname == NULL) { if ((fullname = getenv("NAME")) != NULL) return fullname; pw = getpwuid(user_id); if (pw == NULL) return fullname = "?"; buildfname(pw->pw_gecos, pw->pw_name, inbuf); if (inbuf[0] == 0) strcpy(inbuf, pw->pw_name); fullname = copy_str(inbuf); } return fullname; } nn-6.7.4/ChangeLog0000644000175000017500000010733515214614373013342 0ustar mtpinsmtpinsUnoffical Patch #17 (larry@garfield.cs.mun.ca Nov 19, 1991) - core dump due to selecting last_article+1 on screen - core dump due to consolidating (closing) last subject in a group under certain circumstances. - selecting consolidated articles with = command did not highlight the selected consolidated articles correctly. - fixed unconditional CLEAR TO END OF LINE that erased the next line on some terminals. Unofficial Patch #18 (larry@garfield.cs.mun.ca; Dec 1, 1991) - possible core dump/strange behavior when using '=' command to return to the menu from article reading mode. (Reported by Olaf Schlueter [olaf@oski.toppoint.de]) - articles marked as A_LEAVE_NEXT ('=') are not given any special attention in consolidated menu mode. I considered this incorrect (I leave stuff around all the time :) and modified nn's behavior to notice them. - unable to de-select an auto-selected subject when using consolidated-menu mode. Patch #17 and #18 made official (peter@DIALix.oz.au) Patch #19 (peter@DIALix.oz.au; 28 Nov 92) Title: Reduction in startup time From: Geoff Collyer The initial startup time can be cut in half. Title: A/UX 3.0 support From: Jim Jagielski Support for Apple A/UX 3.0, originally by Alexis Rosen Title: NeXT-2.2 support From: Lele Gaifax Title: Misc "static" function declarations corrected in prototypes. From: Lele Gaifax Title: NNTP name canolicalization in /usr/lib/nntp_server vs gethostname() From: Mark R. Levinson The code now converts the name from nntp_server to the official name before comparing it to the gethostname() result. This is useful if nntp_server points to an alias (ie: news.your.domain.name) Title: Count lines if no "Lines:" field present. From: Paul Eggert and Luc Rooijakkers If there is no "Lines:" field, NN will count them. Title: Correct line counting algorithm conflict with NNTP and folders/digests. From: Peter Wemm The code would attempt to fetch a sub-article from a folder or digest from the NNTP server - which is clearly not possible... Title: Make line counting optional From: Peter Wemm #define DONT_COUNT_LINES option to config - it is undesirable to fetch all articles from a remote NNTP server just to display a line count on the menu. Title: print a "??" instead of "-1" for lines count in some display modes From: J. Porter Clark If you change the "layout", it is possible for NN to display a -1 in the lines count field. Title: Timezone misalculations From: Paul Eggert NN mishandled non-GMT timezones in sorting resulting in articles being presented out of order. Title: NNGRAB speedups From: Paul Eggert nngrab generated unnecessary files and processes (especially on BSD'ish machines that do not have a built-in "[" in the shell Title: NN wastes time seeking to find the file size From: Paul Eggert Make NN count bytes as they are read rather than seeking all over the place. Title: Correct NN's header parser to RFC compliant behavior From: Paul Eggert NN gets confused on some variations of legitimate header continuation lines. Title: Remove defunct comp.ai.neural-nets digest special handling From: Paul Eggert The comp.ai.neural-nets digests are now in standard form. Title: Improve ":decode" to recognize more formats From: Mark Rawling Fix for fault in the chtbl code Title: Add a "ignore-formfeed" variable From: Romain King Special request: display "^L" instead of a page break. Not on be default. Title: Fix that darn "NO DIRECTORY (ok)" message that nnmaster is so fond of. From: Steve Simmons If nnmaster is going to complain about a missing directory, at least say which one it is.... Title: get aux script to clean up common editor backup files From: Minh Tran-Le Emacs makes file~ backups, some make file.bak, etc. Try and clean up a few of them after launching an editor. Title: make aux script generate control messages that INN will accept. From: Peter Wemm INN's inews is pretty fussy in what it will accept.. Title: Resolve conflict NN with INN on SVR4/386 From: Peter Wemm SVR4/386 has an "interesting" feature: You can write to a file with mmap() **without** updating the timestamps.... INN uses this for the active file, nnmaster now has a "-U" switch to force an unconditional scan of the active file after the timeout, rather than if the timestamps changed. Title: VMS ANU-NEWS compatability From: Matt Heffron VMS ANU-NEWS's NNTP returns a different error code on no-such-article Title: Log group that generated a nntp error From: Matt Heffron If a NNTP error is generated, log the group that caused it. Title: Support terminals without clr_eol capability From: Luc Rooijakkers Some ancient terminals cannot clear to end-of-line. Simulate it by outputting spaces. This means that printf/puts/putchar/stdout can no longer be used in visual mode, because term.c has to keep track of the cursor position. Use the new tprintf/tputc instead (but only from files that are always linked with term.o). A list of the files that may do printf/puts/putchar/stdout is in `noterm'. Title: Miscellaneous cleanup of term.c From: Luc Rooijakkers Use tputs correctly unless BROKEN_TPUTS is #defined (UNISYS?) Title: Allow backslash to escape semicolons in extra headers From: Luc Rooijakkers This is necessary to let the user add a MIME Content-Type header via the news-header or mail-header variables. Title: Fixed annoying cursor position during some messages From: Luc Rooijakkers Don't reposition the cursor to the prompt line after a message if the message has overwritten the prompt. Title: Fixed invisible error messages From: Luc Rooijakkers If terminal has a separate `visual mode' screen, printf/nn_exit sequences don't work. Replaced user_error by nn_exitmsg which has the exit status as additional first argument, and replaced the printf/nn_exit sequences by calls to nn_exitmsg Title: Added charset variable and 8-bit checking for posts From: Luc Rooijakkers The new charset variable can be set to the character set of the terminal. It also determines the default value of the data-bits variable. If a message is posted whose body contains 8-bit characters and charset is not set to "unknown", nn will add MIME-Version and Content-XXX headers indicating its character set. Such messages cannot be posted if the current character set is a 7-bit set. To obtain the previous behaviour, do `set charset unknown'. Title: Cleaned fill/nofill use in nn.1.* files From: Luc Rooijakkers Lots of examples in the manual were being filled by *roff. Title: Sped up aux.sh and made it more portable From: Luc Rooijakkers Using case instead of if is faster on shells that don't have if Title: Preview mode and menu.c From: Steven Grimm The symptom: previewing (usually via auto-preview-mode) an article on the next-to-last page of a group menu then going to the last page of the menu caused nn to dump core if the previewed article had a greater article number than the last article on the last screen. For example, viewing article 'h' would cause the problem if the last screen only went up to 'e'. Title: Correct a small over-optimization in term.c From: Luc Rooijakkers My term.c contained one cursor position update too many, causing things to go wrong sometimes. Title: DecStation/Ultrix 4.2 support From: "stolen" from Toivo Pedaste my Peter Wemm. conf/m-dec3100.h for mips machines running Ultrix. Title: Misc cleanups of bad code From: Paul Eggert The code is loaded with functions that return no value, etc. This is a start at fixing some of the more serious faults. Title: Macro to call metamail From: mikew@kpc.com A simple example macro for passing MIME articles to metamail Title: Bug in folder code procuces core dump. From: Stefan Schwarz Title: rfmail support From: Bo Kullmar Title: Clean up code part 2 From: Peter Wemm Continued on from what Paul Eggert started... AARRGGHH!! It's a mess! Title: Update help screens From: Peter Wemm As suggested by Bill Wohler , sort out the help pages to "clarify" the differences between a few commands, and to add the "(" and ")" keys.. [darn.. It's a tight fit in there.] Title: Domain/OS fixes and others From: Michael T Pins 1> Makes nnmaster run much happier on HP/Apollo's running Domain/OS 2> Allows nn to compile on variants of HP-UX, among others, that it won't compile on otherwise. Title: aux.sh fixes From: Peter Wemm , bug reports from many... The changes to aux.sh to use "here documents" broke a lot of /bin/sh's Hack them out of the code, as not everybody has ksh. Title: SunOS 4.1 supports window siziing From: Paul Eggert Add a change to s-sunos4-0.h Title: Add support for PCS Cadmus with MIPS cpu From: Peter Pichol ) Create m-pcs.h Title: Add support for DEC Alpha's running OSF/1 From: Mike Wexler Create m-alpha.h and s-osf1.h Title: A way to read even less news! From: Keld.Simonsen@dkuug.dk It has been an old desire (viz. the FAQ) to be able to skip followups to subjects, one did not care to read. The following two variables implement a way to omit these followups, plus a way to auto-select the articles that you are most likely to be interested in, namely followups to articles that you have already read, or followups to articles you have posted yourself. The killing of uninteresting followups is done without entries in the kill file. The thread is killed if the root article of the thread is a followup itself, and the subject is not selected. The default of this patch to nn is no changed behaviour. Two new variables are introduced, can be set in the .nn/init file: ignore-re (boolean, default false) If set, articles with subjects already seen in a previous invocation of nn or another newsreader - and not auto-selected - are automatically killed. auto-select-rw (boolean, default false) If set, a subject of an article read or posted is automatically used for subsequent auto-selecting (if not already selected). Title: Fixes for non ANSI SunOS cc From: Paul Eggert NN 6.4.19gamma's term.c contains a cast that assumes that the compiler is an ANSI C compiler. This doesn't work in K&R compilers (e.g. SunOS 4.1.2 with bundled cc). Title: void vs int mixups in return values From: Paul Eggert Here's a patch to NN 6.4.19gamma's term.c that fixes a problem where it returns a value in a void function. Title: After you `:set record xxx', `:set' misdisplays mail-record From: Paul Eggert The :set listing were incorrectly displaying mail-record and news-record as always unset. Title: Improvement/bug fix to nn: archiving full articles using NNTP From: Fred Hucht If you set nnmaster to archive articles while in NNTP mode, it only archived the headers... Title: Add a flag to show Originator From: Jim Jagielski Add's support for 'J' in news-header to display the "Originator:" Title: Catch short term error conditions to prevent nnmaster aborting. From: J.P.M. van der Jagt Prevent things like "400 Server load too high, try again later" from making nnmaster exit. Title: make nn 6.4 PL18 compile (and work ;-)) for hp_ux 8.0. From: Bart Muyzer Title: `nn -xXm' does't clear to end of line (NN 6.4.19gamma) From: Paul Eggert NN wasn't properly clearing to end-of-line after each output line. From: Paul Eggert Title: infelicities in NN 6.4.19gamma term.c The term.c code had some really bizzare conditional tests that were uncessesary, and some were obvious typos. This patch doesn't fix any real bugs, but it removes the needless code. It also introduces a few optimizations in critical places: (This is a particularly big win on most Sparcs, which don't have integer division.) From: Peter Wemm Title: silly bug in nn-use-elm script contrib/nn-use-elm had a leading #/bin/sh instead of #!/bin/sh This causes invocation of csh on many systems... (including mine :-) From: Peter Wemm Title: long standing bug in sort.c non-related articles were often being associated in subject-match-parts mode. This tries to make it less likely. From: Steve Grimm Title: prefix.c fix, HAVE_TRUNCATE for SunOS. prefix.c change #ifdef NNTP to #ifdef INEWS_PATH SunOS has truncate. From: Michael Masterson Title: account.c is broken The code uses a structure before defined if accounting is enabled. Update s-sys5-4.h. m-i80386.h needs work. From: Peter Wemm Title: improve m-i80386.h m-i80386.h didn't deal with network byte order very well. As far as I can tell, if you want network byte order, then you probably want it for NFS sharing. In which case you have TCP/IP. Use it's routines. From: Steven Grimm Title: Patch for preview mode display glitch the 'xn' termcap flag is conflicting with clrline() and long subject lines, causing the following line to be cleared. From: Peter Wemm Title: fix aux.sh aux.sh was failing when using the nn_elm script from the FAQ. In the process of reformatting it, I found the bug, a missing semicolon. It would fail whenever mailer-pipe-input was unset. From: Peter Wemm Title: The Biggie! Merge in NOV code! Well, this is the big one. #ifdef NOV in config.h, and it even works over NNTP. The some of the hacks to get NN to use the libnov code is pretty baroque, but necessary in order to maintain compatability. NN's internals are *very* dependant on the nnmaster database and it does not particularly appreciate the change. If you enable NOV, all bets are off. :-) Most of the code is from Geoff Collyer although the NNTP mods and other tweaks are from others whom I cannot identify.. This is very much a Stop-Gap solution. From: Michael Masterson Title: auto-select-rw and maybe ignore-re kill folder mode NN takes a SEGV while reading a folder with these enabled. [Peter: I'm unable to reproduce the bug....] From: Steven Grimm Title: Patch for ignore-re and -x Here's a tiny patch so that ignore-re is turned off when you're using the -x flag, or when you go into a group to read old articles. I think this is better than the way it currently works; currently, if you have ignore-re set on a group, there's no way (that I can discover) to get nn to show you an article whose thread's initial article has expired. From: Peter Wemm Title: folder move vs. auto-select-rw Basically, patch up Michael Masterson's problem. Although I couldn't core dump (lucky me..), I did notice that if I was reading a folder, the articles I read were added to the kill file. I've added a temporary override to auto-select-rw that makes it ignored in a folder. This should fix Michael's problem. From: Peter Wemm Title: 6.4.19 is no more. So as to distinguish out butchering from Kim's work, we've decided on a revision "bump". It's NN-6.5 now. From: Peter Wemm Title: gcc -Wall cleanup It now compiles a lot more cleanly under -Wall, but not 100% clean.. :-) I found some sneaky bugs in the menu code and the init file parser in the process. From: Peter Wemm Title: Merge in Article search from Andy Burgess There are numerous collisions with the NOV code, and it does not work with digests, etc, etc. It is *not* enabled in config.h by default, as It has not revieved enough testing by the group. It's also not documented in the manual... (Volunteers?) Also, don't enable this if you read via nntp... Your server will hate you... -- 6.5.0 beta goes out Aug 31 1993 -- From: Paul Eggert Title: The code still says 6.4 in various files, including the documentation. From: Paul Eggert Title: Bogus prototypes for syslog() functions are wrong. From: Paul Eggert Title: NO_X_NEWSREADER should be defined by default. [Peter: Awww! If Tin can do it, why can't I? :-) ] From: Paul Eggert Title: Redeclaration of catch_winch on machines with RESIZING From: Paul Eggert Subject: nn-6.5.0 mishandles time zone changes near short month boundaries NN 6.5.0 mishandles non-GMT time zones near short month boundaries, which causes articles to be incorrectly sorted by date. For example, pack_date("30 Sep 1993 23:00 -0700") and pack_date("1 Oct 1993 06:00 GMT") should yield the same value, but in NN 6.5.0 the former yields 3614760 while the latter yields 3616200. This is becoming more and more of a problem as more people use non-GMT time zones in their news. Here is a patch. From: Toivo Pedaste Title: Missing 'static' on some functions db_init_group(), catch_winch(), grep_article() From: Steve Davies Title: signal prototypes misdeclared There were a few places where signal handlers were declared void, but in fact were sig_type. This was OK on machines that had the same definition, but not on those with sig_type == int. Also, truncate() is messed up.. From: Steve Grimm Title: tidy up config.h There were a few typos and errors in config.h-dist From: Peter Wemm Title: fix up prototype problems with truncate and gethostname It seems every system has a different idea on what they are supposed to be. I give up... NN has not got internal functions called nn_truncate and nn_gethostname, which are prototyped, and will call "the real thing" if it's available. It seems the safest way. From: Steve Davies Title: 6.5 NOV + NNTP cleanups NN + NNTP + NOV : db.c, global.h, nn.c, nntp.c - This patch fixes problems when NN is run on the same machine as the NNTP server. - When compiled with NNTP and NOV, a temp file was being created when the active, active.times, and .overview files were read from the NNTP server. This patch eliminates the creation of that temporary file. In db.c (readactfile, readtimfile, db_read_group), instead of reading from the temp file, the data is read directly from the NNTP server. The idea for this comes from Peter Orbaek (poe@daimi.aau.dk). This requires a simple patch to the nov sources (which probably should be applied by hand rather than running this file through patch): [NOTE: NN-6.5 contains a 'nov-lite' distribution with this patch applied and everything non-essential for NN removed. -Peter ] --- nov/libnov/oldlib.c Tue Sep 7 13:47:31 1993 +++ nov/libnov/lib.c Tue Sep 7 13:47:42 1993 @@ -213,6 +213,7 @@ } while ((line = fgetms(gp->g_stream)) != NULL) { + if (line[0] == '.') break; art = (struct novart *)malloc(sizeof *art); if (art == NULL || (prssts = prsovline(line, gp, art, prevart)) == PRSFAIL) { - The code in db_read_art for handling crossposts when NOV is defined does not handle the (hypothetical?) case where there are extra headers besides Xref: in the .overview file. It also doesn't work when cnews is used to maintain the .overview files, since cnews appears to use xref: instead of Xref: in the .overview file. A new routine "db_fixup_cross_postings" is added to db.c . This routine is derived from code by Charles Henrich and rmtodd@servalan.servalan.com. From: Peter Wemm Title: include a minimal nov distribution. Because we need a trivial patch to the libnov code which Geoff Collyer doesn't want to put in his code (and quite understandably because it's a "hack"), and I dont want 10,000 email messages saying "NOV + NNTP doesn't work" when they build it without patching because they never read intructions that tell them to patch it. :-) If anybody does this to me now because they've removed the patched version, I'll reply with a uuencoded 16 Meg kernel crash dump. (ie: a bunny dropping..) :-) From: Peter Wemm Title: Fix "Out of memory: cannot allocate -4 bytes" If NN could not determine the window size from the termcap or the TIOCGWINSZ ioctl's, it would try and malloc a negative buffer.. From: Peter Wemm Title: typo in nn_gethostname() hostname.c would not compile if HAVE_HOSTNAME defined. From: Jim Jagielski Title: nn -a0 botches display on some hosts There is some interaction between curses, tprintf, and terminfo. From: Jim Jagielski Title: Add new conf/s-aux3-0.h From: Robert.Stockton@ELROND.GANDALF.CS.CMU.EDU Title: nov code overrunning '.' on XOVER data when active is incorrect. If the active file says there is articles, but the .overview is empty, the novall() and novnext() miss the "eof" dot and hang forever. From: Toivo Pedaste Title: Explicit support for mouse under xterm This code can detect if running under xterm, and will enable mouse events if so. The mouse clicks are used for menu selection, reading, previewing and so on. It makes NN "almost" hands-free for most news reading tasks. From: Peter Wemm Title: Minor tweaks for mouse-code Because of a "feature" apparently in the SYSV termio driver and possibly the POSIX termios, NN was disabling raw mode during screen updates in order to enable xon-xoff flow control to work. This was causing character echoing of mouse events during screen redraws and looked rather ugly. This problem never occurs on older BSD systems with the sgtty style driver. This tweak causes NN to not switch off raw mode on Xterms when mouse is enabled.. Heck, if xterm needs flow control, something is very wrong... From: Tom Limoncelli Title: Possible memory leaks Purify is not at all happy with some of the things that the NOV code does. I dont think there is much of a problem, although I agree that it's messy. Basically, NN is allocating buffers for the hash tables and not freeing them at exit. As these are reused while NN travels from group to group, this is mostly static. NN does not free() these on exit and Purify complains. The invitation to fix this is open to all.. :-) From: Peter Wemm Title: major reorganisation of NOV code. Shift files from nov/* to the top level directory for radical simplification of compilation. Rationalise include files. Substitute some simple routines to reduce extra files needed from the NOV distibution. From: Peter Wemm Title: major repairs of above reorganisation. Well, the "simple" reorganisation turned out to be rather dramatic in it's affects. This cost weeks of debugging and nearly caused me to give up and reverse the last upmteen patches (I had neglected to do a CVS checkin before starting). It turned out to me a malloc problem. I *suspect* there is still a memory leak there, but it doesn't seem to show up on the process size after a lengthy reading session.. From: Ian Nicholls Title: NCR tower 32 tweaks. nn-6.5.0.b2 had troubles with include files. This helps a bit. From: Marc Boucher Title: additions to conf/s-sys5-4.h This adds some omissions to the config file. From: Marc Boucher Title: bzero/memset are out, clearobj is in... Following Kim's coding conventions, change a couple of memset()'s that I introduced to clearobj's. This looks a lot nicer now.. From: Michael Pins Title: b4 patch An nntp speedup, along with changing a number of chars back to ints so nn will actually work properly on a number of systems. nn-6.5.0.b5: From: mcm@gorgon.unx.sas.com (Mike Mitchell) Subject: Re: Problems with splitting digests Digests are now split under NOV. From: romain@sword.eng.pyramid.com (Romain Kang) Subject: NN hacks for NOV Major NOV hacks. Things should be rather faster now. Also, the addition of nnview for reading folders. From: Michael Pins General bug-fixing and patch cleanup. nn-6.5.0.b6: From lar3ry@zk3.dec.com Subject: OSF/1 (err, Digital Unix) patches Fixes for the Alpha and OSF/1. From: David Dyer-Bennet Subject: s-linux.h config file for linux From: jpc@hammer.msfc.nasa.gov (J. Porter Clark) Subject: libnov.c bug There's a bug in the code that causes .newsrc to be updated even though the corresponding articles aren't read. Swapped the order of the test. From: Jacob Martin Bohn Lorensen Subject: timeout problem with NOV and nntp When the server unexpectedly breaks the connection nn does not always discover this. This can be fixed by just re-opening the connection. From: Michael Pins General bug-fixing, patch cleanup and code cleanup. nn-6.5.0.b7: From: config files for AIX/RS6000, and a patch for AIX brain damage. From: Michael Pins Put accounting logs under LIB instead of DB, so DB isn't needed unless nnmaster is used. Pulled accounting #def's out of account.c into the new account.h so it's no longer necessary to edit a .c file. Fixed line counting in digests (used to claim one too many). Included the inst.sh file for the Apollo I've been using for years. Anyone running Domain/OS will want to use this instead of inst.sh. Yet more general clean-up. nn-6.5.0: Added account.h to account dependencies. Added freebsd config file. Hacked together some docs for nnview. Yet even more general clean-up. nn-6.5.1: Added netbsd config file. Fixed up s-template.h. SunOS doesn't have memmove (and likely neither do most older BSD derived systems). Early versions of Solaris don't have bcopy.... Added NO_MEMMOVE and used it to ifdef around these. Most (all?) can probably be memcpy, but until I have time to insure that the args don't overlap, I'll be paranoid. nn-6.5.2: Changed group_number to int32 so > 32767 groups works Fixed problem with long References: lines Fixed problem with large headers (seen mainly in nnview) Hacked in a workaround for sites that don't support LIST active.times nn will now see new groups properly More general cleanup of the code and minor bug fixes From: mtranle@theoden.intellicorp.com (Minh Tran-Le) nn was eating the first number from a LIST response workaround for M$ newsserver braindamage From: Nick.Holloway@alfie.demon.co.uk (Nick Holloway) fix for INN return message re: mail to moderator nn-6.5.3: Ripped out "LIST active.times" workaround. Typhoon will support it soon (likely before anyone reads this), and the workaround had bugs. Changed accounting so that time used outputs HH:MM instead of HH.MM (does anyone still use accounting?) Added a compile time option to turn on more headers to make the GNKSA people happy. The default behavior hasn't changed. From: Bruce Fisher The news spool directory was hardcoded in libnov.c, conflicting with NEWS_DIRECTORY from config.h From: Dale Talcott A new variable "cross_post_limit" to kill articles posted to more than cross_post_limit newsgroups. Default=0 (no limit). From: Dale Talcott bug in pack_date.c--computes leap year incorrectly From: Tom Kartes Killing the current article sometimes kicks you out of the newsgroup. From: Tom Kartes Saving an article into a file will escape some headerlines if you're using NOV and did not previously view this article. From: 5ar4ro$ri7@bogon.com (John Henders) a patch that adds S)ign [pgp] as an option to the posting menu From: "Maarten Carels" new config.h option to have a different filename for the overview files nn-6.5.4: Changed defaults in config.h to more sane ones. Fixed a bug in libnov.c so NOV_FILENAME actually works. Fixed a nasty bug that would occasionally cause nn to go ballistic when ripping a digest if the overview data wasn't quite right. nn now compiles cleanly on FreeBSD. nn-6.5.5: Changed answer.c to use 4-digit year (making it RFC-822 non-compliant, but son-of-rfc-1036 compliant). Back-ported pack-date.c from nn7. It fixes a number of minor bugs, and deals with Y2K brain-damage from other software. nn-6.5.6: Fixed two new date sorting bugs. Oops. Workaround for new brain-damage in Solaris7 include files. nn-6.6: Fixed large uid/gid problems Fixed Cc (it actually works now) changed X-Newsreader to User-Agent changed -ltermcap to -lncurses in s-linux.h, as they broke it again the date parser now translates 101 -> 2001 (yes, I'm still seeing this) backported some changes to decode from nn7 From: Dave Olson added NNTP_AUTH support added ssl support nn now posts internally via NNTP (NNTP only) all functions of aux now internal (NNTP only) added support for multiple nntp-server/newsrc/nn-directory From: Charles Lindsey aliased groups didn't work correctly with NOV/no-NNTP From: jpc@suespammers.org (J. Porter Clark) added E = save-header-only function nn-6.6.1: online help files will now get installed properly. now compiles if nntp isn't defined. dumped core on posting if news-record had a bad path. Fixed. no longer segfaults if user over quota. no longer segfaults if NNTPSERVER is invalid. will now search path for executables. passwords longer than 8 chars now work from Solaris. nn will silently ignore any non-digests with [D,d]igest in the subject, and apparently has for quite some time. set the default for also_full_digest to true as workaround. nn has long gotten all the articles in a group when any number of extra articles was requested. Fixed. will now reconnect if it times out during a post. nn-6.6.2: nn wouldn't compile if NNTP was defined and NOV wasn't. Fixed. nn-6.6.3: cpp is broken on gcc-3.x. Worked around. ripped out SSL support, as it was apparently fatally broken. removed nnmail and HAVE_ROUTING. No longer needed. converted from varargs to stdargs. added MacOS X support nn will now attempt to discover the domain if not defined. will now reconnect, even to broken servers that don't send 503 From: Bob Hain use full_name instead of pw_gecos in gen_frompath nn-6.6.4: added iso-8859-15 to chset increased END_OF_LIST (some servers have very large article numbers) changed ospeed to nnspeed to avoid namespace conflicts always rebuild binary killfile when starting nn(NOV) if the ORGANIZATION environment variable is set, nn will use it the editor again accepts arguments, and starts after the headers added new variable use-editor-line, for the above From: Jacques A. Vidrine nn has a remote security hole. Fixed. From: Felicia Neff (fn@panix.com) the variables for nn_exitmsg when nn is already running were swapped nn-6.6.5: added -lcurses to macosx TERMLIB nnview doesn't need to talk to the NNTPSERVER removed last vestige of sys_errlist From: J.K.Wight@newcastle.ac.uk cp is undefined if DOMAIN is defined and HIDDENNET isn't. Fixed. From: Neil W Rickert nn segfaults if it reconnects during initial authentication. Fixed. From: Jeffery Small nn sends a bogus [post|follow] failed message when using cnews and !nntp. Fixed. (re-added two options to config.h) nn-6.7alpha: Fixed some portability problems with old OSs. First pass at starting to clean up the code. Ick, what a mess. nnmaster and nnacct appear to have been broken during nn-6.6, fixed. The name length displayed is now based upon the width of the screen. nn will now correctly parse From: lines that contain () For example: From: "Jesse James (home)" used to display as "home", the new behavior will display it as "Jesse James". However, this may affect killfiles that depend upon the old behavior. To use the old behavior, set the new run-time variable 'old-packname' to true. The hold command from the post/mail menu failed if the users nn_directory wasn't a local filesystem. Fixed. nnpost didn't check to see if there was a held message. Fixed. Posting from nnview failed due to the nntp connection not being setup. Fixed. When replying with a held message, nn would use the post menu. Fixed. Header check macro is now case insensitive for both news and folders. Added GPG support, which comes with a new run-time variable 'sign-type', and a compile-time variable SIGN_TYPE to set the default to either pgp or gpg. nn will now print the version before connecting to the newsserver internalized :bug information for more accurate reporting nn will now look in /etc/resolv.conf for a domain if DOMAIN isn't defined and hostname isn't a FQDN. new command ":show config" will show the compile-time definitions. From: Henning Schmiedehausen If query-signature wasn't set, nn ignored signature-append-post. Fixed. From: Tim nntp-user and nntp-password should be settable in the init file. Fixed. From: Steve Rumble nn segfaults if a client is unable to resolve it's own name. Fixed. From: flaps@dgp.toronto.edu (Alan J Rosenthal) nn should trim long References: lines. Added patch to do so. nn-6.7beta: Cleaned up some old (unused) variables. More general code cleanup. Updated the manpages to include new variables. Updated INSTALLATION and other docs so they're (roughly) current. If new-group-action is set to 0 in init, nn will no longer get active.times. (Yes, this is what the docs have claimed was always true, it actually works now.) This can cut startup time in half on slower links. nn-6.7.0: And yet even more general code cleanup. rearranged startup code so macros and keymaps work in the init file. only query-signature if .signature exists fixed some portability problems with NeXTstep-3.3 and NetBSD nn-6.7.1: declared main to be int in usercheck.c and cvt-help.c changed a few refs to nn-6.5 and nn-6.6 to nn-6.7 clear display before printing "No News" so as to not overwrite nn-6.7.2: changed TERMIO to TERMIOS in s-linux.h execute needs to exit, not return, if exec fails callable programs (i.e pager) should accept arguments nn-6.7.3: Fixed the Makefile so recent versions of gcc no longer choke. The -N option appears to have been broken since 6.6.0. Fixed. nn-6.7.4: Added a new variable "nntp_forceauth" it can be set either in the init file or on the command-line in order to force nn to authenticate, even when the nntp server doesn't request it % nn nntp-forceauth=true Documented nntp-forceauth, nntp-user, and nntp-password Disabled :debug in FreeBSD as sbrk is no longer supported Changed getline() to nn_getline() to avoid namespace collisions added linux to list of systems that need sys/ioctl.h not stropts.h changed logfle chmod to 644 changed MAILX to /usr/bin/mailx for linux various code cleanup fixed many typos/misspelled words use xmakefile.gcc to work around gmake brain damage worked around gcc's -fno-common default changed NEWS_HEADER_BUFFER to 8192 to work around ever growing email headers until I have time to rewrite header parsing nn-6.7.4/global.c0000644000175000017500000006166415213127370013173 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2026 Michael T Pins. All rights reserved. * * Global declarations and auxiliary functions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "global.h" #include "execute.h" #include "nn.h" #include "patchlevel.h" #include "nn_term.h" #ifdef HAVE_SYSLOG #include #endif /* HAVE_SYSLOG */ /* global.c */ static sig_type catch_keyboard(int n); static sig_type catch_pipe(int n); static sig_type catch_suspend(int n); static void mail_sys_error(char *err, int isfatal); static void mem_error(int t, int32 bytes); #ifdef NEED_STRERROR char *strerror(int); #endif char *home_directory; char *nn_directory; /* ~/.nn as default */ char *news_directory; /* /usr/spool/news */ char *news_lib_directory; /* /usr/lib/news */ char *lib_directory; /* /usr/local/lib/nn */ char *master_directory; /* = lib */ char *help_directory; /* = lib/help */ char *bin_directory = BIN_DIRECTORY; char *db_directory; /* /usr/spool/nn or NEWS_DIR/.nn */ char *db_data_directory; /* ..../DATA or undefined */ int db_data_subdirs = 0; /* set if DATA/[0-9]/ exist */ char *news_active; /* NLIB/active or DB/ACTIVE */ char *pager; char *organization; char *log_file; /* = lib/Log */ char *log_entry_filter = NULL; char *temp_file; #ifndef TMP_DIRECTORY #define TMP_DIRECTORY "/var/tmp" #endif char *tmp_directory = TMP_DIRECTORY; char version_id[100]; int use_nntp = 0; /* bool: t iff we use nntp */ uid_t user_id; static gid_t group_id; int process_id; int who_am_i; int dont_write_console = 0; int mail_errors_mode = 2; extern int errno; extern char *getlogin(), *getenv(const char *); #ifdef HAVE_MULTIGROUP #ifndef NGROUPS #include #endif static uid_t user_eid; static int ngroups; static gid_t gidset[NGROUPS]; static int in_grplist(gid_t gid) { int n; if (gid == group_id) return 1; for (n = 0; n < ngroups; ++n) if (gidset[n] == gid) return 1; return 0; } #define group_access(gpid) in_grplist(gpid) #else #define group_access(gid) ((gid) == group_id) #endif /* signal handler interface */ int s_hangup = 0; /* hangup signal */ int s_keyboard = 0; /* keyboard interrupt */ int s_pipe = 0; /* broken pipe */ int s_redraw = 0; /* redraw signal (if job control) */ #ifdef RESIZING int s_resized = 0; /* screen resized */ #endif #ifdef FAKE_INTERRUPT #include jmp_buf fake_keyb_sig; int arm_fake_keyb_sig = 0; char fake_keyb_siglist[NSIG]; #endif sig_type catch_hangup(int n) { s_hangup = 1; signal(n, SIG_IGN); #ifdef FAKE_INTERRUPT if (fake_keyb_siglist[n] && arm_fake_keyb_sig) longjmp(fake_keyb_sig, 1); #endif } static sig_type catch_keyboard(int n) { #ifdef RESET_SIGNAL_WHEN_CAUGHT signal(n, catch_keyboard); #endif #ifdef FAKE_INTERRUPT if (fake_keyb_siglist[n] && arm_fake_keyb_sig) longjmp(fake_keyb_sig, 1); #endif s_keyboard++; } static sig_type catch_pipe(int n) { s_pipe++; #ifdef RESET_SIGNAL_WHEN_CAUGHT signal(n, catch_pipe); #endif #ifdef FAKE_INTERRUPT if (fake_keyb_siglist[n] && arm_fake_keyb_sig) longjmp(fake_keyb_sig, 1); #endif } #ifdef HAVE_JOBCONTROL static sig_type catch_suspend(int n) { s_redraw++; #ifdef RESET_SIGNAL_WHEN_CAUGHT signal(n, catch_suspend); #endif suspend_nn(); #ifdef FAKE_INTERRUPT if (fake_keyb_siglist[n] && arm_fake_keyb_sig) { #ifdef RESIZING s_resized++; #endif longjmp(fake_keyb_sig, 1); } #endif } #endif int init_global(void) { #ifdef NOV char *nov_id = " (NOV)"; #else char *nov_id = ""; #endif #ifdef FAKE_INTERRUPT for (i = 0; i < NSIG; i++) fake_keyb_siglist[i] = i == 2 ? 1 : 0; #endif if (who_am_i != I_AM_NN) { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } signal(SIGTERM, catch_hangup); signal(SIGHUP, catch_hangup); signal(SIGPIPE, catch_pipe); signal(SIGALRM, SIG_IGN); #ifdef SIGPWR signal(SIGPWR, catch_hangup); #endif #ifdef CONFIG_NUM_IN_VERSION sprintf(version_id, "%s.%s #%d%s", RELEASE, PATCHLEVEL, #include "update.h" ,nov_id); #else sprintf(version_id, "%s.%s%s", RELEASE, PATCHLEVEL, nov_id); #endif user_id = getuid(); #ifdef HAVE_MULTIGROUP ngroups = getgroups(NGROUPS, gidset); /* Get users's group set */ #endif group_id = getegid(); user_eid = geteuid(); process_id = getpid(); #ifdef CLIENT_DIRECTORY lib_directory = CLIENT_DIRECTORY; #else lib_directory = LIB_DIRECTORY; #endif #ifdef NEWS_DIRECTORY news_directory = NEWS_DIRECTORY; #else news_directory = "/usr/spool/news"; #endif #ifdef DB_DIRECTORY db_directory = DB_DIRECTORY; #else db_directory = mk_file_name(lib_directory, ".nn"); #endif #ifdef ACCOUNTING if (who_am_i == I_AM_ACCT) return 0; #endif #ifdef DB_DATA_DIRECTORY db_data_directory = DB_DATA_DIRECTORY; #else #ifdef DB_DIRECTORY db_data_directory = mk_file_name(db_directory, "DATA"); #else db_data_directory = NULL; #endif #endif #ifndef DB_LONG_NAMES if (db_data_directory != NULL) db_data_subdirs = file_exist(relative(db_data_directory, "0"), "dx"); #endif #ifdef NEWS_LIB_DIRECTORY news_lib_directory = NEWS_LIB_DIRECTORY; #else news_lib_directory = "/usr/lib/news"; #endif /* this may later be changed by nntp_check */ news_active = mk_file_name(news_lib_directory, "active"); #ifdef MASTER_DIRECTORY master_directory = MASTER_DIRECTORY; #else master_directory = LIB_DIRECTORY; #endif #ifdef HELP_DIRECTORY help_directory = HELP_DIRECTORY; #else help_directory = mk_file_name(lib_directory, "help"); #endif #ifdef LOG_FILE log_file = LOG_FILE; #else log_file = mk_file_name(LIB_DIRECTORY, "Log"); #endif if (who_am_i == I_AM_MASTER || who_am_i == I_AM_SPEW) return 0; signal(SIGINT, catch_keyboard); signal(SIGQUIT, catch_keyboard); #ifdef HAVE_JOBCONTROL signal(SIGTSTP, catch_suspend); #endif if ((home_directory = getenv("HOME")) == NULL) nn_exitmsg(1, "No HOME environment variable"); if ((pager = getenv("PAGER")) == NULL) pager = DEFAULT_PAGER; if (!nn_directory) /* if not set on command line */ nn_directory = mk_file_name(home_directory, ".nn"); organization = getenv("ORGANIZATION"); return 0; } int init_global2(void) { if (who_am_i != I_AM_ADMIN && !file_exist(nn_directory, "drwx")) { if (who_am_i != I_AM_NN) return -1; if (mkdir(nn_directory, 0755) < 0) nn_exitmsg(1, "Cannot create %s directory", nn_directory); return 1; } return 0; } void new_temp_file(void) { static char buf[FILENAME]; static char *temp_dir = NULL; if (temp_dir == NULL) { if ((temp_dir = getenv("TMPDIR")) == NULL) temp_dir = tmp_directory; /* just to make test above false */ else tmp_directory = temp_dir; } sprintf(buf, "%s/nn.XXXXXX", tmp_directory); mktemp(buf); temp_file = buf; } FILE * open_file(char *name, int mode) { FILE *f = NULL; int fd; if ((mode & DONT_CREATE) && !file_exist(name, (char *) NULL)) return NULL; switch (mode & 0x0f) { case OPEN_READ: f = fopen(name, "r"); break; case OPEN_UPDATE: /* f = fopen(name, "r+"); -- not reliable on many systems (sigh) */ if ((fd = open(name, O_WRONLY)) >= 0) { if ((f = fdopen(fd, "w")) != NULL) return f; close(fd); } /* FALLTHROUGH */ case OPEN_CREATE: f = fopen(name, "w"); break; case OPEN_APPEND: f = fopen(name, "a"); break; case OPEN_CREATE_RW: f = fopen(name, "w+"); /* not safe on all systems -- beware */ break; default: sys_error("Illegal mode: open_file(%s, 0x%x)", name, mode); } if (f) { if (mode & OPEN_UNLINK) unlink(name); return f; } if ((mode & MUST_EXIST) == 0) return NULL; sys_error("Cannot open file %s, mode=0x%x, errno=%d", name, mode, errno); return NULL; } FILE * open_file_search_path(char *name, int mode) { FILE *f; if (name == NULL) return NULL; if (*name == '/') return open_file(name, mode); f = NULL; if (!use_nntp) f = open_file(relative(news_lib_directory, name), OPEN_READ); if (f == NULL) f = open_file(relative(lib_directory, name), OPEN_READ); if (f == NULL) f = open_file(relative(db_directory, name), OPEN_READ); return f; } int fgets_multi(char *buf, int size, register FILE * f) { register char *s = buf; register int c, n = size; while (--n > 0) { c = getc(f); if (c == '\\') { if ((c = getc(f)) == NL) continue; *s++ = '\\'; if (--n < 0) break; } if (c == EOF) { *s = NUL; return s != buf; } if (c == NL) { *s = NUL; return 1; } *s++ = c; } buf[30] = NUL; sys_error("Line too long \"%s...\" (max %d)", buf, size); return 0; } /* * relative -- concat directory name and file name */ char * relative(char *dir, char *name) { static char concat_path[FILENAME]; sprintf(concat_path, "%s/%s", dir, name); return concat_path; } char * mk_file_name(char *dir, char *name) { char *buf; buf = newstr(strlen(dir) + strlen(name) + 2); sprintf(buf, "%s/%s", dir, name); return buf; } char * home_relative(char *dir) { if (dir) { if (*dir == '/') return copy_str(dir); else { if (*dir == '~' && *++dir == '/') dir++; return mk_file_name(home_directory, dir); } } return NULL; } char * substchr(char *str, char c1, char c2) { char *p; if ((p = strchr(str, c1)) != NULL) *p = c2; return p; } char * copy_str(char *str) { char *new; new = newstr(strlen(str) + 1); if (new) strcpy(new, str); return new; } time_t m_time(FILE * f) { struct stat st; if (fstat(fileno(f), &st) < 0) return 0; return st.st_mtime; } time_t file_exist(char *name, char *mode) { static struct stat statb; int mask; if (name != NULL && stat(name, &statb)) return 0; if (mode == NULL) return statb.st_mtime; if (statb.st_uid == user_eid) mask = 0700; else if (group_access(statb.st_gid)) mask = 0070; else mask = 0007; while (*mode) { switch (*mode++) { case 'd': if ((statb.st_mode & S_IFMT) == S_IFDIR) continue; errno = ENOTDIR; return 0; case 'f': if ((statb.st_mode & S_IFMT) == S_IFREG) continue; if ((statb.st_mode & S_IFMT) == 0000000) continue; if ((statb.st_mode & S_IFMT) == S_IFDIR) { errno = EISDIR; return 0; } break; case 'r': if (statb.st_mode & mask & 0444) continue; break; case 'w': if (statb.st_mode & mask & 0222) continue; break; case 'x': if (statb.st_mode & mask & 0111) continue; break; } errno = EACCES; return 0; } /* all modes are ok */ return statb.st_mtime; } /* * cmp_file: compare two files * * Returns 0: The files are identical. * 1: The files are different. * 2: An error occurred. */ int cmp_file(char *work, char *copy) { int fd1, fd2; struct stat sb1, sb2; u_char *p1, *p2; off_t len1, len2; if ((fd1 = open(work, O_RDONLY, 0)) < 0) { msg("%s %s", work, strerror(errno)); return (2); } if ((fd2 = open(copy, O_RDONLY, 0)) < 0) { msg("%s %s", copy, strerror(errno)); return (2); } if (fstat(fd1, &sb1)) { msg("%s %s", work, strerror(errno)); return (2); } if (fstat(fd2, &sb2)) { msg("%s %s", copy, strerror(errno)); return (2); } len1 = sb1.st_size; len2 = sb2.st_size; if (len1 != len2) return (1); if ((p1 = (u_char *) mmap(NULL, (size_t) len1, PROT_READ, MAP_SHARED, fd1, (off_t) 0)) == (u_char *) - 1) { msg("work=%s %s", work, strerror(errno)); sleep(5); return (2); } if ((p2 = (u_char *) mmap(NULL, (size_t) len2, PROT_READ, MAP_SHARED, fd2, (off_t) 0)) == (u_char *) - 1) { msg("copy=%s %s", copy, strerror(errno)); sleep(5); return (2); } for (; len1--; ++p1, ++p2) { if (*p1 != *p2) return (1); } return (0); } /* * copy_file: copy (or append) src file to dest file. * * Returns number of characters copied or an error code: * -1: source file not found * -2: cannot create destination * -3: write error */ int copy_file(char *src, char *dest, int append) { register FILE *s, *d; register int n; register int c; s = open_file(src, OPEN_READ); if (s == NULL) return -1; d = open_file(dest, append ? OPEN_APPEND : OPEN_CREATE); if (d == NULL) { fclose(s); return -2; } n = 0; while ((c = getc(s)) != EOF) { putc(c, d); n++; } fclose(s); if (fclose(d) == EOF) { if (!append) unlink(dest); return -3; } return n; } /* * move_file: move old file to new file, linking if possible. * * The third arg determines what is acceptable if the old file cannot be * removed after copying to the new file: * 0: must remove old, else remove new and fail, * 1: must remove or truncate old, else remove new and fail, * 2: just leave old if it cannot be removed or truncated. * * Returns positive value for success, negative for failure: * 0: file renamed (link) * 1: file copied, old removed * 2: file copied, but old file is only truncated. * 3: file copied, but old file still exist. * -1: source file not found * -2: cannot create destination * -3: write error * -4: cannot unlink/truncate old * -5: cannot unlink new * -6: cannot link old to new * -9: messy situation: old and new linked on return (cannot happen?) */ int move_file(char *old, char *new, int may_keep_old) { int n; if (file_exist(new, (char *) NULL)) { if (file_exist((char *) NULL, "d")) return -5; if (unlink(new) < 0) /* careful - new may be directory ? */ switch (errno) { case ENOENT: break; case EACCES: if (file_exist((char *) NULL, "w")) goto do_copy; default: return -5; } } if (link(old, new) < 0) switch (errno) { case EACCES: /* can just as well try to copy */ case EXDEV: goto do_copy; default: return -6; } if (unlink(old) == 0) return 0; /* we were able to link but not unlink old */ /* remove new, and attempt a copy instead */ if (unlink(new) < 0) return -9; /* cannot happen? */ do_copy: if ((n = copy_file(old, new, 0)) < 0) return n; if (unlink(old) == 0) return 1; if (may_keep_old) if (n == 0 || nn_truncate(old, (off_t) 0) == 0) return 2; if (may_keep_old == 2) return 3; unlink(new); return -4; } int save_old_file(char *name, char *suffix) { char buf[FILENAME]; sprintf(buf, "%s%s", name, suffix); return move_file(name, buf, 0); } static int enter_log(int type, char *fmt, va_list ap) { FILE *log; char buf[512]; vsprintf(buf, fmt, ap); /* cannot use relative: one of the args may be generated by it */ log = open_file(log_file, OPEN_APPEND); if (log == NULL) return 0; fprintf(log, "%c: %s (%s): %s\n", type, date_time((time_t) 0), user_name(), buf); fclose(log); return 1; } static void mail_sys_error(char *err, int isfatal) { FILE *f; char cmd[FILENAME * 2]; switch (mail_errors_mode) { case 0: return; case 1: if (!isfatal) return; default: break; } #ifdef FATAL_ERROR_MAIL_CMD strcpy(cmd, FATAL_ERROR_MAIL_CMD); #else #ifdef MAILX sprintf(cmd, "%s -s 'nnmaster %s' %s", MAILX, isfatal ? "fatal error" : "warning", OWNER); #else sprintf(cmd, "mail %s", OWNER); #endif #endif if ((f = popen(cmd, "w")) == NULL) return; fprintf(f, "nnmaster %s\n\n%system error:\n%s\n", isfatal ? "terminated" : "warning", isfatal ? "Fatal s" : "S", err); pclose(f); } void sys_error(char *fmt,...) { va_list ap; int rval; char buf[512]; #ifndef HAVE_SYSLOG FILE *f; #endif va_start(ap, fmt); rval = enter_log('E', fmt, ap); va_end(ap); va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); if (who_am_i == I_AM_MASTER) { mail_sys_error(buf, 1); if (dont_write_console) nn_exit(7); #ifndef HAVE_SYSLOG f = open_file("/dev/console", OPEN_CREATE); if (f == NULL) nn_exit(8); fprintf(f, "\n\rNNMASTER FATAL ERROR\n\r%s\n\n\r", buf); fclose(f); #else /* HAVE_SYSLOG */ openlog("nnmaster", LOG_CONS, LOG_DAEMON); syslog(LOG_ALERT, "%s", buf); closelog(); #endif /* HAVE_SYSLOG */ nn_exit(7); } if (rval) nn_exitmsg(1, "%s", buf); else nn_exitmsg(1, "Can't open log file!\n%s", buf); } /* * sys_warning: like sys_error but MASTER will return with -1 * instead of exit. Clients still terminate! */ int sys_warning(char *fmt,...) { va_list ap; int rval; char buf[512]; #ifndef HAVE_SYSLOG FILE *f; #endif static char *last_err = NULL; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); if (last_err != NULL) { if (strcmp(last_err, buf) == 0) return -1; free(last_err); } last_err = copy_str(buf); va_start(ap, fmt); rval = enter_log('R', fmt, ap); va_end(ap); if (who_am_i != I_AM_MASTER) { if (rval) nn_exitmsg(1, "%s", buf); else nn_exitmsg(1, "Can't open log file!\n%s", buf); } mail_sys_error(buf, 0); if (dont_write_console) return -1; #ifndef HAVE_SYSLOG if ((f = open_file("/dev/console", OPEN_CREATE)) != NULL) { fprintf(f, "\n\rNNMASTER WARNING\n\r%s\n\n\r", buf); fclose(f); } #else /* HAVE_SYSLOG */ openlog("nnmaster", LOG_CONS, LOG_DAEMON); syslog(LOG_ALERT, "%s", buf); closelog(); #endif /* HAVE_SYSLOG */ return -1; } int log_entry(int type, char *fmt,...) { va_list ap; int rval; va_start(ap, fmt); rval = enter_log(type, fmt, ap); va_end(ap); return rval; } char * user_name(void) { static char *user = NULL; struct passwd *pw; if (who_am_i == I_AM_MASTER) return "M"; if (who_am_i == I_AM_EXPIRE) return "X"; if (user == NULL) { user = getlogin(); if (user != NULL && *user != NUL) goto out; pw = getpwuid(user_id); if (pw != NULL && pw->pw_name[0] != NUL) { user = copy_str(pw->pw_name); goto out; } user = getenv("LOGNAME"); if (user != NULL && *user != NUL) goto out; user = getenv("USER"); if (user != NULL && *user != NUL) goto out; user = "?"; } out: return user; } time_t cur_time(void) { time_t t; time(&t); return t; } char * date_time(time_t t) { char *str; if (t == (time_t) 0) t = cur_time(); str = ctime(&t); str[16] = 0; return str + 4; } char * plural(long n) { return n != 1 ? "s" : ""; } /* * memory management */ /* #define MEM_DEBUG *//* trace memory usage */ static void mem_error(int t, int32 bytes) { char buf[200]; if (t == 1) { sprintf(buf, "Alloc failed: unsigned too short to represent %ld bytes", (long) bytes); } else { sprintf(buf, "Out of memory - cannot allocate %ld bytes", (long) bytes); } sys_error(buf); } char * mem_obj(unsigned size, int32 nelt) { unsigned n; char *obj; n = nelt; if (n != nelt) mem_error(1, nelt); obj = calloc(n, size); #ifdef MEM_DEBUG printf("CALLOC(%u,%u) => %lx\n", n, size, (long) obj); #endif if (obj == NULL) mem_error(2, (int32) (size * nelt)); return obj; } char * mem_str(int32 nelt) { unsigned n; char *obj; n = nelt; if (n != nelt) mem_error(1, nelt); obj = malloc(n); #ifdef MEM_DEBUG printf("MALLOC(%u) => %lx\n", n, (long) obj); #endif if (obj == NULL) mem_error(2, nelt); return obj; } char * mem_resize(char *obj, unsigned size, int32 nelt) { unsigned n; char *obj1; if (obj == NULL) return mem_obj(size, nelt); nelt *= size; n = nelt; if (n != nelt) mem_error(1, nelt); obj1 = realloc(obj, n); #ifdef MEM_DEBUG printf("REALLOC(%lx, %u) => %lx\n", (long) obj, n, (long) obj1); #endif if (obj1 == NULL) mem_error(2, (int32) size); return obj1; } void mem_clear(register char *obj, unsigned size, register int32 nelt) { nelt *= size; while (--nelt >= 0) *obj++ = NUL; } void mem_free(char *obj) { #ifdef MEM_DEBUG printf("FREE(%lx)\n", (long) obj); #endif if (obj != NULL) free(obj); } #ifndef HAVE_MKDIR mkdir(char *path, int mode) { char command[FILENAME * 2 + 20]; sprintf(command, "{ mkdir %s && chmod %o %s ; } > /dev/null 2>&1", path, mode, path); return system(command) != 0 ? -1 : 0; } #endif int nn_truncate(char *path, off_t len) { #ifdef HAVE_TRUNCATE /* Easy... we already have it... */ return truncate(path, len); #else /* HAVE_TRUNCATE */ int fd; #ifndef O_TRUNC struct stat st; #endif if (len != 0) sys_error("nn_truncate(%s,%ld): non-zero length", path, (long) len); #ifdef O_TRUNC fd = open(path, O_WRONLY | O_TRUNC); #else if (stat(path, &st) < 0) return -1; fd = creat(path, st.st_mode & 07777); #endif if (fd < 0) return -1; close(fd); return 0; #endif } /* Should really have some sort of configure file and use strdup().. */ char * strsave(char *s) { int l; char *buf = NULL; if (s) { l = strlen(s); buf = malloc(l + 1); if (buf) { if (l) strcpy(buf, s); else buf[0] = '\0'; } } return buf; } char * str3save(char *s1, char *s2, char *s3) { int l; char *buf; if (!s1) s1 = ""; if (!s2) s2 = ""; if (!s3) s3 = ""; l = strlen(s1) + strlen(s2) + strlen(s3); buf = malloc(l + 1); if (buf) { strcpy(buf, s1); strcat(buf, s2); strcat(buf, s3); } return buf; } /* * This quick function is a hack of a replacement for fgets, but is * unlimited in line length. It returns a pointer to a buffer which * just happens to be malloc()ed and is reused next time. IE: you had * best strdup() it. * * This was inspired by an article I saw in alt.sources but forgot to * save. * * -Peter Wemm */ static char *buf = NULL; /* buffer for line */ static int size = 0; /* size of buffer */ #define INITIAL_CHUNK 256 /* Initial malloc size */ #define GROW_CHUNK 256 /* extend with realloc in units if this */ /* * Get a LONG line. * Returns a pointer to a '\0' terminated string which will be * overwritten on the next call. * Returns NULL if error or eof, or if nothing could be read at all. * * Note: It does not react very well to '\0' characters in the byte stream */ char * fgetstr(FILE * f) { int len; /* # of chars stored into buf before '\0' */ char *s; if (size == 0 && buf == NULL) { size = INITIAL_CHUNK; buf = malloc(size); if (buf == NULL) { size = 0; nn_exitmsg(50, "cant allocate initial chunk\n"); return NULL; } clearobj(buf, size, 1); } len = 0; while (fgets(buf + len, size - len, f) != NULL) { len += strlen(len + buf); if (len > 0 && buf[len - 1] == '\n') break; /* the whole line has been read */ size += GROW_CHUNK; s = realloc(buf, size); if (!s) { /* Malloc failure */ size = 0; /* panic... */ buf = NULL; nn_exitmsg(51, "cant realloc chunk\n"); return NULL; } buf = s; clearobj(buf + len, size - len, 1); } if (len == 0) { return NULL; /* nothing read (eof or error) */ } /* For when we are reading from a NNTP stream. */ if (len > 1 && buf[len - 1] == '\n') --len; if (len > 1 && buf[len - 1] == '\r') --len; buf[len] = '\0'; /* unconditionally terminate string, */ /* possibly overwriting CR and/or NL */ return buf; } /* * nn_getline - gets a line from stdin * returns the length of the line */ int nn_getline(char *line, int max) { if (fgets(line, max, stdin) == NULL) return 0; else return strlen(line); } #ifdef NEED_STRERROR /* strerror for old OSs that don't include it */ extern int sys_nerr; extern char *sys_errlist[]; char * strerror(int num) { static char mesg[40]; if (num < 0 || num > sys_nerr) { sprintf(mesg, "Unknown error (%d)", num); return mesg; } else return sys_errlist[num]; } #endif nn-6.7.4/chset.c0000644000175000017500000000160010222577503013023 0ustar mtpinsmtpins/* * (c) Copyright 1992, Luc Rooijakkers. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Character set support (rudimentary) */ #include #include #include "config.h" #include "chset.h" static struct chset chsets[] = { "us-ascii", 7, "iso-8859-1", 8, "iso-8859-2", 8, "iso-8859-3", 8, "iso-8859-4", 8, "iso-8859-5", 8, "iso-8859-6", 8, "iso-8859-7", 8, "iso-8859-8", 8, "iso-8859-9", 8, "iso-8859-15", 8, "unknown", 0, NULL, 0, }; struct chset *curchset = chsets; struct chset * getchset(char *name) { struct chset *csp; char *sp; for (sp = name; *sp; sp++) if (isupper(*sp)) *sp = tolower(*sp); for (csp = chsets; csp->cs_name != NULL; csp++) { if (strcmp(csp->cs_name, name) == 0) return csp; } return NULL; } nn-6.7.4/active.c0000644000175000017500000000662010222576600013174 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * read/update incore copy of active file */ #include #include #include #include "config.h" #include "global.h" #include "active.h" #include "db.h" #include "master.h" #include "newsrc.h" void read_active_file(FILE * act, FILE * copy) { char line[512]; register char *cp, *name; register group_header *gh, *gh1; int must_update; register flag_type old_flag; Loop_Groups_Header(gh) { gh->master_flag &= ~M_VALID; gh->first_a_article = 0; gh->last_a_article = 0; } while (fgets(line, 512, act)) { if (copy != NULL) fputs(line, copy); must_update = 0; cp = line; while (*cp && isspace(*cp)) cp++; /* eat blank lines */ if (*cp == NUL || *cp == '#') continue; /* cp -> NAME space 00888 ... nl */ name = cp; while (*cp != ' ') cp++; *cp++ = NUL; gh = lookup_no_alias(name); if (gh == NULL) { /* new group */ gh = add_new_group(name); if (gh == NULL) continue; must_update = 1; } while (*cp && isspace(*cp)) cp++; gh->last_a_article = atol(cp); while (*cp && isdigit(*cp)) cp++; while (*cp && isspace(*cp)) cp++; if (*cp == NUL) { log_entry('E', "Error in active file for entry %s", name); continue; } gh->first_a_article = atol(cp); if (gh->first_a_article == 0) gh->first_a_article = 1; while (*cp && isdigit(*cp)) cp++; while (*cp && isspace(*cp)) cp++; gh->master_flag |= M_VALID; if (gh->master_flag & M_IGNORE_G) continue; old_flag = gh->master_flag & (M_IGNORE_A | M_MODERATED | M_NOPOST | M_ALIASED); gh->master_flag &= ~(M_IGNORE_A | M_MODERATED | M_NOPOST | M_ALIASED); switch (*cp) { default: break; case 'x': gh->master_flag |= M_IGNORE_A; if ((old_flag & (M_IGNORE_A | M_ALIASED)) == M_IGNORE_A) continue; must_update++; break; case 'm': gh->master_flag |= M_MODERATED; if (old_flag & M_MODERATED) continue; must_update++; break; case 'n': gh->master_flag |= M_NOPOST; if (old_flag & M_NOPOST) continue; must_update++; break; case '=': while (*++cp && isspace(*cp)) cp++; name = cp; while (*cp && !isspace(*cp)) cp++; *cp = NUL; if (old_flag & M_ALIASED) { /* quick check: has the alias changed */ int32 n = (int32) gh->data_write_offset; if (n >= 0 && n < master.number_of_groups) { gh1 = ACTIVE_GROUP(n); if (strcmp(gh1->group_name, name) == 0) { gh->master_flag |= M_ALIASED | M_IGNORE_A; continue; } } } gh1 = lookup_no_alias(name); if (gh1 == NULL) { /* '=unknown.group' is treated just like 'x' */ if ((old_flag & M_IGNORE_A) == 0) log_entry('R', "Group %s aliased to unknown group (%s)", gh->group_name, name); gh->master_flag |= M_IGNORE_A; if ((old_flag & (M_IGNORE_A | M_ALIASED)) == M_IGNORE_A) continue; } else { gh->master_flag |= M_ALIASED | M_IGNORE_A; gh->data_write_offset = (long) gh1->group_num; } must_update = 1; break; } if ((old_flag & M_ALIASED) && (gh->master_flag & M_ALIASED) == 0) { gh->data_write_offset = 0; gh->master_flag |= M_MUST_CLEAN; continue; } if (must_update && who_am_i == I_AM_MASTER) db_write_group(gh); } } nn-6.7.4/account.c0000644000175000017500000003032310222711236013346 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Accounting for news reading. * * The nnacct program is called by nn in three cases: * * - on startup (-q) to check for permission to run nn (at this time) * - when the :cost command is executed (-cUSAGE -r) to * produce a "cost sofar" report, and * - at exit (-uUSAGE -r) to add the USAGE to the user's account * and print a "cost" report. * * It can also be invoked by nnusage to print a usage and cost * report for the current user (default), or by the super user * to produce a usage and cost report for all users. */ #include #include #include #include "config.h" #include "global.h" #include "account.h" #include "execute.h" #include "options.h" #include "proto.h" extern char *lib_directory; extern int errno; struct account { off_t ac_offset; /* offset in acct file */ int ac_found; /* present in acct file */ char ac_user[24];/* user name */ long ac_total; /* total usage */ time_t ac_last; /* last active */ int ac_policy; /* assigned policy */ int ac_quota; /* time quota */ }; /* account.c */ static void put_entry(FILE * acctf, struct account * ac); static int get_entry(FILE * acctf, char *user, struct account * ac); static void do_cost(struct account * ac, int ses); static void do_report(struct account * ac, int hdr); static void do_report_all(FILE * acctf); static void do_zero(void); static int news_admin(char *caller); /* * local authorization policy checking * * return/exit values of policy_check (nnacct -P0) are: * * 0: access granted * 1: access granted, but cannot post * 2: access denied (not authorized) * 3: access denied (not allowed at this time of day) * 4: access denied (quota exceeded) */ #ifdef AUTHORIZE #include static int holiday(struct tm * tm) { /* Kim's birthday - 23 April */ if (tm->tm_mon == 3 && tm->tm_mday == 23) return 1; /* another birthday - 25 December :-) */ if (tm->tm_mon == 11 && tm->tm_mday == 25) return 1; /* ... */ return 0; } static int policy_check(int policy) { struct tm *tm, *localtime(); time_t t; int no_post = 0; if (policy >= NO_POST) { policy -= NO_POST; no_post = 1; } switch (policy % 10) { case DENY_ACCESS: return 2; case ALL_HOURS: case FREE_ACCOUNT: break; case OFF_HOURS: /* adapt this to your local requirements */ time(&t); tm = localtime(&t); if (tm->tm_wday == 0) break; /* Sunday */ if (tm->tm_wday == 6) break; /* Saturday */ if (tm->tm_hour < 9) break; /* morning */ if (tm->tm_hour > 16) break; /* evening */ if (holiday(tm)) break; /* holidays */ /* etc. */ return 3; default: return 2; } return no_post; } #endif static int add_usage = -1; static int show_cost = -1; static int report = 0; static int report_all = 0; static int quiet = 0; static char *acct_file = NULL; static int ck_policy = -1; static int new_policy = -1; static int new_quota = -1; static int who_am_caller = I_AM_ACCT; static char *zero_accounts = NULL; Option_Description(acct_options) { 'C', Int_Option(show_cost), 'U', Int_Option(add_usage), 'W', Int_Option(who_am_caller), 'P', Int_Option(ck_policy), 'a', Bool_Option(report_all), 'f', String_Option(acct_file), 'p', Int_Option(new_policy), 'q', Int_Option(new_quota), 'r', Bool_Option(report), 'Q', Bool_Option(quiet), 'Z', String_Option(zero_accounts), '\0', }; /* * Accounting information: * * xxxxxxx 00000000 00000000 00 00000\n * * login time used last pol quota * name (minutes) active icy (hours) * * See the printf/scanf formats later on. */ #define INPUT_FMT "%s %ld %ld %d %d\n" #define OUTPUT_FMT "%s %08ld %08lx %02d %05d\n" static int get_entry(FILE * acctf, char *user, struct account * ac) { char line[100]; if (acctf != NULL && user != NULL) rewind(acctf); ac->ac_found = 0; for (;;) { ac->ac_policy = DEFAULT_POLICY; ac->ac_last = 0; ac->ac_total = 0; ac->ac_quota = DEFAULT_QUOTA; ac->ac_user[0] = NUL; if (acctf == NULL) break; ac->ac_offset = ftell(acctf); if (fgets(line, 100, acctf) == NULL) break; sscanf(line, INPUT_FMT, ac->ac_user, &ac->ac_total, &ac->ac_last, &ac->ac_policy, &ac->ac_quota); if (user == NULL) return 1; if (strcmp(user, ac->ac_user) == 0) { ac->ac_found = 1; return 1; } } if (user != NULL) strcpy(ac->ac_user, user); return 0; } static void put_entry(FILE * acctf, struct account * ac) { if (ac->ac_found) fseek(acctf, ac->ac_offset, 0); else fseek(acctf, (off_t) 0, 2); fprintf(acctf, OUTPUT_FMT, ac->ac_user, ac->ac_total, ac->ac_last, ac->ac_policy, ac->ac_quota); } static void do_cost(struct account * ac, int ses) { long r; #ifdef COST_PER_MINUTE #ifndef COST_UNIT #define COST_UNIT "" #endif if (ses >= 0) printf("Cost this session: %ld %s Period total:", ((long) ses * COST_PER_MINUTE) / 100, COST_UNIT); printf("%6ld %s", (ac->ac_total * COST_PER_MINUTE) / 100, COST_UNIT); #else printf("Time used this session: %d:%02d Period total: %ld:%02ld", ses / 60, ses % 60, ac->ac_total / 60, ac->ac_total % 60); #endif if (ses >= 0 && ac->ac_quota > 0) { r = ac->ac_quota - ac->ac_total / 60; printf(" Quota: %ld hour%s", r, plural(r)); } fl; } static void do_report(struct account * ac, int hdr) { if (hdr) { printf("USER USAGE QUOTA LAST_ACTIVE"); #ifdef COST_PER_MINUTE printf(" COST/PERIOD"); #endif #ifdef AUTHORIZE printf(" POLICY"); #endif putchar(NL); } printf("%-8.8s %4ld.%02ld %5d %-12.12s ", ac->ac_user, ac->ac_total / 60, ac->ac_total % 60, ac->ac_quota, ac->ac_last ? date_time(ac->ac_last) : ""); #ifdef COST_PER_MINUTE do_cost(ac, -1); #endif #ifdef AUTHORIZE printf(" %2d ", ac->ac_policy); #endif printf("\n"); } static void do_report_all(FILE * acctf) { struct account ac; int first = 1; while (get_entry(acctf, (char *) NULL, &ac)) { do_report(&ac, first); first = 0; } } static char *ZERO_STAMP = "(Zeroed)"; static void do_zero(void) { FILE *old, *new; char *acct, bak[FILENAME]; struct account ac; acct = relative(lib_directory, "acct"); old = open_file(acct, OPEN_READ); if (old == NULL) goto err; sprintf(bak, "%s.old", acct); if (unlink(bak) < 0 && errno != ENOENT) goto err; if (link(acct, bak) < 0) goto err; if (unlink(acct) < 0) { unlink(bak); goto err; } umask(0177); new = open_file(acct, OPEN_CREATE); if (new == NULL) goto err2; ac.ac_found = 0; strcpy(ac.ac_user, ZERO_STAMP); ac.ac_total = ac.ac_policy = ac.ac_quota = 0; time(&(ac.ac_last)); put_entry(new, &ac); while (get_entry(old, (char *) NULL, &ac)) { if (strcmp(ac.ac_user, ZERO_STAMP) == 0) continue; ac.ac_total = 0; ac.ac_found = 0; put_entry(new, &ac); } fclose(old); if (fclose(new) == EOF) goto err2; return; err2: unlink(acct); if (link(bak, acct) == 0) unlink(bak); err: fprintf(stderr, "ZERO of accounts failed -- check permissions etc.\n"); } static int news_admin(char *caller) { FILE *adm; char line[80]; int len, ok; adm = open_file(relative(lib_directory, "admins"), OPEN_READ); if (adm == NULL) return 2; len = strlen(caller); ok = 0; while (fgets(line, 80, adm)) { if (line[len] != NL) continue; line[len] = NUL; if (strcmp(caller, line) == 0) { ok = 1; break; } } fclose(adm); return ok; } int main(int argc, char *argv[]) { char *caller; FILE *acctf; char *fname = NULL; int users, i; struct account ac, *actab = NULL; who_am_i = I_AM_ACCT; init_global(); users = parse_options(argc, argv, (char *) NULL, acct_options, ""); if (zero_accounts && strcmp(zero_accounts, "ERO")) { fprintf(stderr, "Must specify -ZERO to clear accounts\n"); exit(1); } if (user_id != 0) { caller = user_name(); if (news_admin(caller) == 1) goto caller_ok; if (report_all) { fprintf(stderr, "Only root can request complete reports\n"); exit(9); } if (new_policy >= 0) { fprintf(stderr, "Only root can change user authorization\n"); exit(9); } if (new_quota >= 0) { fprintf(stderr, "Only root can change user quotas\n"); exit(9); } if (zero_accounts) { fprintf(stderr, "Only root can zero user accounts\n"); exit(9); } if (users > 0) { fprintf(stderr, "Only root can request reports for other users\n"); exit(9); } } else caller = "root"; caller_ok: if ((new_policy >= 0 || new_quota >= 0) && users == 0) { fprintf(stderr, "usage: %s -pPOLICY -qQUOTA user...\n", argv[0]); exit(1); } if (add_usage == 0 && report) { show_cost = 0; add_usage = -1; } if (zero_accounts || add_usage > 0 || new_policy >= 0 || new_quota >= 0) { if (acct_file) { fprintf(stderr, "Can only update current acct file %s\n", acct_file); exit(2); } proto_lock(I_AM_ACCT, PL_SET_QUICK); } if (zero_accounts) { do_zero(); proto_lock(I_AM_ACCT, PL_CLEAR); exit(0); } if (acct_file) { if ((acctf = open_file(acct_file, OPEN_READ)) == NULL) acctf = open_file(relative(lib_directory, acct_file), OPEN_READ); if (acctf == NULL) { fprintf(stderr, "Accounting file %s not found\n", acct_file); if (add_usage > 0 || new_policy >= 0 || new_quota >= 0) proto_lock(I_AM_ACCT, PL_CLEAR); exit(1); } } else { fname = relative(lib_directory, "acct"); acctf = open_file(fname, OPEN_READ); } if (report_all) { do_report_all(acctf); fclose(acctf); exit(0); } if (ck_policy >= 0) { #ifdef AUTHORIZE get_entry(acctf, caller, &ac); #ifdef ACCOUNTING if (ac.ac_quota > 0 && ac.ac_quota < ac.ac_total / 60) exit(4); #endif exit(policy_check(ac.ac_policy)); #else exit(0); #endif } if (show_cost >= 0) { get_entry(acctf, caller, &ac); if (ac.ac_policy == FREE_ACCOUNT) exit(0); ac.ac_total += show_cost; do_cost(&ac, show_cost); exit(0); } if (add_usage > 0) { get_entry(acctf, caller, &ac); if (ac.ac_policy == FREE_ACCOUNT) goto unlock; ac.ac_total += add_usage; time(&ac.ac_last); } else if (users > 0) { actab = newobj(struct account, users + 1); for (i = 1; i <= users; i++) { get_entry(acctf, argv[i], &actab[i]); if (new_policy >= 0 || new_quota >= 0) { if (new_policy >= 0) actab[i].ac_policy = new_policy; if (new_quota >= 0) actab[i].ac_quota = new_quota; } else do_report(&actab[i], i == 1); } } else if (report) { if (get_entry(acctf, caller, &ac)) do_report(&ac, 1); exit(0); } if (acctf) fclose(acctf); if (add_usage <= 0 && new_policy < 0 && new_quota < 0) exit(0); umask(0177); acctf = open_file(fname, OPEN_UPDATE | MUST_EXIST); if (new_policy >= 0 || new_quota >= 0) { for (i = 1; i <= users; i++) put_entry(acctf, &actab[i]); fclose(acctf); goto unlock; } if (add_usage > 0) { put_entry(acctf, &ac); if (report) { do_cost(&ac, add_usage); } fclose(acctf); #ifdef ACCTLOG fname = relative(lib_directory, "acctlog"); acctf = open_file(fname, OPEN_APPEND | MUST_EXIST); fprintf(acctf, "%s\t%s\t%ld\n", caller, date_time(ac.ac_last), (long) add_usage); fclose(acctf); #endif goto unlock; } unlock: proto_lock(I_AM_ACCT, PL_CLEAR); exit(0); /* NOTREACHED */ } void nn_exit(int n) { exit(n); } void nn_exitmsg(int n, char *fmt,...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); putchar(NL); va_end(ap); nn_exit(n); /* NOTREACHED */ } /* * dummy routines - should never be called by nnacct */ int no_update = 0; int set_variable(char *variable, int on, char *val_string) { return 0; } void msg(char *fmt,...) { } #ifdef HAVE_JOBCONTROL int suspend_nn(void) { return 0; } #endif nn-6.7.4/MACHINES0000644000175000017500000000636410227334476012704 0ustar mtpinsmtpinsThis is a list of the status of NN on various machines and systems. Last updated 1 April 2005. This is not necessarrily complete. Additions or corrections are welcome! ------------------------------------------------------------------ Systems: s-386bsd.h For 386BSD s-3b1g.h For 3b1 (unix-pc) with GCC s-NeXT1-0.h For NeXT 1.0 s-NeXT2-2.h For NeXT 2.2 s-NeXT3.h For NeXT 3.x s-aix221.h For AIX 2.2.1 s-aix32.h For RS6000 AIX 3.2.x (also for AIX 4.1) s-apollo.h For Apollo Domain/OS systems running BSD 4.3 s-aux1-1.h For A/UX 1.1 s-aux3-0.h For A/UX 3.0.x s-bsd4-2.h For 4.2 BSD systems s-bsd4-3.h For 4.3 BSD systems s-dnix5-2.h For dnix 5.2 on DIAB DS90. s-dnix5-3.h For dnix 5.3 on DIAB DS90. s-dynix3-0.h For Dynix 3.0 on Symmetry. s-fortune.h For Fortune 32:16 [read comments in the file] s-freebsd.h For FreeBSD 2.x, 3.x, 4.x, 5.x s-hpux.h For HP-UX (series 300) s-hpux2-1.h For HP-UX 2.1 (on HP9000 Series 800) s-hpux3-0.h For HP-UX 3.0 (on HP9000 Series 800) s-hpux6-5.h For HP-UX 6.5 or newer (series 300) s-hpux7-0.h For HP-UX 7.0 s-hpux8-0.h For HP-UX 8.0 s-hpux9-0.h For HP-UX 9.0 s-hpux9gcc.h For HP-UX 9.0 with GCC s-ix386.h For ISC 386/ix 2.0.x (also Dell UNIX 1.x) s-linux.h For Linux kernel 2.x s-macosx.h For MacOSX s-netbsd.h For NetBSD s-osf1.h For OSF/1 aka DECUnix aka Tru64 s-ptx1-1.h For Dynix/PTX on symmetry s-pyramid.h For Pyramid (and Nixdorf Targon 35) s-scoV386.h For SCO UNIX System V/386 s-sgi4D.h For IRIX 3.1/3.2 [read comments in the file] s-sinix.h For Siemens SINIX system on MX300 s-sunos3.h For SunOS 3.x s-sunos4-0.h For SunOS 4.0 s-sunos4-v.h For SunOS 4.1 SysV environment s-sunos5.h For SunOS 5.x s-sys5-4.h For generic SVR4 s-sys5-tcap.h For System V using termcap rather than terminfo s-sys5.h For generic System V Release 2 & 3 s-sysV88.h For Motorola System V/88 Release 3 s-texas1500.h For Texas Instruments System 1500 s-tower32.h For NCR Tower-32 s-ultrix.h For ULTRIX systems (BSD 4.2 based) s-umipsb.h For Mips Risc/os 4.0 or later s-uport2-2.h For Microport UNIX V.2 s-uts2-0.h For Amdahl UTS 2.0 s-xenix286.h For SCO Xenix Sys V Release 2.2.1 -- terminfo s-xenix286b.h For SCO XENIX 286 -- termcap s-xenix386.h For SCO XENIX/386 [termcap version] s-xenix386ds.h For XENIX386 release 2.3.2 development system s-xenix386wg.h For SCO XENIX/386 [termcap version] s-xenix68k.h For Tandy 68000/Xenix Version 3.2 -------------------------------------------------------------------- Machines: m-3b1g.h For 3b1 (unix-pc) with GCC m-alpha.h For DEC alpha based systems m-amdahl.h For Amdahl 5890 (big iron) m-att3b.h For AT&T 3b2 m-convex.h For Convex. m-dec3100.h For DECstation 3100 m-gould.h For Gould PN6000 m-hp9000.h For HP9000 series 320 and 800 (at least) m-i80286.h For Intel 80286 processors m-i80386.h For Intel 80386 processors m-m680x0.h For Motorola 68000 family processors m-m88000.h For Motorola 88000 risc processors m-mips.h For MIPS processors m-mx300.h For Siemens MX300 m-pcs.h For PCS m-powerpc.h For PowerPC m-pyramid.h For Pyramid (and Targon 35). m-rs6000.h For IBM RS6000 m-rt6150.h For IBM/RT 6150 m-sgi4D.h For Silicon Graphics 4D series m-sparc.h For SPARC processors m-sun386i.h For 80386 based SUNs m-symmetry.h For Sequent Symmetry m-vax.h For VAX family nn-6.7.4/master.c0000644000175000017500000005430710222723565013225 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * nn database daemon (nnmaster) * * maintains the article header database. */ #include #include #include #include #include #include #include #include #include "config.h" #include "global.h" #include "master.h" #include "active.h" #include "collect.h" #include "db.h" #include "digest.h" #include "execute.h" #include "expire.h" #include "nntp.h" #include "options.h" #include "proto.h" /* master.c */ static void clean_group_internal(register group_header * gh); static void group_restriction(register group_header * gh); static void set_group_restrictions(char **restr, int n); static void visit_active_file(void); static void build_master(void); static void set_lock_message(void); static void do_reread_groups(void); static int receive_admin(void); extern char *bin_directory; extern char proto_host[]; /* * nnmaster options: * * -e [N] expire a group if more than N articles are gone * -r N repeat every N minutes * -h N don't update if active file is less than N *seconds* old * * -f foreground execution (use with -r) * -y N retry N times on error * * -E [N] expire mode (see expire.c) [N==1 if omitted] * -F Run ONLY expire ONCE and exit. * -R N auto-recollect mode (see expire.c) * * -C check consistency of database on start-up * -b include 'bad' articles (disables -B) * -B remove 'bad' articles (just unlink the files) * -O N Consider articles older than N days as bad articles. * * -I [N] initialize [ limit to N articles in each group ] * -G reread groups file. * -X clean ignored groups. * * -l"MSG" lock database with message MSG * -l unlock database * -i ignore lock (run collection on locked database) * -k kill the running master and take over. * * -Q quiet: don't write fatal errors to /dev/console (if no syslog). * -t trace collection of each group * -v print version and exit * -u update even if active is not modified * -w send wakeup to real master * -Ltypes exclude 'types' entries from the log * -D [N] debug, N = +(1 => verbose, 2 => nntp trace) * * [master group]... Collect these groups only. */ extern int dont_write_console, expire_method, expire_level, mail_errors_mode, recollect_method, reread_groups_file, ignore_bad_articles, remove_bad_articles, retry_on_error; #ifdef NNTP extern int nntp_local_server, nntp_debug; #endif extern time_t max_article_age; extern char *log_entry_filter; int trace = 0, debug_mode = 0, Debug = 0, no_update = 0; #ifdef NNTP int silent = 1; #endif int Name_Length = 16; static int check_on_startup = 0, clean_ignored = 0, expire_once = 0, foreground = 0, ignore_lock = 0, initialize = -1, kill_running = 0, max_age_days = 0, prt_vers = 0, unconditional = 0, Unconditional = 0, wakeup_master = 0; static unsigned hold_updates = 0, repeat_delay = 0; static char *lock_message = NULL; static Option_Description(master_options) { 'b', Bool_Option(ignore_bad_articles), 'B', Bool_Option(remove_bad_articles), 'C', Bool_Option(check_on_startup), 'D', Int_Option_Optional(debug_mode, 1), 'e', Int_Option_Optional(expire_level, 1), 'E', Int_Option_Optional(expire_method, 1), 'f', Bool_Option(foreground), 'F', Bool_Option(expire_once), 'G', Bool_Option(reread_groups_file), 'h', Int_Option_Optional(hold_updates, 60), #ifdef NNTP 'H', Bool_Option(nntp_local_server), #endif 'i', Bool_Option(ignore_lock), 'I', Int_Option_Optional(initialize, 0), 'k', Bool_Option(kill_running), 'l', String_Option_Optional(lock_message, ""), 'L', String_Option(log_entry_filter), 'M', Int_Option(mail_errors_mode), 'O', Int_Option(max_age_days), 'Q', Bool_Option(dont_write_console), 'r', Int_Option_Optional(repeat_delay, 10), 'R', Int_Option(recollect_method), 't', Bool_Option(trace), 'u', Bool_Option(unconditional), 'U', Bool_Option(Unconditional), 'v', Bool_Option(prt_vers), 'w', Bool_Option(wakeup_master), 'X', Bool_Option(clean_ignored), 'y', Int_Option(retry_on_error), '\0', }; extern char *master_directory, *db_directory, *news_active; static int unlock_on_exit = 0; /* * nn_exit() --- called whenever a program exits. */ void nn_exit(int n) { #ifdef NNTP if (use_nntp) nntp_cleanup(); #endif /* NNTP */ close_master(); if (unlock_on_exit) proto_lock(I_AM_MASTER, PL_CLEAR); if (n) log_entry('E', "Abnormal termination, exit=%d", n); else if (unlock_on_exit) log_entry('M', "Master terminated%s", s_hangup ? " (hangup)" : ""); exit(n); } static void clean_group_internal(register group_header * gh) { /* no write */ gh->first_db_article = 0; gh->last_db_article = 0; gh->data_write_offset = 0; gh->index_write_offset = 0; if (init_group(gh)) { (void) open_data_file(gh, 'd', -1); (void) open_data_file(gh, 'x', -1); } gh->master_flag &= ~(M_EXPIRE | M_BLOCKED); if ((gh->master_flag & M_IGNORE_GROUP) == 0) gh->master_flag |= M_BLOCKED; } void clean_group(group_header * gh) { /* does write */ if (trace) log_entry('T', "CLEAN %s", gh->group_name); if (debug_mode) printf("CLEAN %s\n", gh->group_name); clean_group_internal(gh); db_write_group(gh); } static char **restrictions = NULL; static int *restr_len, *restr_excl; static void group_restriction(register group_header * gh) { register char **rp; register int *lp, *xp; if (restrictions == NULL) return; for (rp = restrictions, lp = restr_len, xp = restr_excl; *lp > 0; rp++, lp++, xp++) if (strncmp(gh->group_name, *rp, *lp) == 0) { if (*xp) break; return; } if (*lp == 0) return; gh->master_flag |= M_IGNORE_G; } static void set_group_restrictions(char **restr, int n) { register group_header *gh; register int i; restrictions = restr; restr_len = newobj(int, n + 1); restr_excl = newobj(int, n); for (i = 0; i < n; i++) { if (restrictions[i][0] == '!') { restr_excl[i] = 1; restrictions[i]++; } else restr_excl[i] = 0; restr_len[i] = strlen(restrictions[i]); } restr_len[n] = -1; Loop_Groups_Header(gh) { if (gh->master_flag & M_IGNORE_GROUP) continue; group_restriction(gh); if (clean_ignored && (gh->master_flag & M_IGNORE_G)) { log_entry('X', "Group %s ignored", gh->group_name); clean_group(gh); } } } /* * add new group to master file */ group_header * add_new_group(char *name) { register group_header *gh; if (master.free_groups <= 0) db_expand_master(); master.free_groups--; #ifdef MALLOC_64K_LIMITATION gh = newobj(group_header, 1); active_groups[master.number_of_groups] = gh; #else gh = &active_groups[master.number_of_groups]; #endif gh->group_name_length = strlen(name); gh->group_name = copy_str(name); gh->group_num = master.number_of_groups++; gh->creation_time = cur_time(); db_rewrite_groups(1, (group_header *) NULL); group_restriction(gh); /* done after append to avoid setting ! */ clean_group(gh); db_write_master(); sort_groups(); log_entry('C', "new group: %s (%d)", gh->group_name, gh->group_num); return gh; } static void visit_active_file(void) { FILE *act; FILE *nntp_act = NULL; #ifdef NNTP if (!use_nntp) /* copy 'active' to DB/ACTIVE */ nntp_act = open_file(relative(db_directory, "ACTIVE"), OPEN_CREATE | MUST_EXIST); #endif act = open_file(news_active, OPEN_READ | MUST_EXIST); read_active_file(act, nntp_act); master.last_size = ftell(act); fclose(act); #ifdef NNTP if (nntp_act != NULL) fclose(nntp_act); #endif } /* * Build initial master file. */ static void build_master(void) { char command[512]; char groupname[512]; group_header *groups, *next_g, *gh; group_header *dupg; FILE *src; int lcount, use_group_file, found_nn_group = 0; printf("Confirm initialization by typing 'OK': "); fl; fgets(command, sizeof(command), stdin); if (strcmp(command, "OK")) { printf("No initialization\n"); nn_exit(0); } if (chdir(master_directory) < 0) /* so we can use open_file (?) */ sys_error("master"); #ifdef NNTP if (use_nntp && nntp_get_active() < 0) sys_error("Can't get active file"); #endif /* check active file for duplicates */ sprintf(command, "awk 'NF>0{print $1}' %s | sort | uniq -d", news_active); src = popen(command, "r"); if (src == NULL) sys_error("popen(%s) failed", command); for (lcount = 0; fgets(groupname, 512, src); lcount++) { if (lcount == 0) printf("\n%s contains duplicate entries for the following groups:", news_active); fputs(groupname, stdout); } pclose(src); if (lcount > 0) { printf("Do you want to repair this file before continuing ? (y)"); fgets(command, sizeof(command), stdin); if (s_hangup || command[0] == NUL || command[0] == 'y' || command[0] == 'Y') { printf("Initialization stopped.\n"); printf("Redo ./inst INIT after repairing active file\n"); nn_exit(0); } } /* if a "GROUPS" file exist offer to use that, else */ /* read group names from active file */ use_group_file = 0; if (open_groups(OPEN_READ)) { printf("\nA GROUPS file already exist -- reuse it? (y)"); fl; fgets(command, sizeof(command), stdin); if (command[0] == NUL || command[0] == 'y' || command[0] == 'Y') { use_group_file = 1; } else close_groups(); if (s_hangup) return; } printf("\nBuilding %s/MASTER file\n", db_directory); fl; if (!use_group_file) { sprintf(command, "awk 'NF>0{print $1}' %s | sort -u", news_active); src = popen(command, "r"); if (src == NULL) sys_error("popen(%s) failed", command); } open_master(OPEN_CREATE); master.db_magic = NNDB_MAGIC; master.last_scan = 0; master.number_of_groups = 0; strcpy(master.db_lock, "Initializing database"); db_write_master(); groups = next_g = newobj(group_header, 1); next_g->next_group = NULL; for (;;) { if (s_hangup) goto intr; gh = newobj(group_header, 1); gh->master_flag = 0; if (use_group_file) { gh->group_name_length = 0; if (db_parse_group(gh, 0) <= 0) break; } else { if (fgets(groupname, 512, src) == NULL) break; gh->group_name_length = strlen(groupname) - 1; /* strip NL */ groupname[gh->group_name_length] = NUL; gh->creation_time = 0; gh->group_name = copy_str(groupname); gh->archive_file = NULL; } for (dupg = groups->next_group; dupg != NULL; dupg = dupg->next_group) if (strcmp(dupg->group_name, gh->group_name) == 0) break; if (dupg != NULL) { printf("Ignored duplicate of group %s\n", dupg->group_name); continue; } gh->group_num = master.number_of_groups++; if (trace || debug_mode) printf("%4ld '%s' (%d)\n", gh->group_num, gh->group_name, gh->group_name_length); next_g->next_group = gh; next_g = gh; gh->next_group = NULL; /* moderation flag will be set by first visit_active_file call */ /* might have the INN control.newgrp, control.cancel, etc */ if (strncmp(gh->group_name, "control", 7) == 0) gh->master_flag |= M_CONTROL; if (strcmp(gh->group_name, "news.software.nn") == 0) found_nn_group++; gh->master_flag &= ~M_MUST_CLEAN; clean_group_internal(gh); gh->master_flag |= M_VALID; /* better than the reverse */ db_write_group(gh); } if (use_group_file) close_groups(); else pclose(src); printf("%s %s/GROUPS file\n", use_group_file ? "Updating" : "Building", db_directory); db_rewrite_groups(use_group_file, groups); if (initialize > 0) { printf("Setting articles per group limit to %d...\n", initialize); db_write_master(); open_master(OPEN_READ); open_master(OPEN_UPDATE); visit_active_file(); Loop_Groups_Header(gh) { gh->first_db_article = gh->last_a_article - initialize + 1; if (gh->first_db_article <= gh->first_a_article) continue; gh->last_db_article = gh->first_db_article - 1; if (gh->last_db_article < 0) gh->last_db_article = 0; db_write_group(gh); } } master.db_lock[0] = NUL; master.db_created = cur_time(); db_write_master(); close_master(); printf("Done\n"); fl; log_entry('M', "Master data base initialized"); if (!found_nn_group) printf("\nNotice: nn's own news group `news.software.nn' was not found\n"); return; intr: printf("\nINTERRUPT\n\nDatabase NOT completed\n"); log_entry('M', "Master data base initialization not completed (INTERRUPTED)"); } static void set_lock_message(void) { if (lock_message[0] || master.db_lock[0]) { open_master(OPEN_UPDATE); strncpy(master.db_lock, lock_message, DB_LOCK_MESSAGE); master.db_lock[DB_LOCK_MESSAGE - 1] = NUL; db_write_master(); printf("DATABASE %sLOCKED\n", lock_message[0] ? "" : "UN"); } } static void do_reread_groups(void) { register group_header *gh; open_master(OPEN_UPDATE); Loop_Groups_Header(gh) if (gh->master_flag & M_MUST_CLEAN) { gh->master_flag &= ~M_MUST_CLEAN; clean_group(gh); } else db_write_group(gh); master.last_scan--; /* force update */ db_write_master(); close_master(); log_entry('M', "Reread GROUPS file"); } #ifdef HAVE_HARD_SLEEP /* * Hard sleeper... The normal sleep is not terminated by SIGALRM or * other signals which nnadmin may send to wake it up. */ static int got_alarm; static sig_type catch_alarm(void) { signal(SIGALRM, SIG_IGN); got_alarm = 1; } static int take_a_nap(int t) { got_alarm = 0; signal(SIGALRM, catch_alarm); alarm(repeat_delay); /* the timeout is at least 1 minute, so we ignore racing condition here */ pause(); if (!got_alarm) { signal(SIGALRM, SIG_IGN); alarm(0); } } #else #define take_a_nap(t) sleep(t) #endif int main(int argc, char **argv) { register group_header *gh; time_t age_active; int group_selection; int temp; int skip_pass; long pass_no; umask(002); /* avoid paranoia */ who_am_i = I_AM_MASTER; init_global(); group_selection = parse_options(argc, argv, (char *) NULL, master_options, (char *) NULL); if (debug_mode) { #ifdef NNTP nntp_debug = debug_mode & 2; #endif debug_mode = debug_mode & 1; } if (debug_mode) { signal(SIGINT, catch_hangup); } if (wakeup_master) { if (proto_lock(I_AM_MASTER, PL_WAKEUP) < 0) printf("master is not running\n"); exit(0); } if (prt_vers) { printf("nnmaster release %s\n", version_id); exit(0); } if (kill_running) { if (proto_lock(I_AM_MASTER, PL_TERMINATE) < 0) temp = 0; else { int mpid; if (proto_host[0]) { printf("Can't kill master on another host (%s)\n", proto_host); log_entry('R', "Attempt to kill master on host %s", proto_host); exit(1); } for (temp = 10; --temp >= 0; sleep(3)) { sleep(3); mpid = proto_lock(I_AM_MASTER, PL_CHECK); if (mpid < 0) break; sleep(1); kill(mpid, SIGTERM); /* SIGHUP lost??? */ } } if (temp < 0) { printf("The running master will not die....!\n"); log_entry('E', "Could not kill running master"); exit(1); } if (repeat_delay == 0 && !foreground && !reread_groups_file && lock_message == NULL && group_selection == 0 && initialize < 0) exit(0); } if (!file_exist(db_directory, "drwx")) { fprintf(stderr, "%s invoked with wrong user privileges\n", argv[0]); exit(9); } if (proto_lock(I_AM_MASTER, PL_SET) != 0) { if (proto_host[0]) printf("The master is running on another host (%s)\n", proto_host); else printf("The master is already running\n"); exit(0); } unlock_on_exit = 1; #ifdef NNTP nntp_check(); #endif if (initialize >= 0) { build_master(); nn_exit(0); } open_master(OPEN_READ); if (lock_message != NULL) { set_lock_message(); if (repeat_delay == 0 && !foreground && !reread_groups_file && group_selection == 0) nn_exit(0); } if (!ignore_lock && master.db_lock[0]) { printf("Database locked (unlock with -l or ignore with -i)\n"); nn_exit(88); } if (reread_groups_file) { do_reread_groups(); nn_exit(0); } if (!debug_mode) { close(0); close(1); close(2); if (open("/dev/null", 2) == 0) dup(0), dup(0); } if (repeat_delay && !debug_mode && !foreground) { while ((temp = fork()) < 0) sleep(1); if (temp) exit(0); /* not nn_exit() !!! */ process_id = getpid(); /* init_global saved parent's pid */ proto_lock(I_AM_MASTER, PL_TRANSFER); #ifdef DETACH_TERMINAL DETACH_TERMINAL #endif } log_entry('M', "Master started -r%d -e%d %s-E%d", repeat_delay, expire_level, expire_once ? "-F " : "", expire_method); if (check_on_startup) { char cmd[FILENAME]; sprintf(cmd, "%s/nnadmin Z", bin_directory); system(cmd); log_entry('M', "Database validation completed"); } repeat_delay *= 60; init_digest_parsing(); open_master(OPEN_UPDATE); if (group_selection) set_group_restrictions(argv + 1, group_selection); if (max_age_days && !use_nntp) /* we have to stat spool files */ max_article_age = cur_time() - (time_t) max_age_days *24 * 60 * 60; else max_article_age = 0; if (expire_once) { if (group_selection) /* mark selected groups for expire */ Loop_Groups_Header(gh) { if (gh->master_flag & M_IGNORE_GROUP) continue; gh->master_flag |= M_EXPIRE; } unconditional = 1; } for (pass_no = 0; !s_hangup; pass_no++) { if (pass_no > 0) { take_a_nap(repeat_delay); if (s_hangup) break; } #ifdef NNTP if (use_nntp && nntp_get_active() < 0) { nntp_close_server(); current_group = NULL; /* for init_group */ log_entry('N', "Can't access active file --- %s", repeat_delay ? "sleeping" : "terminating"); if (repeat_delay == 0) nn_exit(1); continue; } #endif temp = 2; while ((age_active = file_exist(news_active, "fr")) == (time_t) 0) { if (use_nntp) break; if (--temp < 0) sys_error("Cannot access active file"); sleep(5); /* maybe a temporary glitch ? */ } skip_pass = 0; if (Unconditional) unconditional = 1; if (unconditional) { unconditional = 0; } else if (age_active <= master.last_scan || (hold_updates && (cur_time() - age_active) < hold_updates)) skip_pass = 1; if (receive_admin()) skip_pass = 0; if (skip_pass) { if (repeat_delay == 0) break; if (s_hangup) break; #ifdef NNTP if (use_nntp) nntp_cleanup(); #endif if (debug_mode) { printf("NONE (*** SLEEP ***)\n"); } if (trace) log_entry('T', "none"); continue; } visit_active_file(); if (do_expire()) if (!expire_once && do_collect()) { if (Unconditional) master.last_scan = cur_time(); else master.last_scan = age_active; } db_write_master(); if (expire_once || s_hangup) break; if (repeat_delay == 0) break; #ifdef NNTP if (use_nntp) nntp_cleanup(); #endif } nn_exit(0); /* NOTREACHED */ return 0; } /* * receive commands from administrator */ static int receive_admin(void) { FILE *gate; char buffer[128], *bp; char command, opt, *user_date; int32 arg; int must_collect; register group_header *gh; gate = open_gate_file(OPEN_READ); if (gate == NULL) return 0; sleep(2); /* give administrator time to flush buffers */ must_collect = 0; while (fgets(buffer, 128, gate)) { bp = buffer; command = *bp++; if (*bp++ != ';') continue; arg = atol(bp); if (arg >= master.number_of_groups) continue; gh = (arg >= 0) ? ACTIVE_GROUP(arg) : NULL; if ((bp = strchr(bp, ';')) == NULL) continue; bp++; opt = *bp++; if (*bp++ != ';') continue; arg = atol(bp); if ((bp = strchr(bp, ';')) == NULL) continue; user_date = ++bp; if ((bp = strchr(bp, ';')) == NULL) continue; *bp++ = NUL; if (*bp != NL) continue; log_entry('A', "RECV %c %s %c %ld (%s)", command, gh == NULL ? "(all)" : gh->group_name, opt, arg, user_date); switch (command) { case SM_SET_OPTION: switch (opt) { case 'r': repeat_delay = arg; continue; case 'e': expire_level = arg; continue; case 't': trace = (arg < 0) ? !trace : arg; continue; } continue; /* XXX: Is this the right thing to do? */ case SM_EXPIRE: if (gh) { gh->master_flag |= M_EXPIRE | M_BLOCKED; db_write_group(gh); break; } Loop_Groups_Header(gh) { if (gh->master_flag & M_IGNORE_GROUP) continue; if (gh->index_write_offset == 0) continue; if (gh->master_flag & M_EXPIRE) continue; gh->master_flag |= M_EXPIRE; db_write_group(gh); } break; case SM_SET_FLAG: if (opt == 's') gh->master_flag |= (flag_type) arg; else gh->master_flag &= ~(flag_type) arg; db_write_group(gh); continue; case SM_RECOLLECT: /* recollect */ if (gh) { if ((gh->master_flag & M_IGNORE_GROUP) == 0) clean_group(gh); } else Loop_Groups_Header(gh) if ((gh->master_flag & M_IGNORE_GROUP) == 0) clean_group(gh); break; case SM_SCAN_ONCE: /* unconditional pass */ unconditional++; break; default: continue; } must_collect = 1; } fclose(gate); return must_collect; } void write_error(void) { /* * should wait for problems to clear out rather than die... */ sys_error("DISK WRITE ERROR"); } /* * dummy routines - should never be called by master */ char delayed_msg[100] = ""; FILE * open_purpose_file(void) { return NULL; } int set_variable(char *variable, int on, char *val_string) { return 0; } char * alloc_str(int len) { return 0; } void nn_exitmsg(int n, char *fmt,...) { } void msg(char *fmt,...) { } void init_term(int full) { } char * full_name(void) { return 0; } void clrdisp(void) { } void prompt(char *fmt,...) { } void user_delay(int ticks) { return; } #ifdef HAVE_JOBCONTROL int suspend_nn(void) { return 0; } #endif nn-6.7.4/help/0000755000175000017500000000000015213101030012463 5ustar mtpinsmtpinsnn-6.7.4/help/help.show0000644000175000017500000000111705750347763014353 0ustar mtpinsmtpins;:ASHOW COMMAND;:A :show kill show auto kill/select entries for current groups and all groups. :show groups show groups with unread articles (= Y command) :show groups all show all subscribed groups :show groups subscr show unread subscribed groups :show groups total show all available groups :show groups unsub show unsubscribed groups :show map show current mode's key bindings :show map key show global key bindings :show map menu/show show specified mode's key bindings :show map # show multi key definitions :show rc [GROUP] show (current) GROUP's .newsrc and select data. nn-6.7.4/help/help.set0000644000175000017500000000063505750347763014172 0ustar mtpinsmtpins;:ASET COMMAND;:A :set show changed variables :set all show all variables Modified variables are marked with a * Local variables are marked with a > :set VARIABLE VALUE set VARIABLE to VALUE :unset VARIABLE reset VARIABLE :toggle VARIABLE toggle boolean VARIABLE :local VARIABLE [VALUE] make VARIABLE local to current group if VALUE is specified assign it to the local variable ;:Chelp.variables nn-6.7.4/help/help.variables0000644000175000017500000001624015212301370015320 0ustar mtpinsmtpins;:AVARIABLES;:A Certain variables can be toggled with the specified command line options. also-subgroups Group names in sequence includes subgroups as well append-signature-mail Explicitly append .signature to outgoing mail append-signature-post Explicitly append .signature to posted articles attributes Article attribute symbols auto-junk-seen Automatically mark seen articles read auto-preview-mode Enter preview mode when selecting an article on menu backup Keep backup of rc file (-B) backup-suffix STR String to append to backup file names (.bak) bug-report-address Mail address for the :bug command case-fold-search String and pattern matching is case independent (! -i) collapse-subject Offset at which long subjects are compressed columns N Screen width comp1-key KEY Completion key 1 (space) comp2-key KEY Completion key 2 (tab) confirm-append User must confirm saving in existing files confirm-auto-quit User must confirm quit after reading last group confirm-create User must confirm creation of new files confirm-entry User must confirm entry to groups confirm-entry-limit N - confirm only for groups with more than N unread art. confirm-junk-seen Ask for confirmation before marking seen articles read confirm-messages User must confirm all messages with return (-W) consolidated-menu Collapse original subject and replies into one line consolidated-manual Group manual headings in a hierarchy cross-filter-seq Show cross posted articles in first group in sequence cross-post Show cross posted articles in all groups (-X) data-bits N Screen output is 7 or 8 bits date Show article dates (-D) decode-header-file FILE Save file for headers of :decoded articles decode-skip-prefix N Automatically unshar uuencoded articles if N>0 default-distribution DISTR Default answer when asking for distribution default-save-file FILE Default save file (for + abbreviation) delay-redraw Do not redraw screen immediately after : commands edit-patch-command Allow user to edit command used by :patch before exec edit-print-command Allow user to edit command used by :print before exec edit-unshar-command Allow user to edit command used by :unshar before exec edit-response-check Don't send articles if they are not edited. editor CMD Use specified editor instead of $EDITOR entry-report-limit N Give entry report for groups with >N unread articles erase-key KEY Erase last input character or completion expert Give less output expired-message-delay N Wait for N seconds after telling article is expired. flow-control Toggle between raw and cooked to enable flow control flush-typeahead Flush typeahead before reading each command folder DIR Folder directory fsort Sort folders in subject order (nofsort = -f) header-lines LIST Customized article header format help-key KEY Completion help key (?) include-art-id Include article-id in ".... writes:" line in follow-ups include-full-header M command includes full header in mailed articles. included-mark STR Prefix on included lines in replies (>) keep-unsubscribed Keep unsubscribed groups in .newsrc kill Enable/disable auto kill/select (nokill = -k) kill-key KEY Delete input line key layout N Menu layout N (-LN) limit N Limit number of presented articles to N (-aN) lines N Screen length long-menu Use all lines on the menu screen macro-debug Trace the execution of all macros mail Mailbox file to check for arrival of new mail mail-format Folders are created in mail compatible format mail-header STRING Extra header lines to include in posted mail mail-record FILE Save all replies in FILE mail-script FILE Use FILE instead of aux script when mailing mailer CMD Use CMD to send outgoing mail mailer-pipe-input 'mailer' reads standard input/takes file argument mark-overlap Underline last line from prev page to show overlap min-window Minimum size of preview window, clear if smaller mmdf-format Folders are written in MMDF format monitor Print all characters when reading article new-group-action N Specifies how NEW groups are handles -- see :man news-header STRING Extra header lines to include in posted articles news-record FILE Save all posted articles in FILE news-script FILE Use FILE instead of aux script when posting articles newsrc FILE Specify alternative .newsrc file nntp-cache-dir DIR Alternative directory for nntp cache files nntp-cache-size N Max number of different files in cache. old N Show read articles also (-xN, or -x if N is omitted) orig-to-include-mask N Specifies which header fields are placed in Orig-To: overlap N Display last N lines on next page when reading pager CMD Screen pager used by (v)iew action patch-command CMD Command used by :patch command preview-continuation N Specifies what to do after preview of one article preview-mark-read Previewing an article marks it read printer CMD Default print command (e.g. lp -s) query-signature Ask for confirmation before appending .signature quick-count Use quick method to count unread articles in .newsrc quick-save Save in default save file without asking re-layout N Presentation of Re: prefixes on menu subjects record FILE Set both news-record and mail-record to FILE repeat Do not truncate repeated subject lines repeat-group-query Cause 'nn -g' to repeat group query (-r) report-cost Report cost of session on exit response-check-pause N Wait N seconds after mailing/posting for error check response-default-answer STR Default answer to action prompt retain-seen-status Retain articles' seen status between invocations retry-on-error N Retry N times if open of article fails save-counter FMT Format of the save counter (e.g. .%02d) save-counter-offset First value of save counter save-report Display number of lines saved scroll-clear-page Scrolling clears page before drawing next page select-on-sender Should = command on menu match on sender or subject select-leave-next Ask to mark leave-next articles selected on entry shell CMD Shell to use for ! command. shell-restrictions Restricted environment silent Do not print "No news" etc. slow-mode Minimize screen output slow-speed SPEED 'on slow' clause is true for this and lower speeds sort Sort articles on menu (-q) sort-mode N Default sort mode for menus and :sort command spell-checker CMD Spelling checker for edited messages (one arg) split Split digests (-d) stop N Stop printing of articles after N lines (-lN) subject-match-limit N Treat subjects matching in N characters as identical subject-match-offset N Skip N characters of subjects before comparison subject-match-parts Treat subjects matching up to first digit as identical suggest-default-save Show default save file name on prompt line. tidy-newsrc Automatically remove garbage from .newsrc time Show current time on prompt line (-T) unshar-command CMD Command used by :unshar command for unpacking unshar-header-file FILE Save file for headers from unshared articles unsubscribe-mark-read Unread articles are marked read by (U)nsubscribe. update-frequency N Write .newsrc for every N groups use-selections Use select file visible-bell Use visible bell if defined in termcap/terminfo window N Preview window size is N lines (-wN) word-key KEY Delete last component (word) of input (^W) wrap-header-margin Fold long header lines over multiple lines nn-6.7.4/help/help.commands0000644000175000017500000000564215212301171015154 0ustar mtpinsmtpins;:ACOMMAND NAMES;:A ;:AMAP COMMAND;:A ;:BNAME MENU MORE FUNCTION advance-article A advance to next article from menu advance-group A advance one group in sequence article N a-z select article N (0..no of menu lines-1) back-article B go back one article from menu back-group B go back one group in sequence cancel C C cancel an article command : : extended command prefix compress c compress text (eliminate extra spaces) continue SPACE SPACE the "space bar" command continue-no-mark CR CR as the "space bar" command, but don't mark decode :decode :decode decode uuencoded article(s) find / regular expression search find-next . repeat regular expression search follow F f F follow up full-digest H show complete digest goto-group G G goto group or open folder goto-menu = go back to menu help ? ? online help junk-articles J change marking of articles on the menu kill-select K K kill/select handling layout " change menu layout leave-article l l leave (mark) article for later use leave-next L L permanently mark article for later use line+1 down CR next menu line/scroll one line line-1 up previous menu line line=@ g goto specific line macro N invoke macro number N (0..100) mail M m M mail or forward message ^P ^P repeat last prompt line message next-article n skip to next article next-group N goto to next group without reading current next-subject k skip to next article with different subject nil unbound key overview Y Y show groups with unread news page+1 > goto next page if any page+1/2 d ^D scroll half page forward page-1 < DEL goto one page back page-1/2 u ^U scroll half page backwards page=$ $ $ goto end of menu/article page=0 h goto header of article page=1 ^ ^ goto first menu/article page page=@ goto specific page of article (not implemented) patch :patch :patch pipe article through patch program post :post :post post new article preview % preview article previous P p goto previous group/article print :print P print article quit Q Q quit nn read-return Z read selected articles and return to menu read-skip X read selected article, skip unseen menu pages redraw ^L ^R ^L ^R redraw screen reply R r R reply rot13 D decrypt rot13 article save-full S s S save article with full header save-short O o O save article with short header save-header E e E save only the header of the article save-body W w W save article without header select . select (or deselect) current menu entry select-auto + select "auto-selected" articles select-invert @ invert all selections on current menu page select-range - select range of articles select-subject * select all articles with current subject shell ! ! shell command prefix skip-lines TAB skip lines starting with same char as last line unselect-all ~ unselect all articles unshar unshar article(s) unsub U U unsubscribe (or subscribe) to current group version V V print release information nn-6.7.4/help/help.more0000644000175000017500000000233607351755426014337 0ustar mtpinsmtpins;:ASCROLLING;:A ;:AABSOLUTE LINE;:A ;:ASEARCHING;:A SP 1 page forw ^ top /RE find regular expr. d 1/2 page forw gNUM line NUM . // repeat last search CR 1 line forw $ last line DEL 1 page back h header ;:ATEXT CONVERSIONS;:A u 1/2 page back H full digest D decrypt article (rot13) TAB skip section c compress spaces ;:AGOTO ANOTHER ARTICLE;:A SP next (at end of current article) ;:ACANCEL, SUBSCRIBE, KILL;:A n, p next/previous article C cancel article l mark article for later action U (un)subscribe to group k kill subject (not permanently) K kill/select handling * select subject ;:AQUIT / ESCAPE;:A ;:ASAVE;:A = back to menu s,o,e,w save with full/short/only/no header N goto next group :unshar :decode :patch unpack article X as N, mark as read !, ^Z Shell escape, suspend ;:AREPLY, POST;:A Q quit nn r mail reply to author of article m mail (or forward article) ;:AREDRAW;:A f post follow-up to article ^P Repeat last message :post post new article ^L, ^R Redraw screen nn-6.7.4/help/adm.upgrade10000644000175000017500000000062007351743325014703 0ustar mtpinsmtpins;:AUPGRADE NOTICE;:A You are now using nn release 6.6. The major change from release 6.3 is that the ~/.nn/rc file is no longer used, since release 6.4 onwards uses the standard .newsrc file to store information about which groups you subscribe to and which articles you have read. Since you have no .newsrc file already, your current rc file will now be converted into a .newsrc automatically... nn-6.7.4/help/adm.upgrade20000644000175000017500000000124207351743355014710 0ustar mtpinsmtpins;:AUPGRADE NOTICE;:A You are now using nn release 6.6. The major change from release 6.3 is that the ~/.nn/rc file is no longer used, since release 6.4 onwards uses the standard .newsrc file to store information about which groups you subscribe to and which articles you have read. To upgrade to release 6.6, your old rc file must be converted to the standard .newsrc format. You already have a .newsrc, but it is older than the rc file, so you may just proceed and let nn upgrade the rc file to the new format. The existing .newsrc will be saved in .newsrc.old, so you can still go back and merge parts of it into the new .newsrc in case you have special requirement. nn-6.7.4/help/help.extended0000644000175000017500000000251105750347760015167 0ustar mtpinsmtpins;:AEXTENDED COMMANDS;:A :help COMMAND give help on specific command :q! quit nn without updating .newsrc :x quit nn, mark current group as read :! SHELL-COMMAND execute SHELL-COMMAND w/o screen redraw. :admin enter administration mode :bug send a bug report :cd [DIR] change working directory to DIR :compile compile & reload kill file :coredump abort with a core dump :cost show current accounting figures :decode decode uuencoded article(s) :define N ... end define macro N :local VARIABLE [VALUE] make VARIABLE local to current group [and set to VALUE] :man read online manual :map MODE KEY COMMAND remap key or command (use ':help map' for more info) :mkdir [DIR] create directory DIR (will prompt for DIR if omitted) :print print article :pwd print current directory :rmail read incoming mail (no update) :set VARIABLE [VALUE] set or unset variable (use ':help set' for more info) :show groups HOW show group subscriptions etc. :show kill show kill file entries for current group :show map [MAP] show key mappings (MAP = #, key, menu, show) :show rc [GROUP] show (current) GROUPS .newsrc entry :sort [MODE] sort menu according to subject, age, or arrival :toggle VARIABLE toggle boolean variable :unread (N) mark current group as unread (last N articles) :unset VARIABLE clear variable :unshar unshar article(s) nn-6.7.4/help/help.sort0000644000175000017500000000156005750347764014365 0ustar mtpinsmtpins;:AMENU SORTING MODES AND COMMANDS;:A Variable: sort-mode Determines the default menu sorting method. It is a numeric value corresponding to the modes described below: :sort Sort articles on menu according to current sort-mode. :sort arrival (sort-mode 0) Sort articles in arrival, i.e. article number order. :sort subject (sort-mode 1) Articles are grouped according to subject, each article within a group is sorted according to posting date and time, and the groups are sorted according to the age of the first article in each group. :sort lexical (sort-mode 2) Articles are sorted in lexicographical order on subject. Articles with identical subjects are sorted after age. :sort age (sort-mode 3) Articles ordered after posting date only. :sort sender (sort-mode 4) Articles ordered after sender's name. Articles from same sender are sorted on age. nn-6.7.4/help/help.read0000644000175000017500000000001505750347762014301 0ustar mtpinsmtpins;:Chelp.more nn-6.7.4/help/adm.upgrade30000644000175000017500000000117207351743365014714 0ustar mtpinsmtpins;:AUPGRADE NOTICE;:A You are now using nn release 6.6. The major change from release 6.3 is that the ~/.nn/rc file is no longer used, since release 6.4 onwards uses the standard .newsrc file to store information about which groups you subscribe to and which articles you have read. To upgrade to release 6.6, your old rc file must be converted to the standard .newsrc format, and nn may do this for you if you proceed. However, the current .newsrc file is newer than the .nn/rc file, so it is likely that you have used another news reader since you last used nn, and that you just want to proceed using the existing .newsrc file. nn-6.7.4/help/adm.welcome0000644000175000017500000000106607351743435014635 0ustar mtpinsmtpins;:AWelcome to the nn news reader;:A ;:ARelease ;:D;:A Unlike the other news readers you might be familiar with, the ultimate goal of nn is "not to read news"; actually, "nn" is an acronym for "No News", and the motto of nn is: No news is good news, but nn is better. I hope that you will enjoy using nn. Three levels of online help is available: ;:A?;:A gives a quick reference guide for the current mode. ;:A:help;:A explains how to get help on specific subjects. ;:A:man;:A opens the online manual. Use ;:AQ;:A to quit nn. Have fun, Michael T. Pins nn-6.7.4/help/help.attr0000644000175000017500000000051005750347760014336 0ustar mtpinsmtpins;:AARTICLE ATTRIBUTES;:A The following attributes are used to mark articles on the menu: (blank) Unread * Selected (if terminal doesn't have standout mode or at slow speed) . Read (or saved) , Seen on menu (but not read) + Marked with `l' for later treatment = Marked with `L' for treatment on next invocation # Cancelled nn-6.7.4/help/adm.upgrade40000644000175000017500000000074505750347757014730 0ustar mtpinsmtpins;:AUPGRADE NOTICE (continued);:A Since you did not want to use the current .newsrc file although it is newer than the .nn/rc file, you will now have the option to let nn convert the rc file to .newsrc format and replace the current .newsrc with the result. The existing .newsrc file will be saved in .newsrc.old, so you will still have the opportunity to go back to using the current .newsrc file, or merge it with the .newsrc file created by nn if you have special requirements. nn-6.7.4/help/help.help0000644000175000017500000000047205750347761014324 0ustar mtpinsmtpins;:AHELP COMMAND;:A :man Read online manual :help SUBJECT Get help on specified SUBJECT Help is available on the the following subjects: commands commands that can be bound to keys extended extended commands (:command) map key mapping set variable setting show displaying various tables variables variables nn-6.7.4/help/help.map0000644000175000017500000000141515212301323014121 0ustar mtpinsmtpins;:AMAP COMMAND;:A map TABLE show contents of mapping table map menu KEY COMMAND map KEY into COMMAND in menu mode map show KEY COMMAND map KEY into COMMAND in article presentation mode map both KEY COMMAND map KEY into COMMAND in both modes map key KEY1 KEY2 map KEY1 into KEY2 (unconditionally) map #N CH1 CH2 ... define the input sequence CH1 CH2 ... as special key N ;:ASPECIAL FORMS;:A (TABLE = menu, show, both) map TABLE KEY1 as KEY2 map KEY1 into the command bound to KEY2 map TABLE KEY macro N map KEY to invoke macro number N map menu KEY article N map KEY to select item number N (base 0) on the menu ;:AKEYS;:A 0xNN the ascii character NN C the character C ^C control-C ^? DEL #N special key number N up, down, left, right: the arrow keys ;:Chelp.commands nn-6.7.4/help/help.menu0000644000175000017500000000204707351755406014336 0ustar mtpinsmtpins;:ASELECT (toggle);:A ;:AMOVE;:A a-z0-9 Specified article , Next menu line x-y Range x to y / Previous menu line x* Same subject as x SPACE Next menu page (if any) . Current article < > Prev/Next menu page @ ~ Reverse/Undo all selections ^ $ First/Last menu page =regexp Matching subjects (=. selects all) L/JJJJ Leave/Change attributes ( ) Open/Close Consolidated line ;:ASHOW SELECTED ARTICLES;:A SPACE Show (only when on last menu page) Z Show NOW, and return to this group afterwards X Show NOW, and continue with next group ;:AGOTO OTHER GROUPS;:A X Update current group, skip to next. Y Group overview N P Goto next/previous group. ~/.nn/init: G Goto named group or open a folder. Defines group B A Go back/forward in groups already read. presentation sequence. ;:AMISCELLANEOUS;:A U Unsubscribe / Subscribe toggle :man Online manual F R M Follow-up/Reply/Mail :help More online help S O E W Save articles ! Shell escape :post C Post new article / Cancel current " Change menu layout :unshar :decode :patch Unpack articles Q Quit nn nn-6.7.4/decode.c0000644000175000017500000002730610222577112013147 0ustar mtpinsmtpins/* * Decode one or more uuencoded article back to binary form. * * UNIX/NN VERSION * This version cannot be used as a stand-alone uud! * This version is made: 16 June 1989. * * From the Berkeley original, modified by MSD, RDR, JPHD & WLS. */ #include #include #include "config.h" #include "global.h" #include "save.h" #include "nn_term.h" /* decode.c */ static int strncmp_skip(char *buf, char *str, int n); static int decode_line(char *buf, register int len, int dont_write); static void inittbls(void); static int gettable(FILE * in); static void new_file(void); /* #define DEC_DEBUG *//* never define this */ char *decode_header_file = "Decode.Headers"; int decode_skip_prefix = 2; int decode_keep = 0; #define MAXCHAR 256 #define LINELEN 256 #define NORMLEN 60 /* allows for 80 encoded chars per line */ #define SEQMAX 'z' #define SEQMIN 'a' static char seqc, partn; static int first, secnd, check; #define MAX_PREFIX 10 static int prefix_lgt, set_prefix; static char prefix_str[MAX_PREFIX]; static FILE *out; static char *target; static char blank; static int chtbl[MAXCHAR], cdlen[NORMLEN + 3]; static char ofname[FILENAME], arcname[FILENAME]; static int state, arcpart; #define NO_ADVANCE 0x10 #define DECODE_TEXT 1 #define FIND_BEGIN 2 #define FIND_BEGIN_AFTER_ERROR 3 #define FIND_BEGIN_AFTER_INCLUDE 4 #define NEW_BEGIN (5 | NO_ADVANCE) #define FOUND_END (6 | NO_ADVANCE) #define FOUND_INCLUDE (7 | NO_ADVANCE) #define SKIP_LEADING 8 #define SKIP_TRAILING (9 | NO_ADVANCE) #define DECODE_ERROR (10 | NO_ADVANCE) #define OTHER_ERROR (11 | NO_ADVANCE) #define MIN_DECODE_LEADING 8 /* lines to decode ok when doing skip-leading */ #ifdef DEC_DEBUG char *state_tbl[] = { "-", "decode", "find begin", "find a/error", "find a/include", "new begin", "found end", "found include", "skip leading", "skip trail", "error", "other error" }; #endif /* * decode one line, write on out file */ static int strncmp_skip(char *buf, char *str, int n) { register int i; register char *line = buf; if (!set_prefix) return strncmp(line, str, n); if (decode_skip_prefix > MAX_PREFIX) decode_skip_prefix = MAX_PREFIX; for (i = 0; i <= decode_skip_prefix; i++, line++) { if (*line == NUL) break; if (*line == '#' || *line == ':') break; if (strncmp(line, str, n)) continue; prefix_lgt = i; if (i) strncpy(prefix_str, buf, i); set_prefix = 0; #ifdef DEC_DEBUG msg("match %s", str); user_delay(1); #endif return 0; } return 1; } static int decode_line(char *buf, register int len, int dont_write) /* len actual input line length */ /* dont_write doing leading check */ { char outl[LINELEN]; register char *bp, *ut; register int *trtbl = chtbl; register int n; register int blen; /* binary length (from decoded file) */ register int rlen; /* calculated input line length */ /* * Get the binary line length. */ if ((blen = trtbl[buf[0]]) < 0) { if (strncmp(buf, "begin", 5) == 0 || strncmp(buf, "table", 5) == 0) return NEW_BEGIN; if (state == SKIP_LEADING) return SKIP_LEADING; /* * end of uuencoded file ? */ if (strncmp(buf, "end", 3) == 0) return FOUND_END; /* * end of current file ? : get next one. */ if (strncmp(buf, "include", 7) == 0) return FOUND_INCLUDE; /* * trailing garbage */ return SKIP_TRAILING; } /* * Some common endings that survive to this point. Could fail if a line * really started with one of these, but this is sufficiently unlikely, * plus it handles lots of news post formats. */ if (state != SKIP_LEADING && (strncmp(buf, "END", 3) == 0 || strncmp(buf, "CUT", 3) == 0)) return SKIP_TRAILING; rlen = cdlen[blen]; if (len < rlen) goto d_err; if (len > (rlen + 2)) goto d_err; /* line too long */ /* * Is it the empty line before the end line ? */ if (blen == 0) return state; /* * Pad with blanks. */ for (bp = buf + len, n = rlen - len; --n >= 0;) *bp++ = blank; /* * Verify */ for (n = rlen, bp = buf; --n >= 0; bp++) if (trtbl[*bp] < 0) { #ifdef DEC_DEBUG msg("%s - verify failed %d '%.30s'", state_tbl[state & 0xf], rlen - n, buf); user_delay(2); #endif goto d_err; } /* * Check for uuencodes that append a 'z' to each line.... */ if (check) { if (secnd) { secnd = 0; if (buf[rlen] == SEQMAX) { check = 0; } } else if (first) { first = 0; secnd = 1; if (buf[rlen] != SEQMAX) { check = 0; } } } /* * There we check. */ if (check) { if (buf[rlen] != seqc) { #ifdef DEC_DEBUG msg("check failed %d != %d", buf[rlen], seqc); user_delay(1); #endif goto d_err; } if (--seqc < SEQMIN) seqc = SEQMAX; } if (dont_write) return DECODE_TEXT; /* * output a group of 3 bytes (4 input characters). */ ut = outl; n = blen; bp = &buf[1]; while (--n >= 0) { *(ut++) = trtbl[*bp] << 2 | trtbl[bp[1]] >> 4; if (n > 0) { *(ut++) = (trtbl[bp[1]] << 4) | (trtbl[bp[2]] >> 2); n--; } if (n > 0) { *(ut++) = trtbl[bp[2]] << 6 | trtbl[bp[3]]; n--; } bp += 4; } if ((int) fwrite(outl, 1, blen, out) <= 0) { msg("Error on writing decoded file"); return OTHER_ERROR; } return DECODE_TEXT; d_err: if (state == SKIP_LEADING) return SKIP_LEADING; return DECODE_ERROR; } /* * Install the table in memory for later use. */ static void inittbls(void) { register int i, j; /* * Set up the default translation table. */ for (i = 0; i < ' '; i++) chtbl[i] = -1; for (i = ' ', j = 0; i < ' ' + 64; i++, j++) chtbl[i] = j; for (i = ' ' + 64; i < MAXCHAR; i++) chtbl[i] = -1; chtbl['`'] = chtbl[' ']; /* common mutation */ chtbl['~'] = chtbl['^']; /* an other common mutation */ blank = ' '; /* * set up the line length table, to avoid computing lotsa * and / ... */ cdlen[0] = 1; for (i = 1, j = 5; i <= NORMLEN; i += 3, j += 4) cdlen[i] = (cdlen[i + 1] = (cdlen[i + 2] = j)); } static int gettable(FILE * in) { char buf[LINELEN], *line; register int c, n = 0; register char *cpt; for (c = 0; c < MAXCHAR; c++) chtbl[c] = -1; for (;;) { if (fgets(buf, sizeof buf, in) == NULL) { msg("EOF while in translation table."); return -1; } line = buf + prefix_lgt; if ((prefix_lgt > 0 && strncmp(buf, prefix_str, prefix_lgt)) || strncmp(line, "begin", 5) == 0) { msg("Incomplete translation table."); return -1; } cpt = line + strlen(line) - 1; *cpt = ' '; while (*(cpt) == ' ') { *cpt = 0; cpt--; } cpt = line; while (*cpt) { c = *cpt; if (chtbl[c] != -1) { msg("Duplicate char in translation table."); return -1; } if (n == 0) blank = c; chtbl[c] = n++; if (n >= 64) return 0; cpt++; } } } static void new_file(void) { out = NULL; seqc = SEQMAX; partn = 'a'; check = 1; first = 1; secnd = 0; state = FIND_BEGIN; prefix_lgt = 0; set_prefix = decode_skip_prefix; arcpart = 0; inittbls(); } void uud_start(char *dir) { target = dir; new_file(); } void uud_end(void) { if (out != NULL) { fclose(out); if (decode_keep) { msg("%s INCOMPLETE -- removed", arcname); unlink(ofname); user_delay(5); } out = NULL; } } int uudecode(register article_header * ah, FILE * in) { mode_t mode; int onedone, len, lead_check = 0; char buf[LINELEN], part[2], *line; long real_size, start_offset; long expect_size; onedone = 0; /* * search for header or translation table line. */ start_offset = ftell(in); for (;;) { if ((state & NO_ADVANCE) == 0) { if (ftell(in) >= ah->lpos) break; if (fgets(buf, sizeof buf, in) == NULL) break; } if (s_keyboard) return -1; len = strlen(buf); if (len > 0 && buf[len - 1] == NL) buf[--len] = NUL; #ifdef DEC_DEBUG if (state != ostate) { msg("%s->%s - '%.30s'", state_tbl[ostate & 0xf], state_tbl[state & 0xf], buf); user_delay(2); ostate = state; } #endif switch (state) { case NEW_BEGIN: if (out != NULL) { uud_end(); user_delay(5); } new_file(); /* FALLTHRU */ case FIND_BEGIN: case FIND_BEGIN_AFTER_ERROR: case FIND_BEGIN_AFTER_INCLUDE: set_prefix = decode_skip_prefix; if (strncmp_skip(buf, "table", 5) == 0) { gettable(in); continue; } if (strncmp_skip(buf, "begin", 5)) continue; line = buf + prefix_lgt; if (state == FIND_BEGIN_AFTER_INCLUDE) { if (sscanf(line, "begin part %1s%s", part, arcname) != 2) { msg("Invalid 'begin' line after 'include'"); continue; } partn++; if (partn > 'z') partn = 'a'; if (part[0] != partn) { msg("PARTS NOT IN SEQUENCE: %s -- removed", arcname); user_delay(5); fclose(out); unlink(ofname); new_file(); state = FIND_BEGIN_AFTER_ERROR; goto err; } } else { if (sscanf(line, "begin %o %s", (unsigned int *) &mode, arcname) != 2) continue; if (target != NULL) sprintf(ofname, "%s%s", target, arcname); else strcpy(ofname, arcname); if ((out = open_file(ofname, OPEN_CREATE)) == NULL) { msg("Cannot create file: %s", ofname); goto err; } chmod(ofname, mode & 0666); if (decode_header_file) store_header(ah, in, target, decode_header_file); } state = DECODE_TEXT; continue; case SKIP_LEADING: if (len > prefix_lgt && (!prefix_lgt || strncmp(buf, prefix_str, prefix_lgt) == 0)) { state = decode_line(buf + prefix_lgt, len - prefix_lgt, 1); if (state == DECODE_TEXT) { if (++lead_check == MIN_DECODE_LEADING) { fseek(in, start_offset, 0); continue; } state = SKIP_LEADING; continue; } } else { set_prefix = decode_skip_prefix; if (strncmp_skip(buf, "begin", 5) == 0 || strncmp_skip(buf, "table", 5) == 0) state = NEW_BEGIN; } lead_check = 0; start_offset = ftell(in); continue; case DECODE_TEXT: if (len <= prefix_lgt || (prefix_lgt > 0 && strncmp(buf, prefix_str, prefix_lgt))) { state = SKIP_TRAILING; continue; } if (onedone == 0) { msg("Decoding%s: %s (part %d)", prefix_lgt ? " & Unsharing" : "", arcname, ++arcpart); onedone = 1; } state = decode_line(buf + prefix_lgt, len - prefix_lgt, 0); continue; case FOUND_END: real_size = ftell(out); fclose(out); if (ftell(in) >= ah->lpos || fgets(buf, sizeof buf, in) == NULL) { new_file(); break; } if ((!prefix_lgt || strncmp(buf, prefix_str, prefix_lgt) == 0) && sscanf(buf + prefix_lgt, "size%ld", &expect_size) == 1 && real_size != expect_size) { msg("%s decoded with wrong size %ld (exp. %ld)", arcname, real_size, expect_size); user_delay(3); } else { msg("%s complete", arcname); user_delay(1); } new_file(); state = NEW_BEGIN; continue; case FOUND_INCLUDE: state = FIND_BEGIN_AFTER_INCLUDE; return 0; case SKIP_TRAILING: state = SKIP_LEADING; return 0; case DECODE_ERROR: state = SKIP_TRAILING; continue; case OTHER_ERROR: fclose(out); new_file(); state = FIND_BEGIN_AFTER_ERROR; goto err; } break; /* break in switch => break in loop */ } if (onedone) { if (state == DECODE_TEXT) state = SKIP_LEADING; return 0; } if (state == FIND_BEGIN_AFTER_ERROR) return -1; msg("No 'begin' line"); err: user_delay(2); return -1; } nn-6.7.4/data.h0000644000175000017500000001611610220660074012635 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Internal representation of the master, group, and article * information read from the database. * * For each article read from the database, an article_header * structure is initialized. */ #ifndef _NN_DATA_H #define _NN_DATA_H 1 /* * The magic number allows us to change the format of the database * and tell the users about it */ #define NNDB_MAGIC 0x4e4e2302 /* NN#2 */ /* * global master data */ #define DB_LOCK_MESSAGE 80 typedef struct { int32 db_magic; char db_lock[DB_LOCK_MESSAGE]; time_t db_created; /* when database was last built */ time_t last_scan; /* age of active file at last scan */ off_t last_size; /* size of active file at last scan */ group_number number_of_groups; int32 free_groups; } master_header; /* * group information */ typedef struct group_header { /* this part of the header is read from the MASTER file */ flag_type master_flag;/* master's flags */ #define M_VALID FLAG(1) /* group present in active file */ #define M_CONTROL FLAG(2) /* group is control group */ #define M_NO_DIRECTORY FLAG(3) /* group directory not found */ #define M_ALWAYS_DIGEST FLAG(4) /* D in GROUPS */ #define M_NEVER_DIGEST FLAG(5) /* N in GROUPS */ #define M_EXPIRE FLAG(6) /* expire in progress or pending */ #define M_BLOCKED FLAG(7) /* don't trust this entry */ #define M_MUST_CLEAN FLAG(8) /* group should be cleaned */ #define M_MODERATED FLAG(9) /* group is moderated */ #define M_ALIASED FLAG(10)/* =xxx in active file */ #define M_NOPOST FLAG(11) /* 'n' in active file */ #define M_AUTO_RECOLLECT FLAG(12) /* R in GROUPS */ #define M_AUTO_ARCHIVE FLAG(13)/* >file in GROUPS */ #define M_INCLUDE_OLD FLAG(14)/* O in GROUPS */ #define M_IGNORE_G FLAG(15)/* ignore this group (GROUPS X) */ #define M_IGNORE_A FLAG(16)/* ignore this group (ACTIVE x) */ #define M_IGNORE_GROUP (M_IGNORE_G | M_IGNORE_A) article_number first_db_article; /* min article in db */ article_number last_db_article; /* max article in db */ article_number first_a_article; /* min article in active */ article_number last_a_article; /* max article in active */ long index_write_offset; long data_write_offset; time_t creation_time; /* when group was created */ int group_name_length; /* this part is initialized during reading of the GROUPS file */ /* DO NOT CHANGE THE POSITION OF group_flag AS THE FIRST FIELD */ /* AFTER THE PART WHICH IS SAVED IN THE MASTER FILE */ flag_type group_flag; /* client's flags */ #define G_UNSUBSCRIBED FLAG(1) /* ! in .newsrc */ #define G_READ FLAG(2) /* group has been visited */ #define G_FAKED FLAG(3) /* faked group - not in .newsrc */ #define G_NEW FLAG(5) /* new group */ #define G_FOLDER FLAG(6) /* "group" is a folder file */ #define G_DIRECTORY FLAG(7) /* "group" is directory */ #define G_SEQUENCE FLAG(8) /* in presentation sequence */ #define G_DONE FLAG(9) /* sequence is done with group */ #define G_MAILBOX FLAG(10)/* user's mail box file */ #define G_MERGE_HEAD FLAG(11)/* merged group head */ #define G_MERGE_SUB FLAG(12)/* merged group sub element */ #define G_MERGED (G_MERGE_HEAD | G_MERGE_SUB) #define G_COUNTED FLAG(13)/* counted in unread_count */ group_number group_num; group_number preseq_index; char *group_name; char *archive_file; article_number first_article; article_number last_article; article_number current_first; /* first article currently read in */ struct group_header *next_group; /* group sequence */ struct group_header *prev_group; struct group_header *merge_with; /* merged groups */ char *kill_list; char *save_file; /* default save file from init */ char *enter_macro; struct group_header *newsrc_seq; /* .newsrc sequence */ char *newsrc_line;/* .newsrc line for this group */ char *newsrc_orig;/* original newsrc_line if changed */ char *select_line;/* .nn/select line for this group */ char *select_orig;/* original select_line if changed */ article_number unread_count; /* number of unread articles */ } group_header; /* size of the part of the group header placed on backing storage */ #define SAVED_GROUP_HEADER_SIZE(group) \ (((char *)(&((group).group_flag))) - ((char *)(&(group)))) /* * Internal article header information. */ typedef struct { union { article_number au_number; /* article number in the group */ char *au_string; } au_union; group_header *a_group; /* if merged article menu */ /* indexes to header line text */ long hpos; /* first byte of header */ long fpos; /* first byte in article text */ off_t lpos; /* last pos of article */ time_stamp t_stamp; /* encoded time_stamp */ time_stamp root_t_stamp; /* subject's time_stamp */ char *sender; /* sender's name */ char *subject; /* subject (w/o Re:) */ int16 lines; /* no of lines */ int8 replies; /* no of Re: */ int8 subj_length;/* length of subject */ int8 name_length;/* length of sender */ int8 menu_line; /* current line on menu */ attr_type attr; /* attributes: */ attr_type disp_attr; /* currently displayed attr. */ /* warning: notice relation between A_SELECT and A_AUTO_SELECT */ #define A_READ 0x01 /* article has been read */ #define A_SEEN 0x02 /* article presented on menu */ #define A_LEAVE 0x03 /* marked for later activity */ #define A_LEAVE_NEXT 0x04 /* marked for next invokation */ #define A_CANCEL 0x05 /* folder entry cancelled */ #define A_KILL 0x06 /* eliminate article */ #define A_SELECT 0x08 /* article has been selected */ #define A_AUTO_SELECT 0x09 /* will match & A_SELECT */ #define A_NOT_DISPLAYED 0x7f /* not currently on menu */ flag_type flag; /* attributes and flags: */ #define A_SAME FLAG(1) /* same subject as prev. article */ #define A_ALMOST_SAME FLAG(2) /* A_SAME (match-limit) */ #define A_ROOT_ART FLAG(3) /* root article in subject */ #define A_NEXT_SAME FLAG(4) /* next is same subject */ #define A_DIGEST FLAG(5) /* digest sub article */ #define A_FULL_DIGEST FLAG(6) /* full digest */ #define A_FOLDER FLAG(7) /* article file = "folder_path" */ #define A_FAKED FLAG(8) /* only 'number' is valid */ #define A_ST_FILED FLAG(10)/* articles is saved */ #define A_ST_REPLY FLAG(11)/* sent reply to article */ #define A_ST_FOLLOW FLAG(12)/* sent followup to article */ #define A_CLOSED FLAG(13) /* subject closed on menu */ #define A_HIDE FLAG(14)/* hide subject on menu */ } article_header; #define a_number au_union.au_number #define a_string au_union.au_string #endif /* _NN_DATA_H */ nn-6.7.4/macro.c0000644000175000017500000003610710222575666013040 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * Macro parsing and execution. */ #include #include #include #include "config.h" #include "global.h" #include "db.h" #include "init.h" #include "keymap.h" #include "macro.h" #include "menu.h" #include "nn_term.h" #include "variable.h" /* macro.c */ static void m_error(char *fmt, char *arg); static void m_new(int t); static int parse_word(char *w); static int parse_line(char *lp); static struct macro *m_call(int who); static void macro_dbg(void); int in_menu_mode = 0; int get_from_macro = 0; int macro_debug = 0; char *dflt_enter_macro = NULL; char *start_up_macro = NULL; #ifdef M_BREAK #undef M_BREAK #endif #define M_DUMMY 0 /* do nothing (end of branch) */ #define M_COMMAND 1 /* a command (get_c()) */ #define M_KEY 2 /* a key stroke (get_c()) */ #define M_STRING 3 /* a string (get_s()) */ #define M_INPUT 4 /* take input from keyboard (get_c/get_s) */ #define M_YES 5 /* answer yes to confirmation */ #define M_NO 6 /* answer no to confirmation and break */ /* -- if neither are present, take input */ #define M_PUTS 7 /* puts "..." */ #define M_PROMPT 8 /* prompt(...) */ #define M_ECHO 9 /* msg(...) */ #define M_IS_MENU 10 /* in menu mode ? */ #define M_IS_SHOW 11 /* in reading mode ? */ #define M_IS_GROUP 12 /* are we in a news group ? */ #define M_IS_FOLDER 13 /* are we in a folder ? */ #define M_CONFIRM 14 /* ask for confirmation to procede */ #define M_REJECT 15 /* ask for !confirmation to procede */ #define M_VARTEST 16 /* test value of variable */ #define M_BREAK 17 /* exit from macroes */ #define M_RETURN 18 /* return from this macro */ #define M_SET_COMMAND 19 /* set/unset command */ struct macro { int m_type; /* entry type */ union { int mu_int; /* command or char */ char *mu_string; /* string for get_s */ struct macro *mu_branch; /* false conditional */ } m_value; struct macro *m_next; /* next macro element */ }; #define m_int m_value.mu_int #define m_string m_value.mu_string #define m_branch m_value.mu_branch #define NUM_MACRO 101 /* numbered macros */ #define ANON_MACRO 100 /* anonymous macroes */ #define NMACRO (NUM_MACRO + ANON_MACRO) #define MSTACK 5 /* max nesting level */ static struct macro *macro[NMACRO + 1]; /* macro table */ /* the extra slot is for entry macroes */ static struct macro *mstack[MSTACK]; /* macro stack */ static int cstack[MSTACK + 1]; static int m_level = 0; static struct macro *m = NULL; /* current macro */ static int no_advance = 0; static int changed_prompt = 0; static int cur_m; #define MERROR ((struct macro *)1) static void m_error(char *fmt, char *arg) { char buf[80]; if (arg) { sprintf(buf, fmt, arg); fmt = buf; } init_message("Error in macro %d: %s", cur_m, fmt); } void init_macro(void) { int n; for (n = 0; n <= NMACRO; n++) macro[n] = NULL; } static void m_new(int t) { struct macro *m1; m1 = newobj(struct macro, 1); if (m == NULL) m = macro[cur_m] = m1; else { m->m_next = m1; m = m1; } m->m_type = t; m->m_next = NULL; } /* * Define macro "id" reading from file f until "end" * * Macro definition syntax: * define * * end * * Id string interpretation: * NULL use next free numbered macro * Return: pointer to macro * "nnn" nnn>=0 Numbered macro nnn * Return: pointer to macro * "-1" entry macro * Return: pointer to macro * "-2" anonymous macro * Return: K_MACRO code or K_UNBOUND on error */ static int initial_set_commands; static int parse_word(char *w) { int cmd; register struct macro *m1; if (m && m->m_type == M_COMMAND && m->m_int == (GETC_COMMAND | K_MACRO)) { if (isdigit(*w)) { m->m_int |= atoi(w); goto ok; } m_error("macro number missing", (char *) NULL); return 1; } if (*w == '"') { if (m == NULL || (m->m_type != M_PROMPT && m->m_type != M_ECHO && m->m_type != M_PUTS)) m_new(M_STRING); m->m_string = copy_str(w + 1); goto ok; } if (*w == '\'') { m_new(M_KEY); m->m_int = parse_key(w + 1); goto ok; } if (*w == '?') { if (strchr(w, '=')) { m->m_type = M_VARTEST; m1 = m; m_new(M_DUMMY); m->m_branch = m1->m_branch; m1->m_string = copy_str(w + 1); goto ok; } switch (w[1]) { case 'f': /* ?folder */ cmd = M_IS_FOLDER; break; case 'g': /* ?group */ cmd = M_IS_GROUP; break; case 'm': /* ?menu */ cmd = M_IS_MENU; break; case 'n': /* ?no */ cmd = M_REJECT; break; case 's': /* ?show */ cmd = M_IS_SHOW; break; case 'y': /* ?yes */ cmd = M_CONFIRM; break; default: m_error("unknown conditional %s", w - 1); return 1; } m->m_type = cmd; goto ok; } if ((cmd = lookup_command(w, (K_ONLY_MENU | K_ONLY_MORE))) > K_INVALID) { m_new(M_COMMAND); m->m_int = GETC_COMMAND | cmd; goto ok; } if (strcmp(w, "prompt") == 0) { m_new(M_PROMPT); m->m_string = "?"; goto ok; } if (strcmp(w, "echo") == 0) { m_new(M_ECHO); m->m_string = "ups"; goto ok; } if (strcmp(w, "puts") == 0) { m_new(M_PUTS); m->m_string = ""; goto ok; } if (strcmp(w, "input") == 0) { m_new(M_INPUT); goto ok; } if (strcmp(w, "yes") == 0) { m_new(M_YES); goto ok; } if (strcmp(w, "no") == 0) { m_new(M_NO); goto ok; } if (strcmp(w, "break") == 0) { m_new(M_BREAK); goto ok; } if (strcmp(w, "return") == 0) { m_new(M_RETURN); goto ok; } m_error("Unknown word >>%s<<", w); return 1; ok: return 0; } static int parse_line(char *lp) { char *word; struct macro *m1, *branch = NULL; while (*lp) { if (*lp == '#') break; if (*lp == ':') { lp++; if (initial_set_commands) { #ifdef REL_640_COMPAT if (strncmp(lp, "local", 5) == 0 || strncmp(lp, "set", 3) == 0 || strncmp(lp, "unset", 5) == 0) { #else if (lp[0] == ':') lp++; else { #endif m_new(M_SET_COMMAND); m->m_string = copy_str(lp); break; } initial_set_commands = 0; } m_new(M_COMMAND); m->m_int = GETC_COMMAND | K_EXTENDED_CMD; m_new(M_STRING); m->m_string = copy_str(lp); break; } initial_set_commands = 0; if (*lp == '?') { m_new(M_IS_MENU); if (branch == NULL) { m1 = m; m_new(M_DUMMY); branch = m; m = m1; } m->m_branch = branch; } word = lp; if (*lp == '"') { do lp++; while (*lp && *lp != '"'); } else if (*lp == '\'') { do lp++; while (*lp && *lp != '\''); } else while (*lp && !isspace(*lp)) lp++; if (*lp) { *lp++ = NUL; while (*lp && isspace(*lp)) lp++; } if (parse_word(word)) return 1; } if (branch) { m->m_next = branch; m = branch; } return 0; } char * m_define(char *id, FILE * f) { char line[1024], *lp, skip; int type = 0; if (id) { cur_m = atoi(id); if (cur_m == -1) { cur_m = NMACRO; /* special slot for this purpose */ } else if (cur_m == -2) { for (cur_m = NUM_MACRO; cur_m < NMACRO; cur_m++) if (macro[cur_m] == NULL) break; if (cur_m == NMACRO) { init_message("No unused macro slots"); return (char *) K_UNBOUND; } type = 1; } else if (cur_m < 0 || cur_m >= NUM_MACRO) { m_error("macro number out of range\n", id); return (char *) 0; } } else { for (cur_m = 0; cur_m < NUM_MACRO; cur_m++) if (macro[cur_m] == NULL) break; if (cur_m == NUM_MACRO) { init_message("No unused macro numbers"); return (char *) 0; } } if (f == NULL) { clrdisp(); tprintf("DEFINE %sMACRO %d -- END WITH 'end'\n\n\r", cur_m >= NUM_MACRO ? "ANONYMOUS " : "", cur_m >= NUM_MACRO ? cur_m - NUM_MACRO : cur_m); unset_raw(); f = stdin; } m = NULL; skip = 0; initial_set_commands = (cur_m == NMACRO); while (fgets_multi(line, 1024, f)) { for (lp = line; *lp && isspace(*lp); lp++); if (*lp == NUL) continue; if (*lp == ')' || strncmp(lp, "end", 3) == 0) goto out; if (!skip && parse_line(lp)) { macro[cur_m] = NULL; skip++; } } if (f != stdin) m_error("end missing", (char *) NULL); out: if (f == stdin) nn_raw(); m = NULL; return type == 0 ? (char *) macro[cur_m] : (char *) (K_MACRO | cur_m); } static char * m_get_macro(char *id) { if (id) { cur_m = atoi(id); if (cur_m < 0 || cur_m >= NMACRO) { m_error("macro number out of range\n", id); return (char *) 0; } } return (char *) macro[cur_m]; } char * parse_enter_macro(FILE * f, register int c) { register char *gp; char other[FILENAME]; group_header *gh; static char *last_defined = NULL; while (c != EOF && c != NL && (!isascii(c) || isspace(c))) c = getc(f); if (c == ')') return last_defined; if (c == EOF) return (char *) NULL; if (c == NL) return last_defined = m_define("-1", f); gp = other; do { *gp++ = c; c = getc(f); } while (c != EOF && c != ')' && isascii(c) && !isspace(c)); *gp = NUL; if ((gh = lookup(other))) return gh->enter_macro; return m_get_macro(other); } /* * Invoke macro # N */ void m_invoke(int n) { if (n == -2) { n = NMACRO; if ((macro[n] = (struct macro *) start_up_macro) == NULL) return; } else if (n < 0) { n = NMACRO; if ((macro[n] = (struct macro *) (current_group->enter_macro)) == NULL) if ((macro[n] = (struct macro *) dflt_enter_macro) == NULL) return; } else if (n >= NMACRO || macro[n] == NULL) { msg("undefined macro %d", n); return; } if (m_level == 0) no_advance = 0; else if (m == NULL) m_level--; else { if (m_level > MSTACK) { msg("Macro stack overflow"); m_break(); return; } mstack[m_level] = m; cstack[m_level] = cur_m; } m_level++; cur_m = n; m = macro[cur_m]; while (m && m->m_type == M_SET_COMMAND) { char buffer[128]; strcpy(buffer, m->m_string); if (macro_debug) { msg(":%s", buffer); user_delay(1); } parse_command(buffer, AC_UNCHANGED, (FILE *) NULL); m = m->m_next; } } void m_startinput(void) { no_advance = 1; } void m_endinput(void) { if (no_advance) { no_advance = 0; if (m && m->m_type == M_INPUT) m = m->m_next; } } void m_advinput(void) { if (m && m->m_type == M_INPUT) m = m->m_next; } static struct macro * m_call(int who) { struct macro *m1; for (;;) { while (m == NULL && m_level > 1) { m_level--; m = mstack[m_level]; cur_m = cstack[m_level]; } if (m == NULL) { if (macro_debug) msg("end"); m_break(); return NULL; } if (macro_debug) macro_dbg(); if (who == 3) { if (m->m_type == M_YES || m->m_type == M_NO) goto out; return NULL; } switch (m->m_type) { case M_COMMAND: if (m->m_int == (GETC_COMMAND | K_REDRAW)) changed_prompt = 0; /* FALLTHRU */ case M_KEY: if (who == 1) goto out; goto err; case M_STRING: if (who == 2) goto out; goto err; case M_INPUT: if (no_advance) return m; goto out; case M_YES: case M_NO: case M_DUMMY: break; case M_PUTS: tprintf("%s", m->m_string); fl; break; case M_PROMPT: if (m->m_string[0] == NUL) { changed_prompt = 0; break; } if (!changed_prompt) prompt(P_SAVE); changed_prompt = 1; prompt("\1%s\1 ", m->m_string); break; case M_ECHO: msg(m->m_string); restore_xy(); break; case M_IS_MENU: if (!in_menu_mode) m = m->m_branch; break; case M_IS_SHOW: if (in_menu_mode) m = m->m_branch; break; case M_IS_GROUP: if (current_group->group_flag & G_FOLDER) m = m->m_branch; break; case M_IS_FOLDER: if ((current_group->group_flag & G_FOLDER) == 0) m = m->m_branch; break; case M_CONFIRM: if (yes(0) == 0) m = m->m_branch; break; case M_REJECT: if (yes(0) == 1) m = m->m_branch; break; case M_VARTEST: m1 = m; m = m->m_next; switch (test_variable(m1->m_string)) { case 0: m = m->m_branch; break; case -1: goto err1; } break; case M_RETURN: m = NULL; continue; case M_BREAK: goto term; } if (m) m = m->m_next; } out: m1 = m; m = m->m_next; no_advance = 0; return m1; err: msg("Error in macro %d", cur_m); err1: user_delay(1); m_break(); return MERROR; term: m_break(); return NULL; } void m_break_entry(void) { if (current_group->enter_macro || dflt_enter_macro) m = NULL; } void m_break(void) { if (changed_prompt) prompt(P_RESTORE); changed_prompt = 0; m = NULL; m_level = 0; } static void macro_dbg(void) { char *name = NULL; switch (m->m_type) { case M_COMMAND: msg("COMMAND: %s", command_name(m->m_int)); goto delay; case M_KEY: msg("KEY: %s", key_name((key_type) (m->m_int))); goto delay; case M_STRING: msg("STRING: %s", m->m_string); goto delay; case M_INPUT: name = "input"; break; case M_YES: name = "yes"; break; case M_NO: name = "no"; break; case M_DUMMY: name = "dummy"; break; case M_PROMPT: msg("PROMPT: %s", m->m_string); goto delay; case M_ECHO: msg("ECHO: %s", m->m_string); goto delay; case M_IS_MENU: msg("?menu => %d", in_menu_mode); goto delay; case M_IS_SHOW: msg("?show => %d", !in_menu_mode); goto delay; case M_IS_GROUP: msg("?group => %d", (current_group->group_flag & G_FOLDER) == 0); goto delay; case M_IS_FOLDER: msg("?group => %d", (current_group->group_flag & G_FOLDER)); goto delay; case M_CONFIRM: name = "?yes"; break; case M_REJECT: name = "?no"; break; case M_VARTEST: msg("?%s => %d", m->m_string, test_variable(m->m_string)); goto delay; case M_RETURN: name = "return"; break; case M_BREAK: name = "break"; break; } msg(name); delay: user_delay(1); } /* * Macro processing for get_c() */ int m_getc(int *cp) { struct macro *m1; get_from_macro = 0; if (m_level && (m1 = m_call(1))) { if (m1 == MERROR) return 2; if (m1->m_type == M_INPUT) return 0; *cp = m1->m_int; get_from_macro = 1; return 1; } return 0; } /* * Macro processing for get_s() */ int m_gets(char *s) { struct macro *m1; get_from_macro = 0; if (m_level && (m1 = m_call(2))) { if (m1 == MERROR) return 2; if (m1->m_type == M_INPUT) return 0; strcpy(s, m1->m_string); get_from_macro = 1; return 1; } return 0; } /* * Macro processing for yes() */ int m_yes(void) { struct macro *m1; if (m) if (m->m_type == M_CONFIRM || m->m_type == M_REJECT) return 3; if (m_level) { if ((m1 = m_call(3))) { if (m1->m_type == M_NO) return 1; else return 2; } else { return 3; } } return 0; } nn-6.7.4/hash.c0000644000175000017500000001114010222444256012636 0ustar mtpinsmtpins/*******************WARNING********************* This is a *MODIFIED* version of Geoff Coller's proof-of-concept NOV implementation. It has been modified to support threading directly from a file handle to a NNTP server without a temporary file. This is not a complete distribution. We have only distributed enough to support NN's needs. The original version came from world.std.com:/src/news/nov.dist.tar.Z and was dated 11 Aug 1993. In any case, bugs you find here are probably my fault, as I've trimmed a fair bit of unused code. -Peter Wemm */ /* * Copyright (c) Geoffrey Collyer 1992, 1993. * All rights reserved. * Written by Geoffrey Collyer. * Thanks to UUNET Communications Services Inc for financial support. * * This software is not subject to any license of the American Telephone * and Telegraph Company, the Regents of the University of California, or * the Free Software Foundation. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * * 4. This notice may not be removed or altered. */ /* * general-purpose in-core hashing, normal interface (wrapper) */ #include #include "config.h" #include "hash.h" #include "hdbm.h" #ifdef notdef static unsigned /* not yet taken modulus table size */ hashdef(register HASHDATUM key) { register unsigned hash = 0; register char c; while ((c = *key++) != '\0') hash += c; return hash; } #endif HASHTABLE * hashcreate(unsigned size, unsigned (*hashfunc) ()) /* size a crude guide to size */ { return hdbmcreate(size, hashfunc); } void hashdestroy(register HASHTABLE * tbl) { hdbmdestroy(tbl); } int hashstore(HASHTABLE * tbl, register HASHDATUM key, register HASHDATUM data) { register HDBMDATUM hdbmkey, hdbmdata; hdbmkey.dat_ptr = key; /* actually a string */ hdbmkey.dat_len = strlen(key); hdbmdata.dat_ptr = data; /* just an address */ hdbmdata.dat_len = 0; /* no promises */ return hdbmstore(tbl, hdbmkey, hdbmdata); } #if 0 static HASHDATUM(*hashalloc) (); static HDBMDATUM hdbmalloc(HDBMDATUM key) { /* hdbm->hash->hdbm allocator translator */ register HDBMDATUM hdbmdata; hdbmdata.dat_ptr = (*hashalloc) (key.dat_ptr); hdbmdata.dat_len = 0; /* just a string */ return hdbmdata; } /* return any existing entry for key; otherwise call allocator to make one */ static HASHDATUM hashentry(register HASHTABLE * tbl, HASHDATUM key, HASHDATUM(*allocator) ()) { register HDBMDATUM hdbmkey, hdbmdata; hdbmkey.dat_ptr = key; /* just a string */ hdbmkey.dat_len = strlen(key); hashalloc = allocator; hdbmdata = hdbmentry(tbl, hdbmkey, hdbmalloc); return hdbmdata.dat_ptr; } #endif HASHDATUM /* data corresponding to key */ hashfetch(HASHTABLE * tbl, register HASHDATUM key) { register HDBMDATUM hdbmkey, hdbmdata; hdbmkey.dat_ptr = key; /* actually a string */ hdbmkey.dat_len = strlen(key); hdbmdata = hdbmfetch(tbl, hdbmkey); return hdbmdata.dat_ptr; /* just an address */ } int hashdelete(HASHTABLE * tbl, register HASHDATUM key) { register HDBMDATUM hdbmkey; hdbmkey.dat_ptr = key; /* actually a string */ hdbmkey.dat_len = strlen(key); return hdbmdelete(tbl, hdbmkey); } struct translate { char *realhook; int (*func) (); }; static int hdbmtohash(HDBMDATUM key, HDBMDATUM data, char *hook) { register struct translate *thp = (struct translate *) hook; (*thp->func) (key.dat_ptr, data.dat_ptr, thp->realhook); } /* * arrange that at each node, hdbmtohash gets called to map the * HDBMDATUM arguments to HASHDATUM arguments. this also demonstrates * how to use the hook argument. */ void hashwalk(HASHTABLE * tbl, int (*nodefunc) (), char *hook) /* hook (void *) really */ { struct translate transhook; register struct translate *tp = &transhook; tp->realhook = hook; tp->func = nodefunc; hdbmwalk(tbl, hdbmtohash, (char *) tp); } nn-6.7.4/match.h0000644000175000017500000000062407751420627013032 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ #include "regexp.h" void fold_string(register char *); int strmatch_fold(char *, register char *); int strmatch(char *, register char *); int strmatch_cf(char *, char *); int regexec_fold(register regexp *, char *); int regexec_cf(register regexp *, char *); nn-6.7.4/menu.h0000644000175000017500000000475107751420627012707 0ustar mtpinsmtpins/* * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved. * Copyright (c) 1996-2003 Michael T Pins. All rights reserved. * * Various module return codes. */ #ifndef _NN_MENU_H #define _NN_MENU_H 1 /* menu commands */ #define ME_QUIT 0 /* quit nn */ #define ME_NEXT 1 /* continue to next group */ #define ME_PREV 3 /* previous group */ #define ME_NO_ARTICLES 4 /* no articles in group */ #define ME_REDRAW 5 /* redraw screen after return */ #define ME_NO_REDRAW 6 /* screen is not corrupted */ #define ME_REENTER_GROUP 7 /* reenter after .newsrc update */ /* more commands */ #define MC_QUIT 0 /* quit nn */ #define MC_NEXT 1 /* next article */ #define MC_MENU 2 /* return to menu */ #define MC_PREV 3 /* previous article */ #define MC_NEXTSUBJ 4 /* show next subject */ #define MC_ALLSUBJ 5 /* show all with same subject */ #define MC_NEXTGROUP 6 /* next group, no read */ #define MC_READGROUP 7 /* next group, mark as read */ #define MC_PREVIEW_NEXT 8 /* preview next article */ #define MC_PREVIEW_OTHER 9 /* preview another article */ #define MC_REDRAW 10 /* redraw screen after return */ #define MC_NO_REDRAW 11 /* screen is not corrupted */ #define MC_BACK_ART 12 /* back one article (don't deselect cur) */ #define MC_FORW_ART 13 /* forward one article (deselect cur) */ #define MC_DO_KILL 14 /* did kill-select kill */ #define MC_DO_SELECT 15 /* did kill-select selection */ #define MC_REENTER_GROUP 16 /* reenter after .newsrc update */ /* more modes */ #define MM_NORMAL 0x0000 /* show article */ #define MM_DIGEST 0x0001 /* show full digest */ #define MM_PREVIOUS 0x0010 /* previous article exists */ #define MM_LAST_SELECTED 0x0020 /* last selected article in group */ #define MM_LAST_GROUP 0x0040 /* last group */ #define MM_PREVIEW 0x0080 /* preview mode flag */ #define MM_FIRST_ARTICLE 0x0100 /* first article in group */ #define MM_LAST_ARTICLE 0x0200 /* last article in group */ /* alt_command return values */ #define AC_QUIT 0 /* quit nn */ #define AC_PROMPT 1 /* just redraw prompt line */ #define AC_REDRAW 2 /* redraw screen */ #define AC_REORDER 3 /* articles have been reordered */ #define AC_REENTER_GROUP 4 /* reenter group after .newsrc update */ #define AC_KEYCMD 5 /* alt_cmd_key contains command */ #define AC_UNCHANGED 6 /* no display changes */ char *pct(long, long, long, long); int menu(fct_type); article_header *get_menu_article(void); int alt_command(void); int prt_replies(int); #endif /* _NN_MENU_H */ nn-6.7.4/awksplit.h0000644000175000017500000000022407751420622013563 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int awksplit(char *, register char ***, register int, char *); nn-6.7.4/active.h0000644000175000017500000000017307751420622013203 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ void read_active_file(FILE *, FILE *); nn-6.7.4/LICENSE0000644000175000017500000000471010225377600012562 0ustar mtpinsmtpins NN LICENSE License Terms: Use, Modification and Redistribution (including distribution of any modified or derived work) in source and binary forms is permitted only if each of the following conditions is met: 1. Redistributions qualify as "freeware" or "Open Source Software" under one of the following terms: (a) Redistributions are made at no charge beyond the reasonable cost of materials and delivery. (b) Redistributions are accompanied by a copy of the Source Code or by an irrevocable offer to provide a copy of the Source Code for up to three years at the cost of materials and delivery. Such redistributions must allow further use, modification, and redistribution of the Source Code under substantially the same terms as this license. For the purposes of redistribution "Source Code" means the complete compilable and linkable source code of nn including all modifications. 2. Redistributions of source code must retain the copyright notices as they appear in each source code file, these license terms, and the disclaimer/limitation of liability set forth as paragraph 4 below. 3. Redistributions in binary form must reproduce the Copyright Notice, these license terms, and the disclaimer/limitation of liability set forth as paragraph 4 below, in the documentation and/or other materials provided with the distribution. For the purposes of binary distribution the "Copyright Notice" refers to the following language: "Copyright (c) 1989-1991 by Kim Fabricius Storm. All rights reserved." "Copyright (c) 1996-2005 Michael T. Pins. All rights reserved." 4. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. $Revision: 6.7b $, Last updated $Date: 2005/04/07 21:40:23 $ nn-6.7.4/xmakefile0000644000175000017500000002276010222725421013445 0ustar mtpinsmtpins* * DO NOT CHANGE THIS MAKEFILE DIRECTLY * * THERE ARE NO CONFIGURATION PARAMETERS IN THIS FILE * #include "config.h" #undef global #undef SHELL #ifndef COMPILER_FLAGS #define COMPILER_FLAGS #endif #ifndef EXTRA_LIB #define EXTRA_LIB #endif #ifndef LOADER_FLAGS #define LOADER_FLAGS #endif #ifndef NNTP #undef NNTP_EXTRA_LIB #endif #ifndef NNTP_EXTRA_LIB #define NNTP_EXTRA_LIB #endif * Symmetry style parallel make #undef PARALLEL #ifdef PARALLEL_MAKE #define PARALLEL & #else #define PARALLEL #endif #ifdef ACCOUNTING #define ACCOUNT nnacct #else #ifdef AUTHORIZE #define ACCOUNT nnacct #else #define ACCOUNT #endif #endif ------------------ MAKE WILL CUT HERE ------------- * * Notice: ymakefile is made from xmakefile by the Makefile. * CC = COMPILER CPP = PREPROC LDFLAGS = LDEBUG LOADER_FLAGS EXTRA_LIB CFLAGS = -Iconf COMPILER_FLAGS CDEBUG * * Resulting programs * BIN_PROG = nn nnusage nngrab nnstats ACCOUNT BIN_LINK = nncheck nnadmin nntidy nngoback nngrep nnpost nnbatch nnview LIB_PROG = aux upgrade_rc #ifdef NOV MASTER_PROG = back_act nnspew NN.1 = nn.1.nov #else MASTER_PROG = nnmaster back_act nnspew NN.1 = nn.1.nnmaster #endif * * Compilation * SHELL = /bin/sh MASTER = master.o collect.o expire.o proto.o hostname.o \ global.o options.o active.o db.o nntp.o \ pack_date.o pack_name.o pack_subject.o news.o digest.o NN = nn.o admin.o proto.o global.o options.o db.o nntp.o \ init.o variable.o term.o keymap.o macro.o regexp.o \ menu.o more.o newsrc.o group.o folder.o dir.o aux.o \ sort.o articles.o sequence.o kill.o active.o fullname.o \ answer.o reroute.o hostname.o save.o unshar.o decode.o execute.o \ pack_date.o pack_name.o pack_subject.o news.o digest.o match.o \ chset.o printconf.o #ifdef NOV NOVOBJ = awksplit.o hash.o hdbm.o libnov.o split.o #else NOVOBJ = #endif ACCT = account.o global.o options.o proto.o hostname.o all: $(BIN_PROG) $(LIB_PROG) $(MASTER_PROG) inst client: $(BIN_PROG) $(LIB_PROG) inst master: $(MASTER_PROG) inst nn: PARALLEL $(NN) $(NOVOBJ) @echo linking nn @$(CC) -o nn $(CFLAGS) $(NN) $(NOVOBJ) $(LDFLAGS) TERMLIB NNTP_EXTRA_LIB nnmaster: PARALLEL $(MASTER) @echo linking nnmaster @$(CC) -o nnmaster $(CFLAGS) $(MASTER) $(LDFLAGS) NNTP_EXTRA_LIB nnstats: nnstats.sh prefix cat prefix nnstats.sh > nnstats ; chmod +x nnstats nnusage: nnusage.sh prefix cat prefix nnusage.sh > nnusage ; chmod +x nnusage nngrab: nngrab.sh prefix cat prefix nngrab.sh > nngrab ; chmod +x nngrab aux: aux.sh prefix cat prefix aux.sh > aux ; chmod +x aux upgrade_rc: upgrade_rc.sh prefix cat prefix upgrade_rc.sh > upgrade_rc ; chmod +x upgrade_rc nnacct: PARALLEL $(ACCT) @echo linking nnacct @$(CC) -o nnacct $(CFLAGS) $(ACCT) $(LDFLAGS) back_act: back_act.sh prefix cat prefix back_act.sh > back_act ; chmod +x back_act nnspew: nnspew.sh prefix cat prefix nnspew.sh > nnspew ; chmod +x nnspew prefix: config.h mkprefix ./mkprefix prefix < /dev/null > prefix mkprefix: PARALLEL prefix.o global.o $(CC) -o mkprefix $(CFLAGS) prefix.o global.o $(LDFLAGS) * * Configuration counter updating * update.h: config.h patchlevel.h Makefile @sh -c "[ -f update.h ] || (echo 0 > update.h)" @sh -c "expr `cat update.h` + 1 > update1.h && mv update1.h update.h" @echo configuration number updated to `cat update.h` * * Installation * cvt-help: PARALLEL config.h cvt-help.c $(CC) -o cvt-help cvt-help.c $(LDFLAGS) usercheck: PARALLEL config.h usercheck.c $(CC) -o usercheck usercheck.c $(LDFLAGS) inst: config.h xmakefile inst.sh cvt-help usercheck mkprefix man/nn.1 @echo building install script: ./inst @./mkprefix full < /dev/null > inst @echo BIN_PROG='"'$(BIN_PROG)'"' >> inst @echo BIN_LINK='"'$(BIN_LINK)'"' >> inst @echo LIB_PROG='"'$(LIB_PROG)'"' >> inst @echo MASTER_PROG='"'$(MASTER_PROG)'"' >> inst @cat inst.sh >> inst @chmod 755 inst * create the proper man page man/nn.1: man/nn.1.nov man/nn.1.nnmaster cp -p man/$(NN.1) man/nn.1 * * Clean -- remove compiled programs * clean: rm -f $(BIN_PROG) $(LIB_PROG) $(MASTER_PROG) cvt-help usercheck rm -f prefix mkprefix inst rm -f man/nn.1 * * dependencies * account.o: account.c account.h config.h global.h execute.h options.h \ proto.h active.o: active.c active.h config.h data.h global.h db.h master.h \ newsrc.h admin.o: admin.c admin.h config.h data.h global.h db.h execute.h \ group.h keymap.h nn.h proto.h nn_term.h answer.o: answer.c answer.h config.h data.h global.h aux.h db.h \ fullname.h group.h hostname.h init.h keymap.h kill.h \ match.h news.h nn.h nntp.h options.h printconf.h regexp.h \ reroute.h nn_term.h variable.h articles.o: articles.c articles.h config.h data.h global.h db.h keymap.h \ kill.h match.h news.h newsrc.h regexp.h sort.h nn_term.h aux.o: aux.c aux.h config.h data.h global.h chset.h execute.h \ keymap.h news.h nn.h nntp.h reroute.h nn_term.h chset.o: chset.c chset.h config.h collect.o: collect.c collect.h config.h global.h db.h news.h digest.h \ master.h news.h nntp.h pack_date.h pack_name.h pack_subject.h db.o: db.c db.h config.h data.h global.h answer.h articles.h \ keymap.h libnov.h nntp.h nn_term.h pack_date.h pack_name.h \ pack_subject.h hash.h newsoverview.h decode.o: decode.c decode.h config.h data.h global.h keymap.h save.h \ nn_term.h digest.o: digest.c digest.h config.h data.h global.h debug.h keymap.h \ news.h pack_name.h nn_term.h dir.o: dir.c dir.h config.h data.h global.h articles.h keymap.h \ nn_term.h execute.o: execute.c execute.h config.h data.h global.h folder.h \ keymap.h nn_term.h expire.o: expire.c expire.h config.h global.h db.h keymap.h master.h \ nntp.h nn_term.h folder.o: folder.c folder.h config.h data.h global.h articles.h \ db.h digest.h dir.h keymap.h match.h menu.h news.h \ pack_date.h pack_name.h pack_subject.h regexp.h save.h \ sort.h nn_term.h fullname.o: fullname.c fullname.h config.h data.h global.h global.o: global.c data.h global.h config.h execute.h keymap.h nn.h \ patchlevel.h nn_term.h update.h group.o: group.c group.h config.h data.h global.h articles.h db.h \ folder.h keymap.h macro.h match.h menu.h newsrc.h nn.h \ nntp.h regexp.h sort.h nn_term.h variable.h hostname.o: hostname.c hostname.h config.h init.o: init.c init.h config.h data.h global.h articles.h admin.h \ answer.h db.h execute.h folder.h group.h hostname.h \ keymap.h kill.h macro.h menu.h newsrc.h nn.h printconf.h \ save.h sequence.h sort.h nn_term.h variable.h keymap.o: keymap.c keymap.h config.h data.h global.h init.h nn_term.h kill.o: kill.c kill.h config.h data.h global.h db.h keymap.h \ match.h menu.h regexp.h nn_term.h macro.o: macro.c macro.h config.h data.h global.h db.h init.h \ keymap.h menu.h nn_term.h variable.h master.o: master.c master.h config.h global.h active.h collect.h db.h \ digest.h execute.h expire.h news.h nntp.h options.h proto.h match.o: match.c match.h config.h data.h global.h regexp.h menu.o: menu.c menu.h config.h data.h global.h articles.h \ answer.h db.h execute.h folder.h group.h init.h keymap.h \ kill.h macro.h match.h more.h newsrc.h regexp.h save.h \ sort.h nn_term.h more.o: more.c more.h config.h data.h global.h articles.h answer.h \ db.h execute.h folder.h group.h init.h keymap.h kill.h \ macro.h match.h menu.h news.h regexp.h save.h nn_term.h news.o: news.c news.h config.h data.h global.h digest.h more.h nntp.h newsrc.o: newsrc.c newsrc.h config.h data.h global.h articles.h \ active.h db.h keymap.h nn.h options.h regexp.h nn_term.h nn.o: nn.c nn.h config.h data.h global.h admin.h answer.h \ articles.h db.h execute.h folder.h group.h init.h keymap.h \ kill.h libnov.h macro.h match.h menu.h newsrc.h nntp.h \ options.h proto.h regexp.h sequence.h nn_term.h nntp.o: nntp.c nntp.h config.h data.h global.h fullname.h hash.h \ hostname.h keymap.h libnov.h nn_term.h patchlevel.h \ newsoverview.h options.o: options.c options.h config.h data.h global.h nn.h variable.h pack_date.o: pack_date.c pack_date.h config.h data.h global.h pack_name.o: pack_name.c pack_name.h config.h data.h global.h pack_subject.o: pack_subject.c pack_subject.h config.h data.h global.h prefix.o: prefix.c config.h data.h global.h execute.h printconf.o: printconf.c printconf.h config.h data.h global.h proto.o: proto.c proto.h config.h data.h global.h hostname.h regexp.o: regexp.c regexp.h config.h data.h global.h keymap.h nn_term.h reroute.o: reroute.c reroute.h config.h data.h global.h save.o: save.c save.h config.h data.h global.h db.h decode.h \ digest.h folder.h keymap.h more.h news.h nn_term.h unshar.h sequence.o: sequence.c sequence.h config.h data.h global.h db.h \ debug.h keymap.h macro.h nn.h nn_term.h variable.h sort.o: sort.c sort.h config.h data.h global.h articles.h term.o: term.c nn_term.h config.h data.h global.h execute.h \ folder.h keymap.h macro.h nn.h regexp.h unshar.o: unshar.c unshar.h config.h data.h global.h keymap.h nn_term.h variable.o: variable.c variable.h config.h data.h global.h chset.h \ folder.h init.h keymap.h regexp.h sort.h nn_term.h awksplit.o: awksplit.c awksplit.h config.h split.h hash.o: hash.c hash.h config.h hdbm.h hdbm.o: hdbm.c hdbm.h config.h hdbmint.h libnov.o: libnov.c libnov.h config.h data.h global.h awksplit.h \ digest.h hash.h newsoverview.h news.h nntp.h split.h split.o: split.c split.h config.h * link debugging version nn1: $(NN) $(CC) -o nn1 -Mnn1 $(CFLAGS) $(NN) $(LDFLAGS) TERMLIB NNTP_EXTRA_LIB nnmaster1: $(MASTER) $(CC) -o nnmaster1 -Mnnmaster1 $(CFLAGS) $(MASTER) $(LDFLAGS) NNTP_EXTRA_LIB lint: echo LINTING NN lint -Iconf -u $(NN:.o=.c) * echo LINTING MASTER * lint -Iconf -u $(MASTER:.o=.c) echo LINTING NOVOBJ lint -Iconf -u $(NOVOBJ:.o=.c) nn-6.7.4/folder.h0000644000175000017500000000040307751420624013201 0ustar mtpinsmtpins/* * Copyright (c) 2003 Michael T Pins. All rights reserved. */ int expand_file_name(char *, char *, int); int file_completion(char *, int); void fcancel(article_header *); int folder_menu(char *, int); nn-6.7.4/man/0000755000175000017500000000000015214614406012327 5ustar mtpinsmtpinsnn-6.7.4/man/nnmaster.80000644000175000017500000005373015212302132014243 0ustar mtpinsmtpins.TH NNMASTER 8 "Release 6.6" .\" (c) Copyright 1988, 1990, Kim F. Storm. All rights reserved. .UC 4 .SH NAME nnmaster \- nn database manager .SH SYNOPSIS \fBnnmaster\fP \-\fBI\fP [\fIlimit\fP] .br \fBnnmaster\fP \-\fBw\fP .br \fBnnmaster\fP \-\fBv\fP .br \fBnnmaster\fP \-\fBl\fP [ "\fIlock message\fP" ] .br \fBnnmaster\fP [ \fIoptions\fP ] [ \fIgroups\fP ] .br. \fBnnmaster\fP \-\fBF\fP [ \fIoptions\fP ] [ \fIgroups\fP ] .SH DESCRIPTION .I nnmaster is the daemon which is responsible for building and maintaining the database used by the \fInn\fP(1) news reader. .LP Normally, .I nnmaster is started when the system enters multi-user mode, and runs until system shutdown. To facilitate this, you should place the following call in /etc/rc (or similar) to invoke the .I nnmaster daemon: .sp 0.5v $master/nnmaster -l -r -C .sp 0.5v where $master is the MASTER_DIRECTORY defined during configuration of \fInn\fP. .LP When .I nnmaster is started as specified above, it will first unlock the database in case it was locked (\-\fBl\fP), perform a thorough consistency check on the database (\-\fBC\fP). .LP Then, every 10 minutes (\-\fBr\fP), it will look at the time-stamp of the news active file to see whether new articles have arrived on the system (or whether articles have been expired). (See \-\fBU\fP) .LP If the active file has been modified, .I nnmaster will collect the header information from the new articles and enter them into the database (or remove the headers of the expired articles from the database). .LP If it detects that some articles have been expired, it will automatically remove the header information of the expired articles from the database. .SH ARTICLE COLLECTION OPTIONS Normally, \fInnmaster\fP will collect all available news groups defined in the news active file. The set of collected groups can be controlled via the argument line. Groups can be either included or excluded: .sp 0.5v A group name, e.g. comp, will cause the group and all its subgroups to be collected. Individual groups, e.g. news.software.nn, can also be specified .sp 0.5v A group name preceded by an exclamation mark, e.g. !talk.politics, will cause the group and all its subgroups to be ignored. .sp 0.5v An empty argument, i.e. "", will cause all groups that are not ignored to be collected. For example, to collect everything but rec and misc, use the following command: .br \fInnmaster\fP -r !rec !misc "" .br If the empty argument had been omitted, nothing would be collected, since the presence of any \fIgroups\fP arguments causes \fInnmaster\fP to ignore all groups which are not explicitly mentioned. .sp 0.5v Example 1: The following commands can be executed by cron to collect different sets of groups at different intervals or under different conditions: .br \fInnmaster\fP -B -O14 rec misc sci -LBO -u .br \fInnmaster\fP !rec !misc !sci "" -u .sp 0.5v Example 2: The group arguments are used in the given sequence, e.g. to leave out comp.sys, but still collect comp.sys.ibm.pc, use the command: .br \fInnmaster\fP -r comp.sys.ibm.pc !comp.sys "" .sp 0.5v The use of the \-\fBu\fP option in the first example is essential, since each of the commands will update the active file time stamp which will prevent the other command from detecting new articles that have arrived. .sp 0.5v Using this method to keep specific groups out of the database must be used with great caution; just a single invocation of \fInnmaster\fP without any arguments will collect all the otherwise ignored groups! .SH COLLECTION OF ARTICLES The following options control how \fInnmaster\fP performs the collection of new articles. .TP \-\fBr\fP [ \fImin\fP ] .br Daemon mode. The \fInnmaster\fP will put itself in the background (unless \-\fBf\fP is also specified), and will checks for arrival of new articles and expired articles every .I min minutes (and update the database accordingly). If .I min is omitted, the default is to check every 10 minutes. .sp 0.5v Without the \-r option, the \fInnmaster\fP will just perform a single collection of new articles (if any) and then exit. This can be used to have the \fInnmaster\fP started by .IR cron (8) at regular intervals instead of having it as a daemon which sleeps between checking for new articles. Since the \fInnmaster\fP is a bit expensive to start up (it has to read a few files), it is up to you to decide which mode is best on your system. (I have also heard that it works to call \fInnmaster\fP without \-r from .IR inews (1). I cannot recommend this unless you receive batched news; invoking \fInnmaster\fP for every received article sounds too expensive to me.) .TP \-\fBh\fP [ \fIsec\fP ] Hold collection of new articles until next scan if new articles have arrived within the last \fIsec\fP [60] seconds. This is useful to prevent \fInnmaster\fP from competing for disk i/o with an \fIrnews\fP process which is unbatching incoming news, or a running \fIexpire\fP process. It will have the side effect of limiting the number of C: entries in the log, since collection of incoming batches will typically be done in larger thunks. .TP .B \-f Run \fInnmaster\fP in foreground in daemon mode (see \-\fBr\fP). Useful if \fInnmaster\fP is invoked from inittab. (Notice that if you use a \fIrespawn\fP entry in inittab, you will not be able to stop \fInnmaster\fP using the \-\fBk\fP option, since init will immediately start another master.) .TP .B \-C Perform a consistency check on the database on start-up, and rebuild corrupted database files. This operation can be quite time-consuming since it reads through all the database files. .TP .B \-b Normally, articles without a proper news header (no Newsgroups: line) are ignored. Specifying the \-\fBb\fP option causes these `bad' articles to be included in the database (normally with no sender or subject). .TP .B \-B Remove `bad' articles. Sometimes, articles without a header ends up in the news spool directory. These articles have no article id, and therefore, they will never be expired by \fBexpire\fP(8). This option will allow the \fInnmaster\fP to silently remove these articles (a `B' entry will occur in the log file). .TP \-\fBO\fP \fIdays\fP Ignore articles which are older than the given number of \fIdays\fP. This may help keep old 'stray' articles out of the database. If the \-\fBB\fP options is also specified, the old articles will be removed from the news spool directories. Old ignored or removed articles will be reported with an `O' entry in the log file. This option can be disable for individual groups by the \fBO\fP flag in the GROUPS file (see below). .TP \-\fBR\fP \fIN\fP Specifies how the \fIauto-recollect\fP operation is performed on the groups having this option set in the GROUPS file (see below). Four methods are available (default is method 1): .br \fB1\fP: Run expire on the group when new articles arrive. .br \fB2\fP: Run expire on the group on every scan. .br \fB3\fP: Recollect all articles when new articles arrive. .br \fB4\fP: Recollect all articles on every scan. .br .TP \-\fBM\fP \fImode\fP Normally, \fInnmaster\fP will send a message via mail to the news administrator (OWNER) when an error or potential problems (primarily nntp related) occur. This can be restricted to only fatal errors (\fInnmaster\fP terminated) if \fImode\fP is 1, and disabled completely if \fImode\fP is 0. .TP \-\fBQ\fP Normally, \fInnmaster\fP will print a message on the system console or in the syslog if a fatal error happens. This option will prevent this, so only a type 'E' entry is written to the Log file. .SH DATABASE EXPIRATION Since articles does not stay forever in the news system, the database must be cleaned up regularly to remove the information stored for expired articles. Expiration of the database is normally \fBscheduled\fP using the \fInnadmin\fP(1M) command executed by cron at a suitable time when expire on the news articles has completed. The following command will send a message to the \fInnmaster\fP and cause it to initiate expire on all news groups: .sp 0.5v nnadmin =EYW .LP Selective expiration of individual groups can be done from \fInnadmin\fP (interactive mode). It can also be done by invoking \fInnmaster\fP with the \-\fBF\fP option. For example, the following command will run expire on all groups except the `rec' groups: .sp 0.5v nnmaster \-F \-k !rec "" .sp 0.5v The \-\fBk\fP option is required to terminate the currently running master since two daemons cannot be running at the same time. Thus to run expire (on all groups) in this way from cron, the following commands must be used: nnmaster \-Fk "" ; nnmaster \-r ... .LP It is also possible to have \fInnmaster\fP detect expiration automatically (see \-\fBe\fP). This requires that the \fImin\fP field in the active file is updated by the news expire (this is not the default case when Cnews is used). However, this is not always a safe indication since the first article may not have been expired, while a lot of other articles have been deleted. .LP There are several strategies available in the \fInnmaster\fP to do this clean-up, each having their strengths and weaknesses. .LP \fBMethod 1\fP (default): Rebuilds the database from the existing database information by comparing the current database contents with the contents of the news group directories, eliminating entries whose file no longer exists. This method is guaranteed to eliminate all expired articles from the database, and it is reasonably fast because it only has to read the directories, not each article file. If news is accessed remotely via nntp, the list of existing articles cannot efficiently be fetched by reading a local directory. Instead an XHDR request is sent to the nntp server to get a list of articles. .LP \fBMethod 2\fP: Eliminates only the expired articles before the first article in the group. This is very fast since only the active file and the database itself is accessed, but it will obviously leave some expired articles in the database. This method requires that the \fImin\fP field in the active file is updated by \fIexpire\fP. .LP \fBMethod 3\fP: Discard the current database information and recollects all articles. This is obviously very time consuming, and it is therefore not recommended, especially not with nntp. .LP The options related to database expiration are: .TP \-\fBE\fP \fIN\fP Select expire method \fIN\fP. (If \fIN\fP is omitted, the default method is used). .TP \-\fBe\fP [\fIN\fP] Automatically run expire in the database on groups where the \fImin\fP number in the active file has increased by \fIN\fP (1 if omitted) articles. This is disabled by default (since the \fImin\fP field is often unreliable). .TP \-\fBF\fP Run expire once and exit. If a list of \fIgroups\fP is specified on the command line, the matched groups (see the rules above) will be marked for expiration. If no groups are specified, only the groups already scheduled for expire will be expired. Consequently, to expire all groups, a blank argument "" (matching all groups) must be specified. .SH DATABASE LOCKING The database can be locked centrally, which will normally disallow all access to the database, and even block \fInnmaster\fP from being (accidentally) started. When a lock is set on the database, all currently running clients will terminate the next time they try to access the database. Setting a lock on the database can thus also be used to force running clients to terminate. .LP The following options set and clear locks on the database: .TP \-\fBl\fP \fImessage\fP Locks the database with the given \fImessage\fP. The message will be displayed every time a program tries to access the database. .TP .B \-l Unlock the database if it was locked. .TP .B \-i Ignore a possible lock and continue. This can be used to have \fInnmaster\fP operate on a database which is blocked from normal user access. .LP Since only one \fInnmaster\fP can operate on the database at any one time, a running \fInnmaster\fP daemon must be stopped before a lock can be set on the database. If neither .B \-f nor .B \-r is specified with the .B \-l option (in both forms), \fInnmaster\fP will terminate after setting or clearing the lock. .SH DATABASE INITIALIZATION The following options are used to initialize and update the central database files: .TP \fB\-I\fP [\fIlimit\fP] Initialize database. This option will erase an existing database, and create an empty database containing entries for the currently known groups. \fInnmaster\fP will offer you to use an existing GROUPS file when initializing the database. .sp 0.5v The optional \fIlimit\fP can be used to put a limit on the number of articles that will be collected in each group during the first collection of articles following the database initialization. This is useful on systems where the 'min' field in the active file is unreliable or not maintained (Cnews doesn't) to limit the work done to do the initial collection of news after the initialization of the database. If news is accessed remotely from an NNTP server, this is even more important! If \fIlimit\fP is omitted, or is zero, \fInnmaster\fP will trust the min field and collect all articles in the range min..last. .TP .B \-G Reread the GROUPS file. This option is used to instruct \fInnmaster\fP to parse the GROUPS file after it has been edited. See the section on the GROUPS file below. .SH MISCELLANEOUS OPTIONS The following options controls various details of the \fInnmaster\fP's behaviour: .TP \fB\-D\fP [ \fIN\fP ] Run \fInnmaster\fP in "debug mode". If \fIN\fP is omitted, or equals 1 or 3, this will produce a compact but still very informative trace of the collection or expire process directly on the terminal. This is most useful when doing the first collection of articles after initializing the database with -I. If \fIN\fP is 2 or 3, a trace of the NNTP traffic is written to a file nnmaster.log in the TMP directory. This option disables \-\fBr\fP. .TP \-\fBH\fP Identifies the host which \fInnmaster\fP is running on as the nntp-server for its clients, i.e. that it can access the news spool directory locally without using NNTP. Normally, \fInnmaster\fP will detect this by itself by comparing the host name to the contents of the nntp_server file, so this option should really be superfluous. .TP \-\fBy\fP \fIretries\fP .br In some networked environment, opening an article (shared from another machine via NFS) may fail for no obvious reason. Using this option, it is possible to cause \fInnmaster\fP to perform \fIretries\fP attempts to open an article before marking the article as non-existing in the database. .TP \-\fBL\fP \fItypes\fP Exclude the specified entry types from the log file. This is normally used to exclude the 'C'ollecting and e'X'pire entries (\-\fBL\fPCXO). .TP .B \-t Trace the collection process. This will place a lot of information into the log file (T: entries). .TP .B \-u Normally, \fInnmaster\fP will just compare the time-stamp on the active file with a time-stamp saved in the database to see if new articles have arrived. The \-u option forces the \fInnmaster\fP to .I read the active file on start-up to see if new articles have arrived. .TP .B \-U Some SVR4 systems (and maybe SunOS) have a useful "feature". Writing files with mmap() may not update the last-changed timestamp on the file. Since INN uses mmap() for writing the active file, this becomes a problem for \fInnmaster\fP. The \-U option causes \fInnmaster\fP to unconditionally read the active file each time the repeat delay (\-r) time expires. .TP .B \-v Print the release and version identification for \fInnmaster\fP, and exit. .TP .B \-w Wakeup the real \fInnmaster\fP. Send a signal to the \fInnmaster\fP daemon to have it check for new articles immediately. .TP .B \-k Kill the already running \fInnmaster\fP daemon before proceeding with the operation specified by the other options (or terminate if no other operation is requested). .SH THE GROUPS FILE The primary purpose of the GROUPS file is to store the names of the news groups represented in the database. Each line in the file corresponds to an entry in the (binary) MASTER file, and the sequence of the lines in the GROUPS file must never be changed unless the database is reinitialized afterwards. .LP However, the contents of the lines in the GROUPS file can be edited to control how the \fInnmaster\fP should handle each individual group. .LP The format of each line is: .sp 0.5v news.group.name [ timestamp ] [ flags ] .LP The news group name is the name of the group, and must not be changed in any way. If the group is no longer in the news active file, and consequently the group does no longer exist, group name can be replaced by a `@' character which will instruct \fInnmaster\fP to ignore this entry without having to rebuild the entire database. .LP The optional time stamp indicates when the line was added to the GROUPS file and is used by \fInn\fP to detect new groups. When the GROUPS file is built initially from the active file, the time stamps are omitted which simply means that they are "old". .LP One or more of the following flags can be added to the GROUPS line to control \fInnmaster\fP's handling of the group: .TP .B D Causes \fInnmaster\fP to treat all articles in the group as digests, even when they don't initially look like digests. Articles which are found not to be digests after all, are still not digested. .TP .B N Instructs \fInnmaster\fP to never digest any articles in the group. .TP .B O Disables the \-\fBO\fP option for this group, i.e. all existing articles will be included in the database (and they will not be removed if the \-\fBB\fP option is specified). This flag should be set on groups which you never expire, or have a very long expire time! .TP .B R Causes \fInnmaster\fP to \fIrecollect all\fP available articles in the group whenever a new article is received. This is said to be useful is some high-traffic clarinet groups with many cancelled articles. .TP \fB>\fP\fIfile\fP Instructs \fInnmaster\fP to \fIappend\fP all new articles to the specified file. This makes it possible to keep specific groups out of the way of expire. The archive file can be access directly from the \fInn\fP client using the \fIgoto-group\fP command. The file name must be a full path name to a file in an existing, writeable directory. .TP .B @ Instructs \fInnmaster\fP to completely ignore this group - this is equivalent to setting the group name to `@' as described above. .TP \fB!\fP or \fBX\fP Causes \fInnmaster\fP to ignore the group and not collect the group's articles in the database. .LP Comments (starting with `#' and continuing to the end of line) and empty lines are allow in the GROUPS file, but it is strongly recommended to keep the changes to the GROUPS file as small as possible, because of the implicit correspondence with the binary MASTER file. .LP It is not recommended to edit the GROUPS file while \fInnmaster\fP is running because it may add new groups to the file. After editing the GROUPS file, the command .br \fInnmaster\fP -G .br must be run before restarting the \fInnmaster\fP to parse and verify the new GROUPS file. .SH NNTP SUPPORT The \fInnmaster\fP can access the news articles from a local news spool directory as well as from an NNTP server. When compiled with NNTP enabled, \fInnmaster\fP will compare the name of the NNTP server and the name of the local host; if they are identical, \fInnmaster\fP will bypass NNTP and access the articles directly. .LP When it has to access the news articles via NNTP, it cannot time-stamp the active file, so instead it transfers the entire active file from the NNTP server and compares it with a local copy of the last active file fetched from the NNTP server. This is not very expensive in terms of cpu-cycles, disk-load, or net-bandwidth, but to stay on friendly terms with the NNTP server administrator, you should probably not use shorter update intervals than the standard 10 minutes. .LP Setting a much higher update interval than the standard 10 minutes is not really recommended either, since an update normally implies fetching a burst of news articles from the NNTP server, so setting the interval too long may imply that the load on the NNTP server will be more un-even. .LP In expire method 1, the use of XHDR just to get a list of existing articles in a group is definitely a waste of resources on the nntp server (but still lower than using method 3). Before using the XHDR request, \fInnmaster\fP will send a non-standard "LISTGROUP" request; if the nntp server supports this request, it should return an OK_HEAD status followed by an (unordered) list of article numbers (one per line) terminated by a `.' line. The nntp servers supporting this request will be much less loaded during expire. .LP The \-\fBO\fP option does not work with NNTP. The \-\fBB\fP option will only work with NNTP if the \fInnmaster\fP is running on the NNTP server. .SH FILES The $db, $master, and $news names used below are synonyms for the DB_DIRECTORY, MASTER_DIRECTORY, and NEWS_LIB_DIRECTORY defined during configuration. .LP .DT .ta \w'$db/DATA/\fInnn\fP.[dx]'u+3m .\"ta 0 20 $db/MASTER Database master index .br $db/GROUPS News group names and flags in MASTER file order .br $db/DATA/\fInnn\fP.[dx] Database files for group number \fInnn\fP .br \&.../.nn[dx] Database files if located in the group directories .br $master/GATE Message channel from \fInnadmin\fP to \fInnmaster\fP .br $master/MPID The process id of the \fInnmaster\fP daemon. .br $Log The log file (the location is configuration dependent) .br $news/active Existing articles and groups .br /usr/lib/nntp_server Contains the name of the NNTP server. .DT .LP The MASTER file contains a record for each news group, occurring in the same sequence as the group names in the GROUPS file. The sequence also defines the group numbers used to identify the files in the database's DATA directory. .LP The GATE file will be created by \fInnadmin\fP when needed, and removed by \fInnmaster\fP when it has read it. Therefore, to send a message to the \fInnmaster\fP requires that you are allowed to write in the $master directory. .LP The contents of the Log file are described in the \fInnadmin\fP manual. .SH SEE ALSO nn(1), nncheck(1), nngrep(1), nntidy(1) .br nnadmin(1M), nnspew(8), nnusage(1M) .SH AUTHOR Kim F. Storm, Texas Instruments A/S, Denmark .br E-mail: storm@texas.dk nn-6.7.4/man/nngrab.10000644000175000017500000000321207317770560013667 0ustar mtpinsmtpins.TH NNGRAB 1 "Release 6.6" .UC 4 .SH NAME nngrab \- news retrieval by keyword (nn) .SH SYNOPSIS \fBnngrab\fP [ \-\fBc\fP ] \fIkeyword\fP .SH DESCRIPTION .I nngrab invokes \fInn\fP on all USENET articles whose subject (or keyword) field(s) contain an instance of \fIkeyword\fP. \fInngrab\fP is a fast equivalent for: .sp 0.5v nn -mxX -s/\fIkeyword\fP all .LP For example, .sp 0.5v nngrab tesla .sp 0.5v will retrieve items concerning Nikola Tesla. .LP Keyword case is ignored unless \-\fBc\fP is specified, and the \fIkeyword\fP can be a regular expressions (escaped to avoid conflicts with the shell). For example, .sp 0.5v nngrab "n.*tesla" .LP The range of search includes all newsgroups on the system, including ones which are unsubscribed. .SH FILES .DT .ta \w'$db/subjects'u+3m .\"ta 0 16 $db/subjects subject database .DT .SH SEE ALSO nn(1), nnspew(8), egrep(1) .SH NOTES \fInngrab\fP \fIcan be\fP much faster than the equivalent command shown above, if the tertiary news subject database generated by the \fInnspew\fP(8) daemon exists. To enable the faster operation, \fInnspew\fP must be executed regularly by cron. .LP \fInngrab\fP uses \fIegrep\fP(1) to scan the subject database, so if you are not running \fIfast\fP egrep (GNU-style) this is all for naught. .LP \fInngrab\fP will use a subject database generated by \fInnspew\fP independent of its age. Thus, if you stop running \fInnspew\fP, remember to remove the subjects file as well. .SH BUGS Under version 6.4 onwards, search of the "Keywords:" field is not supported. .br Search on name is not possible either. .SH AUTHOR James A. Woods, NASA Ames Research Center .br E-mail: jaw@ames.arc.nasa.gov nn-6.7.4/man/nngoback.10000644000175000017500000000726607317770555014223 0ustar mtpinsmtpins.TH NNGOBACK 1 "Release 6.6" .\" (c) Copyright 1988, 1990, Kim F. Storm. All rights reserved. .UC 4 .SH NAME nngoback \- make news articles unread on a day-by-day basis (nn) .SH SYNOPSIS .B nngoback [ \-\fBNQvi\fP ] [\-\fBd\fP] \fIdays\fP [ \fIgroup\fP ]... .SH DESCRIPTION .I nngoback will rewind the .newsrc record file of \fBnn\fP(1) one or more \fIdays\fP. It can be used to rewind all groups, or only a specified set of \fIgroups\fP. In other words, \fInngoback\fP can mark news articles which have arrived on the system during the last \fIdays\fP days unread. .LP Only \fIsubscribed\fP groups that occur in the current presentation sequence are rewound. That means that if no \fIgroup\fP arguments are specified, all groups occurring in the sequence defined in the \fIinit\fP file will be rewound. Otherwise, only the groups specified on the argument line will be rewound. .LP When a group is rewound, the information about selections, partially read digests etc. are discarded. It will print notifications about this unless the \-\fBQ\fP (quiet) option is used. .LP If the \-\fBi\fP (interactive) option is specified, \fInngoback\fP will report for each how many articles \fIcan be\fP marked unread, and ask for confirmation before going back in that group. .LP If the \-\fBv\fP (verbose) option is specified, \fInngoback\fP will report how many articles are marked unread. .LP If the \-\fBN\fP (no-update) option is specified, \fInngoback\fP will perform the entire goback operation, but not update the .newsrc file. .LP If you are not up-to-date with your news reading, you can also use \fBnngoback\fP to catch up to only have the last few days of news waiting to be read in the following way: .br nn \-a0 .br nngoback 3 .br The \fBnn\fP command will mark all articles in all groups as read (answer .I all to the catch-up question.) The following \fBnngoback\fP will then make the last three days of news unread again. .LP Examples: .TP nngoback 0 Mark the articles which have arrived today as unread. .TP nngoback 1 Mark the articles which have arrived yesterday and today as unread. .TP nngoback 6 Mark the articles which have arrived during the last week as unread. .LP You cannot go more than 14 days back with \fInngoback\fP. (You can change this limit as described below.) .SH THE BACK_ACT DAEMON It is a prerequisite for the use of \fInngoback\fP that the script \fBback_act\fP is executed at an appropriate time once (and only once) every day. Preferably this is done by \fBcron\fP right before the bacth of news for `today' is received. \fBback_act\fP will maintain copies of the active file for the last 14 days. .LP Optionally, the \fBback_act\fP program accepts a single numerical argument specifying how many copies of the active file it should maintain. This is useful if news is expired after 7 days, in which case keeping more than 7 days of active file copies is wasteful. .SH FILES .DT .ta \w'~/.newsrc.goback'u+5m .\"ta 0 21 ~/.newsrc The record of read articles. .br ~/.newsrc.goback The original rc file before goback. .br $db/active.\fIN\fP The \fIN\fP days `old' active file. .br $master/back_act Script run by cron to maintain old active files. .br .DT .SH SEE ALSO nn(1), nncheck(1), nngrab(1), nngrep(1), nnpost(1), nntidy(1) .br nnadmin(1M), nnusage(1M), nnmaster(8) .SH NOTES \fBnngoback\fP does not check the age of the `old' active files; it will blindly believe that active.0 was created today, and that active.7 is really seven days old! Therefore, the \fIback_act\fP script should be run once and only once every day for \fInngoback\fP to work properly. .LP The days are counted relative to the time the active files were copied. .SH AUTHOR Kim F. Storm, Texas Instruments A/S, Denmark .br E-mail: storm@texas.dk nn-6.7.4/man/nnadmin.1m0000644000175000017500000004754615212302305014220 0ustar mtpinsmtpins.TH NNADMIN 1M "Release 6.6" .\" (c) Copyright 1988, 1990, Kim F. Storm. All rights reserved. .UC 4 .SH NAME nnadmin \- nn database administration .SH SYNOPSIS .B nnadmin [ .I commands ] .SH DESCRIPTION .I nnadmin is a control program for the \fInnmaster\fP(1M) daemon which is responsible for building and maintaining the database used by the \fInn\fP(1) news reader. .LP \fInnadmin\fP allows you to display extracts from the log file, display the "raw" contents of the database, make consistency checks on the database, instruct the running \fInnmaster\fP to expire one or more groups, alter the options of the running \fInnmaster\fP, and much more. .LP \fInnadmin\fP runs in two modes: interactive and non-interactive. .LP In interactive mode, simple one line menus are used to show the available operations which are then selected by typing the letter associated with the command (normally the first letter in the command name). .LP In non-interactive mode, the .I commands argument will be used as a series of key-strokes which are interpreted exactly as if they were typed in from the keyboard in interactive mode. For example, to stop the \fInnmaster\fP, the following invocation of nnadmin can be used: .br \fInnadmin\fP MK .br which will select the (M)aster submenu from the main menu, and then the (K)ill entry from the submenu. .LP In non-interactive mode, the menus are not displayed and the commands are not echoed! \fInnadmin\fP will exit when there are no more key-strokes to be read from the .I commands argument. It is not possible to specify a group name in the .I commands argument, so the functionalities of \fInnadmin\fP that relates to specific groups are only available in interactive mode. .LP Some "dangerous" commands will require that you confirm them by following them by "Y" on the command line. The most notable are IY (initialize database) and EY (expire all groups). These commands will be marked with a \fB[Y]\fP following the command name. .LP You can also invoke an interactive \fInnadmin\fP using the .B :admin command in \fInn\fP. .SH SHELL ESCAPES At all prompts you can hit `!' to spawn a subshell. .LP The working directory of the subshell will be changed to the database directory when invoked from the MASTER or DUMP menus, and it will changed to the group's spool directory (if it exists) when invoked from the GROUP menu. .SH MAIN MENU From the main menu (identified by the .B ADMIN prompt) you can select the following operations: .TP .B C)onf .br Show current configuration parameters such as directories, files, programs, network usage, etc. .TP \fBE)xpire [Y]\fP .br Send a request to the \fInnmaster\fP daemon to schedule (and run) expire for all groups in the database. .TP .B G)roups .br Enter the GROUP submenu. .TP \fBI)nit [Y]\fP .br Send a request to the \fInnmaster\fP daemon to recollect all groups in the database. .TP .B L)og .br Enter the LOG submenu. .TP .B M)aster .br Enter the MASTER submenu. .TP .B Q)uit .br Quit \fInnadmin\fP. .TP .B S)tat .br Print general statistics about the database. See the section on Database Statistics below. .TP .B U)pdate .br Update the incore copy of the database master index. .TP .B V)alidate .br Make a thorough consistency check on the database. If inconsistencies are found in a group, you will be asked whether a request should be sent to the \fInnmaster\fP daemon to recollect the group (in non-interactive mode, requests will be sent automatically for all corrupted groups). .TP .B W)akeup .br Send a wakeup signal to the \fInnmaster\fP daemon to have it receive messages sent to it, perform the required actions, and then collect articles as necessary. .TP \fBZ\fP (silent validation) .br This operation is identical to the Validate operation, expect that no output is produced during the consistency check; this operation is used by the \fInnmaster\fP to execute the \-\fBC\fP option. .SH THE MASTER MENU The master menu (identified by the .B MASTER prompt) provides access to overall database information, and to send control messages to the \fInnmaster\fP daemon. .TP .B C)heck In interactive mode and in verbose batch mode (\fInnadmin\fP MC), print a message telling whether \fInnmaster\fP is running or not. In silent batch mode (\fInnadmin\fP =MC) exit with a status code of 0 if \fInnmaster\fP is running and 1 otherwise; this may be useful is administrative scripts. .TP .B D)ump Enter the DUMP submenu. .TP .B F)iles .br Print a listing (using .IR ls (1)) of all the data and index files in the database. .TP .B G)roup .br Print the master index entry for a single group identified by its internal group number. .TP .B K)ill .br Stop the \fInnmaster\fP when it has finished its current task. .TP .B O)ptions .br Change the runtime options of the running \fInnmaster\fP daemon. Currently, only the value of the \-r and \-e options can be modified. .TP .B S)tat .br Print general statistics about the database. See the section on Database Statistics below. .TP .B T)race .br Turn the trace option \-t on or off in the running \fInnmaster\fP. .SH THE DUMP MENU The dump menu (identified by the .B DUMP prompt) allows you to print the master index entry for various selections of groups in the database. .TP .B A)ll .br Print all groups in the database. .TP .B E)mpty .br Print the empty groups in the database. .TP .B H)oles Print the groups where the `min' field in the active file is not the first article saved in the database (because it doesn't exist or because it is ignored for some other reason, e.g. bad or old). .TP .B I)gnored Print groups which are ignored, either in the GROUPS file or because of some other condition (mainly no spool directory). .TP .B N)on-empty .br Print the non-empty groups in the database. .TP .B V)alid Print the groups which are present in the active file. .TP .B in(W)alid Print the groups in the database which are not present in the active file. .SH THE LOG MENU The log menu (identified by the .B LOG prompt) enables you the extract specific entries from the log file, and to truncate the log file. .LP The entries in the log file share the following format: .sp 0.5v :