1 /* $OpenBSD: sendbug.c,v 1.36 2007/04/06 03:24:08 ray Exp $ */ 2 3 /* 4 * Written by Ray Lai <ray@cyth.net>. 5 * Public domain. 6 */ 7 8 #include <sys/types.h> 9 #include <sys/mman.h> 10 #include <sys/param.h> 11 #include <sys/stat.h> 12 #include <sys/sysctl.h> 13 #include <sys/wait.h> 14 15 #include <ctype.h> 16 #include <err.h> 17 #include <errno.h> 18 #include <fcntl.h> 19 #include <limits.h> 20 #include <paths.h> 21 #include <pwd.h> 22 #include <signal.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <unistd.h> 27 28 #include "atomicio.h" 29 30 #define _PATH_DMESG "/var/run/dmesg.boot" 31 32 void dmesg(FILE *); 33 int editit(char *); 34 void init(void); 35 int prompt(void); 36 int send_file(const char *, int); 37 int sendmail(const char *); 38 void template(FILE *); 39 40 const char *categories = "system user library documentation ports kernel " 41 "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax"; 42 char *version = "4.2"; 43 44 struct passwd *pw; 45 char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ]; 46 char *fullname, *tmppath; 47 int wantcleanup; 48 49 __dead void 50 usage(void) 51 { 52 fprintf(stderr, "usage: sendbug [-DLPV]\n"); 53 exit(1); 54 } 55 56 void 57 cleanup() 58 { 59 if (wantcleanup && tmppath && unlink(tmppath) == -1) 60 warn("unlink"); 61 } 62 63 64 int 65 main(int argc, char *argv[]) 66 { 67 int ch, c, Dflag = 0, fd, ret = 1; 68 const char *tmpdir; 69 struct stat sb; 70 char *pr_form; 71 time_t mtime; 72 FILE *fp; 73 74 while ((ch = getopt(argc, argv, "DLPV")) != -1) 75 switch (ch) { 76 case 'D': 77 Dflag = 1; 78 break; 79 case 'L': 80 printf("Known categories:\n"); 81 printf("%s\n\n", categories); 82 exit(0); 83 case 'P': 84 init(); 85 template(stdout); 86 exit(0); 87 case 'V': 88 printf("%s\n", version); 89 exit(0); 90 default: 91 usage(); 92 } 93 argc -= optind; 94 argv += optind; 95 96 if (argc > 1) 97 usage(); 98 99 if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0') 100 tmpdir = _PATH_TMP; 101 if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir, 102 tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1) 103 err(1, "asprintf"); 104 if ((fd = mkstemp(tmppath)) == -1) 105 err(1, "mkstemp"); 106 wantcleanup = 1; 107 atexit(cleanup); 108 if ((fp = fdopen(fd, "w+")) == NULL) 109 err(1, "fdopen"); 110 111 init(); 112 113 pr_form = getenv("PR_FORM"); 114 if (pr_form) { 115 char buf[BUFSIZ]; 116 size_t len; 117 FILE *frfp; 118 119 frfp = fopen(pr_form, "r"); 120 if (frfp == NULL) { 121 fprintf(stderr, "sendbug: can't seem to read your" 122 " template file (`%s'), ignoring PR_FORM\n", 123 pr_form); 124 template(fp); 125 } else { 126 while (!feof(frfp)) { 127 len = fread(buf, 1, sizeof buf, frfp); 128 if (len == 0) 129 break; 130 if (fwrite(buf, 1, len, fp) != len) 131 break; 132 } 133 fclose(frfp); 134 } 135 } else { 136 template(fp); 137 if (!Dflag) 138 dmesg(fp); 139 } 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 && errno != ECHILD) 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 c = prompt(); 156 switch (c) { 157 case 'a': 158 case EOF: 159 wantcleanup = 0; 160 errx(1, "unsent report in %s", tmppath); 161 case 'e': 162 goto edit; 163 case 's': 164 if (sendmail(tmppath) == -1) 165 goto quit; 166 break; 167 default: 168 goto prompt; 169 } 170 171 ret = 0; 172 quit: 173 return (ret); 174 } 175 176 void 177 dmesg(FILE *fp) 178 { 179 char buf[BUFSIZ]; 180 FILE *dfp; 181 off_t offset = -1; 182 183 dfp = fopen(_PATH_DMESG, "r"); 184 if (dfp == NULL) { 185 warn("can't read dmesg"); 186 return; 187 } 188 189 fputs("\n" 190 "<dmesg is attached.>\n" 191 "<Feel free to delete or use the -D flag if it contains " 192 "sensitive information.>\n", fp); 193 /* Find last line starting with "OpenBSD". */ 194 for (;;) { 195 off_t o; 196 197 o = ftello(dfp); 198 if (fgets(buf, sizeof(buf), dfp) == NULL) 199 break; 200 if (!strncmp("OpenBSD ", buf, sizeof("OpenBSD ") - 1)) 201 offset = o; 202 } 203 if (offset != -1) { 204 size_t len; 205 206 clearerr(dfp); 207 fseeko(dfp, offset, SEEK_SET); 208 while (offset != -1 && !feof(dfp)) { 209 len = fread(buf, 1, sizeof buf, dfp); 210 if (len == 0) 211 break; 212 if (fwrite(buf, 1, len, fp) != len) 213 break; 214 } 215 } 216 fclose(dfp); 217 } 218 219 int 220 editit(char *tmpfile) 221 { 222 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 223 sig_t sighup, sigint, sigquit; 224 pid_t pid, xpid; 225 int st; 226 227 ed = getenv("VISUAL"); 228 if (ed == NULL || ed[0] == '\0') 229 ed = getenv("EDITOR"); 230 if (ed == NULL || ed[0] == '\0') 231 ed = _PATH_VI; 232 if (asprintf(&p, "%s %s", ed, tmpfile) == -1) 233 return (-1); 234 argp[2] = p; 235 236 top: 237 sighup = signal(SIGHUP, SIG_IGN); 238 sigint = signal(SIGINT, SIG_IGN); 239 sigquit = signal(SIGQUIT, SIG_IGN); 240 if ((pid = fork()) == -1) { 241 int saved_errno = errno; 242 243 (void)signal(SIGHUP, sighup); 244 (void)signal(SIGINT, sigint); 245 (void)signal(SIGQUIT, sigquit); 246 if (saved_errno == EAGAIN) { 247 sleep(1); 248 goto top; 249 } 250 free(p); 251 errno = saved_errno; 252 return (-1); 253 } 254 if (pid == 0) { 255 execv(_PATH_BSHELL, argp); 256 _exit(127); 257 } 258 free(p); 259 for (;;) { 260 xpid = waitpid(pid, &st, WUNTRACED); 261 if (xpid == -1) { 262 if (errno != EINTR) 263 return (-1); 264 } else if (WIFSTOPPED(st)) 265 raise(WSTOPSIG(st)); 266 else 267 break; 268 } 269 (void)signal(SIGHUP, sighup); 270 (void)signal(SIGINT, sigint); 271 (void)signal(SIGQUIT, sigquit); 272 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 273 errno = ECHILD; 274 return (-1); 275 } 276 return (0); 277 } 278 279 int 280 prompt(void) 281 { 282 int c, ret; 283 284 fpurge(stdin); 285 fprintf(stderr, "a)bort, e)dit, or s)end: "); 286 fflush(stderr); 287 ret = getchar(); 288 if (ret == EOF || ret == '\n') 289 return (ret); 290 do { 291 c = getchar(); 292 } while (c != EOF && c != '\n'); 293 return (ret); 294 } 295 296 int 297 sendmail(const char *tmppath) 298 { 299 int filedes[2]; 300 301 if (pipe(filedes) == -1) { 302 warn("pipe: unsent report in %s", tmppath); 303 return (-1); 304 } 305 switch (fork()) { 306 case -1: 307 warn("fork error: unsent report in %s", 308 tmppath); 309 return (-1); 310 case 0: 311 close(filedes[1]); 312 if (dup2(filedes[0], STDIN_FILENO) == -1) { 313 warn("dup2 error: unsent report in %s", 314 tmppath); 315 return (-1); 316 } 317 close(filedes[0]); 318 execl("/usr/sbin/sendmail", "sendmail", 319 "-oi", "-t", (void *)NULL); 320 warn("sendmail error: unsent report in %s", 321 tmppath); 322 return (-1); 323 default: 324 close(filedes[0]); 325 /* Pipe into sendmail. */ 326 if (send_file(tmppath, filedes[1]) == -1) { 327 warn("send_file error: unsent report in %s", 328 tmppath); 329 return (-1); 330 } 331 close(filedes[1]); 332 wait(NULL); 333 break; 334 } 335 return (0); 336 } 337 338 void 339 init(void) 340 { 341 size_t amp, len, gecoslen, namelen; 342 int sysname[2]; 343 char ch, *cp; 344 345 if ((pw = getpwuid(getuid())) == NULL) 346 err(1, "getpwuid"); 347 namelen = strlen(pw->pw_name); 348 349 /* Count number of '&'. */ 350 for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp) 351 if (*cp == '&') 352 ++amp; 353 354 /* Truncate gecos to full name. */ 355 gecoslen = cp - pw->pw_gecos; 356 pw->pw_gecos[gecoslen] = '\0'; 357 358 /* Expanded str = orig str - '&' chars + concatenated logins. */ 359 len = gecoslen - amp + (amp * namelen) + 1; 360 if ((fullname = malloc(len)) == NULL) 361 err(1, "malloc"); 362 363 /* Upper case first char of login. */ 364 ch = pw->pw_name[0]; 365 pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]); 366 367 cp = pw->pw_gecos; 368 fullname[0] = '\0'; 369 while (cp != NULL) { 370 char *token; 371 372 token = strsep(&cp, "&"); 373 if (token != pw->pw_gecos && 374 strlcat(fullname, pw->pw_name, len) >= len) 375 errx(1, "truncated string"); 376 if (strlcat(fullname, token, len) >= len) 377 errx(1, "truncated string"); 378 } 379 /* Restore case of first char of login. */ 380 pw->pw_name[0] = ch; 381 382 sysname[0] = CTL_KERN; 383 sysname[1] = KERN_OSTYPE; 384 len = sizeof(os) - 1; 385 if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) 386 err(1, "sysctl"); 387 388 sysname[0] = CTL_KERN; 389 sysname[1] = KERN_OSRELEASE; 390 len = sizeof(rel) - 1; 391 if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) 392 err(1, "sysctl"); 393 394 sysname[0] = CTL_KERN; 395 sysname[1] = KERN_VERSION; 396 len = sizeof(details) - 1; 397 if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1) 398 err(1, "sysctl"); 399 400 cp = strchr(details, '\n'); 401 if (cp) { 402 cp++; 403 if (*cp) 404 *cp++ = '\t'; 405 if (*cp) 406 *cp++ = '\t'; 407 if (*cp) 408 *cp++ = '\t'; 409 } 410 411 sysname[0] = CTL_HW; 412 sysname[1] = HW_MACHINE; 413 len = sizeof(mach) - 1; 414 if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) 415 err(1, "sysctl"); 416 } 417 418 int 419 send_file(const char *file, int dst) 420 { 421 int blank = 0; 422 size_t len; 423 char *buf; 424 FILE *fp; 425 426 if ((fp = fopen(file, "r")) == NULL) 427 return (-1); 428 while ((buf = fgetln(fp, &len))) { 429 /* Skip lines starting with "SENDBUG". */ 430 if (len >= sizeof("SENDBUG") - 1 && 431 memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0) 432 continue; 433 if (len == 1 && buf[0] == '\n') 434 blank = 1; 435 /* Skip comments, but only if we encountered a blank line. */ 436 while (len) { 437 char *sp = NULL, *ep = NULL; 438 size_t copylen; 439 440 if (blank && (sp = memchr(buf, '<', len)) != NULL) 441 ep = memchr(sp, '>', len - (sp - buf + 1)); 442 /* Length of string before comment. */ 443 if (ep) 444 copylen = sp - buf; 445 else 446 copylen = len; 447 if (atomicio(vwrite, dst, buf, copylen) != copylen) { 448 int saved_errno = errno; 449 450 fclose(fp); 451 errno = saved_errno; 452 return (-1); 453 } 454 if (!ep) 455 break; 456 /* Skip comment. */ 457 len -= ep - buf + 1; 458 buf = ep + 1; 459 } 460 } 461 fclose(fp); 462 return (0); 463 } 464 465 void 466 template(FILE *fp) 467 { 468 fprintf(fp, "SENDBUG: -*- sendbug -*-\n"); 469 fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will" 470 " be removed automatically, as\n"); 471 fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>').\n"); 472 fprintf(fp, "SENDBUG:\n"); 473 fprintf(fp, "SENDBUG: Choose from the following categories:\n"); 474 fprintf(fp, "SENDBUG:\n"); 475 fprintf(fp, "SENDBUG: %s\n", categories); 476 fprintf(fp, "SENDBUG:\n"); 477 fprintf(fp, "SENDBUG:\n"); 478 fprintf(fp, "To: %s\n", "gnats@openbsd.org"); 479 fprintf(fp, "Subject: \n"); 480 fprintf(fp, "From: %s\n", pw->pw_name); 481 fprintf(fp, "Cc: %s\n", pw->pw_name); 482 fprintf(fp, "Reply-To: %s\n", pw->pw_name); 483 fprintf(fp, "X-sendbug-version: %s\n", version); 484 fprintf(fp, "\n"); 485 fprintf(fp, "\n"); 486 fprintf(fp, ">Submitter-Id:\tnet\n"); 487 fprintf(fp, ">Originator:\t%s\n", fullname); 488 fprintf(fp, ">Organization:\n"); 489 fprintf(fp, "net\n"); 490 fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n"); 491 fprintf(fp, ">Severity:\t" 492 "<[ non-critical | serious | critical ] (one line)>\n"); 493 fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n"); 494 fprintf(fp, ">Category:\t<PR category (one line)>\n"); 495 fprintf(fp, ">Class:\t\t" 496 "<[ sw-bug | doc-bug | change-request | support ] (one line)>\n"); 497 fprintf(fp, ">Release:\t<release number or tag (one line)>\n"); 498 fprintf(fp, ">Environment:\n"); 499 fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n"); 500 fprintf(fp, "\tSystem : %s %s\n", os, rel); 501 fprintf(fp, "\tDetails : %s\n", details); 502 fprintf(fp, "\tArchitecture: %s.%s\n", os, mach); 503 fprintf(fp, "\tMachine : %s\n", mach); 504 fprintf(fp, ">Description:\n"); 505 fprintf(fp, "\t<precise description of the problem (multiple lines)>\n"); 506 fprintf(fp, ">How-To-Repeat:\n"); 507 fprintf(fp, "\t<code/input/activities to reproduce the problem" 508 " (multiple lines)>\n"); 509 fprintf(fp, ">Fix:\n"); 510 fprintf(fp, "\t<how to correct or work around the problem," 511 " if known (multiple lines)>\n"); 512 } 513