From 9ab204f57de0883f1f876a2b58f3359ab606fd0a Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 17 Dec 2016 09:06:15 +0200 Subject: Improve rename/copy/reomove API. * examples/rename.c: Remove. * examples/fcopy.c: New file. * examples/fremove.c: New file. * examples/frename.c: New file. * examples/Makefile.am: Update. * include/mailutils/util.h (mu_rename_file): Add flags. (mu_remove_file): New function. (MU_COPY_OVERWRITE): New flag. * libmailutils/base/renamefile.c: New file. * libmailutils/base/Makefile.am: Add newe file. * libmailutils/base/copyfile.c: Fix error handling. * libmailutils/base/renamefile.c (mu_rename_file): Refuse to proceed if the destination file exists and MU_COPY_OVERWRITE flag is not set * libmailutils/diag/errors (MU_ERR_REMOVE_SOURCE) (MU_ERR_RESTORE_META): New errors * imap4d/rename.c (imap4d_rename): Use mu_rename_file * mh/forw.c: Likewise. * mh/mh_whatnow.c: Likewise. * mh/mhn.c: Likewise. * mh/send.c: Likewise. * include/mailutils/cstr.h (mu_str_count): New proto. * include/mailutils/util.h (mu_file_name_is_safe): New proto. * libmailutils/string/safefilename.c: New file. * libmailutils/string/strcount.c: New file. * libmailutils/string/Makefile.am: Update. --- examples/.gitignore | 3 + examples/Makefile.am | 4 +- examples/fcopy.c | 63 +++++++++ examples/fremove.c | 49 +++++++ examples/frename.c | 59 ++++++++ examples/rename.c | 60 -------- imap4d/rename.c | 25 +++- include/mailutils/cstr.h | 2 + include/mailutils/util.h | 18 ++- libmailutils/base/Makefile.am | 1 + libmailutils/base/copyfile.c | 122 ++++++++-------- libmailutils/base/removefile.c | 275 +++++++++++++++++++++++++++++++++++++ libmailutils/base/renamefile.c | 55 +++++++- libmailutils/diag/errors | 4 + libmailutils/string/Makefile.am | 2 + libmailutils/string/safefilename.c | 84 +++++++++++ libmailutils/string/strcount.c | 50 +++++++ mh/forw.c | 10 +- mh/mh_whatnow.c | 8 +- mh/mhn.c | 26 ++-- mh/send.c | 10 +- 21 files changed, 781 insertions(+), 149 deletions(-) create mode 100644 examples/fcopy.c create mode 100644 examples/fremove.c create mode 100644 examples/frename.c delete mode 100644 examples/rename.c create mode 100644 libmailutils/base/removefile.c create mode 100644 libmailutils/string/safefilename.c create mode 100644 libmailutils/string/strcount.c diff --git a/examples/.gitignore b/examples/.gitignore index e75f2264a..d4792a31f 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -5,6 +5,9 @@ base64 decode2047 echosrv encode2047 +fcopy +fremove +frename header http iconv diff --git a/examples/Makefile.am b/examples/Makefile.am index ca5e0947a..fa63b6624 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -30,6 +30,9 @@ noinst_PROGRAMS = \ addr\ base64\ echosrv\ + fcopy\ + fremove\ + frename\ header\ http\ iconv\ @@ -44,7 +47,6 @@ noinst_PROGRAMS = \ murun\ musocio\ $(NNTPCLIENT)\ - rename\ sa\ sfrom diff --git a/examples/fcopy.c b/examples/fcopy.c new file mode 100644 index 000000000..e4aa86852 --- /dev/null +++ b/examples/fcopy.c @@ -0,0 +1,63 @@ +#include + +int owner_option; +int mode_option; +int force_option; +int deref_option; + +static struct mu_option copy_options[] = { + { "owner", 'u', NULL, MU_OPTION_DEFAULT, + "copy ownership", + mu_c_bool, &owner_option }, + { "mode", 'm', NULL, MU_OPTION_DEFAULT, + "copy mode", + mu_c_bool, &mode_option }, + { "force", 'f', NULL, MU_OPTION_DEFAULT, + "force overwriting the destination file if it exists", + mu_c_bool, &force_option }, + { "overwrite", 0, NULL, MU_OPTION_ALIAS }, + { "dereference", 'h', NULL, MU_OPTION_DEFAULT, + "dereference symbolic links", + mu_c_bool, &deref_option }, + MU_OPTION_END +}, *options[] = { copy_options, NULL }; + +struct mu_cli_setup cli = { + options, + NULL, + "copy file", + "SRC DST" +}; + +static char *capa[] = { + "debug", + NULL +}; + +int +main (int argc, char **argv) +{ + int rc; + int flags; + + mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv); + + if (argc != 2) + { + mu_error ("wrong number of arguments"); + return 1; + } + + flags = (owner_option ? MU_COPY_OWNER : 0) + | (mode_option ? MU_COPY_MODE : 0) + | (force_option ? MU_COPY_OVERWRITE : 0) + | (deref_option ? MU_COPY_DEREF : 0); + rc = mu_copy_file (argv[0], argv[1], flags); + + if (rc) + mu_diag_funcall (MU_DIAG_ERROR, "mu_copy_file", NULL, rc); + + return !!rc; +} + + diff --git a/examples/fremove.c b/examples/fremove.c new file mode 100644 index 000000000..e7c2d9118 --- /dev/null +++ b/examples/fremove.c @@ -0,0 +1,49 @@ +#include + +int owner_option; +int mode_option; +int force_option; + +static struct mu_option *options[] = { NULL }; + +struct mu_cli_setup cli = { + options, + NULL, + "delete file", + "FILE" +}; + +static char *capa[] = { + "debug", + NULL +}; + +int +main (int argc, char **argv) +{ + int rc; + + mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv); + + if (argc != 1) + { + mu_error ("wrong number of arguments"); + return 1; + } + + if (!mu_file_name_is_safe (argv[0]) + || (argv[0][0] == '/' && mu_str_count (argv[0], '/') < 2)) + { + mu_error ("unsafe file name"); + return 1; + } + + rc = mu_remove_file (argv[0]); + + if (rc) + mu_diag_funcall (MU_DIAG_ERROR, "mu_remove_file", NULL, rc); + + return !!rc; +} + + diff --git a/examples/frename.c b/examples/frename.c new file mode 100644 index 000000000..1bf6556f4 --- /dev/null +++ b/examples/frename.c @@ -0,0 +1,59 @@ +#include + +int force_option; + +static struct mu_option rename_options[] = { + { "force", 'f', NULL, MU_OPTION_DEFAULT, + "force overwriting the destination file if it exists", + mu_c_bool, &force_option }, + { "overwrite", 0, NULL, MU_OPTION_ALIAS }, + MU_OPTION_END +}, *options[] = { rename_options, NULL }; + +struct mu_cli_setup cli = { + options, + NULL, + "rename file", + "SRC DST" +}; + +static char *capa[] = { + "debug", + NULL +}; + +int +main (int argc, char **argv) +{ + int rc; + + mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv); + + if (argc != 2) + { + mu_error ("wrong number of arguments"); + return 1; + } + + if (!mu_file_name_is_safe (argv[0]) + || (argv[0][0] == '/' && mu_str_count (argv[0], '/') < 2)) + { + mu_error ("%s: unsafe file name", argv[0]); + return 1; + } + if (!mu_file_name_is_safe (argv[1]) + || (argv[1][0] == '/' && mu_str_count (argv[1], '/') < 2)) + { + mu_error ("%sunsafe file name", argv[0]); + return 1; + } + + rc = mu_rename_file (argv[0], argv[1], force_option ? MU_COPY_OVERWRITE : 0); + + if (rc) + mu_diag_funcall (MU_DIAG_ERROR, "mu_rename_file", NULL, rc); + + return !!rc; +} + + diff --git a/examples/rename.c b/examples/rename.c deleted file mode 100644 index f12264ac7..000000000 --- a/examples/rename.c +++ /dev/null @@ -1,60 +0,0 @@ -#include - -int copy_option; -int owner_option; -int mode_option; - -static struct mu_option rename_options[] = { - { "copy", 'c', NULL, MU_OPTION_DEFAULT, - "copy the file", - mu_c_bool, ©_option }, - { "owner", 'u', NULL, MU_OPTION_DEFAULT, - "copy ownership", - mu_c_bool, &owner_option }, - { "mode", 'm', NULL, MU_OPTION_DEFAULT, - "copy mode", - mu_c_bool, &mode_option }, - MU_OPTION_END -}, *options[] = { rename_options, NULL }; - -struct mu_cli_setup cli = { - options, - NULL, - "copy or rename file", - "SRC DST" -}; - -static char *capa[] = { - "debug", - NULL -}; - -int -main (int argc, char **argv) -{ - int rc; - - mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv); - - if (argc != 2) - { - mu_error ("wrong number of arguments"); - return 1; - } - - if (copy_option) - { - int flags = (owner_option ? MU_COPY_OWNER : 0) - | (mode_option ? MU_COPY_MODE : 0); - rc = mu_copy_file (argv[0], argv[1], flags); - } - else - rc = mu_rename_file (argv[0], argv[1]); - - if (rc) - mu_diag_funcall (MU_DIAG_ERROR, "mu_rename_file", NULL, rc); - - return !!rc; -} - - diff --git a/imap4d/rename.c b/imap4d/rename.c index bca502383..c551d2564 100644 --- a/imap4d/rename.c +++ b/imap4d/rename.c @@ -125,8 +125,8 @@ imap4d_rename (struct imap4d_session *session, if (!newname) return io_completion_response (command, RESP_NO, "Permission denied"); - /* It is an error to attempt to rename from a mailbox name that already - exist. */ + /* It is an error to attempt to rename from a mailbox name that does not + exist or to a mailbox name that already exists. */ if (stat (newname, &newst) == 0) { /* FIXME: What if it's a maildir?!? */ @@ -216,9 +216,26 @@ imap4d_rename (struct imap4d_session *session, } else { - if (rename (oldname, newname) != 0) + rc = mu_rename_file (oldname, newname, 0); + if (rc) { - mu_diag_funcall (MU_DIAG_ERROR, "rename", oldname, errno); + switch (rc) + { + case MU_ERR_REMOVE_SOURCE: + mu_error (_("failed to remove source mailbox after moving %s to %s"), + oldname, newname); + break; + + case MU_ERR_RESTORE_META: + mu_error (_("failed to restore mailbox ownership/modes after moving %s to %s"), + oldname, newname); + break; + + default: + mu_error (_("error renaming mailbox %s to %s: %s"), + oldname, newname, mu_strerror (rc)); + } + rc = RESP_NO; msg = "Failed"; } diff --git a/include/mailutils/cstr.h b/include/mailutils/cstr.h index f98439fd8..579a49686 100644 --- a/include/mailutils/cstr.h +++ b/include/mailutils/cstr.h @@ -45,6 +45,8 @@ char *mu_str_skip_cset_comp (const char *str, const char *cset); char *mu_str_stripws (char *string); int mu_string_split (const char *string, char *delim, mu_list_t list); + +size_t mu_str_count (char const *str, int chr); #ifdef __cplusplus } diff --git a/include/mailutils/util.h b/include/mailutils/util.h index 23792e26f..d539099b5 100644 --- a/include/mailutils/util.h +++ b/include/mailutils/util.h @@ -140,8 +140,6 @@ struct mu_param char *name; char *value; }; - - int mu_content_type_parse (const char *input, mu_content_type_t *retct); void mu_content_type_destroy (mu_content_type_t *pptr); @@ -207,17 +205,23 @@ int mu_str_to_c (char const *string, mu_c_type_t type, void *tgt, /* -------------------------- */ /* Safe file copy and rename */ /* -------------------------- */ -#define MU_COPY_MODE 0x01 -#define MU_COPY_OWNER 0x02 -#define MU_COPY_SYMLINK 0x04 -#define MU_COPY_FORCE 0x08 +/* Bits for the flags argument of mu_copy_file and mu_rename_file. The + MU_COPY_OVERWRITE is valid for both calls. The rest is for mu_copy_file + only */ +#define MU_COPY_OVERWRITE 0x01 /* Overwrite destination file, if it exists */ +#define MU_COPY_MODE 0x02 /* Preserve file mode */ +#define MU_COPY_OWNER 0x04 /* Preserve file ownership */ +#define MU_COPY_DEREF 0x08 /* Dereference the source file */ int mu_copy_file (const char *srcpath, const char *dstpath, int flags); -int mu_rename_file (const char *oldpath, const char *newpath); +int mu_rename_file (const char *oldpath, const char *newpath, int flags); +int mu_remove_file (const char *path); /* ----------------------- */ /* Assorted functions. */ /* ----------------------- */ +int mu_file_name_is_safe (char const *str); + int mu_getmaxfd (void); /* Get the host name, doing a gethostbyname() if possible. */ int mu_get_host_name (char **host); diff --git a/libmailutils/base/Makefile.am b/libmailutils/base/Makefile.am index e16fb4489..24f08ba44 100644 --- a/libmailutils/base/Makefile.am +++ b/libmailutils/base/Makefile.am @@ -59,6 +59,7 @@ libbase_la_SOURCES = \ registrar.c\ refcount.c\ renamefile.c\ + removefile.c\ rfc2047.c\ schemeauto.c\ sha1.c\ diff --git a/libmailutils/base/copyfile.c b/libmailutils/base/copyfile.c index f65d59c0e..ff03a4b98 100644 --- a/libmailutils/base/copyfile.c +++ b/libmailutils/base/copyfile.c @@ -1,3 +1,19 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see . */ + #include #include #include @@ -16,13 +32,24 @@ static int copy_regular_file (const char *srcpath, const char *dstpath, static int copy_symlink (const char *srcpath, const char *dstpath); static int copy_dir (const char *srcpath, const char *dstpath, int flags); +/* Copy SRCPATH to DSTPATH. SRCPATH can be any kind of file. If it is + a directory, its content will be copied recursively. + + FLAGS: + + MU_COPY_OVERWRITE Overwrite destination file, if it exists. + MU_COPY_MODE Preserve file mode + MU_COPY_OWNER Preserve file ownership + MU_COPY_DEREF Dereference symbolic links: operate on files they + refer to. +*/ int mu_copy_file (const char *srcpath, const char *dstpath, int flags) { int rc = 0; struct stat st; - if (((flags & MU_COPY_SYMLINK) ? lstat : stat) (srcpath, &st)) + if (((flags & MU_COPY_DEREF) ? stat : lstat) (srcpath, &st)) { mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, (_("can't stat file %s: %s"), @@ -30,6 +57,23 @@ mu_copy_file (const char *srcpath, const char *dstpath, int flags) return errno; } + if (access (dstpath, F_OK) == 0) + { + if (flags & MU_COPY_OVERWRITE) + { + rc = mu_remove_file (dstpath); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't remove destination %s: %s"), + dstpath, mu_strerror (rc))); + return rc; + } + } + else + return EEXIST; + } + switch (st.st_mode & S_IFMT) { case S_IFREG: @@ -37,11 +81,9 @@ mu_copy_file (const char *srcpath, const char *dstpath, int flags) case S_IFLNK: return copy_symlink (srcpath, dstpath); - break; case S_IFDIR: return copy_dir (srcpath, dstpath, flags); - break; case S_IFBLK: case S_IFCHR: @@ -123,16 +165,16 @@ copy_regular_file (const char *srcpath, const char *dstpath, int flags, { if (fchmod ((int) trans[0], mode)) { - rc = errno; mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, (_("%s: cannot chmod: %s"), - dstpath, mu_strerror (rc))); + dstpath, mu_strerror (errno))); + rc = MU_ERR_RESTORE_META; } else if (flags & MU_COPY_OWNER) { uid_t uid; gid_t gid; - + if (getuid () == 0) { uid = st->st_uid; @@ -153,13 +195,13 @@ copy_regular_file (const char *srcpath, const char *dstpath, int flags, { if (fchown ((int) trans[0], uid, gid)) { - rc = errno; mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, (_("%s: cannot chown to %lu.%lu: %s"), dstpath, (unsigned long) uid, (unsigned long) gid, - mu_strerror (rc))); + mu_strerror (errno))); + rc = MU_ERR_RESTORE_META; } } } @@ -212,9 +254,8 @@ copy_dir (const char *srcpath, const char *dstpath, int flags) { DIR *dirp; struct dirent *dp; - struct stat st, st1; + struct stat st; int rc; - int create = 0; mode_t mode, mask; if (stat (srcpath, &st)) @@ -225,67 +266,28 @@ copy_dir (const char *srcpath, const char *dstpath, int flags) return errno; } - if (stat (dstpath, &st1)) - { - if (errno == ENOENT) - create = 1; - else - { - mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, - (_("can't stat directory %s: %s"), - dstpath, mu_strerror (errno))); - return errno; - } - } - else if (!S_ISDIR (st1.st_mode)) - { - if (flags & MU_COPY_FORCE) - { - if (unlink (dstpath)) - { - rc = errno; - mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, - (_("%s is not a directory and cannot be unlinked: %s"), - dstpath, mu_strerror (rc))); - return rc; - } - create = 1; - } - else - { - mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, - (_("%s is not a directory"), - dstpath)); - return EEXIST; - } - } - mask = umask (077); mode = ((flags & MU_COPY_MODE) ? st.st_mode : (0777 & ~mask)) & 0777; - if (create) - { - rc = mkdir (dstpath, 0700); - umask (mask); + rc = mkdir (dstpath, 0700); + umask (mask); - if (rc) - { - mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, - (_("can't create directory %s: %s"), - dstpath, mu_strerror (errno))); - return errno; - } + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't create directory %s: %s"), + dstpath, mu_strerror (errno))); + return errno; } - else - umask (mask); dirp = opendir (srcpath); if (dirp == NULL) { - mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR, + rc = errno; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, ("cannot open directory %s: %s", srcpath, mu_strerror (errno))); - return 1; + return rc; } while ((dp = readdir (dirp))) diff --git a/libmailutils/base/removefile.c b/libmailutils/base/removefile.c new file mode 100644 index 000000000..405eb7ba0 --- /dev/null +++ b/libmailutils/base/removefile.c @@ -0,0 +1,275 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int removedir (const char *path); + +int +mu_remove_file (const char *path) +{ + int rc = 0; + struct stat st; + + if (stat (path, &st)) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't stat file %s: %s"), + path, mu_strerror (errno))); + return errno; + } + + if (S_ISDIR (st.st_mode)) + rc = removedir (path); + else if (unlink (path)) + { + rc = errno; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't unlink file %s: %s"), + path, mu_strerror (rc))); + } + + return rc; +} + +struct nameent +{ + int isdir; + char name[1]; +}; + +static int +name_add (mu_list_t list, char const *name) +{ + int rc; + size_t len = strlen (name); + struct nameent *ent = malloc (sizeof (*ent) + len); + if (!ent) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("%s", mu_strerror (errno))); + return 1; + } + ent->isdir = -1; + strcpy (ent->name, name); + rc = mu_list_append (list, ent); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("mu_list_append: %s", mu_strerror (rc))); + free (ent); + } + + return rc; +} + +static int +lsdir (const char *path, mu_list_t list) +{ + DIR *dirp; + struct dirent *dp; + int rc = 0; + + dirp = opendir (path); + if (dirp == NULL) + { + rc = errno; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("cannot open directory %s: %s", + path, mu_strerror (errno))); + return rc; + } + + while ((dp = readdir (dirp))) + { + char const *ename = dp->d_name; + char *filename; + + if (ename[ename[0] != '.' ? 0 : ename[1] != '.' ? 1 : 2] == 0) + continue; + + filename = mu_make_file_name (path, ename); + if (!filename) + { + rc = errno; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("%s: can't create file name: %s", + path, mu_strerror (errno))); + break; + } + + rc = name_add (list, filename); + free (filename); + if (rc) + break; + } + + closedir (dirp); + + return rc; +} + +static int +namecmp (const void *a, const void *b) +{ + struct nameent const *enta = a; + struct nameent const *entb = b; + int d = enta->isdir - entb->isdir; + if (d) + return d; + return strcmp (entb->name, enta->name); +} + +static int +check_parent_access (const char *path) +{ + int rc; + char *name, *p; + + name = strdup (path); + if (!name) + return errno; + p = strrchr (name, '/'); + if (p) + *p = 0; + else + strcpy (name, "."); + rc = access (name, R_OK|W_OK|X_OK); + free (name); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("not enough privileges to remove files from %s"), + name)); + return EACCES; + } + + return 0; +} + +static int +removedir (const char *path) +{ + int rc; + struct stat st; + mu_list_t namelist; + mu_iterator_t itr; + struct nameent *ent; + + rc = check_parent_access (path); + if (rc) + return rc; + + rc = mu_list_create (&namelist); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("mu_list_create: %s", mu_strerror (rc))); + return rc; + } + mu_list_set_destroy_item (namelist, mu_list_free_item); + mu_list_set_comparator (namelist, namecmp); + + rc = name_add (namelist, path); + if (rc) + { + mu_list_destroy (&namelist); + return rc; + } + + rc = mu_list_get_iterator (namelist, &itr); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + ("mu_list_get_iterator: %s", mu_strerror (rc))); + mu_list_destroy (&namelist); + return rc; + } + + for (mu_iterator_first (itr); + !mu_iterator_is_done (itr); + mu_iterator_next (itr)) + { + mu_iterator_current (itr, (void **)&ent); + + if (lstat (ent->name, &st)) + { + rc = errno; + if (rc == ENOENT) + continue; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't lstat file %s: %s"), + ent->name, mu_strerror (rc))); + break; + } + + if (S_ISDIR (st.st_mode)) + { + ent->isdir = 1; + if (access (ent->name, R_OK|W_OK|X_OK)) + { + rc = EACCES; + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("not enough privileges to remove files from %s"), + ent->name)); + } + else + rc = lsdir (ent->name, namelist); + if (rc) + break; + } + else + ent->isdir = 0; + } + + if (rc == 0) + { + mu_list_sort (namelist, namecmp); + + for (mu_iterator_first (itr); + !mu_iterator_is_done (itr); + mu_iterator_next (itr)) + { + mu_iterator_current (itr, (void **)&ent); + rc = (ent->isdir ? rmdir : unlink) (ent->name); + if (rc) + { + mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, + (_("can't remove %s: %s"), + ent->name, mu_strerror (rc))); + } + } + } + mu_iterator_destroy (&itr); + mu_list_destroy (&namelist); + + return rc; +} + + diff --git a/libmailutils/base/renamefile.c b/libmailutils/base/renamefile.c index 5bc0befa9..a791619af 100644 --- a/libmailutils/base/renamefile.c +++ b/libmailutils/base/renamefile.c @@ -1,6 +1,23 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see . */ + #include #include #include +#include #include #include #include @@ -8,10 +25,36 @@ #include #include +/* Rename file OLDPATH to NEWPATH. Prefer rename(2), unless OLDPATH + and NEWPATH reside on different devices. In the latter case, fall + back to recursive copying. + + FLAGS controls what to do if NEWPATH exists. If it has MU_COPY_OVERWRITE + bit set, the NEWPATH will be overwritten (if possible, atomically). + Otherwise EEXIST will be returned. +*/ int -mu_rename_file (const char *oldpath, const char *newpath) +mu_rename_file (const char *oldpath, const char *newpath, int flags) { int rc; + struct stat st; + + if (access (oldpath, F_OK)) + return errno; + + if (stat (newpath, &st) == 0) + { + if (flags & MU_COPY_OVERWRITE) + { + if (S_ISDIR (st.st_mode)) + { + if (mu_remove_file (newpath)) + return MU_ERR_REMOVE_DEST; + } + } + else + return EEXIST; + } if (rename (oldpath, newpath) == 0) return 0; @@ -24,16 +67,20 @@ mu_rename_file (const char *oldpath, const char *newpath) mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_TRACE1, (_("attempting copy"))); - rc = mu_copy_file (oldpath, newpath, MU_COPY_MODE|MU_COPY_OWNER); + rc = mu_copy_file (oldpath, newpath, flags|MU_COPY_MODE|MU_COPY_OWNER); if (rc == 0) { - if (unlink (oldpath)) + rc = mu_remove_file (oldpath); + if (rc) { mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR, (_("copied %s to %s, but failed to remove the source: %s"), - oldpath, newpath, mu_strerror (errno))); + oldpath, newpath, mu_strerror (rc))); + rc = MU_ERR_REMOVE_SOURCE; } } } + else + rc = errno; return rc; } diff --git a/libmailutils/diag/errors b/libmailutils/diag/errors index 4b394f442..c4fec20e1 100644 --- a/libmailutils/diag/errors +++ b/libmailutils/diag/errors @@ -125,3 +125,7 @@ MU_ERR_PERM_DIR_IWOTH _("File in world writable directory") MU_ERR_DISABLED _("Requested feature disabled in configuration") MU_ERR_FORMAT _("Error in format string") + +MU_ERR_REMOVE_SOURCE _("Failed to remove source file") +MU_ERR_REMOVE_DEST _("Failed to remove destination file") +MU_ERR_RESTORE_META _("Failed to restore ownership or mode") diff --git a/libmailutils/string/Makefile.am b/libmailutils/string/Makefile.am index 47e578e62..4bcea5b9b 100644 --- a/libmailutils/string/Makefile.am +++ b/libmailutils/string/Makefile.am @@ -24,8 +24,10 @@ libstring_la_SOURCES = \ cstrlower.c\ cstrupper.c\ hexstr.c\ + safefilename.c\ stpcpy.c\ str_to_c.c\ + strcount.c\ strltrim.c\ strskip.c\ stripws.c\ diff --git a/libmailutils/string/safefilename.c b/libmailutils/string/safefilename.c new file mode 100644 index 000000000..60c9104db --- /dev/null +++ b/libmailutils/string/safefilename.c @@ -0,0 +1,84 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see . */ + +#include +#include + +/* Return 1 if file name STR is safe, or 0 otherwise. + The name is not safe if it begins with .. or contains /../ +*/ +int +mu_file_name_is_safe (char const *str) +{ + enum { st_init, st_slash, st_dot, st_dotdot } state; + unsigned char c; + int consume = 0; + + if (!str) + return 0; + + state = (*str == '.') ? st_dot : st_init; + + while ((c = *str++) != 0) + { + if (consume) + consume--; + else if (c < 0xc0) + { + switch (state) + { + case st_init: + if (c == '/') + state = st_slash; + break; + + case st_slash: + if (c == '.') + state = st_dot; + else if (c != '/') + state = st_init; + break; + + case st_dot: + if (c == '.') + state = st_dotdot; + else if (c == '/') + state = st_slash; + else + state = st_init; + break; + + case st_dotdot: + if (c == '/') + return 0; + else + state = st_init; + break; + } + } + else if (c & 0xc0) + consume = 1; + else if (c & 0xe0) + consume = 2; + else if (c & 0xf0) + consume = 3; + } + + if (state == st_dotdot) + return 0; + + return 1; +} diff --git a/libmailutils/string/strcount.c b/libmailutils/string/strcount.c new file mode 100644 index 000000000..4316c0a19 --- /dev/null +++ b/libmailutils/string/strcount.c @@ -0,0 +1,50 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see . */ + +#include +#include +#include + +/* Return the number of occurrences of the ASCII character CHR in the + UTF-8 string STR. */ +size_t +mu_str_count (char const *str, int chr) +{ + unsigned char c; + size_t count = 0; + int consume = 0; + + if (!str || chr < 0 || chr > UCHAR_MAX) + return 0; + + while ((c = *str++) != 0) + { + if (consume) + consume--; + else if (c < 0xc0) + { + if (c == chr) + count++; + } + else if (c & 0xc0) + consume = 1; + else if (c & 0xe0) + consume = 2; + else if (c & 0xf0) + consume = 3; + } + return count; +} diff --git a/mh/forw.c b/mh/forw.c index 33d120f07..cd053c03a 100644 --- a/mh/forw.c +++ b/mh/forw.c @@ -436,7 +436,15 @@ main (int argc, char **argv) if (build_only || wh_env.nowhatnowproc) { if (strcmp (wh_env.file, wh_env.draftfile)) - rename (wh_env.file, wh_env.draftfile); + { + rc = mu_rename_file (wh_env.file, wh_env.draftfile, MU_COPY_OVERWRITE); + if (rc) + { + mu_error (_("can't rename %s to %s: %s"), + wh_env.file, wh_env.draftfile, mu_strerror (rc)); + return 1; + } + } return 0; } diff --git a/mh/mh_whatnow.c b/mh/mh_whatnow.c index 95a403a83..6b866486a 100644 --- a/mh/mh_whatnow.c +++ b/mh/mh_whatnow.c @@ -425,7 +425,13 @@ quit (struct mh_whatnow_env *wh, int argc, char **argv, int *status) { mu_printf (_("draft left on \"%s\"."), wh->draftfile); if (strcmp (wh->file, wh->draftfile)) - rename (wh->file, wh->draftfile); + { + int rc; + rc = mu_rename_file (wh->file, wh->draftfile, MU_COPY_OVERWRITE); + if (rc) + mu_error (_("can't rename %s to %s: %s"), + wh->file, wh->draftfile, mu_strerror (rc)); + } } } mu_printf ("\n"); diff --git a/mh/mhn.c b/mh/mhn.c index 0d240115a..79131b5b9 100644 --- a/mh/mhn.c +++ b/mh/mhn.c @@ -1204,7 +1204,7 @@ list_iterator (size_t num, mu_message_t msg, void *data) } int -mhn_list () +mhn_list (void) { int rc; @@ -1329,7 +1329,7 @@ sigint (int sig) } static int -mhn_pause () +mhn_pause (void) { char c; int rc = 0; @@ -1438,7 +1438,7 @@ show_iterator (size_t num, mu_message_t msg, void *data) } int -mhn_show () +mhn_show (void) { int rc; @@ -1680,7 +1680,7 @@ store_iterator (size_t num, mu_message_t msg, void *data) } int -mhn_store () +mhn_store (void) { int rc = 0; @@ -2697,7 +2697,7 @@ mhn_header (mu_message_t msg, mu_message_t omsg) } int -mhn_compose () +mhn_compose (void) { int rc; mu_mime_t mime = NULL; @@ -2760,12 +2760,22 @@ mhn_compose () /* Preserve the backup copy and replace the draft */ unlink (backup); - rename (input_file, backup); - rename (name, input_file); + + rc = mu_rename_file (input_file, backup, MU_COPY_OVERWRITE); + if (rc) + mu_error (_("can't rename %s to backup file %s: %s"), + input_file, backup, mu_strerror (rc)); + else + { + rc = mu_rename_file (name, input_file, 0); + if (rc) + mu_error (_("can't rename %s to %s: %s"), + name, input_file, mu_strerror (rc)); + } free (name); mu_mime_unref (mime); - return 0; + return rc; } diff --git a/mh/send.c b/mh/send.c index 99dd5701c..5bf5b4aca 100644 --- a/mh/send.c +++ b/mh/send.c @@ -562,9 +562,13 @@ backup_file (const char *file_name) if (unlink (new_name) && errno != ENOENT) mu_diag_funcall (MU_DIAG_ERROR, "unlink", new_name, errno); - else if (rename (file_name, new_name)) - mu_error (_("cannot rename `%s' to `%s': %s"), - file_name, new_name, mu_strerror (errno)); + else + { + int rc = mu_rename_file (file_name, new_name, MU_COPY_OVERWRITE); + if (rc) + mu_error (_("cannot rename %s to %s: %s"), + file_name, new_name, mu_strerror (errno)); + } free (new_name); } -- cgit v1.2.1