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