bobm@rtech.UUCP (Bob Mcqueer) (12/21/87)
#! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # server.doc # std.c # std.h # config_std.h # This archive created: Wed Dec 16 13:50:20 1987 export PATH; PATH=/bin:$PATH if test -f 'server.doc' then echo shar: will not over-write existing file "'server.doc'" else cat << \SHAR_EOF > 'server.doc' vns_envir() called to allow server layer to set environment. Will be called before anything else. See the vn_env() routine if the server interface intends to use any environment variables. There are some procedure pointers in vn which may optionally be set by the server interface. By default, all of these are NULL: (*Massage)(hdr) ARTHEADER *hdr; Mail path massaging function. Allows server interface to interact with the user prior to editing the reply file on mail replies. See vns_aopen(). (*Headedit)() If set, will be called instead of the default action when the user chooses the "toggle header flag" command. This is intended to allow the server interface to implement a user selection of which headers to display when reading an article. If this is set, note that the reader code will never rewind an article past the point set by vns_aopen(). (*Postfunc)(hdr,fn) (*Mailfunc)(hdr,fn) ARTHEADER *hdr; char *fn; If set, these functions will be called for mail / post instead of spawning a command. They are intended for the instance in which these operations can be handled within the executable rather than forking another process. If forking another process has to be done, you might as well set up postcmd / mailcmd, and let vn do it. fn is the name of a file containing the user's article or mail reply. All of the procedures will be called with the user's terminal in raw mode and will be expected to leave it that way on return. If these routines need to control ioctl settings or send control sequences to the terminal, they should do so through the tty_set / term_set calls used by the rest of vn. These routines basically hide ioctl / termcap from everybody. vns_news (argc,argv,lfirst,unsub) int argc; char **argv; int *lfirst; int *unsub; called to let server layer process options and enter known newsgroups in vn structure. arguments are vn's command line. It calls four specific support routines: hashenter(group,highnum,lownum) char *group; int highnum, lownum; Enter information about a newsgroup. Group is newsgroup name, highnum the highest article number, lownum the lowest. NODE * hashfind(group) char *group; Return information on entered group. See node.h header file for structure and legal usage of its items by server interface routines. May be called by other vns_ rouines as well. fw_group(group,new,sub,read,look) char *group; int new, sub, read, look; prepare to enter display information for a new newsgroup. new is non-zero if this is to be considered a "new" newsgroup since the user's last session. sub is non-zero if user is to be considered "subscribed" to it. read is the initial "articles read" number for the newsgroup. look is non-zero if server interface intends to look for articles in this newsgroup. This parameter is open to interpretation, but should indicate that user's options mean group is to be looked at. This should NOT reflect the fact that no articles will be found because "read" is up to date. The parameter is only used for statistical collection, anyway. fw_art(anum,subj,lines,author) int anum; char *subj, *lines, *author; enter information for an article in the newsgroup last set with fw_group(). anum's should be entered in ascending sequence. fw_chg(new,sub,read,look) int new, sub, read, look; change the parameters for the current group. To allow the server interface to modify its setting for the group based on what it discovered while scanning the articles. fw_group() should be called once and only once for each group hashenter()'ed. The order of fw_group() calls determines the order of vn's display to the user. fw_group() calls may be made after all the hashenter() calls have been made, or they may be interleaved. If no fw_art() calls are made following a particular fw_group() call, no display page will be generated for that group. If vns_news does not stick to the rules regarding calls to fw_group, vn will exit with a fatal error message. Rationale for this is for the server layer to have no knowledge of device dependencies (the calls to fw_art will split up display pages), or any of the vn structures beyond the newsgroup information. And the newsgroup NODE structure has clearly documented pieces which are legal for the server to read or use, and others which are out-of-bounds. It isolates things fairly well. Note that vns_news hides all knowledge of the user's .newsrc file or similar concept, and all knowledge of where articles live and how they are administered. Error handling is brutally simple. If the server doesn't obey the rules, vn will exit with a fatal error message if it detects the fact. lfirst and unsub are returned to the caller based on the command line: lfirst - non-zero if vn is supposed to go directly to the newsgroup summary list on startup (-% option). unsub - set non-zero if unsubscribed newsgroups are to be updated on "write alls" (-U option). We COULD have vn look for these, and nail them down, but most command line options are things which make sense only to the server interface. Different environments may have different options which make sense. Might as well give the server interface control of the command line exclusively, and advise to consistently use -% and -U if possible. See below for some other support routines which might be useful for vns_news(). In summary, what vns_news should do: Call hashenter() for all the legitimate newsgroups. After this, hashfind() may be called at any time to retrieve the information about a group. Call fw_group() once and only once for each group, in the order newsgroups should be displayed to the user. The order need bear no relation to the order of hashenter calls. After each fw_group call, shovel in article information with fw_art calls. Optionally call fw_chg() if anything ought to be changed from the original fw_group call. If there are fatal errors call printex() with an explanation. To produce non-fatal diagnostics / informational messages, call fgprintf(). If you absolutely want to produce the message, backgrounded or not, you CAN simply output to stderr / stdout, but think before doing it. It's obnoxious to have a backgrounded program stop on output before it's ready to interact with you. ISN'T THAT EASY? FILE *vns_aopen(art,hdr) int art; ARTHEADER *hdr; Returns an open file pointer to article text for vn's use, or NULL for failure. Fills in the ARTHEADER argument, containing information conceptually thought of as extracted from the "header" lines. File pointer should be positioned at the start of the bonafide article text, if headers are contained in the file. See head.h for the items to be filled in. If (*Massage)(), (*Postfunc)() or (*Mailfunc)() are defined, they will be called with this same ARTHEADER instance when they are called. vns_aopen may set up any arguments in the priv item for the convenience of these routines. CAUTION: hdr contains string pointers. vns_aopen must be sure to point to legitimate storage, not its stack. And it SHOULD NOT simply malloc() the strings every time without freeing the old ones somehow - this will lose storage every time the user reads a new article. You MAY NOT assume that the same instance of an ARTHEADER structure is passed to each vns_aopen() call, either. See str_tpool, and associated routines, below. vns_close(fp) FILE *fp; Called to close article opened with vns_open(). In many instances will simply be an fclose(). vn will NEVER have multiple articles open, so vns_aopen / vns_aclose may maintain static information if they like. vns_gset(grp) char *grp; Called as the user moves from page to page of the display. vns_aopen and vns_aclose calls will refer to this newsgroup. May do chdir()'s if it likes. vns_exit(stat) int stat; Called before exit to allow the server layer to clean up. Called on normal exit & from within printex() (ie. DON'T call printex in unconditionally in this routine) vns_write(news,count) NODE **news; int count; Called to do whatever is the conceptual equivalent of updating the user's .newsrc file. news is an array of newsgroup nodes in the order that fw_group was called, count is the length of the array. Vn may have modified the rdnum items & the FLG_SUB flag based on user interaction. User exit, "write" commands, and "subscription" commands will result in this call. vn will set a flag bit, FLG_ECHG, in the flag item of all newsgroups whose data it changed since last call. This bit may be used by vns_write to do selective updates, and will be cleared for all groups following return from vns_write(). vns_asave(art,fp,count,name,mode) int art; FILE *fp; int count; char *name,*mode; Called to shovel an article out to an open file pointer. Intended to allow the server interface to implement any kind of massaging of the article text that is called for. count starts with 0, and counts the articles being written on this opening of the file. name is the file name, and mode is the mode it was opened with. This information is being passed in to allow implementation of selective copy of header lines, etc. based on the state of the file. Note that it WILL exist since it's already been opened. On count 0, this routine may be interested in whether it's empty or not. This routine WILL get called between vns_aopen and vns_aclose. char *Vns_version; The server layer must define this string. It should be a short string which describes the server type / version (something like "NNTP-1.0"). Will be printed as part of the vn version message. Some support routines: regex(), regcmp(), strtok() and strpbrk() are standard SYSV routines not normally available for BSD. vn uses them, so they are implemented in vn for those systems. Thus, they are available for use in the server interface. Be careful with strtok(), which retains context from call to call, if you are not familiar with its use. To free storage handed you by regcmp(), use regfree() instead of free(). char *vn_env(var, def) char *var, *def; Use this in place of getenv() to be consistent with the rest of vn. This routine checks first for the variable prefixed with "VN" if it doesn't already begin with "VN". If neither the prefixed or unprefixed name exists, it returns the def argument. This allows consistent override of EDITOR with VNEDITOR, etc. printex(....) printf style argument list. Generates a fatal error and exits. No newline necessary in the message, and printex() adds the error number to its output automatically. printex() may be called from anywhere in the server interface to generate a fatal error, except from vns_exit(). fgprintf(....) nonfatal diagnostic / informational message. printf style argument list. No extra formatting printed. Will only print if program is running in the foreground. fgprintf() should be called only from vns_envir() and vns_news(), as all the other routines come after user interaction has begun. char * str_store(s) char *s; Returns a pointer to a permanent copy of a string. DO NOT FREE THAT POINTER!! This routine is intended to save malloc() overhead, and returns pointers into larger buffer allocated with a single request. char * str_tstore(ptr,s) char *ptr; char *s; char * str_tpool(n) int n; char ** str_taptr(ptr) char *ptr; str_tfree(ptr) char *ptr; Temporary string storage. str_tpool is called with the maximum number of strings to be allocated, and it returns a pointer to use with the other calls (ptr argument). str_tstore returns allocated copies of strings. str_taptr returns an address which may be treated as a string array containing the str_tstore allocated strings beginning with the NEXT str_tstore() call. May be called several times to get several array pointers. str_tfree() frees all the strings allocated on the given ptr returned from str_tpool, as well as the pool pointer itself. The returned addresses are no longer valid, nor is the pool pointer. Multiple pool pointers may be maintained. Pool pointer contents should not be touched by the caller. These routines are intended for the convenience of vns_aopen(). HEADER FILES: server.h includes the header files used by vn which are intended to be known to server interface routines. The server interface routines may include system headers, this one, and their own headers. Among their own headers should be a config_*.h containing parameters which are likely to be changed from installation to installation, such as file paths. server.h contains node.h and head.h, defining the NODE and ARTHEADER structures. A useful constant defined in server.h is RECLEN. This should be used only as an array dimension for a stack character buffer, intended to be large enough to hold an arbitrary string. If you use this constant, you are making "large enough" the same value used in vn. Use it for fgets() buffers, or sprintf buffers prior to making allocated copies of the string, and you'll be working about the way vn does in a lot of places. SHAR_EOF fi # end of overwriting check if test -f 'std.c' then echo shar: will not over-write existing file "'std.c'" else cat << \SHAR_EOF > 'std.c' #include <stdio.h> #include <pwd.h> #include <ctype.h> #include <sys/param.h> #include "server.h" #include "config_std.h" #include "std.h" extern NODE *hashfind(); extern FILE *fopen(); extern char *index(), *rindex(); extern char *malloc(); extern char *str_tstore(), *str_tpool(), *str_store(); extern char *strtok(), *strpbrk(); extern char *regex(), *regcmp(); #ifdef MAILCHOOSE extern int (*Massage)(); #endif /* global flags signifying options set */ #define GF_ALL 1 /* -x option - scan everything */ #define GF_SPEC 2 /* -n option(s) - user specified groups */ #define GF_OVER 4 /* command line specification - overide marks */ char *Vns_version = "res1.0"; static char *Onews, *Newsrc; static int Ntopt, Nntopt, Nwopt, Nnwopt; static char *Wopt[NUMFILTER]; /* regular expressions for -w options */ static char *Topt[NUMFILTER]; /* for -t options */ static char *Negwopt[NUMFILTER]; /* for negated -w options */ static char *Negtopt[NUMFILTER]; /* for negated -t options */ static char *Options[OPTLINES]; static int Max_name, Optlines; static unsigned Gflags = 0; static char **Active; static int Actnum; static char *Mailer, *Poster; static char *RT_head = RTHEAD; static char *P_head = PHEAD; static char *M_head = MHEAD; static char *R_head = RHEAD; static char *TO_head = TOHEAD; static char *F_head = FHEAD; static char *FT_head = FTHEAD; static char *T_head = THEAD; static char *DIS_head = DISHEAD; static char *L_head = LHEAD; static char *N_head = NHEAD; static char *Fpfix = FPFIX; /* ** environment setup. */ vns_envir() { char dbuf[MAXPATHLEN], *rcname; char *vn_env(); struct passwd *ptr, *getpwuid(); #ifdef MAILCHOOSE int mail_prompt(); Massage = mail_prompt; #endif ptr = getpwuid (getuid()); rcname = vn_env("MAILER",DEF_MAIL); #ifdef INLETTER sprintf(dbuf,"cat %%s | %s",rcname); #else /* used as a format string TWICE (%%%% -> %% -> %) */ sprintf(dbuf,"cat %%%%s | %s %%s",rcname); #endif Mailer = str_store(dbuf); rcname = vn_env("VNPOSTER",DEF_POST); sprintf(dbuf,"%s %%s",rcname); Poster = str_store(dbuf); rcname = vn_env("NEWSRC",DEF_NEWSRC); if (*rcname != '/') { sprintf (dbuf, "%s/%s",ptr->pw_dir,rcname); Newsrc = str_store (dbuf); } else Newsrc = str_store (rcname); sprintf (dbuf, "%s/%s%s",ptr->pw_dir,".vn","XXXXXX"); mktemp(dbuf); Onews = str_store (dbuf); if (access (Newsrc,0) != 0) creat (Newsrc,0666); } /* change directory to group */ vns_gset(grp) char *grp; { char dbuf [RECLEN]; g_dir (grp,dbuf); if (chdir(dbuf) < 0) printex("can't change to newsgroup directory"); } /* g_dir converts newsgroup name to directory string */ static g_dir(s,t) char *s,*t; { char *ptr; sprintf (t,"%s/%s",SPOOLDIR,s); for (ptr=t+strlen(SPOOLDIR)+1; (ptr = index(ptr,'.')) != NULL; *ptr = '/') ; } /* ** myfind is used for hashfind() calls which aren't supposed to fail. */ static NODE * myfind(name) char *name; { NODE *n; n = hashfind(name); if (n == NULL) printex("Unexpected table lookup failure"); return (n); } vns_news(argc,argv,lfirst,nun) int argc; char **argv; int *lfirst, *nun; { FILE *fp; static char marks[] = { NEWS_ON, NEWS_OFF, '\0' }; int line, len, num; char buf [RECLEN], trail, optpflag, submark, *fret, *ptr; ++argv; --argc; /* fill table with active newsgroups */ fill_active (); if (argc > 0) { Gflags |= GF_OVER; arg_opt(argc,argv,lfirst,nun); optpflag = 'y'; } else optpflag = 'n'; if ((fp = fopen (Newsrc,"r")) == NULL) printex ("can't open %s for reading",Newsrc); Optlines = 0; for (line = 1; (fret = fgets(buf,RECLEN-1,fp)) != NULL && emptyline(buf) == 1; ++line) ; if (fret != NULL && strncmp (buf,"options",7) == 0) { Options[0] = str_store(buf); Optlines = 1; trail = buf [strlen(buf)-2]; for ( ; (fret = fgets(buf,RECLEN-1,fp)) != NULL; ++line) { if (trail != '\\' && buf[0] != ' ' && buf[0] != '\t') break; if (Optlines >= OPTLINES) printex ("%s - too many option lines (%d allowed)",Newsrc,OPTLINES); Options[Optlines] = str_store(buf); ++Optlines; if ((len = strlen(buf)) >= 2 && buf[len-2] != '\\') trail = buf[len-2]; else trail = '\0'; } } /* do the options from the newsrc file if there weren't command line args */ if (Optlines > 0 && optpflag == 'n') newsrc_opt (lfirst,nun); for ( ; fret != NULL; ++line, fret = fgets(buf,RECLEN-1,fp)) { if (emptyline(buf) == 1) continue; if ((ptr = strpbrk(buf,marks)) == NULL) { fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n", line,Newsrc,buf); continue; } submark = *ptr; *ptr = '\0'; ++ptr; num = 0; for (ptr = strtok(ptr," ,-\n"); ptr != NULL; ptr = strtok(NULL," ,-\n")) { len = atoi (ptr); for ( ; *ptr >= '0' && *ptr <= '9'; ++ptr) ; if (*ptr != '\0' || len < num) { num = -1; fprintf (stderr,"\nwarning: line %d of %s (%s) - bad syntax\n", line,Newsrc,buf); break; } num = len; } if (num < 0) continue; chkgroup (buf,submark,num,0); } fclose (fp); /* now take care of groups not specified in .newsrc */ art_active(); /* free up the option string storage */ for (num=0; num < Ntopt; ++num) regfree (Topt[num]); for (num=0; num < Nwopt; ++num) regfree (Wopt[num]); for (num=0; num < Nntopt; ++num) regfree (Negtopt[num]); for (num=0; num < Nnwopt; ++num) regfree (Negwopt[num]); Ntopt = Nwopt = Nntopt = Nnwopt = 0; /* free the active list */ free ((char *) Active); } static emptyline(s) char *s; { while (isspace(*s)) ++s; if (*s == '\0') return (1); return (0); } /* fill hash table from active news group list This is needed to be able to process options before scanning user order. Constructs an array of active newsgroup names for the rest of vns_nws(). */ static fill_active () { FILE *f; char *nread, act_rec[RECLEN]; int num,lownum,rcount; Max_name = 0; if ((f = fopen (ACTFILE,"r")) == NULL) printex ("couldn't open %s\n",ACTFILE); /* ** we do things this way so that we only examine active records ** once, minimizing the window where changes could screw us up ** at the cost of possibly alloc'ing a few extra bytes. We start ** with a count of one to have a positive rcount for alloc. */ for(rcount=1; fgets(act_rec, RECLEN-1, f) != NULL; ++rcount) ; if ((Active = (char **) malloc(rcount*sizeof(char *))) == NULL) printex("Memory allocation failure"); rewind(f); Actnum = 0; while (Actnum < rcount && fgets(act_rec, RECLEN-1, f) != NULL) { if (strtok (act_rec," \n") == NULL) continue; nread = strtok (NULL, " \n"); if (nread != NULL) num = atoi(nread); else num = 0; nread = strtok (NULL, " \n"); if (nread != NULL) lownum = atoi(nread); else lownum = 0; if (lownum > 0) --lownum; if (strlen(act_rec) > Max_name) Max_name = strlen(act_rec); /* enter newsgroup, point to permanent copy of name */ hashenter (act_rec, num, lownum); Active[Actnum] = (myfind(act_rec))->nd_name; ++Actnum; } fclose (f); } /* check active newsgroups not mentioned in NEWSRC file (SFLG_SCAN not set) */ static art_active () { int i; NODE *ptr; for( i=0; i < Actnum; ++i) { ptr = myfind(Active[i]); if ((ptr->state & SFLG_SCAN) == 0) chkgroup (ptr->nd_name, NEWS_ON, 0, 1); } } /* check group for new articles: s - group c - subscription indicator from NEWSRC n - number read new - new newsgroup flag */ static chkgroup (s,c,n,new) char *s,c; int n; int new; { NODE *ptr; char sub; int nrd; int lowart; int st; if ((ptr = hashfind(s)) != NULL && (ptr->state & SFLG_SCAN) == 0) { ptr->state |= SFLG_SCAN; #ifdef SYN_CHECK /* if "read" more than exist, reset */ if (n > ptr->highnum) { n = ptr->highnum - SYN_SETBACK; fgprintf("%s: .newsrc out of synch, resetting\n",s); } #endif lowart = ptr->lownum; if (n < ptr->lownum) n = ptr->lownum; nrd = n; sub = c; /* ** scan decision is rather complex, since GF_ALL setting ** overides "n" value, GF_SPEC indicates SFLG_SPEC flag used. ** if GF_OVER set, SFLG_SPEC overides subscription mark, else ** SFLG_SPEC AND subscribed is neccesary. */ if ((Gflags & GF_SPEC) != 0) { if ((ptr->state & SFLG_SPEC) == 0) c = NEWS_OFF; else { if ((Gflags & GF_OVER) != 0) c = NEWS_ON; } } if ((Gflags & GF_ALL) != 0) n = lowart; fw_group(s, new, sub == NEWS_ON, nrd, c == NEWS_ON); if (c == NEWS_ON && ptr->highnum > n) { st = outgroup (s,n,ptr->highnum); if (st > nrd) fw_chg(new, sub == NEWS_ON, st, c == NEWS_ON); } } } /* vns_write writes the .newsrc file */ vns_write(news,ncount) NODE **news; int ncount; { FILE *fp; NODE *p; char c; int i,rc; if (link(Newsrc,Onews) < 0) printex ("can't backup %s to %s before writing",Newsrc,Onews); if (unlink(Newsrc) < 0 || (fp = fopen(Newsrc,"w")) == NULL) printex ("can't open %s for writing (backed up in %s)",Newsrc,Onews); else { clearerr(fp); for (i=0; (rc = ferror(fp)) == 0 && i < Optlines; ++i) fprintf (fp,"%s",Options[i]); for (i=0; rc == 0 && i < ncount; ++i) { p = news[i]; if ((p->flags & FLG_SUB) == 0) c = NEWS_OFF; else c = NEWS_ON; #ifdef OLDRC fprintf (fp,"%s%c %d\n",p->nd_name,c,p->rdnum); #else if (p->rdnum > 0) fprintf(fp,"%s%c 1-%d\n",p->nd_name,c,p->rdnum); else fprintf(fp,"%s%c 0\n",p->nd_name,c); #endif rc = ferror(fp); } fclose (fp); if (rc != 0) printex ("write of %s failed, old copy stored in %s",Newsrc,Onews); else unlink (Onews); } } /* arg_opt must be called prior to option scanning, since it uses the options array. This is a bit of a kludge, but it saves a bunch of work. NOTE - no command name argument */ static arg_opt (argc,argv,lfirst,nun) int argc; char **argv; int *lfirst, *nun; { if (argc > OPTLINES) printex ("too many command line options (%d allowed)\n",OPTLINES); for (Optlines=0; Optlines < argc; ++Optlines) { Options[Optlines] = *argv; ++argv; } newsrc_opt(lfirst,nun); } /* option setting routine: sets global flags: GF_ALL for -x option GF_SPEC for -n. sets up filter array for article scanning */ static newsrc_opt(lfirst,nun) int *lfirst, *nun; { int i; char curopt,tmp[RECLEN],*tok; *nun = *lfirst = 0; Ntopt = Nwopt = Nnwopt = Nntopt = 0; curopt = '\0'; for (i=0; i < Optlines; ++i) { strcpy(tmp,Options[i]); for (tok = strtok(tmp,",\\ \t\n"); tok != NULL; tok = strtok(NULL,",\\ \t\n")) { if (*tok != '-') do_opt (curopt,tok); else { for (++tok; index("nwt",*tok) == NULL; ++tok) { /* options with no strings */ switch(*tok) { case 'S': Gflags &= ~GF_OVER; break; case '%': *lfirst = 1; break; case 'U': *nun = 1; break; #ifdef OLDRC case 'i': /* Treat "-i" as synonym for "-x" */ #endif case 'x': Gflags |= GF_ALL; default: break; } } curopt = *tok; if (*(++tok) != '\0') do_opt (curopt,tok); } } } } /* do_opt is for options with strings attached */ static do_opt (opt,str) char opt, *str; { switch (opt) { case 'n': Gflags |= GF_SPEC; specmark(str); break; case 'w': specfilter (FIL_AUTHOR,str); break; case 't': specfilter (FIL_TITLE,str); break; default: #ifdef OLDRC Gflags |= GF_SPEC; /* Assume anything else is newsgroup */ specmark(str); #endif break; } } static specfilter (comp,str) char comp,*str; { int *count; char **rex; /* ** we may set rex one past end of array. we will error before ** referencing it if that's the case, however. */ if (*str == '!') { if (comp == FIL_TITLE) { count = &Nntopt; rex = Negtopt + *count; } else { count = &Nnwopt; rex = Negwopt + *count; } ++str; } else { if (comp == FIL_TITLE) { count = &Ntopt; rex = Topt + *count; } else { count = &Nwopt; rex = Wopt + *count; } } if (*count >= NUMFILTER) printex ("too many %c options, %d allowed",comp,NUMFILTER); if ((*rex = regcmp(str,(char *) 0)) == NULL) printex ("%c option regular expression syntax: %s",comp,str); ++(*count); } /* handle the newsgroup specification string. ("all" convention - braack!!!) */ static specmark (s) char *s; { unsigned ormask,andmask; int i,len; char *ptr,*re,pattern[RECLEN]; NODE *nptr; if (*s == '!') { ++s; ormask = 0; andmask = ~SFLG_SPEC; if (*s == '\0') return; } else { ormask = SFLG_SPEC; andmask = 0xffff; } /* convert "all" not bounded by alphanumerics to ".*". ".all" becomes ".*" */ for (ptr = s; (len = findall(ptr)) >= 0; ptr += len+1) { if (len > 0 && isalnum (s[len-1])) continue; if (isalnum (s[len+3])) continue; if (len > 0 && s[len-1] == '.') { --len; strcpy (s+len,s+len+1); } s[len] = '.'; s[len+1] = '*'; strcpy (s+len+2,s+len+3); } /* now use regular expressions */ sprintf (pattern,"^%s$",s); if ((re = regcmp(pattern,(char *) 0)) == NULL) printex ("n option regular expression syntax: %s",s); for (i=0; i < Actnum; ++i) { nptr = myfind(Active[i]); if (regex(re,nptr->nd_name) != NULL) { nptr->state |= ormask; nptr->state &= andmask; } } regfree (re); } static findall (s) char *s; { int len; for (len=0; *s != '\0'; ++s,++len) { if (*s == 'a' && strncmp(s,"all",3) == 0) return (len); } return (-1); } static grp_indic (s,ok) char *s; int ok; { if (ok) fgprintf(" %s\n",s); else fgprintf(" %s - Can't access spool directory\n",s); } /* enter newsgroup articles. all articles between low and hi are to be included. Returns the highest number less than an OPENED (not neccesarily accepted) article to allow caller to revise "articles read" number beyond non-existent articles. */ outgroup (s,low,hi) char *s; int low,hi; { int i; char subj[RECLEN], lines[RECLEN], auth[RECLEN], gd[RECLEN]; int ret,op; if ((hi-low) > MAXARTRANGE) low = hi - MAXARTRANGE; ret = low; op = 1; g_dir(s,gd); if (chdir(gd) < 0) { grp_indic(s,0); return (ret); } grp_indic(s,1); for (i=low+1; i <= hi; ++i) { if (digname(i,subj,lines,auth,&op) >= 0) { fw_art(i,subj,lines,auth); } else { if (op) ret = i; } } return(ret); } /* ** open article and interpret options, if any. The op parameter is set ** to ZERO if and only if an article is opened. Used above as a flag to ** indicate no articles opened yet. */ static digname (n, subj, lines, auth, op) int n; char *subj, *lines, *auth; int *op; { int i,j; FILE *fp; char t[RECLEN]; char *nfgets(); /* open article */ sprintf (t,"%d", n); if ((fp = fopen(t,"r")) == NULL) return (-1); *op = 0; /* get subject, from and lines by reading article */ subj[0] = lines[0] = auth[0] = '?'; subj[1] = lines[1] = auth[1] = '\0'; for (i = 0; i < HDR_LINES && nfgets(t,RECLEN-1,fp) != NULL; ++i) { if (index(CHFIRST,t[0]) == NULL) continue; t[strlen(t) - 1] = '\0'; if (strncmp(T_head,t,THDLEN) == 0) { for (j=0; j < Nntopt; ++j) { if (regex(Negtopt[j],t+THDLEN) != NULL) { fclose(fp); return(-1); } } if (Ntopt > 0) { for (j=0; j < Ntopt; ++j) { if (regex(Topt[j],t+THDLEN) != NULL) break; } if (j >= Ntopt) { fclose(fp); return(-1); } } strcpy(subj,t+THDLEN); continue; } if (strncmp(F_head,t,FHDLEN) == 0) { for (j=0; j < Nnwopt; ++j) { if (regex(Negwopt[j],t+FHDLEN) != NULL) { fclose(fp); return(-1); } } if (Nwopt > 0) { for (j=0; j < Nwopt; ++j) { if (regex(Wopt[j],t+FHDLEN) != NULL) break; } if (j >= Nwopt) { fclose(fp); return(-1); } } strcpy(auth,t+FHDLEN); continue; } if (strncmp(L_head,t,LHDLEN) == 0) { strcpy(lines,t+LHDLEN); break; } } fclose (fp); /* reject empty or 1 line files */ if (i < 2) return (-1); return (0); } /* ** special fgets for reading header lines, which unfolds continued lines ** and throws away trailing stuff on buffer overflow. */ static char * nfgets(buf, size, fp) char *buf; int size; FILE *fp; { register int c; while (!feof(fp)) { if ((c = getc(fp)) == '\n') { if ((c = getc(fp)) == '\t' || c == ' ') continue; ungetc(c, fp); *buf = '\n'; ++buf; *buf = '\0'; ++buf; return (buf); } /* prevent "terminal bombs" */ if (c < ' ' || c == '\177') { switch(c) { case '\r': case '\010': case '\07': break; case '\177': c = '~'; break; case '\t': c = ' '; break; default: if (size > 1) { *buf = '^'; ++buf; --size; } c += 'A' - 1; break; } } if (size > 0) { *buf = c; ++buf; --size; } if (c == '\r') { if ((c = getc(fp)) != '\n') { ungetc(c, fp); continue; } if ((c = getc(fp)) != ' ' && c != '\t') { *buf = '\0'; ++buf; ungetc(c, fp); return (buf); } --buf; ++size; continue; } } *buf = '\0'; ++buf; return (NULL); } static char *Mail[2], *Show[6], *Post[4]; static char *Priv[8]; static char *Pool; FILE * vns_aopen(art,hdr) int art; ARTHEADER *hdr; { char buf[RECLEN]; char *dist, *reply, *from, *ngrp, *flto, *path, *resubj; FILE *fp; int n; char *mail_trim(); dist = resubj = path = reply = from = ngrp = flto = NULL; sprintf(buf,"%d",art); if ((fp = fopen(buf,"r")) == NULL) return(NULL); /* ** we only really need a lot extra if MAILCHOOSE, but allocating ** a temporary array of pointers isn't that much. Similarly, a ** few assignments, and the "Priv" declaration are only needed ** with some define settings. Not worth ifdef'ing. */ Pool = str_tpool(100); hdr->artid = "<some article>"; hdr->from = "<somebody>"; hdr->priv = Priv; hdr->postcmd = Poster; hdr->mail = Mail; hdr->show = Show; hdr->post = Post; hdr->priv_num = hdr->show_num = hdr->post_num = hdr->mail_num = 0; /* for conditional is abnormal - expected exit is break */ for (n=0; n < HDR_LINES && fgets(buf,RECLEN-1,fp) != NULL; ++n) { /* bail out at first non-header line */ if (buf[0] == '\n') break; if (strncmp(buf,RT_head,RTHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; reply = str_tstore(Pool,buf+RTHDLEN); continue; } if (strncmp(buf,P_head,PHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; path = str_tstore(Pool,buf+PHDLEN); continue; } if (strncmp(buf,DIS_head,DISHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; dist = str_tstore(Pool,buf); continue; } if (strncmp(buf,M_head,MHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; hdr->artid = str_tstore(Pool,buf+MHDLEN); continue; } if (strncmp(buf,F_head,FHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; (hdr->show)[hdr->show_num] = str_tstore(Pool,buf); from = hdr->from = (hdr->show)[hdr->show_num]+FHDLEN; ++(hdr->show_num); continue; } if (strncmp(buf,T_head,THDLEN) == 0) { buf [strlen(buf)-1] = '\0'; (hdr->show)[hdr->show_num] = str_tstore(Pool,buf); if (strncmp(buf+THDLEN,Fpfix,FPFLEN) != 0) { sprintf(buf,"%s%s%s",T_head,Fpfix, ((hdr->show)[hdr->show_num])+THDLEN); resubj = str_tstore(Pool,buf); } else resubj = (hdr->show)[hdr->show_num]; ++(hdr->show_num); continue; } if (strncmp(buf,N_head,NHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; /* if multiple newsgroups, include in "show" */ if (index(buf,',') != NULL) { (hdr->show)[hdr->show_num] = str_tstore(Pool,buf); ngrp = (hdr->show)[hdr->show_num] + NHDLEN; ++(hdr->show_num); } else ngrp = str_tstore(Pool,buf+NHDLEN); continue; } if (strncmp(buf,FT_head,FTHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; (hdr->show)[hdr->show_num] = str_tstore(Pool,buf); flto = (hdr->show)[hdr->show_num] + FTHDLEN; ++(hdr->show_num); continue; } if (strncmp(buf,L_head,LHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; hdr->lines = atoi(buf+LHDLEN); (hdr->show)[hdr->show_num] = str_tstore(Pool,buf); ++(hdr->show_num); continue; } } hdr->hlines = n; #ifdef MAILCHOOSE (hdr->priv)[hdr->priv_num] = resubj; ++(hdr->priv_num); if (reply != NULL) { (hdr->priv)[hdr->priv_num] = mail_trim(reply); ++(hdr->priv_num); } if (from != NULL) { (hdr->priv)[hdr->priv_num] = mail_trim(from); ++(hdr->priv_num); } if (path != NULL) { (hdr->priv)[hdr->priv_num] = mail_trim(path); ++(hdr->priv_num); } #else #ifdef MAILSMART if (reply == NULL) if (from != NULL) reply = from; else { if (path != NULL) reply = path; } #else if (path != NULL) reply = path; #endif reply = mail_trim(reply); if (reply != NULL) mail_cmd(hdr,reply,resubj); #endif /* MAILCHOOSE */ if (flto == NULL) { if ((flto = ngrp) == NULL) flto = "group.unknown"; } ngrp = rindex(flto,'.'); if (strncmp("mod.",flto,4) == 0 || (ngrp != NULL && strcmp(".announce",ngrp) == 0)) { sprintf(buf,"Cannot post a follow-up to \"%s\", reply with mail to moderator",flto); hdr->post_err = str_tstore(Pool,buf); return (fp); } if (ngrp != NULL && strcmp(ngrp,".general") == 0) { *ngrp = '\0'; sprintf(buf,"%s%s.followup",N_head,flto); } else sprintf(buf,"%s%s",N_head,flto); flto = str_tstore(Pool,buf); hdr->post_err = NULL; if (resubj != NULL) { (hdr->post)[hdr->post_num] = resubj; ++(hdr->post_num); } (hdr->post)[hdr->post_num] = flto; ++(hdr->post_num); sprintf(buf,"%s%s",R_head,hdr->artid); (hdr->post)[hdr->post_num] = str_tstore(Pool,buf); ++(hdr->post_num); if (dist != NULL) { (hdr->post)[hdr->post_num] = dist; ++(hdr->post_num); } return (fp); } #ifdef MAILCHOOSE /* ** routine to prompt user for mail path approval */ static mail_prompt(hdr) ARTHEADER *hdr; { int i; char buf[RECLEN],*ptr; tty_set(SAVEMODE); for (i=1; i < hdr->priv_num; ++i) { printf("%d - %s\n",i,(hdr->priv)[i]); } printf("\nType number to choose one of the above, or input address: "); fgets(buf,RECLEN-1,stdin); tty_set(RESTORE); ptr = strtok(buf," \t\n"); if (ptr == NULL) ptr = ""; i = strlen(ptr); if (i == 1) { i = atoi(ptr); if (i > 0 && i <= hdr->priv_num) ptr = (hdr->priv)[i]; i = 1; } /* ** If the user keeps cycling through here on the same article, ** we will eventually run out of strings. We made Pool large ** enough to make it unlikely (user will have to retry about 80 ** times without switching articles). Hardly elegant, but should ** be sufficient. */ if (i > 1 && hdr->priv_num < 8) { (hdr->priv)[hdr->priv_num] = str_tstore(Pool,ptr); ++(hdr->priv_num); } mail_cmd(hdr,ptr,(hdr->priv)[0]); } #endif /* ** trim () off potential mail address, and make copy if needed. ** addr must be allocated string. */ static char * mail_trim(addr) char *addr; { char buf[RECLEN]; char *ptr; if (index(addr,'(') == NULL) return(addr); strcpy(buf,addr); ptr = index(buf,'('); for (--ptr; *ptr == ' ' || *ptr == '\t'; --ptr) ; ++ptr; *ptr = '\0'; return (str_tstore(Pool,buf)); } /* ** format mail command. Subj must point to allocated string. */ static mail_cmd(hdr,addr,subj) ARTHEADER *hdr; char *addr, *subj; { char buf[RECLEN]; if (addr == NULL || *addr == '\0') { hdr->mail_err = "No address"; return; } hdr->mail_err = NULL; ; #ifdef INLETTER hdr->mailcmd = Mailer; sprintf(buf,"%s%s",TO_head,addr); (hdr->mail)[0] = str_tstore(Pool,buf); hdr->mail_num = 1; #else sprintf(buf,Mailer,addr); hdr->mailcmd = str_tstore(Pool,buf); hdr->mail_num = 0; #endif if (subj != NULL) { (hdr->mail)[hdr->mail_num] = subj; ++(hdr->mail_num); } } vns_aclose(fp) FILE *fp; { str_tfree(Pool); fclose(fp); } /* ** we don't use the count / name / mode arguments because this doesn't ** implement any fancy article massaging */ vns_asave(art,fp) int art; FILE *fp; { char buf[RECLEN]; FILE *fin; sprintf(buf,"%d",art); if ((fin = fopen(buf,"r")) == NULL) return; while (fgets(buf,RECLEN-1,fin) != NULL) fputs(buf,fp); fclose(fin); } vns_exit() { } SHAR_EOF fi # end of overwriting check if test -f 'std.h' then echo shar: will not over-write existing file "'std.h'" else cat << \SHAR_EOF > 'std.h' /* newsrc states */ #define NEWS_ON ':' #define NEWS_OFF '!' #define SFLG_SCAN 1 #define SFLG_SPEC 2 #define FPFIX "Re: " #define FPFLEN 4 #define FIL_AUTHOR 'w' #define FIL_TITLE 't' /* header lines and associated lengths. Strings should actually be used only once. */ #define RHEAD "References: " #define RHDLEN 12 #define MHEAD "Message-ID: " #define MHDLEN 12 #define PHEAD "Path: " #define PHDLEN 6 #define DHEAD "Date: " #define DHDLEN 6 #define RTHEAD "Reply-To: " #define RTHDLEN 10 #define TOHEAD "To: " #define TOHDLEN 4 #define FHEAD "From: " #define FHDLEN 6 #define FTHEAD "Followup-To: " #define FTHDLEN 13 #define DISHEAD "Distribution: " #define DISHDLEN 14 #define THEAD "Subject: " #define THDLEN 9 #define LHEAD "Lines: " #define LHDLEN 7 #define NHEAD "Newsgroups: " #define NHDLEN 12 #define CHFIRST "FSL" /* first char's of those used in page display */ SHAR_EOF fi # end of overwriting check if test -f 'config_std.h' then echo shar: will not over-write existing file "'config_std.h'" else cat << \SHAR_EOF > 'config_std.h' /* ** default news poster */ #define DEF_POST "/usr/lib/news/inews -h" /* ** default user .newsrc file */ #define DEF_NEWSRC ".newsrc" /* ** If INLETTER is defined, the address line will be placed into the ** file editted by the user, and the mailer is assumed smart enough ** to understand about header lines in the file. Otherwise the ** address is part of the mailer's command line. ** ** if MAILSMART is defined, The From: line will be used for mail replies, ** or overridden by a "Reply-to:" line if present - "Path:" will be used ** as a last resort. If MAILSMART is not defined, "Path:" will simply be ** used. ** ** if MAILCHOOSE is defined, the user is prompted before edit with all ** of the address lines to choose from, or to input a new one. MAILCHOOSE ** makes MAILSMART irrelevant, but the two are independent of INLETTER. ** #define MAILCHOOSE */ #define MAILSMART #define INLETTER /* ** default mail sender. If INLETTER, will be done as ** cat <file> | DEF_MAIL, Otherwise, cat <file> | DEF_MAIL <address> ** user's MAILER variable will have to conform, too. */ #ifdef INLETTER #define DEF_MAIL "/usr/lib/sendmail -t" #else #define DEF_MAIL "/bin/mail" #endif /* ** OLDRC defined for an apparently earlier news version which took unnamed ** command line options as synonyms for -n, and did not take ranges in ** the .newsrc file. Probably useless, but kept in for historical reasons. ** **#define OLDRC */ /* ** article spool directory */ #define SPOOLDIR "/usr/spool/news" /* ** active file */ #define ACTFILE "/usr/lib/news/active" /* ** maximum number of option lines in .newsrc */ #define OPTLINES 60 /* ** maximum number of filter options */ #define NUMFILTER 30 /* ** maximum number of file lines to search looking for header lines. */ #define HDR_LINES 36 /* ** When a newsgroup is scanned, we ignore articles less than <high spool> - ** MAXARTRANGE. This is intended to prevent ridiculous numbers of article ** opening attempts the first time a user reads a new newsgroup which has a ** huge difference between the high and low spool numbers, perhaps due to ** some articles not getting expired. */ #define MAXARTRANGE 1600 /* about 2 weeks of soc.singles */ /* ** If we detect that the user has a higher number in .newsrc than the ** high article number, obviously the active file is out of synch with the ** .newsrc. We set the user's number back to the low article number in ** this case, on the theory that it's better to repeat stuff than miss ** articles. On such setbacks, we won't backdate the user by more than ** SYN_SETBACK articles, preventing floods of articles on large newsgroups ** if you don't define SYN_CHECK, the user's number won't be adjusted in ** this case, choosing to lose articles rather than show old ones. */ #define SYN_CHECK #define SYN_SETBACK 60 SHAR_EOF fi # end of overwriting check # End of shell archive exit 0