Blob


1 /*
2 * mirror manager.
3 * a work in progress.
4 * use at your own risk.
5 */
7 #include "stdinc.h"
8 #include <regexp.h>
9 #include <bio.h>
10 #include "dat.h"
11 #include "fns.h"
13 #ifdef PLAN9PORT
14 #define sp s.sp
15 #define ep e.ep
16 #endif
18 void sendmail(char *content, char *subject, char *msg);
19 #define TIME "[0-9]+/[0-9]+ [0-9]+:[0-9]+:[0-9]+"
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;
31 char *verifyregexp =
32 "^" TIME " ("
33 "([^ ]+: unsealed [0-9,]+ bytes)"
34 ")$";
35 Reprog *verifyprog;
37 #undef pipe
38 enum
39 {
40 LogSize = 4*1024*1024 // TODO: make smaller
41 };
43 VtLog *errlog;
45 typedef struct Mirror Mirror;
46 struct Mirror
47 {
48 char *src;
49 char *dst;
50 };
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 };
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 };
88 Job *job;
89 int njob;
90 char *bin;
92 vlong time0;
93 Conf conf;
95 void
96 usage(void)
97 {
98 fprint(2, "usage: mgr [-s] [-b bin/venti/] venti.conf\n");
99 threadexitsall(0);
102 int
103 rdconf(char *file, Conf *conf)
105 char *s, *line, *flds[10];
106 int i, ok;
107 IFile f;
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;
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;
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;
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;
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;
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;
167 conf->mailto = estrdup(flds[2]);
168 }else{
169 seterr(EAdmin, "illegal line '%s' in configuration file %s", line, file);
170 break;
172 free(line);
173 line = nil;
175 free(line);
176 freeifile(&f);
177 return ok;
180 static QLock loglk;
181 static char *logbuf;
183 char*
184 logtext(VtLog *l)
186 int i;
187 char *p;
188 VtLogChunk *c;
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;
198 *p = 0;
199 return logbuf;
203 typedef struct HttpObj HttpObj;
205 static int fromwebdir(HConnect*);
207 enum
209 ObjNameSize = 64,
210 MaxObjs = 64
211 };
213 struct HttpObj
215 char name[ObjNameSize];
216 int (*f)(HConnect*);
217 };
219 static HttpObj objs[MaxObjs];
220 static void httpproc(void*);
222 static HConnect*
223 mkconnect(void)
225 HConnect *c;
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;
236 static int
237 preq(HConnect *c)
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;
249 int
250 hsettype(HConnect *c, char *type)
252 Hio *hout;
253 int r;
255 r = preq(c);
256 if(r < 0)
257 return r;
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");
268 if(http11(c))
269 hxferenc(hout, 1);
270 else
271 c->head.closeit = 1;
272 return 0;
275 int
276 hsethtml(HConnect *c)
278 return hsettype(c, "text/html; charset=utf-8");
281 int
282 hsettext(HConnect *c)
284 return hsettype(c, "text/plain; charset=utf-8");
287 int
288 hnotfound(HConnect *c)
290 int r;
292 r = preq(c);
293 if(r < 0)
294 return r;
295 return hfail(c, HNotFound, c->req.uri);
298 static int
299 xloglist(HConnect *c)
301 if(hsettype(c, "text/html") < 0)
302 return -1;
303 vtloghlist(&c->hout);
304 hflush(&c->hout);
305 return 0;
308 static int
309 strpcmp(const void *va, const void *vb)
311 return strcmp(*(char**)va, *(char**)vb);
314 void
315 vtloghlist(Hio *h)
317 char **p;
318 int i, n;
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");
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");
333 void
334 vtloghdump(Hio *h, VtLog *l)
336 int i;
337 VtLogChunk *c;
338 char *name;
340 name = l ? l->name : "&lt;nil&gt;";
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);
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);
355 hprint(h, "</body></html>\n");
359 char*
360 hargstr(HConnect *c, char *name, char *def)
362 HSPairs *p;
364 for(p=c->req.searchpairs; p; p=p->next)
365 if(strcmp(p->s, name) == 0)
366 return p->t;
367 return def;
370 static int
371 xlog(HConnect *c)
373 char *name;
374 VtLog *l;
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;
386 vtloghdump(&c->hout, l);
387 vtlogclose(l);
388 hflush(&c->hout);
389 return 0;
392 static void
393 httpdproc(void *vaddress)
395 HConnect *c;
396 char *address, ndir[NETPATHLEN], dir[NETPATHLEN];
397 int ctl, nctl, data;
399 address = vaddress;
400 ctl = announce(address, dir);
401 if(ctl < 0){
402 sysfatal("announce %s: %r", address);
403 return;
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;
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;
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);
434 static void
435 httpproc(void *v)
437 HConnect *c;
438 int ok, i, n;
440 c = v;
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;
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;
458 ok = fromwebdir(c);
459 found:
460 hflush(&c->hout);
461 if(c->head.closeit)
462 ok = -1;
463 hreqcleanup(c);
465 if(ok < 0)
466 break;
468 hreqcleanup(c);
469 close(c->hin.fd);
470 free(c);
473 static int
474 httpdobj(char *name, int (*f)(HConnect*))
476 int i;
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;
486 if(strcmp(objs[i].name, name) == 0)
487 return -1;
489 return -1;
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",
503 };
505 static int
506 fromwebdir(HConnect *c)
508 char buf[4096], *p, *ext, *type;
509 int i, fd, n, defaulted;
510 Dir *d;
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);
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;
532 free(d);
533 return hnotfound(c);
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;
545 if(hsettype(c, type) < 0){
546 close(fd);
547 return 0;
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;
557 static int
558 hmanager(HConnect *c)
560 Hio *hout;
561 int r;
562 int i, k;
563 Job *j;
564 VtLog *l;
565 VtLogChunk *ch;
567 r = hsethtml(c);
568 if(r < 0)
569 return r;
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");
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>");
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);
599 hprint(hout, "</pre></font>\n");
600 hprint(hout, "\n");
602 hprint(hout, "</body></html>\n");
603 hflush(hout);
604 return 0;
607 void
608 piper(void *v)
610 Job *j;
611 char buf[512];
612 VtLog *l;
613 int n;
614 int fd;
615 char *p;
616 int ok;
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);
626 qlock(&loglk);
627 p = logtext(l);
628 ok = j->ok(p);
629 qunlock(&loglk);
630 j->newok = ok;
631 close(fd);
634 void
635 kickjob(Job *j)
637 int i;
638 int fd[3];
639 int p[2];
640 VtLog *l;
642 if((fd[0] = open("/dev/null", ORDWR)) < 0) {
643 vtlogprint(errlog, "%T open /dev/null: %r\n");
644 return;
646 if(pipe(p) < 0) {
647 vtlogprint(errlog, "%T pipe: %r\n");
648 close(fd[0]);
649 return;
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);
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]);
672 // fd[0], fd[1], fd[2] are closed now
673 j->pipe = p[0];
674 j->nrun++;
675 vtproc(piper, j);
678 int
679 getline(Resub *text, Resub *line)
681 char *p;
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;
694 return 0;
697 int
698 verifyok(char *output)
700 Resub text, line, m;
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';
711 return 1;
714 int
715 mirrorok(char *output)
717 Resub text, line, m;
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';
728 return 1;
731 void
732 mkjob(Job *j, ...)
734 int i;
735 char *p;
736 va_list arg;
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");
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);
752 void
753 manager(void *v)
755 int i;
756 Job *j;
757 vlong now;
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
769 continue;
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);
781 void
782 waitproc(void *v)
784 Channel *c;
785 Waitmsg *w;
786 int i;
787 Job *j;
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;
800 free(w);
804 void
805 threadmain(int argc, char **argv)
807 int i;
808 int nofork;
809 char *prog;
810 Job *j;
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
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");
850 time0 = time(0);
851 // sendmail("startup", "mgr is starting\n");
853 logbuf = vtmalloc(LogSize+1); // +1 for NUL
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;
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;
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);
892 void
893 qp(Biobuf *b, char *p)
895 int n, nspace;
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");
905 Bputc(b, '\n');
906 n = 0;
907 continue;
909 if(n > 70) {
910 Bprint(b, "=\n");
911 nspace = 0;
912 continue;
914 if(33 <= *p && *p <= 126 && *p != '=') {
915 Bputc(b, *p);
916 n++;
917 nspace = 0;
918 continue;
920 if(*p == ' ' || *p == '\t') {
921 Bputc(b, *p);
922 n++;
923 nspace++;
924 continue;
926 Bprint(b, "=%02X", (uchar)*p);
927 n += 3;
928 nspace = 0;
932 int
933 smtpread(Biobuf *b, int code)
935 char *p, *q;
936 int n;
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;
946 if(*q == ' ') {
947 if(n == code) {
948 free(p);
949 return 0;
951 goto error;
953 if(*q != '-') {
954 goto error;
957 return -1;
961 void
962 sendmail(char *content, char *subject, char *msg)
964 int fd;
965 Biobuf *bin, *bout;
967 if((fd = dial(conf.smtp, 0, 0, 0)) < 0) {
968 vtlogprint(errlog, "dial %s: %r\n", conf.smtp);
969 return;
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;
983 Bprint(bout, "HELO venti-mgr\n");
984 Bflush(bout);
985 if(smtpread(bin, 250) < 0)
986 goto error;
988 Bprint(bout, "MAIL FROM:<%s>\n", conf.mailfrom);
989 Bflush(bout);
990 if(smtpread(bin, 250) < 0)
991 goto error;
993 Bprint(bout, "RCPT TO:<%s>\n", conf.mailfrom);
994 Bflush(bout);
995 if(smtpread(bin, 250) < 0)
996 goto error;
998 Bprint(bout, "DATA\n");
999 Bflush(bout);
1000 if(smtpread(bin, 354) < 0)
1001 goto error;
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;
1017 Bprint(bout, "QUIT\n");
1018 Bflush(bout);
1019 Bterm(bin);
1020 Bterm(bout);
1021 close(fd);