5481 CVE-2012-1750 mailx(1) tilde expansion vulnerability
[unleashed.git] / usr / src / cmd / mailx / fio.c
blob53907ca7605a406778497662ea37ab714fe30ff1
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
20 * CDDL HEADER END
24 * Copyright 2014 Joyent, Inc.
28 * Copyright 1999 Sun Microsystems, Inc. All rights reserved.
29 * Use is subject to license terms.
32 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
33 /* All Rights Reserved */
35 #include "rcv.h"
36 #include <locale.h>
37 #include <wordexp.h>
40 * mailx -- a modified version of a University of California at Berkeley
41 * mail program
43 * File I/O.
46 static int getln(char *line, int max, FILE *f);
47 static int linecount(char *lp, long size);
50 * Set up the input pointers while copying the mail file into
51 * /tmp.
54 void
55 setptr(register FILE *ibuf)
57 int n, newline = 1, blankline = 1;
58 int StartNewMsg = TRUE;
59 int ToldUser = FALSE;
60 long clen = -1L;
61 int hdr = 0;
62 int cflg = 0; /* found Content-length in header */
63 register char *cp;
64 register int l;
65 register long s;
66 off_t offset;
67 char linebuf[LINESIZE];
68 int inhead, newmail, Odot;
69 short flag;
71 if (!space) {
72 msgCount = 0;
73 offset = 0;
74 space = 32;
75 newmail = 0;
76 message =
77 (struct message *)calloc(space, sizeof (struct message));
78 if (message == NULL) {
79 fprintf(stderr, gettext(
80 "calloc: insufficient memory for %d messages\n"),
81 space);
82 exit(1);
83 /* NOTREACHED */
85 dot = message;
86 } else {
87 newmail = 1;
88 offset = fsize(otf);
90 s = 0L;
91 l = 0;
93 * Set default flags. When reading from
94 * a folder, assume the message has been
95 * previously read.
97 if (edit)
98 flag = MUSED|MREAD;
99 else
100 flag = MUSED|MNEW;
102 inhead = 0;
103 while ((n = getln(linebuf, sizeof (linebuf), ibuf)) > 0) {
104 if (!newline) {
105 goto putout;
107 top:
108 hdr = inhead && (headerp(linebuf) ||
109 (linebuf[0] == ' ' || linebuf[0] == '\t'));
110 if (!hdr && cflg) { /* nonheader, Content-length seen */
111 if (clen > 0 && clen < n) { /* read too much */
113 * NB: this only can happen if there is a
114 * small content that is NOT \n terminated
115 * and has no leading blank line, i.e., never.
117 if (fwrite(linebuf, 1, (int)clen, otf) !=
118 clen) {
119 fclose(ibuf);
120 fflush(otf);
121 } else {
122 l += linecount(linebuf, clen);
124 offset += clen;
125 s += clen;
126 n -= (int)clen;
127 /* shift line to the left, copy null as well */
128 memcpy(linebuf, linebuf+clen, n+1);
129 cflg = 0;
130 message[msgCount-1].m_clen = clen + 1;
131 blankline = 1;
132 StartNewMsg = TRUE;
133 goto top;
135 /* here, clen == 0 or clen >= n */
136 if (n == 1 && linebuf[0] == '\n') {
137 /* leading empty line */
138 clen++; /* cheat */
139 inhead = 0;
141 offset += clen;
142 s += (long)clen;
143 message[msgCount-1].m_clen = clen;
144 for (;;) {
145 if (fwrite(linebuf, 1, n, otf) != n) {
146 fclose(ibuf);
147 fflush(otf);
148 } else {
149 l += linecount(linebuf, n);
151 clen -= n;
152 if (clen <= 0) {
153 break;
155 n = clen < sizeof (linebuf) ?
156 (int)clen : (int)sizeof (linebuf);
157 if ((n = fread(linebuf, 1, n, ibuf)) <= 0) {
158 fprintf(stderr, gettext(
159 "%s:\tYour mailfile was found to be corrupted.\n"),
160 progname);
161 fprintf(stderr, gettext(
162 "\t(Unexpected end-of-file).\n"));
163 fprintf(stderr, gettext(
164 "\tMessage #%d may be truncated.\n\n"),
165 msgCount);
166 offset -= clen;
167 s -= clen;
168 clen = 0; /* stop the loop */
171 /* All done, go to top for next message */
172 cflg = 0;
173 blankline = 1;
174 StartNewMsg = TRUE;
175 continue;
178 /* Look for a From line that starts a new message */
179 if (blankline && linebuf[0] == 'F' && is_headline(linebuf)) {
180 if (msgCount > 0 && !newmail) {
181 message[msgCount-1].m_size = s;
182 message[msgCount-1].m_lines = l;
183 message[msgCount-1].m_flag = flag;
185 if (msgCount >= space) {
187 * Limit the speed at which the
188 * allocated space grows.
190 if (space < 512)
191 space = space*2;
192 else
193 space += 512;
194 errno = 0;
195 Odot = dot - &(message[0]);
196 message = (struct message *)
197 realloc(message,
198 space*(sizeof (struct message)));
199 if (message == NULL) {
200 perror("realloc failed");
201 fprintf(stderr, gettext(
202 "realloc: insufficient memory for %d messages\n"),
203 space);
204 exit(1);
206 dot = &message[Odot];
208 message[msgCount].m_offset = offset;
209 message[msgCount].m_text = TRUE;
210 message[msgCount].m_clen = 0;
211 newmail = 0;
212 msgCount++;
213 if (edit)
214 flag = MUSED|MREAD;
215 else
216 flag = MUSED|MNEW;
217 inhead = 1;
218 s = 0L;
219 l = 0;
220 StartNewMsg = FALSE;
221 ToldUser = FALSE;
222 goto putout;
225 /* if didn't get a header line, we're no longer in the header */
226 if (!hdr)
227 inhead = 0;
228 if (!inhead)
229 goto putout;
232 * Look for Status: line. Do quick check for second character,
233 * many headers start with "S" but few have "t" as second char.
235 if ((linebuf[1] == 't' || linebuf[1] == 'T') &&
236 ishfield(linebuf, "status")) {
237 cp = hcontents(linebuf);
238 flag = MUSED|MNEW;
239 if (strchr(cp, 'R'))
240 flag |= MREAD;
241 if (strchr(cp, 'O'))
242 flag &= ~MNEW;
245 * Look for Content-Length and Content-Type headers. Like
246 * above, do a quick check for the "-", which is rare.
248 if (linebuf[7] == '-') {
249 if (ishfield(linebuf, "content-length")) {
250 if (!cflg) {
251 clen = atol(hcontents(linebuf));
252 cflg = clen >= 0;
254 } else if (ishfield(linebuf, "content-type")) {
255 char word[LINESIZE];
256 char *cp2;
258 cp = hcontents(linebuf);
259 cp2 = word;
260 while (!isspace(*cp))
261 *cp2++ = *cp++;
262 *cp2 = '\0';
263 if (icequal(word, "binary"))
264 message[msgCount-1].m_text = FALSE;
267 putout:
268 offset += n;
269 s += (long)n;
270 if (fwrite(linebuf, 1, n, otf) != n) {
271 fclose(ibuf);
272 fflush(otf);
273 } else {
274 l++;
276 if (ferror(otf)) {
277 perror("/tmp");
278 exit(1);
280 if (msgCount == 0) {
281 fclose(ibuf);
282 fflush(otf);
284 if (linebuf[n-1] == '\n') {
285 blankline = newline && n == 1;
286 newline = 1;
287 if (n == 1) {
288 /* Blank line. Skip StartNewMsg check below */
289 continue;
291 } else {
292 newline = 0;
294 if (StartNewMsg && !ToldUser) {
295 fprintf(stderr, gettext(
296 "%s:\tYour mailfile was found to be corrupted\n"),
297 progname);
298 fprintf(stderr,
299 gettext("\t(Content-length mismatch).\n"));
300 fprintf(stderr, gettext(
301 "\tMessage #%d may be truncated,\n"), msgCount);
302 fprintf(stderr, gettext(
303 "\twith another message concatenated to it.\n\n"));
304 ToldUser = TRUE;
308 if (n == 0) {
309 fflush(otf);
310 if (fferror(otf)) {
311 perror("/tmp");
312 exit(1);
314 if (msgCount) {
315 message[msgCount-1].m_size = s;
316 message[msgCount-1].m_lines = l;
317 message[msgCount-1].m_flag = flag;
319 fclose(ibuf);
324 * Compute the content length of a message and set it into m_clen.
327 void
328 setclen(register struct message *mp)
330 long c;
331 FILE *ibuf;
332 char line[LINESIZE];
333 int fline, nread;
335 ibuf = setinput(mp);
336 c = mp->m_size;
337 fline = 1;
338 while (c > 0L) {
339 nread = getln(line, sizeof (line), ibuf);
340 c -= nread;
342 * First line is the From line, so no headers
343 * there to worry about.
345 if (fline) {
346 fline = 0;
347 continue;
350 * If line is blank, we've reached end of headers.
352 if (line[0] == '\n')
353 break;
355 * If this line is a continuation
356 * of a previous header field, keep going.
358 if (isspace(line[0]))
359 continue;
361 * If we are no longer looking at real
362 * header lines, we're done.
363 * This happens in uucp style mail where
364 * there are no headers at all.
366 if (!headerp(line)) {
367 c += nread;
368 break;
371 if (c == 0)
372 c = 1;
373 mp->m_clen = c;
376 static int
377 getln(char *line, int max, FILE *f)
379 register int c;
380 register char *cp, *ecp;
382 cp = line;
383 ecp = cp + max - 1;
384 while (cp < ecp && (c = getc(f)) != EOF)
385 if ((*cp++ = (char)c) == '\n')
386 break;
387 *cp = '\0';
388 return (cp - line);
392 * Read up a line from the specified input into the line
393 * buffer. Return the number of characters read. Do not
394 * include the newline at the end.
398 readline(FILE *ibuf, char *linebuf)
400 register char *cp;
401 register int c;
402 int seennulls = 0;
404 clearerr(ibuf);
405 c = getc(ibuf);
406 for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) {
407 if (c == 0) {
408 if (!seennulls) {
409 fprintf(stderr,
410 gettext("mailx: NUL changed to @\n"));
411 seennulls++;
413 c = '@';
415 if (cp - linebuf < LINESIZE-2)
416 *cp++ = (char)c;
418 *cp = 0;
419 if (c == EOF && cp == linebuf)
420 return (0);
421 return (cp - linebuf + 1);
425 * linecount - determine the number of lines in a printable file.
428 static int
429 linecount(char *lp, long size)
431 register char *cp, *ecp;
432 register int count;
434 count = 0;
435 cp = lp;
436 ecp = cp + size;
437 while (cp < ecp)
438 if (*cp++ == '\n')
439 count++;
440 return (count);
444 * Return a file buffer all ready to read up the
445 * passed message pointer.
448 FILE *
449 setinput(register struct message *mp)
451 fflush(otf);
452 if (fseek(itf, mp->m_offset, 0) < 0) {
453 perror("fseek");
454 panic("temporary file seek");
456 return (itf);
461 * Delete a file, but only if the file is a plain file.
465 removefile(char name[])
467 struct stat statb;
468 extern int errno;
470 if (stat(name, &statb) < 0)
471 if (errno == ENOENT)
472 return (0); /* it's already gone, no error */
473 else
474 return (-1);
475 if ((statb.st_mode & S_IFMT) != S_IFREG) {
476 errno = EISDIR;
477 return (-1);
479 return (unlink(name));
483 * Terminate an editing session by attempting to write out the user's
484 * file from the temporary. Save any new stuff appended to the file.
487 edstop(
488 int noremove /* don't allow the file to be removed, trunc instead */
491 register int gotcha, c;
492 register struct message *mp;
493 FILE *obuf, *ibuf, *tbuf = 0, *readstat;
494 struct stat statb;
495 char tempname[STSIZ], *id;
496 int tmpfd = -1;
498 if (readonly)
499 return (0);
500 holdsigs();
501 if (Tflag != NOSTR) {
502 if ((readstat = fopen(Tflag, "w")) == NULL)
503 Tflag = NOSTR;
505 for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) {
506 if (mp->m_flag & MNEW) {
507 mp->m_flag &= ~MNEW;
508 mp->m_flag |= MSTATUS;
510 if (mp->m_flag & (MODIFY|MDELETED|MSTATUS))
511 gotcha++;
512 if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) {
513 if ((id = hfield("article-id", mp, addone)) != NOSTR)
514 fprintf(readstat, "%s\n", id);
517 if (Tflag != NOSTR)
518 fclose(readstat);
519 if (!gotcha || Tflag != NOSTR)
520 goto done;
521 if ((ibuf = fopen(editfile, "r+")) == NULL) {
522 perror(editfile);
523 relsesigs();
524 longjmp(srbuf, 1);
526 lock(ibuf, "r+", 1);
527 if (fstat(fileno(ibuf), &statb) >= 0 && statb.st_size > mailsize) {
528 nstrcpy(tempname, STSIZ, "/tmp/mboxXXXXXX");
529 if ((tmpfd = mkstemp(tempname)) == -1) {
530 perror(tempname);
531 fclose(ibuf);
532 relsesigs();
533 longjmp(srbuf, 1);
535 if ((obuf = fdopen(tmpfd, "w")) == NULL) {
536 perror(tempname);
537 fclose(ibuf);
538 removefile(tempname);
539 relsesigs();
540 (void) close(tmpfd);
541 longjmp(srbuf, 1);
543 fseek(ibuf, mailsize, 0);
544 while ((c = getc(ibuf)) != EOF)
545 putc(c, obuf);
546 fclose(obuf);
547 if ((tbuf = fopen(tempname, "r")) == NULL) {
548 perror(tempname);
549 fclose(ibuf);
550 removefile(tempname);
551 relsesigs();
552 longjmp(srbuf, 1);
554 removefile(tempname);
556 if ((obuf = fopen(editfile, "r+")) == NULL) {
557 if ((obuf = fopen(editfile, "w")) == NULL) {
558 perror(editfile);
559 fclose(ibuf);
560 if (tbuf)
561 fclose(tbuf);
562 relsesigs();
563 longjmp(srbuf, 1);
566 printf("\"%s\" ", editfile);
567 flush();
568 c = 0;
569 for (mp = &message[0]; mp < &message[msgCount]; mp++) {
570 if ((mp->m_flag & MDELETED) != 0)
571 continue;
572 c++;
573 if (msend(mp, obuf, 0, fputs) < 0) {
574 perror(editfile);
575 fclose(ibuf);
576 fclose(obuf);
577 if (tbuf)
578 fclose(tbuf);
579 relsesigs();
580 longjmp(srbuf, 1);
583 gotcha = (c == 0 && tbuf == NULL);
584 if (tbuf != NULL) {
585 while ((c = getc(tbuf)) != EOF)
586 putc(c, obuf);
587 fclose(tbuf);
589 fflush(obuf);
590 if (fferror(obuf)) {
591 perror(editfile);
592 fclose(ibuf);
593 fclose(obuf);
594 relsesigs();
595 longjmp(srbuf, 1);
597 if (gotcha && !noremove && (value("keep") == NOSTR)) {
598 removefile(editfile);
599 printf(gettext("removed.\n"));
601 else
602 printf(gettext("updated.\n"));
603 fclose(ibuf);
604 trunc(obuf);
605 fclose(obuf);
606 flush();
608 done:
609 relsesigs();
610 return (1);
613 #ifndef OLD_BSD_SIGS
614 static int sigdepth = 0; /* depth of holdsigs() */
615 #ifdef VMUNIX
616 static int omask = 0;
617 #else
618 static sigset_t mask, omask;
619 #endif
620 #endif
622 * Hold signals SIGHUP - SIGQUIT.
624 void
625 holdsigs(void)
627 #ifndef OLD_BSD_SIGS
628 if (sigdepth++ == 0) {
629 #ifdef VMUNIX
630 omask = sigblock(sigmask(SIGHUP) |
631 sigmask(SIGINT)|sigmask(SIGQUIT));
632 #else
633 sigemptyset(&mask);
634 sigaddset(&mask, SIGHUP);
635 sigaddset(&mask, SIGINT);
636 sigaddset(&mask, SIGQUIT);
637 sigprocmask(SIG_BLOCK, &mask, &omask);
638 #endif
640 #else
641 sighold(SIGHUP);
642 sighold(SIGINT);
643 sighold(SIGQUIT);
644 #endif
648 * Release signals SIGHUP - SIGQUIT
650 void
651 relsesigs(void)
653 #ifndef OLD_BSD_SIGS
654 if (--sigdepth == 0)
655 #ifdef VMUNIX
656 sigsetmask(omask);
657 #else
658 sigprocmask(SIG_SETMASK, &omask, NULL);
659 #endif
660 #else
661 sigrelse(SIGHUP);
662 sigrelse(SIGINT);
663 sigrelse(SIGQUIT);
664 #endif
667 #if !defined(OLD_BSD_SIGS) && !defined(VMUNIX)
668 void
669 (*sigset(int sig, void (*act)(int)))(int)
671 struct sigaction sa, osa;
673 sa.sa_handler = (void (*)())act;
674 sigemptyset(&sa.sa_mask);
675 sa.sa_flags = SA_RESTART;
676 if (sigaction(sig, &sa, &osa) < 0)
677 return ((void (*)(int))-1);
678 return ((void (*)(int))osa.sa_handler);
680 #endif
683 * Flush the standard output.
686 void
687 flush(void)
689 fflush(stdout);
690 fflush(stderr);
694 * Determine the size of the file possessed by
695 * the passed buffer.
698 off_t
699 fsize(FILE *iob)
701 register int f;
702 struct stat sbuf;
704 f = fileno(iob);
705 if (fstat(f, &sbuf) < 0)
706 return (0);
707 return (sbuf.st_size);
711 * Check for either a stdio recognized error, or
712 * a possibly delayed write error (in case it's
713 * an NFS file, for instance).
717 fferror(FILE *iob)
719 return (ferror(iob) || fsync(fileno(iob)) < 0);
723 * Take a file name, possibly with shell meta characters
724 * in it and expand it by using wordexp().
725 * Return the file name as a dynamic string.
726 * If the name cannot be expanded (for whatever reason)
727 * return NULL.
730 char *
731 expand(char *name)
733 char xname[BUFSIZ];
734 char foldbuf[BUFSIZ];
735 register char *cp;
736 register int l;
737 wordexp_t wrdexp_buf;
739 if (debug) fprintf(stderr, "expand(%s)=", name);
740 cp = strchr(name, '\0') - 1; /* pointer to last char of name */
741 if (isspace(*cp)) {
742 /* strip off trailing blanks */
743 while (cp > name && isspace(*cp))
744 cp--;
745 l = *++cp; /* save char */
746 *cp = '\0';
747 name = savestr(name);
748 *cp = (char)l; /* restore char */
750 if (name[0] == '+' && getfold(foldbuf) >= 0) {
751 snprintf(xname, sizeof (xname), "%s/%s", foldbuf, name + 1);
752 cp = safeexpand(savestr(xname));
753 if (debug) fprintf(stderr, "%s\n", cp);
754 return (cp);
756 if (!anyof(name, "~{[*?$`'\"\\")) {
757 if (debug) fprintf(stderr, "%s\n", name);
758 return (name);
760 if (wordexp(name, &wrdexp_buf, WRDE_NOCMD) != 0) {
761 fprintf(stderr, gettext("Syntax error in \"%s\"\n"), name);
762 fflush(stderr);
763 return (NOSTR);
765 if (wrdexp_buf.we_wordc > 1) {
766 fprintf(stderr, gettext("\"%s\": Ambiguous\n"), name);
767 fflush(stderr);
768 return (NOSTR);
770 if (debug) fprintf(stderr, "%s\n", wrdexp_buf.we_wordv[0]);
771 return (savestr(wrdexp_buf.we_wordv[0]));
775 * Take a file name, possibly with shell meta characters
776 * in it and expand it by using "sh -c echo filename"
777 * Return the file name as a dynamic string.
778 * If the name cannot be expanded (for whatever reason)
779 * return the original file name.
782 char *
783 safeexpand(char name[])
785 char *t = expand(name);
786 return (t) ? t : savestr(name);
790 * Determine the current folder directory name.
793 getfold(char *name)
795 char *folder;
797 if ((folder = value("folder")) == NOSTR || *folder == '\0')
798 return (-1);
800 * If name looks like a folder name, don't try
801 * to expand it, to prevent infinite recursion.
803 if (*folder != '+' && (folder = expand(folder)) == NOSTR ||
804 *folder == '\0')
805 return (-1);
806 if (*folder == '/') {
807 nstrcpy(name, BUFSIZ, folder);
808 } else
809 snprintf(name, BUFSIZ, "%s/%s", homedir, folder);
810 return (0);
814 * A nicer version of Fdopen, which allows us to fclose
815 * without losing the open file.
818 FILE *
819 Fdopen(int fildes, char *mode)
821 register int f;
823 f = dup(fildes);
824 if (f < 0) {
825 perror("dup");
826 return (NULL);
828 return (fdopen(f, mode));
832 * return the filename associated with "s". This function always
833 * returns a non-null string (no error checking is done on the receiving end)
835 char *
836 Getf(register char *s)
838 register char *cp;
839 static char defbuf[PATHSIZE];
841 if (((cp = value(s)) != 0) && *cp) {
842 return (safeexpand(cp));
843 } else if (strcmp(s, "MBOX") == 0) {
844 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
845 "mbox");
846 return (defbuf);
847 } else if (strcmp(s, "DEAD") == 0) {
848 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
849 "dead.letter");
850 return (defbuf);
851 } else if (strcmp(s, "MAILRC") == 0) {
852 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
853 ".mailrc");
854 return (defbuf);
855 } else if (strcmp(s, "HOME") == 0) {
856 /* no recursion allowed! */
857 return (".");
859 return ("DEAD"); /* "cannot happen" */