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.
35 /* FIXME: ugly to the max */
38 static int compare_stat(struct stat *osb, struct stat *nsb)
40 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
41 || osb->st_rdev != nsb->st_rdev)
49 /****************************************************************************/
51 /****************************************************************************/
53 int safe_open(const char *path, int flags)
60 if ((fd = open(path, flags, 0666)) < 0)
63 /* make sure the file is not symlink */
64 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
65 || compare_stat(&osb, &nsb) == -1)
75 int safe_symlink(const char *oldpath, const char *newpath)
79 if (!oldpath || !newpath)
82 if (unlink(newpath) < 0 && errno != ENOENT)
85 if (oldpath[0] == '/') {
86 if (symlink(oldpath, newpath) < 0)
89 char abs_oldpath[_POSIX_PATH_MAX];
92 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
95 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
96 if (len >= ssizeof(abs_oldpath))
99 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
100 if (len >= ssizeof(abs_oldpath))
103 if (symlink (abs_oldpath, newpath) < 0)
107 if (stat(oldpath, &osb) < 0
108 || stat(newpath, &nsb) < 0
109 || compare_stat(&osb, &nsb) < 0)
119 * This function is supposed to do nfs-safe renaming of files.
121 * Warning: We don't check whether src and target are equal.
123 int safe_rename(const char *src, const char *target)
125 struct stat ssb, tsb;
130 if (link(src, target) != 0) {
132 * Coda does not allow cross-directory links, but tells
133 * us it's a cross-filesystem linking attempt.
135 * However, the Coda rename call is allegedly safe to use.
137 * With other file systems, rename should just fail when
138 * the files reside on different file systems, so it's safe
144 return rename(src, target);
149 /* Stat both links and check if they are equal. */
151 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
156 * pretend that the link failed because the target file
160 if (compare_stat(&ssb, &tsb) == -1) {
166 * Unlink the original link. Should we really ignore the return
174 void mutt_unlink(const char *s)
178 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
182 /* Defend against symlink attacks */
184 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
187 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
188 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
194 if ((f = fdopen(fd, "r+"))) {
198 p_clear(buf, countof(buf));
199 while (sb.st_size > 0) {
200 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
201 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
209 /****************************************************************************/
211 /****************************************************************************/
213 /* when opening files for writing, make sure the file doesn't already exist
214 to avoid race conditions. */
215 FILE *safe_fopen(const char *path, const char *mode)
217 /* first set the current umask */
220 if (mode[0] == 'w') {
222 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
224 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
226 if ((fd = safe_open(path, flags)) < 0)
229 return fdopen (fd, mode);
232 return fopen(path, mode);
235 int safe_fclose(FILE **f)
245 /* If rfc1524_expand_command() is used on a recv'd message, then
246 * the filename doesn't exist yet, but if its used while sending a message,
247 * then we need to rename the existing file.
249 * This function returns 0 on successful move, 1 on old file doesn't exist,
250 * 2 on new file already exists, and 3 on other failure.
253 /* note on access(2) use: No dangling symlink problems here due to
256 int mutt_rename_file(char *oldfile, char *newfile)
260 if (access(oldfile, F_OK) != 0)
262 if (access(newfile, F_OK) == 0)
265 ofp = fopen(oldfile, "r");
269 nfp = safe_fopen(newfile, "w");
275 mutt_copy_stream(ofp, nfp);
278 mutt_unlink(oldfile);
282 /* Read a line from ``fp'' into the dynamically allocated ``s'',
283 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
284 * If a line ends with "\", this char and the linefeed is removed,
285 * and the next line is read too.
287 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
293 s = p_new(char, STRING);
298 if (fgets(s + offset, *size - offset, fp) == NULL) {
302 if ((ch = strchr(s + offset, '\n')) != NULL) {
305 if (ch > s && *(ch - 1) == '\r')
307 if (ch == s || *(ch - 1) != '\\')
313 c = getc (fp); /* This is kind of a hack. We want to know if the
314 char at the current point in the input stream is EOF.
315 feof() will only tell us if we've already hit EOF, not
316 if the next character is EOF. So, we need to read in
317 the next character and manually check if it is EOF. */
319 /* The last line of fp isn't \n terminated */
323 ungetc (c, fp); /* undo our dammage */
324 /* There wasn't room for the line -- increase ``s'' */
325 offset = *size - 1; /* overwrite the terminating 0 */
327 p_realloc(&s, *size);
333 int mutt_copy_stream(FILE *fin, FILE *fout)
338 while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
339 if (fwrite(buf, 1, l, fout) != l)
346 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
351 size_t chunk = MIN(size, ssizeof(buf));
353 if ((chunk = fread(buf, 1, chunk, in)) < 1)
355 if (fwrite(buf, 1, chunk, out) != chunk) {
365 /****************************************************************************/
366 /* ligben-like funcs */
367 /****************************************************************************/
369 const char *mutt_basename(const char *f)
371 const char *p = strrchr(f, '/');
372 return p ? p + 1 : f;
377 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
381 pos = m_strcpy(d, n, dir);
385 if (pos && d[pos - 1] != '/')
388 m_strcpy(d + pos, n - pos, fname);
393 void mutt_sanitize_filename(char *s, short slash)
395 static char safe_chars[] =
396 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
402 if ((slash && *s == '/') || !strchr (safe_chars, *s))
407 /* prepare a file name to survive the shell's quoting rules.
408 * From the Unix programming FAQ by way of Liviu.
411 /* FIXME: API is very wrong */
412 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
421 /* leave some space for the trailing characters. */
426 for (i = 0; j < l && s[i]; i++) {
427 if (s[i] == '\'' || s[i] == '`') {