1 /* $OpenBSD: sendbug.c,v 1.80 2022/04/10 17:47:54 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 aarch64 amd64 arm hppa i386 m88k mips64 mips64el powerpc powerpc64 "
48 "riscv64 sh sparc64";
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 = _PATH_TMP;
60 char *tmppath;
61 int Dflag, Pflag, wantcleanup;
62
63 __dead void
usage(void)64 usage(void)
65 {
66 extern char *__progname;
67
68 fprintf(stderr, "usage: %s [-DEP]\n", __progname);
69 exit(1);
70 }
71
72 void
cleanup()73 cleanup()
74 {
75 if (wantcleanup && tmppath && unlink(tmppath) == -1)
76 warn("unlink");
77 }
78
79
80 int
main(int argc,char * argv[])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 if (pledge("stdio rpath wpath cpath tmppath getpw proc exec", NULL) == -1)
90 err(1, "pledge");
91
92 while ((ch = getopt(argc, argv, "DEP")) != -1)
93 switch (ch) {
94 case 'D':
95 Dflag = 1;
96 break;
97 case 'E':
98 debase();
99 exit(0);
100 case 'P':
101 Pflag = 1;
102 break;
103 default:
104 usage();
105 }
106 argc -= optind;
107 argv += optind;
108
109 if (argc > 0)
110 usage();
111
112 if (Pflag) {
113 init();
114 template(stdout);
115 exit(0);
116 }
117
118 if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir,
119 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
120 err(1, "asprintf");
121 if ((fd = mkstemp(tmppath)) == -1)
122 err(1, "mkstemp");
123 wantcleanup = 1;
124 atexit(cleanup);
125 if ((fp = fdopen(fd, "w+")) == NULL)
126 err(1, "fdopen");
127
128 init();
129
130 pr_form = getenv("PR_FORM");
131 if (pr_form) {
132 char buf[BUFSIZ];
133 size_t len;
134 FILE *frfp;
135
136 frfp = fopen(pr_form, "r");
137 if (frfp == NULL) {
138 warn("can't seem to read your template file "
139 "(`%s'), ignoring PR_FORM", pr_form);
140 template(fp);
141 } else {
142 while (!feof(frfp)) {
143 len = fread(buf, 1, sizeof buf, frfp);
144 if (len == 0)
145 break;
146 if (fwrite(buf, 1, len, fp) != len)
147 break;
148 }
149 fclose(frfp);
150 }
151 } else
152 template(fp);
153
154 if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF)
155 err(1, "error creating template");
156 mtime = sb.st_mtime;
157
158 edit:
159 if (editit(tmppath) == -1)
160 err(1, "error running editor");
161
162 if (stat(tmppath, &sb) == -1)
163 err(1, "stat");
164 if (mtime == sb.st_mtime)
165 errx(1, "report unchanged, nothing sent");
166
167 prompt:
168 checkfile(tmppath);
169 c = prompt();
170 switch (c) {
171 case 'a':
172 case EOF:
173 wantcleanup = 0;
174 errx(1, "unsent report in %s", tmppath);
175 case 'e':
176 goto edit;
177 case 's':
178 if (sendmail(tmppath) == -1)
179 goto quit;
180 break;
181 default:
182 goto prompt;
183 }
184
185 ret = 0;
186 quit:
187 return (ret);
188 }
189
190 void
dmesg(FILE * fp)191 dmesg(FILE *fp)
192 {
193 char buf[BUFSIZ];
194 FILE *dfp;
195 off_t offset = -1;
196
197 dfp = fopen(_PATH_DMESG, "r");
198 if (dfp == NULL) {
199 warn("can't read dmesg");
200 return;
201 }
202
203 /* Find last dmesg. */
204 for (;;) {
205 off_t o;
206
207 o = ftello(dfp);
208 if (fgets(buf, sizeof(buf), dfp) == NULL)
209 break;
210 if (!strncmp(DMESG_START, buf, sizeof(DMESG_START) - 1))
211 offset = o;
212 }
213 if (offset != -1) {
214 size_t len;
215
216 clearerr(dfp);
217 fseeko(dfp, offset, SEEK_SET);
218 while (offset != -1 && !feof(dfp)) {
219 len = fread(buf, 1, sizeof buf, dfp);
220 if (len == 0)
221 break;
222 if (fwrite(buf, 1, len, fp) != len)
223 break;
224 }
225 }
226 fclose(dfp);
227 }
228
229 void
usbdevs(FILE * ofp)230 usbdevs(FILE *ofp)
231 {
232 char buf[BUFSIZ];
233 FILE *ifp;
234 size_t len;
235
236 if ((ifp = popen("usbdevs -v", "r")) != NULL) {
237 while (!feof(ifp)) {
238 len = fread(buf, 1, sizeof buf, ifp);
239 if (len == 0)
240 break;
241 if (fwrite(buf, 1, len, ofp) != len)
242 break;
243 }
244 pclose(ifp);
245 }
246 }
247
248 /*
249 * Execute an editor on the specified pathname, which is interpreted
250 * from the shell. This means flags may be included.
251 *
252 * Returns -1 on error, or the exit value on success.
253 */
254 int
editit(const char * pathname)255 editit(const char *pathname)
256 {
257 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p;
258 sig_t sighup, sigint, sigquit, sigchld;
259 pid_t pid;
260 int saved_errno, st, ret = -1;
261
262 ed = getenv("VISUAL");
263 if (ed == NULL || ed[0] == '\0')
264 ed = getenv("EDITOR");
265 if (ed == NULL || ed[0] == '\0')
266 ed = _PATH_VI;
267 if (asprintf(&p, "%s %s", ed, pathname) == -1)
268 return (-1);
269 argp[2] = p;
270
271 sighup = signal(SIGHUP, SIG_IGN);
272 sigint = signal(SIGINT, SIG_IGN);
273 sigquit = signal(SIGQUIT, SIG_IGN);
274 sigchld = signal(SIGCHLD, SIG_DFL);
275 if ((pid = fork()) == -1)
276 goto fail;
277 if (pid == 0) {
278 execv(_PATH_BSHELL, argp);
279 _exit(127);
280 }
281 while (waitpid(pid, &st, 0) == -1) {
282 if (errno != EINTR)
283 goto fail;
284 }
285 if (!WIFEXITED(st))
286 errno = EINTR;
287 else
288 ret = WEXITSTATUS(st);
289
290 fail:
291 saved_errno = errno;
292 (void)signal(SIGHUP, sighup);
293 (void)signal(SIGINT, sigint);
294 (void)signal(SIGQUIT, sigquit);
295 (void)signal(SIGCHLD, sigchld);
296 free(p);
297 errno = saved_errno;
298 return (ret);
299 }
300
301 int
prompt(void)302 prompt(void)
303 {
304 int c, ret;
305
306 fpurge(stdin);
307 fprintf(stderr, "a)bort, e)dit, or s)end: ");
308 fflush(stderr);
309 ret = getchar();
310 if (ret == EOF || ret == '\n')
311 return (ret);
312 do {
313 c = getchar();
314 } while (c != EOF && c != '\n');
315 return (ret);
316 }
317
318 int
sendmail(const char * pathname)319 sendmail(const char *pathname)
320 {
321 int filedes[2];
322 pid_t pid;
323
324 if (pipe(filedes) == -1) {
325 warn("pipe: unsent report in %s", pathname);
326 return (-1);
327 }
328 switch ((pid = fork())) {
329 case -1:
330 warn("fork error: unsent report in %s",
331 pathname);
332 return (-1);
333 case 0:
334 close(filedes[1]);
335 if (dup2(filedes[0], STDIN_FILENO) == -1) {
336 warn("dup2 error: unsent report in %s",
337 pathname);
338 return (-1);
339 }
340 close(filedes[0]);
341 execl(_PATH_SENDMAIL, "sendmail",
342 "-oi", "-t", (char *)NULL);
343 warn("sendmail error: unsent report in %s",
344 pathname);
345 return (-1);
346 default:
347 close(filedes[0]);
348 /* Pipe into sendmail. */
349 if (send_file(pathname, filedes[1]) == -1) {
350 warn("send_file error: unsent report in %s",
351 pathname);
352 return (-1);
353 }
354 close(filedes[1]);
355 while (waitpid(pid, NULL, 0) == -1) {
356 if (errno != EINTR)
357 break;
358 }
359 break;
360 }
361 return (0);
362 }
363
364 void
init(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
send_file(const char * file,int dst)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
matchline(const char * s,const char * line,size_t linelen)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 void
checkfile(const char * pathname)517 checkfile(const char *pathname)
518 {
519 FILE *fp;
520 size_t len;
521 int category = 0, synopsis = 0, subject = 0;
522 char *buf;
523
524 if ((fp = fopen(pathname, "r")) == NULL) {
525 warn("%s", pathname);
526 return;
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 else if (matchline("Subject:", buf, len))
534 subject = 1;
535 }
536 fclose(fp);
537 if (!category || !synopsis || !subject) {
538 fprintf(stderr, "Some fields are blank, please fill them in: ");
539 if (!subject)
540 fprintf(stderr, "Subject ");
541 if (!synopsis)
542 fprintf(stderr, "Synopsis ");
543 if (!category)
544 fprintf(stderr, "Category ");
545 fputc('\n', stderr);
546 }
547 }
548
549 void
template(FILE * fp)550 template(FILE *fp)
551 {
552 fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
553 fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will"
554 " be removed automatically.\n");
555 fprintf(fp, "SENDBUG:\n");
556 fprintf(fp, "SENDBUG: Choose from the following categories:\n");
557 fprintf(fp, "SENDBUG:\n");
558 fprintf(fp, "SENDBUG: %s\n", categories);
559 fprintf(fp, "SENDBUG:\n");
560 fprintf(fp, "SENDBUG:\n");
561 fprintf(fp, "To: %s\n", "bugs@openbsd.org");
562 fprintf(fp, "Subject: \n");
563 fprintf(fp, "From: %s\n", pw->pw_name);
564 fprintf(fp, "Cc: %s\n", pw->pw_name);
565 fprintf(fp, "Reply-To: %s\n", pw->pw_name);
566 fprintf(fp, "\n");
567 fprintf(fp, ">Synopsis:\t%s\n", comment[0]);
568 fprintf(fp, ">Category:\t%s\n", comment[1]);
569 fprintf(fp, ">Environment:\n");
570 fprintf(fp, "\tSystem : %s %s\n", os, rel);
571 fprintf(fp, "\tDetails : %s\n", details);
572 fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
573 fprintf(fp, "\tMachine : %s\n", mach);
574 fprintf(fp, ">Description:\n");
575 fprintf(fp, "\t%s\n", comment[2]);
576 fprintf(fp, ">How-To-Repeat:\n");
577 fprintf(fp, "\t%s\n", comment[3]);
578 fprintf(fp, ">Fix:\n");
579 fprintf(fp, "\t%s\n", comment[4]);
580
581 if (!Dflag) {
582 int root;
583
584 fprintf(fp, "\n");
585 root = !geteuid();
586 if (!root)
587 fprintf(fp, "SENDBUG: Run sendbug as root "
588 "if this is an ACPI report!\n");
589 fprintf(fp, "SENDBUG: dmesg%s and usbdevs are attached.\n"
590 "SENDBUG: Feel free to delete or use the -D flag if they "
591 "contain sensitive information.\n",
592 root ? ", pcidump, acpidump" : "");
593 fputs("\ndmesg:\n", fp);
594 dmesg(fp);
595 fputs("\nusbdevs:\n", fp);
596 usbdevs(fp);
597 if (root)
598 hwdump(fp);
599 }
600 }
601
602 void
hwdump(FILE * ofp)603 hwdump(FILE *ofp)
604 {
605 char buf[BUFSIZ];
606 FILE *ifp;
607 char *cmd, *acpidir;
608 size_t len;
609
610 if (asprintf(&acpidir, "%s%sp.XXXXXXXXXX", tmpdir,
611 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1)
612 err(1, "asprintf");
613 if (mkdtemp(acpidir) == NULL)
614 err(1, "mkdtemp");
615
616 if (asprintf(&cmd, "echo \"\\npcidump:\"; pcidump -xxv; "
617 "echo \"\\nacpidump:\"; cd %s && cp /var/db/acpi/* .; "
618 "for i in *; do b64encode $i $i; done; rm -rf %s",
619 acpidir, acpidir) == -1)
620 err(1, "asprintf");
621
622 if ((ifp = popen(cmd, "r")) != NULL) {
623 while (!feof(ifp)) {
624 len = fread(buf, 1, sizeof buf, ifp);
625 if (len == 0)
626 break;
627 if (fwrite(buf, 1, len, ofp) != len)
628 break;
629 }
630 pclose(ifp);
631 }
632 free(cmd);
633 free(acpidir);
634 }
635
636 void
debase(void)637 debase(void)
638 {
639 char buf[BUFSIZ];
640 FILE *fp = NULL;
641 size_t len;
642
643 while (fgets(buf, sizeof(buf), stdin) != NULL) {
644 len = strlen(buf);
645 if (!strncmp(buf, BEGIN64, sizeof(BEGIN64) - 1)) {
646 if (fp)
647 errx(1, "double begin");
648 fp = popen("b64decode", "w");
649 if (!fp)
650 errx(1, "popen b64decode");
651 }
652 if (fp && fwrite(buf, 1, len, fp) != len)
653 errx(1, "pipe error");
654 if (!strncmp(buf, END64, sizeof(END64) - 1)) {
655 if (pclose(fp) == -1)
656 errx(1, "pclose b64decode");
657 fp = NULL;
658 }
659 }
660 }
661