1 /* $OpenBSD: sendbug.c,v 1.57 2008/06/14 20:45:45 pvalchev 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 32 int checkfile(const char *); 33 void dmesg(FILE *); 34 int editit(const char *); 35 void init(void); 36 int matchline(const char *, const char *, size_t); 37 int prompt(void); 38 int send_file(const char *, int); 39 int sendmail(const char *); 40 void template(FILE *); 41 42 const char *categories = "system user library documentation kernel " 43 "alpha amd64 arm hppa i386 m68k m88k mips64 powerpc sh sparc sparc64 vax"; 44 char *version = "4.2"; 45 const char *comment[] = { 46 "<synopsis of the problem (one line)>", 47 "<[ non-critical | serious | critical ] (one line)>", 48 "<[ low | medium | high ] (one line)>", 49 "<PR category (one line)>", 50 "<[ sw-bug | doc-bug | change-request | support ] (one line)>", 51 "<release number or tag (one line)>", 52 "<machine, os, target, libraries (multiple lines)>", 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 char *fullname, *tmppath; 61 int Dflag, wantcleanup; 62 63 __dead void 64 usage(void) 65 { 66 extern char *__progname; 67 68 fprintf(stderr, "usage: %s [-DLPV]\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 const char *tmpdir; 85 struct stat sb; 86 char *pr_form; 87 time_t mtime; 88 FILE *fp; 89 90 while ((ch = getopt(argc, argv, "DLPV")) != -1) 91 switch (ch) { 92 case 'D': 93 Dflag = 1; 94 break; 95 case 'L': 96 printf("Known categories:\n"); 97 printf("%s\n\n", categories); 98 exit(0); 99 case 'P': 100 init(); 101 template(stdout); 102 exit(0); 103 case 'V': 104 printf("%s\n", version); 105 exit(0); 106 default: 107 usage(); 108 } 109 argc -= optind; 110 argv += optind; 111 112 if (argc > 0) 113 usage(); 114 115 if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0') 116 tmpdir = _PATH_TMP; 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 if (!checkfile(tmppath)) 168 fprintf(stderr, "fields are blank, must be filled in\n"); 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; 186 quit: 187 return (ret); 188 } 189 190 void 191 dmesg(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 fputs("\n" 204 "SENDBUG: dmesg is attached.\n" 205 "SENDBUG: Feel free to delete or use the -D flag if it contains " 206 "sensitive information.\n", fp); 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 amp, len, gecoslen, namelen; 348 int sysname[2]; 349 char ch, *cp; 350 351 if ((pw = getpwuid(getuid())) == NULL) 352 err(1, "getpwuid"); 353 namelen = strlen(pw->pw_name); 354 355 /* Count number of '&'. */ 356 for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp) 357 if (*cp == '&') 358 ++amp; 359 360 /* Truncate gecos to full name. */ 361 gecoslen = cp - pw->pw_gecos; 362 pw->pw_gecos[gecoslen] = '\0'; 363 364 /* Expanded str = orig str - '&' chars + concatenated logins. */ 365 len = gecoslen - amp + (amp * namelen) + 1; 366 if ((fullname = malloc(len)) == NULL) 367 err(1, "malloc"); 368 369 /* Upper case first char of login. */ 370 ch = pw->pw_name[0]; 371 pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]); 372 373 cp = pw->pw_gecos; 374 fullname[0] = '\0'; 375 while (cp != NULL) { 376 char *token; 377 378 token = strsep(&cp, "&"); 379 if (token != pw->pw_gecos && 380 strlcat(fullname, pw->pw_name, len) >= len) 381 errx(1, "truncated string"); 382 if (strlcat(fullname, token, len) >= len) 383 errx(1, "truncated string"); 384 } 385 /* Restore case of first char of login. */ 386 pw->pw_name[0] = ch; 387 388 sysname[0] = CTL_KERN; 389 sysname[1] = KERN_OSTYPE; 390 len = sizeof(os) - 1; 391 if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) 392 err(1, "sysctl"); 393 394 sysname[0] = CTL_KERN; 395 sysname[1] = KERN_OSRELEASE; 396 len = sizeof(rel) - 1; 397 if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) 398 err(1, "sysctl"); 399 400 sysname[0] = CTL_KERN; 401 sysname[1] = KERN_VERSION; 402 len = sizeof(details) - 1; 403 if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1) 404 err(1, "sysctl"); 405 406 cp = strchr(details, '\n'); 407 if (cp) { 408 cp++; 409 if (*cp) 410 *cp++ = '\t'; 411 if (*cp) 412 *cp++ = '\t'; 413 if (*cp) 414 *cp++ = '\t'; 415 } 416 417 sysname[0] = CTL_HW; 418 sysname[1] = HW_MACHINE; 419 len = sizeof(mach) - 1; 420 if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) 421 err(1, "sysctl"); 422 } 423 424 int 425 send_file(const char *file, int dst) 426 { 427 size_t len; 428 char *buf, *lbuf; 429 FILE *fp; 430 int rval = -1, saved_errno; 431 432 if ((fp = fopen(file, "r")) == NULL) 433 return (-1); 434 lbuf = NULL; 435 while ((buf = fgetln(fp, &len))) { 436 if (buf[len - 1] == '\n') { 437 buf[len - 1] = '\0'; 438 --len; 439 } else { 440 /* EOF without EOL, copy and add the NUL */ 441 if ((lbuf = malloc(len + 1)) == NULL) 442 goto end; 443 memcpy(lbuf, buf, len); 444 lbuf[len] = '\0'; 445 buf = lbuf; 446 } 447 448 /* Skip lines starting with "SENDBUG". */ 449 if (strncmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0) 450 continue; 451 while (len) { 452 char *sp = NULL, *ep = NULL; 453 size_t copylen; 454 455 if ((sp = strchr(buf, '<')) != NULL) { 456 size_t i; 457 458 for (i = 0; i < sizeof(comment) / sizeof(*comment); ++i) { 459 size_t commentlen = strlen(comment[i]); 460 461 if (strncmp(sp, comment[i], commentlen) == 0) { 462 ep = sp + commentlen - 1; 463 break; 464 } 465 } 466 } 467 /* Length of string before comment. */ 468 if (ep) 469 copylen = sp - buf; 470 else 471 copylen = len; 472 if (atomicio(vwrite, dst, buf, copylen) != copylen) 473 goto end; 474 if (!ep) 475 break; 476 /* Skip comment. */ 477 len -= ep - buf + 1; 478 buf = ep + 1; 479 } 480 if (atomicio(vwrite, dst, "\n", 1) != 1) 481 goto end; 482 } 483 rval = 0; 484 end: 485 saved_errno = errno; 486 free(lbuf); 487 fclose(fp); 488 errno = saved_errno; 489 return (rval); 490 } 491 492 /* 493 * Does line start with `s' and end with non-comment and non-whitespace? 494 * Note: Does not treat `line' as a C string. 495 */ 496 int 497 matchline(const char *s, const char *line, size_t linelen) 498 { 499 size_t slen; 500 int iscomment; 501 502 slen = strlen(s); 503 /* Is line shorter than string? */ 504 if (linelen <= slen) 505 return (0); 506 /* Does line start with string? */ 507 if (memcmp(line, s, slen) != 0) 508 return (0); 509 /* Does line contain anything but comments and whitespace? */ 510 line += slen; 511 linelen -= slen; 512 iscomment = 0; 513 while (linelen) { 514 if (iscomment) { 515 if (*line == '>') 516 iscomment = 0; 517 } else if (*line == '<') 518 iscomment = 1; 519 else if (!isspace((unsigned char)*line)) 520 return (1); 521 ++line; 522 --linelen; 523 } 524 return (0); 525 } 526 527 /* 528 * Are all required fields filled out? 529 */ 530 int 531 checkfile(const char *pathname) 532 { 533 FILE *fp; 534 size_t len; 535 int category, class, priority, release, severity, synopsis; 536 char *buf; 537 538 if ((fp = fopen(pathname, "r")) == NULL) { 539 warn("%s", pathname); 540 return (0); 541 } 542 category = class = priority = release = severity = synopsis = 0; 543 while ((buf = fgetln(fp, &len))) { 544 if (matchline(">Category:", buf, len)) 545 category = 1; 546 else if (matchline(">Class:", buf, len)) 547 class = 1; 548 else if (matchline(">Priority:", buf, len)) 549 priority = 1; 550 else if (matchline(">Release:", buf, len)) 551 release = 1; 552 else if (matchline(">Severity:", buf, len)) 553 severity = 1; 554 else if (matchline(">Synopsis:", buf, len)) 555 synopsis = 1; 556 } 557 fclose(fp); 558 return (category && class && priority && release && severity && 559 synopsis); 560 } 561 562 void 563 template(FILE *fp) 564 { 565 fprintf(fp, "SENDBUG: -*- sendbug -*-\n"); 566 fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will" 567 " be removed automatically.\n"); 568 fprintf(fp, "SENDBUG:\n"); 569 fprintf(fp, "SENDBUG: Choose from the following categories:\n"); 570 fprintf(fp, "SENDBUG:\n"); 571 fprintf(fp, "SENDBUG: %s\n", categories); 572 fprintf(fp, "SENDBUG:\n"); 573 fprintf(fp, "SENDBUG:\n"); 574 fprintf(fp, "To: %s\n", "gnats@openbsd.org"); 575 fprintf(fp, "Subject: \n"); 576 fprintf(fp, "From: %s\n", pw->pw_name); 577 fprintf(fp, "Cc: %s\n", pw->pw_name); 578 fprintf(fp, "Reply-To: %s\n", pw->pw_name); 579 fprintf(fp, "X-sendbug-version: %s\n", version); 580 fprintf(fp, "\n"); 581 fprintf(fp, "\n"); 582 fprintf(fp, ">Submitter-Id:\tnet\n"); 583 fprintf(fp, ">Originator:\t%s\n", fullname); 584 fprintf(fp, ">Organization:\n"); 585 fprintf(fp, "net\n"); 586 fprintf(fp, ">Synopsis:\t%s\n", comment[0]); 587 fprintf(fp, ">Severity:\t%s\n", comment[1]); 588 fprintf(fp, ">Priority:\t%s\n", comment[2]); 589 fprintf(fp, ">Category:\t%s\n", comment[3]); 590 fprintf(fp, ">Class:\t\t%s\n", comment[4]); 591 fprintf(fp, ">Release:\t%s\n", comment[5]); 592 fprintf(fp, ">Environment:\n"); 593 fprintf(fp, "\t%s\n", comment[6]); 594 fprintf(fp, "\tSystem : %s %s\n", os, rel); 595 fprintf(fp, "\tDetails : %s\n", details); 596 fprintf(fp, "\tArchitecture: %s.%s\n", os, mach); 597 fprintf(fp, "\tMachine : %s\n", mach); 598 fprintf(fp, ">Description:\n"); 599 fprintf(fp, "\t%s\n", comment[7]); 600 fprintf(fp, ">How-To-Repeat:\n"); 601 fprintf(fp, "\t%s\n", comment[8]); 602 fprintf(fp, ">Fix:\n"); 603 fprintf(fp, "\t%s\n", comment[9]); 604 605 if (!Dflag) 606 dmesg(fp); 607 } 608