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