xref: /openbsd-src/usr.bin/sendbug/sendbug.c (revision e051e8fa7d50f64c41fd90d7ed4c3a8c640d05ce)
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