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