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