/*Id: ezmlm-make.c,v 1.31 1997/12/08 23:44:02 lindberg Exp lindberg $*/ /*$Name: ezmlm-idx-040 $*/ #include #include #include "sgetopt.h" #include "stralloc.h" #include "strerr.h" #include "exit.h" #include "readwrite.h" #include "open.h" #include "substdio.h" #include "str.h" #include "auto_bin.h" #include "getln.h" #include "error.h" #include "lock.h" #include "errtxt.h" #include "idx.h" /* defaults. All other flags are false = 0 */ char *defflags="ap"; /* archived list -a */ /* public list -p */ /* no ezmlm-archive -I */ /* no text edit for remote admin -D */ /* not in edit mode -E */ /* no subs list for remote admin -L */ /* no remote admin -R */ /* no message moderation -M */ /* no subscription moderation -S */ /* don't use .ezmlmrc from dot-file dir -C */ /* no prefix -F */ /* no trailer -T */ #define NO_FLAGS ('z' - 'a' + 1) int flags[NO_FLAGS]; /* holds flags */ char *popt[10]; stralloc dotplus = {0}; stralloc dirplus = {0}; stralloc line = {0}; #define FATAL "ezmlm-make: fatal: " #define WARNING "ezmlm-make: warning: " void die_usage() { strerr_die1x(100, "ezmlm-make: usage: ezmlm-make [-+] [ -a..zA..Z03..9 ] dir dot local host"); } void die_relative() { strerr_die2x(100,FATAL,ERR_SLASH); } void die_newline() { strerr_die2x(100,FATAL,ERR_NEWLINE); } void die_quote() { strerr_die2x(100,FATAL,ERR_QUOTE); } void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } void die_read() { strerr_die4sys(111,FATAL,ERR_READ,dirplus.s,": "); } stralloc cmdline = {0}; stralloc outline = {0}; substdio sstext; char textbuf[1024]; stralloc fname = {0}; /* file name */ stralloc oldfname = {0}; /* file name from prevoius tag */ stralloc dname = {0}; /* directory name */ stralloc lname = {0}; /* link name */ stralloc template = {0}; /* template file name */ stralloc ext1 = {0}; /* dot = dir/.qmail-ext1-ext2-list */ stralloc ext2 = {0}; stralloc f = {0}; stralloc key = {0}; struct timeval tv; char sz[2] = "?"; void keyadd(u) unsigned long u; { char ch; ch = (char) u; if (!stralloc_append(&key,&ch)) die_nomem(); u >>= 8; ch = (char) u; if (!stralloc_append(&key,&ch)) die_nomem(); u >>= 8; ch = (char) u; if (!stralloc_append(&key,&ch)) die_nomem(); u >>= 8; ch = (char) u; if (!stralloc_append(&key,&ch)) die_nomem(); } void keyaddtime() { gettimeofday(&tv,(struct timezone *) 0); keyadd(tv.tv_usec); } char *dir; char *dot; char *local = (char *) 0; char *host = (char *) 0; void dirplusmake(slash) char *slash; { if (!stralloc_copys(&dirplus,dir)) die_nomem(); if (!stralloc_cats(&dirplus,slash)) die_nomem(); if (!stralloc_0(&dirplus)) die_nomem(); } void linkdotdir(dash,slash) char *dash; char *slash; { if (!stralloc_copys(&dotplus,dot)) die_nomem(); if (!stralloc_cats(&dotplus,dash)) die_nomem(); if (!stralloc_0(&dotplus)) die_nomem(); dirplusmake(slash); if (flags['e' - 'a']) if (unlink(dotplus.s) == -1) if (errno != error_noent) strerr_die4x(111,FATAL,ERR_DELETE,dotplus.s,": "); if (symlink(dirplus.s,dotplus.s) == -1) strerr_die4sys(111,FATAL,ERR_CREATE,dotplus.s,": "); keyaddtime(); } void dcreate(slash) char *slash; { dirplusmake(slash); if (mkdir(dirplus.s,0755) == -1) if ((errno != error_exist) || !flags['e' - 'a']) strerr_die4sys(111,FATAL,ERR_CREATE,dirplus.s,": "); keyaddtime(); } substdio ss; char ssbuf[SUBSTDIO_OUTSIZE]; void f_open(slash) char *slash; { int fd; dirplusmake(slash); fd = open_trunc(dirplus.s); if (fd == -1) strerr_die4sys(111,FATAL,ERR_CREATE,dirplus.s,": "); substdio_fdbuf(&ss,write,fd,ssbuf,sizeof(ssbuf)); } void f_put(buf,len) char *buf; unsigned int len; { if (substdio_bput(&ss,buf,len) == -1) strerr_die4sys(111,FATAL,ERR_WRITE,dirplus.s,": "); } void f_puts(buf) char *buf; { if (substdio_bputs(&ss,buf) == -1) strerr_die4sys(111,FATAL,ERR_WRITE,dirplus.s,": "); } void f_close() { if (substdio_flush(&ss) == -1) strerr_die4sys(111,FATAL,ERR_FLUSH,dirplus.s,": "); if (fsync(ss.fd) == -1) strerr_die4sys(111,FATAL,ERR_SYNC,dirplus.s,": "); if (close(ss.fd) == -1) /* NFS stupidity */ strerr_die4sys(111,FATAL,ERR_CLOSE,dirplus.s,": "); keyaddtime(); } void frm(slash) char *slash; { dirplusmake(slash); if (unlink(dirplus.s) == -1) if (errno != error_noent) strerr_die4sys(111,FATAL,ERR_DELETE,dirplus.s,": "); } void main(argc,argv) int argc; char **argv; { unsigned long euid; int opt; int flagdo; int flagnot; int flagover; int flagnotexist; int flagforce = 0; int flagforce_p = 0; int usecfg = 0; int match; unsigned int next,i,j; int last; unsigned int slpos,hashpos,pos; int fdin,fdlock,fdtmp; char *p; char *oldflags = (char *) 0; char *code = (char *) 0; char *cfname = (char *) 0; /* config file if spec as -C cf_file */ char ch; keyadd((unsigned long) getpid()); keyadd((unsigned long) getppid()); euid = (unsigned long) geteuid(); keyadd(euid); keyadd((unsigned long) getgid()); gettimeofday(&tv,(struct timezone *) 0); keyadd(tv.tv_sec); (void) umask(077); /* flags with defined use. vV for version. Others free */ for (pos = 0; pos < (unsigned int) NO_FLAGS; pos++) { flags[pos] = 0; } for (pos = 0; pos < 10; popt[pos++] = (char *) 0); while ((opt = getopt(argc,argv, "+aAbBcC:dDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0:3:4:5:6:7:8:9:")) != opteof) { if (opt == 'v' || opt == 'V') strerr_die2x(0,"ezmlm-make version: ezmlm-0.53+",EZIDX_VERSION); if (opt =='C') /* treat this like nl switch to allow override of -c*/ cfname = optarg; if (opt >= 'a' && opt <= 'z') { flags[opt - 'a'] = 3; /* Dominant "set" */ if (opt == 'e') flagforce++; /* two 'e' => ignore 'E' */ } else if (opt >= 'A' && opt <= 'Z') flags[opt - 'A'] = 2; /* Dominant "unset" */ else if (opt >= '0' && opt <= '9') popt[opt-'0'] = optarg; else if (opt == '+') { flagforce_p++; /* two '+' => ignore 'E' */ flags['e' - 'a'] = 3; /* -+ implies -e */ usecfg = 1; } else die_usage(); } argv += optind; if (flagforce_p > 1 || flagforce > 1) flagforce = 1; else flagforce = 0; if (!(dir = *argv++)) die_usage(); if (dir[0] != '/') die_relative(); if (dir[str_chr(dir,'\'')]) die_quote(); if (dir[str_chr(dir,'\n')]) die_newline(); if (flags['e' - 'a'] & 1) { /* lock for edit */ dirplusmake("/lock"); fdlock = open_append(dirplus.s); if (fdlock == -1) strerr_die4sys(111,FATAL,ERR_OPEN,dirplus.s,": "); if (lock_ex(fdlock) == -1) strerr_die4sys(111,FATAL,ERR_OBTAIN,dirplus.s,": "); /* for edit, try to get args from dir/config */ dirplusmake("/config"); if ((fdin = open_read(dirplus.s)) == -1) { if (errno != error_noent) die_read(); } else { substdio_fdbuf(&sstext,read,fdin,textbuf,sizeof(textbuf)); for (;;) { if (getln(&sstext,&line,&match,'\n') == -1) die_read(); if (!match) break; if (line.s[0] == '#') continue; if (line.len == 1) break; if (line.s[1] != ':') break; line.s[line.len - 1] = '\0'; if (!stralloc_cat(&cmdline,&line)) die_nomem(); } close(fdin); pos = 0; while (pos < cmdline.len) { ch = cmdline.s[pos]; pos += 2; switch (ch) { case 'X': if (euid && !flags['c' - 'a'] && (!cfname)) cfname = cmdline.s + pos; /* cmdline overrides */ break; /* for safety: ignore if root */ case 'T': dot = cmdline.s + pos; break; case 'L': local = cmdline.s + pos; break; case 'H': host = cmdline.s + pos; break; case 'C': code = cmdline.s + pos; break; case 'D': break; /* no reason to check */ case 'F': oldflags = cmdline.s + pos; break; default: if (ch == '0' || (ch >= '3' && ch <= '9')) { if (usecfg && !popt[ch - '0']) popt[ch - '0'] = cmdline.s + pos; } else strerr_die4x(111,FATAL,dirplus.s,ERR_SYNTAX, cmdline.s+pos); break; } pos += str_len(cmdline.s + pos) + 1; } } } if (p = *argv++) { dot = p; if (p = *argv++) { if (!local || str_diff(local,p)) flagforce = 1; /* must rewrite if list name changed */ local = p; if (p = *argv++) { if (!host || str_diff(host,p)) flagforce = 1; /* must rewrite if list name changed */ host = p; if (p = *argv++) { code = p; } } } } if (!dot || !local || !host) die_usage(); if (dot[0] != '/') die_relative(); /* force absolute dot */ /* use flags from config, overridden with new values */ /* if there are old flags, we're in "edit" and "-+" */ /* Previous versions only wrote _set_ flags to */ /* to DIR/confiag. We need to make sure that we */ /* don't apply the defaults for non-specified ones! */ if (usecfg && oldflags && flags['e' - 'a']) { while ((ch = *(oldflags++))) { if (ch >= 'a' && ch <= 'z') { /* unset flags ignored */ if (ch != 'e') if (!flags[ch - 'a']) /* cmd line overrides */ flags[ch - 'a'] = 1; } } } if (!usecfg) { /* apply defaults */ while (( ch = *(defflags++))) { /* gets used up! */ if (ch >= 'a' && ch <= 'z') { /* defensive! */ if (!flags[ch - 'a']) /* cmdline still overrides */ flags[ch - 'a'] = 1; } } } for (pos = 0; pos < (unsigned int) NO_FLAGS; pos++) { /* set real flags */ if (flags[pos] & 2) /* 2 = "dominant" 0 */ flags[pos] = flags[pos] & 1; /* 3 = "dominant" 1 */ } if (local[str_chr(local,'\n')]) die_newline(); if (host[str_chr(host,'\n')]) die_newline(); /* build 'f' for <#F#> */ if (!stralloc_ready(&f,28)) die_nomem(); if (!stralloc_copys(&f,"-")) die_nomem(); for (ch = 0; ch <= 'z' - 'a'; ch++) { /* build string with flags */ if (flags[ch]) sz[0] = 'a' + ch; else sz[0] = 'A' + ch; if (!stralloc_append(&f,sz)) die_nomem(); } fdin = -1; /* assure failure for .ezmlmrc in case flags['c'-'a'] = 0 */ slpos = str_len(dot); while ((--slpos > 0) && dot[slpos] != '/'); if (dot[slpos] == '/') { if (!stralloc_copyb(&template,dot,slpos+1)) die_nomem(); /* dot dir */ slpos += str_chr(dot+slpos,'-'); if (dot[slpos]) { slpos++; pos = slpos + str_chr(dot+slpos,'-'); if (dot[pos]) { if (!stralloc_copyb(&ext1,dot+slpos,pos-slpos)) die_nomem(); pos++; slpos = pos + str_chr(dot+pos,'-'); if (dot[slpos]) if (!stralloc_copyb(&ext2,dot+pos,slpos-pos)) die_nomem(); } } } if (!stralloc_0(&ext1)) die_nomem(); if (!stralloc_0(&ext2)) die_nomem(); popt[1] = ext1.s; popt[2] = ext2.s; /* if 'c', template already has the dot directory. If 'C', cfname */ /* (if exists and != '') points to the file name to use instead. */ if (flags['c'-'a'] || (cfname && *cfname)) { if (!flags['c'-'a']) { /* i.e. there is a cfname specified */ if (!stralloc_copys(&template,cfname)) die_nomem(); } else if (!stralloc_cats(&template,TXT_DOTEZMLMRC)) die_nomem(); if (!stralloc_0(&template)) die_nomem(); if ((fdin = open_read(template.s)) == -1) if (errno != error_noent) strerr_die4sys(111,FATAL,ERR_OPEN,template.s,": "); else strerr_die3x(100,FATAL,template.s,ERR_NOEXIST); } else { /* /etc/ezmlmrc */ if (!stralloc_copys(&template,TXT_ETC_EZMLMRC)) die_nomem(); if (!stralloc_0(&template)) die_nomem(); if ((fdin = open_read(template.s)) == -1) if (errno != error_noent) strerr_die4sys(111,FATAL,ERR_OPEN,template.s,": "); else { /* ezbin/ezmlmrc */ if (!stralloc_copys(&template,auto_bin)) die_nomem(); if (!stralloc_cats(&template,TXT_EZMLMRC)) die_nomem(); if (!stralloc_0(&template)) die_nomem(); if ((fdin = open_read(template.s)) == -1) if (errno != error_noent) strerr_die4sys(111,FATAL,ERR_OPEN,template.s,": "); else strerr_die3x(100,FATAL,template.s,ERR_NOEXIST); } } dcreate(""); /* This is all we do, the rest is up to ezmlmrc */ /* do it after opening template to avoid aborts */ /* with created DIR. Well we also write DIR/key */ /* at the end except in -e[dit] mode. */ substdio_fdbuf(&sstext,read,fdin,textbuf,sizeof(textbuf)); if (!stralloc_0(&oldfname)) die_nomem(); /* init oldfname */ flagdo = 0; if (getln(&sstext,&line,&match,'\n') == -1) strerr_die4sys(111,FATAL,ERR_READ,template.s,": "); if (!match) strerr_die4sys(111,FATAL,ERR_READ,template.s,": "); i = str_rchr(EZIDX_VERSION,'-'); /* check version */ if (EZIDX_VERSION[i]) i++; j = 0; while (line.s[j] == EZIDX_VERSION[i] && j < line.len && EZIDX_VERSION[i] != '.' && EZIDX_VERSION[i]) { i++; j++; /* major */ } /* first minor */ if (EZIDX_VERSION[i] != '.' || j + 1 >= line.len || EZIDX_VERSION[i+1] != line.s[j+1]) strerr_warn2(WARNING,ERR_VERSION, (struct strerr *) 0); for (;;) { if (getln(&sstext,&line,&match,'\n') == -1) strerr_die4sys(111,FATAL,ERR_READ,template.s,": "); if (!match) break; if (line.s[0] == '#') /* comment */ continue; if (!stralloc_0(&line)) die_nomem(); if (line.s[0] == '<' && line.s[1] == '/') { /* tag */ if (line.s[str_chr(line.s,'.')]) strerr_die3x(100,FATAL,ERR_PERIOD,line.s); flagdo = 1; flagover = 0; hashpos = 0; pos = str_chr(line.s+2,'#')+2; if (line.s[pos]) { hashpos = pos; pos++; flagnot = 0; while ((ch = line.s[pos]) && (line.s[pos] != '/' && line.s[pos+1] != '>')) { if (ch == '^') { flagnot = 1; pos++; continue; } /* E is ignored. For files => create unless exists */ if (ch == 'E' && !flagnot || ch == 'e' && flagnot) { if (flags['e' - 'a'] && !flagforce) flagover = 1; /* ignore #E & #^e, but set flagover */ } else if (ch >= 'a' && ch <= 'z') flagdo &= (flags[ch - 'a'] ^ flagnot); else if (ch >= 'A' && ch <= 'Z') flagdo &= !(flags[ch - 'A'] ^ flagnot); else if (ch >= '0' && ch <= '9') flagdo &= (popt[ch - '0'] && *popt[ch - '0']) ^flagnot; flagnot = 0; pos++; } if (line.s[pos] != '/' || line.s[pos+1] != '>') strerr_die3x(100,FATAL,ERR_ENDTAG,line.s); } else { flagdo = 1; pos = 2; /* name needs to be >= 1 char */ while (line.s[pos = str_chr(line.s+pos,'/')+pos]) { if (line.s[pos+1] == '>') break; pos++; } if (!line.s[pos]) strerr_die3x(100,FATAL,ERR_ENDTAG,line.s); } if (hashpos) pos = hashpos; /* points to after file name */ if (line.s[2] == '+') { /* mkdir */ if (!flagdo) continue; if (!stralloc_copys(&dname,"/")) die_nomem(); if (!stralloc_catb(&dname,line.s+3,pos-3)) die_nomem(); if (!stralloc_0(&dname)) die_nomem(); dcreate(dname.s); flagdo = 0; continue; } else if (line.s[2] == ':') { /* ln -s */ if (!flagdo) continue; slpos = str_chr(line.s + 3,'/') + 3; if (slpos >= pos) strerr_die3x(100,FATAL,ERR_LINKDIR,line.s); if (!stralloc_copyb(&dname,line.s+slpos,pos-slpos)) die_nomem(); if (!stralloc_copyb(&lname,line.s+3,slpos-3)) die_nomem(); if (!stralloc_0(&dname)) die_nomem(); if (!stralloc_0(&lname)) die_nomem(); linkdotdir(lname.s,dname.s); flagdo = 0; continue; } else if (line.s[2] == '-') { /* rm */ if (!flagdo) continue; if (!stralloc_copys(&dname,"/")) die_nomem(); if (!stralloc_catb(&dname,line.s+3,pos-3)) die_nomem(); if (!stralloc_0(&dname)) die_nomem(); frm(dname.s); flagdo = 0; continue; } /* only plain files left */ /* first get file name */ if (pos > 2) { /* => add to open file */ if (!stralloc_copyb(&fname,line.s+1,pos-1)) die_nomem(); if (!stralloc_0(&fname)) die_nomem(); } if (str_diff(fname.s, oldfname.s)) { flagnotexist = 1; /* Treat special case of #E when editing which _should*/ /* write only if the file does not exist. flagover */ /* is set if we need to check */ if (flagover) { /* skip if exists */ dirplusmake(fname.s); /* decided by FIRST tag for file */ fdtmp = open_read(dirplus.s); if (fdtmp == -1) { if (errno != error_noent) strerr_die3sys(111,ERR_OPEN,dirplus.s,": "); } else { flagnotexist = 0; /* already there - don't do it */ close(fdtmp); } } if (oldfname.len > 1) { f_close(); if (!stralloc_copys(&oldfname,"")) die_nomem(); if (!stralloc_0(&oldfname)) die_nomem(); } if (flagdo && flagnotexist) { if (!fname.len) strerr_die3x(100,FATAL,ERR_FILENAME,line.s); f_open(fname.s); if (!stralloc_copy(&oldfname,&fname)) die_nomem(); } } if (flagdo) flagdo = flagnotexist; continue; } else if (!flagdo) continue; /* part not to go out */ last = -1; next = 0; outline.len = 0; for (;;) { pos = next + str_chr(line.s+next,'<'); if (line.s[pos] && line.s[pos+1] == '#' && line.s[pos+2] && line.s[pos+3] == '#' && line.s[pos+4] == '>') { /* host/local */ if (!stralloc_catb(&outline,line.s+last+1,pos-last-1)) die_nomem(); switch (line.s[pos+2]) { case 'B': /* path to ezmlm binaries (no trailing /) */ if (!stralloc_cats(&outline,auto_bin)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'C': /* digestcode */ if (code && *code) if (!stralloc_cats(&outline,code)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'D': /* listdir */ if (!stralloc_cats(&outline,dir)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'F': /* flags */ if (!stralloc_cat(&outline,&f)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'H': /* hostname */ if (!stralloc_cats(&outline,host)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'L': /* local */ if (!stralloc_cats(&outline,local)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'T': /* dot */ if (!stralloc_cats(&outline,dot)) die_nomem(); last = pos + 4; next = pos + 5; break; case 'X': /* config file name */ if (cfname) if (!stralloc_cats(&outline,cfname)) die_nomem(); last = pos + 4; next = pos + 5; break; default: /* copy unknown tag as is for e.g. <#A#> and*/ /* <#R#> to be processed by -manage/store */ /* stuff in args for <#0#> .. <#9#> */ if ((line.s[pos+2] >= '0') && (line.s[pos+2] <= '9')) { if (popt[line.s[pos+2] - '0']) if (!stralloc_cats(&outline,popt[line.s[pos+2]-'0'])) die_nomem(); } else if (!stralloc_catb(&outline,line.s+pos,5)) die_nomem(); last = pos + 4; next = pos + 5; break; } } else { /* not tag */ if (line.s[pos]) { next++; } else { if (!stralloc_catb(&outline,line.s+last+1,line.len-last-1)) die_nomem(); f_puts(outline.s); break; } } } } close(fdin); if (oldfname.len > 1) f_close(); if (!flags['e' - 'a']) { /* don't redo key when editing a list */ f_open("/key"); f_put(key.s,key.len); f_close(); } _exit(0); }