1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1980, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #ifndef lint 33 #if 0 34 static char sccsid[] = "@(#)fio.c 8.2 (Berkeley) 4/20/95"; 35 #endif 36 #endif /* not lint */ 37 #include <sys/cdefs.h> 38 __FBSDID("$FreeBSD$"); 39 40 #include "rcv.h" 41 #include <sys/file.h> 42 #include <sys/wait.h> 43 44 #include <unistd.h> 45 #include <paths.h> 46 #include <errno.h> 47 #include "extern.h" 48 49 /* 50 * Mail -- a mail program 51 * 52 * File I/O. 53 */ 54 55 extern int wait_status; 56 57 /* 58 * Set up the input pointers while copying the mail file into /tmp. 59 */ 60 void 61 setptr(FILE *ibuf, off_t offset) 62 { 63 int c, count; 64 char *cp, *cp2; 65 struct message this; 66 FILE *mestmp; 67 int maybe, inhead; 68 char linebuf[LINESIZE], pathbuf[PATHSIZE]; 69 int omsgCount; 70 71 /* Get temporary file. */ 72 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir); 73 if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL) 74 err(1, "can't open %s", pathbuf); 75 (void)rm(pathbuf); 76 77 if (offset == 0) { 78 msgCount = 0; 79 } else { 80 /* Seek into the file to get to the new messages */ 81 (void)fseeko(ibuf, offset, SEEK_SET); 82 /* 83 * We need to make "offset" a pointer to the end of 84 * the temp file that has the copy of the mail file. 85 * If any messages have been edited, this will be 86 * different from the offset into the mail file. 87 */ 88 (void)fseeko(otf, (off_t)0, SEEK_END); 89 offset = ftello(otf); 90 } 91 omsgCount = msgCount; 92 maybe = 1; 93 inhead = 0; 94 this.m_flag = MUSED|MNEW; 95 this.m_size = 0; 96 this.m_lines = 0; 97 this.m_block = 0; 98 this.m_offset = 0; 99 for (;;) { 100 if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) { 101 if (append(&this, mestmp)) 102 errx(1, "temporary file"); 103 makemessage(mestmp, omsgCount); 104 return; 105 } 106 count = strlen(linebuf); 107 /* 108 * Transforms lines ending in <CR><LF> to just <LF>. 109 * This allows mail to be able to read Eudora mailboxes. 110 */ 111 if (count >= 2 && linebuf[count - 1] == '\n' && 112 linebuf[count - 2] == '\r') { 113 count--; 114 linebuf[count - 1] = '\n'; 115 } 116 117 (void)fwrite(linebuf, sizeof(*linebuf), count, otf); 118 if (ferror(otf)) 119 errx(1, "/tmp"); 120 if (count) 121 linebuf[count - 1] = '\0'; 122 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 123 msgCount++; 124 if (append(&this, mestmp)) 125 errx(1, "temporary file"); 126 this.m_flag = MUSED|MNEW; 127 this.m_size = 0; 128 this.m_lines = 0; 129 this.m_block = blockof(offset); 130 this.m_offset = boffsetof(offset); 131 inhead = 1; 132 } else if (linebuf[0] == 0) { 133 inhead = 0; 134 } else if (inhead) { 135 for (cp = linebuf, cp2 = "status";; cp++) { 136 if ((c = *cp2++) == '\0') { 137 while (isspace((unsigned char)*cp++)) 138 ; 139 if (cp[-1] != ':') 140 break; 141 while ((c = *cp++) != '\0') 142 if (c == 'R') 143 this.m_flag |= MREAD; 144 else if (c == 'O') 145 this.m_flag &= ~MNEW; 146 inhead = 0; 147 break; 148 } 149 if (*cp != c && *cp != toupper((unsigned char)c)) 150 break; 151 } 152 } 153 offset += count; 154 this.m_size += count; 155 this.m_lines++; 156 maybe = linebuf[0] == 0; 157 } 158 } 159 160 /* 161 * Drop the passed line onto the passed output buffer. 162 * If a write error occurs, return -1, else the count of 163 * characters written, including the newline if requested. 164 */ 165 int 166 putline(FILE *obuf, char *linebuf, int outlf) 167 { 168 int c; 169 170 c = strlen(linebuf); 171 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 172 if (outlf) { 173 fprintf(obuf, "\n"); 174 c++; 175 } 176 if (ferror(obuf)) 177 return (-1); 178 return (c); 179 } 180 181 /* 182 * Read up a line from the specified input into the line 183 * buffer. Return the number of characters read. Do not 184 * include the newline (or carriage return) at the end. 185 */ 186 int 187 readline(FILE *ibuf, char *linebuf, int linesize) 188 { 189 int n; 190 191 clearerr(ibuf); 192 if (fgets(linebuf, linesize, ibuf) == NULL) 193 return (-1); 194 n = strlen(linebuf); 195 if (n > 0 && linebuf[n - 1] == '\n') 196 linebuf[--n] = '\0'; 197 if (n > 0 && linebuf[n - 1] == '\r') 198 linebuf[--n] = '\0'; 199 return (n); 200 } 201 202 /* 203 * Return a file buffer all ready to read up the 204 * passed message pointer. 205 */ 206 FILE * 207 setinput(struct message *mp) 208 { 209 210 (void)fflush(otf); 211 if (fseeko(itf, 212 positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 213 err(1, "fseeko"); 214 return (itf); 215 } 216 217 /* 218 * Take the data out of the passed ghost file and toss it into 219 * a dynamically allocated message structure. 220 */ 221 void 222 makemessage(FILE *f, int omsgCount) 223 { 224 size_t size; 225 struct message *nmessage; 226 227 size = (msgCount + 1) * sizeof(struct message); 228 nmessage = (struct message *)realloc(message, size); 229 if (nmessage == NULL) 230 errx(1, "Insufficient memory for %d messages\n", 231 msgCount); 232 if (omsgCount == 0 || message == NULL) 233 dot = nmessage; 234 else 235 dot = nmessage + (dot - message); 236 message = nmessage; 237 size -= (omsgCount + 1) * sizeof(struct message); 238 (void)fflush(f); 239 (void)lseek(fileno(f), (off_t)sizeof(*message), 0); 240 if (read(fileno(f), (void *)&message[omsgCount], size) != size) 241 errx(1, "Message temporary file corrupted"); 242 message[msgCount].m_size = 0; 243 message[msgCount].m_lines = 0; 244 (void)Fclose(f); 245 } 246 247 /* 248 * Append the passed message descriptor onto the temp file. 249 * If the write fails, return 1, else 0 250 */ 251 int 252 append(struct message *mp, FILE *f) 253 { 254 return (fwrite((char *)mp, sizeof(*mp), 1, f) != 1); 255 } 256 257 /* 258 * Delete a file, but only if the file is a plain file. 259 */ 260 int 261 rm(char *name) 262 { 263 struct stat sb; 264 265 if (stat(name, &sb) < 0) 266 return (-1); 267 if (!S_ISREG(sb.st_mode)) { 268 errno = EISDIR; 269 return (-1); 270 } 271 return (unlink(name)); 272 } 273 274 static int sigdepth; /* depth of holdsigs() */ 275 static sigset_t nset, oset; 276 /* 277 * Hold signals SIGHUP, SIGINT, and SIGQUIT. 278 */ 279 void 280 holdsigs(void) 281 { 282 283 if (sigdepth++ == 0) { 284 (void)sigemptyset(&nset); 285 (void)sigaddset(&nset, SIGHUP); 286 (void)sigaddset(&nset, SIGINT); 287 (void)sigaddset(&nset, SIGQUIT); 288 (void)sigprocmask(SIG_BLOCK, &nset, &oset); 289 } 290 } 291 292 /* 293 * Release signals SIGHUP, SIGINT, and SIGQUIT. 294 */ 295 void 296 relsesigs(void) 297 { 298 299 if (--sigdepth == 0) 300 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 301 } 302 303 /* 304 * Determine the size of the file possessed by 305 * the passed buffer. 306 */ 307 off_t 308 fsize(FILE *iob) 309 { 310 struct stat sbuf; 311 312 if (fstat(fileno(iob), &sbuf) < 0) 313 return (0); 314 return (sbuf.st_size); 315 } 316 317 /* 318 * Evaluate the string given as a new mailbox name. 319 * Supported meta characters: 320 * % for my system mail box 321 * %user for user's system mail box 322 * # for previous file 323 * & invoker's mbox file 324 * +file file in folder directory 325 * any shell meta character 326 * Return the file name as a dynamic string. 327 */ 328 char * 329 expand(char *name) 330 { 331 char xname[PATHSIZE]; 332 char cmdbuf[PATHSIZE]; /* also used for file names */ 333 int pid, l; 334 char *cp, *sh; 335 int pivec[2]; 336 struct stat sbuf; 337 338 /* 339 * The order of evaluation is "%" and "#" expand into constants. 340 * "&" can expand into "+". "+" can expand into shell meta characters. 341 * Shell meta characters expand into constants. 342 * This way, we make no recursive expansion. 343 */ 344 switch (*name) { 345 case '%': 346 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 347 return (savestr(xname)); 348 case '#': 349 if (name[1] != 0) 350 break; 351 if (prevfile[0] == 0) { 352 printf("No previous file\n"); 353 return (NULL); 354 } 355 return (savestr(prevfile)); 356 case '&': 357 if (name[1] == 0 && (name = value("MBOX")) == NULL) 358 name = "~/mbox"; 359 /* fall through */ 360 } 361 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 362 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 363 name = savestr(xname); 364 } 365 /* catch the most common shell meta character */ 366 if (name[0] == '~' && homedir != NULL && 367 (name[1] == '/' || name[1] == '\0')) { 368 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 369 name = savestr(xname); 370 } 371 if (!strpbrk(name, "~{[*?$`'\"\\")) 372 return (savestr(name)); 373 if (pipe(pivec) < 0) { 374 warn("pipe"); 375 return (NULL); 376 } 377 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name); 378 if ((sh = value("SHELL")) == NULL) 379 sh = _PATH_CSHELL; 380 pid = start_command(sh, 0, -1, pivec[1], "-c", cmdbuf, NULL); 381 if (pid < 0) { 382 (void)close(pivec[0]); 383 (void)close(pivec[1]); 384 return (NULL); 385 } 386 (void)close(pivec[1]); 387 l = read(pivec[0], xname, BUFSIZ); 388 (void)close(pivec[0]); 389 if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) && 390 WTERMSIG(wait_status) != SIGPIPE) { 391 fprintf(stderr, "\"%s\": Expansion failed.\n", name); 392 return (NULL); 393 } 394 if (l < 0) { 395 warn("read"); 396 return (NULL); 397 } 398 if (l == 0) { 399 fprintf(stderr, "\"%s\": No match.\n", name); 400 return (NULL); 401 } 402 if (l == BUFSIZ) { 403 fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name); 404 return (NULL); 405 } 406 xname[l] = '\0'; 407 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) 408 ; 409 cp[1] = '\0'; 410 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) { 411 fprintf(stderr, "\"%s\": Ambiguous.\n", name); 412 return (NULL); 413 } 414 return (savestr(xname)); 415 } 416 417 /* 418 * Determine the current folder directory name. 419 */ 420 int 421 getfold(char *name, int namelen) 422 { 423 char *folder; 424 int copylen; 425 426 if ((folder = value("folder")) == NULL) 427 return (-1); 428 if (*folder == '/') 429 copylen = strlcpy(name, folder, namelen); 430 else 431 copylen = snprintf(name, namelen, "%s/%s", 432 homedir ? homedir : ".", folder); 433 return (copylen < 0 || copylen >= namelen ? (-1) : (0)); 434 } 435 436 /* 437 * Return the name of the dead.letter file. 438 */ 439 char * 440 getdeadletter(void) 441 { 442 char *cp; 443 444 if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL) 445 cp = expand("~/dead.letter"); 446 else if (*cp != '/') { 447 char buf[PATHSIZE]; 448 449 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 450 cp = expand(buf); 451 } 452 return (cp); 453 } 454