GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/sendbug/sendbug.c Lines: 0 330 0.0 %
Date: 2017-11-07 Branches: 0 250 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: sendbug.c,v 1.78 2017/08/21 21:41:13 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/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 flock", 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
	}
284
	if (!WIFEXITED(st))
285
		errno = EINTR;
286
	else
287
		ret = WEXITSTATUS(st);
288
289
 fail:
290
	saved_errno = errno;
291
	(void)signal(SIGHUP, sighup);
292
	(void)signal(SIGINT, sigint);
293
	(void)signal(SIGQUIT, sigquit);
294
	(void)signal(SIGCHLD, sigchld);
295
	free(p);
296
	errno = saved_errno;
297
	return (ret);
298
}
299
300
int
301
prompt(void)
302
{
303
	int c, ret;
304
305
	fpurge(stdin);
306
	fprintf(stderr, "a)bort, e)dit, or s)end: ");
307
	fflush(stderr);
308
	ret = getchar();
309
	if (ret == EOF || ret == '\n')
310
		return (ret);
311
	do {
312
		c = getchar();
313
	} while (c != EOF && c != '\n');
314
	return (ret);
315
}
316
317
int
318
sendmail(const char *pathname)
319
{
320
	int filedes[2];
321
	pid_t pid;
322
323
	if (pipe(filedes) == -1) {
324
		warn("pipe: unsent report in %s", pathname);
325
		return (-1);
326
	}
327
	switch ((pid = fork())) {
328
	case -1:
329
		warn("fork error: unsent report in %s",
330
		    pathname);
331
		return (-1);
332
	case 0:
333
		close(filedes[1]);
334
		if (dup2(filedes[0], STDIN_FILENO) == -1) {
335
			warn("dup2 error: unsent report in %s",
336
			    pathname);
337
			return (-1);
338
		}
339
		close(filedes[0]);
340
		execl(_PATH_SENDMAIL, "sendmail",
341
		    "-oi", "-t", (char *)NULL);
342
		warn("sendmail error: unsent report in %s",
343
		    pathname);
344
		return (-1);
345
	default:
346
		close(filedes[0]);
347
		/* Pipe into sendmail. */
348
		if (send_file(pathname, filedes[1]) == -1) {
349
			warn("send_file error: unsent report in %s",
350
			    pathname);
351
			return (-1);
352
		}
353
		close(filedes[1]);
354
		while (waitpid(pid, NULL, 0) == -1) {
355
			if (errno != EINTR)
356
				break;
357
		}
358
		break;
359
	}
360
	return (0);
361
}
362
363
void
364
init(void)
365
{
366
	size_t len;
367
	int sysname[2];
368
	char *cp;
369
370
	if ((pw = getpwuid(getuid())) == NULL)
371
		err(1, "getpwuid");
372
373
	sysname[0] = CTL_KERN;
374
	sysname[1] = KERN_OSTYPE;
375
	len = sizeof(os) - 1;
376
	if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1)
377
		err(1, "sysctl");
378
379
	sysname[0] = CTL_KERN;
380
	sysname[1] = KERN_OSRELEASE;
381
	len = sizeof(rel) - 1;
382
	if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1)
383
		err(1, "sysctl");
384
385
	sysname[0] = CTL_KERN;
386
	sysname[1] = KERN_VERSION;
387
	len = sizeof(details) - 1;
388
	if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1)
389
		err(1, "sysctl");
390
391
	cp = strchr(details, '\n');
392
	if (cp) {
393
		cp++;
394
		if (*cp)
395
			*cp++ = '\t';
396
		if (*cp)
397
			*cp++ = '\t';
398
		if (*cp)
399
			*cp++ = '\t';
400
	}
401
402
	sysname[0] = CTL_HW;
403
	sysname[1] = HW_MACHINE;
404
	len = sizeof(mach) - 1;
405
	if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1)
406
		err(1, "sysctl");
407
}
408
409
int
410
send_file(const char *file, int dst)
411
{
412
	size_t len;
413
	char *buf, *lbuf;
414
	FILE *fp;
415
	int rval = -1, saved_errno;
416
417
	if ((fp = fopen(file, "r")) == NULL)
418
		return (-1);
419
	lbuf = NULL;
420
	while ((buf = fgetln(fp, &len))) {
421
		if (buf[len - 1] == '\n') {
422
			buf[len - 1] = '\0';
423
			--len;
424
		} else {
425
			/* EOF without EOL, copy and add the NUL */
426
			if ((lbuf = malloc(len + 1)) == NULL)
427
				goto end;
428
			memcpy(lbuf, buf, len);
429
			lbuf[len] = '\0';
430
			buf = lbuf;
431
		}
432
433
		/* Skip lines starting with "SENDBUG". */
434
		if (strncmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0)
435
			continue;
436
		while (len) {
437
			char *sp = NULL, *ep = NULL;
438
			size_t copylen;
439
440
			if ((sp = strchr(buf, '<')) != NULL) {
441
				size_t i;
442
443
				for (i = 0; i < sizeof(comment) / sizeof(*comment); ++i) {
444
					size_t commentlen = strlen(comment[i]);
445
446
					if (strncmp(sp, comment[i], commentlen) == 0) {
447
						ep = sp + commentlen - 1;
448
						break;
449
					}
450
				}
451
			}
452
			/* Length of string before comment. */
453
			if (ep)
454
				copylen = sp - buf;
455
			else
456
				copylen = len;
457
			if (atomicio(vwrite, dst, buf, copylen) != copylen)
458
				goto end;
459
			if (!ep)
460
				break;
461
			/* Skip comment. */
462
			len -= ep - buf + 1;
463
			buf = ep + 1;
464
		}
465
		if (atomicio(vwrite, dst, "\n", 1) != 1)
466
			goto end;
467
	}
468
	rval = 0;
469
 end:
470
	saved_errno = errno;
471
	free(lbuf);
472
	fclose(fp);
473
	errno = saved_errno;
474
	return (rval);
475
}
476
477
/*
478
 * Does line start with `s' and end with non-comment and non-whitespace?
479
 * Note: Does not treat `line' as a C string.
480
 */
481
int
482
matchline(const char *s, const char *line, size_t linelen)
483
{
484
	size_t slen;
485
	int iscomment;
486
487
	slen = strlen(s);
488
	/* Is line shorter than string? */
489
	if (linelen <= slen)
490
		return (0);
491
	/* Does line start with string? */
492
	if (memcmp(line, s, slen) != 0)
493
		return (0);
494
	/* Does line contain anything but comments and whitespace? */
495
	line += slen;
496
	linelen -= slen;
497
	iscomment = 0;
498
	while (linelen) {
499
		if (iscomment) {
500
			if (*line == '>')
501
				iscomment = 0;
502
		} else if (*line == '<')
503
			iscomment = 1;
504
		else if (!isspace((unsigned char)*line))
505
			return (1);
506
		++line;
507
		--linelen;
508
	}
509
	return (0);
510
}
511
512
/*
513
 * Are all required fields filled out?
514
 */
515
void
516
checkfile(const char *pathname)
517
{
518
	FILE *fp;
519
	size_t len;
520
	int category = 0, synopsis = 0, subject = 0;
521
	char *buf;
522
523
	if ((fp = fopen(pathname, "r")) == NULL) {
524
		warn("%s", pathname);
525
		return;
526
	}
527
	while ((buf = fgetln(fp, &len))) {
528
		if (matchline(">Category:", buf, len))
529
			category = 1;
530
		else if (matchline(">Synopsis:", buf, len))
531
			synopsis = 1;
532
		else if (matchline("Subject:", buf, len))
533
			subject = 1;
534
	}
535
	fclose(fp);
536
	if (!category || !synopsis || !subject) {
537
		fprintf(stderr, "Some fields are blank, please fill them in: ");
538
		if (!subject)
539
			fprintf(stderr, "Subject ");
540
		if (!synopsis)
541
			fprintf(stderr, "Synopsis ");
542
		if (!category)
543
			fprintf(stderr, "Category ");
544
		fputc('\n', stderr);
545
	}
546
}
547
548
void
549
template(FILE *fp)
550
{
551
	fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
552
	fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will"
553
	    " be removed automatically.\n");
554
	fprintf(fp, "SENDBUG:\n");
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");
560
	fprintf(fp, "To: %s\n", "bugs@openbsd.org");
561
	fprintf(fp, "Subject: \n");
562
	fprintf(fp, "From: %s\n", pw->pw_name);
563
	fprintf(fp, "Cc: %s\n", pw->pw_name);
564
	fprintf(fp, "Reply-To: %s\n", pw->pw_name);
565
	fprintf(fp, "\n");
566
	fprintf(fp, ">Synopsis:\t%s\n", comment[0]);
567
	fprintf(fp, ">Category:\t%s\n", comment[1]);
568
	fprintf(fp, ">Environment:\n");
569
	fprintf(fp, "\tSystem      : %s %s\n", os, rel);
570
	fprintf(fp, "\tDetails     : %s\n", details);
571
	fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
572
	fprintf(fp, "\tMachine     : %s\n", mach);
573
	fprintf(fp, ">Description:\n");
574
	fprintf(fp, "\t%s\n", comment[2]);
575
	fprintf(fp, ">How-To-Repeat:\n");
576
	fprintf(fp, "\t%s\n", comment[3]);
577
	fprintf(fp, ">Fix:\n");
578
	fprintf(fp, "\t%s\n", comment[4]);
579
580
	if (!Dflag) {
581
		int root;
582
583
		fprintf(fp, "\n");
584
		root = !geteuid();
585
		if (!root)
586
			fprintf(fp, "SENDBUG: Run sendbug as root "
587
			    "if this is an ACPI report!\n");
588
		fprintf(fp, "SENDBUG: dmesg%s and usbdevs are attached.\n"
589
		    "SENDBUG: Feel free to delete or use the -D flag if they "
590
		    "contain sensitive information.\n",
591
		    root ? ", pcidump, acpidump" : "");
592
		fputs("\ndmesg:\n", fp);
593
		dmesg(fp);
594
		fputs("\nusbdevs:\n", fp);
595
		usbdevs(fp);
596
		if (root)
597
			hwdump(fp);
598
	}
599
}
600
601
void
602
hwdump(FILE *ofp)
603
{
604
	char buf[BUFSIZ];
605
	FILE *ifp;
606
	char *cmd, *acpidir;
607
	size_t len;
608
609
	if (asprintf(&acpidir, "%s%sp.XXXXXXXXXX", tmpdir,
610
	    tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
611
		err(1, "asprintf");
612
	if (mkdtemp(acpidir) == NULL)
613
		err(1, "mkdtemp");
614
615
	if (asprintf(&cmd, "echo \"\\npcidump:\"; pcidump -xxv; "
616
	    "echo \"\\nacpidump:\"; cd %s && cp /var/db/acpi/* .; "
617
	    "for i in *; do b64encode $i $i; done; rm -rf %s",
618
	    acpidir, acpidir) == -1)
619
		err(1, "asprintf");
620
621
	if ((ifp = popen(cmd, "r")) != NULL) {
622
		while (!feof(ifp)) {
623
			len = fread(buf, 1, sizeof buf, ifp);
624
			if (len == 0)
625
				break;
626
			if (fwrite(buf, 1, len, ofp) != len)
627
				break;
628
		}
629
		pclose(ifp);
630
	}
631
	free(cmd);
632
	free(acpidir);
633
}
634
635
void
636
debase(void)
637
{
638
	char buf[BUFSIZ];
639
	FILE *fp = NULL;
640
	size_t len;
641
642
	while (fgets(buf, sizeof(buf), stdin) != NULL) {
643
		len = strlen(buf);
644
		if (!strncmp(buf, BEGIN64, sizeof(BEGIN64) - 1)) {
645
			if (fp)
646
				errx(1, "double begin");
647
			fp = popen("b64decode", "w");
648
			if (!fp)
649
				errx(1, "popen b64decode");
650
		}
651
		if (fp && fwrite(buf, 1, len, fp) != len)
652
			errx(1, "pipe error");
653
		if (!strncmp(buf, END64, sizeof(END64) - 1)) {
654
			if (pclose(fp) == -1)
655
				errx(1, "pclose b64decode");
656
			fp = NULL;
657
		}
658
	}
659
}