1*e051e8faSderaadt /* $OpenBSD: sendbug.c,v 1.14 2007/03/23 15:46:40 deraadt 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> 225ae6585fSray #include <stdio.h> 235ae6585fSray #include <stdlib.h> 245ae6585fSray #include <string.h> 255ae6585fSray #include <unistd.h> 265ae6585fSray 275ae6585fSray #include "atomicio.h" 285ae6585fSray 29b2e65daaStedu void init(void); 305ae6585fSray int prompt(void); 315ae6585fSray int send_file(const char *, int dst); 325ae6585fSray int sendmail(const char *); 335ae6585fSray void template(FILE *); 345ae6585fSray 355ae6585fSray const char *categories = "system user library documentation ports kernel " 365ae6585fSray "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax"; 37823e89efSderaadt char *version = "4.2"; 38823e89efSderaadt 39823e89efSderaadt struct passwd *pw; 40*e051e8faSderaadt char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ], details[BUFSIZ]; 415ae6585fSray char *fullname; 42b2e65daaStedu char *tmppath; 43b2e65daaStedu int wantcleanup; 44823e89efSderaadt 45b2e65daaStedu __dead void 46903e61cbSderaadt usage(void) 47903e61cbSderaadt { 48994a1f98Sderaadt fprintf(stderr, "usage: sendbug [-LPV]\n"); 49b2e65daaStedu exit(1); 50903e61cbSderaadt } 51903e61cbSderaadt 52b2e65daaStedu void 53b2e65daaStedu cleanup() 54b2e65daaStedu { 55b2e65daaStedu if (wantcleanup && tmppath && unlink(tmppath) == -1) 56b2e65daaStedu warn("unlink"); 57b2e65daaStedu } 58b2e65daaStedu 59b2e65daaStedu 605ae6585fSray int 615ae6585fSray main(int argc, char *argv[]) 625ae6585fSray { 635ae6585fSray const char *editor, *tmpdir; 64b2e65daaStedu char *argp[] = {"sh", "-c", NULL, NULL}, *pr_form; 65903e61cbSderaadt int ch, c, fd, ret = 1; 6670dfcc75Sderaadt struct stat sb; 6770dfcc75Sderaadt time_t mtime; 6870dfcc75Sderaadt FILE *fp; 695ae6585fSray 705711e205Sderaadt while ((ch = getopt(argc, argv, "LPV")) != -1) 71903e61cbSderaadt switch (ch) { 72903e61cbSderaadt case 'L': 73903e61cbSderaadt printf("Known categories:\n"); 74903e61cbSderaadt printf("%s\n\n", categories); 75903e61cbSderaadt exit(0); 76903e61cbSderaadt case 'P': 77b2e65daaStedu init(); 78903e61cbSderaadt template(stdout); 79903e61cbSderaadt exit(0); 805711e205Sderaadt case 'V': 815711e205Sderaadt printf("%s\n", version); 825711e205Sderaadt exit(0); 83903e61cbSderaadt default: 84903e61cbSderaadt usage(); 85903e61cbSderaadt } 86903e61cbSderaadt 87903e61cbSderaadt if (argc > 1) { 88903e61cbSderaadt usage(); 89903e61cbSderaadt } 90903e61cbSderaadt 915ae6585fSray if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0') 925ae6585fSray tmpdir = _PATH_TMP; 93795a8b97Sray if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir, 94795a8b97Sray tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1) { 95b2e65daaStedu err(1, "asprintf"); 965ae6585fSray } 975ae6585fSray if ((fd = mkstemp(tmppath)) == -1) 985ae6585fSray err(1, "mkstemp"); 99b2e65daaStedu wantcleanup = 1; 100b2e65daaStedu atexit(cleanup); 1015ae6585fSray if ((fp = fdopen(fd, "w+")) == NULL) { 102b2e65daaStedu err(1, "fdopen"); 1035ae6585fSray } 1045ae6585fSray 105b2e65daaStedu init(); 1065ae6585fSray 1074ff3398aSderaadt pr_form = getenv("PR_FORM"); 1084ff3398aSderaadt if (pr_form) { 1094ff3398aSderaadt char buf[BUFSIZ]; 1104ff3398aSderaadt size_t len; 1114ff3398aSderaadt FILE *frfp; 1124ff3398aSderaadt 1134ff3398aSderaadt frfp = fopen(pr_form, "r"); 1144ff3398aSderaadt if (frfp == NULL) { 1154ff3398aSderaadt fprintf(stderr, "sendbug: can't seem to read your" 1164ff3398aSderaadt " template file (`%s'), ignoring PR_FORM\n", 1174ff3398aSderaadt pr_form); 1184ff3398aSderaadt template(fp); 1194ff3398aSderaadt } else { 1204ff3398aSderaadt while (!feof(frfp)) { 1214ff3398aSderaadt len = fread(buf, 1, sizeof buf, frfp); 1224ff3398aSderaadt if (len == 0) 1234ff3398aSderaadt break; 1244ff3398aSderaadt if (fwrite(buf, 1, len, fp) != len) 1254ff3398aSderaadt break; 1264ff3398aSderaadt } 1274ff3398aSderaadt fclose(frfp); 1284ff3398aSderaadt } 1294ff3398aSderaadt } else 1305ae6585fSray template(fp); 1315ae6585fSray 1325ae6585fSray if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF) { 133b2e65daaStedu err(1, "error creating template"); 1345ae6585fSray } 1355ae6585fSray mtime = sb.st_mtime; 1365ae6585fSray 1375ae6585fSray edit: 1385ae6585fSray if ((editor = getenv("EDITOR")) == NULL) 1395ae6585fSray editor = "vi"; 1405ae6585fSray switch (fork()) { 1415ae6585fSray case -1: 142b2e65daaStedu err(1, "fork"); 1435ae6585fSray case 0: 144b2e65daaStedu wantcleanup = 0; 1456df8a6c0Sray if (asprintf(&argp[2], "%s %s", editor, tmppath) == -1) 1466df8a6c0Sray err(1, "asprintf"); 1476df8a6c0Sray execv(_PATH_BSHELL, argp); 1486df8a6c0Sray err(1, "execv"); 1495ae6585fSray default: 1505ae6585fSray wait(NULL); 1515ae6585fSray break; 1525ae6585fSray } 1535ae6585fSray 1545ae6585fSray if (stat(tmppath, &sb) == -1) { 155b2e65daaStedu err(1, "stat"); 1565ae6585fSray } 1575ae6585fSray if (mtime == sb.st_mtime) { 158b2e65daaStedu errx(1, "report unchanged, nothing sent"); 1595ae6585fSray } 1605ae6585fSray 1615ae6585fSray prompt: 1625ae6585fSray c = prompt(); 1635ae6585fSray switch (c) { 1645711e205Sderaadt case 'a': 1655711e205Sderaadt case EOF: 166b2e65daaStedu wantcleanup = 0; 167b2e65daaStedu errx(1, "unsent report in %s", tmppath); 1685ae6585fSray case 'e': 1695ae6585fSray goto edit; 1705ae6585fSray case 's': 1715ae6585fSray if (sendmail(tmppath) == -1) 1725ae6585fSray goto quit; 1735ae6585fSray break; 1745ae6585fSray default: 1755ae6585fSray goto prompt; 1765ae6585fSray } 1775ae6585fSray 1785ae6585fSray ret = 0; 1795ae6585fSray quit: 1805ae6585fSray return (ret); 1815ae6585fSray } 1825ae6585fSray 183b2e65daaStedu 1845ae6585fSray int 1855ae6585fSray prompt(void) 1865ae6585fSray { 1875ae6585fSray int c, ret; 1885ae6585fSray 1895ae6585fSray fpurge(stdin); 1905ae6585fSray fprintf(stderr, "a)bort, e)dit, or s)end: "); 1915ae6585fSray fflush(stderr); 1925ae6585fSray ret = getchar(); 1935ae6585fSray if (ret == EOF || ret == '\n') 1945ae6585fSray return (ret); 1955ae6585fSray do { 1965ae6585fSray c = getchar(); 1975ae6585fSray } while (c != EOF && c != '\n'); 1985ae6585fSray return (ret); 1995ae6585fSray } 2005ae6585fSray 2015ae6585fSray int 2025ae6585fSray sendmail(const char *tmppath) 2035ae6585fSray { 2045ae6585fSray int filedes[2]; 2055ae6585fSray 2065ae6585fSray if (pipe(filedes) == -1) { 2075ae6585fSray warn("pipe: unsent report in %s", tmppath); 2085ae6585fSray return (-1); 2095ae6585fSray } 2105ae6585fSray switch (fork()) { 2115ae6585fSray case -1: 2125ae6585fSray warn("fork error: unsent report in %s", 2135ae6585fSray tmppath); 2145ae6585fSray return (-1); 2155ae6585fSray case 0: 2165ae6585fSray close(filedes[1]); 2175ae6585fSray if (dup2(filedes[0], STDIN_FILENO) == -1) { 2185ae6585fSray warn("dup2 error: unsent report in %s", 2195ae6585fSray tmppath); 2205ae6585fSray return (-1); 2215ae6585fSray } 2225ae6585fSray close(filedes[0]); 2235ae6585fSray execl("/usr/sbin/sendmail", "sendmail", 22470dfcc75Sderaadt "-oi", "-t", (void *)NULL); 2255ae6585fSray warn("sendmail error: unsent report in %s", 2265ae6585fSray tmppath); 2275ae6585fSray return (-1); 2285ae6585fSray default: 2295ae6585fSray close(filedes[0]); 2305ae6585fSray /* Pipe into sendmail. */ 2315ae6585fSray if (send_file(tmppath, filedes[1]) == -1) { 2325ae6585fSray warn("send_file error: unsent report in %s", 2335ae6585fSray tmppath); 2345ae6585fSray return (-1); 2355ae6585fSray } 2365ae6585fSray close(filedes[1]); 2375ae6585fSray wait(NULL); 2385ae6585fSray break; 2395ae6585fSray } 2405ae6585fSray return (0); 2415ae6585fSray } 2425ae6585fSray 243b2e65daaStedu void 2445ae6585fSray init(void) 2455ae6585fSray { 24649d07c37Sray size_t len = 0, namelen; 2475ae6585fSray int sysname[2]; 24849d07c37Sray const char *src; 249*e051e8faSderaadt char *dst, *cp; 2505ae6585fSray 2515ae6585fSray if ((pw = getpwuid(getuid())) == NULL) { 252b2e65daaStedu err(1, "getpwuid"); 2535ae6585fSray } 25449d07c37Sray namelen = strlen(pw->pw_name); 2555ae6585fSray 25649d07c37Sray /* Add length of expanded '&', minus existing '&'. */ 25749d07c37Sray src = pw->pw_gecos; 25849d07c37Sray src += strcspn(src, ",&"); 25949d07c37Sray while (*src == '&') { 26049d07c37Sray len += namelen - 1; 26149d07c37Sray /* Look for next '&', skipping the one we just found. */ 26249d07c37Sray src += 1 + strcspn(src, ",&"); 26349d07c37Sray } 26449d07c37Sray /* Add full name length, including all those '&' we skipped. */ 26549d07c37Sray len += src - pw->pw_gecos; 2665ae6585fSray if ((fullname = malloc(len + 1)) == NULL) { 267b2e65daaStedu err(1, "malloc"); 2685ae6585fSray } 26949d07c37Sray dst = fullname; 27049d07c37Sray src = pw->pw_gecos; 27149d07c37Sray while (*src != ',' && *src != '\0') { 27249d07c37Sray /* Copy text up to ',' or '&' and skip. */ 27349d07c37Sray len = strcspn(src, ",&"); 27449d07c37Sray memcpy(dst, src, len); 27549d07c37Sray dst += len; 27649d07c37Sray src += len; 27749d07c37Sray /* Replace '&' with login. */ 27849d07c37Sray if (*src == '&') { 27949d07c37Sray memcpy(dst, pw->pw_name, namelen); 28049d07c37Sray *dst = toupper((unsigned char)*dst); 28149d07c37Sray dst += namelen; 28249d07c37Sray ++src; 28349d07c37Sray } 28449d07c37Sray } 28549d07c37Sray *dst = '\0'; 2865ae6585fSray 2875ae6585fSray sysname[0] = CTL_KERN; 2885ae6585fSray sysname[1] = KERN_OSTYPE; 2895ae6585fSray len = sizeof(os) - 1; 2905ae6585fSray if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) { 291b2e65daaStedu err(1, "sysctl"); 2925ae6585fSray } 2935ae6585fSray 2945ae6585fSray sysname[0] = CTL_KERN; 2955ae6585fSray sysname[1] = KERN_OSRELEASE; 2965ae6585fSray len = sizeof(rel) - 1; 2975ae6585fSray if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) { 298b2e65daaStedu err(1, "sysctl"); 2995ae6585fSray } 3005ae6585fSray 301*e051e8faSderaadt sysname[0] = CTL_KERN; 302*e051e8faSderaadt sysname[1] = KERN_VERSION; 303*e051e8faSderaadt len = sizeof(details) - 1; 304*e051e8faSderaadt if (sysctl(sysname, 2, &details, &len, NULL, 0) == -1) { 305*e051e8faSderaadt err(1, "sysctl"); 306*e051e8faSderaadt } 307*e051e8faSderaadt 308*e051e8faSderaadt cp = strchr(details, '\n'); 309*e051e8faSderaadt if (cp) { 310*e051e8faSderaadt cp++; 311*e051e8faSderaadt if (*cp) 312*e051e8faSderaadt *cp++ = '\t'; 313*e051e8faSderaadt if (*cp) 314*e051e8faSderaadt *cp++ = '\t'; 315*e051e8faSderaadt if (*cp) 316*e051e8faSderaadt *cp++ = '\t'; 317*e051e8faSderaadt } 318*e051e8faSderaadt 3195ae6585fSray sysname[0] = CTL_HW; 3205ae6585fSray sysname[1] = HW_MACHINE; 3215ae6585fSray len = sizeof(mach) - 1; 3225ae6585fSray if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) { 323b2e65daaStedu err(1, "sysctl"); 3245ae6585fSray } 3255ae6585fSray 3265ae6585fSray } 3275ae6585fSray 3285ae6585fSray int 3295ae6585fSray send_file(const char *file, int dst) 3305ae6585fSray { 3315ae6585fSray int blank = 0; 33270dfcc75Sderaadt size_t len; 33370dfcc75Sderaadt char *buf; 33470dfcc75Sderaadt FILE *fp; 3355ae6585fSray 3365ae6585fSray if ((fp = fopen(file, "r")) == NULL) 3375ae6585fSray return (-1); 3385ae6585fSray while ((buf = fgetln(fp, &len))) { 3395ae6585fSray /* Skip lines starting with "SENDBUG". */ 3405ae6585fSray if (len >= sizeof("SENDBUG") - 1 && 3415ae6585fSray memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0) 3425ae6585fSray continue; 3435ae6585fSray if (len == 1 && buf[0] == '\n') 3445ae6585fSray blank = 1; 3455ae6585fSray /* Skip comments, but only if we encountered a blank line. */ 3465ae6585fSray while (len) { 347*e051e8faSderaadt char *sp = NULL, *ep = NULL; 3485ae6585fSray size_t copylen; 3495ae6585fSray 3505ae6585fSray if (blank && (sp = memchr(buf, '<', len)) != NULL) 3515ae6585fSray ep = memchr(sp, '>', len - (sp - buf + 1)); 3525ae6585fSray /* Length of string before comment. */ 3538cf86b71Sray if (ep) 3548cf86b71Sray copylen = sp - buf; 3558cf86b71Sray else 3568cf86b71Sray copylen = len; 3575ae6585fSray if (atomicio(vwrite, dst, buf, copylen) != copylen) { 3585ae6585fSray int saved_errno = errno; 3595ae6585fSray 3605ae6585fSray fclose(fp); 3615ae6585fSray errno = saved_errno; 3625ae6585fSray return (-1); 3635ae6585fSray } 3645ae6585fSray if (!ep) 3655ae6585fSray break; 3665ae6585fSray /* Skip comment. */ 3675ae6585fSray len -= ep - buf + 1; 3685ae6585fSray buf = ep + 1; 3695ae6585fSray } 3705ae6585fSray } 3715ae6585fSray fclose(fp); 3725ae6585fSray return (0); 3735ae6585fSray } 3745ae6585fSray 3755ae6585fSray void 3765ae6585fSray template(FILE *fp) 3775ae6585fSray { 3785ae6585fSray fprintf(fp, "SENDBUG: -*- sendbug -*-\n"); 3795ae6585fSray fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will be removed automatically, as\n"); 3805ae6585fSray fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>'). \n"); 3815ae6585fSray fprintf(fp, "SENDBUG:\n"); 3825ae6585fSray fprintf(fp, "SENDBUG: Choose from the following categories:\n"); 3835ae6585fSray fprintf(fp, "SENDBUG:\n"); 3845ae6585fSray fprintf(fp, "SENDBUG: %s\n", categories); 3855ae6585fSray fprintf(fp, "SENDBUG:\n"); 3865ae6585fSray fprintf(fp, "SENDBUG:\n"); 3875ae6585fSray fprintf(fp, "To: %s\n", "gnats@openbsd.org"); 3885ae6585fSray fprintf(fp, "Subject: \n"); 3895ae6585fSray fprintf(fp, "From: %s\n", pw->pw_name); 3905ae6585fSray fprintf(fp, "Cc: \n"); 3915ae6585fSray fprintf(fp, "Reply-To: %s\n", pw->pw_name); 3925711e205Sderaadt fprintf(fp, "X-sendbug-version: %s\n", version); 3935ae6585fSray fprintf(fp, "\n"); 3945ae6585fSray fprintf(fp, "\n"); 3955ae6585fSray fprintf(fp, ">Submitter-Id:\tnet\n"); 3965ae6585fSray fprintf(fp, ">Originator:\t%s\n", fullname); 3975ae6585fSray fprintf(fp, ">Organization:\n"); 3985ae6585fSray fprintf(fp, "net\n"); 3995ae6585fSray fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n"); 4005ae6585fSray fprintf(fp, ">Severity:\t<[ non-critical | serious | critical ] (one line)>\n"); 4015ae6585fSray fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n"); 4025ae6585fSray fprintf(fp, ">Category:\t<PR category (one line)>\n"); 4035ae6585fSray fprintf(fp, ">Class:\t\t<[ sw-bug | doc-bug | change-request | support ] (one line)>\n"); 4045ae6585fSray fprintf(fp, ">Release:\t<release number or tag (one line)>\n"); 4055ae6585fSray fprintf(fp, ">Environment:\n"); 4065ae6585fSray fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n"); 4075ae6585fSray fprintf(fp, "\tSystem : %s %s\n", os, rel); 408*e051e8faSderaadt fprintf(fp, "\tDetails : %s\n", details); 4095ae6585fSray fprintf(fp, "\tArchitecture: %s.%s\n", os, mach); 4105ae6585fSray fprintf(fp, "\tMachine : %s\n", mach); 4115ae6585fSray fprintf(fp, ">Description:\n"); 4125ae6585fSray fprintf(fp, "\t<precise description of the problem (multiple lines)>\n"); 4135ae6585fSray fprintf(fp, ">How-To-Repeat:\n"); 4145ae6585fSray fprintf(fp, "\t<code/input/activities to reproduce the problem (multiple lines)>\n"); 4155ae6585fSray fprintf(fp, ">Fix:\n"); 4165ae6585fSray fprintf(fp, "\t<how to correct or work around the problem, if known (multiple lines)>\n"); 4175ae6585fSray } 418