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 /* If rfc1524_expand_command() is used on a recv'd message, then
236 * the filename doesn't exist yet, but if its used while sending a message,
237 * then we need to rename the existing file.
239 * This function returns 0 on successful move, 1 on old file doesn't exist,
240 * 2 on new file already exists, and 3 on other failure.
243 /* note on access(2) use: No dangling symlink problems here due to
246 int mutt_rename_file(char *oldfile, char *newfile)
250 if (access(oldfile, F_OK) != 0)
252 if (access(newfile, F_OK) == 0)
255 ofp = fopen(oldfile, "r");
259 nfp = safe_fopen(newfile, "w");
265 mutt_copy_stream(ofp, nfp);
268 mutt_unlink(oldfile);
272 /* Read a line from ``fp'' into the dynamically allocated ``s'',
273 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
274 * If a line ends with "\", this char and the linefeed is removed,
275 * and the next line is read too.
277 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
283 s = p_new(char, STRING);
288 if (fgets(s + offset, *size - offset, fp) == NULL) {
292 if ((ch = strchr(s + offset, '\n')) != NULL) {
295 if (ch > s && *(ch - 1) == '\r')
297 if (ch == s || *(ch - 1) != '\\')
303 c = getc (fp); /* This is kind of a hack. We want to know if the
304 char at the current point in the input stream is EOF.
305 feof() will only tell us if we've already hit EOF, not
306 if the next character is EOF. So, we need to read in
307 the next character and manually check if it is EOF. */
309 /* The last line of fp isn't \n terminated */
313 ungetc (c, fp); /* undo our dammage */
314 /* There wasn't room for the line -- increase ``s'' */
315 offset = *size - 1; /* overwrite the terminating 0 */
317 p_realloc(&s, *size);
323 int mutt_copy_stream(FILE *fin, FILE *fout)
328 while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
329 if (fwrite(buf, 1, l, fout) != l)
336 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
341 size_t chunk = MIN(size, ssizeof(buf));
343 if ((chunk = fread(buf, 1, chunk, in)) < 1)
345 if (fwrite(buf, 1, chunk, out) != chunk) {
355 /****************************************************************************/
356 /* ligben-like funcs */
357 /****************************************************************************/
359 const char *mutt_basename(const char *f)
361 const char *p = strrchr(f, '/');
362 return p ? p + 1 : f;
367 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
371 pos = m_strcpy(d, n, dir);
375 if (pos && d[pos - 1] != '/')
378 m_strcpy(d + pos, n - pos, fname);
383 void mutt_sanitize_filename(char *s, short slash)
385 static char safe_chars[] =
386 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
392 if ((slash && *s == '/') || !strchr (safe_chars, *s))
397 /* prepare a file name to survive the shell's quoting rules.
398 * From the Unix programming FAQ by way of Liviu.
401 /* FIXME: API is very wrong */
402 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
411 /* leave some space for the trailing characters. */
416 for (i = 0; j < l && s[i]; i++) {
417 if (s[i] == '\'' || s[i] == '`') {
433 ssize_t m_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
439 for (p = strchr(fmt, '%'); p; p = strchr(fmt, '%')) {
441 pos += m_strncpy(dst + pos, n - pos, fmt, p - fmt);
442 pos += m_strcpy(dst + pos, n - pos, src);
446 pos += m_strncpy(dst + pos, n - pos, fmt, p + 1 - fmt);
447 fmt = p + 1 + (p[1] == '%');
450 pos += m_strcpy(dst + pos, n - pos, fmt);
453 pos += snprintf(dst + pos, n - pos, " %s", src);
459 m_tempftplize(char *dst, ssize_t dlen, const char *fmt, const char *s)
463 while ((p = strchr(fmt, '/'))) {
468 return m_strcpy(dst, dlen, s);
470 for (p = strchr(fmt, '%'); p; p = strchr(p, '%')) {
472 return m_file_fmt(dst, dlen, fmt, s);
474 p += 1 + (p[1] == '%');
477 p = strrchr(fmt, '.');
479 return snprintf(dst, dlen, "%s%s", s, p);
481 return snprintf(dst, dlen, "%s.%s", s, fmt);
485 int m_tempfd(char *dst, ssize_t n, const char *dir, const char *fmt)
487 char raw[_POSIX_PATH_MAX], tpl[_POSIX_PATH_MAX];
488 const char *path = fmt ? tpl : raw;
492 snprintf(raw, sizeof(raw), "%s/madmutt-%04x-%04x-%08x",
493 dir, (int)getuid(), (int)getpid(), (int)rand());
496 m_tempftplize(tpl, sizeof(tpl), fmt, raw);
499 fd = open(path, O_CREAT | O_EXCL | O_RDWR | O_NOFOLLOW, 0600);
507 m_strcpy(dst, n, path);
511 FILE *m_tempfile(char *dst, ssize_t n, const char *dir, const char *fmt)
513 int fd = m_tempfd(dst, n, dir, fmt);
514 return fd < 0 ? NULL : fdopen(fd, "w+");