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