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