xref: /openbsd-src/usr.bin/sendbug/sendbug.c (revision 823e89ef0f9c5c66697ef314dc4f23e5ff571875)
1 /*	$OpenBSD: sendbug.c,v 1.11 2007/03/23 03:43:46 deraadt 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 <err.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <paths.h>
20 #include <pwd.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "atomicio.h"
27 
28 int init(void);
29 int prompt(void);
30 int send_file(const char *, int dst);
31 int sendmail(const char *);
32 void template(FILE *);
33 
34 const char *categories = "system user library documentation ports kernel "
35     "alpha amd64 arm i386 m68k m88k mips ppc sgi sparc sparc64 vax";
36 char *version = "4.2";
37 
38 struct passwd *pw;
39 char os[BUFSIZ], rel[BUFSIZ], mach[BUFSIZ];
40 char *fullname;
41 
42 void
43 usage(void)
44 {
45 	fprintf(stderr, "usage: sendbug [-LPV]\n");
46 }
47 
48 int
49 main(int argc, char *argv[])
50 {
51 	const char *editor, *tmpdir;
52 	char *argp[] = {"sh", "-c", NULL, NULL}, *tmppath = NULL, *pr_form;
53 	int ch, c, fd, ret = 1;
54 	struct stat sb;
55 	time_t mtime;
56 	FILE *fp;
57 
58 	while ((ch = getopt(argc, argv, "LPV")) != -1)
59 		switch (ch) {
60 		case 'L':
61 			printf("Known categories:\n");
62 			printf("%s\n\n", categories);
63 			exit(0);
64 		case 'P':
65 			if (init() == -1)
66 				exit(1);
67 			template(stdout);
68 			exit(0);
69 		case 'V':
70 			printf("%s\n", version);
71 			exit(0);
72 		default:
73 			usage();
74 			exit(1);
75 		}
76 
77 	if (argc > 1) {
78 		usage();
79 		exit(1);
80 	}
81 
82 	if ((tmpdir = getenv("TMPDIR")) == NULL || tmpdir[0] == '\0')
83 		tmpdir = _PATH_TMP;
84 	if (asprintf(&tmppath, "%s%sp.XXXXXXXXXX", tmpdir,
85 	    tmpdir[strlen(tmpdir) - 1] == '/' ? "" : "/") == -1) {
86 		warn("asprintf");
87 		goto quit;
88 	}
89 	if ((fd = mkstemp(tmppath)) == -1)
90 		err(1, "mkstemp");
91 	if ((fp = fdopen(fd, "w+")) == NULL) {
92 		warn("fdopen");
93 		goto cleanup;
94 	}
95 
96 	if (init() == -1)
97 		goto cleanup;
98 
99 	pr_form = getenv("PR_FORM");
100 	if (pr_form) {
101 		char buf[BUFSIZ];
102 		size_t len;
103 		FILE *frfp;
104 
105 		frfp = fopen(pr_form, "r");
106 		if (frfp == NULL) {
107 			fprintf(stderr, "sendbug: can't seem to read your"
108 			    " template file (`%s'), ignoring PR_FORM\n",
109 			    pr_form);
110 			template(fp);
111 		} else {
112 			while (!feof(frfp)) {
113 				len = fread(buf, 1, sizeof buf, frfp);
114 				if (len == 0)
115 					break;
116 				if (fwrite(buf, 1, len, fp) != len)
117 					break;
118 			}
119 			fclose(frfp);
120 		}
121 	} else
122 		template(fp);
123 
124 	if (fflush(fp) == EOF || fstat(fd, &sb) == -1 || fclose(fp) == EOF) {
125 		warn("error creating template");
126 		goto cleanup;
127 	}
128 	mtime = sb.st_mtime;
129 
130  edit:
131 	if ((editor = getenv("EDITOR")) == NULL)
132 		editor = "vi";
133 	switch (fork()) {
134 	case -1:
135 		warn("fork");
136 		goto cleanup;
137 	case 0:
138 		if (asprintf(&argp[2], "%s %s", editor, tmppath) == -1)
139 			err(1, "asprintf");
140 		execv(_PATH_BSHELL, argp);
141 		err(1, "execv");
142 	default:
143 		wait(NULL);
144 		break;
145 	}
146 
147 	if (stat(tmppath, &sb) == -1) {
148 		warn("stat");
149 		goto cleanup;
150 	}
151 	if (mtime == sb.st_mtime) {
152 		warnx("report unchanged, nothing sent");
153 		goto cleanup;
154 	}
155 
156  prompt:
157 	c = prompt();
158 	switch (c) {
159 	case 'a':
160 	case EOF:
161 		warnx("unsent report in %s", tmppath);
162 		goto quit;
163 	case 'e':
164 		goto edit;
165 	case 's':
166 		if (sendmail(tmppath) == -1)
167 			goto quit;
168 		break;
169 	default:
170 		goto prompt;
171 	}
172 
173 	ret = 0;
174 
175  cleanup:
176 	if (tmppath && unlink(tmppath) == -1)
177 		warn("unlink");
178 
179  quit:
180 	return (ret);
181 }
182 
183 int
184 prompt(void)
185 {
186 	int c, ret;
187 
188 	fpurge(stdin);
189 	fprintf(stderr, "a)bort, e)dit, or s)end: ");
190 	fflush(stderr);
191 	ret = getchar();
192 	if (ret == EOF || ret == '\n')
193 		return (ret);
194 	do {
195 		c = getchar();
196 	} while (c != EOF && c != '\n');
197 	return (ret);
198 }
199 
200 int
201 sendmail(const char *tmppath)
202 {
203 	int filedes[2];
204 
205 	if (pipe(filedes) == -1) {
206 		warn("pipe: unsent report in %s", tmppath);
207 		return (-1);
208 	}
209 	switch (fork()) {
210 	case -1:
211 		warn("fork error: unsent report in %s",
212 		    tmppath);
213 		return (-1);
214 	case 0:
215 		close(filedes[1]);
216 		if (dup2(filedes[0], STDIN_FILENO) == -1) {
217 			warn("dup2 error: unsent report in %s",
218 			    tmppath);
219 			return (-1);
220 		}
221 		close(filedes[0]);
222 		execl("/usr/sbin/sendmail", "sendmail",
223 		    "-oi", "-t", (void *)NULL);
224 		warn("sendmail error: unsent report in %s",
225 		    tmppath);
226 		return (-1);
227 	default:
228 		close(filedes[0]);
229 		/* Pipe into sendmail. */
230 		if (send_file(tmppath, filedes[1]) == -1) {
231 			warn("send_file error: unsent report in %s",
232 			    tmppath);
233 			return (-1);
234 		}
235 		close(filedes[1]);
236 		wait(NULL);
237 		break;
238 	}
239 	return (0);
240 }
241 
242 int
243 init(void)
244 {
245 	size_t len;
246 	int sysname[2];
247 
248 	if ((pw = getpwuid(getuid())) == NULL) {
249 		warn("getpwuid");
250 		return (-1);
251 	}
252 
253 	/* Get full name. */
254 	len = strcspn(pw->pw_gecos, ",");
255 	if ((fullname = malloc(len + 1)) == NULL) {
256 		warn("malloc");
257 		return (-1);
258 	}
259 	memcpy(fullname, pw->pw_gecos, len);
260 	fullname[len] = '\0';
261 
262 	sysname[0] = CTL_KERN;
263 	sysname[1] = KERN_OSTYPE;
264 	len = sizeof(os) - 1;
265 	if (sysctl(sysname, 2, &os, &len, NULL, 0) == -1) {
266 		warn("sysctl");
267 		return (-1);
268 	}
269 
270 	sysname[0] = CTL_KERN;
271 	sysname[1] = KERN_OSRELEASE;
272 	len = sizeof(rel) - 1;
273 	if (sysctl(sysname, 2, &rel, &len, NULL, 0) == -1) {
274 		warn("sysctl");
275 		return (-1);
276 	}
277 
278 	sysname[0] = CTL_HW;
279 	sysname[1] = HW_MACHINE;
280 	len = sizeof(mach) - 1;
281 	if (sysctl(sysname, 2, &mach, &len, NULL, 0) == -1) {
282 		warn("sysctl");
283 		return (-1);
284 	}
285 
286 	return (0);
287 }
288 
289 int
290 send_file(const char *file, int dst)
291 {
292 	int blank = 0;
293 	size_t len;
294 	char *buf;
295 	FILE *fp;
296 
297 	if ((fp = fopen(file, "r")) == NULL)
298 		return (-1);
299 	while ((buf = fgetln(fp, &len))) {
300 		/* Skip lines starting with "SENDBUG". */
301 		if (len >= sizeof("SENDBUG") - 1 &&
302 		    memcmp(buf, "SENDBUG", sizeof("SENDBUG") - 1) == 0)
303 			continue;
304 		if (len == 1 && buf[0] == '\n')
305 			blank = 1;
306 		/* Skip comments, but only if we encountered a blank line. */
307 		while (len) {
308 			char *sp, *ep = NULL;
309 			size_t copylen;
310 
311 			if (blank && (sp = memchr(buf, '<', len)) != NULL)
312 				ep = memchr(sp, '>', len - (sp - buf + 1));
313 			/* Length of string before comment. */
314 			if (ep)
315 				copylen = sp - buf;
316 			else
317 				copylen = len;
318 			if (atomicio(vwrite, dst, buf, copylen) != copylen) {
319 				int saved_errno = errno;
320 
321 				fclose(fp);
322 				errno = saved_errno;
323 				return (-1);
324 			}
325 			if (!ep)
326 				break;
327 			/* Skip comment. */
328 			len -= ep - buf + 1;
329 			buf = ep + 1;
330 		}
331 	}
332 	fclose(fp);
333 	return (0);
334 }
335 
336 void
337 template(FILE *fp)
338 {
339 	fprintf(fp, "SENDBUG: -*- sendbug -*-\n");
340 	fprintf(fp, "SENDBUG: Lines starting with `SENDBUG' will be removed automatically, as\n");
341 	fprintf(fp, "SENDBUG: will all comments (text enclosed in `<' and `>').              \n");
342 	fprintf(fp, "SENDBUG:\n");
343 	fprintf(fp, "SENDBUG: Choose from the following categories:\n");
344 	fprintf(fp, "SENDBUG:\n");
345 	fprintf(fp, "SENDBUG: %s\n", categories);
346 	fprintf(fp, "SENDBUG:\n");
347 	fprintf(fp, "SENDBUG:\n");
348 	fprintf(fp, "To: %s\n", "gnats@openbsd.org");
349 	fprintf(fp, "Subject: \n");
350 	fprintf(fp, "From: %s\n", pw->pw_name);
351 	fprintf(fp, "Cc: \n");
352 	fprintf(fp, "Reply-To: %s\n", pw->pw_name);
353 	fprintf(fp, "X-sendbug-version: %s\n", version);
354 	fprintf(fp, "\n");
355 	fprintf(fp, "\n");
356 	fprintf(fp, ">Submitter-Id:\tnet\n");
357 	fprintf(fp, ">Originator:\t%s\n", fullname);
358 	fprintf(fp, ">Organization:\n");
359 	fprintf(fp, "net\n");
360 	fprintf(fp, ">Synopsis:\t<synopsis of the problem (one line)>\n");
361 	fprintf(fp, ">Severity:\t<[ non-critical | serious | critical ] (one line)>\n");
362 	fprintf(fp, ">Priority:\t<[ low | medium | high ] (one line)>\n");
363 	fprintf(fp, ">Category:\t<PR category (one line)>\n");
364 	fprintf(fp, ">Class:\t\t<[ sw-bug | doc-bug | change-request | support ] (one line)>\n");
365 	fprintf(fp, ">Release:\t<release number or tag (one line)>\n");
366 	fprintf(fp, ">Environment:\n");
367 	fprintf(fp, "\t<machine, os, target, libraries (multiple lines)>\n");
368 	fprintf(fp, "\tSystem      : %s %s\n", os, rel);
369 	fprintf(fp, "\tArchitecture: %s.%s\n", os, mach);
370 	fprintf(fp, "\tMachine     : %s\n", mach);
371 	fprintf(fp, ">Description:\n");
372 	fprintf(fp, "\t<precise description of the problem (multiple lines)>\n");
373 	fprintf(fp, ">How-To-Repeat:\n");
374 	fprintf(fp, "\t<code/input/activities to reproduce the problem (multiple lines)>\n");
375 	fprintf(fp, ">Fix:\n");
376 	fprintf(fp, "\t<how to correct or work around the problem, if known (multiple lines)>\n");
377 }
378