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