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