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 } 659