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