1*d23b3302Sray /* $OpenBSD: sendbug.c,v 1.34 2007/03/28 04:05:52 ray Exp $ */ 25ae6585fSray 35ae6585fSray /* 45ae6585fSray * Written by Ray Lai <ray@cyth.net>. 55ae6585fSray * Public domain. 65ae6585fSray */ 75ae6585fSray 85ae6585fSray #include <sys/types.h> 95ae6585fSray #include <sys/mman.h> 105ae6585fSray #include <sys/param.h> 115ae6585fSray #include <sys/stat.h> 125ae6585fSray #include <sys/sysctl.h> 135ae6585fSray #include <sys/wait.h> 145ae6585fSray 1549d07c37Sray #include <ctype.h> 165ae6585fSray #include <err.h> 175ae6585fSray #include <errno.h> 185ae6585fSray #include <fcntl.h> 195ae6585fSray #include <limits.h> 205ae6585fSray #include <paths.h> 215ae6585fSray #include <pwd.h> 224b9386a8Sderaadt #include <signal.h> 235ae6585fSray #include <stdio.h> 245ae6585fSray #include <stdlib.h> 255ae6585fSray #include <string.h> 265ae6585fSray #include <unistd.h> 275ae6585fSray 285ae6585fSray #include "atomicio.h" 295ae6585fSray 304b9386a8Sderaadt int editit(char *); 31b2e65daaStedu void init(void); 325ae6585fSray int prompt(void); 330690c094Sray int send_file(const char *, int); 345ae6585fSray int sendmail(const char *); 355ae6585fSray void template(FILE *); 365ae6585fSray 375ae6585fSray const char *categories = "system user library documentation ports kernel " 385ae6585fSray "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax"; 39823e89efSderaadt char *version = "4.2"; 40823e89efSderaadt 41823e89efSderaadt struct passwd *pw; 42e051e8faSderaadt char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ]; 436b1a1e2aSderaadt char *fullname, *tmppath; 44b2e65daaStedu int wantcleanup; 45823e89efSderaadt 46b2e65daaStedu __dead void 47903e61cbSderaadt usage(void) 48903e61cbSderaadt { 49994a1f98Sderaadt fprintf(stderr, "usage: sendbug [-LPV]\n"); 50b2e65daaStedu exit(1); 51903e61cbSderaadt } 52903e61cbSderaadt 53b2e65daaStedu void 54b2e65daaStedu cleanup() 55b2e65daaStedu { 56b2e65daaStedu if (wantcleanup && tmppath && unlink(tmppath) == -1) 57b2e65daaStedu warn("unlink"); 58b2e65daaStedu } 59b2e65daaStedu 60b2e65daaStedu 615ae6585fSray int 625ae6585fSray main(int argc, char *argv[]) 635ae6585fSray { 64903e61cbSderaadt int ch, c, fd, ret = 1; 65f4348cb9Sderaadt const char *tmpdir; 6670dfcc75Sderaadt struct stat sb; 67f4348cb9Sderaadt char *pr_form; 6870dfcc75Sderaadt time_t mtime; 6970dfcc75Sderaadt FILE *fp; 705ae6585fSray 715711e205Sderaadt while ((ch = getopt(argc, argv, "LPV")) != -1) 72903e61cbSderaadt switch (ch) { 73903e61cbSderaadt case 'L': 74903e61cbSderaadt printf("Known categories:\n"); 75903e61cbSderaadt printf("%s\n\n", categories); 76903e61cbSderaadt exit(0); 77903e61cbSderaadt case 'P': 78b2e65daaStedu init(); 79903e61cbSderaadt template(stdout); 80903e61cbSderaadt exit(0); 815711e205Sderaadt case 'V': 825711e205Sderaadt printf("%s\n", version); 835711e205Sderaadt exit(0); 84903e61cbSderaadt default: 85903e61cbSderaadt usage(); 86903e61cbSderaadt } 87903e61cbSderaadt 88903e61cbSderaadt if (argc > 1) { 89903e61cbSderaadt usage(); 90903e61cbSderaadt } 91903e61cbSderaadt 925ae6585fSray if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0') 935ae6585fSray tmpdir = _PATH_TMP; 94795a8b97Sray if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir, 95f4348cb9Sderaadt tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1) 96b2e65daaStedu err(1, "asprintf"); 975ae6585fSray if ((fd = mkstemp(tmppath)) == -1) 985ae6585fSray err(1, "mkstemp"); 99b2e65daaStedu wantcleanup = 1; 100b2e65daaStedu atexit(cleanup); 101f4348cb9Sderaadt if ((fp = fdopen(fd, "w+")) == NULL) 102b2e65daaStedu err(1, "fdopen"); 1035ae6585fSray 104b2e65daaStedu init(); 1055ae6585fSray 1064ff3398aSderaadt pr_form = getenv("PR_FORM"); 1074ff3398aSderaadt if (pr_form) { 1084ff3398aSderaadt char buf[BUFSIZ]; 1094ff3398aSderaadt size_t len; 1104ff3398aSderaadt FILE *frfp; 1114ff3398aSderaadt 1124ff3398aSderaadt frfp = fopen(pr_form, "r"); 1134ff3398aSderaadt if (frfp == NULL) { 1144ff3398aSderaadt fprintf(stderr, "sendbug: can't seem to read your" 1154ff3398aSderaadt " template file (`%s'), ignoring PR_FORM\n", 1164ff3398aSderaadt pr_form); 1174ff3398aSderaadt template(fp); 1184ff3398aSderaadt } else { 1194ff3398aSderaadt while (!feof(frfp)) { 1204ff3398aSderaadt len = fread(buf, 1, sizeof buf, frfp); 1214ff3398aSderaadt if (len == 0) 1224ff3398aSderaadt break; 1234ff3398aSderaadt if (fwrite(buf, 1, len, fp) != len) 1244ff3398aSderaadt break; 1254ff3398aSderaadt } 1264ff3398aSderaadt fclose(frfp); 1274ff3398aSderaadt } 1284ff3398aSderaadt } else 1295ae6585fSray template(fp); 1305ae6585fSray 131f4348cb9Sderaadt if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF) 132b2e65daaStedu err(1, "error creating template"); 1335ae6585fSray mtime = sb.st_mtime; 1345ae6585fSray 1355ae6585fSray edit: 136648f0ffcSray if (editit(tmppath) == -1 && errno != ECHILD) 137648f0ffcSray err(1, "error running editor"); 1385ae6585fSray 139f4348cb9Sderaadt if (stat(tmppath, &sb) == -1) 140b2e65daaStedu err(1, "stat"); 141f4348cb9Sderaadt if (mtime == sb.st_mtime) 142b2e65daaStedu errx(1, "report unchanged, nothing sent"); 1435ae6585fSray 1445ae6585fSray prompt: 1455ae6585fSray c = prompt(); 1465ae6585fSray switch (c) { 1475711e205Sderaadt case 'a': 1485711e205Sderaadt case EOF: 149b2e65daaStedu wantcleanup = 0; 150b2e65daaStedu errx(1, "unsent report in %s", tmppath); 1515ae6585fSray case 'e': 1525ae6585fSray goto edit; 1535ae6585fSray case 's': 1545ae6585fSray if (sendmail(tmppath) == -1) 1555ae6585fSray goto quit; 1565ae6585fSray break; 1575ae6585fSray default: 1585ae6585fSray goto prompt; 1595ae6585fSray } 1605ae6585fSray 1615ae6585fSray ret = 0; 1625ae6585fSray quit: 1635ae6585fSray return (ret); 1645ae6585fSray } 1655ae6585fSray 1664b9386a8Sderaadt int 1674b9386a8Sderaadt editit(char *tmpfile) 1684b9386a8Sderaadt { 169f4348cb9Sderaadt char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 1708ec2d82eSray sig_t sighup, sigint, sigquit; 1714b9386a8Sderaadt pid_t pid, xpid; 1726b1a1e2aSderaadt int st; 1734b9386a8Sderaadt 174c8a426adSray ed = getenv("VISUAL"); 175c8a426adSray if (ed == NULL || ed[0] == '\0') 176c8a426adSray ed = getenv("EDITOR"); 177c8a426adSray if (ed == NULL || ed[0] == '\0') 1784b9386a8Sderaadt ed = _PATH_VI; 1794b9386a8Sderaadt if (asprintf(&p, "%s %s", ed, tmpfile) == -1) 1808af53ff7Sray return (-1); 1814b9386a8Sderaadt argp[2] = p; 1824b9386a8Sderaadt 1834b9386a8Sderaadt top: 1848ec2d82eSray sighup = signal(SIGHUP, SIG_IGN); 1858ec2d82eSray sigint = signal(SIGINT, SIG_IGN); 1868ec2d82eSray sigquit = signal(SIGQUIT, SIG_IGN); 1877132776cSray if ((pid = fork()) == -1) { 1883c361d1bSray int saved_errno = errno; 1893c361d1bSray 1908ec2d82eSray (void)signal(SIGHUP, sighup); 1918ec2d82eSray (void)signal(SIGINT, sigint); 1928ec2d82eSray (void)signal(SIGQUIT, sigquit); 1933c361d1bSray if (saved_errno == EAGAIN) { 1944b9386a8Sderaadt sleep(1); 1954b9386a8Sderaadt goto top; 1964b9386a8Sderaadt } 1974b9386a8Sderaadt free(p); 1982bba59c0Sray errno = saved_errno; 1998af53ff7Sray return (-1); 2004b9386a8Sderaadt } 2014b9386a8Sderaadt if (pid == 0) { 2024b9386a8Sderaadt execv(_PATH_BSHELL, argp); 2034b9386a8Sderaadt _exit(127); 2044b9386a8Sderaadt } 2054b9386a8Sderaadt free(p); 2064b9386a8Sderaadt for (;;) { 207ecdc3c8bSray xpid = waitpid(pid, &st, WUNTRACED); 2086b1a1e2aSderaadt if (xpid == -1) { 2092bba59c0Sray if (errno != EINTR) 2106b1a1e2aSderaadt return (-1); 2116b1a1e2aSderaadt } else if (WIFSTOPPED(st)) 2126b1a1e2aSderaadt raise(WSTOPSIG(st)); 213f27d80daSray else 2144b9386a8Sderaadt break; 2154b9386a8Sderaadt } 2168ec2d82eSray (void)signal(SIGHUP, sighup); 2178ec2d82eSray (void)signal(SIGINT, sigint); 2188ec2d82eSray (void)signal(SIGQUIT, sigquit); 2192bba59c0Sray if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 2202bba59c0Sray errno = ECHILD; 2218af53ff7Sray return (-1); 2222bba59c0Sray } 2234b9386a8Sderaadt return (0); 2244b9386a8Sderaadt } 225b2e65daaStedu 2265ae6585fSray int 2275ae6585fSray prompt(void) 2285ae6585fSray { 2295ae6585fSray int c, ret; 2305ae6585fSray 2315ae6585fSray fpurge(stdin); 2325ae6585fSray fprintf(stderr, "a)bort, e)dit, or s)end: "); 2335ae6585fSray fflush(stderr); 2345ae6585fSray ret = getchar(); 2355ae6585fSray if (ret == EOF || ret == '\n') 2365ae6585fSray return (ret); 2375ae6585fSray do { 2385ae6585fSray c = getchar(); 2395ae6585fSray } while (c != EOF && c != '\n'); 2405ae6585fSray return (ret); 2415ae6585fSray } 2425ae6585fSray 2435ae6585fSray int 2445ae6585fSray sendmail(const char *tmppath) 2455ae6585fSray { 2465ae6585fSray int filedes[2]; 2475ae6585fSray 2485ae6585fSray if (pipe(filedes) == -1) { 2495ae6585fSray warn("pipe: unsent report in %s", tmppath); 2505ae6585fSray return (-1); 2515ae6585fSray } 2525ae6585fSray switch (fork()) { 2535ae6585fSray case -1: 2545ae6585fSray warn("fork error: unsent report in %s", 2555ae6585fSray tmppath); 2565ae6585fSray return (-1); 2575ae6585fSray case 0: 2585ae6585fSray close(filedes[1]); 2595ae6585fSray if (dup2(filedes[0], STDIN_FILENO) == -1) { 2605ae6585fSray warn("dup2 error: unsent report in %s", 2615ae6585fSray tmppath); 2625ae6585fSray return (-1); 2635ae6585fSray } 2645ae6585fSray close(filedes[0]); 2655ae6585fSray execl("/usr/sbin/sendmail", "sendmail", 26670dfcc75Sderaadt "-oi", "-t", (void *)NULL); 2675ae6585fSray warn("sendmail error: unsent report in %s", 2685ae6585fSray tmppath); 2695ae6585fSray return (-1); 2705ae6585fSray default: 2715ae6585fSray close(filedes[0]); 2725ae6585fSray /* Pipe into sendmail. */ 2735ae6585fSray if (send_file(tmppath, filedes[1]) == -1) { 2745ae6585fSray warn("send_file error: unsent report in %s", 2755ae6585fSray tmppath); 2765ae6585fSray return (-1); 2775ae6585fSray } 2785ae6585fSray close(filedes[1]); 2795ae6585fSray wait(NULL); 2805ae6585fSray break; 2815ae6585fSray } 2825ae6585fSray return (0); 2835ae6585fSray } 2845ae6585fSray 285b2e65daaStedu void 2865ae6585fSray init(void) 2875ae6585fSray { 288b088fbb0Sray size_t amp, len, gecoslen, namelen; 289f4348cb9Sderaadt int sysname[2]; 290b088fbb0Sray char ch, *cp; 2915ae6585fSray 292f4348cb9Sderaadt if ((pw = getpwuid(getuid())) == NULL) 293b2e65daaStedu err(1, "getpwuid"); 29449d07c37Sray namelen = strlen(pw->pw_name); 2955ae6585fSray 2969a66796cSmoritz /* Count number of '&'. */ 297b088fbb0Sray for (amp = 0, cp = pw->pw_gecos; *cp && *cp != ','; ++cp) 298b088fbb0Sray if (*cp == '&') 2999a66796cSmoritz ++amp; 300b088fbb0Sray 301b088fbb0Sray /* Truncate gecos to full name. */ 302b088fbb0Sray gecoslen = cp - pw->pw_gecos; 303b088fbb0Sray pw->pw_gecos[gecoslen] = '\0'; 304b088fbb0Sray 3059a66796cSmoritz /* Expanded str = orig str - '&' chars + concatenated logins. */ 306b088fbb0Sray len = gecoslen - amp + (amp * namelen) + 1; 307b088fbb0Sray if ((fullname = malloc(len)) == NULL) 308b2e65daaStedu err(1, "malloc"); 309f4348cb9Sderaadt 310b088fbb0Sray /* Upper case first char of login. */ 311b088fbb0Sray ch = pw->pw_name[0]; 312b088fbb0Sray pw->pw_name[0] = toupper((unsigned char)pw->pw_name[0]); 313b088fbb0Sray 314b088fbb0Sray cp = pw->pw_gecos; 315b088fbb0Sray fullname[0] = '\0'; 316b088fbb0Sray while (cp != NULL) { 317b088fbb0Sray char *token; 318b088fbb0Sray 319b088fbb0Sray token = strsep(&cp, "&"); 320b088fbb0Sray if (token != pw->pw_gecos && 321b088fbb0Sray strlcat(fullname, pw->pw_name, len) >= len) 322b088fbb0Sray errx(1, "truncated string"); 323b088fbb0Sray if (strlcat(fullname, token, len) >= len) 324b088fbb0Sray errx(1, "truncated string"); 32549d07c37Sray } 326b088fbb0Sray /* Restore case of first char of login. */ 327b088fbb0Sray pw->pw_name[0] = ch; 3285ae6585fSray 3295ae6585fSray sysname[0] = CTL_KERN; 3305ae6585fSray sysname[1] = KERN_OSTYPE; 3315ae6585fSray len = sizeof(os) - 1; 332f4348cb9Sderaadt if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) 333b2e65daaStedu err(1, "sysctl"); 3345ae6585fSray 3355ae6585fSray sysname[0] = CTL_KERN; 3365ae6585fSray sysname[1] = KERN_OSRELEASE; 3375ae6585fSray len = sizeof(rel) - 1; 338f4348cb9Sderaadt if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) 339b2e65daaStedu err(1, "sysctl"); 3405ae6585fSray 341e051e8faSderaadt sysname[0] = CTL_KERN; 342e051e8faSderaadt sysname[1] = KERN_VERSION; 343e051e8faSderaadt len = sizeof(details) - 1; 344f4348cb9Sderaadt if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1) 345e051e8faSderaadt err(1, "sysctl"); 346e051e8faSderaadt 347e051e8faSderaadt cp = strchr(details, '\n'); 348e051e8faSderaadt if (cp) { 349e051e8faSderaadt cp++; 350e051e8faSderaadt if (*cp) 351e051e8faSderaadt *cp++ = '\t'; 352e051e8faSderaadt if (*cp) 353e051e8faSderaadt *cp++ = '\t'; 354e051e8faSderaadt if (*cp) 355e051e8faSderaadt *cp++ = '\t'; 356e051e8faSderaadt } 357e051e8faSderaadt 3585ae6585fSray sysname[0] = CTL_HW; 3595ae6585fSray sysname[1] = HW_MACHINE; 3605ae6585fSray len = sizeof(mach) - 1; 361f4348cb9Sderaadt if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) 362b2e65daaStedu err(1, "sysctl"); 3635ae6585fSray } 3645ae6585fSray 3655ae6585fSray int 3665ae6585fSray send_file(const char *file, int dst) 3675ae6585fSray { 3685ae6585fSray int blank = 0; 36970dfcc75Sderaadt size_t len; 37070dfcc75Sderaadt char *buf; 37170dfcc75Sderaadt FILE *fp; 3725ae6585fSray 3735ae6585fSray if ((fp = fopen(file, "r")) == NULL) 3745ae6585fSray return (-1); 3755ae6585fSray while ((buf = fgetln(fp, &len))) { 3765ae6585fSray /* Skip lines starting with "SENDBUG". */ 3775ae6585fSray if (len >= sizeof("SENDBUG") - 1 && 3785ae6585fSray memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0) 3795ae6585fSray continue; 3805ae6585fSray if (len == 1 && buf[0] == '\n') 3815ae6585fSray blank = 1; 3825ae6585fSray /* Skip comments, but only if we encountered a blank line. */ 3835ae6585fSray while (len) { 384e051e8faSderaadt char *sp = NULL, *ep = NULL; 3855ae6585fSray size_t copylen; 3865ae6585fSray 3875ae6585fSray if (blank && (sp = memchr(buf, '<', len)) != NULL) 3885ae6585fSray ep = memchr(sp, '>', len - (sp - buf + 1)); 3895ae6585fSray /* Length of string before comment. */ 3908cf86b71Sray if (ep) 3918cf86b71Sray copylen = sp - buf; 3928cf86b71Sray else 3938cf86b71Sray copylen = len; 3945ae6585fSray if (atomicio(vwrite, dst, buf, copylen) != copylen) { 3955ae6585fSray int saved_errno = errno; 3965ae6585fSray 3975ae6585fSray fclose(fp); 3985ae6585fSray errno = saved_errno; 3995ae6585fSray return (-1); 4005ae6585fSray } 4015ae6585fSray if (!ep) 4025ae6585fSray break; 4035ae6585fSray /* Skip comment. */ 4045ae6585fSray len -= ep - buf + 1; 4055ae6585fSray buf = ep + 1; 4065ae6585fSray } 4075ae6585fSray } 4085ae6585fSray fclose(fp); 4095ae6585fSray return (0); 4105ae6585fSray } 4115ae6585fSray 4125ae6585fSray void 4135ae6585fSray template(FILE *fp) 4145ae6585fSray { 4155ae6585fSray fprintf(fp, "SENDBUG: -*- sendbug -*-\n"); 416f4348cb9Sderaadt fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will" 417f4348cb9Sderaadt " be removed automatically, as\n"); 4185ae6585fSray fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>').\n"); 4195ae6585fSray fprintf(fp, "SENDBUG:\n"); 4205ae6585fSray fprintf(fp, "SENDBUG: Choose from the following categories:\n"); 4215ae6585fSray fprintf(fp, "SENDBUG:\n"); 4225ae6585fSray fprintf(fp, "SENDBUG: %s\n", categories); 4235ae6585fSray fprintf(fp, "SENDBUG:\n"); 4245ae6585fSray fprintf(fp, "SENDBUG:\n"); 4255ae6585fSray fprintf(fp, "To: %s\n", "gnats@openbsd.org"); 4265ae6585fSray fprintf(fp, "Subject: \n"); 4275ae6585fSray fprintf(fp, "From: %s\n", pw->pw_name); 428*d23b3302Sray fprintf(fp, "Cc: %s\n", pw->pw_name); 4295ae6585fSray fprintf(fp, "Reply-To: %s\n", pw->pw_name); 4305711e205Sderaadt fprintf(fp, "X-sendbug-version: %s\n", version); 4315ae6585fSray fprintf(fp, "\n"); 4325ae6585fSray fprintf(fp, "\n"); 4335ae6585fSray fprintf(fp, ">Submitter-Id:\tnet\n"); 4345ae6585fSray fprintf(fp, ">Originator:\t%s\n", fullname); 4355ae6585fSray fprintf(fp, ">Organization:\n"); 4365ae6585fSray fprintf(fp, "net\n"); 4375ae6585fSray fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n"); 438f4348cb9Sderaadt fprintf(fp, ">Severity:\t" 439f4348cb9Sderaadt "<[ non-critical | serious | critical ] (one line)>\n"); 4405ae6585fSray fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n"); 4415ae6585fSray fprintf(fp, ">Category:\t<PR category (one line)>\n"); 442f4348cb9Sderaadt fprintf(fp, ">Class:\t\t" 443f4348cb9Sderaadt "<[ sw-bug | doc-bug | change-request | support ] (one line)>\n"); 4445ae6585fSray fprintf(fp, ">Release:\t<release number or tag (one line)>\n"); 4455ae6585fSray fprintf(fp, ">Environment:\n"); 4465ae6585fSray fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n"); 4475ae6585fSray fprintf(fp, "\tSystem : %s %s\n", os, rel); 448e051e8faSderaadt fprintf(fp, "\tDetails : %s\n", details); 4495ae6585fSray fprintf(fp, "\tArchitecture: %s.%s\n", os, mach); 4505ae6585fSray fprintf(fp, "\tMachine : %s\n", mach); 4515ae6585fSray fprintf(fp, ">Description:\n"); 4525ae6585fSray fprintf(fp, "\t<precise description of the problem (multiple lines)>\n"); 4535ae6585fSray fprintf(fp, ">How-To-Repeat:\n"); 454f4348cb9Sderaadt fprintf(fp, "\t<code/input/activities to reproduce the problem" 455f4348cb9Sderaadt " (multiple lines)>\n"); 4565ae6585fSray fprintf(fp, ">Fix:\n"); 457f4348cb9Sderaadt fprintf(fp, "\t<how to correct or work around the problem," 458f4348cb9Sderaadt " if known (multiple lines)>\n"); 4595ae6585fSray } 460