1 /* $OpenBSD: sendbug.c,v 1.52 2007/10/17 20:02:33 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 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 ports kernel " 43 "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax"; 44 char *version = "4.2"; 45 46 struct passwd *pw; 47 char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ]; 48 char *fullname, *tmppath; 49 int Dflag, wantcleanup; 50 51 __dead void 52 usage(void) 53 { 54 extern char *__progname; 55 56 fprintf(stderr, "usage: %s [-DLPV]\n", __progname); 57 exit(1); 58 } 59 60 void 61 cleanup() 62 { 63 if (wantcleanup && tmppath && unlink(tmppath) == -1) 64 warn("unlink"); 65 } 66 67 68 int 69 main(int argc, char *argv[]) 70 { 71 int ch, c, fd, ret = 1; 72 const char *tmpdir; 73 struct stat sb; 74 char *pr_form; 75 time_t mtime; 76 FILE *fp; 77 78 while ((ch = getopt(argc, argv, "DLPV")) != -1) 79 switch (ch) { 80 case 'D': 81 Dflag = 1; 82 break; 83 case 'L': 84 printf("Known categories:\n"); 85 printf("%s\n\n", categories); 86 exit(0); 87 case 'P': 88 init(); 89 template(stdout); 90 exit(0); 91 case 'V': 92 printf("%s\n", version); 93 exit(0); 94 default: 95 usage(); 96 } 97 argc -= optind; 98 argv += optind; 99 100 if (argc > 0) 101 usage(); 102 103 if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0') 104 tmpdir = _PATH_TMP; 105 if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir, 106 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1) 107 err(1, "asprintf"); 108 if ((fd = mkstemp(tmppath)) == -1) 109 err(1, "mkstemp"); 110 wantcleanup = 1; 111 atexit(cleanup); 112 if ((fp = fdopen(fd, "w+")) == NULL) 113 err(1, "fdopen"); 114 115 init(); 116 117 pr_form = getenv("PR_FORM"); 118 if (pr_form) { 119 char buf[BUFSIZ]; 120 size_t len; 121 FILE *frfp; 122 123 frfp = fopen(pr_form, "r"); 124 if (frfp == NULL) { 125 warn("can't seem to read your template file " 126 "(`%s'), ignoring PR_FORM", pr_form); 127 template(fp); 128 } else { 129 while (!feof(frfp)) { 130 len = fread(buf, 1, sizeof buf, frfp); 131 if (len == 0) 132 break; 133 if (fwrite(buf, 1, len, fp) != len) 134 break; 135 } 136 fclose(frfp); 137 } 138 } else 139 template(fp); 140 141 if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF) 142 err(1, "error creating template"); 143 mtime = sb.st_mtime; 144 145 edit: 146 if (editit(tmppath) == -1) 147 err(1, "error running editor"); 148 149 if (stat(tmppath, &sb) == -1) 150 err(1, "stat"); 151 if (mtime == sb.st_mtime) 152 errx(1, "report unchanged, nothing sent"); 153 154 prompt: 155 if (!checkfile(tmppath)) 156 fprintf(stderr, "fields are blank, must be filled in\n"); 157 c = prompt(); 158 switch (c) { 159 case 'a': 160 case EOF: 161 wantcleanup = 0; 162 errx(1, "unsent report in %s", tmppath); 163 case 'e': 164 goto edit; 165 case 's': 166 if (sendmail(tmppath) == -1) 167 goto quit; 168 break; 169 default: 170 goto prompt; 171 } 172 173 ret = 0; 174 quit: 175 return (ret); 176 } 177 178 void 179 dmesg(FILE *fp) 180 { 181 char buf[BUFSIZ]; 182 FILE *dfp; 183 off_t offset = -1; 184 185 dfp = fopen(_PATH_DMESG, "r"); 186 if (dfp == NULL) { 187 warn("can't read dmesg"); 188 return; 189 } 190 191 fputs("\n" 192 "<dmesg is attached.>\n" 193 "<Feel free to delete or use the -D flag if it contains " 194 "sensitive information.>\n", fp); 195 /* Find last dmesg. */ 196 for (;;) { 197 off_t o; 198 199 o = ftello(dfp); 200 if (fgets(buf, sizeof(buf), dfp) == NULL) 201 break; 202 if (!strncmp(DMESG_START, buf, sizeof(DMESG_START) - 1)) 203 offset = o; 204 } 205 if (offset != -1) { 206 size_t len; 207 208 clearerr(dfp); 209 fseeko(dfp, offset, SEEK_SET); 210 while (offset != -1 && !feof(dfp)) { 211 len = fread(buf, 1, sizeof buf, dfp); 212 if (len == 0) 213 break; 214 if (fwrite(buf, 1, len, fp) != len) 215 break; 216 } 217 } 218 fclose(dfp); 219 } 220 221 /* 222 * Execute an editor on the specified pathname, which is interpreted 223 * from the shell. This means flags may be included. 224 * 225 * Returns -1 on error, or the exit value on success. 226 */ 227 int 228 editit(const char *pathname) 229 { 230 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 231 sig_t sighup, sigint, sigquit, sigchld; 232 pid_t pid; 233 int saved_errno, st, ret = -1; 234 235 ed = getenv("VISUAL"); 236 if (ed == NULL || ed[0] == '\0') 237 ed = getenv("EDITOR"); 238 if (ed == NULL || ed[0] == '\0') 239 ed = _PATH_VI; 240 if (asprintf(&p, "%s %s", ed, pathname) == -1) 241 return (-1); 242 argp[2] = p; 243 244 sighup = signal(SIGHUP, SIG_IGN); 245 sigint = signal(SIGINT, SIG_IGN); 246 sigquit = signal(SIGQUIT, SIG_IGN); 247 sigchld = signal(SIGCHLD, SIG_DFL); 248 if ((pid = fork()) == -1) 249 goto fail; 250 if (pid == 0) { 251 execv(_PATH_BSHELL, argp); 252 _exit(127); 253 } 254 while (waitpid(pid, &st, 0) == -1) 255 if (errno != EINTR) 256 goto fail; 257 if (!WIFEXITED(st)) 258 errno = EINTR; 259 else 260 ret = WEXITSTATUS(st); 261 262 fail: 263 saved_errno = errno; 264 (void)signal(SIGHUP, sighup); 265 (void)signal(SIGINT, sigint); 266 (void)signal(SIGQUIT, sigquit); 267 (void)signal(SIGCHLD, sigchld); 268 free(p); 269 errno = saved_errno; 270 return (ret); 271 } 272 273 int 274 prompt(void) 275 { 276 int c, ret; 277 278 fpurge(stdin); 279 fprintf(stderr, "a)bort, e)dit, or s)end: "); 280 fflush(stderr); 281 ret = getchar(); 282 if (ret == EOF || ret == '\n') 283 return (ret); 284 do { 285 c = getchar(); 286 } while (c != EOF && c != '\n'); 287 return (ret); 288 } 289 290 int 291 sendmail(const char *pathname) 292 { 293 int filedes[2]; 294 295 if (pipe(filedes) == -1) { 296 warn("pipe: unsent report in %s", pathname); 297 return (-1); 298 } 299 switch (fork()) { 300 case -1: 301 warn("fork error: unsent report in %s", 302 pathname); 303 return (-1); 304 case 0: 305 close(filedes[1]); 306 if (dup2(filedes[0], STDIN_FILENO) == -1) { 307 warn("dup2 error: unsent report in %s", 308 pathname); 309 return (-1); 310 } 311 close(filedes[0]); 312 execl("/usr/sbin/sendmail", "sendmail", 313 "-oi", "-t", (void *)NULL); 314 warn("sendmail error: unsent report in %s", 315 pathname); 316 return (-1); 317 default: 318 close(filedes[0]); 319 /* Pipe into sendmail. */ 320 if (send_file(pathname, filedes[1]) == -1) { 321 warn("send_file error: unsent report in %s", 322 pathname); 323 return (-1); 324 } 325 close(filedes[1]); 326 wait(NULL); 327 break; 328 } 329 return (0); 330 } 331 332 void 333 init(void) 334 { 335 size_t amp, len, gecoslen, namelen; 336 int sysname[2]; 337 char ch, *cp; 338 339 if ((pw = getpwuid(getuid())) == NULL) 340 err(1, "getpwuid"); 341 namelen = strlen(pw->pw_name); 342 343 /* Count number of '&'. */ 344 for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp) 345 if (*cp == '&') 346 ++amp; 347 348 /* Truncate gecos to full name. */ 349 gecoslen = cp - pw->pw_gecos; 350 pw->pw_gecos[gecoslen] = '\0'; 351 352 /* Expanded str = orig str - '&' chars + concatenated logins. */ 353 len = gecoslen - amp + (amp * namelen) + 1; 354 if ((fullname = malloc(len)) == NULL) 355 err(1, "malloc"); 356 357 /* Upper case first char of login. */ 358 ch = pw->pw_name[0]; 359 pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]); 360 361 cp = pw->pw_gecos; 362 fullname[0] = '\0'; 363 while (cp != NULL) { 364 char *token; 365 366 token = strsep(&cp, "&"); 367 if (token != pw->pw_gecos && 368 strlcat(fullname, pw->pw_name, len) >= len) 369 errx(1, "truncated string"); 370 if (strlcat(fullname, token, len) >= len) 371 errx(1, "truncated string"); 372 } 373 /* Restore case of first char of login. */ 374 pw->pw_name[0] = ch; 375 376 sysname[0] = CTL_KERN; 377 sysname[1] = KERN_OSTYPE; 378 len = sizeof(os) - 1; 379 if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) 380 err(1, "sysctl"); 381 382 sysname[0] = CTL_KERN; 383 sysname[1] = KERN_OSRELEASE; 384 len = sizeof(rel) - 1; 385 if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) 386 err(1, "sysctl"); 387 388 sysname[0] = CTL_KERN; 389 sysname[1] = KERN_VERSION; 390 len = sizeof(details) - 1; 391 if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1) 392 err(1, "sysctl"); 393 394 cp = strchr(details, '\n'); 395 if (cp) { 396 cp++; 397 if (*cp) 398 *cp++ = '\t'; 399 if (*cp) 400 *cp++ = '\t'; 401 if (*cp) 402 *cp++ = '\t'; 403 } 404 405 sysname[0] = CTL_HW; 406 sysname[1] = HW_MACHINE; 407 len = sizeof(mach) - 1; 408 if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) 409 err(1, "sysctl"); 410 } 411 412 int 413 send_file(const char *file, int dst) 414 { 415 size_t len; 416 char *buf; 417 FILE *fp; 418 int blank = 0, dmesg_line = 0; 419 420 if ((fp = fopen(file, "r")) == NULL) 421 return (-1); 422 while ((buf = fgetln(fp, &len))) { 423 /* Skip lines starting with "SENDBUG". */ 424 if (len >= sizeof("SENDBUG") - 1 && 425 memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0) 426 continue; 427 /* Are we done with the headers? */ 428 if (!blank && len == 1 && buf[0] == '\n') 429 blank = 1; 430 /* Have we reached the dmesg? */ 431 if (blank && !dmesg_line && 432 len >= sizeof(DMESG_START) - 1 && 433 memcmp(buf, DMESG_START, sizeof(DMESG_START) - 1) == 0) 434 dmesg_line = 1; 435 /* Skip comments between headers and dmesg. */ 436 while (len) { 437 char *sp = NULL, *ep = NULL; 438 size_t copylen; 439 440 if (blank && !dmesg_line && 441 (sp = memchr(buf, '<', len)) != NULL) 442 ep = memchr(sp, '>', len - (sp - buf + 1)); 443 /* Length of string before comment. */ 444 if (ep) 445 copylen = sp - buf; 446 else 447 copylen = len; 448 if (atomicio(vwrite, dst, buf, copylen) != copylen) { 449 int saved_errno = errno; 450 451 fclose(fp); 452 errno = saved_errno; 453 return (-1); 454 } 455 if (!ep) 456 break; 457 /* Skip comment. */ 458 len -= ep - buf + 1; 459 buf = ep + 1; 460 } 461 } 462 fclose(fp); 463 return (0); 464 } 465 466 /* 467 * Does line start with `s' and end with non-comment and non-whitespace? 468 * Note: Does not treat `line' as a C string. 469 */ 470 int 471 matchline(const char *s, const char *line, size_t linelen) 472 { 473 size_t slen; 474 int comment; 475 476 slen = strlen(s); 477 /* Is line shorter than string? */ 478 if (linelen <= slen) 479 return (0); 480 /* Does line start with string? */ 481 if (memcmp(line, s, slen) != 0) 482 return (0); 483 /* Does line contain anything but comments and whitespace? */ 484 line += slen; 485 linelen -= slen; 486 comment = 0; 487 while (linelen) { 488 if (comment) { 489 if (*line == '>') 490 comment = 0; 491 } else if (*line == '<') 492 comment = 1; 493 else if (!isspace((unsigned char)*line)) 494 return (1); 495 ++line; 496 --linelen; 497 } 498 return (0); 499 } 500 501 /* 502 * Are all required fields filled out? 503 */ 504 int 505 checkfile(const char *pathname) 506 { 507 FILE *fp; 508 size_t len; 509 int category, class, priority, release, severity, synopsis; 510 char *buf; 511 512 if ((fp = fopen(pathname, "r")) == NULL) { 513 warn("%s", pathname); 514 return (0); 515 } 516 category = class = priority = release = severity = synopsis = 0; 517 while ((buf = fgetln(fp, &len))) { 518 if (matchline(">Category:", buf, len)) 519 category = 1; 520 else if (matchline(">Class:", buf, len)) 521 class = 1; 522 else if (matchline(">Priority:", buf, len)) 523 priority = 1; 524 else if (matchline(">Release:", buf, len)) 525 release = 1; 526 else if (matchline(">Severity:", buf, len)) 527 severity = 1; 528 else if (matchline(">Synopsis:", buf, len)) 529 synopsis = 1; 530 } 531 fclose(fp); 532 return (category && class && priority && release && severity && 533 synopsis); 534 } 535 536 void 537 template(FILE *fp) 538 { 539 fprintf(fp, "SENDBUG: -*- sendbug -*-\n"); 540 fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will" 541 " be removed automatically, as\n"); 542 fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>').\n"); 543 fprintf(fp, "SENDBUG:\n"); 544 fprintf(fp, "SENDBUG: Choose from the following categories:\n"); 545 fprintf(fp, "SENDBUG:\n"); 546 fprintf(fp, "SENDBUG: %s\n", categories); 547 fprintf(fp, "SENDBUG:\n"); 548 fprintf(fp, "SENDBUG:\n"); 549 fprintf(fp, "To: %s\n", "gnats@openbsd.org"); 550 fprintf(fp, "Subject: \n"); 551 fprintf(fp, "From: %s\n", pw->pw_name); 552 fprintf(fp, "Cc: %s\n", pw->pw_name); 553 fprintf(fp, "Reply-To: %s\n", pw->pw_name); 554 fprintf(fp, "X-sendbug-version: %s\n", version); 555 fprintf(fp, "\n"); 556 fprintf(fp, "\n"); 557 fprintf(fp, ">Submitter-Id:\tnet\n"); 558 fprintf(fp, ">Originator:\t%s\n", fullname); 559 fprintf(fp, ">Organization:\n"); 560 fprintf(fp, "net\n"); 561 fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n"); 562 fprintf(fp, ">Severity:\t" 563 "<[ non-critical | serious | critical ] (one line)>\n"); 564 fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n"); 565 fprintf(fp, ">Category:\t<PR category (one line)>\n"); 566 fprintf(fp, ">Class:\t\t" 567 "<[ sw-bug | doc-bug | change-request | support ] (one line)>\n"); 568 fprintf(fp, ">Release:\t<release number or tag (one line)>\n"); 569 fprintf(fp, ">Environment:\n"); 570 fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n"); 571 fprintf(fp, "\tSystem : %s %s\n", os, rel); 572 fprintf(fp, "\tDetails : %s\n", details); 573 fprintf(fp, "\tArchitecture: %s.%s\n", os, mach); 574 fprintf(fp, "\tMachine : %s\n", mach); 575 fprintf(fp, ">Description:\n"); 576 fprintf(fp, "\t<precise description of the problem (multiple lines)>\n"); 577 fprintf(fp, ">How-To-Repeat:\n"); 578 fprintf(fp, "\t<code/input/activities to reproduce the problem" 579 " (multiple lines)>\n"); 580 fprintf(fp, ">Fix:\n"); 581 fprintf(fp, "\t<how to correct or work around the problem," 582 " if known (multiple lines)>\n"); 583 584 if (!Dflag) 585 dmesg(fp); 586 } 587