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