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 33void checkfile(const char *); 34void debase(void); 35void dmesg(FILE *); 36int editit(const char *); 37void hwdump(FILE *); 38void init(void); 39int matchline(const char *, const char *, size_t); 40int prompt(void); 41int send_file(const char *, int); 42int sendmail(const char *); 43void template(FILE *); 44void usbdevs(FILE *); 45 46const char *categories = "system user library documentation kernel " 47 "alpha aarch64 amd64 arm hppa i386 m88k mips64 mips64el powerpc powerpc64 " 48 "riscv64 sh sparc64"; 49const 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 57struct passwd *pw; 58char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ]; 59const char *tmpdir = _PATH_TMP; 60char *tmppath; 61int Dflag, Pflag, wantcleanup; 62 63__dead void 64usage(void) 65{ 66 extern char *__progname; 67 68 fprintf(stderr, "usage: %s [-DEP]\n", __progname); 69 exit(1); 70} 71 72void 73cleanup() 74{ 75 if (wantcleanup && tmppath && unlink(tmppath) == -1) 76 warn("unlink"); 77} 78 79 80int 81main(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; 186quit: 187 return (ret); 188} 189 190void 191dmesg(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 229void 230usbdevs(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 */ 254int 255editit(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 301int 302prompt(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 318int 319sendmail(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 364void 365init(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 410int 411send_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 */ 482int 483matchline(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 */ 516void 517checkfile(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 549void 550template(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 602void 603hwdump(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 636void 637debase(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