This is a new archive version of TRN at patchlevel 3. The original posting took up Volume23, issues 60 to 73, with various problems. These files replace those issues. #! /bin/sh # This is a shell archive. Remove anything before this line, then feed it # into a shell via "sh file" or similar. To overwrite existing files, # type "sh file -c". # The tool that generated this appeared in the comp.sources.unix newsgroup; # send mail to comp-sources-unix@uunet.uu.net if you want that tool. # Contents: intrp.c mt-process.c # Wrapped by rsalz@litchi.bbn.com on Fri Aug 23 16:38:54 1991 PATH=/bin:/usr/bin:/usr/ucb ; export PATH echo If this archive is complete, you will see the following message: echo ' "shar: End of archive 5 (of 14)."' if test -f 'intrp.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'intrp.c'\" else echo shar: Extracting \"'intrp.c'\" \(26891 characters\) sed "s/^X//" >'intrp.c' <<'END_OF_FILE' X/* $Header: intrp.c,v 4.3.3.3 91/01/16 02:43:42 davison Trn $ X * X * $Log: intrp.c,v $ X * Revision 4.3.3.3 91/01/16 02:43:42 davison X * Integrated rn patches 48-54. X * X * Revision 4.3.3.2 90/08/20 16:29:08 davison X * Added HOSTFILE handling. Add OURDOMAIN if site has no '.' X * X * Revision 4.3.3.1 90/07/21 20:20:13 davison X * Initial Trn Release X * X * Revision 4.3.2.11 90/12/31 11:47:44 sob X * NEWSADMIN could not cancel articles because it was not getting set. X * X * Revision 4.3.2.10 90/12/31 00:02:55 sob X * Moved HIDDENET to remove unneeded cruft. X * X * Revision 4.3.2.9 90/12/30 03:48:11 sob X * Changed "hidden" to "hiddennet" to be like nntp and bnews. X * Made it possible to cancel articles if hiddennet is defined. X * X * Revision 4.3.2.8 90/11/22 13:52:27 sob X * Made changes to keep preprocessors from complaining. X * X * Revision 4.3.2.7 90/11/05 23:59:33 sob X * moved the definition of tmpbuf such that it get defined before it is used. X * X * Revision 4.3.2.6 90/11/03 18:52:31 sob X * Fixed bug in the definition of the nodename using the uname() system call. X * X * Revision 4.3.2.5 90/10/01 01:31:18 sob X * Fixed problem with struct utsname reported by jrallen@devildog.att.com X * when rn is compiled on the Amdahl 5890 UTS 2.0 system. X * X * Revision 4.3.2.4 90/04/23 00:31:20 sob X * Removed unneeded atoi call. X * X * Revision 4.3.2.3 90/03/22 23:04:35 sob X * Fixes provided by Wayne Davison X * X * Revision 4.3.2.2 90/03/17 17:03:12 sob X * Fixed determination of the news superuser's id. Fix provided by Chip X * Rosenthal . X * X * Revision 4.3.2.1 89/12/17 02:54:55 sob X * Removed redundant include directive. X * X * Revision 4.3.1.5 85/05/23 17:21:24 lwall X * Now allows 'r' and 'f' on null articles. X * X * Revision 4.3.1.4 85/05/21 13:35:21 lwall X * Sped up "rn -c" by not doing unnecessary initialization. X * X * Revision 4.3.1.3 85/05/17 10:37:11 lwall X * Fixed & substitution to capitalize last name too. X * X * Revision 4.3.1.2 85/05/15 14:39:45 lwall X * Spelled gecos right. X * X * Revision 4.3.1.1 85/05/10 11:33:51 lwall X * Branch for patches. X * X * Revision 4.3 85/05/01 11:40:54 lwall X * Baseline for release with 4.3bsd. X * X */ X X#include "EXTERN.h" X#include "common.h" X#include "util.h" X#include "search.h" X#include "head.h" X#include "rn.h" X#include "artsrch.h" X#include "ng.h" X#include "respond.h" X#include "rcstuff.h" X#include "bits.h" X#include "artio.h" X#include "term.h" X#include "final.h" X#ifdef USETHREADS X#include "rthreads.h" X#endif X#include "INTERN.h" X#include "intrp.h" X Xchar orgname[] = ORGNAME; X X/* name of this site */ X#ifndef HIDDENNET X#ifdef GETHOSTNAME X char *hostname; X# undef SITENAME X# define SITENAME hostname X#else /* !GETHOSTNAME */ X# ifdef DOUNAME X# include X struct utsname utsn; X# undef SITENAME X# define SITENAME utsn.nodename X# else /* !DOUNAME */ X# ifdef PHOSTNAME X char *hostname; X# undef SITENAME X# define SITENAME hostname X# else /* !PHOSTNAME */ X# ifdef WHOAMI X# undef SITENAME X# define SITENAME sysname X# endif /* WHOAMI */ X# endif /* PHOSTNAME */ X# endif /* DOUNAME */ X#endif /* GETHOSTNAME */ X#endif /*HIDDENNET */ X X#ifdef TILDENAME Xstatic char *tildename = Nullch; Xstatic char *tildedir = Nullch; X#endif X Xchar *realname INIT(Nullch); /* real name of sender from /etc/passwd */ X X#ifdef CONDSUB Xchar *skipinterp ANSI((char *,char *)); X#endif X Xstatic void abort_interp ANSI((void)); X Xvoid Xintrp_init(tcbuf) Xchar *tcbuf; X{ X char *getlogin(); X X spool = savestr(filexp(SPOOL)); /* usually /usr/spool/news */ X X /* get environmental stuff */ X X#ifdef NEWSADMIN X#ifdef GETPWENT X { X struct passwd *getpwnam(); X struct passwd *pwd = getpwnam(NEWSADMIN); X X if (pwd != NULL) X newsuid = pwd->pw_uid; X } X#endif /* GETPWENT */ X#endif /* NEWSADMIN */ X /* get home directory */ X X homedir = getenv("HOME"); X if (homedir == Nullch) X homedir = getenv("LOGDIR"); X X dotdir = getval("DOTDIR",homedir); X X /* get login name */ X X logname = getenv("USER"); X if (logname == Nullch) X logname = getenv("LOGNAME"); X#ifdef GETLOGIN X if (logname == Nullch) X logname = savestr(getlogin()); X#endif X X#ifdef NEWSADMIN X /* if this is the news admin than load his UID into newsuid */ X X if ( strEQ(logname,NEWSADMIN) ) X newsuid = getuid(); X#endif X X if (checkflag) /* that getwd below takes ~1/3 sec. */ X return; /* and we do not need it for -c */ X getwd(tcbuf); /* find working directory name */ X origdir = savestr(tcbuf); /* and remember it */ X X /* get the real name of the person (%N) */ X /* Must be done after logname is read in because BERKNAMES uses that */ X X strcpy(tcbuf,getrealname(getuid())); X realname = savestr(tcbuf); X X /* name of header file (%h) */ X X headname = savestr(filexp(HEADNAME)); X X /* name of this site (%H) */ X X#ifndef HIDDENNET X#ifdef HOSTFILE X if ((tmpfp = fopen(HOSTFILE,"r")) == NULL) { X hostname = "unknown"; X printf("Warning: Couldn't open %s to determine hostname!\n", HOSTFILE); X } else { X fgets(buf, sizeof(buf), tmpfp); X buf[strlen(buf)-1] = 0; X hostname = savestr(buf); X fclose(tmpfp); X } X#else X#ifdef GETHOSTNAME X gethostname(buf,sizeof buf); X hostname = savestr(buf); X#else X#ifdef DOUNAME X /* get sysname */ X uname(&utsn); X#else X#ifdef PHOSTNAME X { X FILE *popen(); X FILE *pipefp = popen(PHOSTNAME,"r"); X X if (pipefp == Nullfp) { X printf("Can't find hostname\n"); X sig_catcher(0); X } X fgets(buf,sizeof buf,pipefp); X buf[strlen(buf)-1] = '\0'; /* wipe out newline */ X hostname = savestr(buf); X pclose(pipefp); X } X#endif /* PHOSTNAME */ X#endif /* DOUNAME */ X#endif /* GETHOSTNAME */ X#endif /* HOSTFILE */ X if (index(SITENAME,'.') == NULL) { X sprintf(buf, "%s.%s", SITENAME, OURDOMAIN); X sitename = savestr(buf); X } else X sitename = savestr(SITENAME); X#else X sitename = savestr(OURDOMAIN); X#endif X} X X/* expand filename via %, ~, and $ interpretation */ X/* returns pointer to static area */ X/* Note that there is a 1-deep cache of ~name interpretation */ X Xchar * Xfilexp(s) Xregister char *s; X{ X static char filename[CBUFLEN]; X char scrbuf[CBUFLEN]; X register char *d; X X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("< %s\n",s) FLUSH; X#endif X interp(filename, (sizeof filename), s); /* interpret any % escapes */ X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("%% %s\n",filename) FLUSH; X#endif X s = filename; X if (*s == '~') { /* does destination start with ~? */ X if (!*(++s) || *s == '/') { X sprintf(scrbuf,"%s%s",homedir,s); X /* swap $HOME for it */ X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("~ %s\n",scrbuf) FLUSH; X#endif X strcpy(filename,scrbuf); X } X else { X#ifdef TILDENAME X for (d=scrbuf; isalnum(*s); s++,d++) X *d = *s; X *d = '\0'; X if (tildedir && strEQ(tildename,scrbuf)) { X strcpy(scrbuf,tildedir); X strcat(scrbuf, s); X strcpy(filename, scrbuf); X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("r %s %s\n",tildename,tildedir) FLUSH; X#endif X } X else { X if (tildename) { X free(tildename); X free(tildedir); X } X tildedir = Nullch; X tildename = savestr(scrbuf); X#ifdef GETPWENT /* getpwnam() is not the paragon of efficiency */ X { X struct passwd *getpwnam(); X struct passwd *pwd = getpwnam(tildename); X X sprintf(scrbuf,"%s%s",pwd->pw_dir,s); X tildedir = savestr(pwd->pw_dir); X strcpy(filename,scrbuf); X#ifdef GETPWENT X endpwent(); X#endif X } X#else /* this will run faster, and is less D space */ X { /* just be sure LOGDIRFIELD is correct */ X FILE *pfp = fopen("/etc/passwd","r"); X char tmpbuf[512]; X int i; X X if (pfp == Nullfp) { X printf(cantopen,"passwd") FLUSH; X sig_catcher(0); X } X while (fgets(tmpbuf,512,pfp) != Nullch) { X d = cpytill(scrbuf,tmpbuf,':'); X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("p %s\n",tmpbuf) FLUSH; X#endif X if (strEQ(scrbuf,tildename)) { X for (i=LOGDIRFIELD-2; i; i--) { X if (d) X d = index(d+1,':'); X } X if (d) { X cpytill(scrbuf,d+1,':'); X tildedir = savestr(scrbuf); X strcat(scrbuf,s); X strcpy(filename,scrbuf); X } X break; X } X } X fclose(pfp); X } X#endif X } X#else /* !TILDENAME */ X#ifdef VERBOSE X IF(verbose) X fputs("~loginname not implemented.\n",stdout) FLUSH; X ELSE X#endif X#ifdef TERSE X fputs("~login not impl.\n",stdout) FLUSH; X#endif X#endif X } X } X else if (*s == '$') { /* starts with some env variable? */ X d = scrbuf; X *d++ = '%'; X if (s[1] == '{') X strcpy(d,s+2); X else { X *d++ = '{'; X for (s++; isalnum(*s); s++) *d++ = *s; X /* skip over token */ X *d++ = '}'; X strcpy(d,s); X } X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("$ %s\n",scrbuf) FLUSH; X#endif X interp(filename, (sizeof filename), scrbuf); X /* this might do some extra '%'s but */ X /* that is how the Mercedes Benz */ X } X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X printf("> %s\n",filename) FLUSH; X#endif X return filename; X} X X#ifdef CONDSUB X/* skip interpolations */ X Xchar * Xskipinterp(pattern,stoppers) Xregister char *pattern; Xchar *stoppers; X{ X X while (*pattern && (!stoppers || !index(stoppers,*pattern))) { X#ifdef DEBUGGING X if (debug & 8) X printf("skipinterp till %s at %s\n",stoppers?stoppers:"",pattern); X#endif X if (*pattern == '%' && pattern[1]) { X switch (*++pattern) { X case '{': X for (pattern++; *pattern && *pattern != '}'; pattern++) X if (*pattern == '\\') X pattern++; X break; X case '[': X for (pattern++; *pattern && *pattern != ']'; pattern++) X if (*pattern == '\\') X pattern++; X break; X#ifdef CONDSUB X case '(': { X pattern = skipinterp(pattern+1,"!="); X if (!*pattern) X goto getout; X for (pattern++; *pattern && *pattern != '?'; pattern++) X if (*pattern == '\\') X pattern++; X if (!*pattern) X goto getout; X pattern = skipinterp(pattern+1,":)"); X if (*pattern == ':') X pattern = skipinterp(pattern+1,")"); X break; X } X#endif X#ifdef BACKTICK X case '`': { X pattern = skipinterp(pattern+1,"`"); X break; X } X#endif X#ifdef PROMPTTTY X case '"': X pattern = skipinterp(pattern+1,"\""); X break; X#endif X default: X break; X } X pattern++; X } X else { X if (*pattern == '^' && pattern[1]) X pattern += 2; X else if (*pattern == '\\' && pattern[1]) X pattern += 2; X else X pattern++; X } X } Xgetout: X return pattern; /* where we left off */ X} X#endif X X/* interpret interpolations */ X Xchar * Xdointerp(dest,destsize,pattern,stoppers) Xregister char *dest; Xregister int destsize; Xregister char *pattern; Xchar *stoppers; X{ X char *subj_buf = Nullch; X char *ngs_buf = Nullch; X char *refs_buf = Nullch; X char *artid_buf = Nullch; X char *reply_buf = Nullch; X char *from_buf = Nullch; X char *path_buf = Nullch; X char *follow_buf = Nullch; X char *dist_buf = Nullch; X char *line_buf = Nullch; X register char *s, *h; X register int i; X char scrbuf[512]; X bool upper = FALSE; X bool lastcomp = FALSE; X int metabit = 0; X X while (*pattern && (!stoppers || !index(stoppers,*pattern))) { X#ifdef DEBUGGING X if (debug & 8) X printf("dointerp till %s at %s\n",stoppers?stoppers:"",pattern); X#endif X if (*pattern == '%' && pattern[1]) { X upper = FALSE; X lastcomp = FALSE; X for (s=Nullch; !s; ) { X switch (*++pattern) { X case '^': X upper = TRUE; X break; X case '_': X lastcomp = TRUE; X break; X case '/': X#ifdef ARTSRCH X s = scrbuf; X if (!index("/?g",pattern[-2])) X *s++ = '/'; X strcpy(s,lastpat); X s += strlen(s); X if (pattern[-2] != 'g') { X if (index("/?",pattern[-2])) X *s++ = pattern[-2]; X else X *s++ = '/'; X if (art_howmuch == 1) X *s++ = 'h'; X else if (art_howmuch == 2) X *s++ = 'a'; X if (art_doread) X *s++ = 'r'; X } X *s = '\0'; X s = scrbuf; X#else X s = nullstr; X#endif X break; X case '{': X pattern = cpytill(scrbuf,pattern+1,'}'); X if (s = index(scrbuf,'-')) X *s++ = '\0'; X else X s = nullstr; X s = getval(scrbuf,s); X break; X case '[': X pattern = cpytill(scrbuf,pattern+1,']'); X i = set_line_type(scrbuf,scrbuf+strlen(scrbuf)); X if (line_buf) X free(line_buf); X s = line_buf = fetchlines(art,i); X break; X#ifdef CONDSUB X case '(': { X COMPEX *oldbra_compex = bra_compex; X COMPEX cond_compex; X char rch; X bool matched; X X init_compex(&cond_compex); X pattern = dointerp(dest,destsize,pattern+1,"!="); X rch = *pattern; X if (rch == '!') X pattern++; X if (*pattern != '=') X goto getout; X pattern = cpytill(scrbuf,pattern+1,'?'); X if (!*pattern) X goto getout; X if (s = compile(&cond_compex,scrbuf,TRUE,TRUE)) { X printf("%s: %s\n",scrbuf,s) FLUSH; X pattern += strlen(pattern); X goto getout; X } X matched = (execute(&cond_compex,dest) != Nullch); X if (cond_compex.nbra) /* were there brackets? */ X bra_compex = &cond_compex; X if (matched==(rch == '=')) { X pattern = dointerp(dest,destsize,pattern+1,":)"); X if (*pattern == ':') X pattern = skipinterp(pattern+1,")"); X } X else { X pattern = skipinterp(pattern+1,":)"); X if (*pattern == ':') X pattern++; X pattern = dointerp(dest,destsize,pattern,")"); X } X s = dest; X bra_compex = oldbra_compex; X free_compex(&cond_compex); X break; X } X#endif X#ifdef BACKTICK X case '`': { X FILE *pipefp, *popen(); X X pattern = dointerp(scrbuf,(sizeof scrbuf),pattern+1,"`"); X pipefp = popen(scrbuf,"r"); X if (pipefp != Nullfp) { X int len; X X len = fread(scrbuf,sizeof(char),(sizeof scrbuf)-1, X pipefp); X scrbuf[len] = '\0'; X pclose(pipefp); X } X else { X printf("\nCan't run %s\n",scrbuf); X *scrbuf = '\0'; X } X for (s=scrbuf; *s; s++) { X if (*s == '\n') { X if (s[1]) X *s = ' '; X else X *s = '\0'; X } X } X s = scrbuf; X break; X } X#endif X#ifdef PROMPTTTY X case '"': X pattern = dointerp(scrbuf,(sizeof scrbuf),pattern+1,"\""); X fputs(scrbuf,stdout) FLUSH; X resetty(); X gets(scrbuf); X noecho(); X crmode(); X s = scrbuf; X break; X#endif X case '~': X s = homedir; X break; X case '.': X s = dotdir; X break; X case '$': X s = scrbuf; X sprintf(s,"%d",getpid()); X break; X case '#': X s = scrbuf; X sprintf(s,"%d",perform_cnt); X break; X case '0': case '1': case '2': case '3': case '4': X case '5': case '6': case '7': case '8': case '9': X#ifdef CONDSUB X s = getbracket(bra_compex,*pattern - '0'); X#else X s = nullstr; X#endif X break; X case 'a': X s = scrbuf; X sprintf(s,"%ld",(long)art); X break; X case 'A': X#ifdef LINKART X s = linkartname; /* so Eunice people get right file */ X#else X s = scrbuf; X sprintf(s,"%s/%s/%ld",spool,ngdir,(long)art); X#endif X break; X case 'b': X s = savedest; X break; X case 'B': X s = scrbuf; X sprintf(s,"%ld",(long)savefrom); X break; X case 'c': X s = ngdir; X break; X case 'C': X s = ngname; X break; X case 'd': X s = scrbuf; X sprintf(s,"%s/%s",spool,ngdir); X break; X case 'D': X s = dist_buf = fetchlines(art,DIST_LINE); X break; X case 'e': X s = extractprog; X break; X#ifdef USETHREADS X case 'E': { X int sel, unseen; X X sel = curr_p_art && (selected_roots[curr_p_art->root] & 1); X unseen = (art <= lastart) && !was_read(art); X sprintf(scrbuf,"%ld",(long)toread[ng]-selected_count X -unthreaded-(!sel && unseen)); X s = scrbuf; X break; X } X#endif X case 'f': /* from line */ X#ifdef ASYNC_PARSE X parse_maybe(art); X#endif X if (htype[REPLY_LINE].ht_minpos >= 0) { X /* was there a reply line? */ X if (!(s=reply_buf)) X s = reply_buf = fetchlines(art,REPLY_LINE); X } X else if (!(s = from_buf)) X s = from_buf = fetchlines(art,FROM_LINE); X break; X case 'F': X#ifdef ASYNC_PARSE X parse_maybe(art); X#endif X if (htype[FOLLOW_LINE].ht_minpos >= 0) X /* is there a Followup-To line? */ X s = follow_buf = fetchlines(art,FOLLOW_LINE); X else { X int off; X X s = ngs_buf = fetchlines(art,NGS_LINE); X if (h = instr(s,"net.general")) { X off = h-s; X strncpy(scrbuf,s,off+4); X strcpy(scrbuf+off+4,"followup"); X safecpy(scrbuf+off+12,h+11,sizeof(scrbuf)); X s = scrbuf; X } X } X break; X case 'h': /* header file name */ X s = headname; X break; X case 'H': /* host name */ X s = sitename; X break; X case 'i': X if (!(s=artid_buf)) X s = artid_buf = fetchlines(art,MESSID_LINE); X if (*s && *s != '<') { X sprintf(scrbuf,"<%s>",artid_buf); X s = scrbuf; X } X break; X case 'I': /* ref article indicator */ X s = scrbuf; X sprintf(scrbuf,"'%s'",indstr); X break; X case 'l': /* rn library */ X#ifdef NEWSADMIN X s = newsadmin; X#else X s = "???"; X#endif X break; X case 'L': /* login id */ X s = logname; X break; X case 'm': /* current mode */ X s = scrbuf; X *s = mode; X s[1] = '\0'; X break; X case 'M': X#ifdef DELAYMARK X sprintf(scrbuf,"%ld",(long)dmcount); X s = scrbuf; X#else X s = nullstr; X#endif X break; X case 'n': /* newsgroups */ X s = ngs_buf = fetchlines(art,NGS_LINE); X break; X case 'N': /* full name */ X s = getval("NAME",realname); X break; X case 'o': /* organization */ X s = getval("ORGANIZATION",orgname); X#ifdef ORGFILE X if (*s == '/') { X FILE *ofp = fopen(s,"r"); X X if (ofp) { X fgets(scrbuf,sizeof scrbuf,ofp); X fclose(ofp); X s = scrbuf; X s[strlen(s)-1] = '\0'; X } X } X#endif X break; X case 'O': X s = origdir; X break; X case 'p': X s = cwd; X break; X case 'P': X s = spool; X break; X case 'r': X#ifdef ASYNC_PARSE X parse_maybe(art); X#endif X if (htype[REFS_LINE].ht_minpos >= 0) { X refs_buf = fetchlines(art,REFS_LINE); X refscpy(scrbuf,(sizeof scrbuf),refs_buf); X } X else X *scrbuf = '\0'; X s = rindex(scrbuf,'<'); X break; X case 'R': X#ifdef ASYNC_PARSE X parse_maybe(art); X#endif X if (htype[REFS_LINE].ht_minpos >= 0) { X refs_buf = fetchlines(art,REFS_LINE); X refscpy(scrbuf,(sizeof scrbuf),refs_buf); X /* no more than 3 prior references allowed, X ** including the one concatenated below */ X if ((s = rindex(scrbuf,'<')) > scrbuf) { X *s = '\0'; X h = rindex(scrbuf,'<'); X *s = '<'; X if (h > scrbuf) X strcpy(scrbuf,h); X } X } X else X *scrbuf = '\0'; X if (!artid_buf) X artid_buf = fetchlines(art,MESSID_LINE); X if (artid_buf[0] == '<') X safecat(scrbuf,artid_buf,sizeof(scrbuf)); X else if (artid_buf[0]) { X char tmpbuf[64]; X X sprintf(tmpbuf,"<%s>",artid_buf); X safecat(scrbuf,tmpbuf,sizeof(scrbuf)); X } X s = scrbuf; X break; X case 's': X if (!(s=subj_buf)) X s = subj_buf = fetchsubj(art,TRUE,TRUE); X /* get subject handy */ X while ((*s=='R'||*s=='r')&&(s[1]=='E'||s[1]=='e')&&s[2]==':') { X /* skip extra Re: */ X s += 3; X if (*s == ' ') X s++; X } X if (h = instr(s,"- (nf")) X *h = '\0'; X break; X case 'S': X if (!(s=subj_buf)) X s = subj_buf = fetchsubj(art,TRUE,TRUE); X /* get subject handy */ X if ((*s=='R'||*s=='r')&&(s[1]=='E'||s[1]=='e')&&s[2]==':') { X /* skip extra Re: */ X s += 3; X if (*s == ' ') X s++; X } X break; X case 't': X case 'T': X#ifdef ASYNC_PARSE X parse_maybe(art); X#endif X if (htype[REPLY_LINE].ht_minpos >= 0) { X /* was there a reply line? */ X if (!(s=reply_buf)) X s = reply_buf = fetchlines(art,REPLY_LINE); X } X else if (!(s = from_buf)) X s = from_buf = fetchlines(art,FROM_LINE); X if (*pattern == 'T') { X if (htype[PATH_LINE].ht_minpos >= 0) { X /* should we substitute path? */ X s = path_buf = fetchlines(art,PATH_LINE); X } X i = strlen(sitename); X if (strnEQ(sitename,s,i) && s[i] == '!') X s += i + 1; X } X if ((h=index(s,'(')) != Nullch) X /* strip garbage from end */ X *(h-1) = '\0'; X else if ((h=index(s,'<')) != Nullch) { X /* or perhaps from beginning */ X s = h+1; X if ((h=index(s,'>')) != Nullch) X *h = '\0'; X } X break; X case 'u': X sprintf(scrbuf,"%ld",(long)toread[ng]); X s = scrbuf; X break; X case 'U': { X int unseen; X X unseen = (art <= lastart) && !was_read(art); X#ifdef USETHREADS X if (selected_root_cnt) { X int sel; X X sel = curr_p_art X && (selected_roots[curr_p_art->root] & 1); X sprintf(scrbuf,"%ld", X (long)selected_count-(sel && unseen)); X } X else X sprintf(scrbuf,"%ld",(long)toread[ng]-unthreaded X -unseen); X#else X sprintf(scrbuf,"%ld",(long)toread[ng]-unseen); X#endif X s = scrbuf; X break; X } X case 'x': /* news library */ X s = lib; X break; X case 'X': /* rn library */ X s = rnlib; X break; X case 'z': X#ifdef LINKART X s = linkartname; /* so Eunice people get right file */ X#else X s = scrbuf; X sprintf(s,"%ld",(long)art); X#endif X if (stat(s,&filestat) < 0) X filestat.st_size = 0L; X sprintf(scrbuf,"%5ld",(long)filestat.st_size); X s = scrbuf; X break; X#ifdef USETHREADS X case 'Z': X sprintf(scrbuf,"%ld",(long)selected_count); X s = scrbuf; X break; X#endif X default: X if (--destsize <= 0) X abort_interp(); X *dest++ = *pattern | metabit; X s = nullstr; X break; X } X } X if (!s) X s = nullstr; X pattern++; X if (upper || lastcomp) { X char *t; X X if (s != scrbuf) { X safecpy(scrbuf,s,(sizeof scrbuf)); X s = scrbuf; X } X if (upper || !(t=rindex(s,'/'))) X t = s; X while (*t && !isalpha(*t)) X t++; X if (islower(*t)) X *t = toupper(*t); X } X i = metabit; /* maybe get into register */ X if (s == dest) { X while (*dest) { X if (--destsize <= 0) X abort_interp(); X *dest++ |= i; X } X } X else { X while (*s) { X if (--destsize <= 0) X abort_interp(); X *dest++ = *s++ | i; X } X } X } X else { X if (--destsize <= 0) X abort_interp(); X if (*pattern == '^' && pattern[1]) { X ++pattern; /* skip uparrow */ X i = *pattern; /* get char into a register */ X if (i == '?') X *dest++ = '\177' | metabit; X else if (i == '(') { X metabit = 0200; X destsize++; X } X else if (i == ')') { X metabit = 0; X destsize++; X } X else X *dest++ = i & 037 | metabit; X pattern++; X } X else if (*pattern == '\\' && pattern[1]) { X ++pattern; /* skip backslash */ X i = *pattern; /* get char into a register */ X X /* this used to be a switch but the if may save space */ X X if (i >= '0' && i <= '7') { X i = 1; X while (i < 01000 && *pattern >= '0' && *pattern <= '7') { X i <<= 3; X i += *pattern++ - '0'; X } X *dest++ = i & 0377 | metabit; X --pattern; X } X else if (i == 'b') X *dest++ = '\b' | metabit; X else if (i == 'f') X *dest++ = '\f' | metabit; X else if (i == 'n') X *dest++ = '\n' | metabit; X else if (i == 'r') X *dest++ = '\r' | metabit; X else if (i == 't') X *dest++ = '\t' | metabit; X else X *dest++ = i | metabit; X pattern++; X } X else X *dest++ = *pattern++ | metabit; X } X } X *dest = '\0'; Xgetout: X if (subj_buf != Nullch) /* return any checked out storage */ X free(subj_buf); X if (ngs_buf != Nullch) X free(ngs_buf); X if (refs_buf != Nullch) X free(refs_buf); X if (artid_buf != Nullch) X free(artid_buf); X if (reply_buf != Nullch) X free(reply_buf); X if (from_buf != Nullch) X free(from_buf); X if (path_buf != Nullch) X free(path_buf); X if (follow_buf != Nullch) X free(follow_buf); X if (dist_buf != Nullch) X free(dist_buf); X if (line_buf != Nullch) X free(line_buf); X return pattern; /* where we left off */ X} X Xvoid Xinterp(dest,destsize,pattern) Xchar *dest; Xint destsize; Xchar *pattern; X{ X dointerp(dest,destsize,pattern,Nullch); X#ifdef DEBUGGING X if (debug & DEB_FILEXP) X fputs(dest,stdout); X#endif X} X X/* copy a references line, normalizing as we go */ X Xvoid Xrefscpy(dest,destsize,src) Xregister char *dest, *src; Xregister int destsize; X{ X register char *dot, *at, *beg; X char tmpbuf[64]; X X while (*src) { X if (*src != '<') { X if (--destsize <= 0) X break; X *dest++ = '<'; X at = dot = Nullch; X beg = src; X while (*src && *src != ' ' && *src != ',') { X if (*src == '.') X dot = src; X else if (*src == '@') X at = src; X if (--destsize <= 0) X break; X *dest++ = *src++; X } X if (destsize <= 0) X break; X if (dot && !at) { X int len; X X *dest = *dot++ = '\0'; X sprintf(tmpbuf,"%s@%s.UUCP",dot,beg); X len = strlen(tmpbuf); X if (destsize > len) { X strcpy(dest,tmpbuf); X dest = dest + len; X destsize -= len; X } X } X if (--destsize <= 0) X break; X *dest++ = '>'; X } X else { X while (*src && --destsize > 0 && (*dest++ = *src++) != '>') ; X if (destsize <= 0) X break; X } X while (*src == ' ' || *src == ',') src++; X if (*src && --destsize > 0) X *dest++ = ' '; X } X *dest = '\0'; X} X X/* get the person's real name from /etc/passwd */ X/* (string is overwritten, so it must be copied) */ X Xchar * Xgetrealname(uid) Xint uid; X{ X char *s, *c; X char tmpbuf[512]; X X#ifdef PASSNAMES X#ifdef GETPWENT X struct passwd *pwd = getpwuid(uid); X X s = pwd->pw_gecos; X#else X int i; X X getpw(uid, tmpbuf); X for (s=tmpbuf, i=GCOSFIELD-1; i; i--) { X if (s) X s = index(s,':')+1; X } X if (!s) X return nullstr; X cpytill(tmpbuf,s,':'); X s = tmpbuf; X#endif X#ifdef BERKNAMES X#ifdef BERKJUNK X while (*s && !isalnum(*s) && *s != '&') s++; X#endif X if ((c = index(s, ',')) != Nullch) X *c = '\0'; X if ((c = index(s, ';')) != Nullch) X *c = '\0'; X s = cpytill(buf,s,'&'); X if (*s == '&') { /* whoever thought this one up was */ X c = buf + strlen(buf); /* in the middle of the night */ X strcat(c,logname); /* before the morning after */ X strcat(c,s+1); X if (islower(*c)) X *c = toupper(*c); /* gack and double gack */ X } X#else X if ((c = index(s, '(')) != Nullch) X *c = '\0'; X if ((c = index(s, '-')) != Nullch) X s = c; X strcpy(buf,tmpbuf); X#endif X#ifdef GETPWENT X endpwent(); X#endif X return buf; /* return something static */ X#else X if ((tmpfp=fopen(filexp(FULLNAMEFILE),"r")) != Nullfp) { X fgets(buf,sizeof buf,tmpfp); X fclose(tmpfp); X buf[strlen(buf)-1] = '\0'; X return buf; X } X return "PUT YOUR NAME HERE"; X#endif X} X Xstatic void Xabort_interp() X{ X fputs("\n% interp buffer overflow!\n",stdout) FLUSH; X sig_catcher(0); X} X X END_OF_FILE if test 26891 -ne `wc -c <'intrp.c'`; then echo shar: \"'intrp.c'\" unpacked with wrong size! fi # end of 'intrp.c' fi if test -f 'mt-process.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'mt-process.c'\" else echo shar: Extracting \"'mt-process.c'\" \(34306 characters\) sed "s/^X//" >'mt-process.c' <<'END_OF_FILE' X/* $Header: mt-process.c,v 4.3.3.3 91/01/18 19:13:20 davison Trn $ X** X** $Log: mt-process.c,v $ X** Revision 4.3.3.3 91/01/18 19:13:20 davison X** Removed the code that tried to exclude certain message ids. Added -s option X** X** Revision 4.3.3.2 90/08/20 16:40:31 davison X** Added check of caught_interrupt flag into main loops. X** X** Revision 4.3.3.1 90/07/28 18:04:45 davison X** Initial Trn Release X** X*/ X X#include "EXTERN.h" X#include "common.h" X#include "mthreads.h" X#ifdef SERVER X#include "server.h" X#endif X X#include X#ifndef TZSET X# include X#endif X Xchar buff[1024]; X Xchar references[1024]; X Xchar subject_str[80]; Xbool found_Re; X Xchar author_str[20]; X Xextern int log_verbosity, slow_down; X XDOMAIN *next_domain; X Xvoid insert_article(), expire(), trim_roots(), order_roots(), trim_authors(); Xvoid make_root(), use_root(), merge_roots(), set_root(), unlink_root(); Xvoid link_child(), unlink_child(); Xvoid free_article(), free_domain(), free_subject(), free_root(), free_author(); Xvoid get_subject_str(), get_author_str(); XARTICLE *get_article(); XSUBJECT *new_subject(); XAUTHOR *new_author(); X X#ifdef TZSET Xextern time_t tnow; X#else Xextern struct timeb ftnow; X#endif X X#ifndef SERVER Xstatic FILE *fp_article; X#endif X X/* Given the upper/lower bounds of the articles in the current group, add all X** the ones that we don't know about and remove all the ones that have expired. X** The current directory must be the newgroup's spool directory. X*/ Xvoid Xprocess_articles( first_article, last_article ) XART_NUM first_article, last_article; X{ X register char *cp, *str; X register ARTICLE *article; X register ART_NUM i; X time_t date; X int len; X#ifdef SERVER X bool orig_extra = extra_expire; X#endif X extern int errno; X extern int sys_nerr; X extern char *sys_errlist[]; X X if( first_article > (i = total.last+1) ) { X i = first_article; X } X added_count = last_article - i + 1; X expired_count = 0; X X for( ; i <= last_article; i++ ) { X if( caught_interrupt ) { X return; X } X#ifdef SERVER X if( slow_down ) { X sleep( slow_down ); X } X sprintf( buff, "HEAD %ld", (long)i ); X put_server( buff ); X if( get_server( buff, sizeof buff ) < 0 || *buff == CHAR_FATAL ) { X last_article = i - 1; X extra_expire = FALSE; X break; X } X if( *buff != CHAR_OK ) { X added_count--; X continue; X } X#else X /* Open article in current directory. */ X sprintf( buff, "%ld", (long)i ); X /* Set errno for purely paranoid reasons */ X errno = 0; X if( (fp_article = fopen( buff, "r" )) == Nullfp ) { X /* Missing files are ok -- they've just been expired or canceled */ X if( errno != 0 && errno != ENOENT ) { X if( errno < 0 || errno > sys_nerr ) { X log_error( "Can't open `%s': Error %d.\n", buff, errno ); X } else { X log_error( "Can't open `%s': %s.\n", buff, X sys_errlist[errno] ); X } X } X added_count--; X continue; X } X#endif X X article = Nullart; X *references = '\0'; X *author_str = '\0'; X *subject_str = '\0'; X found_Re = 0; X date = 0; X X#ifdef SERVER X while( get_server( cp = buff, sizeof buff ) == 0 ) { X process_line: X if( *cp == '.' ) { X break; X } X#else X while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) { X process_line: X if( *cp == '\n' ) { /* check for end of header */ X break; /* break out when found */ X } X#endif X if( (unsigned char)*cp <= ' ' ) { /* skip continuation lines */ X continue; /* (except references -- see below) */ X } X if( (str = index( cp, ':' )) == Nullch ) { X break; /* end of header if no colon found */ X } X if( (len = str - cp) > 10 ) { X continue; /* skip keywords > 10 chars */ X } X#ifndef SERVER X cp[strlen(cp)-1] = '\0'; /* remove newline */ X#endif X while( cp < str ) { /* lower-case the keyword */ X if( (unsigned char)*cp <= ' ' ) { /* stop at any whitespace */ X break; X } X if( isupper(*cp) ) { X *cp = tolower(*cp); X } X cp++; X } X *cp = '\0'; X cp = buff; X if( len == 4 && strEQ( cp, "date" ) ) { X#ifdef TZSET X date = getdate( str + 1, tnow, timezone ); X#else X date = getdate( str + 1, ftnow.time, (long) ftnow.timezone ); X#endif X } else X if( len == 4 && strEQ( cp, "from" ) ) { X get_author_str( str + 1 ); X } else X if( len == 7 && strEQ( cp, "subject" ) ) { X get_subject_str( str + 1 ); X } else X if( len == 10 && strEQ( cp, "message-id" ) ) { X if( !article ) { X article = get_article( str + 1 ); X } else { X if( log_verbosity ) { X log_error( "Found multiple Message-IDs! [%ld].\n", X (long)i ); X } X } X } else X if( len == 10 && strEQ( cp, "references" ) ) { X /* include preceding space in saved reference */ X len = strlen( str + 1 ); X bcopy( str + 1, references, len + 1 ); X str = references + len; X /* check for continuation lines */ X#ifdef SERVER X while( get_server( cp = buff, sizeof buff ) == 0 ) { X#else X while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) { X#endif X if( *cp != ' ' && *cp != '\t' ) { X goto process_line; X } X while( *++cp == ' ' || *cp == '\t' ) { X ; X } X *--cp = ' '; X /* If the references are too long, shift them over to X ** always save the most recent ones. X */ X if( (len += strlen( cp )) > 1023 ) { X strcpy( buff, buff + len - 1023 ); X str -= len - 1023; X len = 1023; X } X strcpy( str, cp ); X }/* while */ X break; X }/* if */ X }/* while */ X if( article ) { X insert_article( article, date, i ); X } else { X if( log_verbosity ) { X log_error( "Message-ID line missing! [%ld].\n", (long)i ); X } X } X#ifndef SERVER X fclose( fp_article ); X#endif X } X X if( extra_expire || first_article > total.first ) { X expire( first_article ); X } X if( caught_interrupt ) { X return; X } X trim_roots(); X order_roots(); X trim_authors(); X X total.first = first_article; X total.last = last_article; X#ifdef SERVER X extra_expire = orig_extra; X#endif X} X X/* Search all articles for numbers less than new_first. Traverse the list X** using the domain links so we don't have to deal with the tree structure. X** If extra_expire is true, stat() all valid articles to make sure they are X** really there and expire them if they're not. X*/ Xvoid Xexpire( new_first ) XART_NUM new_first; X{ X register DOMAIN *domain; X register ARTICLE *article, *next_art, *hold; X X for( domain = &unk_domain; domain; domain = next_domain ) { X next_domain = domain->link; X for( article = domain->ids; article; article = next_art ) { X if( caught_interrupt ) { X return; X } X next_art = article->id_link; X if( !article->subject || (article->flags & NEW_ARTICLE) ) { X continue; X } X if( extra_expire && article->num >= new_first ) { X#ifdef SERVER X sprintf( buff, "STAT %ld", (long)article->num ); X put_server( buff ); X if( get_server( buff, sizeof buff ) == 0 && *buff == CHAR_OK ) { X continue; X } X#else X sprintf( buff, "%ld", (long)article->num ); X if( !stat( buff, &filestat ) || errno != ENOENT ) { X continue; X } X#endif X } X if( extra_expire || article->num < new_first ) { X article->subject->count--; X article->subject = 0; X article->author->count--; X article->author = 0; X /* Free expired article if it has no children. Then check X ** if the parent(s) are also fake and can be freed. We'll X ** free any empty roots later. X */ X while( !article->children ) { X hold = article->parent; X unlink_child( article ); X free_article( article ); X if( hold && !hold->subject ) { X if( (article = hold) == next_art ) { X next_art = next_art->id_link; X } X } else { X break; X } X } X expired_count++; X }/* if */ X }/* for */ X }/* for */ X next_domain = Null(DOMAIN*); X} X X/* Trim the article chains down so that we don't have more than one faked X** article between the root any real ones. X*/ Xvoid Xtrim_roots() X{ X register ROOT *root, *last_root; X register ARTICLE *article, *next; X register SUBJECT *subject, *last_subj; X register int found; X X#ifndef lint X last_root = (ROOT *)&root_root; X#else X last_root = Null(ROOT*); X#endif X for( root = root_root; root; root = last_root->link ) { X for( article = root->articles; article; article = article->siblings ) { X /* If an article has no subject, it is a "fake" reference node. X ** If all of its immediate children are also fakes, delete it X ** and graduate the children to the root. If everyone is fake, X ** the chain dies. X */ X while( !article->subject ) { X found = 0; X for( next = article->children; next; next = next->siblings ) { X if( next->subject ) { X found = 1; X break; X } X } X if( !found ) { X /* Remove this faked article and move all its children X ** up to the root. X */ X next = article->children; X unlink_child( article ); X free_article( article ); X for( article = next; article; article = next ) { X next = article->siblings; X article->parent = Nullart; X link_child( article ); X } X article = root->articles; /* start this root over */ X } else { X break; /* else, on to next article */ X } X } X } X /* Free all unused subject strings. Begin by trying to find a X ** subject for the root's pointer. X */ X for( subject = root->subjects; subject && !subject->count; subject = root->subjects ) { X root->subjects = subject->link; X free_subject( subject ); X root->subject_cnt--; X } X /* Then free up any unsed intermediate subjects. X */ X if( (last_subj = subject) != Null(SUBJECT*) ) { X while( (subject = subject->link) != Null(SUBJECT*) ) { X if( !subject->count ) { X last_subj->link = subject->link; X free_subject( subject ); X root->subject_cnt--; X subject = last_subj; X } else { X last_subj = subject; X } X } X } X /* Now, free all roots without articles. Flag unexpeced errors. X */ X if( !root->articles ) { X if( root->subjects ) { X log_error( "** Empty root still had subjects remaining! **\n" ); X } X last_root->link = root->link; X free_root( root ); X } else { X last_root = root; X } X } X} X X/* Descend the author list, find any author names that aren't used X** anymore and free them. X*/ Xvoid Xtrim_authors() X{ X register AUTHOR *author, *last_author; X X#ifndef lint X last_author = (AUTHOR *)&author_root; X#else X last_author = Null(AUTHOR*); X#endif X for( author = author_root; author; author = last_author->link ) { X if( !author->count ) { X last_author->link = author->link; X free_author( author ); X } else { X last_author = author; X } X } X} X X/* Reorder the roots to place the oldest ones first (age determined by X** date of oldest article). X*/ Xvoid Xorder_roots() X{ X register ROOT *root, *next, *search; X X /* If we don't have at least two roots, we're done! */ X if( !(root = root_root) || !(next = root->link) ) { X return; /* RETURN */ X } X /* Break the old list off after the first root, and then start X ** inserting the roots into the list by date. X */ X root->link = Null(ROOT*); X while( (root = next) != Null(ROOT*) ) { X next = next->link; X if( (search = root_root)->articles->date >= root->articles->date ) { X root->link = root_root; X root_root = root; X } else { X while( search->link X && search->link->articles->date < root->articles->date ) { X search = search->link; X } X root->link = search->link; X search->link = root; X } X } X} X X#define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y)) X X/* Parse the subject into 72 characters or less. Remove any "Re[:^]"s from X** the front (noting that it's there), and any "(was: old)" stuff from X** the end. Then, compact multiple whitespace characters into one space, X** trimming leading/trailing whitespace. If it's still too long, unmercifully X** cut it off. We don't bother with subject continuation lines either. X*/ Xvoid Xget_subject_str( str ) Xregister char *str; X{ X register char *cp; X register int len; X X while( *str && (unsigned char)*str <= ' ' ) { X str++; X } X if( !*str ) { X bcopy( "", subject_str, 7 ); X return; /* RETURN */ X } X cp = str; X while( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' ) ) { /* check for Re: */ X cp += 2; X if( *cp == '^' ) { /* allow Re^2: */ X while( *++cp <= '9' && *cp >= '0' ) { X ; X } X } X if( *cp != ':' ) { X break; X } X while( *++cp == ' ' ) { X ; X } X found_Re = 1; X str = cp; X } X /* Remove "(was Re: oldsubject)", because we already know the old subjects. X ** Also match "(Re: oldsubject)". Allow possible spaces after the ('s. X */ X for( cp = str; (cp = index( cp+1, '(' )) != Nullch; ) { X while( *++cp == ' ' ) { X ; X } X if( EQ( cp[0], 'w' ) && EQ( cp[1], 'a' ) && EQ( cp[2], 's' ) X && (cp[3] == ':' || cp[3] == ' ') ) X { X *--cp = '\0'; X break; X } X if( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' ) X && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':')) ) { X *--cp = '\0'; X break; X } X } X /* Copy subject to a temporary string, compacting multiple spaces/tabs */ X for( len = 0, cp = subject_str; len < 72 && *str; len++ ) { X if( (unsigned char)*str <= ' ' ) { X while( *++str && (unsigned char)*str <= ' ' ) { X ; X } X *cp++ = ' '; X } else { X *cp++ = *str++; X } X } X if( cp[-1] == ' ' ) { X cp--; X } X *cp = '\0'; X} X X/* Try to fit the author name in 16 bytes. Use the comment portion in X** parenthesis if present. Cut off non-commented names at the '@' or '%'. X** Then, put as many characters as we can into the 16 bytes, packing multiple X** whitespace characters into a single space. X** We should really implement a nice name shortening algorithm, or simply X** grab the name packing code from nn. X*/ Xvoid Xget_author_str( str ) Xchar *str; X{ X register char *cp, *cp2; X X if( (cp = index( str, '(' )) != Nullch ) { X str = cp+1; X if( (cp = rindex( str, ')' )) != Nullch ) { X *cp = '\0'; X } X } else { X if( (cp = index( str, '@' )) != Nullch ) { X *cp = '\0'; X } X if( (cp = index( str, '%' )) != Nullch ) { X *cp = '\0'; X } X } X for( cp = str, cp2 = author_str; *cp && cp2-author_str < 16; ) { X /* Pack white space and turn ctrl-chars into spaces. */ X if( *cp <= ' ' ) { X while( *++cp && *cp <= ' ' ) { X ; X } X if( cp2 != author_str ) { X *cp2++ = ' '; X } X } else { X *cp2++ = *cp++; X } X } X *cp2 = '\0'; X} X X/* Take a message-id and see if we already know about it. If so, return it. X** If not, create it. We separate the id into its id@domain parts, and X** link all the unique ids to one copy of the domain portion. This saves X** a bit of space. X*/ XARTICLE * Xget_article( msg_id ) Xchar *msg_id; X{ X register DOMAIN *domain; X register ARTICLE *article; X register char *cp, *after_at; X X /* Take message id, break it up into , and try to match it. X */ X while( *msg_id == ' ' ) { X msg_id++; X } X cp = msg_id + strlen( msg_id ) - 1; X if( msg_id >= cp ) { X if( log_verbosity ) { X log_error( "Message-ID is empty!\n" ); X } X return Nullart; X } X if( *msg_id++ != '<' ) { X if( log_verbosity ) { X log_error( "Message-ID doesn't start with '<'.\n" ); X } X msg_id--; X } X if( *cp != '>' ) { X if( log_verbosity ) { X log_error( "Message-ID doesn't end with '>'.\n" ); X } X cp++; X } X *cp = '\0'; X if( msg_id == cp ) { X if( log_verbosity ) { X log_error( "Message-ID is null!\n" ); X } X return Nullart; X } X X if( (after_at = index( msg_id, '@' )) == Nullch ) { X domain = &unk_domain; X } else { X *after_at++ = '\0'; X for( cp = after_at; *cp; cp++ ) { X if( isupper(*cp) ) { X *cp = tolower(*cp); /* lower-case domain portion */ X } X } X *cp = '\0'; X /* Try to find domain name in database. */ X for( domain = unk_domain.link; domain; domain = domain->link ) { X if( strEQ( domain->name, after_at ) ) { X break; X } X } X if( !domain ) { /* if domain doesn't exist, create it */ X register int len = cp - after_at + 1; X domain = (DOMAIN *)safemalloc( sizeof (DOMAIN) ); X total.domain++; X domain->name = safemalloc( len ); X total.string2 += len; X bcopy( after_at, domain->name, len ); X domain->ids = Nullart; X domain->link = unk_domain.link; X unk_domain.link = domain; X } X } X /* Try to find id in this domain. */ X for( article = domain->ids; article; article = article->id_link ) { X if( strEQ( article->id, msg_id ) ) { X break; X } X } X if( !article ) { /* If it doesn't exist, create an article */ X register int len = strlen( msg_id ) + 1; X article = (ARTICLE *)safemalloc( sizeof (ARTICLE) ); X bzero( article, sizeof (ARTICLE) ); X total.article++; X article->num = 0; X article->id = safemalloc( len ); X total.string2 += len; X bcopy( msg_id, article->id, len ); X article->domain = domain; X article->id_link = domain->ids; X domain->ids = article; X } X return article; X} X X/* Take all the data we've accumulated about the article and shove it into X** the article tree at the best place we can possibly imagine. X*/ Xvoid Xinsert_article( article, date, num ) XARTICLE *article; Xtime_t date; XART_NUM num; X{ X register ARTICLE *node, *last; X register char *cp, *end; X int len; X X if( article->subject ) { X if( log_verbosity ) { X log_error( "We've already seen article #%ld (%s@%s)\n", X (long)num, article->id, article->domain->name ); X } X return; /* RETURN */ X } X article->date = date; X article->num = num; X article->flags = NEW_ARTICLE; X X if( !*references && found_Re ) { X if( log_verbosity > 1 ) { X log_error( "Missing reference line! [%ld]\n", (long)num ); X } X } X /* If the article has a non-zero root, it is already in a thread somewhere. X ** Unlink it to try to put it in the best possible spot. X */ X if( article->root ) { X /* Check for a real or shared-fake parent. Articles that have never X ** existed have a num of 0. Expired articles that remain as references X ** have a valid num. (Valid date too, but no subject.) X */ X for( node = article->parent; X node && !node->num && node->child_cnt == 1; X node = node->parent ) X { X ; X } X unlink_child( article ); X if( node ) { /* do we have decent parents? */ X /* Yes: assume that our references are ok, and just reorder us X ** with our siblings by date. X */ X link_child( article ); X use_root( article, article->root ); X /* Freshen the date in any faked parent articles. */ X for( node = article->parent; X node && !node->num && date < node->date; X node = node->parent ) X { X node->date = date; X unlink_child( node ); X link_child( node ); X } X return; /* RETURN */ X } X /* We'll assume that this article has as good or better references X ** than the child that faked us initially. Free the fake reference- X ** chain and process our references as usual. X */ X for( node = article->parent; node; node = node->parent ) { X unlink_child( node ); X free_article( node ); X } X article->parent = Nullart; /* neaten up */ X article->siblings = Nullart; X } X check_references: X if( !*references ) { /* If no references but "Re:" in subject, */ X if( found_Re ) { /* search for a reference in any cited text */ X#ifndef SERVER X for( len = 4; len && fgets( buff, sizeof buff, fp_article ); len-- ) { X if( (cp = index( buff, '<' )) && (end = index( cp, ' ' )) ) { X if( end[-1] == ',' ) { X end--; X } X *end = '\0'; X if( (end = index( cp, '>' )) == Nullch ) { X end = cp + strlen( cp ) - 1; X } X if( valid_message_id( cp, end ) ) { X strcpy( references+1, cp ); X *references = ' '; X if( log_verbosity > 2 ) { X log_error( "Found cited-text reference: '%s' [%ld]\n", X references+1, (long)num ); X } X break; X } X } X } X#endif X } else { X article->flags |= ROOT_ARTICLE; X } X } X /* If we have references, process them from the right end one at a time X ** until we either run into somebody, or we run out of references. X */ X if( *references ) { X last = article; X node = Nullart; X end = references + strlen( references ) - 1; X while( (cp = rindex( references, ' ' )) != Nullch ) { X *cp++ = '\0'; X while( end >= cp && ((unsigned char)*end <= ' ' || *end == ',') ) { X end--; X } X end[1] = '\0'; X /* Quit parsing references if this one is garbage. */ X if( !valid_message_id( cp, end ) ) { X if( log_verbosity ) { X log_error( "Bad ref '%s' [%ld]\n", cp, (long)num ); X } X break; X } X /* Dump all domains that end in '.', such as "..." & "1@DEL." */ X if( end[-1] == '.' ) { X break; X } X node = get_article( cp ); X /* Check for duplicates on the reference line. Brand-new data has X ** no date. Data we just allocated earlier on this line has a X ** date but no root. Special-case the article itself, since it X ** MIGHT have a root. X */ X if( (node->date && !node->root) || node == article ) { X if( log_verbosity ) { X log_error( "Reference line contains duplicates [%ld]\n", X (long)num ); X } X if( (node = last) == article ) { X node = Nullart; X } X continue; X } X last->parent = node; X link_child( last ); X if( node->root ) { X break; X } X node->date = date; X last = node; X end = cp-2; X } X if( !node ) { X *references = '\0'; X goto check_references; X } X /* Check if we ran into anybody that was already linked. If so, we X ** just use their root. X */ X if( node->root ) { X /* See if this article spans the gap between what we thought X ** were two different roots. X */ X if( article->root && article->root != node->root ) { X merge_roots( node->root, article->root ); X /* Set the roots of any children we brought with us. */ X set_root( article, node->root ); X } X use_root( article, node->root ); X } else { X /* We didn't find anybody we knew, so either create a new root or X ** use the article's root if it was previously faked. X */ X if( !article->root ) { X make_root( node ); X use_root( article, node->root ); X } else { X use_root( article, article->root ); X node->root = article->root; X link_child( node ); X } X } X /* Set the roots of the faked articles we created as references. */ X for( node = article->parent; node && !node->root; node = node->parent ) { X node->root = article->root; X } X /* Make sure we didn't circularly link to a child article(!), by X ** ensuring that we run into the root before we run into ourself. X */ X while( node && node->parent != article ) { X node = node->parent; X } X if( node ) { X /* Ugh. Someone's tweaked reference line with an incorrect X ** article order arrived first, and one of our children is X ** really one of our ancestors. Cut off the bogus child branch X ** right where we are and link it to the root. X */ X if( log_verbosity ) { X log_error("Found ancestral child -- fixing.\n"); X } X unlink_child( node ); X node->parent = Nullart; X link_child( node ); X } X } else { X /* The article has no references. Either turn it into a new root, or X ** re-attach fleshed-out (previously faked) article to its old root. X */ X if( !article->root ) { X make_root( article ); X } else { X use_root( article, article->root ); X link_child( article ); X } X } X} X X/* Check if the string we've found looks like a valid message-id reference. X*/ Xint Xvalid_message_id( start, end ) Xregister char *start, *end; X{ X char *mid; X X if( *end != '>' ) { X /* Compensate for spacecadets who include the header in their X ** subsitution of all '>'s into another citation character. X */ X if( *end == '<' || *end == '-' || *end == '!' || *end == '%' X || *end == ')' || *end == '|' || *end == ':' || *end == '}' X || *end == '*' || *end == '+' || *end == '#' || *end == ']' X || *end == '@' ) { X if( log_verbosity ) { X log_error( "Reference ended in '%c'.\n", *end ); X } X *end = '>'; X } X } X /* Id must be "<...@...>" */ X if( *start != '<' || *end != '>' || (mid = index( start, '@' )) == Nullch X || mid == start+1 || mid+1 == end ) { X return 0; /* RETURN */ X } X return 1; X} X X/* Remove an article from its parent/siblings. Leave parent pointer intact. X*/ Xvoid Xunlink_child( child ) Xregister ARTICLE *child; X{ X register ARTICLE *last; X X if( !(last = child->parent) ) { X child->root->thread_cnt--; X if( (last = child->root->articles) == child ) { X child->root->articles = child->siblings; X } else { X goto sibling_search; X } X } else { X last->child_cnt--; X if( last->children == child ) { X last->children = child->siblings; X } else { X last = last->children; X sibling_search: X while( last->siblings != child ) { X last = last->siblings; X } X last->siblings = child->siblings; X } X } X} X X/* Link an article to its parent article. If its parent pointer is zero, X** link it to its root. Sorts siblings by date. X*/ Xvoid Xlink_child( child ) Xregister ARTICLE *child; X{ X register ARTICLE *node; X register ROOT *root; X X if( !(node = child->parent) ) { X root = child->root; X root->thread_cnt++; X node = root->articles; X if( !node || child->date < node->date ) { X child->siblings = node; X root->articles = child; X } else { X goto sibling_search; X } X } else { X node->child_cnt++; X node = node->children; X if( !node || child->date < node->date ) { X child->siblings = node; X child->parent->children = child; X } else { X sibling_search: X for( ; node->siblings; node = node->siblings ) { X if( node->siblings->date > child->date ) { X break; X } X } X child->siblings = node->siblings; X node->siblings = child; X } X } X} X X/* Create a new root for the specified article. If the current subject_str X** matches any pre-existing root's subjects, we'll instead add it on as a X** parallel thread. X*/ Xvoid Xmake_root( article ) XARTICLE *article; X{ X register ROOT *new, *node; X register SUBJECT *subject; X X#ifndef NO_SUBJECT_MATCHING X /* First, check the other root's subjects for a match. */ X for( node = root_root; node; node = node->link ) { X for( subject = node->subjects; subject; subject = subject->link ) { X if( subject_equal( subject->str, subject_str ) ) { X use_root( article, node ); /* use it instead */ X link_child( article ); X return; /* RETURN */ X } X } X } X#endif X X /* Create a new root. */ X new = (ROOT *)safemalloc( sizeof (ROOT) ); X total.root++; X new->articles = article; X new->root_num = article->num; X new->thread_cnt = 1; X if( article->num ) { X article->author = new_author(); X new->subject_cnt = 1; X new->subjects = article->subject = new_subject(); X } else { X new->subject_cnt = 0; X new->subjects = Null(SUBJECT*); X } X article->root = new; X new->link = root_root; X root_root = new; X} X X/* Add this article's subject onto the indicated root's list. Point the X** article at the root. X*/ Xvoid Xuse_root( article, root ) XARTICLE *article; XROOT *root; X{ X register SUBJECT *subject; X register ROOT *root2; X SUBJECT *hold, *child_subj = Null(SUBJECT*); X ARTICLE *node; X X article->root = root; X X /* If it's a fake, there's no subject to add. */ X if( !article->num ) { X return; /* RETURN */ X } X X /* If we haven't picked a unique message number to represent this root, X ** use the first non-zero number we encounter. Which one doesn't matter. X */ X if( !root->root_num ) { X root->root_num = article->num; X } X article->author = new_author(); X X /* Check if the new subject matches any of the other subjects in this root. X ** If so, we just update the count. If not, check all the other roots for X ** a match. If found, the new subject is common between the two roots, so X ** we merge the two roots together. X */ X root2 = root; X#ifndef NO_SUBJECT_MATCHING X do { X#endif X for( subject = root2->subjects; subject; subject = subject->link ) { X if( subject_equal( subject->str, subject_str ) ) { X article->subject = subject; X subject->count++; X#ifndef NO_SUBJECT_MATCHING X if( root2 != root ) { X merge_roots( root, root2 ); X } X#endif X return; /* RETURN */ X } X } X#ifndef NO_SUBJECT_MATCHING X if( (root2 = root2->link) == Null(ROOT*) ) { X root2 = root_root; X } X } while( root2 != root ); X#endif X X article->subject = hold = new_subject(); X root->subject_cnt++; X X /* Find subject of any pre-existing children. We want to insert the new X ** subject before a child's to keep the subject numbering intuitive X ** in the newsreader. X */ X for( node = article->children; node; node = node->children ) { X if( node->subject ) { X child_subj = node->subject; X break; X } X } X if( !(subject = root->subjects) || subject == child_subj ) { X hold->link = root->subjects; X root->subjects = hold; X } else { X while( subject->link && subject->link != child_subj ) { X subject = subject->link; X } X hold->link = subject->link; X subject->link = hold; X } X} X X/* Check subjects in a case-insignificant, punctuation ignoring manner. X*/ Xint Xsubject_equal( str1, str2 ) Xregister char *str1, *str2; X{ X register char ch1, ch2; X X while( (ch1 = *str1++) ) { X if( ch1 == ' ' || ispunct( ch1 ) ) { X while( *str1 && (*str1 == ' ' || ispunct( *str1 )) ) { X str1++; X } X ch1 = ' '; X } else if( isupper( ch1 ) ) { X ch1 = tolower( ch1 ); X } X if( !(ch2 = *str2++) ) { X return 0; X } X if( ch2 == ' ' || ispunct( ch2 ) ) { X while( *str2 && (*str2 == ' ' || ispunct( *str2 )) ) { X str2++; X } X ch2 = ' '; X } else if( isupper( ch2 ) ) { X ch2 = tolower( ch2 ); X } X if( ch1 != ch2 ) { X return 0; X } X } X if( *str2 ) { X return 0; X } X return 1; X} X X/* Create a new subject structure. */ XSUBJECT * Xnew_subject() X{ X register int len = strlen( subject_str ) + 1; X register SUBJECT *subject; X X subject = (SUBJECT *)safemalloc( sizeof (SUBJECT) ); X total.subject++; X subject->count = 1; X subject->link = Null(SUBJECT*); X subject->str = safemalloc( len ); X total.string1 += len; X bcopy( subject_str, subject->str, len ); X X return subject; X} X X/* Create a new author structure. */ XAUTHOR * Xnew_author() X{ X register len = strlen( author_str ) + 1; X register AUTHOR *author, *last_author; X X last_author = Null(AUTHOR*); X for( author = author_root; author; author = author->link ) { X#ifndef DONT_COMPARE_AUTHORS /* might like to define this to save time */ X if( strEQ( author->name, author_str ) ) { X author->count++; X return author; /* RETURN */ X } X#endif X last_author = author; X } X X author = (AUTHOR *)safemalloc( sizeof (AUTHOR) ); X total.author++; X author->count = 1; X author->link = Null(AUTHOR*); X author->name = safemalloc( len ); X total.string1 += len; X bcopy( author_str, author->name, len ); X X if( last_author ) { X last_author->link = author; X } else { X author_root = author; X } X return author; X} X X/* Insert all of root2 into root1, setting the proper root values and X** updating subject counts. X*/ Xvoid Xmerge_roots( root1, root2 ) XROOT *root1, *root2; X{ X register ARTICLE *node, *next; X register SUBJECT *subject; X X /* Remember whoever's root num is lower. This could screw up a X ** newsreader's kill-thread code if someone already saw the roots as X ** being separate, but it must be done. The newsreader code will have X ** to handle this as best as it can. X */ X if( root1->root_num > root2->root_num ) { X root1->root_num = root2->root_num; X } X X for( node = root2->articles; node; node = next ) { X /* For each article attached to root2, detach them, set the X ** branch's root pointers to root1, and then attach it to root1. X */ X next = node->siblings; X unlink_child( node ); X node->siblings = Nullart; X set_root( node, root1 ); /* sets children too */ X /* Link_child() depends on node->parent being null and node->root X ** being set. X */ X link_child( node ); X } X root1->subject_cnt += root2->subject_cnt; X if( !(subject = root1->subjects) ) { X root1->subjects = root2->subjects; X } else { X while( subject->link ) { X subject = subject->link; X } X subject->link = root2->subjects; X } X unlink_root( root2 ); X free_root( root2 ); X} X X/* When merging roots, we need to reset all the root pointers. X*/ Xvoid Xset_root( node, root ) XARTICLE *node; XROOT *root; X{ X do { X node->root = root; X if( node->children ) { X set_root( node->children, root ); X } X } while( node = node->siblings ); X} X X/* Unlink a root from its neighbors. */ Xvoid Xunlink_root( root ) Xregister ROOT *root; X{ X register ROOT *node; X X if( (node = root_root) == root ) { X root_root = root->link; X } else { X while( node->link != root ) { X node = node->link; X } X node->link = root->link; X } X} X X/* Free an article and its message-id string. All other resources must X** already be free, and it must not be attached to any threads. X*/ Xvoid Xfree_article( this ) XARTICLE *this; X{ X register ARTICLE *art; X X if( (art = this->domain->ids) == this ) { X if( !(this->domain->ids = this->id_link) ) { X free_domain( this->domain ); X } X } else { X while( this != art->id_link ) { X art = art->id_link; X } X art->id_link = this->id_link; X } X total.string2 -= strlen( this->id ) + 1; X free( this->id ); X free( this ); X total.article--; X} X X/* Free the domain only when its last unique id has been freed. */ Xvoid Xfree_domain( this ) XDOMAIN *this; X{ X register DOMAIN *domain; X X if( this == (domain = &unk_domain) ) { X return; X } X if( this == next_domain ) { /* help expire routine skip freed domains */ X next_domain = next_domain->link; X } X while( this != domain->link ) { X domain = domain->link; X } X domain->link = this->link; X total.string2 -= strlen( this->name ) + 1; X free( this->name ); X free( this ); X total.domain--; X} X X/* Free the subject structure and its string. */ Xvoid Xfree_subject( this ) XSUBJECT *this; X{ X total.string1 -= strlen( this->str ) + 1; X free( this->str ); X free( this ); X total.subject--; X} X X/* Free a root. It must already be unlinked. */ Xvoid Xfree_root( this ) XROOT *this; X{ X free( this ); X total.root--; X} X X/* Free the author structure when it's not needed any more. */ Xvoid Xfree_author( this ) XAUTHOR *this; X{ X total.string1 -= strlen( this->name ) + 1; X free( this->name ); X free( this ); X total.author--; X} END_OF_FILE if test 34306 -ne `wc -c <'mt-process.c'`; then echo shar: \"'mt-process.c'\" unpacked with wrong size! fi # end of 'mt-process.c' fi echo shar: End of archive 5 \(of 14\). cp /dev/null ark5isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 14 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still must unpack the following archives: echo " " ${MISSING} fi exit 0