Annotation of src/usr.bin/sendbug/sendbug.c, Revision 1.59
1.59 ! ray 1: /* $OpenBSD: sendbug.c,v 1.58 2008/10/06 04:58:37 deraadt Exp $ */
1.1 ray 2:
3: /*
4: * Written by Ray Lai <ray@cyth.net>.
5: * Public domain.
6: */
7:
8: #include <sys/types.h>
9: #include <sys/param.h>
10: #include <sys/stat.h>
11: #include <sys/sysctl.h>
12: #include <sys/wait.h>
13:
1.13 ray 14: #include <ctype.h>
1.1 ray 15: #include <err.h>
16: #include <errno.h>
17: #include <fcntl.h>
18: #include <limits.h>
19: #include <paths.h>
20: #include <pwd.h>
1.15 deraadt 21: #include <signal.h>
1.1 ray 22: #include <stdio.h>
23: #include <stdlib.h>
24: #include <string.h>
25: #include <unistd.h>
26:
27: #include "atomicio.h"
28:
1.35 ray 29: #define _PATH_DMESG "/var/run/dmesg.boot"
1.51 ray 30: #define DMESG_START "OpenBSD "
1.35 ray 31:
1.39 ray 32: int checkfile(const char *);
1.36 ray 33: void dmesg(FILE *);
1.42 ray 34: int editit(const char *);
1.21 deraadt 35: void init(void);
1.40 ray 36: int matchline(const char *, const char *, size_t);
1.21 deraadt 37: int prompt(void);
1.32 ray 38: int send_file(const char *, int);
1.21 deraadt 39: int sendmail(const char *);
40: void template(FILE *);
1.1 ray 41:
1.11 deraadt 42: char *version = "4.2";
1.53 ray 43: const char *comment[] = {
44: "<synopsis of the problem (one line)>",
45: "<precise description of the problem (multiple lines)>",
46: "<code/input/activities to reproduce the problem (multiple lines)>",
47: "<how to correct or work around the problem, if known (multiple lines)>"
48: };
1.11 deraadt 49:
50: struct passwd *pw;
1.14 deraadt 51: char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ];
1.21 deraadt 52: char *fullname, *tmppath;
1.58 deraadt 53: int Dflag, Pflag, wantcleanup;
1.11 deraadt 54:
1.12 tedu 55: __dead void
1.3 deraadt 56: usage(void)
57: {
1.41 ray 58: extern char *__progname;
59:
1.59 ! ray 60: fprintf(stderr, "usage: %s [-DPV]\n", __progname);
1.12 tedu 61: exit(1);
62: }
63:
64: void
65: cleanup()
66: {
67: if (wantcleanup && tmppath && unlink(tmppath) == -1)
68: warn("unlink");
1.3 deraadt 69: }
70:
1.12 tedu 71:
1.1 ray 72: int
73: main(int argc, char *argv[])
74: {
1.47 ray 75: int ch, c, fd, ret = 1;
1.15 deraadt 76: const char *tmpdir;
1.19 deraadt 77: struct stat sb;
1.15 deraadt 78: char *pr_form;
1.1 ray 79: time_t mtime;
1.2 deraadt 80: FILE *fp;
1.3 deraadt 81:
1.59 ! ray 82: while ((ch = getopt(argc, argv, "DPV")) != -1)
1.3 deraadt 83: switch (ch) {
1.35 ray 84: case 'D':
85: Dflag = 1;
86: break;
1.3 deraadt 87: case 'P':
1.58 deraadt 88: Pflag = 1;
89: break;
1.5 deraadt 90: case 'V':
91: printf("%s\n", version);
92: exit(0);
1.3 deraadt 93: default:
94: usage();
1.7 deraadt 95: }
1.35 ray 96: argc -= optind;
97: argv += optind;
1.7 deraadt 98:
1.37 ray 99: if (argc > 0)
1.3 deraadt 100: usage();
1.58 deraadt 101:
102: if (Pflag) {
103: init();
104: template(stdout);
105: exit(0);
106: }
1.1 ray 107:
108: if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
109: tmpdir = _PATH_TMP;
1.9 ray 110: if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir,
1.19 deraadt 111: tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
1.12 tedu 112: err(1, "asprintf");
1.1 ray 113: if ((fd = mkstemp(tmppath)) == -1)
114: err(1, "mkstemp");
1.12 tedu 115: wantcleanup = 1;
116: atexit(cleanup);
1.19 deraadt 117: if ((fp = fdopen(fd, "w+")) == NULL)
1.12 tedu 118: err(1, "fdopen");
1.1 ray 119:
1.12 tedu 120: init();
1.1 ray 121:
1.10 deraadt 122: pr_form = getenv("PR_FORM");
123: if (pr_form) {
124: char buf[BUFSIZ];
125: size_t len;
126: FILE *frfp;
127:
128: frfp = fopen(pr_form, "r");
129: if (frfp == NULL) {
1.41 ray 130: warn("can't seem to read your template file "
131: "(`%s'), ignoring PR_FORM", pr_form);
1.10 deraadt 132: template(fp);
133: } else {
134: while (!feof(frfp)) {
135: len = fread(buf, 1, sizeof buf, frfp);
136: if (len == 0)
137: break;
138: if (fwrite(buf, 1, len, fp) != len)
139: break;
140: }
141: fclose(frfp);
142: }
1.47 ray 143: } else
1.10 deraadt 144: template(fp);
1.1 ray 145:
1.19 deraadt 146: if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF)
1.12 tedu 147: err(1, "error creating template");
1.1 ray 148: mtime = sb.st_mtime;
149:
150: edit:
1.48 ray 151: if (editit(tmppath) == -1)
1.28 ray 152: err(1, "error running editor");
1.1 ray 153:
1.19 deraadt 154: if (stat(tmppath, &sb) == -1)
1.12 tedu 155: err(1, "stat");
1.19 deraadt 156: if (mtime == sb.st_mtime)
1.12 tedu 157: errx(1, "report unchanged, nothing sent");
1.1 ray 158:
159: prompt:
1.39 ray 160: if (!checkfile(tmppath))
161: fprintf(stderr, "fields are blank, must be filled in\n");
1.1 ray 162: c = prompt();
163: switch (c) {
1.5 deraadt 164: case 'a':
165: case EOF:
1.12 tedu 166: wantcleanup = 0;
167: errx(1, "unsent report in %s", tmppath);
1.1 ray 168: case 'e':
169: goto edit;
170: case 's':
171: if (sendmail(tmppath) == -1)
172: goto quit;
173: break;
174: default:
175: goto prompt;
176: }
177:
178: ret = 0;
1.12 tedu 179: quit:
1.1 ray 180: return (ret);
1.36 ray 181: }
182:
183: void
184: dmesg(FILE *fp)
185: {
186: char buf[BUFSIZ];
187: FILE *dfp;
188: off_t offset = -1;
189:
190: dfp = fopen(_PATH_DMESG, "r");
191: if (dfp == NULL) {
192: warn("can't read dmesg");
193: return;
194: }
195:
196: fputs("\n"
1.53 ray 197: "SENDBUG: dmesg is attached.\n"
198: "SENDBUG: Feel free to delete or use the -D flag if it contains "
199: "sensitive information.\n", fp);
1.51 ray 200: /* Find last dmesg. */
1.36 ray 201: for (;;) {
202: off_t o;
203:
204: o = ftello(dfp);
205: if (fgets(buf, sizeof(buf), dfp) == NULL)
206: break;
1.51 ray 207: if (!strncmp(DMESG_START, buf, sizeof(DMESG_START) - 1))
1.36 ray 208: offset = o;
209: }
210: if (offset != -1) {
211: size_t len;
212:
213: clearerr(dfp);
214: fseeko(dfp, offset, SEEK_SET);
215: while (offset != -1 && !feof(dfp)) {
216: len = fread(buf, 1, sizeof buf, dfp);
217: if (len == 0)
218: break;
219: if (fwrite(buf, 1, len, fp) != len)
220: break;
221: }
222: }
223: fclose(dfp);
1.1 ray 224: }
225:
1.48 ray 226: /*
227: * Execute an editor on the specified pathname, which is interpreted
228: * from the shell. This means flags may be included.
229: *
230: * Returns -1 on error, or the exit value on success.
231: */
1.15 deraadt 232: int
1.42 ray 233: editit(const char *pathname)
1.15 deraadt 234: {
1.19 deraadt 235: char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p;
1.52 deraadt 236: sig_t sighup, sigint, sigquit, sigchld;
1.42 ray 237: pid_t pid;
1.52 deraadt 238: int saved_errno, st, ret = -1;
1.15 deraadt 239:
1.26 ray 240: ed = getenv("VISUAL");
241: if (ed == NULL || ed[0] == '\0')
242: ed = getenv("EDITOR");
243: if (ed == NULL || ed[0] == '\0')
1.15 deraadt 244: ed = _PATH_VI;
1.38 ray 245: if (asprintf(&p, "%s %s", ed, pathname) == -1)
1.18 ray 246: return (-1);
1.15 deraadt 247: argp[2] = p;
248:
1.25 ray 249: sighup = signal(SIGHUP, SIG_IGN);
250: sigint = signal(SIGINT, SIG_IGN);
251: sigquit = signal(SIGQUIT, SIG_IGN);
1.52 deraadt 252: sigchld = signal(SIGCHLD, SIG_DFL);
1.49 ray 253: if ((pid = fork()) == -1)
254: goto fail;
1.15 deraadt 255: if (pid == 0) {
256: execv(_PATH_BSHELL, argp);
257: _exit(127);
258: }
1.46 ray 259: while (waitpid(pid, &st, 0) == -1)
260: if (errno != EINTR)
261: goto fail;
1.52 deraadt 262: if (!WIFEXITED(st))
1.48 ray 263: errno = EINTR;
1.52 deraadt 264: else
265: ret = WEXITSTATUS(st);
1.46 ray 266:
267: fail:
268: saved_errno = errno;
269: (void)signal(SIGHUP, sighup);
270: (void)signal(SIGINT, sigint);
271: (void)signal(SIGQUIT, sigquit);
1.52 deraadt 272: (void)signal(SIGCHLD, sigchld);
1.46 ray 273: free(p);
274: errno = saved_errno;
1.52 deraadt 275: return (ret);
1.15 deraadt 276: }
1.12 tedu 277:
1.1 ray 278: int
279: prompt(void)
280: {
281: int c, ret;
282:
283: fpurge(stdin);
284: fprintf(stderr, "a)bort, e)dit, or s)end: ");
285: fflush(stderr);
286: ret = getchar();
287: if (ret == EOF || ret == '\n')
288: return (ret);
289: do {
290: c = getchar();
291: } while (c != EOF && c != '\n');
292: return (ret);
293: }
294:
295: int
1.38 ray 296: sendmail(const char *pathname)
1.1 ray 297: {
298: int filedes[2];
299:
300: if (pipe(filedes) == -1) {
1.38 ray 301: warn("pipe: unsent report in %s", pathname);
1.1 ray 302: return (-1);
303: }
304: switch (fork()) {
305: case -1:
306: warn("fork error: unsent report in %s",
1.38 ray 307: pathname);
1.1 ray 308: return (-1);
309: case 0:
310: close(filedes[1]);
311: if (dup2(filedes[0], STDIN_FILENO) == -1) {
312: warn("dup2 error: unsent report in %s",
1.38 ray 313: pathname);
1.1 ray 314: return (-1);
315: }
316: close(filedes[0]);
1.56 chl 317: execl(_PATH_SENDMAIL, "sendmail",
1.2 deraadt 318: "-oi", "-t", (void *)NULL);
1.1 ray 319: warn("sendmail error: unsent report in %s",
1.38 ray 320: pathname);
1.1 ray 321: return (-1);
322: default:
323: close(filedes[0]);
324: /* Pipe into sendmail. */
1.38 ray 325: if (send_file(pathname, filedes[1]) == -1) {
1.1 ray 326: warn("send_file error: unsent report in %s",
1.38 ray 327: pathname);
1.1 ray 328: return (-1);
329: }
330: close(filedes[1]);
331: wait(NULL);
332: break;
333: }
334: return (0);
335: }
336:
1.12 tedu 337: void
1.1 ray 338: init(void)
339: {
1.33 ray 340: size_t amp, len, gecoslen, namelen;
1.1 ray 341: int sysname[2];
1.33 ray 342: char ch, *cp;
1.1 ray 343:
1.19 deraadt 344: if ((pw = getpwuid(getuid())) == NULL)
1.12 tedu 345: err(1, "getpwuid");
1.13 ray 346: namelen = strlen(pw->pw_name);
1.1 ray 347:
1.31 moritz 348: /* Count number of '&'. */
1.33 ray 349: for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp)
350: if (*cp == '&')
1.31 moritz 351: ++amp;
1.33 ray 352:
353: /* Truncate gecos to full name. */
354: gecoslen = cp - pw->pw_gecos;
355: pw->pw_gecos[gecoslen] = '\0';
356:
1.31 moritz 357: /* Expanded str = orig str - '&' chars + concatenated logins. */
1.33 ray 358: len = gecoslen - amp + (amp * namelen) + 1;
359: if ((fullname = malloc(len)) == NULL)
1.12 tedu 360: err(1, "malloc");
1.19 deraadt 361:
1.33 ray 362: /* Upper case first char of login. */
363: ch = pw->pw_name[0];
364: pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]);
365:
366: cp = pw->pw_gecos;
367: fullname[0] = '\0';
368: while (cp != NULL) {
369: char *token;
370:
371: token = strsep(&cp, "&");
372: if (token != pw->pw_gecos &&
373: strlcat(fullname, pw->pw_name, len) >= len)
374: errx(1, "truncated string");
375: if (strlcat(fullname, token, len) >= len)
376: errx(1, "truncated string");
1.13 ray 377: }
1.33 ray 378: /* Restore case of first char of login. */
379: pw->pw_name[0] = ch;
1.1 ray 380:
381: sysname[0] = CTL_KERN;
382: sysname[1] = KERN_OSTYPE;
383: len = sizeof(os) - 1;
1.19 deraadt 384: if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1)
1.12 tedu 385: err(1, "sysctl");
1.1 ray 386:
387: sysname[0] = CTL_KERN;
388: sysname[1] = KERN_OSRELEASE;
389: len = sizeof(rel) - 1;
1.19 deraadt 390: if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1)
1.12 tedu 391: err(1, "sysctl");
1.1 ray 392:
1.14 deraadt 393: sysname[0] = CTL_KERN;
394: sysname[1] = KERN_VERSION;
395: len = sizeof(details) - 1;
1.19 deraadt 396: if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1)
1.14 deraadt 397: err(1, "sysctl");
398:
399: cp = strchr(details, '\n');
400: if (cp) {
401: cp++;
402: if (*cp)
403: *cp++ = '\t';
404: if (*cp)
405: *cp++ = '\t';
406: if (*cp)
407: *cp++ = '\t';
408: }
409:
1.1 ray 410: sysname[0] = CTL_HW;
411: sysname[1] = HW_MACHINE;
412: len = sizeof(mach) - 1;
1.19 deraadt 413: if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1)
1.12 tedu 414: err(1, "sysctl");
1.1 ray 415: }
416:
417: int
418: send_file(const char *file, int dst)
419: {
1.2 deraadt 420: size_t len;
1.54 ray 421: char *buf, *lbuf;
1.1 ray 422: FILE *fp;
1.54 ray 423: int rval = -1, saved_errno;
1.1 ray 424:
425: if ((fp = fopen(file, "r")) == NULL)
426: return (-1);
1.54 ray 427: lbuf = NULL;
1.1 ray 428: while ((buf = fgetln(fp, &len))) {
1.55 ray 429: if (buf[len - 1] == '\n') {
1.54 ray 430: buf[len - 1] = '\0';
1.55 ray 431: --len;
432: } else {
1.54 ray 433: /* EOF without EOL, copy and add the NUL */
434: if ((lbuf = malloc(len + 1)) == NULL)
435: goto end;
436: memcpy(lbuf, buf, len);
437: lbuf[len] = '\0';
438: buf = lbuf;
439: }
440:
1.1 ray 441: /* Skip lines starting with "SENDBUG". */
1.54 ray 442: if (strncmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0)
1.1 ray 443: continue;
444: while (len) {
1.14 deraadt 445: char *sp = NULL, *ep = NULL;
1.1 ray 446: size_t copylen;
447:
1.54 ray 448: if ((sp = strchr(buf, '<')) != NULL) {
449: size_t i;
450:
451: for (i = 0; i < sizeof(comment) / sizeof(*comment); ++i) {
452: size_t commentlen = strlen(comment[i]);
453:
454: if (strncmp(sp, comment[i], commentlen) == 0) {
455: ep = sp + commentlen - 1;
456: break;
457: }
458: }
459: }
1.1 ray 460: /* Length of string before comment. */
1.4 ray 461: if (ep)
462: copylen = sp - buf;
463: else
464: copylen = len;
1.55 ray 465: if (atomicio(vwrite, dst, buf, copylen) != copylen)
1.54 ray 466: goto end;
1.1 ray 467: if (!ep)
468: break;
469: /* Skip comment. */
470: len -= ep - buf + 1;
471: buf = ep + 1;
472: }
1.55 ray 473: if (atomicio(vwrite, dst, "\n", 1) != 1)
474: goto end;
1.1 ray 475: }
1.54 ray 476: rval = 0;
477: end:
478: saved_errno = errno;
479: free(lbuf);
1.1 ray 480: fclose(fp);
1.54 ray 481: errno = saved_errno;
482: return (rval);
1.39 ray 483: }
484:
485: /*
486: * Does line start with `s' and end with non-comment and non-whitespace?
1.40 ray 487: * Note: Does not treat `line' as a C string.
1.39 ray 488: */
489: int
1.40 ray 490: matchline(const char *s, const char *line, size_t linelen)
1.39 ray 491: {
492: size_t slen;
1.53 ray 493: int iscomment;
1.39 ray 494:
495: slen = strlen(s);
496: /* Is line shorter than string? */
497: if (linelen <= slen)
498: return (0);
499: /* Does line start with string? */
500: if (memcmp(line, s, slen) != 0)
501: return (0);
502: /* Does line contain anything but comments and whitespace? */
503: line += slen;
504: linelen -= slen;
1.53 ray 505: iscomment = 0;
1.39 ray 506: while (linelen) {
1.53 ray 507: if (iscomment) {
1.39 ray 508: if (*line == '>')
1.53 ray 509: iscomment = 0;
1.39 ray 510: } else if (*line == '<')
1.53 ray 511: iscomment = 1;
1.40 ray 512: else if (!isspace((unsigned char)*line))
1.39 ray 513: return (1);
514: ++line;
515: --linelen;
516: }
517: return (0);
518: }
519:
520: /*
521: * Are all required fields filled out?
522: */
523: int
524: checkfile(const char *pathname)
525: {
526: FILE *fp;
527: size_t len;
1.59 ! ray 528: int rval = 0;
1.39 ray 529: char *buf;
530:
531: if ((fp = fopen(pathname, "r")) == NULL) {
532: warn("%s", pathname);
533: return (0);
534: }
535: while ((buf = fgetln(fp, &len))) {
1.59 ! ray 536: if (matchline(">Synopsis:", buf, len)) {
! 537: rval = 1;
! 538: break;
! 539: }
1.39 ray 540: }
541: fclose(fp);
1.59 ! ray 542: return (rval);
1.1 ray 543: }
544:
545: void
546: template(FILE *fp)
547: {
548: fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
1.19 deraadt 549: fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will"
1.54 ray 550: " be removed automatically.\n");
1.1 ray 551: fprintf(fp, "SENDBUG:\n");
552: fprintf(fp, "To: %s\n", "gnats@openbsd.org");
553: fprintf(fp, "Subject: \n");
554: fprintf(fp, "From: %s\n", pw->pw_name);
1.34 ray 555: fprintf(fp, "Cc: %s\n", pw->pw_name);
1.1 ray 556: fprintf(fp, "Reply-To: %s\n", pw->pw_name);
557: fprintf(fp, "\n");
1.53 ray 558: fprintf(fp, ">Synopsis:\t%s\n", comment[0]);
1.1 ray 559: fprintf(fp, ">Environment:\n");
560: fprintf(fp, "\tSystem : %s %s\n", os, rel);
1.14 deraadt 561: fprintf(fp, "\tDetails : %s\n", details);
1.1 ray 562: fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
563: fprintf(fp, "\tMachine : %s\n", mach);
564: fprintf(fp, ">Description:\n");
1.59 ! ray 565: fprintf(fp, "\t%s\n", comment[1]);
1.1 ray 566: fprintf(fp, ">How-To-Repeat:\n");
1.59 ! ray 567: fprintf(fp, "\t%s\n", comment[2]);
1.1 ray 568: fprintf(fp, ">Fix:\n");
1.59 ! ray 569: fprintf(fp, "\t%s\n", comment[3]);
1.47 ray 570:
571: if (!Dflag)
572: dmesg(fp);
1.1 ray 573: }