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