1 /*Id: ezmlm-make.c,v 1.31 1997/12/08 23:44:02 lindberg Exp lindberg $*/
2 
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/time.h>
6 #include <unistd.h>
7 #include "sgetopt.h"
8 #include "stralloc.h"
9 #include "strerr.h"
10 #include "exit.h"
11 #include "readwrite.h"
12 #include "byte.h"
13 #include "open.h"
14 #include "substdio.h"
15 #include "subdb.h"
16 #include "str.h"
17 #include "wrap.h"
18 #include "auto_bin.h"
19 #include "getln.h"
20 #include "error.h"
21 #include "lock.h"
22 #include "messages.h"
23 #include "die.h"
24 #include "idx.h"
25 #include "auto_etc.h"
26 #include "auto_version.h"
27 
28 			/* defaults. All other flags are false = 0 */
29 const char *defflags="ap";	/* archived list -a */
30 				/* public list -p */
31 				/* no ezmlm-archive -I */
32 				/* no text edit for remote admin -D */
33 				/* not in edit mode -E */
34 				/* no subs list for remote admin -L */
35 				/* no remote admin -R */
36 				/* no message moderation -M */
37 				/* no subscription moderation -S */
38 				/* don't use .ezmlmrc from dot-file dir -C */
39 				/* no prefix -F */
40 				/* no trailer -T */
41 
42 #define NO_FLAGS ('z' - 'a' + 1)
43 static int flags[NO_FLAGS];	/* holds flags */
44 
45 static stralloc popt[10] = {{0}};
46 static stralloc dotplus = {0};
47 static stralloc dirplus = {0};
48 static stralloc line = {0};
49 
50 const char FATAL[] = "ezmlm-make: fatal: ";
51 const char WARNING[] = "ezmlm-make: warning: ";
52 const char USAGE[] =
53 "ezmlm-make: usage: ezmlm-make [-+] [ -a..zA..Z03..9 ] dir dot local host";
54 
die_relative(void)55 void die_relative(void)
56 {
57   strerr_die2x(100,FATAL,MSG(ERR_SLASH));
58 }
die_newline(void)59 void die_newline(void)
60 {
61   strerr_die2x(100,FATAL,MSG(ERR_NEWLINE));
62 }
die_quote(void)63 void die_quote(void)
64 {
65   strerr_die2x(100,FATAL,MSG(ERR_QUOTE));
66 }
67 
die_read(void)68 void die_read(void)
69 {
70   strerr_die2sys(111,FATAL,MSG1(ERR_READ,dirplus.s));
71 }
72 
73 static stralloc outline = {0};
74 static substdio sstext;
75 static char textbuf[1024];
76 
77 static stralloc fname = {0};	/* file name */
78 static stralloc oldfname = {0};	/* file name from prevoius tag */
79 static stralloc dname = {0};	/* directory name */
80 static stralloc lname = {0};	/* link name */
81 static stralloc template = {0};	/* template file name */
82 static stralloc f = {0};
83 static stralloc key = {0};
84 static struct timeval tv;
85 static char sz = '?';
86 
keyadd(unsigned long u)87 void keyadd(unsigned long u)
88 {
89   char ch;
90   ch = (char) u; if (!stralloc_append(&key,ch)) die_nomem(); u >>= 8;
91   ch = (char) u; if (!stralloc_append(&key,ch)) die_nomem(); u >>= 8;
92   ch = (char) u; if (!stralloc_append(&key,ch)) die_nomem(); u >>= 8;
93   ch = (char) u; if (!stralloc_append(&key,ch)) die_nomem();
94 }
95 
keyaddtime(void)96 void keyaddtime(void)
97 {
98   gettimeofday(&tv,(struct timezone *) 0);
99   keyadd(tv.tv_usec);
100 }
101 
102 static stralloc dir = {0};
103 static stralloc dot = {0};
104 static stralloc local = {0};
105 static stralloc host = {0};
106 
107 static unsigned long euid;
108 static stralloc cfname = {0};	/* config file if spec as -C cf_file */
109 static stralloc code = {0};
110 static stralloc oldflags = {0};
111 static int usecfg = 0;
112 
dirplusmake(const char * slash)113 void dirplusmake(const char *slash)
114 {
115   if (!stralloc_copy(&dirplus,&dir)) die_nomem();
116   if (!stralloc_cats(&dirplus,slash)) die_nomem();
117   if (!stralloc_0(&dirplus)) die_nomem();
118 }
119 
linkdotdir(const char * dash,const char * slash)120 void linkdotdir(const char *dash,const char *slash)
121 {
122   if (!stralloc_copy(&dotplus,&dot)) die_nomem();
123   if (!stralloc_cats(&dotplus,dash)) die_nomem();
124   if (!stralloc_0(&dotplus)) die_nomem();
125   dirplusmake(slash);
126   if (flags['e' - 'a'])
127     if (unlink(dotplus.s) == -1)
128       if (errno != error_noent)
129         strerr_die2sys(111,FATAL,MSG1(ERR_DELETE,dotplus.s));
130   if (symlink(dirplus.s,dotplus.s) == -1)
131     strerr_die2sys(111,FATAL,MSG1(ERR_CREATE,dotplus.s));
132   keyaddtime();
133 }
134 
dcreate(const char * slash)135 void dcreate(const char *slash)
136 {
137   dirplusmake(slash);
138   if (mkdir(dirplus.s,0755) == -1)
139     if ((errno != error_exist) || !flags['e' - 'a'])
140       strerr_die2sys(111,FATAL,MSG1(ERR_CREATE,dirplus.s));
141   keyaddtime();
142 }
143 
144 substdio ss;
145 char ssbuf[SUBSTDIO_OUTSIZE];
146 
f_open(const char * slash)147 void f_open(const char *slash)
148 {
149   int fd;
150 
151   dirplusmake(slash);
152   fd = open_trunc(dirplus.s);
153   if (fd == -1)
154     strerr_die2sys(111,FATAL,MSG1(ERR_CREATE,dirplus.s));
155 
156   substdio_fdbuf(&ss,write,fd,ssbuf,sizeof(ssbuf));
157 }
158 
f_put(const char * buf,unsigned int len)159 void f_put(const char *buf,unsigned int len)
160 {
161   if (substdio_bput(&ss,buf,len) == -1)
162     strerr_die2sys(111,FATAL,MSG1(ERR_WRITE,dirplus.s));
163 }
f_puts(const char * buf)164 void f_puts(const char *buf)
165 {
166   if (substdio_bputs(&ss,buf) == -1)
167     strerr_die2sys(111,FATAL,MSG1(ERR_WRITE,dirplus.s));
168 }
169 
f_close(void)170 void f_close(void)
171 {
172   if (substdio_flush(&ss) == -1)
173     strerr_die2sys(111,FATAL,MSG1(ERR_FLUSH,dirplus.s));
174   if (fsync(ss.fd) == -1)
175     strerr_die2sys(111,FATAL,MSG1(ERR_SYNC,dirplus.s));
176   if (close(ss.fd) == -1) /* NFS stupidity */
177     strerr_die2sys(111,FATAL,MSG1(ERR_CLOSE,dirplus.s));
178   keyaddtime();
179 }
180 
frm(const char * slash)181 void frm(const char *slash)
182 {
183   dirplusmake(slash);
184   if (unlink(dirplus.s) == -1)
185     if (errno != error_noent)
186     strerr_die2sys(111,FATAL,MSG1(ERR_DELETE,dirplus.s));
187 }
188 
exists(const char * dpm)189 int exists(const char *dpm)
190 {
191   struct stat st;
192   dirplusmake(dpm);
193   if (stat(dirplus.s,&st) == -1) {
194     if (errno == error_noent)
195       return 0;
196     strerr_die2sys(111,FATAL,MSG1(ERR_STAT,dirplus.s));
197   }
198   return 1;
199 }
200 
read_line(const char * dpm,stralloc * sa)201 int read_line(const char *dpm,stralloc *sa)
202 {
203   int fdin;
204   int match;
205   if (sa->len > 0)
206     return 0;
207   dirplusmake(dpm);
208   if ((fdin = open_read(dirplus.s)) == -1) {
209     if (errno != error_noent) die_read();
210     return -1;
211   } else {
212     substdio_fdbuf(&sstext,read,fdin,textbuf,sizeof(textbuf));
213     if (getln(&sstext,sa,&match,'\n') == -1) die_read();
214     sa->len -= match;
215     close(fdin);
216     return 0;
217   }
218 }
219 
read_files(void)220 void read_files(void)
221 {
222   if (euid > 0 && !flags['c' - 'a'])
223     read_line("/ezmlmrc",&cfname);
224   read_line("/dot",&dot);
225   read_line("/outlocal",&local);
226   read_line("/outhost",&host);
227   read_line("/digestcode",&code);
228   read_line("/sublist",&popt[0]);
229   read_line("/fromheader",&popt[3]);
230   read_line("/tstdigopts",&popt[4]);
231   read_line("/owner",&popt[5]);
232   if (read_line("/subdb",&popt[6]) != 0
233       && read_line("/sql",&line) == 0) {
234     if (!stralloc_copyb(&popt[6],"sql:",4)) die_nomem();
235     if (!stralloc_catb(&popt[6],line.s,line.len)) die_nomem();
236   }
237   read_line("/modpost",&popt[7]);
238   read_line("/modsub",&popt[8]);
239   read_line("/remote",&popt[9]);
240 }
241 
read_file_config(void)242 int read_file_config(void)
243 {
244   if (!stralloc_copys(&oldflags,"-ABCDEFGHIJKLMNOPqRSTUVWXYZ")) die_nomem();
245   oldflags.s[1] = "Aa"[exists("/archived")];
246   oldflags.s[2] = "Bb"[exists("/modgetonly")];
247   //oldflags.s[3] = "Cc"[exists("/ezmlmrc")]; /* Should always end up set */
248   oldflags.s[4] = "Dd"[exists("/digested")];
249   /* -e is not applicable */
250   oldflags.s[6] = "Ff"[exists("/prefix")];
251   oldflags.s[7] = "Gg"[exists("/subgetonly")];
252   oldflags.s[8] = "Hh"[exists("/nosubconfirm")];
253   oldflags.s[9] = "Ii"[exists("/threaded")];
254   oldflags.s[10] = "Jj"[exists("/nounsubconfirm")];
255   oldflags.s[11] = 'k';		/* -k is always enabled */
256   oldflags.s[12] = "Ll"[exists("/modcanlist")];
257   oldflags.s[13] = "Mm"[exists("/modpost")];
258   oldflags.s[14] = "Nn"[exists("/modcanedit")];
259   oldflags.s[15] = "Oo"[exists("/modpostonly")];
260   oldflags.s[16] = "Pp"[exists("/public")];
261   oldflags.s[17] = 'q';		/* -q is always enabled */
262   oldflags.s[18] = "Rr"[exists("/remote")];
263   oldflags.s[19] = "Ss"[exists("/modsub")];
264   oldflags.s[20] = "Tt"[exists("/addtrailer")];
265   oldflags.s[21] = "Uu"[exists("/subpostonly")];
266   /* -v is not applicable */
267   oldflags.s[23] = "Ww"[exists("/nowarn")];
268   oldflags.s[24] = "Xx"[exists("/mimeremove")];
269   oldflags.s[25] = "Yy"[exists("/confirmpost")];
270   /* -z is unused */
271   read_files();
272   return 1;
273 }
274 
read_flags_config(void)275 int read_flags_config(void)
276 {
277   if (read_line("/flags",&oldflags) != 0)
278     return 0;
279   read_files();
280   return 1;
281 }
282 
read_old_config(void)283 int read_old_config(void)
284 {
285   unsigned char ch;
286   int fdin;
287   int match;
288 
289   /* for edit, try to get args from dir/config */
290   dirplusmake("/config");
291   if ((fdin = open_read(dirplus.s)) == -1) {
292     if (errno != error_noent) die_read();
293     return 0;
294   } else {
295     substdio_fdbuf(&sstext,read,fdin,textbuf,sizeof(textbuf));
296     for (;;) {
297       if (getln(&sstext,&line,&match,'\n') == -1) die_read();
298       if (!match) break;
299       if (line.s[0] == '#') continue;
300       if (line.len == 1) break;
301       if (line.s[1] != ':') break;
302       line.s[line.len - 1] = '\0';
303       switch (ch = line.s[0]) {
304       case 'X':
305 	if (euid > 0 && !flags['c' - 'a'] && (cfname.len == 0))
306 	  if (!stralloc_copys(&cfname,line.s+2)) die_nomem();
307 	break;	/* for safety: ignore if root */
308       case 'T': if (!stralloc_copys(&dot,line.s+2)) die_nomem(); break;
309       case 'L': if (!stralloc_copys(&local,line.s+2)) die_nomem(); break;
310       case 'H': if (!stralloc_copys(&host,line.s+2)) die_nomem(); break;
311       case 'C': if (!stralloc_copys(&code,line.s+2)) die_nomem(); break;
312       case 'F': if (!stralloc_copys(&oldflags,line.s+2)) die_nomem(); break;
313       case 'D': break;	/* no reason to check */
314       default:
315 	if (ch == '0' || (ch >= '3' && ch <= '9')) {
316 	  if (usecfg && popt[ch - '0'].len == 0)
317 	    if (!stralloc_copys(&popt[ch-'0'],line.s+2)) die_nomem();
318 	} else
319 	  strerr_die4x(111,FATAL,MSG1(ERR_SYNTAX,dirplus.s),": ",line.s+2);
320 	break;
321       }
322     }
323     close(fdin);
324   }
325   return 1;
326 }
327 
open_template(stralloc * fn)328 static int open_template(stralloc *fn)
329 {
330   int fd;
331   struct stat st;
332   if (!stralloc_0(fn)) die_nomem();
333   if (stat(fn->s,&st) == -1)
334     strerr_die2sys(111,FATAL,MSG1(ERR_STAT,fn->s));
335   if (S_ISDIR(st.st_mode)) {
336     --fn->len;
337     if (!stralloc_cats(fn,TXT_EZMLMRC)) die_nomem();
338     if (!stralloc_0(fn)) die_nomem();
339   }
340   if ((fd = open_read(fn->s)) == -1) {
341     if (errno != error_noent)
342       strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn->s));
343     else
344       strerr_die3x(100,FATAL,template.s,MSG(ERR_NOEXIST));
345   }
346   return fd;
347 }
348 
main(int argc,char ** argv)349 int main(int argc,char **argv)
350 {
351   int opt;
352   int flagdo;
353   int flagnot;
354   int flagover;
355   int flagnotexist = 0;
356   int flagforce = 0;
357   int flagforce_p = 0;
358   int match;
359   unsigned int next,i,j;
360   int last;
361   unsigned int slpos,hashpos,pos;
362   int fdin,fdtmp;
363   const char *p;
364   unsigned char ch;
365 
366   keyadd((unsigned long) getpid());
367   keyadd((unsigned long) getppid());
368   euid = (unsigned long) geteuid();
369   keyadd(euid);
370   keyadd((unsigned long) getgid());
371   gettimeofday(&tv,(struct timezone *) 0);
372   keyadd(tv.tv_sec);
373 
374   (void) umask(077);
375 	/* flags with defined use. vV for version. Others free */
376 
377   for (pos = 0; pos < (unsigned int) NO_FLAGS; pos++) {
378     flags[pos] = 0;
379   }
380   for (pos = 0; pos < 10; ++pos)
381     popt[pos].len = 0;
382 
383   while ((opt = getopt(argc,argv,
384    "+aAbBcC:dDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0:3:4:5:6:7:8:9:"))
385            != opteof) {
386     if (opt == 'v' || opt == 'V')
387       strerr_die2x(0,"ezmlm-make version: ",auto_version);
388     if (opt =='C')	/* treat this like nl switch to allow override of -c*/
389       if (!stralloc_copys(&cfname,optarg)) die_nomem();
390     if (opt >= 'a' && opt <= 'z') {
391       flags[opt - 'a'] = 3;		/* Dominant "set" */
392       if (opt == 'e') flagforce++;	/* two 'e' => ignore 'E' */
393     } else if (opt >= 'A' && opt <= 'Z')
394       flags[opt - 'A'] = 2;		/* Dominant "unset" */
395     else if (opt >= '0' && opt <= '9') {
396       if (!stralloc_copys(&popt[opt-'0'],optarg)) die_nomem();
397     }
398     else if (opt == '+') {
399       flagforce_p++;		/* two '+' => ignore 'E' */
400       flags['e' - 'a'] = 3;	/* -+ implies -e */
401       usecfg = 1;
402     } else
403       die_usage();
404   }
405   argv += optind;
406 
407   if (flagforce_p > 1 || flagforce > 1)
408     flagforce = 1;
409   else
410     flagforce = 0;
411 
412   if (*argv == 0) die_usage();
413   if (!stralloc_copys(&dir,*argv++)) die_nomem();
414   if (dir.s[0] != '/') die_relative();
415   if (byte_chr(dir.s,dir.len,'\'') < dir.len) die_quote();
416   if (byte_chr(dir.s,dir.len,'\n') < dir.len) die_newline();
417 
418   if (flags['e' - 'a'] & 1) {
419     /* lock for edit */
420     dirplusmake("/lock");
421     lockfile(dirplus.s);
422     if (!read_flags_config())
423       if (!read_old_config())
424 	read_file_config();
425   }
426 
427   if ((p = *argv++) != 0) {
428     if (!stralloc_copys(&dot,p)) die_nomem();
429     if ((p = *argv++) != 0) {
430       if (local.len == 0 || str_diff(local.s,p))
431 	flagforce = 1;		/* must rewrite if list name changed */
432       if (!stralloc_copys(&local,p)) die_nomem();
433       if ((p = *argv++) != 0) {
434 	if (host.len == 0 || str_diff(host.s,p))
435 	  flagforce = 1;	/* must rewrite if list name changed */
436 	if (!stralloc_copys(&host,p)) die_nomem();
437 	if ((p = *argv++) != 0) {
438 	  if (!stralloc_copys(&code,p)) die_nomem();
439         }
440       }
441     }
442   }
443   if (dot.len == 0 || local.len == 0 || host.len == 0) die_usage();
444   if (dot.s[0] != '/') die_relative();		/* force absolute dot */
445 
446 			/* use flags from config, overridden with new values */
447 			/* if there are old flags, we're in "edit" and "-+" */
448 			/* Previous versions only wrote _set_ flags to */
449 			/* to DIR/confiag. We need to make sure that we */
450 			/* don't apply the defaults for non-specified ones! */
451   if (usecfg && oldflags.len > 0 && flags['e' - 'a']) {
452     for (p = oldflags.s, i = oldflags.len; ch = *p, i > 0; ++p, --i) {
453       if (ch >= 'a' && ch <= 'z') {		/* unset flags ignored */
454         if (ch != 'e')
455           if (!flags[ch - 'a'])			/* cmd line overrides */
456 	    flags[ch - 'a'] = 1;
457       }
458     }
459   }
460 
461   if (!usecfg) {				/* apply defaults */
462     while (( ch = *(defflags++))) {		/* gets used up! */
463       if (ch >= 'a' && ch <= 'z') {		/* defensive! */
464 	if (!flags[ch - 'a'])			/* cmdline still overrides */
465 	  flags[ch - 'a'] = 1;
466       }
467     }
468   }
469 
470   for (pos = 0; pos < (unsigned int) NO_FLAGS; pos++) {	/* set real flags */
471     if (flags[pos] & 2)				/* 2 = "dominant" 0 */
472       flags[pos] = flags[pos] & 1;		/* 3 = "dominant" 1 */
473   }
474 
475   if (byte_chr(local.s,local.len,'\n') < local.len) die_newline();
476   if (byte_chr(host.s,host.len,'\n') < host.len) die_newline();
477 
478 	/* build 'f' for <#F#> */
479   if (!stralloc_ready(&f,28)) die_nomem();
480   if (!stralloc_copys(&f,"-")) die_nomem();
481   for (ch = 0; ch <= 'z' - 'a'; ch++) {		/* build string with flags */
482     if (flags[ch])
483       sz = 'a' + ch;
484     else
485       sz = 'A' + ch;
486     if (!stralloc_append(&f,sz)) die_nomem();
487   }
488 
489   fdin = -1;	/* assure failure for .ezmlmrc in case flags['c'-'a'] = 0 */
490   slpos = dot.len;
491   while ((--slpos > 0) && dot.s[slpos] != '/');
492   if (dot.s[slpos] == '/') {
493     if (!stralloc_copyb(&template,dot.s,slpos+1)) die_nomem();	/* dot dir */
494     slpos += byte_chr(dot.s+slpos,dot.len-slpos,'-');
495     if (dot.s[slpos]) {
496       slpos++;
497       if (slpos < dot.len
498 	  && (pos = slpos+byte_chr(dot.s+slpos,dot.len-slpos,'-')) < dot.len) {
499         if (!stralloc_copyb(&popt[1],dot.s+slpos,pos-slpos)) die_nomem();
500         pos++;
501 	if (pos < dot.len
502 	    && (slpos = pos+byte_chr(dot.s+pos,dot.len-pos,'-')) < dot.len)
503           if (!stralloc_copyb(&popt[2],dot.s+pos,slpos-pos)) die_nomem();
504       }
505     }
506   }
507 	/* if 'c', template already has the dot directory. If 'C', cfname */
508 	/* (if exists and != '') points to the file name to use instead. */
509   if (flags['c'-'a'] || (cfname.len > 0)) {
510     if (!flags['c'-'a']) {	/* i.e. there is a cfname specified */
511       if (!stralloc_copy(&template,&cfname)) die_nomem();
512     } else
513       if (!stralloc_cats(&template,TXT_DOTEZMLMRC)) die_nomem();
514     fdin = open_template(&template);
515   } else {
516     /* /etc/ezmlm/default/ezmlmrc */
517     if (!stralloc_copys(&template,auto_etc())) die_nomem();
518     if (!stralloc_cats(&template,TXT_DEFAULT)) die_nomem();
519     fdin = open_template(&template);
520   }
521 
522   dcreate("");		/* This is all we do, the rest is up to ezmlmrc */
523 			/* do it after opening template to avoid aborts */
524 			/* with created DIR. Well we also write DIR/key */
525 			/* at the end except in -e[dit] mode.           */
526 
527   substdio_fdbuf(&sstext,read,fdin,textbuf,sizeof(textbuf));
528   if (!stralloc_0(&oldfname)) die_nomem();		/* init oldfname */
529   flagdo = 0;
530 
531   if (getln(&sstext,&line,&match,'\n') == -1)
532     strerr_die2sys(111,FATAL,MSG1(ERR_READ,template.s));
533   if (!match)
534     strerr_die2sys(111,FATAL,MSG1(ERR_READ,template.s));
535   i = str_rchr(auto_version,'-');			/* check version */
536   if (auto_version[i]) i++;
537   j = 0;
538   while (line.s[j] == auto_version[i] && j < line.len &&
539 		auto_version[i] != '.' && auto_version[i]) {
540     i++; j++;						/* major */
541   }							/* first minor */
542   if (auto_version[i] != '.' || j + 1 >= line.len ||
543 		auto_version[i+1] != line.s[j+1])
544     strerr_warn2(WARNING,MSG(ERR_VERSION), (struct strerr *) 0);
545 
546   for (;;) {
547     if (getln(&sstext,&line,&match,'\n') == -1)
548       strerr_die2sys(111,FATAL,MSG1(ERR_READ,template.s));
549     if (!match)
550       break;
551     if (line.s[0] == '#')				/* comment */
552       continue;
553     if (!stralloc_0(&line)) die_nomem();
554     if (line.s[0] == '<' && line.s[1] == '/') {		/* tag */
555       if (byte_chr(line.s,line.len,'.') < line.len)
556 	strerr_die4x(100,FATAL,MSG(ERR_PERIOD),": ",line.s);
557       flagdo = 1;
558       flagover = 0;
559       hashpos = 0;
560       pos = byte_chr(line.s+2,line.len-2,'#')+2;
561       if (pos < line.len-2) {
562         hashpos = pos;
563         pos++;
564         flagnot = 0;
565         while (pos < line.len
566 	       && ((ch = line.s[pos]) != '/' && line.s[pos+1] != '>')) {
567           if (ch == '^') {
568             flagnot = 1;
569             pos++;
570             continue;
571           }
572 			/* E is ignored. For files => create unless exists */
573 	  if ((ch == 'E' && !flagnot) || (ch == 'e' && flagnot)) {
574 	    if (flags['e' - 'a'] && !flagforce)
575 	      flagover = 1;		/* ignore #E & #^e, but set flagover */
576           } else if (ch >= 'a' && ch <= 'z')
577             flagdo &= (flags[ch - 'a'] ^ flagnot);
578           else if (ch >= 'A' && ch <= 'Z')
579             flagdo &= !(flags[ch - 'A'] ^ flagnot);
580           else if (ch >= '0' && ch <= '9')
581             flagdo &= (popt[ch - '0'].len > 0) ^flagnot;
582           flagnot = 0;
583           pos++;
584         }
585         if (pos < line.len
586 	    && (line.s[pos] != '/' || line.s[pos+1] != '>'))
587           strerr_die4x(100,FATAL,MSG(ERR_ENDTAG),": ",line.s);
588       } else {
589         flagdo = 1;
590         pos = 2;	/* name needs to be >= 1 char */
591         while ((pos = byte_chr(line.s+pos,line.len-pos,'/')+pos) < line.len) {
592           if (line.s[pos+1] == '>')
593             break;
594           pos++;
595         }
596         if (pos >= line.len)
597           strerr_die4x(100,FATAL,MSG(ERR_ENDTAG),": ",line.s);
598       }
599       if (hashpos)
600         pos = hashpos;	/* points to after file name */
601 
602       if (line.s[2] == '+') {			/* mkdir */
603         if (!flagdo)
604           continue;
605         if (!stralloc_copys(&dname,"/")) die_nomem();
606         if (!stralloc_catb(&dname,line.s+3,pos-3)) die_nomem();
607         if (!stralloc_0(&dname)) die_nomem();
608         dcreate(dname.s);
609         flagdo = 0;
610         continue;
611       } else if (line.s[2] == ':') {		/* ln -s */
612         if (!flagdo)
613           continue;
614         slpos = byte_chr(line.s+3,line.len-3,'/') + 3;
615         if (slpos >= pos)
616           strerr_die2x(100,FATAL,MSG1(ERR_LINKDIR,line.s));
617         if (!stralloc_copyb(&dname,line.s+slpos,pos-slpos)) die_nomem();
618         if (!stralloc_copyb(&lname,line.s+3,slpos-3)) die_nomem();
619         if (!stralloc_0(&dname)) die_nomem();
620         if (!stralloc_0(&lname)) die_nomem();
621         linkdotdir(lname.s,dname.s);
622         flagdo = 0;
623         continue;
624       } else if (line.s[2] == '-') {		/* rm */
625         if (!flagdo)
626           continue;
627         if (!stralloc_copys(&dname,"/")) die_nomem();
628         if (!stralloc_catb(&dname,line.s+3,pos-3)) die_nomem();
629         if (!stralloc_0(&dname)) die_nomem();
630         frm(dname.s);
631         flagdo = 0;
632         continue;
633       }
634 						/* only plain files left */
635 						/* first get file name */
636       if (pos > 2) {			/* </#ai/> => add to open file */
637         if (!stralloc_copyb(&fname,line.s+1,pos-1)) die_nomem();
638         if (!stralloc_0(&fname)) die_nomem();
639       }
640 
641       if (str_diff(fname.s, oldfname.s)) {
642 	flagnotexist = 1;
643 			/* Treat special case of #E when editing which _should*/
644 			/* write only if the file does not exist. flagover */
645 			/* is set if we need to check */
646         if (flagover) {	/* skip if exists */
647 	  dirplusmake(fname.s);		/* decided by FIRST tag for file */
648 	  fdtmp = open_read(dirplus.s);
649 	  if (fdtmp == -1) {
650 	    if (errno != error_noent)
651 	      strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,dirplus.s));
652           } else {
653 	    flagnotexist = 0;		/* already there - don't do it */
654 	    close(fdtmp);
655 	  }
656         }
657         if (oldfname.len > 1) {
658           f_close();
659           if (!stralloc_copys(&oldfname,"")) die_nomem();
660           if (!stralloc_0(&oldfname)) die_nomem();
661           }
662           if (flagdo && flagnotexist) {
663             if (!fname.len)
664               strerr_die2x(100,FATAL,MSG1(ERR_FILENAME,line.s));
665             f_open(fname.s);
666            if (!stralloc_copy(&oldfname,&fname)) die_nomem();
667           }
668         }
669 	if (flagdo) flagdo = flagnotexist;
670         continue;
671     } else if (!flagdo)
672       continue;			/* part not to go out */
673     last = -1;
674     next = 0;
675     outline.len = 0;
676     for (;;) {
677       if (next < line.len &&
678 	  (pos = next + byte_chr(line.s+next,line.len-next,'<')) < line.len &&
679           line.s[pos+1] == '#' &&
680           line.s[pos+2] &&
681           line.s[pos+3] == '#' &&
682           line.s[pos+4] == '>') {	/* host/local */
683         if (!stralloc_catb(&outline,line.s+last+1,pos-last-1))
684                 die_nomem();
685         switch (line.s[pos+2]) {
686           case 'B':		/* path to ezmlm binaries (no trailing /) */
687             if (!stralloc_cats(&outline,auto_bin())) die_nomem();
688             last = pos + 4; next = pos + 5; break;
689           case 'C':		/* digestcode */
690 	    if (!stralloc_cat(&outline,&code)) die_nomem();
691             last = pos + 4; next = pos + 5; break;
692           case 'D':		/* listdir */
693             if (!stralloc_cat(&outline,&dir)) die_nomem();
694             last = pos + 4; next = pos + 5; break;
695           case 'F':		/* flags */
696             if (!stralloc_cat(&outline,&f)) die_nomem();
697             last = pos + 4; next = pos + 5; break;
698           case 'H':		/* hostname */
699             if (!stralloc_cat(&outline,&host)) die_nomem();
700             last = pos + 4; next = pos + 5; break;
701           case 'L':		/* local */
702             if (!stralloc_cat(&outline,&local)) die_nomem();
703             last = pos + 4; next = pos + 5; break;
704           case 'T':		/* dot */
705             if (!stralloc_cat(&outline,&dot)) die_nomem();
706             last = pos + 4; next = pos + 5; break;
707           case 'X':		/* config file name */
708 	    if (!stralloc_cat(&outline,&cfname)) die_nomem();
709             last = pos + 4; next = pos + 5; break;
710           default:		/* copy unknown tag as is for e.g. <#A#> and*/
711 				/* <#R#> to be processed by -manage/store   */
712                                 /* stuff in args for <#0#> .. <#9#> */
713             if ((line.s[pos+2] >= '0') && (line.s[pos+2] <= '9')) {
714 	      if (!stralloc_cat(&outline,&popt[line.s[pos+2]-'0']))
715 		die_nomem();
716             } else
717               if (!stralloc_catb(&outline,line.s+pos,5)) die_nomem();
718             last = pos + 4; next = pos + 5; break;
719         }
720       } else {			/* not tag */
721 	if (pos < line.len) {
722           next++;
723         } else {
724           if (!stralloc_catb(&outline,line.s+last+1,line.len-last-1))
725             die_nomem();
726           f_puts(outline.s);
727           break;
728         }
729       }
730     }
731   }
732 
733   close(fdin);
734   if (oldfname.len > 1)
735     f_close();
736 
737   if (!flags['e' - 'a']) {	/* don't redo key when editing a list */
738     f_open("/key");
739     f_put(key.s,key.len);
740     f_close();
741   }
742 
743   if (!stralloc_0(&dir)) die_nomem();
744   wrap_chdir(dir.s);
745   if (popt[6].len > 0) {
746     if (!stralloc_0(&popt[6])) die_nomem();
747     initsub(popt[6].s);
748   }
749   else
750     initsub(0);
751   if ((p = mktab()) != NULL)
752     strerr_die4x(100,FATAL,MSG(ERR_MKTAB),": ",p);
753   _exit(0);
754 }
755