2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * Copyright © 2006 Pierre Habouzit
20 * Copyright notice from original mutt:
21 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
22 * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
24 * This file is part of mutt-ng, see http://www.muttng.org/.
25 * It's licensed under the GNU General Public License,
26 * please see the file GPL in the top level source directory.
29 #include <sys/types.h>
45 /* FIXME: ugly to the max */
48 static int compare_stat(struct stat *osb, struct stat *nsb)
50 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
51 || osb->st_rdev != nsb->st_rdev)
59 /****************************************************************************/
61 /****************************************************************************/
63 int safe_open(const char *path, int flags)
70 if ((fd = open(path, flags, 0666)) < 0)
73 /* make sure the file is not symlink */
74 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
75 || compare_stat(&osb, &nsb) == -1)
85 int safe_symlink(const char *oldpath, const char *newpath)
89 if (!oldpath || !newpath)
92 if (unlink(newpath) < 0 && errno != ENOENT)
95 if (oldpath[0] == '/') {
96 if (symlink(oldpath, newpath) < 0)
99 char abs_oldpath[_POSIX_PATH_MAX];
102 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
105 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
106 if (len >= ssizeof(abs_oldpath))
109 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
110 if (len >= ssizeof(abs_oldpath))
113 if (symlink (abs_oldpath, newpath) < 0)
117 if (stat(oldpath, &osb) < 0
118 || stat(newpath, &nsb) < 0
119 || compare_stat(&osb, &nsb) < 0)
129 * This function is supposed to do nfs-safe renaming of files.
131 * Warning: We don't check whether src and target are equal.
133 int safe_rename(const char *src, const char *target)
135 struct stat ssb, tsb;
140 if (link(src, target) != 0) {
142 * Coda does not allow cross-directory links, but tells
143 * us it's a cross-filesystem linking attempt.
145 * However, the Coda rename call is allegedly safe to use.
147 * With other file systems, rename should just fail when
148 * the files reside on different file systems, so it's safe
154 return rename(src, target);
159 /* Stat both links and check if they are equal. */
161 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
166 * pretend that the link failed because the target file
170 if (compare_stat(&ssb, &tsb) == -1) {
176 * Unlink the original link. Should we really ignore the return
184 void mutt_unlink(const char *s)
188 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
192 /* Defend against symlink attacks */
194 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
197 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
198 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
204 if ((f = fdopen(fd, "r+"))) {
208 p_clear(buf, countof(buf));
209 while (sb.st_size > 0) {
210 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
211 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
219 /****************************************************************************/
221 /****************************************************************************/
223 /* when opening files for writing, make sure the file doesn't already exist
224 to avoid race conditions. */
225 FILE *safe_fopen(const char *path, const char *mode)
227 /* first set the current umask */
230 if (mode[0] == 'w') {
232 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
234 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
236 if ((fd = safe_open(path, flags)) < 0)
239 return fdopen (fd, mode);
242 return fopen(path, mode);
245 int safe_fclose(FILE **f)
255 /* If rfc1524_expand_command() is used on a recv'd message, then
256 * the filename doesn't exist yet, but if its used while sending a message,
257 * then we need to rename the existing file.
259 * This function returns 0 on successful move, 1 on old file doesn't exist,
260 * 2 on new file already exists, and 3 on other failure.
263 /* note on access(2) use: No dangling symlink problems here due to
266 int mutt_rename_file(char *oldfile, char *newfile)
270 if (access(oldfile, F_OK) != 0)
272 if (access(newfile, F_OK) == 0)
275 ofp = fopen(oldfile, "r");
279 nfp = safe_fopen(newfile, "w");
285 mutt_copy_stream(ofp, nfp);
288 mutt_unlink(oldfile);
292 /* Read a line from ``fp'' into the dynamically allocated ``s'',
293 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
294 * If a line ends with "\", this char and the linefeed is removed,
295 * and the next line is read too.
297 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
303 s = p_new(char, STRING);
308 if (fgets(s + offset, *size - offset, fp) == NULL) {
312 if ((ch = strchr(s + offset, '\n')) != NULL) {
315 if (ch > s && *(ch - 1) == '\r')
317 if (ch == s || *(ch - 1) != '\\')
323 c = getc (fp); /* This is kind of a hack. We want to know if the
324 char at the current point in the input stream is EOF.
325 feof() will only tell us if we've already hit EOF, not
326 if the next character is EOF. So, we need to read in
327 the next character and manually check if it is EOF. */
329 /* The last line of fp isn't \n terminated */
333 ungetc (c, fp); /* undo our dammage */
334 /* There wasn't room for the line -- increase ``s'' */
335 offset = *size - 1; /* overwrite the terminating 0 */
337 p_realloc(&s, *size);
343 int mutt_copy_stream(FILE *fin, FILE *fout)
348 while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
349 if (fwrite(buf, 1, l, fout) != l)
356 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
361 size_t chunk = MIN(size, ssizeof(buf));
363 if ((chunk = fread(buf, 1, chunk, in)) < 1)
365 if (fwrite(buf, 1, chunk, out) != chunk) {
375 /****************************************************************************/
376 /* ligben-like funcs */
377 /****************************************************************************/
379 const char *mutt_basename(const char *f)
381 const char *p = strrchr(f, '/');
382 return p ? p + 1 : f;
387 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
391 pos = m_strcpy(d, n, dir);
395 if (pos && d[pos - 1] != '/')
398 m_strcpy(d + pos, n - pos, fname);
403 void mutt_sanitize_filename(char *s, short slash)
405 static char safe_chars[] =
406 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
412 if ((slash && *s == '/') || !strchr (safe_chars, *s))
417 /* prepare a file name to survive the shell's quoting rules.
418 * From the Unix programming FAQ by way of Liviu.
421 /* FIXME: API is very wrong */
422 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
431 /* leave some space for the trailing characters. */
436 for (i = 0; j < l && s[i]; i++) {
437 if (s[i] == '\'' || s[i] == '`') {