xref: /openbsd-src/usr.bin/sendbug/sendbug.c (revision 6c4efebf7fc17d5220f1caa4fc8afc43de4a88b6)
1 /*	$OpenBSD: sendbug.c,v 1.36 2007/04/06 03:24:08 ray Exp $	*/
2 
3 /*
4  * Written by Ray Lai <ray@cyth.net>.
5  * Public domain.
6  */
7 
8 #include <sys/types.h>
9 #include <sys/mman.h>
10 #include <sys/param.h>
11 #include <sys/stat.h>
12 #include <sys/sysctl.h>
13 #include <sys/wait.h>
14 
15 #include <ctype.h>
16 #include <err.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <limits.h>
20 #include <paths.h>
21 #include <pwd.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "atomicio.h"
29 
30 #define _PATH_DMESG "/var/run/dmesg.boot"
31 
32 void	dmesg(FILE *);
33 int	editit(char *);
34 void	init(void);
35 int	prompt(void);
36 int	send_file(const char *, int);
37 int	sendmail(const char *);
38 void	template(FILE *);
39 
40 const char *categories = "system user library documentation ports kernel "
41     "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax";
42 char *version = "4.2";
43 
44 struct passwd *pw;
45 char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ];
46 char *fullname, *tmppath;
47 int wantcleanup;
48 
49 __dead void
50 usage(void)
51 {
52 	fprintf(stderr, "usage: sendbug [-DLPV]\n");
53 	exit(1);
54 }
55 
56 void
57 cleanup()
58 {
59 	if (wantcleanup && tmppath && unlink(tmppath) == -1)
60 		warn("unlink");
61 }
62 
63 
64 int
65 main(int argc, char *argv[])
66 {
67 	int ch, c, Dflag = 0, fd, ret = 1;
68 	const char *tmpdir;
69 	struct stat sb;
70 	char *pr_form;
71 	time_t mtime;
72 	FILE *fp;
73 
74 	while ((ch = getopt(argc, argv, "DLPV")) != -1)
75 		switch (ch) {
76 		case 'D':
77 			Dflag = 1;
78 			break;
79 		case 'L':
80 			printf("Known categories:\n");
81 			printf("%s\n\n", categories);
82 			exit(0);
83 		case 'P':
84 			init();
85 			template(stdout);
86 			exit(0);
87 		case 'V':
88 			printf("%s\n", version);
89 			exit(0);
90 		default:
91 			usage();
92 		}
93 	argc -= optind;
94 	argv += optind;
95 
96 	if (argc > 1)
97 		usage();
98 
99 	if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
100 		tmpdir = _PATH_TMP;
101 	if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir,
102 	    tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
103 		err(1, "asprintf");
104 	if ((fd = mkstemp(tmppath)) == -1)
105 		err(1, "mkstemp");
106 	wantcleanup = 1;
107 	atexit(cleanup);
108 	if ((fp = fdopen(fd, "w+")) == NULL)
109 		err(1, "fdopen");
110 
111 	init();
112 
113 	pr_form = getenv("PR_FORM");
114 	if (pr_form) {
115 		char buf[BUFSIZ];
116 		size_t len;
117 		FILE *frfp;
118 
119 		frfp = fopen(pr_form, "r");
120 		if (frfp == NULL) {
121 			fprintf(stderr, "sendbug: can't seem to read your"
122 			    " template file (`%s'), ignoring PR_FORM\n",
123 			    pr_form);
124 			template(fp);
125 		} else {
126 			while (!feof(frfp)) {
127 				len = fread(buf, 1, sizeof buf, frfp);
128 				if (len == 0)
129 					break;
130 				if (fwrite(buf, 1, len, fp) != len)
131 					break;
132 			}
133 			fclose(frfp);
134 		}
135 	} else {
136 		template(fp);
137 		if (!Dflag)
138 			dmesg(fp);
139 	}
140 
141 	if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF)
142 		err(1, "error creating template");
143 	mtime = sb.st_mtime;
144 
145  edit:
146 	if (editit(tmppath) == -1 && errno != ECHILD)
147 		err(1, "error running editor");
148 
149 	if (stat(tmppath, &sb) == -1)
150 		err(1, "stat");
151 	if (mtime == sb.st_mtime)
152 		errx(1, "report unchanged, nothing sent");
153 
154  prompt:
155 	c = prompt();
156 	switch (c) {
157 	case 'a':
158 	case EOF:
159 		wantcleanup = 0;
160 		errx(1, "unsent report in %s", tmppath);
161 	case 'e':
162 		goto edit;
163 	case 's':
164 		if (sendmail(tmppath) == -1)
165 			goto quit;
166 		break;
167 	default:
168 		goto prompt;
169 	}
170 
171 	ret = 0;
172 quit:
173 	return (ret);
174 }
175 
176 void
177 dmesg(FILE *fp)
178 {
179 	char buf[BUFSIZ];
180 	FILE *dfp;
181 	off_t offset = -1;
182 
183 	dfp = fopen(_PATH_DMESG, "r");
184 	if (dfp == NULL) {
185 		warn("can't read dmesg");
186 		return;
187 	}
188 
189 	fputs("\n"
190 	    "<dmesg is attached.>\n"
191 	    "<Feel free to delete or use the -D flag if it contains "
192 	    "sensitive information.>\n", fp);
193 	/* Find last line starting with "OpenBSD". */
194 	for (;;) {
195 		off_t o;
196 
197 		o = ftello(dfp);
198 		if (fgets(buf, sizeof(buf), dfp) == NULL)
199 			break;
200 		if (!strncmp("OpenBSD ", buf, sizeof("OpenBSD ") - 1))
201 			offset = o;
202 	}
203 	if (offset != -1) {
204 		size_t len;
205 
206 		clearerr(dfp);
207 		fseeko(dfp, offset, SEEK_SET);
208 		while (offset != -1 && !feof(dfp)) {
209 			len = fread(buf, 1, sizeof buf, dfp);
210 			if (len == 0)
211 				break;
212 			if (fwrite(buf, 1, len, fp) != len)
213 				break;
214 		}
215 	}
216 	fclose(dfp);
217 }
218 
219 int
220 editit(char *tmpfile)
221 {
222 	char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p;
223 	sig_t sighup, sigint, sigquit;
224 	pid_t pid, xpid;
225 	int st;
226 
227 	ed = getenv("VISUAL");
228 	if (ed == NULL || ed[0] == '\0')
229 		ed = getenv("EDITOR");
230 	if (ed == NULL || ed[0] == '\0')
231 		ed = _PATH_VI;
232 	if (asprintf(&p, "%s %s", ed, tmpfile) == -1)
233 		return (-1);
234 	argp[2] = p;
235 
236  top:
237 	sighup = signal(SIGHUP, SIG_IGN);
238 	sigint = signal(SIGINT, SIG_IGN);
239 	sigquit = signal(SIGQUIT, SIG_IGN);
240 	if ((pid = fork()) == -1) {
241 		int saved_errno = errno;
242 
243 		(void)signal(SIGHUP, sighup);
244 		(void)signal(SIGINT, sigint);
245 		(void)signal(SIGQUIT, sigquit);
246 		if (saved_errno == EAGAIN) {
247 			sleep(1);
248 			goto top;
249 		}
250 		free(p);
251 		errno = saved_errno;
252 		return (-1);
253 	}
254 	if (pid == 0) {
255 		execv(_PATH_BSHELL, argp);
256 		_exit(127);
257 	}
258 	free(p);
259 	for (;;) {
260 		xpid = waitpid(pid, &st, WUNTRACED);
261 		if (xpid == -1) {
262 			if (errno != EINTR)
263 				return (-1);
264 		} else if (WIFSTOPPED(st))
265 			raise(WSTOPSIG(st));
266 		else
267 			break;
268 	}
269 	(void)signal(SIGHUP, sighup);
270 	(void)signal(SIGINT, sigint);
271 	(void)signal(SIGQUIT, sigquit);
272 	if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
273 		errno = ECHILD;
274 		return (-1);
275 	}
276 	return (0);
277 }
278 
279 int
280 prompt(void)
281 {
282 	int c, ret;
283 
284 	fpurge(stdin);
285 	fprintf(stderr, "a)bort, e)dit, or s)end: ");
286 	fflush(stderr);
287 	ret = getchar();
288 	if (ret == EOF || ret == '\n')
289 		return (ret);
290 	do {
291 		c = getchar();
292 	} while (c != EOF && c != '\n');
293 	return (ret);
294 }
295 
296 int
297 sendmail(const char *tmppath)
298 {
299 	int filedes[2];
300 
301 	if (pipe(filedes) == -1) {
302 		warn("pipe: unsent report in %s", tmppath);
303 		return (-1);
304 	}
305 	switch (fork()) {
306 	case -1:
307 		warn("fork error: unsent report in %s",
308 		    tmppath);
309 		return (-1);
310 	case 0:
311 		close(filedes[1]);
312 		if (dup2(filedes[0], STDIN_FILENO) == -1) {
313 			warn("dup2 error: unsent report in %s",
314 			    tmppath);
315 			return (-1);
316 		}
317 		close(filedes[0]);
318 		execl("/usr/sbin/sendmail", "sendmail",
319 		    "-oi", "-t", (void *)NULL);
320 		warn("sendmail error: unsent report in %s",
321 		    tmppath);
322 		return (-1);
323 	default:
324 		close(filedes[0]);
325 		/* Pipe into sendmail. */
326 		if (send_file(tmppath, filedes[1]) == -1) {
327 			warn("send_file error: unsent report in %s",
328 			    tmppath);
329 			return (-1);
330 		}
331 		close(filedes[1]);
332 		wait(NULL);
333 		break;
334 	}
335 	return (0);
336 }
337 
338 void
339 init(void)
340 {
341 	size_t amp, len, gecoslen, namelen;
342 	int sysname[2];
343 	char ch, *cp;
344 
345 	if ((pw = getpwuid(getuid())) == NULL)
346 		err(1, "getpwuid");
347 	namelen = strlen(pw->pw_name);
348 
349 	/* Count number of '&'. */
350 	for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp)
351 		if (*cp == '&')
352 			++amp;
353 
354 	/* Truncate gecos to full name. */
355 	gecoslen = cp - pw->pw_gecos;
356 	pw->pw_gecos[gecoslen] = '\0';
357 
358 	/* Expanded str = orig str - '&' chars + concatenated logins. */
359 	len = gecoslen - amp + (amp * namelen) + 1;
360 	if ((fullname = malloc(len)) == NULL)
361 		err(1, "malloc");
362 
363 	/* Upper case first char of login. */
364 	ch = pw->pw_name[0];
365 	pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]);
366 
367 	cp = pw->pw_gecos;
368 	fullname[0] = '\0';
369 	while (cp != NULL) {
370 		char *token;
371 
372 		token = strsep(&cp, "&");
373 		if (token != pw->pw_gecos &&
374 		    strlcat(fullname, pw->pw_name, len) >= len)
375 			errx(1, "truncated string");
376 		if (strlcat(fullname, token, len) >= len)
377 			errx(1, "truncated string");
378 	}
379 	/* Restore case of first char of login. */
380 	pw->pw_name[0] = ch;
381 
382 	sysname[0] = CTL_KERN;
383 	sysname[1] = KERN_OSTYPE;
384 	len = sizeof(os) - 1;
385 	if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1)
386 		err(1, "sysctl");
387 
388 	sysname[0] = CTL_KERN;
389 	sysname[1] = KERN_OSRELEASE;
390 	len = sizeof(rel) - 1;
391 	if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1)
392 		err(1, "sysctl");
393 
394 	sysname[0] = CTL_KERN;
395 	sysname[1] = KERN_VERSION;
396 	len = sizeof(details) - 1;
397 	if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1)
398 		err(1, "sysctl");
399 
400 	cp = strchr(details, '\n');
401 	if (cp) {
402 		cp++;
403 		if (*cp)
404 			*cp++ = '\t';
405 		if (*cp)
406 			*cp++ = '\t';
407 		if (*cp)
408 			*cp++ = '\t';
409 	}
410 
411 	sysname[0] = CTL_HW;
412 	sysname[1] = HW_MACHINE;
413 	len = sizeof(mach) - 1;
414 	if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1)
415 		err(1, "sysctl");
416 }
417 
418 int
419 send_file(const char *file, int dst)
420 {
421 	int blank = 0;
422 	size_t len;
423 	char *buf;
424 	FILE *fp;
425 
426 	if ((fp = fopen(file, "r")) == NULL)
427 		return (-1);
428 	while ((buf = fgetln(fp, &len))) {
429 		/* Skip lines starting with "SENDBUG". */
430 		if (len >= sizeof("SENDBUG") - 1 &&
431 		    memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0)
432 			continue;
433 		if (len == 1 && buf[0] == '\n')
434 			blank = 1;
435 		/* Skip comments, but only if we encountered a blank line. */
436 		while (len) {
437 			char *sp = NULL, *ep = NULL;
438 			size_t copylen;
439 
440 			if (blank && (sp = memchr(buf, '<', len)) != NULL)
441 				ep = memchr(sp, '>', len - (sp - buf + 1));
442 			/* Length of string before comment. */
443 			if (ep)
444 				copylen = sp - buf;
445 			else
446 				copylen = len;
447 			if (atomicio(vwrite, dst, buf, copylen) != copylen) {
448 				int saved_errno = errno;
449 
450 				fclose(fp);
451 				errno = saved_errno;
452 				return (-1);
453 			}
454 			if (!ep)
455 				break;
456 			/* Skip comment. */
457 			len -= ep - buf + 1;
458 			buf = ep + 1;
459 		}
460 	}
461 	fclose(fp);
462 	return (0);
463 }
464 
465 void
466 template(FILE *fp)
467 {
468 	fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
469 	fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will"
470 	    " be removed automatically, as\n");
471 	fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>').\n");
472 	fprintf(fp, "SENDBUG:\n");
473 	fprintf(fp, "SENDBUG: Choose from the following categories:\n");
474 	fprintf(fp, "SENDBUG:\n");
475 	fprintf(fp, "SENDBUG: %s\n", categories);
476 	fprintf(fp, "SENDBUG:\n");
477 	fprintf(fp, "SENDBUG:\n");
478 	fprintf(fp, "To: %s\n", "gnats@openbsd.org");
479 	fprintf(fp, "Subject: \n");
480 	fprintf(fp, "From: %s\n", pw->pw_name);
481 	fprintf(fp, "Cc: %s\n", pw->pw_name);
482 	fprintf(fp, "Reply-To: %s\n", pw->pw_name);
483 	fprintf(fp, "X-sendbug-version: %s\n", version);
484 	fprintf(fp, "\n");
485 	fprintf(fp, "\n");
486 	fprintf(fp, ">Submitter-Id:\tnet\n");
487 	fprintf(fp, ">Originator:\t%s\n", fullname);
488 	fprintf(fp, ">Organization:\n");
489 	fprintf(fp, "net\n");
490 	fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n");
491 	fprintf(fp, ">Severity:\t"
492 	    "<[ non-critical | serious | critical ] (one line)>\n");
493 	fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n");
494 	fprintf(fp, ">Category:\t<PR category (one line)>\n");
495 	fprintf(fp, ">Class:\t\t"
496 	    "<[ sw-bug | doc-bug | change-request | support ] (one line)>\n");
497 	fprintf(fp, ">Release:\t<release number or tag (one line)>\n");
498 	fprintf(fp, ">Environment:\n");
499 	fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n");
500 	fprintf(fp, "\tSystem      : %s %s\n", os, rel);
501 	fprintf(fp, "\tDetails     : %s\n", details);
502 	fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
503 	fprintf(fp, "\tMachine     : %s\n", mach);
504 	fprintf(fp, ">Description:\n");
505 	fprintf(fp, "\t<precise description of the problem (multiple lines)>\n");
506 	fprintf(fp, ">How-To-Repeat:\n");
507 	fprintf(fp, "\t<code/input/activities to reproduce the problem"
508 	    " (multiple lines)>\n");
509 	fprintf(fp, ">Fix:\n");
510 	fprintf(fp, "\t<how to correct or work around the problem,"
511 	    " if known (multiple lines)>\n");
512 }
513