GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/sendbug/sendbug.c Lines: 0 345 0.0 %
Date: 2016-12-06 Branches: 0 251 0.0 %

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