| 1 | /* |
| 2 | * Copyright (c) 1980 Regents of the University of California. |
| 3 | * All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms are permitted |
| 6 | * provided that the above copyright notice and this paragraph are |
| 7 | * duplicated in all such forms and that any documentation, |
| 8 | * advertising materials, and other materials related to such |
| 9 | * distribution and use acknowledge that the software was developed |
| 10 | * by the University of California, Berkeley. The name of the |
| 11 | * University may not be used to endorse or promote products derived |
| 12 | * from this software without specific prior written permission. |
| 13 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
| 14 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
| 15 | * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| 16 | */ |
| 17 | |
| 18 | #ifndef lint |
| 19 | static char sccsid[] = "@(#)fio.c 5.18 (Berkeley) %G%"; |
| 20 | #endif /* not lint */ |
| 21 | |
| 22 | #include "rcv.h" |
| 23 | #include <sys/stat.h> |
| 24 | #include <sys/file.h> |
| 25 | #include <sys/wait.h> |
| 26 | #include <errno.h> |
| 27 | |
| 28 | /* |
| 29 | * Mail -- a mail program |
| 30 | * |
| 31 | * File I/O. |
| 32 | */ |
| 33 | |
| 34 | /* |
| 35 | * Set up the input pointers while copying the mail file into |
| 36 | * /tmp. |
| 37 | */ |
| 38 | setptr(ibuf) |
| 39 | register FILE *ibuf; |
| 40 | { |
| 41 | register c; |
| 42 | register char *cp, *cp2; |
| 43 | register count; |
| 44 | char linebuf[LINESIZE]; |
| 45 | int maybe, inhead; |
| 46 | FILE *mestmp; |
| 47 | off_t offset; |
| 48 | struct message this; |
| 49 | extern char tempSet[]; |
| 50 | |
| 51 | if ((c = opentemp(tempSet)) < 0) |
| 52 | exit(1); |
| 53 | if ((mestmp = fdopen(c, "r+")) == NULL) |
| 54 | panic("Can't open temporary"); |
| 55 | msgCount = 0; |
| 56 | maybe = 1; |
| 57 | inhead = 0; |
| 58 | offset = 0; |
| 59 | this.m_flag = MUSED|MNEW; |
| 60 | this.m_size = 0; |
| 61 | this.m_lines = 0; |
| 62 | this.m_block = 0; |
| 63 | this.m_offset = 0; |
| 64 | for (;;) { |
| 65 | if (fgets(linebuf, LINESIZE, ibuf) == NULL) { |
| 66 | if (append(&this, mestmp)) { |
| 67 | perror(tempSet); |
| 68 | exit(1); |
| 69 | } |
| 70 | fclose(ibuf); |
| 71 | makemessage(mestmp); |
| 72 | return; |
| 73 | } |
| 74 | count = strlen(linebuf); |
| 75 | fwrite(linebuf, sizeof *linebuf, count, otf); |
| 76 | if (ferror(otf)) { |
| 77 | perror("/tmp"); |
| 78 | exit(1); |
| 79 | } |
| 80 | linebuf[count - 1] = 0; |
| 81 | if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { |
| 82 | msgCount++; |
| 83 | if (append(&this, mestmp)) { |
| 84 | perror(tempSet); |
| 85 | exit(1); |
| 86 | } |
| 87 | this.m_flag = MUSED|MNEW; |
| 88 | this.m_size = 0; |
| 89 | this.m_lines = 0; |
| 90 | this.m_block = blockof(offset); |
| 91 | this.m_offset = offsetof(offset); |
| 92 | inhead = 1; |
| 93 | } else if (linebuf[0] == 0) { |
| 94 | inhead = 0; |
| 95 | } else if (inhead) { |
| 96 | for (cp = linebuf, cp2 = "status";; cp++) { |
| 97 | if ((c = *cp2++) == 0) { |
| 98 | while (isspace(*cp++)) |
| 99 | ; |
| 100 | if (cp[-1] != ':') |
| 101 | break; |
| 102 | while (c = *cp++) |
| 103 | if (c == 'R') |
| 104 | this.m_flag |= MREAD; |
| 105 | else if (c == 'O') |
| 106 | this.m_flag &= ~MNEW; |
| 107 | inhead = 0; |
| 108 | break; |
| 109 | } |
| 110 | if (*cp != c && *cp != toupper(c)) |
| 111 | break; |
| 112 | } |
| 113 | } |
| 114 | offset += count; |
| 115 | this.m_size += count; |
| 116 | this.m_lines++; |
| 117 | maybe = linebuf[0] == 0; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | * Drop the passed line onto the passed output buffer. |
| 123 | * If a write error occurs, return -1, else the count of |
| 124 | * characters written, including the newline. |
| 125 | */ |
| 126 | putline(obuf, linebuf) |
| 127 | FILE *obuf; |
| 128 | char *linebuf; |
| 129 | { |
| 130 | register int c; |
| 131 | |
| 132 | c = strlen(linebuf); |
| 133 | fwrite(linebuf, sizeof *linebuf, c, obuf); |
| 134 | putc('\n', obuf); |
| 135 | if (ferror(obuf)) |
| 136 | return (-1); |
| 137 | return (c + 1); |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | * Read up a line from the specified input into the line |
| 142 | * buffer. Return the number of characters read. Do not |
| 143 | * include the newline at the end. |
| 144 | */ |
| 145 | readline(ibuf, linebuf) |
| 146 | FILE *ibuf; |
| 147 | char *linebuf; |
| 148 | { |
| 149 | register int n; |
| 150 | |
| 151 | clearerr(ibuf); |
| 152 | if (fgets(linebuf, LINESIZE, ibuf) == NULL) |
| 153 | return -1; |
| 154 | n = strlen(linebuf); |
| 155 | if (n > 0 && linebuf[n - 1] == '\n') |
| 156 | linebuf[--n] = '\0'; |
| 157 | return n; |
| 158 | } |
| 159 | |
| 160 | /* |
| 161 | * Return a file buffer all ready to read up the |
| 162 | * passed message pointer. |
| 163 | */ |
| 164 | FILE * |
| 165 | setinput(mp) |
| 166 | register struct message *mp; |
| 167 | { |
| 168 | |
| 169 | fflush(otf); |
| 170 | if (fseek(itf, positionof(mp->m_block, mp->m_offset), 0) < 0) { |
| 171 | perror("fseek"); |
| 172 | panic("temporary file seek"); |
| 173 | } |
| 174 | return (itf); |
| 175 | } |
| 176 | |
| 177 | /* |
| 178 | * Take the data out of the passed ghost file and toss it into |
| 179 | * a dynamically allocated message structure. |
| 180 | */ |
| 181 | makemessage(f) |
| 182 | FILE *f; |
| 183 | { |
| 184 | register size = (msgCount + 1) * sizeof (struct message); |
| 185 | off_t lseek(); |
| 186 | |
| 187 | if (message != 0) |
| 188 | free((char *) message); |
| 189 | if ((message = (struct message *) malloc((unsigned) size)) == 0) |
| 190 | panic("Insufficient memory for %d messages", msgCount); |
| 191 | dot = message; |
| 192 | size -= sizeof (struct message); |
| 193 | fflush(f); |
| 194 | lseek(fileno(f), (long) sizeof *message, 0); |
| 195 | if (read(fileno(f), (char *) message, size) != size) |
| 196 | panic("Message temporary file corrupted"); |
| 197 | message[msgCount].m_size = 0; |
| 198 | message[msgCount].m_lines = 0; |
| 199 | fclose(f); |
| 200 | } |
| 201 | |
| 202 | /* |
| 203 | * Append the passed message descriptor onto the temp file. |
| 204 | * If the write fails, return 1, else 0 |
| 205 | */ |
| 206 | append(mp, f) |
| 207 | struct message *mp; |
| 208 | FILE *f; |
| 209 | { |
| 210 | return fwrite((char *) mp, sizeof *mp, 1, f) != 1; |
| 211 | } |
| 212 | |
| 213 | /* |
| 214 | * Delete a file, but only if the file is a plain file. |
| 215 | */ |
| 216 | remove(name) |
| 217 | char name[]; |
| 218 | { |
| 219 | struct stat statb; |
| 220 | extern int errno; |
| 221 | |
| 222 | if (stat(name, &statb) < 0) |
| 223 | return(-1); |
| 224 | if ((statb.st_mode & S_IFMT) != S_IFREG) { |
| 225 | errno = EISDIR; |
| 226 | return(-1); |
| 227 | } |
| 228 | return unlink(name); |
| 229 | } |
| 230 | |
| 231 | /* |
| 232 | * Terminate an editing session by attempting to write out the user's |
| 233 | * file from the temporary. Save any new stuff appended to the file. |
| 234 | */ |
| 235 | edstop() |
| 236 | { |
| 237 | register int gotcha, c; |
| 238 | register struct message *mp; |
| 239 | FILE *obuf, *ibuf, *readstat; |
| 240 | struct stat statb; |
| 241 | char tempname[30]; |
| 242 | char *mktemp(); |
| 243 | |
| 244 | if (readonly) |
| 245 | return; |
| 246 | holdsigs(); |
| 247 | if (Tflag != NOSTR) { |
| 248 | if ((readstat = fopen(Tflag, "w")) == NULL) |
| 249 | Tflag = NOSTR; |
| 250 | } |
| 251 | for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) { |
| 252 | if (mp->m_flag & MNEW) { |
| 253 | mp->m_flag &= ~MNEW; |
| 254 | mp->m_flag |= MSTATUS; |
| 255 | } |
| 256 | if (mp->m_flag & (MODIFY|MDELETED|MSTATUS)) |
| 257 | gotcha++; |
| 258 | if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) { |
| 259 | char *id; |
| 260 | |
| 261 | if ((id = hfield("article-id", mp)) != NOSTR) |
| 262 | fprintf(readstat, "%s\n", id); |
| 263 | } |
| 264 | } |
| 265 | if (Tflag != NOSTR) |
| 266 | fclose(readstat); |
| 267 | if (!gotcha || Tflag != NOSTR) |
| 268 | goto done; |
| 269 | ibuf = NULL; |
| 270 | if (stat(mailname, &statb) >= 0 && statb.st_size > mailsize) { |
| 271 | strcpy(tempname, "/tmp/mboxXXXXXX"); |
| 272 | mktemp(tempname); |
| 273 | if ((obuf = fopen(tempname, "w")) == NULL) { |
| 274 | perror(tempname); |
| 275 | relsesigs(); |
| 276 | reset(0); |
| 277 | } |
| 278 | if ((ibuf = fopen(mailname, "r")) == NULL) { |
| 279 | perror(mailname); |
| 280 | fclose(obuf); |
| 281 | remove(tempname); |
| 282 | relsesigs(); |
| 283 | reset(0); |
| 284 | } |
| 285 | fseek(ibuf, mailsize, 0); |
| 286 | while ((c = getc(ibuf)) != EOF) |
| 287 | putc(c, obuf); |
| 288 | fclose(ibuf); |
| 289 | fclose(obuf); |
| 290 | if ((ibuf = fopen(tempname, "r")) == NULL) { |
| 291 | perror(tempname); |
| 292 | remove(tempname); |
| 293 | relsesigs(); |
| 294 | reset(0); |
| 295 | } |
| 296 | remove(tempname); |
| 297 | } |
| 298 | printf("\"%s\" ", mailname); |
| 299 | fflush(stdout); |
| 300 | if ((obuf = fopen(mailname, "r+")) == NULL) { |
| 301 | perror(mailname); |
| 302 | relsesigs(); |
| 303 | reset(0); |
| 304 | } |
| 305 | trunc(obuf); |
| 306 | c = 0; |
| 307 | for (mp = &message[0]; mp < &message[msgCount]; mp++) { |
| 308 | if ((mp->m_flag & MDELETED) != 0) |
| 309 | continue; |
| 310 | c++; |
| 311 | if (send(mp, obuf, (struct ignoretab *) NULL, NOSTR) < 0) { |
| 312 | perror(mailname); |
| 313 | relsesigs(); |
| 314 | reset(0); |
| 315 | } |
| 316 | } |
| 317 | gotcha = (c == 0 && ibuf == NULL); |
| 318 | if (ibuf != NULL) { |
| 319 | while ((c = getc(ibuf)) != EOF) |
| 320 | putc(c, obuf); |
| 321 | fclose(ibuf); |
| 322 | } |
| 323 | fflush(obuf); |
| 324 | if (ferror(obuf)) { |
| 325 | perror(mailname); |
| 326 | relsesigs(); |
| 327 | reset(0); |
| 328 | } |
| 329 | fclose(obuf); |
| 330 | if (gotcha) { |
| 331 | remove(mailname); |
| 332 | printf("removed\n"); |
| 333 | } else |
| 334 | printf("complete\n"); |
| 335 | fflush(stdout); |
| 336 | |
| 337 | done: |
| 338 | relsesigs(); |
| 339 | } |
| 340 | |
| 341 | static int sigdepth; /* depth of holdsigs() */ |
| 342 | static int omask; |
| 343 | /* |
| 344 | * Hold signals SIGHUP, SIGINT, and SIGQUIT. |
| 345 | */ |
| 346 | holdsigs() |
| 347 | { |
| 348 | |
| 349 | if (sigdepth++ == 0) |
| 350 | omask = sigblock(sigmask(SIGHUP)|sigmask(SIGINT)|sigmask(SIGQUIT)); |
| 351 | } |
| 352 | |
| 353 | /* |
| 354 | * Release signals SIGHUP, SIGINT, and SIGQUIT. |
| 355 | */ |
| 356 | relsesigs() |
| 357 | { |
| 358 | |
| 359 | if (--sigdepth == 0) |
| 360 | sigsetmask(omask); |
| 361 | } |
| 362 | |
| 363 | /* |
| 364 | * Open a temp file by creating and unlinking. |
| 365 | * Return the open file descriptor. |
| 366 | */ |
| 367 | opentemp(file) |
| 368 | char file[]; |
| 369 | { |
| 370 | int f; |
| 371 | |
| 372 | if ((f = open(file, O_CREAT|O_EXCL|O_RDWR, 0600)) < 0) |
| 373 | perror(file); |
| 374 | remove(file); |
| 375 | return (f); |
| 376 | } |
| 377 | |
| 378 | /* |
| 379 | * Determine the size of the file possessed by |
| 380 | * the passed buffer. |
| 381 | */ |
| 382 | off_t |
| 383 | fsize(iob) |
| 384 | FILE *iob; |
| 385 | { |
| 386 | struct stat sbuf; |
| 387 | |
| 388 | if (fstat(fileno(iob), &sbuf) < 0) |
| 389 | return 0; |
| 390 | return sbuf.st_size; |
| 391 | } |
| 392 | |
| 393 | /* |
| 394 | * Evaluate the string given as a new mailbox name. |
| 395 | * Supported meta characters: |
| 396 | * % for my system mail box |
| 397 | * %user for user's system mail box |
| 398 | * # for previous file |
| 399 | * & invoker's mbox file |
| 400 | * +file file in folder directory |
| 401 | * any shell meta character |
| 402 | * Return the file name as a dynamic string. |
| 403 | */ |
| 404 | char * |
| 405 | expand(name) |
| 406 | register char *name; |
| 407 | { |
| 408 | char xname[PATHSIZE]; |
| 409 | char cmdbuf[PATHSIZE]; /* also used for file names */ |
| 410 | register int pid, l; |
| 411 | register char *cp, *shell; |
| 412 | int pivec[2]; |
| 413 | struct stat sbuf; |
| 414 | extern union wait wait_status; |
| 415 | |
| 416 | /* |
| 417 | * The order of evaluation is "%" and "#" expand into constants. |
| 418 | * "&" can expand into "+". "+" can expand into shell meta characters. |
| 419 | * Shell meta characters expand into constants. |
| 420 | * This way, we make no recursive expansion. |
| 421 | */ |
| 422 | switch (*name) { |
| 423 | case '%': |
| 424 | findmail(name[1] ? name + 1 : myname, xname); |
| 425 | return savestr(xname); |
| 426 | case '#': |
| 427 | if (name[1] != 0) |
| 428 | break; |
| 429 | if (prevfile[0] == 0) { |
| 430 | printf("No previous file\n"); |
| 431 | return NOSTR; |
| 432 | } |
| 433 | return savestr(prevfile); |
| 434 | case '&': |
| 435 | if (name[1] == 0 && (name = value("MBOX")) == NOSTR) |
| 436 | name = "~/mbox"; |
| 437 | /* fall through */ |
| 438 | } |
| 439 | if (name[0] == '+' && getfold(cmdbuf) >= 0) { |
| 440 | sprintf(xname, "%s/%s", cmdbuf, name + 1); |
| 441 | name = savestr(xname); |
| 442 | } |
| 443 | /* catch the most common shell meta character */ |
| 444 | if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { |
| 445 | sprintf(xname, "%s%s", homedir, name + 1); |
| 446 | name = savestr(xname); |
| 447 | } |
| 448 | if (!anyof(name, "~{[*?$`'\"\\")) |
| 449 | return name; |
| 450 | if (pipe(pivec) < 0) { |
| 451 | perror("pipe"); |
| 452 | return name; |
| 453 | } |
| 454 | sprintf(cmdbuf, "echo %s", name); |
| 455 | if ((shell = value("SHELL")) == NOSTR) |
| 456 | shell = SHELL; |
| 457 | pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NOSTR); |
| 458 | if (pid < 0) { |
| 459 | close(pivec[0]); |
| 460 | close(pivec[1]); |
| 461 | return NOSTR; |
| 462 | } |
| 463 | close(pivec[1]); |
| 464 | l = read(pivec[0], xname, BUFSIZ); |
| 465 | close(pivec[0]); |
| 466 | if (wait_child(pid) < 0 && wait_status.w_termsig != SIGPIPE) { |
| 467 | fprintf(stderr, "\"%s\": Expansion failed.\n", name); |
| 468 | return NOSTR; |
| 469 | } |
| 470 | if (l < 0) { |
| 471 | perror("read"); |
| 472 | return NOSTR; |
| 473 | } |
| 474 | if (l == 0) { |
| 475 | fprintf(stderr, "\"%s\": No match.\n", name); |
| 476 | return NOSTR; |
| 477 | } |
| 478 | if (l == BUFSIZ) { |
| 479 | fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name); |
| 480 | return NOSTR; |
| 481 | } |
| 482 | xname[l] = 0; |
| 483 | for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) |
| 484 | ; |
| 485 | cp[1] = '\0'; |
| 486 | if (index(xname, ' ') && stat(xname, &sbuf) < 0) { |
| 487 | fprintf(stderr, "\"%s\": Ambiguous.\n", name); |
| 488 | return NOSTR; |
| 489 | } |
| 490 | return savestr(xname); |
| 491 | } |
| 492 | |
| 493 | /* |
| 494 | * Determine the current folder directory name. |
| 495 | */ |
| 496 | getfold(name) |
| 497 | char *name; |
| 498 | { |
| 499 | char *folder; |
| 500 | |
| 501 | if ((folder = value("folder")) == NOSTR) |
| 502 | return (-1); |
| 503 | if (*folder == '/') |
| 504 | strcpy(name, folder); |
| 505 | else |
| 506 | sprintf(name, "%s/%s", homedir, folder); |
| 507 | return (0); |
| 508 | } |
| 509 | |
| 510 | /* |
| 511 | * Return the name of the dead.letter file. |
| 512 | */ |
| 513 | char * |
| 514 | getdeadletter() |
| 515 | { |
| 516 | register char *cp; |
| 517 | |
| 518 | if ((cp = value("DEAD")) == NOSTR || (cp = expand(cp)) == NOSTR) |
| 519 | cp = expand("~/dead.letter"); |
| 520 | else if (*cp != '/') { |
| 521 | char buf[PATHSIZE]; |
| 522 | |
| 523 | (void) sprintf(buf, "~/%s", cp); |
| 524 | cp = expand(buf); |
| 525 | } |
| 526 | return cp; |
| 527 | } |
| 528 | |
| 529 | /* |
| 530 | * A nicer version of Fdopen, which allows us to fclose |
| 531 | * without losing the open file. |
| 532 | */ |
| 533 | FILE * |
| 534 | Fdopen(fildes, mode) |
| 535 | char *mode; |
| 536 | { |
| 537 | int f; |
| 538 | |
| 539 | if ((f = dup(fildes)) < 0) { |
| 540 | perror("dup"); |
| 541 | return (NULL); |
| 542 | } |
| 543 | return fdopen(f, mode); |
| 544 | } |