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