1 /*
2  * mirror manager.
3  * a work in progress.
4  * use at your own risk.
5  */
6 
7 #include "stdinc.h"
8 #include <regexp.h>
9 #include <bio.h>
10 #include "dat.h"
11 #include "fns.h"
12 
13 #ifdef PLAN9PORT
14 #define sp s.sp
15 #define ep e.ep
16 #endif
17 
18 void sendmail(char *content, char *subject, char *msg);
19 #define TIME "[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+"
20 
21 char *mirrorregexp =
22 	"^" TIME " ("
23 		"([^ ]+ \\([0-9,]+-[0-9,]+\\))"
24 		"|(  copy [0-9,]+-[0-9,]+ (data|hole|directory|tail))"
25 		"|(  sha1 [0-9,]+-[0-9,]+)"
26 		"|([^ ]+: [0-9,]+ used mirrored)"
27 		"|([^ \\-]+-[^ \\-]+( mirrored| sealed| empty)+)"
28 	")$";
29 Reprog *mirrorprog;
30 
31 char *verifyregexp =
32 	"^" TIME " ("
33 		"([^ ]+: unsealed [0-9,]+ bytes)"
34 	")$";
35 Reprog *verifyprog;
36 
37 #undef pipe
38 enum
39 {
40 	LogSize = 4*1024*1024	// TODO: make smaller
41 };
42 
43 VtLog *errlog;
44 
45 typedef struct Mirror Mirror;
46 struct Mirror
47 {
48 	char *src;
49 	char *dst;
50 };
51 
52 typedef struct Conf Conf;
53 struct Conf
54 {
55 	Mirror *mirror;
56 	int nmirror;
57 	char **verify;
58 	int nverify;
59 	char *httpaddr;
60 	char *webroot;
61 	char *smtp;
62 	char *mailfrom;
63 	char *mailto;
64 	int mirrorfreq;
65 	int verifyfreq;
66 };
67 
68 typedef struct Job Job;
69 struct Job
70 {
71 	char *name;
72 	QLock lk;
73 	char *argv[10];
74 	int oldok;
75 	int newok;
76 	VtLog *oldlog;
77 	VtLog *newlog;
78 	int pid;
79 	int pipe;
80 	int nrun;
81 	vlong freq;
82 	vlong runstart;
83 	vlong runend;
84 	double offset;
85 	int (*ok)(char*);
86 };
87 
88 Job *job;
89 int njob;
90 char *bin;
91 
92 vlong time0;
93 Conf conf;
94 
95 void
usage(void)96 usage(void)
97 {
98 	fprint(2, "usage: mgr [-s] [-b bin/venti/] venti.conf\n");
99 	threadexitsall(0);
100 }
101 
102 int
rdconf(char * file,Conf * conf)103 rdconf(char *file, Conf *conf)
104 {
105 	char *s, *line, *flds[10];
106 	int i, ok;
107 	IFile f;
108 
109 	if(readifile(&f, file) < 0)
110 		return -1;
111 	memset(conf, 0, sizeof *conf);
112 	ok = -1;
113 	line = nil;
114 	for(;;){
115 		s = ifileline(&f);
116 		if(s == nil){
117 			ok = 0;
118 			break;
119 		}
120 		line = estrdup(s);
121 		i = getfields(s, flds, nelem(flds), 1, " \t\r");
122 		if(i <= 0 || strcmp(flds[0], "mgr") != 0) {
123 			/* do nothing */
124 		}else if(i == 4 && strcmp(flds[1], "mirror") == 0) {
125 			if(conf->nmirror%64 == 0)
126 				conf->mirror = vtrealloc(conf->mirror, (conf->nmirror+64)*sizeof(conf->mirror[0]));
127 			conf->mirror[conf->nmirror].src = vtstrdup(flds[2]);
128 			conf->mirror[conf->nmirror].dst = vtstrdup(flds[3]);
129 			conf->nmirror++;
130 		}else if(i == 3 && strcmp(flds[1], "mirrorfreq") == 0) {
131 			conf->mirrorfreq = atoi(flds[2]);
132 		}else if(i == 3 && strcmp(flds[1], "verify") == 0) {
133 			if(conf->nverify%64 == 0)
134 				conf->verify = vtrealloc(conf->verify, (conf->nverify+64)*sizeof(conf->verify[0]));
135 			conf->verify[conf->nverify++] = vtstrdup(flds[2]);
136 		}else if(i == 3 && strcmp(flds[1], "verifyfreq") == 0) {
137 			conf->verifyfreq = atoi(flds[2]);
138 		}else if(i == 3 && strcmp(flds[1], "httpaddr") == 0){
139 			if(conf->httpaddr){
140 				seterr(EAdmin, "duplicate httpaddr lines in configuration file %s", file);
141 				break;
142 			}
143 			conf->httpaddr = estrdup(flds[2]);
144 		}else if(i == 3 && strcmp(flds[1], "webroot") == 0){
145 			if(conf->webroot){
146 				seterr(EAdmin, "duplicate webroot lines in configuration file %s", file);
147 				break;
148 			}
149 			conf->webroot = estrdup(flds[2]);
150 		}else if(i == 3 && strcmp(flds[1], "smtp") == 0) {
151 			if(conf->smtp){
152 				seterr(EAdmin, "duplicate smtp lines in configuration file %s", file);
153 				break;
154 			}
155 			conf->smtp = estrdup(flds[2]);
156 		}else if(i == 3 && strcmp(flds[1], "mailfrom") == 0) {
157 			if(conf->mailfrom){
158 				seterr(EAdmin, "duplicate mailfrom lines in configuration file %s", file);
159 				break;
160 			}
161 			conf->mailfrom = estrdup(flds[2]);
162 		}else if(i == 3 && strcmp(flds[1], "mailto") == 0) {
163 			if(conf->mailto){
164 				seterr(EAdmin, "duplicate mailto lines in configuration file %s", file);
165 				break;
166 			}
167 			conf->mailto = estrdup(flds[2]);
168 		}else{
169 			seterr(EAdmin, "illegal line '%s' in configuration file %s", line, file);
170 			break;
171 		}
172 		free(line);
173 		line = nil;
174 	}
175 	free(line);
176 	freeifile(&f);
177 	return ok;
178 }
179 
180 static QLock loglk;
181 static char *logbuf;
182 
183 char*
logtext(VtLog * l)184 logtext(VtLog *l)
185 {
186 	int i;
187 	char *p;
188 	VtLogChunk *c;
189 
190 	p = logbuf;
191 	c = l->w;
192 	for(i=0; i<l->nchunk; i++) {
193 		if(++c == l->chunk+l->nchunk)
194 			c = l->chunk;
195 		memmove(p, c->p, c->wp - c->p);
196 		p += c->wp - c->p;
197 	}
198 	*p = 0;
199 	return logbuf;
200 }
201 
202 
203 typedef struct HttpObj	HttpObj;
204 
205 static int fromwebdir(HConnect*);
206 
207 enum
208 {
209 	ObjNameSize	= 64,
210 	MaxObjs		= 64
211 };
212 
213 struct HttpObj
214 {
215 	char	name[ObjNameSize];
216 	int	(*f)(HConnect*);
217 };
218 
219 static HttpObj	objs[MaxObjs];
220 static void httpproc(void*);
221 
222 static HConnect*
mkconnect(void)223 mkconnect(void)
224 {
225 	HConnect *c;
226 
227 	c = mallocz(sizeof(HConnect), 1);
228 	if(c == nil)
229 		sysfatal("out of memory");
230 	c->replog = nil;
231 	c->hpos = c->header;
232 	c->hstop = c->header;
233 	return c;
234 }
235 
236 static int
preq(HConnect * c)237 preq(HConnect *c)
238 {
239 	if(hparseheaders(c, 0) < 0)
240 		return -1;
241 	if(strcmp(c->req.meth, "GET") != 0
242 	&& strcmp(c->req.meth, "HEAD") != 0)
243 		return hunallowed(c, "GET, HEAD");
244 	if(c->head.expectother || c->head.expectcont)
245 		return hfail(c, HExpectFail, nil);
246 	return 0;
247 }
248 
249 int
hsettype(HConnect * c,char * type)250 hsettype(HConnect *c, char *type)
251 {
252 	Hio *hout;
253 	int r;
254 
255 	r = preq(c);
256 	if(r < 0)
257 		return r;
258 
259 	hout = &c->hout;
260 	if(c->req.vermaj){
261 		hokheaders(c);
262 		hprint(hout, "Content-type: %s\r\n", type);
263 		if(http11(c))
264 			hprint(hout, "Transfer-Encoding: chunked\r\n");
265 		hprint(hout, "\r\n");
266 	}
267 
268 	if(http11(c))
269 		hxferenc(hout, 1);
270 	else
271 		c->head.closeit = 1;
272 	return 0;
273 }
274 
275 int
hsethtml(HConnect * c)276 hsethtml(HConnect *c)
277 {
278 	return hsettype(c, "text/html; charset=utf-8");
279 }
280 
281 int
hsettext(HConnect * c)282 hsettext(HConnect *c)
283 {
284 	return hsettype(c, "text/plain; charset=utf-8");
285 }
286 
287 int
hnotfound(HConnect * c)288 hnotfound(HConnect *c)
289 {
290 	int r;
291 
292 	r = preq(c);
293 	if(r < 0)
294 		return r;
295 	return hfail(c, HNotFound, c->req.uri);
296 }
297 
298 static int
xloglist(HConnect * c)299 xloglist(HConnect *c)
300 {
301 	if(hsettype(c, "text/html") < 0)
302 		return -1;
303 	vtloghlist(&c->hout);
304 	hflush(&c->hout);
305 	return 0;
306 }
307 
308 static int
strpcmp(const void * va,const void * vb)309 strpcmp(const void *va, const void *vb)
310 {
311 	return strcmp(*(char**)va, *(char**)vb);
312 }
313 
314 void
vtloghlist(Hio * h)315 vtloghlist(Hio *h)
316 {
317 	char **p;
318 	int i, n;
319 
320 	hprint(h, "<html><head>\n");
321 	hprint(h, "<title>Venti Server Logs</title>\n");
322 	hprint(h, "</head><body>\n");
323 	hprint(h, "<b>Venti Server Logs</b>\n<p>\n");
324 
325 	p = vtlognames(&n);
326 	qsort(p, n, sizeof(p[0]), strpcmp);
327 	for(i=0; i<n; i++)
328 		hprint(h, "<a href=\"/log?log=%s\">%s</a><br>\n", p[i], p[i]);
329 	vtfree(p);
330 	hprint(h, "</body></html>\n");
331 }
332 
333 void
vtloghdump(Hio * h,VtLog * l)334 vtloghdump(Hio *h, VtLog *l)
335 {
336 	int i;
337 	VtLogChunk *c;
338 	char *name;
339 
340 	name = l ? l->name : "&lt;nil&gt;";
341 
342 	hprint(h, "<html><head>\n");
343 	hprint(h, "<title>Venti Server Log: %s</title>\n", name);
344 	hprint(h, "</head><body>\n");
345 	hprint(h, "<b>Venti Server Log: %s</b>\n<p>\n", name);
346 
347 	if(l){
348 		c = l->w;
349 		for(i=0; i<l->nchunk; i++){
350 			if(++c == l->chunk+l->nchunk)
351 				c = l->chunk;
352 			hwrite(h, c->p, c->wp-c->p);
353 		}
354 	}
355 	hprint(h, "</body></html>\n");
356 }
357 
358 
359 char*
hargstr(HConnect * c,char * name,char * def)360 hargstr(HConnect *c, char *name, char *def)
361 {
362 	HSPairs *p;
363 
364 	for(p=c->req.searchpairs; p; p=p->next)
365 		if(strcmp(p->s, name) == 0)
366 			return p->t;
367 	return def;
368 }
369 
370 static int
xlog(HConnect * c)371 xlog(HConnect *c)
372 {
373 	char *name;
374 	VtLog *l;
375 
376 	name = hargstr(c, "log", "");
377 	if(!name[0])
378 		return xloglist(c);
379 	l = vtlogopen(name, 0);
380 	if(l == nil)
381 		return hnotfound(c);
382 	if(hsettype(c, "text/html") < 0){
383 		vtlogclose(l);
384 		return -1;
385 	}
386 	vtloghdump(&c->hout, l);
387 	vtlogclose(l);
388 	hflush(&c->hout);
389 	return 0;
390 }
391 
392 static void
httpdproc(void * vaddress)393 httpdproc(void *vaddress)
394 {
395 	HConnect *c;
396 	char *address, ndir[NETPATHLEN], dir[NETPATHLEN];
397 	int ctl, nctl, data;
398 
399 	address = vaddress;
400 	ctl = announce(address, dir);
401 	if(ctl < 0){
402 		sysfatal("announce %s: %r", address);
403 		return;
404 	}
405 
406 	if(0) print("announce ctl %d dir %s\n", ctl, dir);
407 	for(;;){
408 		/*
409 		 *  wait for a call (or an error)
410 		 */
411 		nctl = listen(dir, ndir);
412 		if(0) print("httpd listen %d %s...\n", nctl, ndir);
413 		if(nctl < 0){
414 			fprint(2, "mgr: httpd can't listen on %s: %r\n", address);
415 			return;
416 		}
417 
418 		data = accept(ctl, ndir);
419 		if(0) print("httpd accept %d...\n", data);
420 		if(data < 0){
421 			fprint(2, "mgr: httpd accept: %r\n");
422 			close(nctl);
423 			continue;
424 		}
425 		if(0) print("httpd close nctl %d\n", nctl);
426 		close(nctl);
427 		c = mkconnect();
428 		hinit(&c->hin, data, Hread);
429 		hinit(&c->hout, data, Hwrite);
430 		vtproc(httpproc, c);
431 	}
432 }
433 
434 static void
httpproc(void * v)435 httpproc(void *v)
436 {
437 	HConnect *c;
438 	int ok, i, n;
439 
440 	c = v;
441 
442 	for(;;){
443 		/*
444 		 * No timeout because the signal appears to hit every
445 		 * proc, not just us.
446 		 */
447 		if(hparsereq(c, 0) < 0)
448 			break;
449 
450 		for(i = 0; i < MaxObjs && objs[i].name[0]; i++){
451 			n = strlen(objs[i].name);
452 			if((objs[i].name[n-1] == '/' && strncmp(c->req.uri, objs[i].name, n) == 0)
453 			|| (objs[i].name[n-1] != '/' && strcmp(c->req.uri, objs[i].name) == 0)){
454 				ok = (*objs[i].f)(c);
455 				goto found;
456 			}
457 		}
458 		ok = fromwebdir(c);
459 	found:
460 		hflush(&c->hout);
461 		if(c->head.closeit)
462 			ok = -1;
463 		hreqcleanup(c);
464 
465 		if(ok < 0)
466 			break;
467 	}
468 	hreqcleanup(c);
469 	close(c->hin.fd);
470 	free(c);
471 }
472 
473 static int
httpdobj(char * name,int (* f)(HConnect *))474 httpdobj(char *name, int (*f)(HConnect*))
475 {
476 	int i;
477 
478 	if(name == nil || strlen(name) >= ObjNameSize)
479 		return -1;
480 	for(i = 0; i < MaxObjs; i++){
481 		if(objs[i].name[0] == '\0'){
482 			strcpy(objs[i].name, name);
483 			objs[i].f = f;
484 			return 0;
485 		}
486 		if(strcmp(objs[i].name, name) == 0)
487 			return -1;
488 	}
489 	return -1;
490 }
491 
492 
493 struct {
494 	char *ext;
495 	char *type;
496 } exttab[] = {
497 	".html",	"text/html",
498 	".txt",	"text/plain",
499 	".xml",	"text/xml",
500 	".png",	"image/png",
501 	".gif",	"image/gif",
502 	0
503 };
504 
505 static int
fromwebdir(HConnect * c)506 fromwebdir(HConnect *c)
507 {
508 	char buf[4096], *p, *ext, *type;
509 	int i, fd, n, defaulted;
510 	Dir *d;
511 
512 	if(conf.webroot == nil || strstr(c->req.uri, ".."))
513 		return hnotfound(c);
514 	snprint(buf, sizeof buf-20, "%s/%s", conf.webroot, c->req.uri+1);
515 	defaulted = 0;
516 reopen:
517 	if((fd = open(buf, OREAD)) < 0)
518 		return hnotfound(c);
519 	d = dirfstat(fd);
520 	if(d == nil){
521 		close(fd);
522 		return hnotfound(c);
523 	}
524 	if(d->mode&DMDIR){
525 		if(!defaulted){
526 			defaulted = 1;
527 			strcat(buf, "/index.html");
528 			free(d);
529 			close(fd);
530 			goto reopen;
531 		}
532 		free(d);
533 		return hnotfound(c);
534 	}
535 	free(d);
536 	p = buf+strlen(buf);
537 	type = "application/octet-stream";
538 	for(i=0; exttab[i].ext; i++){
539 		ext = exttab[i].ext;
540 		if(p-strlen(ext) >= buf && strcmp(p-strlen(ext), ext) == 0){
541 			type = exttab[i].type;
542 			break;
543 		}
544 	}
545 	if(hsettype(c, type) < 0){
546 		close(fd);
547 		return 0;
548 	}
549 	while((n = read(fd, buf, sizeof buf)) > 0)
550 		if(hwrite(&c->hout, buf, n) < 0)
551 			break;
552 	close(fd);
553 	hflush(&c->hout);
554 	return 0;
555 }
556 
557 static int
hmanager(HConnect * c)558 hmanager(HConnect *c)
559 {
560 	Hio *hout;
561 	int r;
562 	int i, k;
563 	Job *j;
564 	VtLog *l;
565 	VtLogChunk *ch;
566 
567 	r = hsethtml(c);
568 	if(r < 0)
569 		return r;
570 
571 	hout = &c->hout;
572 	hprint(hout, "<html><head><title>venti mgr status</title></head>\n");
573 	hprint(hout, "<body><h2>venti mgr status</h2>\n");
574 
575 	for(i=0; i<njob; i++) {
576 		j = &job[i];
577 		hprint(hout, "<b>");
578 		if(j->nrun == 0)
579 			hprint(hout, "----/--/-- --:--:--");
580 		else
581 			hprint(hout, "%+T", (long)(j->runstart + time0));
582 		hprint(hout, " %s", j->name);
583 		if(j->nrun > 0) {
584 			if(j->newok == -1) {
585 				hprint(hout, " (running)");
586 			} else if(!j->newok) {
587 				hprint(hout, " <font color=\"#cc0000\">(FAILED)</font>");
588 			}
589 		}
590 		hprint(hout, "</b>\n");
591 		hprint(hout, "<font size=-1><pre>\n");
592 		l = j->newlog;
593 		ch = l->w;
594 		for(k=0; k<l->nchunk; k++){
595 			if(++ch == l->chunk+l->nchunk)
596 				ch = l->chunk;
597 			hwrite(hout, ch->p, ch->wp-ch->p);
598 		}
599 		hprint(hout, "</pre></font>\n");
600 		hprint(hout, "\n");
601 	}
602 	hprint(hout, "</body></html>\n");
603 	hflush(hout);
604 	return 0;
605 }
606 
607 void
piper(void * v)608 piper(void *v)
609 {
610 	Job *j;
611 	char buf[512];
612 	VtLog *l;
613 	int n;
614 	int fd;
615 	char *p;
616 	int ok;
617 
618 	j = v;
619 	fd = j->pipe;
620 	l = j->newlog;
621 	while((n = read(fd, buf, 512-1)) > 0) {
622 		buf[n] = 0;
623 		if(l != nil)
624 			vtlogprint(l, "%s", buf);
625 	}
626 	qlock(&loglk);
627 	p = logtext(l);
628 	ok = j->ok(p);
629 	qunlock(&loglk);
630 	j->newok = ok;
631 	close(fd);
632 }
633 
634 void
kickjob(Job * j)635 kickjob(Job *j)
636 {
637 	int i;
638 	int fd[3];
639 	int p[2];
640 	VtLog *l;
641 
642 	if((fd[0] = open("/dev/null", ORDWR)) < 0) {
643 		vtlogprint(errlog, "%T open /dev/null: %r\n");
644 		return;
645 	}
646 	if(pipe(p) < 0) {
647 		vtlogprint(errlog, "%T pipe: %r\n");
648 		close(fd[0]);
649 		return;
650 	}
651 	qlock(&j->lk);
652 	l = j->oldlog;
653 	j->oldlog = j->newlog;
654 	j->newlog = l;
655 	qlock(&l->lk);
656 	for(i=0; i<l->nchunk; i++)
657 		l->chunk[i].wp = l->chunk[i].p;
658 	qunlock(&l->lk);
659 	j->oldok = j->newok;
660 	j->newok = -1;
661 	qunlock(&j->lk);
662 
663 	fd[1] = p[1];
664 	fd[2] = p[1];
665 	j->pid = threadspawn(fd, j->argv[0], j->argv);
666 	if(j->pid < 0) {
667 		vtlogprint(errlog, "%T exec %s: %r\n", j->argv[0]);
668 		close(fd[0]);
669 		close(fd[1]);
670 		close(p[0]);
671 	}
672 	// fd[0], fd[1], fd[2] are closed now
673 	j->pipe = p[0];
674 	j->nrun++;
675 	vtproc(piper, j);
676 }
677 
678 int
getline(Resub * text,Resub * line)679 getline(Resub *text, Resub *line)
680 {
681 	char *p;
682 
683 	if(text->sp >= text->ep)
684 		return -1;
685 	line->sp = text->sp;
686 	p = memchr(text->sp, '\n', text->ep - text->sp);
687 	if(p == nil) {
688 		line->ep = text->ep;
689 		text->sp = text->ep;
690 	} else {
691 		line->ep = p;
692 		text->sp = p+1;
693 	}
694 	return 0;
695 }
696 
697 int
verifyok(char * output)698 verifyok(char *output)
699 {
700 	Resub text, line, m;
701 
702 	text.sp = output;
703 	text.ep = output+strlen(output);
704 	while(getline(&text, &line) >= 0) {
705 		*line.ep = 0;
706 		memset(&m, 0, sizeof m);
707 		if(!regexec(verifyprog, line.sp, nil, 0))
708 			return 0;
709 		*line.ep = '\n';
710 	}
711 	return 1;
712 }
713 
714 int
mirrorok(char * output)715 mirrorok(char *output)
716 {
717 	Resub text, line, m;
718 
719 	text.sp = output;
720 	text.ep = output+strlen(output);
721 	while(getline(&text, &line) >= 0) {
722 		*line.ep = 0;
723 		memset(&m, 0, sizeof m);
724 		if(!regexec(mirrorprog, line.sp, nil, 0))
725 			return 0;
726 		*line.ep = '\n';
727 	}
728 	return 1;
729 }
730 
731 void
mkjob(Job * j,...)732 mkjob(Job *j, ...)
733 {
734 	int i;
735 	char *p;
736 	va_list arg;
737 
738 	memset(j, 0, sizeof *j);
739 	i = 0;
740 	va_start(arg, j);
741 	while((p = va_arg(arg, char*)) != nil) {
742 		j->argv[i++] = p;
743 		if(i >= nelem(j->argv))
744 			sysfatal("job argv size too small");
745 	}
746 	j->argv[i] = nil;
747 	j->oldlog = vtlogopen(smprint("log%ld.0", j-job), LogSize);
748 	j->newlog = vtlogopen(smprint("log%ld.1", j-job), LogSize);
749 	va_end(arg);
750 }
751 
752 void
manager(void * v)753 manager(void *v)
754 {
755 	int i;
756 	Job *j;
757 	vlong now;
758 
759 	USED(v);
760 	for(;; sleep(1000)) {
761 		for(i=0; i<njob; i++) {
762 			now = time(0) - time0;
763 			j = &job[i];
764 			if(j->pid > 0 || j->newok == -1) {
765 				// still running
766 				if(now - j->runstart > 2*j->freq) {
767 					//TODO: log slow running j
768 				}
769 				continue;
770 			}
771 			if((j->nrun > 0 && now - j->runend > j->freq)
772 			|| (j->nrun == 0 && now > (vlong)(j->offset*j->freq))) {
773 				j->runstart = now;
774 				j->runend = 0;
775 				kickjob(j);
776 			}
777 		}
778 	}
779 }
780 
781 void
waitproc(void * v)782 waitproc(void *v)
783 {
784 	Channel *c;
785 	Waitmsg *w;
786 	int i;
787 	Job *j;
788 
789 	c = v;
790 	for(;;) {
791 		w = recvp(c);
792 		for(i=0; i<njob; i++) {
793 			j = &job[i];
794 			if(j->pid == w->pid) {
795 				j->pid = 0;
796 				j->runend = time(0) - time0;
797 				break;
798 			}
799 		}
800 		free(w);
801 	}
802 }
803 
804 void
threadmain(int argc,char ** argv)805 threadmain(int argc, char **argv)
806 {
807 	int i;
808 	int nofork;
809 	char *prog;
810 	Job *j;
811 
812 	ventilogging = 1;
813 	ventifmtinstall();
814 #ifdef PLAN9PORT
815 	bin = unsharp("#9/bin/venti");
816 #else
817 	bin = "/bin/venti";
818 #endif
819 	nofork = 0;
820 	ARGBEGIN{
821 	case 'b':
822 		bin = EARGF(usage());
823 		break;
824 	case 's':
825 		nofork = 1;
826 		break;
827 	default:
828 		usage();
829 	}ARGEND
830 
831 	if(argc != 1)
832 		usage();
833 	if(rdconf(argv[0], &conf) < 0)
834 		sysfatal("reading config: %r");
835 	if(conf.httpaddr == nil)
836 		sysfatal("config has no httpaddr");
837 	if(conf.smtp != nil && conf.mailfrom == nil)
838 		sysfatal("config has smtp but no mailfrom");
839 	if(conf.smtp != nil && conf.mailto == nil)
840 		sysfatal("config has smtp but no mailto");
841 	if((mirrorprog = regcomp(mirrorregexp)) == nil)
842 		sysfatal("mirrorregexp did not complete");
843 	if((verifyprog = regcomp(verifyregexp)) == nil)
844 		sysfatal("verifyregexp did not complete");
845 	if(conf.nverify > 0 && conf.verifyfreq == 0)
846 		sysfatal("config has no verifyfreq");
847 	if(conf.nmirror > 0 && conf.mirrorfreq == 0)
848 		sysfatal("config has no mirrorfreq");
849 
850 	time0 = time(0);
851 //	sendmail("startup", "mgr is starting\n");
852 
853 	logbuf = vtmalloc(LogSize+1);	// +1 for NUL
854 
855 	errlog = vtlogopen("errors", LogSize);
856 	job = vtmalloc((conf.nmirror+conf.nverify)*sizeof job[0]);
857 	prog = smprint("%s/mirrorarenas", bin);
858 	for(i=0; i<conf.nmirror; i++) {
859 		// job: /bin/venti/mirrorarenas -v src dst
860 		// filter output
861 		j = &job[njob++];
862 		mkjob(j, prog, "-v", conf.mirror[i].src, conf.mirror[i].dst, nil);
863 		j->name = smprint("mirror %s %s", conf.mirror[i].src, conf.mirror[i].dst);
864 		j->ok = mirrorok;
865 		j->freq = conf.mirrorfreq;	// 4 hours	// TODO: put in config
866 		j->offset = (double)i/conf.nmirror;
867 	}
868 
869 	prog = smprint("%s/verifyarena", bin);
870 	for(i=0; i<conf.nverify; i++) {
871 		// job: /bin/venti/verifyarena -b 64M -s 1000 -v arena
872 		// filter output
873 		j = &job[njob++];
874 		mkjob(j, prog, "-b64M", "-s1000", conf.verify[i], nil);
875 		j->name = smprint("verify %s", conf.verify[i]);
876 		j->ok = verifyok;
877 		j->freq = conf.verifyfreq;
878 		j->offset = (double)i/conf.nverify;
879 	}
880 
881 	httpdobj("/mgr", hmanager);
882 	httpdobj("/log", xlog);
883 	vtproc(httpdproc, conf.httpaddr);
884 	vtproc(waitproc, threadwaitchan());
885 	if(nofork)
886 		manager(nil);
887 	else
888 		vtproc(manager, nil);
889 }
890 
891 
892 void
qp(Biobuf * b,char * p)893 qp(Biobuf *b, char *p)
894 {
895 	int n, nspace;
896 
897 	nspace = 0;
898 	n = 0;
899 	for(; *p; p++) {
900 		if(*p == '\n') {
901 			if(nspace > 0) {
902 				nspace = 0;
903 				Bprint(b, "=\n");
904 			}
905 			Bputc(b, '\n');
906 			n = 0;
907 			continue;
908 		}
909 		if(n > 70) {
910 			Bprint(b, "=\n");
911 			nspace = 0;
912 			continue;
913 		}
914 		if(33 <= *p && *p <= 126 && *p != '=') {
915 			Bputc(b, *p);
916 			n++;
917 			nspace = 0;
918 			continue;
919 		}
920 		if(*p == ' ' || *p == '\t') {
921 			Bputc(b, *p);
922 			n++;
923 			nspace++;
924 			continue;
925 		}
926 		Bprint(b, "=%02X", (uchar)*p);
927 		n += 3;
928 		nspace = 0;
929 	}
930 }
931 
932 int
smtpread(Biobuf * b,int code)933 smtpread(Biobuf *b, int code)
934 {
935 	char *p, *q;
936 	int n;
937 
938 	while((p = Brdstr(b, '\n', 1)) != nil) {
939 		n = strtol(p, &q, 10);
940 		if(n == 0 || q != p+3) {
941 		error:
942 			vtlogprint(errlog, "sending mail: %s\n", p);
943 			free(p);
944 			return -1;
945 		}
946 		if(*q == ' ') {
947 			if(n == code) {
948 				free(p);
949 				return 0;
950 			}
951 			goto error;
952 		}
953 		if(*q != '-') {
954 			goto error;
955 		}
956 	}
957 	return -1;
958 }
959 
960 
961 void
sendmail(char * content,char * subject,char * msg)962 sendmail(char *content, char *subject, char *msg)
963 {
964 	int fd;
965 	Biobuf *bin, *bout;
966 
967 	if((fd = dial(conf.smtp, 0, 0, 0)) < 0) {
968 		vtlogprint(errlog, "dial %s: %r\n", conf.smtp);
969 		return;
970 	}
971 	bin = vtmalloc(sizeof *bin);
972 	bout = vtmalloc(sizeof *bout);
973 	Binit(bin, fd, OREAD);
974 	Binit(bout, fd, OWRITE);
975 	if(smtpread(bin, 220) < 0){
976 	error:
977 		close(fd);
978 		Bterm(bin);
979 		Bterm(bout);
980 		return;
981 	}
982 
983 	Bprint(bout, "HELO venti-mgr\n");
984 	Bflush(bout);
985 	if(smtpread(bin, 250) < 0)
986 		goto error;
987 
988 	Bprint(bout, "MAIL FROM:<%s>\n", conf.mailfrom);
989 	Bflush(bout);
990 	if(smtpread(bin, 250) < 0)
991 		goto error;
992 
993 	Bprint(bout, "RCPT TO:<%s>\n", conf.mailfrom);
994 	Bflush(bout);
995 	if(smtpread(bin, 250) < 0)
996 		goto error;
997 
998 	Bprint(bout, "DATA\n");
999 	Bflush(bout);
1000 	if(smtpread(bin, 354) < 0)
1001 		goto error;
1002 
1003 	Bprint(bout, "From: \"venti mgr\" <%s>\n", conf.mailfrom);
1004 	Bprint(bout, "To: <%s>\n", conf.mailto);
1005 	Bprint(bout, "Subject: %s\n", subject);
1006 	Bprint(bout, "MIME-Version: 1.0\n");
1007 	Bprint(bout, "Content-Type: %s; charset=\"UTF-8\"\n", content);
1008 	Bprint(bout, "Content-Transfer-Encoding: quoted-printable\n");
1009 	Bprint(bout, "Message-ID: %08lux%08lux@venti.swtch.com\n", fastrand(), fastrand());
1010 	Bprint(bout, "\n");
1011 	qp(bout, msg);
1012 	Bprint(bout, ".\n");
1013 	Bflush(bout);
1014 	if(smtpread(bin, 250) < 0)
1015 		goto error;
1016 
1017 	Bprint(bout, "QUIT\n");
1018 	Bflush(bout);
1019 	Bterm(bin);
1020 	Bterm(bout);
1021 	close(fd);
1022 }
1023