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