xref: /openbsd-src/usr.bin/sendbug/sendbug.c (revision c025b898e22be3878dc4a79533818f6fccea9cca)
1 /*	$OpenBSD: sendbug.c,v 1.66 2010/04/20 21:33:25 sthen Exp $	*/
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 
14 #include <ctype.h>
15 #include <err.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <paths.h>
20 #include <pwd.h>
21 #include <signal.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "atomicio.h"
28 
29 #define _PATH_DMESG "/var/run/dmesg.boot"
30 #define DMESG_START "OpenBSD "
31 #define BEGIN64 "begin-base64 "
32 #define END64 "===="
33 
34 int	checkfile(const char *);
35 void	debase(void);
36 void	dmesg(FILE *);
37 int	editit(const char *);
38 void	hwdump(FILE *);
39 void	init(void);
40 int	matchline(const char *, const char *, size_t);
41 int	prompt(void);
42 int	send_file(const char *, int);
43 int	sendmail(const char *);
44 void	template(FILE *);
45 void	usbdevs(FILE *);
46 
47 const char *categories = "system user library documentation kernel "
48     "alpha amd64 arm hppa i386 m68k m88k mips64 powerpc sh sparc sparc64 vax";
49 char *version = "4.2";
50 const char *comment[] = {
51 	"<synopsis of the problem (one line)>",
52 	"<PR category (one line)>",
53 	"<precise description of the problem (multiple lines)>",
54 	"<code/input/activities to reproduce the problem (multiple lines)>",
55 	"<how to correct or work around the problem, if known (multiple lines)>"
56 };
57 
58 struct passwd *pw;
59 char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ];
60 const char *tmpdir;
61 char *tmppath;
62 int Dflag, Pflag, wantcleanup;
63 
64 __dead void
65 usage(void)
66 {
67 	extern char *__progname;
68 
69 	fprintf(stderr, "usage: %s [-DEPV]\n", __progname);
70 	exit(1);
71 }
72 
73 void
74 cleanup()
75 {
76 	if (wantcleanup && tmppath && unlink(tmppath) == -1)
77 		warn("unlink");
78 }
79 
80 
81 int
82 main(int argc, char *argv[])
83 {
84 	int ch, c, fd, ret = 1;
85 	struct stat sb;
86 	char *pr_form;
87 	time_t mtime;
88 	FILE *fp;
89 
90 	while ((ch = getopt(argc, argv, "DEPV")) != -1)
91 		switch (ch) {
92 		case 'D':
93 			Dflag = 1;
94 			break;
95 		case 'E':
96 			debase();
97 			exit(0);
98 		case 'P':
99 			Pflag = 1;
100 			break;
101 		case 'V':
102 			printf("%s\n", version);
103 			exit(0);
104 		default:
105 			usage();
106 		}
107 	argc -= optind;
108 	argv += optind;
109 
110 	if (argc > 0)
111 		usage();
112 
113 	if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
114 		tmpdir = _PATH_TMP;
115 
116 	if (Pflag) {
117 		init();
118 		template(stdout);
119 		exit(0);
120 	}
121 
122 	if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir,
123 	    tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
124 		err(1, "asprintf");
125 	if ((fd = mkstemp(tmppath)) == -1)
126 		err(1, "mkstemp");
127 	wantcleanup = 1;
128 	atexit(cleanup);
129 	if ((fp = fdopen(fd, "w+")) == NULL)
130 		err(1, "fdopen");
131 
132 	init();
133 
134 	pr_form = getenv("PR_FORM");
135 	if (pr_form) {
136 		char buf[BUFSIZ];
137 		size_t len;
138 		FILE *frfp;
139 
140 		frfp = fopen(pr_form, "r");
141 		if (frfp == NULL) {
142 			warn("can't seem to read your template file "
143 			    "(`%s'), ignoring PR_FORM", pr_form);
144 			template(fp);
145 		} else {
146 			while (!feof(frfp)) {
147 				len = fread(buf, 1, sizeof buf, frfp);
148 				if (len == 0)
149 					break;
150 				if (fwrite(buf, 1, len, fp) != len)
151 					break;
152 			}
153 			fclose(frfp);
154 		}
155 	} else
156 		template(fp);
157 
158 	if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF)
159 		err(1, "error creating template");
160 	mtime = sb.st_mtime;
161 
162  edit:
163 	if (editit(tmppath) == -1)
164 		err(1, "error running editor");
165 
166 	if (stat(tmppath, &sb) == -1)
167 		err(1, "stat");
168 	if (mtime == sb.st_mtime)
169 		errx(1, "report unchanged, nothing sent");
170 
171  prompt:
172 	if (!checkfile(tmppath))
173 		fprintf(stderr, "fields are blank, must be filled in\n");
174 	c = prompt();
175 	switch (c) {
176 	case 'a':
177 	case EOF:
178 		wantcleanup = 0;
179 		errx(1, "unsent report in %s", tmppath);
180 	case 'e':
181 		goto edit;
182 	case 's':
183 		if (sendmail(tmppath) == -1)
184 			goto quit;
185 		break;
186 	default:
187 		goto prompt;
188 	}
189 
190 	ret = 0;
191 quit:
192 	return (ret);
193 }
194 
195 void
196 dmesg(FILE *fp)
197 {
198 	char buf[BUFSIZ];
199 	FILE *dfp;
200 	off_t offset = -1;
201 
202 	dfp = fopen(_PATH_DMESG, "r");
203 	if (dfp == NULL) {
204 		warn("can't read dmesg");
205 		return;
206 	}
207 
208 	/* Find last dmesg. */
209 	for (;;) {
210 		off_t o;
211 
212 		o = ftello(dfp);
213 		if (fgets(buf, sizeof(buf), dfp) == NULL)
214 			break;
215 		if (!strncmp(DMESG_START, buf, sizeof(DMESG_START) - 1))
216 			offset = o;
217 	}
218 	if (offset != -1) {
219 		size_t len;
220 
221 		clearerr(dfp);
222 		fseeko(dfp, offset, SEEK_SET);
223 		while (offset != -1 && !feof(dfp)) {
224 			len = fread(buf, 1, sizeof buf, dfp);
225 			if (len == 0)
226 				break;
227 			if (fwrite(buf, 1, len, fp) != len)
228 				break;
229 		}
230 	}
231 	fclose(dfp);
232 }
233 
234 void
235 usbdevs(FILE *ofp)
236 {
237 	char buf[BUFSIZ];
238 	FILE *ifp;
239 	size_t len;
240 
241 	if ((ifp = popen("usbdevs -v", "r")) != NULL) {
242 		while (!feof(ifp)) {
243 			len = fread(buf, 1, sizeof buf, ifp);
244 			if (len == 0)
245 				break;
246 			if (fwrite(buf, 1, len, ofp) != len)
247 				break;
248 		}
249 		pclose(ifp);
250 	}
251 }
252 
253 /*
254  * Execute an editor on the specified pathname, which is interpreted
255  * from the shell.  This means flags may be included.
256  *
257  * Returns -1 on error, or the exit value on success.
258  */
259 int
260 editit(const char *pathname)
261 {
262 	char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p;
263 	sig_t sighup, sigint, sigquit, sigchld;
264 	pid_t pid;
265 	int saved_errno, st, ret = -1;
266 
267 	ed = getenv("VISUAL");
268 	if (ed == NULL || ed[0] == '\0')
269 		ed = getenv("EDITOR");
270 	if (ed == NULL || ed[0] == '\0')
271 		ed = _PATH_VI;
272 	if (asprintf(&p, "%s %s", ed, pathname) == -1)
273 		return (-1);
274 	argp[2] = p;
275 
276 	sighup = signal(SIGHUP, SIG_IGN);
277 	sigint = signal(SIGINT, SIG_IGN);
278 	sigquit = signal(SIGQUIT, SIG_IGN);
279 	sigchld = signal(SIGCHLD, SIG_DFL);
280 	if ((pid = fork()) == -1)
281 		goto fail;
282 	if (pid == 0) {
283 		execv(_PATH_BSHELL, argp);
284 		_exit(127);
285 	}
286 	while (waitpid(pid, &st, 0) == -1)
287 		if (errno != EINTR)
288 			goto fail;
289 	if (!WIFEXITED(st))
290 		errno = EINTR;
291 	else
292 		ret = WEXITSTATUS(st);
293 
294  fail:
295 	saved_errno = errno;
296 	(void)signal(SIGHUP, sighup);
297 	(void)signal(SIGINT, sigint);
298 	(void)signal(SIGQUIT, sigquit);
299 	(void)signal(SIGCHLD, sigchld);
300 	free(p);
301 	errno = saved_errno;
302 	return (ret);
303 }
304 
305 int
306 prompt(void)
307 {
308 	int c, ret;
309 
310 	fpurge(stdin);
311 	fprintf(stderr, "a)bort, e)dit, or s)end: ");
312 	fflush(stderr);
313 	ret = getchar();
314 	if (ret == EOF || ret == '\n')
315 		return (ret);
316 	do {
317 		c = getchar();
318 	} while (c != EOF && c != '\n');
319 	return (ret);
320 }
321 
322 int
323 sendmail(const char *pathname)
324 {
325 	int filedes[2];
326 
327 	if (pipe(filedes) == -1) {
328 		warn("pipe: unsent report in %s", pathname);
329 		return (-1);
330 	}
331 	switch (fork()) {
332 	case -1:
333 		warn("fork error: unsent report in %s",
334 		    pathname);
335 		return (-1);
336 	case 0:
337 		close(filedes[1]);
338 		if (dup2(filedes[0], STDIN_FILENO) == -1) {
339 			warn("dup2 error: unsent report in %s",
340 			    pathname);
341 			return (-1);
342 		}
343 		close(filedes[0]);
344 		execl(_PATH_SENDMAIL, "sendmail",
345 		    "-oi", "-t", (void *)NULL);
346 		warn("sendmail error: unsent report in %s",
347 		    pathname);
348 		return (-1);
349 	default:
350 		close(filedes[0]);
351 		/* Pipe into sendmail. */
352 		if (send_file(pathname, filedes[1]) == -1) {
353 			warn("send_file error: unsent report in %s",
354 			    pathname);
355 			return (-1);
356 		}
357 		close(filedes[1]);
358 		wait(NULL);
359 		break;
360 	}
361 	return (0);
362 }
363 
364 void
365 init(void)
366 {
367 	size_t len;
368 	int sysname[2];
369 	char *cp;
370 
371 	if ((pw = getpwuid(getuid())) == NULL)
372 		err(1, "getpwuid");
373 
374 	sysname[0] = CTL_KERN;
375 	sysname[1] = KERN_OSTYPE;
376 	len = sizeof(os) - 1;
377 	if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1)
378 		err(1, "sysctl");
379 
380 	sysname[0] = CTL_KERN;
381 	sysname[1] = KERN_OSRELEASE;
382 	len = sizeof(rel) - 1;
383 	if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1)
384 		err(1, "sysctl");
385 
386 	sysname[0] = CTL_KERN;
387 	sysname[1] = KERN_VERSION;
388 	len = sizeof(details) - 1;
389 	if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1)
390 		err(1, "sysctl");
391 
392 	cp = strchr(details, '\n');
393 	if (cp) {
394 		cp++;
395 		if (*cp)
396 			*cp++ = '\t';
397 		if (*cp)
398 			*cp++ = '\t';
399 		if (*cp)
400 			*cp++ = '\t';
401 	}
402 
403 	sysname[0] = CTL_HW;
404 	sysname[1] = HW_MACHINE;
405 	len = sizeof(mach) - 1;
406 	if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1)
407 		err(1, "sysctl");
408 }
409 
410 int
411 send_file(const char *file, int dst)
412 {
413 	size_t len;
414 	char *buf, *lbuf;
415 	FILE *fp;
416 	int rval = -1, saved_errno;
417 
418 	if ((fp = fopen(file, "r")) == NULL)
419 		return (-1);
420 	lbuf = NULL;
421 	while ((buf = fgetln(fp, &len))) {
422 		if (buf[len - 1] == '\n') {
423 			buf[len - 1] = '\0';
424 			--len;
425 		} else {
426 			/* EOF without EOL, copy and add the NUL */
427 			if ((lbuf = malloc(len + 1)) == NULL)
428 				goto end;
429 			memcpy(lbuf, buf, len);
430 			lbuf[len] = '\0';
431 			buf = lbuf;
432 		}
433 
434 		/* Skip lines starting with "SENDBUG". */
435 		if (strncmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0)
436 			continue;
437 		while (len) {
438 			char *sp = NULL, *ep = NULL;
439 			size_t copylen;
440 
441 			if ((sp = strchr(buf, '<')) != NULL) {
442 				size_t i;
443 
444 				for (i = 0; i < sizeof(comment) / sizeof(*comment); ++i) {
445 					size_t commentlen = strlen(comment[i]);
446 
447 					if (strncmp(sp, comment[i], commentlen) == 0) {
448 						ep = sp + commentlen - 1;
449 						break;
450 					}
451 				}
452 			}
453 			/* Length of string before comment. */
454 			if (ep)
455 				copylen = sp - buf;
456 			else
457 				copylen = len;
458 			if (atomicio(vwrite, dst, buf, copylen) != copylen)
459 				goto end;
460 			if (!ep)
461 				break;
462 			/* Skip comment. */
463 			len -= ep - buf + 1;
464 			buf = ep + 1;
465 		}
466 		if (atomicio(vwrite, dst, "\n", 1) != 1)
467 			goto end;
468 	}
469 	rval = 0;
470  end:
471 	saved_errno = errno;
472 	free(lbuf);
473 	fclose(fp);
474 	errno = saved_errno;
475 	return (rval);
476 }
477 
478 /*
479  * Does line start with `s' and end with non-comment and non-whitespace?
480  * Note: Does not treat `line' as a C string.
481  */
482 int
483 matchline(const char *s, const char *line, size_t linelen)
484 {
485 	size_t slen;
486 	int iscomment;
487 
488 	slen = strlen(s);
489 	/* Is line shorter than string? */
490 	if (linelen <= slen)
491 		return (0);
492 	/* Does line start with string? */
493 	if (memcmp(line, s, slen) != 0)
494 		return (0);
495 	/* Does line contain anything but comments and whitespace? */
496 	line += slen;
497 	linelen -= slen;
498 	iscomment = 0;
499 	while (linelen) {
500 		if (iscomment) {
501 			if (*line == '>')
502 				iscomment = 0;
503 		} else if (*line == '<')
504 			iscomment = 1;
505 		else if (!isspace((unsigned char)*line))
506 			return (1);
507 		++line;
508 		--linelen;
509 	}
510 	return (0);
511 }
512 
513 /*
514  * Are all required fields filled out?
515  */
516 int
517 checkfile(const char *pathname)
518 {
519 	FILE *fp;
520 	size_t len;
521 	int category = 0, synopsis = 0;
522 	char *buf;
523 
524 	if ((fp = fopen(pathname, "r")) == NULL) {
525 		warn("%s", pathname);
526 		return (0);
527 	}
528 	while ((buf = fgetln(fp, &len))) {
529 		if (matchline(">Category:", buf, len))
530 			category = 1;
531 		else if (matchline(">Synopsis:", buf, len))
532 			synopsis = 1;
533 	}
534 	fclose(fp);
535 	return (category && synopsis);
536 }
537 
538 void
539 template(FILE *fp)
540 {
541 	fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
542 	fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will"
543 	    " be removed automatically.\n");
544 	fprintf(fp, "SENDBUG:\n");
545 	fprintf(fp, "SENDBUG: Choose from the following categories:\n");
546 	fprintf(fp, "SENDBUG:\n");
547 	fprintf(fp, "SENDBUG: %s\n", categories);
548 	fprintf(fp, "SENDBUG:\n");
549 	fprintf(fp, "SENDBUG:\n");
550 	fprintf(fp, "To: %s\n", "gnats@openbsd.org");
551 	fprintf(fp, "Subject: \n");
552 	fprintf(fp, "From: %s\n", pw->pw_name);
553 	fprintf(fp, "Cc: %s\n", pw->pw_name);
554 	fprintf(fp, "Reply-To: %s\n", pw->pw_name);
555 	fprintf(fp, "\n");
556 	fprintf(fp, ">Synopsis:\t%s\n", comment[0]);
557 	fprintf(fp, ">Category:\t%s\n", comment[1]);
558 	fprintf(fp, ">Environment:\n");
559 	fprintf(fp, "\tSystem      : %s %s\n", os, rel);
560 	fprintf(fp, "\tDetails     : %s\n", details);
561 	fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
562 	fprintf(fp, "\tMachine     : %s\n", mach);
563 	fprintf(fp, ">Description:\n");
564 	fprintf(fp, "\t%s\n", comment[2]);
565 	fprintf(fp, ">How-To-Repeat:\n");
566 	fprintf(fp, "\t%s\n", comment[3]);
567 	fprintf(fp, ">Fix:\n");
568 	fprintf(fp, "\t%s\n", comment[4]);
569 
570 	if (!Dflag) {
571 		int root;
572 
573 		fprintf(fp, "\n");
574 		root = !geteuid();
575 		if (!root)
576 			fprintf(fp, "SENDBUG: Run sendbug as root "
577 			    "if this is an ACPI report!\n");
578 		fprintf(fp, "SENDBUG: dmesg%s and usbdevs are attached.\n"
579 		    "SENDBUG: Feel free to delete or use the -D flag if they "
580 		    "contain sensitive information.\n",
581 		    root ? ", pcidump, acpidump" : "");
582 		fputs("\ndmesg:\n", fp);
583 		dmesg(fp);
584 		fputs("\nusbdevs:\n", fp);
585 		usbdevs(fp);
586 		if (root)
587 			hwdump(fp);
588 	}
589 }
590 
591 void
592 hwdump(FILE *ofp)
593 {
594 	char buf[BUFSIZ];
595 	FILE *ifp;
596 	char *cmd, *acpidir;
597 	size_t len;
598 
599 	if (gethostname(buf, sizeof(buf)) == -1)
600 		err(1, "gethostname");
601 	buf[strcspn(buf, ".")] = '\0';
602 
603 	if (asprintf(&acpidir, "%s%sp.XXXXXXXXXX", tmpdir,
604 	    tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
605 		err(1, "asprintf");
606 	if (mkdtemp(acpidir) == NULL)
607 		err(1, "mkdtemp");
608 
609 	if (asprintf(&cmd, "echo \"\\npcidump:\"; pcidump -xxv; "
610 	    "echo \"\\nacpidump:\"; cd %s && acpidump -o %s; "
611 	    "for i in *; do b64encode $i $i; done; rm -rf %s",
612 	    acpidir, buf, acpidir) == -1)
613 		err(1, "asprintf");
614 
615 	if ((ifp = popen(cmd, "r")) != NULL) {
616 		while (!feof(ifp)) {
617 			len = fread(buf, 1, sizeof buf, ifp);
618 			if (len == 0)
619 				break;
620 			if (fwrite(buf, 1, len, ofp) != len)
621 				break;
622 		}
623 		pclose(ifp);
624 	}
625 	free(cmd);
626 	free(acpidir);
627 }
628 
629 void
630 debase(void)
631 {
632 	char buf[BUFSIZ];
633 	FILE *fp = NULL;
634 	size_t len;
635 
636 	while (fgets(buf, sizeof(buf), stdin) != NULL) {
637 		len = strlen(buf);
638 		if (!strncmp(buf, BEGIN64, sizeof(BEGIN64) - 1)) {
639 			if (fp)
640 				errx(1, "double begin");
641 			fp = popen("b64decode", "w");
642 			if (!fp)
643 				errx(1, "popen b64decode");
644 		}
645 		if (fp && fwrite(buf, 1, len, fp) != len)
646 			errx(1, "pipe error");
647 		if (!strncmp(buf, END64, sizeof(END64) - 1)) {
648 			if (pclose(fp) == -1)
649 				errx(1, "pclose b64decode");
650 			fp = NULL;
651 		}
652 	}
653 }
654