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