/************************************************************************ * multigram - The human mail address reader * * * * It uses multigrams to intelligently filter out mail addresses * * from the garbage in any arbitrary mail. * * Multigram is currently unable to pick out addresses that * * contain embedded whitespace. * * This program also contains some swiss-army-knife mailinglist * * support features. * * * * Most notably: flist A program that should be setuid root * * or setuid to the list user and setgid to the list user's * * primary group. * * * * Seems to be relatively bug free. * * * * Copyright (c) 1992-1999, S.R. van den Berg, The Netherlands * * Copyright (c) 1998-2001, Philip Guenther, The United States * * of America * * #include "../README" * ************************************************************************/ #ifdef RCS static /*const*/char rcsid[]= "$Id: multigram.c,v 1.98 2001/06/07 21:03:49 guenther Exp $"; #endif static /*const*/char rcsdate[]="$Date: 2001/06/07 21:03:49 $"; #include "includes.h" #include "sublib.h" #include "hsort.h" #include "shell.h" #include "ecommon.h" #include "mcommon.h" #include "lastdirsep.h" #include "../patchlevel.h" #include "targetdir.h" /* see ../SmartList/install.sh2 for more details */ #define BUFSTEP 16 #define ADDR_INCR 128 #define COPYBUF 16384 /*#define SPEEDBUF COPYBUF /* uncomment to get a speed increase? */ #define SCALE_WEIGHT 0x7fff #define EXCL_THRESHOLD 30730 #define DEFmaxgram 4 #define DEFminweight (SCALE_WEIGHT/4) /* sanity cutoff value */ #define DEFbest_matches 2 #define TMPMAILFILE "/tmp/choplist.%ld" #define DEFAULTS_DIR ".etc" /* some configurable paths */ #define GLOCKFILE "../.etc/rc.lock" #define RCMAIN "./../.etc/rc.main" #define LLOCKFILE "rc.lock" #define LINKMOVED "../.etc/Moved" #define REQUEST "-request" #define RCSUBMIT "./rc.submit" #define RCREQUEST "./rc.request" #define RCPOST "./../.etc/rc.post" #define RCINIT "RC_INIT=rc.init" #define XENVELOPETO "X_ENVELOPE_TO=" #define LIST "list=" #define metoo_SENDMAIL "-om" #define nometoo_SENDMAIL "-omF" #define REMOV1_DELIM "(Only" #define REMOV2_DELIM "addresses below this line can be automatically removed)" #define NOT_METOO "(-n)" struct string{char*text,*itext;size_t textlen,buflen;}; const char dirsep[]=DIRSEP; static remov_delim,maxgram; #ifdef NOstrncasecmp int strncasecmp(a,b,l)const char*a,*b;size_t l; /* stub */ { return strncmp(a,b,l); } #endif static long tcoffset; static tcguessed; static void ctellinit P((void)) { tcoffset=tcguessed=0; } static long ctell(fp)FILE*fp; /* caching ftell() */ { if(tcguessed==3) /* three good guesses for confidence */ return tcoffset; /* eliminated another lseek() */ ;{ long offset; if((offset=ftell(fp))==tcoffset) tcguessed++; /* got this one right */ return offset; } } /* read a string from a file into a struct string buffer */ static size_t readstr(file,p,linewise)FILE*const file;struct string*p; const int linewise; { size_t tccount,len;int i,firstspc; static const char rem1str[]=REMOV1_DELIM,rem2str[]=REMOV2_DELIM; for(tccount=len=firstspc=0;;) { tccount++; switch(i=getc(file)) { case ' ':case '\t':case '\n': if(!len) /* only skip leading space */ continue; if(!linewise) /* do we need a complete line? */ break; /* no, a word will do */ if(!firstspc) /* already seen spaces? */ { if(i=='\n') /* no, so check for EOL */ { p->text[len]='\0'; /* terminate the first word, split */ if(++len==p->buflen) /* still buffer space left? */ p->text=realloc(p->text,p->buflen+=BUFSTEP); break; } i='\0';firstspc=1; } /* not the first word on the line, continue */ if(i=='\n') break; default:p->text[len]=i; /* regular character, store it */ if(++len==p->buflen) /* watch our buffer space */ p->text=realloc(p->text,p->buflen+=BUFSTEP); continue; /* next character */ case EOF:; } tcoffset+=tccount; for(;p->text[len]='\0',len;--len) /* terminate buffer in any case */ { switch(p->text[len-1]) /* trailing whitespace? */ { case ' ':case '\t': /* wipe it out */ continue; } break; } if(linewise&&len) for(i=0;!remov_delim&&!i;i=1) if(!strcmp(p->text+i,rem1str)&& !strcmp(p->text+sizeof rem1str+i,rem2str)) /* special delimiter? */ remov_delim=1; return len; } } static char*tstrdup(p)const char*const p; { return strcpy(malloc(strlen(p)+1),p); } static const char*mailfile; static int retval=EX_UNAVAILABLE; static void sterminate(sig)int sig; { unlink(mailfile); if(sig) _exit(retval); /* can't call exit from signal handler */ else exit(retval); } static int strIcmp(s1,s2)const char*const s1,*const s2; { register unsigned i,j;register const char*a,*b; a=s1;b=s2; do { while(*a&&*a==*b) a++,b++; if((i= *a++)-'A'<='Z'-'A') i+='a'-'A'; if((j= *b++)-'A'<='Z'-'A') j+='a'-'A'; if(j!=i) return i>j?a-s1:s1-a; } while(i&&j); return 0; } static int pstrIcmp(s1,s2)const void*const s1,*const s2; { return strIcmp(*(const char*const*)s1,*(const char*const*)s2); } static void revstr(p)register char*p; /* reverse the string */ { register char*q; for(q=strchr(p,'\0')-1;ptext)) { size_t l,l1; q=malloc((l=strlen(str->text))+1); *((char*)tmemmove(q,p,l1=str->text+(int)l-p)+l)='\0'; tmemmove(q+l1,str->text,l-l1); /* swap the sides around the '@' */ } /* this improves the boundary behaviour */ else q=tstrdup(str->text); castlower(str->itext=q); } static void elog(a)const char*const a; { fputs(a,stderr); } /* the program names */ static const char idhash[]="idhash",flist[]="flist",senddigest[]="senddigest", choplist[]="choplist"; static const char*progname="multigram"; #define ISPROGRAM(curname,refname) \ (!strncmp(curname,refname,STRLEN(refname))) void nlog(a)const char*const a; /* log error with identification */ { fprintf(stderr,"%s: %s",progname,a); } void logqnl(a)const char*const a; { fprintf(stderr," \"%s\"\n",a); } /* check rc.lock file age */ static int rclock(file,stbuf)const char*const file;struct stat*const stbuf; { int waited=0; while(!stat(file,stbuf)&&time((time_t*)0)-stbuf->st_mtime0) /* more opening than closing parens? */ shftleft: tmemmove(chp,chp+1,echp-chp+2); /* delete left paren */ } else if(parens<0&&*echp==pright) /* more closing than opening parens? */ *echp='\0'; /* delete right paren */ } static PROGID; static int matchgram(fuzzstr,hardstr) const struct string*const fuzzstr;struct string*const hardstr; { size_t minlen,maxlen;unsigned maxweight;int meter; register size_t gramsize; if((minlen=hardstr->textlen=strlen(hardstr->text))>(maxlen=fuzzstr->textlen)) minlen=fuzzstr->textlen,maxlen=hardstr->textlen; if((gramsize=minlen-1)>maxgram) gramsize=maxgram; maxweight=SCALE_WEIGHT/(gramsize?gramsize:1); /* distribute weight */ meter=(int)((unsigned long)SCALE_WEIGHT/2*minlen/maxlen)-SCALE_WEIGHT/2; do /* reset local multigram counter */ { register lmeter=0;size_t cmaxlen=maxlen; ;{ register const char*fzz,*hrd; fzz=fuzzstr->itext; do { for(hrd=fzz+1;hrd=strchr(hrd,*fzz);) /* is it present in */ if(!strncmp(++hrd,fzz+1,gramsize)) /* own string? */ { if(cmaxlen>gramsize+1) cmaxlen--; goto dble_gram; /* skip until it's last */ } for(hrd=hardstr->itext;hrd=strchr(hrd,*fzz);) /* otherwise */ if(!strncmp(++hrd,fzz+1,gramsize)) /* search it in the */ { lmeter++; /* dist entry */ break; } dble_gram:; } while(*(++fzz+gramsize)); /* next gram */ } if(lmeter) { unsigned weight; if(cmaxlen>minlen) cmaxlen=minlen; meter+=lmeter*(weight=maxweight/(unsigned)(cmaxlen-gramsize)); meter-=weight* (unsigned long)((lmeter+=gramsize-cmaxlen)<0?-lmeter:lmeter)/cmaxlen; } } while(1pw_gid)||initgroups(listid,pass->pw_gid)|| setuid(pass->pw_uid)) /* this can fail on certain */ return EX_OSERR; /* broken systems e.g. Linux 2.2.14 */ } else if(!(pass=getpwuid(euid))) { nlog("Euid");fprintf(stderr," %d",(int)euid); bailout: elog(" unknown\n"); return EX_NOUSER; } else /* * we weren't root, so try to get the uid and gid belonging to * the euid we started under */ { int error; setrgid(pass->pw_gid);error=setgid(pass->pw_gid);setruid(euid); if(setuid(euid)|| error|| getuid()!=euid|| getgid()!=pass->pw_gid) { nlog("Insufficient privileges to become"); logqnl(pass->pw_name); return EX_NOPERM; } } endpwent(); if(chdir(chp=pass->pw_dir)) goto nochdir; } if(*targetdir&&chdir(chp=(char*)targetdir)) nochdir: { nlog("Couldn't chdir to");logqnl(chp); return EX_NOINPUT; } if(stat(defdir,&stbuf)) { nlog("Can't find \"");elog(defdir);elog("\" in");logqnl(chp); return EX_NOINPUT; } if(pass->pw_uid!=stbuf.st_uid||pass->pw_gid!=stbuf.st_gid) nlog("Strange group or user id\n"); /* check for -request */ if((chp=strchr(arg,'\0'))-arg>STRLEN(request)&& !strcmp(chp-=STRLEN(request),request)) *chp='\0',pmexec[1]=rcrequest,Endpmexec(1)=0,Endpmexec(2)=rcpost; else chp=0; if(!strcmp(arg,chPARDIR)||strpbrk(arg,dirsep)) { nlog("Bogus listname\n"); return EX_DATAERR; } ;{ int foundlock; do { foundlock=0; if(chdir(arg)) /* goto the list's subdirectory */ pmexec[1]=RCMAIN,Endpmexec(2)=0,chdir(defdir); while(rclock(GLOCKFILE,&stbuf)||rclock(LLOCKFILE,&stbuf)) foundlock=1; /* stall */ } while(foundlock&&!chdir(LINKMOVED)); /* did the lists move? */ } Endpmexec(5)=INIT_PATH; Endpmexec(4)=argstr(list,arg); /* pass on the list name */ if(chp) /* was it a -request list? */ *chp= *request; /* then restore the leading '-' */ Endpmexec(3)=argstr(XENVELOPETO,arg); execv(pmexec[0],(char*const*)pmexec);nlog("Couldn't exec"); logqnl(pmexec[0]); return EX_UNAVAILABLE; /* panic */ } /* * revoke any suid permissions now, since we're not flist */ if((setgid(getgid())&&getgid()!=getegid())|| (setuid(getuid())&&getuid()!=geteuid())) return EX_OSERR; /* this _really_ can't happen */ if(ISPROGRAM(chp,idhash)) /* idhash program? */ { unsigned long hash=0;int i; progname=idhash; if(argc!=1) { elog("Usage: idhash\n"); return EX_USAGE; } while(i=fgetc(stdin),!feof(stdin)) /* hash away! */ hash=hash*67067L+i; printf("%lx",hash); return EXIT_SUCCESS; } if(ISPROGRAM(chp,senddigest)) /* senddigest program? */ { struct stat stbuf; progname=senddigest; if(argc<5) { elog( "Usage: senddigest maxage maxsize bodyfile trailerfile [file] ...\n"); return EX_USAGE; } if(!stat(argv[3],&stbuf)) { time_t newt;long size; newt=stbuf.st_mtime;size=stbuf.st_size; if(!stat(argv[argc=4],&stbuf)) { long maxsize; if(stbuf.st_mtime+strtol(argv[1],(char**)0,10)-newt<=0) return EXIT_SUCCESS; /* digest too old */ maxsize=strtol(argv[2],(char**)0,10); goto statd; do { if(!stat(argv[argc],&stbuf)) statd: if((size+=stbuf.st_size)>maxsize) /* digest too big? */ return EXIT_SUCCESS; } while(argv[++argc]); } } return 1; } hardstr.text=malloc(hardstr.buflen=BUFSTEP); if(ISPROGRAM(chp,choplist)) { unsigned long minnames,mindiffnames,maxnames,maxsplits,maxsize, maxconcur,maxensize; char*distfile,**revarr;int mailfd;size_t revfilled; static const char tmpmailfile[]=TMPMAILFILE; char lmailfile[STRLEN(TMPMAILFILE)+8*sizeof(pid_t)*4/10+1+1]; progname=choplist; if(argc<9) { elog( "Usage: choplist minnames mindiffnames maxnames maxsplits maxsize maxconcur\n\ \tdistfile sendmail [flags ...]\n"); return EX_USAGE; } minnames=strtol(argv[1],(char**)0,10); mindiffnames=strtol(argv[2],(char**)0,10); maxnames=strtol(argv[3],(char**)0,10); maxsplits=strtol(argv[4],(char**)0,10); maxsize=strtol(argv[5],(char**)0,10); maxconcur=strtol(argv[6],(char**)0,10);distfile=argv[7]; nargv=argv+8;argc-=8;setbuf(stdin,(char*)0);setbuf(stdout,(char*)0); sprintf((char*)(mailfile=lmailfile),tmpmailfile,(long)getpid()); qsignal(SIGTERM,sterminate);qsignal(SIGINT,sterminate); qsignal(SIGHUP,sterminate);qsignal(SIGQUIT,sterminate); #ifdef SIGCHLD signal(SIGCHLD,SIG_DFL); #endif signal(SIGPIPE,SIG_DFL);unlink(mailfile); #ifndef O_CREAT if(0>(mailfd=creat(mailfile,NORMperm))) #else if(0>(mailfd=open(mailfile,O_RDWR|O_CREAT|O_EXCL,NORMperm))) #endif { nlog("Can't create temporary file");logqnl(mailfile); return EX_CANTCREAT; } ;{ char*buf;int i;unsigned long totsize=0; buf=malloc(COPYBUF); goto jin; do { char*a=buf;size_t len; totsize+=(len=i); do { while(0>(i=write(mailfd,buf,(size_t)len))&&errno==EINTR); if(i<0) { nlog("Can't write temporary file");logqnl(mailfile); retval=EX_IOERR;sterminate(0); } a+=i; } while(i>0&&(len-=i)); jin: while(0>(i=read(STDIN,buf,(size_t)COPYBUF))&&errno==EINTR); } while(i>0); if(!totsize) nlog("Can't find the mail\n"),retval=EX_NOINPUT,sterminate(0); free(buf);totsize=(maxsize+totsize-1)/totsize; if(maxsize&&(!maxsplits||totsize0) /* lose our tail */ return EXIT_SUCCESS; /* causes procmail to release the lockfile */ revarr=realloc(revarr,revfilled*sizeof*revarr); /* be modest */ hsort(revarr,revfilled,sizeof*revarr,pstrIcmp); /* sort'em */ if(maxsplits) { maxsplits=(revfilled+maxsplits-1)/maxsplits; if(!minnames||minnamesMAX_argc-argc) maxnames=MAX_argc-argc; ;{ size_t envc;const char*const*nam; nam=(const char*const*)environ;envc=argc; #define MAX_envc 0 /* should be dynamic in the future */ for(maxensize=(MAX_argc+MAX_envc)*16L;*nam; envc++,maxensize-=strlen(*nam++)+1+sizeof*nam); if(maxnames>MAX_argc+MAX_envc-envc) maxnames=MAX_argc+MAX_envc-envc; } if(minnames>maxnames) minnames=maxnames; if(!minnames) minnames=1; ;{ int*rdist,*ip;char**nam;size_t n; ip=rdist=malloc((n=revfilled)*sizeof*rdist);nam=revarr; while(--n) { int i,j;char*left,*right; left= *nam; if(!(i=strIcmp(right= *++nam,left))) j=SCALE_WEIGHT; /* identical! don't split them up */ else for(j=0;--i;) switch(*right++) { case '@':j=SCALE_WEIGHT/2; /* domains match! */ case '.':j++; /* domain borders */ } revstr(left);*ip++=j; } revstr(*nam);*ip=0;nam=malloc(1+ ++argc*sizeof*argv); tmemmove(nam+1,nargv,argc*sizeof*argv);*(nargv=nam)=BinSh; ;{ unsigned long cnames,cnsize,cconcur; char**first,**best; ;{ unsigned long maxnsize; for(maxnsize=0;*nam;maxnsize+=strlen(*nam++)+1+sizeof*nam); maxensize-=maxnsize; if(maxensize<(maxnsize=MAX_argc-maxnsize)) maxensize=maxnsize; } n=cconcur=0; do { int bestval; cnsize=strlen(*(first=nam=revarr+n))+1+sizeof*nam;cnames=0; do { if(nam-first=SCALE_WEIGHT/2&&rdist[n]>=SCALE_WEIGHT/2|| rdist[n]=(cnsize+=strlen(*++nam)+1+sizeof*nam)&& maxnames>cnames); nam=(nargv=realloc(nargv, (1+(bestval=best-first+1)+argc)*sizeof*argv))+argc; if(maxconcur&&maxconcur<++cconcur) wait((int*)0); tmemmove(nam,first,bestval*sizeof*argv);nam[bestval]=0; if(STDIN!=open(mailfile,O_RDONLY)) { nlog("Lost");logqnl(mailfile);retval=EX_NOINPUT; sterminate(0); } for(;;) { switch(fork()) { case -1:nlog("Couldn't fork, retrying\n"); if(wait((int*)0)==-1) sleep(DEFsuspend); continue; case 0: /* may be a script */ execv(nargv[1],nargv+1);execv(nargv[0],nargv); kill(getppid(),SIGTERM);nlog("Couldn't exec"); logqnl(nargv[0]); return EX_UNAVAILABLE; } break; } close(STDIN); } while((n=best-revarr+1)1) goto usg; if(excstr.text) { excstr.textlen=strlen(excstr.text);lowcase(&excstr); if(exc2str.text) exc2str.textlen=strlen(exc2str.text),lowcase(&exc2str); } hfiles=malloc((argc+1-(nargv-argv))*sizeof*hfiles); ;{ const char*accstr=remov||renam||addit?"r+":"r"; unsigned i; if(!(*hfiles=hardfile=fopen(chp,accstr))) { nlog(cldntopen);logqnl(chp); return EX_IOERR; } for(i=1;*nargv;nargv++) if(hfiles[i]=fopen(*nargv,accstr)) i++; argc=i; } #ifdef SPEEDBUF /* allocate a bigger stdio buffer */ setvbuf(hardfile,malloc(SPEEDBUF),_IOFBF,(size_t)SPEEDBUF); #endif } else usg: { elog(usage); return EX_USAGE; } if(addit) /* special subfunction, to add entries */ { int lnl;long lasttell; /* to the dist file */ for(lnl=1,lasttell=0;;) { switch(getc(hardfile)) /* step through the file */ { case '\n': if(!lnl) /* looking for trailing newlines */ lnl=1,lasttell=ftell(hardfile); continue; default:lnl=0; continue; case EOF:; /* or the end of the file */ } break; } /* go back there, and add the new entry */ fseek(hardfile,lasttell,SEEK_SET);fprintf(hardfile,"%s\n",addit); printf("Added: %s\n",addit); return EXIT_SUCCESS; } if(!maxgram) maxgram=DEFmaxgram; maxgram--; if(minweight==SCALE_WEIGHT) minweight=DEFminweight; if(!best_matches) best_matches=DEFbest_matches; fuzzstr.text=malloc(fuzzstr.buflen=BUFSTEP); ;{ int i; best=malloc(best_matches--*sizeof*best);i=best_matches; do { best[i]=malloc(sizeof**best);best[i]->hard=malloc(1); best[i]->fuzz=malloc(1);best[i]->metric= -SCALE_WEIGHT; } while(i--); } for(lastfrom= -1;dodomain||readstr(stdin,&fuzzstr,0);) { int meter;long linentry;long offs1,offs2; unsigned hfile; ;{ char*chp; static const char tpunctuation[]="@\\/!#$%^&*-_=+|~`';:,.?{}"; #define punctuation (tpunctuation+3) static const char colonpunct[]="/:@!"; #define routepunct (colonpunct+1) chp=fuzzstr.text; if(!dodomain) /* still have to do with domain? */ { char*echp=strchr(chp,'\0')-1; while(*chp&&strchr(punctuation,*chp)) chp++; /* strip leading punctuation */ if(*chp=='"'&&!strchr(chp+1,'"')) /* strip leading unbalanced " */ chp++; while(*chp&&strchr(punctuation,*chp)) chp++; /* strip leading punctuation */ ;{ const char*colon; /* no decnet address */ if(*(colon=chp+strcspn(chp,colonpunct))==':'&&colon[1]!=':') chp=(char*)colon+1; /* strip leading ...: garbage */ } while(echp>=chp&&strchr(tpunctuation,*echp)) *echp--='\0'; /* strip trailing punctuation */ if(echp>=chp&&*echp=='"'&&strchr(chp,'"')==echp) *echp--='\0'; /* strip trailing unbalanced " */ while(echp>=chp&&strchr(tpunctuation,*echp)) *echp--='\0'; /* strip trailing punctuation */ if(echp')&& /* RFC-822 machine literal */ !(incomplete&&strchr(chp,'.'))) /* domain name */ reject: { if(lastfrom<0) lastfrom=!strcmp(SHFROM,chp); continue; /* apparently not an e-mail address */ } ;{ const char*colon; if((*chp=='@'||*chp=='<'&&chp[1]=='@')&& /* leading at's are */ (!(colon=strchr(chp,':'))||strchr(routepunct,colon[1]))) goto reject; /* only allowed on route addresses */ } lastfrom=0;tmemmove(fuzzstr.text,chp,echp-chp+2); checkparens('(',')',fuzzstr.text,echp); checkparens('[',']',fuzzstr.text,strchr(fuzzstr.text,'\0')); if(*(chp=fuzzstr.text)=='<'&&*(echp=strchr(chp,'\0')-1)=='>') { if(chp=strstr(chp,">,<")) /* take the first of a dense */ (echp=chp)[1]='\0'; /* list of addresses */ if(!strchr(chp=fuzzstr.text,',')) /* strip '<' and '>' ? */ *echp='\0',tmemmove(chp,chp+1,echp-chp); } } ;{ size_t len; if(!(len=strlen(chp))) /* still something left? */ continue; /* it's gone, next word please */ if(dodomain) /* add default local domain and reiterate */ { dodomain=0;fuzzstr.text=chp=realloc(chp,len+lldomain+1); chp[len]='@';strcpy(chp+len+1,ldomain);len+=lldomain; } else if(ldomain&&!strpbrk(chp,"@!/")) /* no domain attached? */ dodomain=1; /* mark it for the next run */ fuzzstr.textlen=len; } lowcase(&fuzzstr); /* cast it into lowercase */ if(excstr.text&&matchgram(&fuzzstr,&excstr)>=EXCL_THRESHOLD|| exc2str.text&&matchgram(&fuzzstr,&exc2str)>=EXCL_THRESHOLD) { free(fuzzstr.itext); continue; } ;{ int i=0; do { if(best[i]->metric==-SCALE_WEIGHT&&!strcmp(best[i]->fuzz,chp)) break; if(!strcmp(best[i]->fuzz,chp)) /* already matched this one? */ goto dupl_addr; } while(++i<=best_matches); } if(!curmatch) curmatch=malloc(sizeof*curmatch); curmatch->fuzz=tstrdup(chp);curmatch->hard=malloc(1); curmatch->metric= -SCALE_WEIGHT; } for(hfile=0;hfilemetric; fseek(hardfile=hfiles[hfile++],(long)0,SEEK_SET);ctellinit(); for(remov_delim=offs2=linentry=0; offs1=offs2,readstr(hardfile,&hardstr,1);) { offs2=ctell(hardfile);linentry++; if(*hardstr.text=='(') continue; /* unsuitable for matches */ lowcase(&hardstr);meter=matchgram(&fuzzstr,&hardstr); free(hardstr.itext); /* check if we had any luck */ if(meter>maxmetric&&(fremov||remov_delim||!renam&&!remov)) { size_t hardlen; curmatch->metric=maxmetric=meter;curmatch->lentry=linentry; curmatch->offs1=offs1;curmatch->offs2=offs2; curmatch->hardfile=hardfile; free(curmatch->hard);hardlen=hardstr.textlen+1; curmatch->hard=malloc(hardlen+=strlen(hardstr.text+hardlen)+1); tmemmove(curmatch->hard,hardstr.text,hardlen); if(multiple) { struct match*mp,**mmp; free((mp= *(mmp=best+best_matches))->fuzz);free(mp->hard); mp->fuzz=tstrdup(curmatch->fuzz); tmemmove(mp->hard=malloc(hardlen),hardstr.text,hardlen); mp->metric=meter;mp->lentry=linentry;mp->offs1=offs1; mp->offs2=offs2;mp->hardfile=hardfile; ;{ struct match*mpt; while(--mmp>=best&&(mpt= *mmp)->metricmetric; } } } } free(fuzzstr.itext); /* maybe this match can be put in the array */ if(!multiple&&curmatch->metric>-SCALE_WEIGHT) /* of best matches so far */ { struct match*mp,**mmp; free((mp= *(mmp=best+best_matches))->fuzz);free(mp->hard);free(mp); while(--mmp>=best&&(mp= *mmp)->metricmetric) mmp[1]=mp; /* keep it sorted */ mmp[1]=curmatch;curmatch=0; } else free(curmatch->fuzz),free(curmatch->hard); dupl_addr:; } ;{ int i;struct match*mp; for(i=0;i<=best_matches&&(mp=best[i++])->metric>=minweight;) #if 0 /* metoo support removed, not supported by sendmail */ if(chkmetoo) printf("%s\n",strcmp(mp->hard+strlen(mp->hard)+1,NOT_METOO) ?metoo_SENDMAIL:nometoo_SENDMAIL); else #endif printf("%3ld %-34s %5d %s\n", (long)(charoffs?mp->offs1:mp->lentry),mp->hard,mp->metric,mp->fuzz); if((mp= *best)->metric>=minweight) { struct match*worse; if(renam) { long line;int w1;unsigned maxweight; maxweight=SCALE_WEIGHT/(maxgram?maxgram:1)>>1;; for(i=1,line=mp->lentry,w1=mp->metric,worse=0; i<=best_matches&&(mp=best[i++])->metric>=minweight;) if(mp->lentry==line&&mp->metric+maxweightmetric>=minweight;) if(mp->metric+maxweightoffs1;offs2=mp->offs2; hardfile=mp->hardfile; while(fseek(hardfile,offs2,SEEK_SET), readin=fread(buf,1,COPYBUF,hardfile)) { offs2=ftell(hardfile);fseek(hardfile,offs1,SEEK_SET); if(buf[readin-1]=='\n') /* try and remove some empty lines */ while(readin>1&&buf[readin-2]=='\n') /* at the end, since */ readin--; /* every time could be the last */ fwrite(buf,1,readin,hardfile);offs1=ftell(hardfile); } free(buf);fseek(hardfile,offs1,SEEK_SET); printf("Removed: %s\n",mp->hard); if(renam) fputs(worse->fuzz,hardfile),printf("Added: %s\n",worse->fuzz); fflush(hardfile); /* flush before we truncate */ if(ftruncate(fileno(hardfile),(off_t)ftell(hardfile))) do putc('\n',hardfile); /* truncate failed, erase the tail */ while(ftell(hardfile)