From 78538c614f97139fb23c7e7b4db9d8319b0e26e1 Mon Sep 17 00:00:00 2001 From: Richard Russon Date: Wed, 2 Aug 2017 15:49:32 +0100 Subject: [PATCH] move the file functions to the library --- lib.c | 698 -------------------------------------- lib.h | 34 -- lib/Makefile.am | 4 +- lib/lib.h | 2 + lib/lib_file.c | 872 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/lib_file.h | 57 ++++ po/POTFILES.in | 1 + 7 files changed, 934 insertions(+), 734 deletions(-) create mode 100644 lib/lib_file.c create mode 100644 lib/lib_file.h diff --git a/lib.c b/lib.c index e1df5d57a..ed7882cc9 100644 --- a/lib.c +++ b/lib.c @@ -117,646 +117,6 @@ void mutt_nocurses_error(const char *fmt, ...) fputc('\n', stderr); } -int safe_fclose(FILE **f) -{ - int r = 0; - - if (*f) - r = fclose(*f); - - *f = NULL; - return r; -} - -int safe_fsync_close(FILE **f) -{ - int r = 0; - - if (*f) - { - if (fflush(*f) || fsync(fileno(*f))) - { - int save_errno = errno; - r = -1; - safe_fclose(f); - errno = save_errno; - } - else - r = safe_fclose(f); - } - - return r; -} - -void mutt_unlink(const char *s) -{ - int fd; - int flags; - FILE *f = NULL; - struct stat sb, sb2; - char buf[2048]; - -/* Defend against symlink attacks */ - -#ifdef O_NOFOLLOW - flags = O_RDWR | O_NOFOLLOW; -#else - flags = O_RDWR; -#endif - - if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) - { - if ((fd = open(s, flags)) < 0) - return; - - if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode) || - (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino)) - { - close(fd); - return; - } - - if ((f = fdopen(fd, "r+"))) - { - unlink(s); - memset(buf, 0, sizeof(buf)); - while (sb.st_size > 0) - { - fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f); - sb.st_size -= MIN(sizeof(buf), sb.st_size); - } - safe_fclose(&f); - } - } -} - -int mutt_copy_bytes(FILE *in, FILE *out, size_t size) -{ - char buf[2048]; - size_t chunk; - - while (size > 0) - { - chunk = (size > sizeof(buf)) ? sizeof(buf) : size; - if ((chunk = fread(buf, 1, chunk, in)) < 1) - break; - if (fwrite(buf, 1, chunk, out) != chunk) - { - return -1; - } - size -= chunk; - } - - if (fflush(out) != 0) - return -1; - return 0; -} - -int mutt_copy_stream(FILE *fin, FILE *fout) -{ - size_t l; - char buf[LONG_STRING]; - - while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) - { - if (fwrite(buf, 1, l, fout) != l) - return -1; - } - - if (fflush(fout) != 0) - return -1; - return 0; -} - -static bool compare_stat(struct stat *osb, struct stat *nsb) -{ - if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino || osb->st_rdev != nsb->st_rdev) - { - return false; - } - - return true; -} - -int safe_symlink(const char *oldpath, const char *newpath) -{ - struct stat osb, nsb; - - if (!oldpath || !newpath) - return -1; - - if (unlink(newpath) == -1 && errno != ENOENT) - return -1; - - if (oldpath[0] == '/') - { - if (symlink(oldpath, newpath) == -1) - return -1; - } - else - { - char abs_oldpath[_POSIX_PATH_MAX]; - - if ((getcwd(abs_oldpath, sizeof(abs_oldpath)) == NULL) || - (strlen(abs_oldpath) + 1 + strlen(oldpath) + 1 > sizeof(abs_oldpath))) - return -1; - - strcat(abs_oldpath, "/"); - strcat(abs_oldpath, oldpath); - if (symlink(abs_oldpath, newpath) == -1) - return -1; - } - - if (stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1 || !compare_stat(&osb, &nsb)) - { - unlink(newpath); - return -1; - } - - return 0; -} - - -/** - * safe_rename - NFS-safe renaming of files - * - * Warning: We don't check whether src and target are equal. - */ -int safe_rename(const char *src, const char *target) -{ - struct stat ssb, tsb; - - if (!src || !target) - return -1; - - if (link(src, target) != 0) - { - /* - * Coda does not allow cross-directory links, but tells - * us it's a cross-filesystem linking attempt. - * - * However, the Coda rename call is allegedly safe to use. - * - * With other file systems, rename should just fail when - * the files reside on different file systems, so it's safe - * to try it here. - * - */ - - mutt_debug(1, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target, - strerror(errno), errno); - - /* - * FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's - * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. - */ - if (errno == EXDEV || errno == ENOSYS || errno == EPERM -#ifdef ENOTSUP - || errno == ENOTSUP -#endif -#ifdef EOPNOTSUPP - || errno == EOPNOTSUPP -#endif - ) - { - mutt_debug(1, "safe_rename: trying rename...\n"); - if (rename(src, target) == -1) - { - mutt_debug(1, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src, - target, strerror(errno), errno); - return -1; - } - mutt_debug(1, "safe_rename: rename succeeded.\n"); - - return 0; - } - - return -1; - } - - /* - * Stat both links and check if they are equal. - */ - - if (lstat(src, &ssb) == -1) - { - mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno); - return -1; - } - - if (lstat(target, &tsb) == -1) - { - mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno); - return -1; - } - - /* - * pretend that the link failed because the target file - * did already exist. - */ - - if (!compare_stat(&ssb, &tsb)) - { - mutt_debug(1, "safe_rename: stat blocks for %s and %s diverge; " - "pretending EEXIST.\n", - src, target); - errno = EEXIST; - return -1; - } - - /* - * Unlink the original link. Should we really ignore the return - * value here? XXX - */ - - if (unlink(src) == -1) - { - mutt_debug(1, "safe_rename: unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno); - } - - - return 0; -} - - -/** - * mkwrapdir - Create a temporary directory next to a file name - */ -static int mkwrapdir(const char *path, char *newfile, size_t nflen, char *newdir, size_t ndlen) -{ - const char *basename = NULL; - char parent[_POSIX_PATH_MAX]; - char *p = NULL; - - strfcpy(parent, NONULL(path), sizeof(parent)); - - if ((p = strrchr(parent, '/'))) - { - *p = '\0'; - basename = p + 1; - } - else - { - strfcpy(parent, ".", sizeof(parent)); - basename = path; - } - - snprintf(newdir, ndlen, "%s/%s", parent, ".muttXXXXXX"); - if (mkdtemp(newdir) == NULL) - { - mutt_debug(1, "mkwrapdir: mkdtemp() failed\n"); - return -1; - } - - if (snprintf(newfile, nflen, "%s/%s", newdir, NONULL(basename)) >= nflen) - { - rmdir(newdir); - mutt_debug(1, "mkwrapdir: string was truncated\n"); - return -1; - } - return 0; -} - -/** - * mutt_rmtree - remove a directory and everything under it - */ -int mutt_rmtree(const char *path) -{ - DIR *dirp = NULL; - struct dirent *de = NULL; - char cur[_POSIX_PATH_MAX]; - struct stat statbuf; - int rc = 0; - - if (!(dirp = opendir(path))) - { - mutt_debug(1, "mutt_rmtree: error opening directory %s\n", path); - return -1; - } - while ((de = readdir(dirp))) - { - if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0)) - continue; - - snprintf(cur, sizeof(cur), "%s/%s", path, de->d_name); - /* XXX make nonrecursive version */ - - if (stat(cur, &statbuf) == -1) - { - rc = 1; - continue; - } - - if (S_ISDIR(statbuf.st_mode)) - rc |= mutt_rmtree(cur); - else - rc |= unlink(cur); - } - closedir(dirp); - - rc |= rmdir(path); - - return rc; -} - -static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir) -{ - int rv; - - rv = safe_rename(safe_file, path); - unlink(safe_file); - rmdir(safe_dir); - return rv; -} - -int safe_open(const char *path, int flags) -{ - struct stat osb, nsb; - int fd; - - if (flags & O_EXCL) - { - char safe_file[_POSIX_PATH_MAX]; - char safe_dir[_POSIX_PATH_MAX]; - - if (mkwrapdir(path, safe_file, sizeof(safe_file), safe_dir, sizeof(safe_dir)) == -1) - return -1; - - if ((fd = open(safe_file, flags, 0600)) < 0) - { - rmdir(safe_dir); - return fd; - } - - /* NFS and I believe cygwin do not handle movement of open files well */ - close(fd); - if (put_file_in_place(path, safe_file, safe_dir) == -1) - return -1; - } - - if ((fd = open(path, flags & ~O_EXCL, 0600)) < 0) - return fd; - - /* make sure the file is not symlink */ - if (lstat(path, &osb) < 0 || fstat(fd, &nsb) < 0 || !compare_stat(&osb, &nsb)) - { - close(fd); - return -1; - } - - return fd; -} - -/** - * safe_fopen - Call fopen() safely - * - * when opening files for writing, make sure the file doesn't already exist to - * avoid race conditions. - */ -FILE *safe_fopen(const char *path, const char *mode) -{ - if (mode[0] == 'w') - { - int fd; - int flags = O_CREAT | O_EXCL; - -#ifdef O_NOFOLLOW - flags |= O_NOFOLLOW; -#endif - - if (mode[1] == '+') - flags |= O_RDWR; - else - flags |= O_WRONLY; - - if ((fd = safe_open(path, flags)) < 0) - return NULL; - - return (fdopen(fd, mode)); - } - else - return (fopen(path, mode)); -} - -static const char safe_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/"; - -void mutt_sanitize_filename(char *f, short slash) -{ - if (!f) - return; - - for (; *f; f++) - { - if ((slash && *f == '/') || !strchr(safe_chars, *f)) - *f = '_'; - } -} - -/* these characters must be escaped in regular expressions */ - -static const char rx_special_chars[] = "^.[$()|*+?{\\"; - -int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src) -{ - while (*src && --destlen > 2) - { - if (strchr(rx_special_chars, *src)) - { - *dest++ = '\\'; - destlen--; - } - *dest++ = *src++; - } - - *dest = '\0'; - - if (*src) - return -1; - else - return 0; -} - -/** - * mutt_read_line - Read a line from a file - * - * Read a line from ``fp'' into the dynamically allocated ``s'', increasing - * ``s'' if necessary. The ending "\n" or "\r\n" is removed. If a line ends - * with "\", this char and the linefeed is removed, and the next line is read - * too. - */ -char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags) -{ - size_t offset = 0; - char *ch = NULL; - - if (!s) - { - s = safe_malloc(STRING); - *size = STRING; - } - - while (true) - { - if (fgets(s + offset, *size - offset, fp) == NULL) - { - FREE(&s); - return NULL; - } - if ((ch = strchr(s + offset, '\n')) != NULL) - { - if (line) - (*line)++; - if (flags & MUTT_EOL) - return s; - *ch = 0; - if (ch > s && *(ch - 1) == '\r') - *--ch = 0; - if (!(flags & MUTT_CONT) || ch == s || *(ch - 1) != '\\') - return s; - offset = ch - s - 1; - } - else - { - int c; - c = getc(fp); /* This is kind of a hack. We want to know if the - char at the current point in the input stream is EOF. - feof() will only tell us if we've already hit EOF, not - if the next character is EOF. So, we need to read in - the next character and manually check if it is EOF. */ - if (c == EOF) - { - /* The last line of fp isn't \n terminated */ - if (line) - (*line)++; - return s; - } - else - { - ungetc(c, fp); /* undo our damage */ - /* There wasn't room for the line -- increase ``s'' */ - offset = *size - 1; /* overwrite the terminating 0 */ - *size += STRING; - safe_realloc(&s, *size); - } - } - } -} - -/** - * mutt_quote_filename - Quote a filename to survive the shell's quoting rules - * - * From the Unix programming FAQ by way of Liviu. - */ -size_t mutt_quote_filename(char *d, size_t l, const char *f) -{ - size_t j = 0; - - if (!f) - { - *d = '\0'; - return 0; - } - - /* leave some space for the trailing characters. */ - l -= 6; - - d[j++] = '\''; - - for (size_t i = 0; j < l && f[i]; i++) - { - if (f[i] == '\'' || f[i] == '`') - { - d[j++] = '\''; - d[j++] = '\\'; - d[j++] = f[i]; - d[j++] = '\''; - } - else - d[j++] = f[i]; - } - - d[j++] = '\''; - d[j] = '\0'; - - return j; -} - -/** - * mutt_concatn_path - Concatenate directory and filename - * @param dst Buffer for result - * @param dstlen Buffer length - * @param dir Directory - * @param dirlen Directory length - * @param fname Filename - * @param fnamelen Filename length - * @retval NULL Error - * @retval ptr Pointer to \a dst on success - * - * Write the concatenated pathname (dir + "/" + fname) into dst. - * The slash is omitted when dir or fname is of 0 length. - */ -char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir, - size_t dirlen, const char *fname, size_t fnamelen) -{ - size_t req; - size_t offset = 0; - - if (dstlen == 0) - return NULL; /* probably should not mask errors like this */ - - /* size check */ - req = dirlen + fnamelen + 1; /* +1 for the trailing nul */ - if (dirlen && fnamelen) - req++; /* when both components are non-nul, we add a "/" in between */ - if (req > dstlen) - { /* check for condition where the dst length is too short */ - /* Two options here: - * 1) assert(0) or return NULL to signal error - * 2) copy as much of the path as will fit - * It doesn't appear that the return value is actually checked anywhere mutt_concat_path() - * is called, so we should just copy set dst to nul and let the calling function fail later. - */ - dst[0] = 0; /* safe since we bail out early if dstlen == 0 */ - return NULL; - } - - if (dirlen) - { /* when dir is not empty */ - memcpy(dst, dir, dirlen); - offset = dirlen; - if (fnamelen) - dst[offset++] = '/'; - } - if (fnamelen) - { /* when fname is not empty */ - memcpy(dst + offset, fname, fnamelen); - offset += fnamelen; - } - dst[offset] = 0; - return dst; -} - -char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l) -{ - const char *fmt = "%s/%s"; - - if (!*fname || (*dir && dir[strlen(dir) - 1] == '/')) - fmt = "%s%s"; - - snprintf(d, l, fmt, dir, fname); - return d; -} - -const char *mutt_basename(const char *f) -{ - const char *p = strrchr(f, '/'); - if (p) - return p + 1; - else - return f; -} - const char *mutt_strsysexit(int e) { int i; @@ -859,61 +219,3 @@ int mutt_inbox_cmp(const char *a, const char *b) return 0; } -/** - * mutt_mkdir - Recursively create directories - * @param path Directories to create - * @param mode Permissions for final directory - * @retval 0 Success - * @retval -1 Error (errno set) - * - * Create a directory, creating the parents if necessary. (like mkdir -p) - * - * @note The permissions are only set on the final directory. - * The permissions of any parent directories are determined by the umask. - * (This is how "mkdir -p" behaves) - */ -int mutt_mkdir(const char *path, mode_t mode) -{ - if (!path || !*path) - { - errno = EINVAL; - return -1; - } - - errno = 0; - char *p = NULL; - char _path[PATH_MAX]; - const size_t len = strlen(path); - - if (len >= sizeof(_path)) - { - errno = ENAMETOOLONG; - return -1; - } - - struct stat sb; - if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode)) - return 0; - - /* Create a mutable copy */ - strfcpy(_path, path, sizeof(_path)); - - for (p = _path + 1; *p; p++) - { - if (*p != '/') - continue; - - /* Temporarily truncate the path */ - *p = '\0'; - - if ((mkdir(_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST)) - return -1; - - *p = '/'; - } - - if ((mkdir(_path, mode) != 0) && (errno != EEXIST)) - return -1; - - return 0; -} diff --git a/lib.h b/lib.h index dac61a684..0935b84f3 100644 --- a/lib.h +++ b/lib.h @@ -32,11 +32,6 @@ #include #include -#undef MAX -#undef MIN -#define MAX(a, b) ((a) < (b) ? (b) : (a)) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - /* Use this with care. If the compiler can't see the array * definition, it obviously won't produce a correct result. */ #define mutt_array_size(x) (sizeof(x) / sizeof((x)[0])) @@ -61,38 +56,9 @@ extern int debuglevel_cmdline; #define S_ERR 127 #define S_BKG 126 -/* Flags for mutt_read_line() */ -#define MUTT_CONT (1 << 0) /**< \-continuation */ -#define MUTT_EOL (1 << 1) /**< don't strip `\n` / `\r\n` */ - -/* The actual library functions. */ - -FILE *safe_fopen(const char *path, const char *mode); - -char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir, - size_t dirlen, const char *fname, size_t fnamelen); -char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l); -char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags); - -const char *mutt_basename(const char *f); - -int mutt_copy_stream(FILE *fin, FILE *fout); -int mutt_copy_bytes(FILE *in, FILE *out, size_t size); -int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src); int safe_asprintf(char **, const char *, ...); -int safe_open(const char *path, int flags); -int safe_rename(const char *src, const char *target); -int safe_symlink(const char *oldpath, const char *newpath); -int safe_fclose(FILE **f); -int safe_fsync_close(FILE **f); -int mutt_rmtree(const char *path); -int mutt_mkdir(const char *path, mode_t mode); - -size_t mutt_quote_filename(char *d, size_t l, const char *f); void mutt_nocurses_error(const char *, ...); -void mutt_sanitize_filename(char *f, short slash); -void mutt_unlink(const char *s); int mutt_inbox_cmp(const char *a, const char *b); const char *mutt_strsysexit(int e); diff --git a/lib/Makefile.am b/lib/Makefile.am index 81728a281..83526af1c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -3,12 +3,12 @@ include $(top_srcdir)/flymake.am AUTOMAKE_OPTIONS = 1.6 foreign -EXTRA_DIST = lib.h lib_ascii.h lib_base64.h lib_date.h lib_debug.h lib_exit.h lib_md5.h lib_memory.h lib_message.h lib_sha1.h lib_string.h +EXTRA_DIST = lib.h lib_ascii.h lib_base64.h lib_date.h lib_debug.h lib_exit.h lib_file.h lib_md5.h lib_memory.h lib_message.h lib_sha1.h lib_string.h AM_CPPFLAGS = -I$(top_srcdir) noinst_LIBRARIES = liblib.a noinst_HEADERS = -liblib_a_SOURCES = lib_ascii.c lib_base64.c lib_date.c lib_debug.c lib_exit.c lib_md5.c lib_memory.c lib_message.c lib_sha1.c lib_string.c +liblib_a_SOURCES = lib_ascii.c lib_base64.c lib_date.c lib_debug.c lib_exit.c lib_file.c lib_md5.c lib_memory.c lib_message.c lib_sha1.c lib_string.c diff --git a/lib/lib.h b/lib/lib.h index ba9da80bb..4986a69e4 100644 --- a/lib/lib.h +++ b/lib/lib.h @@ -33,6 +33,7 @@ * -# @subpage date * -# @subpage debug * -# @subpage exit + * -# @subpage file * -# @subpage md5 * -# @subpage memory * -# @subpage message @@ -48,6 +49,7 @@ #include "lib_date.h" #include "lib_debug.h" #include "lib_exit.h" +#include "lib_file.h" #include "lib_md5.h" #include "lib_memory.h" #include "lib_message.h" diff --git a/lib/lib_file.c b/lib/lib_file.c new file mode 100644 index 000000000..e33592872 --- /dev/null +++ b/lib/lib_file.c @@ -0,0 +1,872 @@ +/** + * @file + * File management functions + * + * @authors + * Copyright (C) 2017 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page file File management functions + * + * Commonly used file/dir management routines. + * + * | Function | Description + * | :------------------------ | :----------------------------------------------------------- + * | mutt_basename() | Find the last component for a pathname + * | mutt_concatn_path() | Concatenate directory and filename + * | mutt_concat_path() | Join a directory name and a filename + * | mutt_copy_bytes() | Copy some content from one file to another + * | mutt_copy_stream() | Copy the contents of one file into another + * | mutt_mkdir() | Recursively create directories + * | mutt_quote_filename() | Quote a filename to survive the shell's quoting rules + * | mutt_read_line() | Read a line from a file + * | mutt_rmtree() | Recursively remove a directory + * | mutt_rx_sanitize_string() | Escape any regex-magic characters in a string + * | mutt_sanitize_filename() | Replace unsafe characters in a filename + * | mutt_unlink() | Delete a file, carefully + * | safe_fclose() | Close a FILE handle (and NULL the pointer) + * | safe_fopen() | Call fopen() safely + * | safe_fsync_close() | Flush the data, before closing a file (and NULL the pointer) + * | safe_open() | Open a file + * | safe_rename() | NFS-safe renaming of files + * | safe_symlink() | Create a symlink + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib_file.h" +#include "lib_debug.h" +#include "lib_memory.h" +#include "lib_string.h" + +/* these characters must be escaped in regular expressions */ +static const char rx_special_chars[] = "^.[$()|*+?{\\"; + +static const char safe_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/"; + +/** + * compare_stat - Compare the struct stat's of two files/dirs + * @param osb struct stat of the first file/dir + * @param nsb struct stat of the second file/dir + * @retval boolean + * + * This compares the device id (st_dev), inode number (st_ino) and special id + * (st_rdev) of the files/dirs. + */ +static bool compare_stat(struct stat *osb, struct stat *nsb) +{ + return ((osb->st_dev == nsb->st_dev) && (osb->st_ino == nsb->st_ino) && + (osb->st_rdev == nsb->st_rdev)); +} + +/** + * mkwrapdir - Create a temporary directory next to a file name + * @param path Existing filename + * @param newfile New filename + * @param nflen Length of new filename + * @param newdir New directory name + * @param ndlen Length of new directory name + * @retval 0 Succes 0 Success + * @retval -1 Error + */ +static int mkwrapdir(const char *path, char *newfile, size_t nflen, char *newdir, size_t ndlen) +{ + const char *basename = NULL; + char parent[_POSIX_PATH_MAX]; + char *p = NULL; + + strfcpy(parent, NONULL(path), sizeof(parent)); + + p = strrchr(parent, '/'); + if (p) + { + *p = '\0'; + basename = p + 1; + } + else + { + strfcpy(parent, ".", sizeof(parent)); + basename = path; + } + + snprintf(newdir, ndlen, "%s/%s", parent, ".muttXXXXXX"); + if (mkdtemp(newdir) == NULL) + { + mutt_debug(1, "mkwrapdir: mkdtemp() failed\n"); + return -1; + } + + if (snprintf(newfile, nflen, "%s/%s", newdir, NONULL(basename)) >= nflen) + { + rmdir(newdir); + mutt_debug(1, "mkwrapdir: string was truncated\n"); + return -1; + } + return 0; +} + +/** + * put_file_in_place - Move a file into place + * @param path Destination path + * @param safe_file Current filename + * @param safe_dir Current directory name + * @retval 0 Success + * @retval -1 Error, see errno + */ +static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir) +{ + int rv; + + rv = safe_rename(safe_file, path); + unlink(safe_file); + rmdir(safe_dir); + return rv; +} + +/** + * safe_fclose - Close a FILE handle (and NULL the pointer) + * @param f FILE handle to close + * @retval 0 Success + * @retval EOF Error, see errno + */ +int safe_fclose(FILE **f) +{ + int r = 0; + + if (*f) + r = fclose(*f); + + *f = NULL; + return r; +} + +/** + * safe_fsync_close - Flush the data, before closing a file (and NULL the pointer) + * @param f FILE handle to close + * @retval 0 Success + * @retval EOF Error, see errno + */ +int safe_fsync_close(FILE **f) +{ + int r = 0; + + if (*f) + { + if (fflush(*f) || fsync(fileno(*f))) + { + int save_errno = errno; + r = -1; + safe_fclose(f); + errno = save_errno; + } + else + r = safe_fclose(f); + } + + return r; +} + +/** + * mutt_unlink - Delete a file, carefully + * @param s Filename + * + * This won't follow symlinks. + */ +void mutt_unlink(const char *s) +{ + int fd; + int flags; + FILE *f = NULL; + struct stat sb, sb2; + char buf[2048]; + +/* Defend against symlink attacks */ + +#ifdef O_NOFOLLOW + flags = O_RDWR | O_NOFOLLOW; +#else + flags = O_RDWR; +#endif + + if ((lstat(s, &sb) == 0) && S_ISREG(sb.st_mode)) + { + fd = open(s, flags); + if (fd < 0) + return; + + if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode) || + (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino)) + { + close(fd); + return; + } + + f = fdopen(fd, "r+"); + if (f) + { + unlink(s); + memset(buf, 0, sizeof(buf)); + while (sb.st_size > 0) + { + fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f); + sb.st_size -= MIN(sizeof(buf), sb.st_size); + } + safe_fclose(&f); + } + } +} + +/** + * mutt_copy_bytes - Copy some content from one file to another + * @param in Source file + * @param out Destination file + * @param size Maximum number of bytes to copy + * @retval 0 Success + * @retval -1 Error, see errno + */ +int mutt_copy_bytes(FILE *in, FILE *out, size_t size) +{ + char buf[2048]; + size_t chunk; + + while (size > 0) + { + chunk = (size > sizeof(buf)) ? sizeof(buf) : size; + chunk = fread(buf, 1, chunk, in); + if (chunk < 1) + break; + if (fwrite(buf, 1, chunk, out) != chunk) + return -1; + + size -= chunk; + } + + if (fflush(out) != 0) + return -1; + return 0; +} + +/** + * mutt_copy_stream - Copy the contents of one file into another + * @param fin Source file + * @param fout Destination file + * @retval 0 Success + * @retval -1 Error, see errno + */ +int mutt_copy_stream(FILE *fin, FILE *fout) +{ + size_t l; + char buf[LONG_STRING]; + + while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) + { + if (fwrite(buf, 1, l, fout) != l) + return -1; + } + + if (fflush(fout) != 0) + return -1; + return 0; +} + +/** + * safe_symlink - Create a symlink + * @param oldpath Existing pathname + * @param newpath New pathname + * @retval 0 Success + * @retval -1 Error, see errno + */ +int safe_symlink(const char *oldpath, const char *newpath) +{ + struct stat osb, nsb; + + if (!oldpath || !newpath) + return -1; + + if ((unlink(newpath) == -1) && (errno != ENOENT)) + return -1; + + if (oldpath[0] == '/') + { + if (symlink(oldpath, newpath) == -1) + return -1; + } + else + { + char abs_oldpath[_POSIX_PATH_MAX]; + + if ((getcwd(abs_oldpath, sizeof(abs_oldpath)) == NULL) || + ((strlen(abs_oldpath) + 1 + strlen(oldpath) + 1) > sizeof(abs_oldpath))) + { + return -1; + } + + strcat(abs_oldpath, "/"); + strcat(abs_oldpath, oldpath); + if (symlink(abs_oldpath, newpath) == -1) + return -1; + } + + if ((stat(oldpath, &osb) == -1) || (stat(newpath, &nsb) == -1) || !compare_stat(&osb, &nsb)) + { + unlink(newpath); + return -1; + } + + return 0; +} + +/** + * safe_rename - NFS-safe renaming of files + * @param src Original filename + * @param target New filename + * @retval 0 Success + * @retval -1 Error, see errno + * + * Warning: We don't check whether src and target are equal. + */ +int safe_rename(const char *src, const char *target) +{ + struct stat ssb, tsb; + + if (!src || !target) + return -1; + + if (link(src, target) != 0) + { + /* Coda does not allow cross-directory links, but tells + * us it's a cross-filesystem linking attempt. + * + * However, the Coda rename call is allegedly safe to use. + * + * With other file systems, rename should just fail when + * the files reside on different file systems, so it's safe + * to try it here. */ + mutt_debug(1, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target, + strerror(errno), errno); + + /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's + * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */ + if (errno == EXDEV || errno == ENOSYS || errno == EPERM +#ifdef ENOTSUP + || errno == ENOTSUP +#endif +#ifdef EOPNOTSUPP + || errno == EOPNOTSUPP +#endif + ) + { + mutt_debug(1, "safe_rename: trying rename...\n"); + if (rename(src, target) == -1) + { + mutt_debug(1, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src, + target, strerror(errno), errno); + return -1; + } + mutt_debug(1, "safe_rename: rename succeeded.\n"); + + return 0; + } + + return -1; + } + + /* Stat both links and check if they are equal. */ + + if (lstat(src, &ssb) == -1) + { + mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno); + return -1; + } + + if (lstat(target, &tsb) == -1) + { + mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno); + return -1; + } + + /* pretend that the link failed because the target file did already exist. */ + + if (!compare_stat(&ssb, &tsb)) + { + mutt_debug(1, "safe_rename: stat blocks for %s and %s diverge; " + "pretending EEXIST.\n", + src, target); + errno = EEXIST; + return -1; + } + + /* Unlink the original link. + * Should we really ignore the return value here? XXX */ + if (unlink(src) == -1) + { + mutt_debug(1, "safe_rename: unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno); + } + + return 0; +} + +/** + * mutt_rmtree - Recursively remove a directory + * @param path Directory to delete + * @retval 0 Success + * @retval -1 Error, see errno + */ +int mutt_rmtree(const char *path) +{ + DIR *dirp = NULL; + struct dirent *de = NULL; + char cur[_POSIX_PATH_MAX]; + struct stat statbuf; + int rc = 0; + + dirp = opendir(path); + if (!dirp) + { + mutt_debug(1, "mutt_rmtree: error opening directory %s\n", path); + return -1; + } + while ((de = readdir(dirp))) + { + if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0)) + continue; + + snprintf(cur, sizeof(cur), "%s/%s", path, de->d_name); + /* XXX make nonrecursive version */ + + if (stat(cur, &statbuf) == -1) + { + rc = 1; + continue; + } + + if (S_ISDIR(statbuf.st_mode)) + rc |= mutt_rmtree(cur); + else + rc |= unlink(cur); + } + closedir(dirp); + + rc |= rmdir(path); + + return rc; +} + +/** + * safe_open - Open a file + * @param path Pathname to open + * @param flags Flags, e.g. O_EXCL + * @retval >0 Success, file handle + * @retval -1 Error + */ +int safe_open(const char *path, int flags) +{ + struct stat osb, nsb; + int fd; + + if (flags & O_EXCL) + { + char safe_file[_POSIX_PATH_MAX]; + char safe_dir[_POSIX_PATH_MAX]; + + if (mkwrapdir(path, safe_file, sizeof(safe_file), safe_dir, sizeof(safe_dir)) == -1) + return -1; + + fd = open(safe_file, flags, 0600); + if (fd < 0) + { + rmdir(safe_dir); + return fd; + } + + /* NFS and I believe cygwin do not handle movement of open files well */ + close(fd); + if (put_file_in_place(path, safe_file, safe_dir) == -1) + return -1; + } + + fd = open(path, flags & ~O_EXCL, 0600); + if (fd < 0) + return fd; + + /* make sure the file is not symlink */ + if ((lstat(path, &osb) < 0 || fstat(fd, &nsb) < 0) || !compare_stat(&osb, &nsb)) + { + close(fd); + return -1; + } + + return fd; +} + +/** + * safe_fopen - Call fopen() safely + * @param path Filename + * @param mode Mode e.g. "r" readonly; "w" read-write + * @retval ptr FILE handle + * @retval NULL Error, see errno + * + * When opening files for writing, make sure the file doesn't already exist to + * avoid race conditions. + */ +FILE *safe_fopen(const char *path, const char *mode) +{ + if (mode[0] == 'w') + { + int fd; + int flags = O_CREAT | O_EXCL; + +#ifdef O_NOFOLLOW + flags |= O_NOFOLLOW; +#endif + + if (mode[1] == '+') + flags |= O_RDWR; + else + flags |= O_WRONLY; + + fd = safe_open(path, flags); + if (fd < 0) + return NULL; + + return (fdopen(fd, mode)); + } + else + return (fopen(path, mode)); +} + +/** + * mutt_sanitize_filename - Replace unsafe characters in a filename + * @param f Filename to make safe + * @param slash Replace '/' characters too + */ +void mutt_sanitize_filename(char *f, short slash) +{ + if (!f) + return; + + for (; *f; f++) + { + if ((slash && *f == '/') || !strchr(safe_chars, *f)) + *f = '_'; + } +} + +/** + * mutt_rx_sanitize_string - Escape any regex-magic characters in a string + * @param dest Buffer for result + * @param destlen Length of buffer + * @param src String to transform + * @retval 0 Success + * @retval -1 Error + */ +int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src) +{ + while (*src && (--destlen > 2)) + { + if (strchr(rx_special_chars, *src)) + { + *dest++ = '\\'; + destlen--; + } + *dest++ = *src++; + } + + *dest = '\0'; + + if (*src) + return -1; + else + return 0; +} + +/** + * mutt_read_line - Read a line from a file + * @param[out] s Buffer allocated on the head (optional) + * @param[in] size Length of buffer (optional) + * @param[in] fp File to read + * @param[out] line Current line number + * @param[in] flags Flags, e.g. #MUTT_CONT + * @retval ptr The allocated string + * + * Read a line from ``fp'' into the dynamically allocated ``s'', increasing + * ``s'' if necessary. The ending "\n" or "\r\n" is removed. If a line ends + * with "\", this char and the linefeed is removed, and the next line is read + * too. + */ +char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags) +{ + size_t offset = 0; + char *ch = NULL; + + if (!s) + { + s = safe_malloc(STRING); + *size = STRING; + } + + while (true) + { + if (fgets(s + offset, *size - offset, fp) == NULL) + { + FREE(&s); + return NULL; + } + if ((ch = strchr(s + offset, '\n')) != NULL) + { + if (line) + (*line)++; + if (flags & MUTT_EOL) + return s; + *ch = 0; + if ((ch > s) && (*(ch - 1) == '\r')) + *--ch = 0; + if (!(flags & MUTT_CONT) || (ch == s) || (*(ch - 1) != '\\')) + return s; + offset = ch - s - 1; + } + else + { + int c; + c = getc(fp); /* This is kind of a hack. We want to know if the + char at the current point in the input stream is EOF. + feof() will only tell us if we've already hit EOF, not + if the next character is EOF. So, we need to read in + the next character and manually check if it is EOF. */ + if (c == EOF) + { + /* The last line of fp isn't \n terminated */ + if (line) + (*line)++; + return s; + } + else + { + ungetc(c, fp); /* undo our damage */ + /* There wasn't room for the line -- increase ``s'' */ + offset = *size - 1; /* overwrite the terminating 0 */ + *size += STRING; + safe_realloc(&s, *size); + } + } + } +} + +/** + * mutt_quote_filename - Quote a filename to survive the shell's quoting rules + * @param d Buffer for the result + * @param l Length of buffer + * @param f String to convert + * @retval num Number of bytes written to the buffer + * + * From the Unix programming FAQ by way of Liviu. + */ +size_t mutt_quote_filename(char *d, size_t l, const char *f) +{ + size_t j = 0; + + if (!f) + { + *d = '\0'; + return 0; + } + + /* leave some space for the trailing characters. */ + l -= 6; + + d[j++] = '\''; + + for (size_t i = 0; (j < l) && f[i]; i++) + { + if ((f[i] == '\'') || (f[i] == '`')) + { + d[j++] = '\''; + d[j++] = '\\'; + d[j++] = f[i]; + d[j++] = '\''; + } + else + d[j++] = f[i]; + } + + d[j++] = '\''; + d[j] = '\0'; + + return j; +} + +/** + * mutt_concatn_path - Concatenate directory and filename + * @param dst Buffer for result + * @param dstlen Buffer length + * @param dir Directory + * @param dirlen Directory length + * @param fname Filename + * @param fnamelen Filename length + * @retval NULL Error + * @retval ptr Pointer to \a dst on success + * + * Write the concatenated pathname (dir + "/" + fname) into dst. + * The slash is omitted when dir or fname is of 0 length. + */ +char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir, + size_t dirlen, const char *fname, size_t fnamelen) +{ + size_t req; + size_t offset = 0; + + if (dstlen == 0) + return NULL; /* probably should not mask errors like this */ + + /* size check */ + req = dirlen + fnamelen + 1; /* +1 for the trailing nul */ + if (dirlen && fnamelen) + req++; /* when both components are non-nul, we add a "/" in between */ + if (req > dstlen) + { /* check for condition where the dst length is too short */ + /* Two options here: + * 1) assert(0) or return NULL to signal error + * 2) copy as much of the path as will fit + * It doesn't appear that the return value is actually checked anywhere mutt_concat_path() + * is called, so we should just copy set dst to nul and let the calling function fail later. + */ + dst[0] = 0; /* safe since we bail out early if dstlen == 0 */ + return NULL; + } + + if (dirlen) + { /* when dir is not empty */ + memcpy(dst, dir, dirlen); + offset = dirlen; + if (fnamelen) + dst[offset++] = '/'; + } + if (fnamelen) + { /* when fname is not empty */ + memcpy(dst + offset, fname, fnamelen); + offset += fnamelen; + } + dst[offset] = 0; + return dst; +} + +/** + * mutt_concat_path - Join a directory name and a filename + * @param d Buffer for the result + * @param dir Directory name + * @param fname File name + * @param l Length of buffer + * @retval ptr Destination buffer + * + * If both dir and fname are supplied, they are separated with '/'. + * If either is missing, then the other will be copied exactly. + */ +char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l) +{ + const char *fmt = "%s/%s"; + + if (!*fname || (*dir && dir[strlen(dir) - 1] == '/')) + fmt = "%s%s"; + + snprintf(d, l, fmt, dir, fname); + return d; +} + +/** + * mutt_basename - Find the last component for a pathname + * @param f String to be examined + * @retval ptr Part of pathname after last '/' character + */ +const char *mutt_basename(const char *f) +{ + const char *p = strrchr(f, '/'); + if (p) + return p + 1; + else + return f; +} + +/** + * mutt_mkdir - Recursively create directories + * @param path Directories to create + * @param mode Permissions for final directory + * @retval 0 Success + * @retval -1 Error (errno set) + * + * Create a directory, creating the parents if necessary. (like mkdir -p) + * + * @note The permissions are only set on the final directory. + * The permissions of any parent directories are determined by the umask. + * (This is how "mkdir -p" behaves) + */ +int mutt_mkdir(const char *path, mode_t mode) +{ + if (!path || !*path) + { + errno = EINVAL; + return -1; + } + + errno = 0; + char *p = NULL; + char _path[PATH_MAX]; + const size_t len = strlen(path); + + if (len >= sizeof(_path)) + { + errno = ENAMETOOLONG; + return -1; + } + + struct stat sb; + if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode)) + return 0; + + /* Create a mutable copy */ + strfcpy(_path, path, sizeof(_path)); + + for (p = _path + 1; *p; p++) + { + if (*p != '/') + continue; + + /* Temporarily truncate the path */ + *p = '\0'; + + if ((mkdir(_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST)) + return -1; + + *p = '/'; + } + + if ((mkdir(_path, mode) != 0) && (errno != EEXIST)) + return -1; + + return 0; +} diff --git a/lib/lib_file.h b/lib/lib_file.h new file mode 100644 index 000000000..c0c136524 --- /dev/null +++ b/lib/lib_file.h @@ -0,0 +1,57 @@ +/** + * @file + * File management functions + * + * @authors + * Copyright (C) 2017 Richard Russon + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef _LIB_FILE_H +#define _LIB_FILE_H + +#include +#include + +#undef MAX +#undef MIN +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +/* Flags for mutt_read_line() */ +#define MUTT_CONT (1 << 0) /**< \-continuation */ +#define MUTT_EOL (1 << 1) /**< don't strip `\n` / `\r\n` */ + +const char *mutt_basename(const char *f); +char * mutt_concatn_path(char *dst, size_t dstlen, const char *dir, size_t dirlen, const char *fname, size_t fnamelen); +char * mutt_concat_path(char *d, const char *dir, const char *fname, size_t l); +int mutt_copy_bytes(FILE *in, FILE *out, size_t size); +int mutt_copy_stream(FILE *fin, FILE *fout); +int mutt_mkdir(const char *path, mode_t mode); +size_t mutt_quote_filename(char *d, size_t l, const char *f); +char * mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags); +int mutt_rmtree(const char *path); +int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src); +void mutt_sanitize_filename(char *f, short slash); +void mutt_unlink(const char *s); +int safe_fclose(FILE **f); +FILE * safe_fopen(const char *path, const char *mode); +int safe_fsync_close(FILE **f); +int safe_open(const char *path, int flags); +int safe_rename(const char *src, const char *target); +int safe_symlink(const char *oldpath, const char *newpath); + +#endif /* _LIB_FILE_H */ diff --git a/po/POTFILES.in b/po/POTFILES.in index e927b9a6a..821ed95b2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -63,6 +63,7 @@ lib/lib_base64.c lib/lib_date.c lib/lib_debug.c lib/lib_exit.c +lib/lib_file.c lib/lib_md5.c lib/lib_memory.c lib/lib_message.c -- 2.40.0