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