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