getcifsacl: fix raw SID printing routine
[jlayton/cifs-utils.git] / mount.cifs.c
index 29b0d4c2952cdb0c062f1f2a18ca3c4ba432a7cb..9cf58a591c31cb5593d0bceefc4e8defde5c5b97 100644 (file)
 #include <fcntl.h>
 #include <limits.h>
 #include <paths.h>
+#include <libgen.h>
 #include <sys/mman.h>
 #include <sys/wait.h>
+#include <stdbool.h>
+#ifdef HAVE_SYS_FSUID_H
+#include <sys/fsuid.h>
+#endif /* HAVE_SYS_FSUID_H */
 #ifdef HAVE_LIBCAP_NG
 #include <cap-ng.h>
 #else /* HAVE_LIBCAP_NG */
 #define MOUNT_PASSWD_SIZE 128
 #define MAX_DOMAIN_SIZE 64
 
-/*
- * value of the ver= option that gets passed to the kernel. Used to indicate
- * behavioral changes introduced in the mount helper.
- */
-#define OPTIONS_VERSION "1"
-
 /*
  * mount.cifs has been the subject of many "security" bugs that have arisen
  * because of users and distributions installing it as a setuid root program
 #define OPT_MAND       27
 #define OPT_NOMAND     28
 #define OPT_CRUID      29
+#define OPT_BKUPUID    30
+#define OPT_BKUPGID    31
+#define OPT_NOFAIL     32
 
+#define MNT_TMP_FILE "/.mtab.cifs.XXXXXX"
 
 /* struct for holding parsed mount info for use by privleged process */
 struct parsed_mount_info {
@@ -176,11 +179,11 @@ struct parsed_mount_info {
        unsigned int fakemnt:1;
        unsigned int nomtab:1;
        unsigned int verboseflag:1;
+       unsigned int nofail:1;
 };
 
 const char *thisprogram;
 const char *cifs_fstype = "cifs";
-const char *smb2_fstype = "smb2";
 
 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
 
@@ -249,7 +252,7 @@ check_fstab(const char *progname, const char *mountpoint, const char *devname,
 
 BB end finish BB */
 
-static int mount_cifs_usage(FILE * stream)
+static int mount_usage(FILE * stream)
 {
        fprintf(stream, "\nUsage:  %s <remotetarget> <dir> -o <options>\n",
                thisprogram);
@@ -289,52 +292,6 @@ static int mount_cifs_usage(FILE * stream)
        return 0;
 }
 
-static int mount_smb2_usage(FILE *stream)
-{
-       fprintf(stream, "\nUsage:  %s <remotetarget> <dir> -o <options>\n",
-               thisprogram);
-       fprintf(stream, "\nMount the remote target, specified as a UNC name,");
-       fprintf(stream, " to a local directory.\n\nOptions:\n");
-       fprintf(stream, "\tuser=<arg>\n\tpass=<arg>\n\tdom=<arg>\n");
-       fprintf(stream, "\nLess commonly used options:");
-       fprintf(stream,
-               "\n\tcredentials=<filename>,guest,perm,noperm,rw,ro,");
-       fprintf(stream,
-               "\n\tsep=<char>,iocharset=<codepage>,exec,noexec");
-       fprintf(stream,
-               "\n\tnolock,directio,sec=<authentication mechanism>,sign");
-       fprintf(stream,
-               "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>");
-       fprintf(stream, "\n\nRarely used options:");
-       fprintf(stream,
-               "\n\tport=<tcpport>,rsize=<size>,wsize=<size>,unc=<unc_name>,ip=<ip_address>,");
-       fprintf(stream,
-               "\n\tdev,nodev,hard,soft,intr,");
-       fprintf(stream,
-               "\n\tnointr,ignorecase,noacl,prefixpath=<path>,nobrl");
-       fprintf(stream,
-               "\n\nOptions are described in more detail in the manual page");
-       fprintf(stream, "\n\tman 8 mount.smb2\n");
-       fprintf(stream, "\nTo display the version number of the mount helper:");
-       fprintf(stream, "\n\tmount.smb2 -V\n");
-
-       if (stream == stderr)
-               return EX_USAGE;
-       return 0;
-}
-
-static int mount_usage(FILE *stream)
-{
-       int rc;
-
-       if (strcmp(thisprogram, "mount.smb2") == 0)
-               rc = mount_smb2_usage(stream);
-       else
-               rc = mount_cifs_usage(stream);
-
-       return rc;
-}
-
 /*
  * CIFS has to "escape" commas in the password field so that they don't
  * end up getting confused for option delimiters. Copy password into pw
@@ -359,17 +316,6 @@ static int set_password(struct parsed_mount_info *parsed_info, const char *src)
        return 0;
 }
 
-/* caller frees username if necessary */
-static char *getusername(uid_t uid)
-{
-       char *username = NULL;
-       struct passwd *password = getpwuid(uid);
-
-       if (password)
-               username = password->pw_name;
-       return username;
-}
-
 /*
  * Parse a username string into parsed_mount_info fields. The format is:
  *
@@ -377,15 +323,22 @@ static char *getusername(uid_t uid)
  *
  * ...obviously the only required component is "username". The source string
  * is modified in the process, but it should remain unchanged at the end.
+ *
+ * NOTE: the above syntax does not allow for usernames that have slashes in
+ * them, as some krb5 usernames do. Support for the above syntax will be
+ * removed in a later version of cifs-utils. Users should use separate options
+ * instead of overloading this info into the username.
  */
 static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
 {
        char *user, *password, slash;
        int rc = 0;
+       bool warn = false;
 
        /* everything after first % sign is a password */
        password = strchr(rawuser, '%');
        if (password) {
+               warn = true;
                rc = set_password(parsed_info, password + 1);
                if (rc)
                        return rc;
@@ -399,6 +352,7 @@ static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
 
        /* everything before that slash is a domain */
        if (user) {
+               warn = true;
                slash = *user;
                *user = '\0';
                strlcpy(parsed_info->domain, rawuser,
@@ -413,6 +367,11 @@ static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
        if (password)
                *password = '%';
 
+       if (warn)
+               fprintf(stderr, "WARNING: The DOMAIN/username%%password syntax "
+                               "for usernames is deprecated and will be "
+                               "removed in version 5.9 of cifs-utils.\n");
+
        return 0;
 }
 
@@ -552,13 +511,10 @@ free_caps:
 static int
 toggle_dac_capability(int writable, int enable)
 {
-       int rc;
+       int rc = 0;
        cap_t caps;
        cap_value_t capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
 
-       if (getuid() != 0)
-               return 0;
-
        caps = cap_get_proc();
        if (caps == NULL) {
                fprintf(stderr, "Unable to get current capability set: %s\n",
@@ -581,17 +537,17 @@ toggle_dac_capability(int writable, int enable)
        }
 free_caps:
        cap_free(caps);
-       return 0;
+       return rc;
 }
 #else /* HAVE_LIBCAP */
 static int
-drop_capabilities(int parent)
+drop_capabilities(int parent __attribute((unused)))
 {
        return 0;
 }
 
 static int
-toggle_dac_capability(int writable, int enable)
+toggle_dac_capability(int writable __attribute((unused)), int enable __attribute((unused)))
 {
        return 0;
 }
@@ -634,7 +590,8 @@ parsing_err:
 }
 
 static int open_cred_file(char *file_name,
-                       struct parsed_mount_info *parsed_info)
+                       struct parsed_mount_info *parsed_info,
+                       char **saved_username)
 {
        char *line_buf = NULL;
        char *temp_val = NULL;
@@ -650,6 +607,7 @@ static int open_cred_file(char *file_name,
        i = access(file_name, R_OK);
        if (i) {
                toggle_dac_capability(0, 0);
+               i = errno;
                goto return_i;
        }
 
@@ -682,9 +640,11 @@ static int open_cred_file(char *file_name,
                /* parse next token */
                switch (parse_cred_line(line_buf + i, &temp_val)) {
                case CRED_USER:
-                       i = parse_username(temp_val, parsed_info);
-                       if (i)
+                       *saved_username = strdup(temp_val);
+                       if (!*saved_username) {
+                               i = EX_SYSERR;
                                goto return_i;
+                       }
                        break;
                case CRED_PASS:
                        i = set_password(parsed_info, temp_val);
@@ -837,12 +797,18 @@ static int parse_opt_token(const char *token)
                return OPT_GUEST;
        if (strncmp(token, "ro", 2) == 0)
                return OPT_RO;
-       if (strncmp(token, "rw", 2) == 0)
+       if (strncmp(token, "rw", 2) == 0 && strlen(token) == 2)
                return OPT_RW;
        if (strncmp(token, "remount", 7) == 0)
                return OPT_REMOUNT;
        if (strncmp(token, "_netdev", 7) == 0)
                return OPT_IGNORE;
+       if (strncmp(token, "backupuid", 9) == 0)
+               return OPT_BKUPUID;
+       if (strncmp(token, "backupgid", 9) == 0)
+               return OPT_BKUPGID;
+       if (strncmp(token, "nofail", 6) == 0)
+               return OPT_NOFAIL;
 
        return OPT_ERROR;
 }
@@ -858,14 +824,18 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
        int out_len = 0;
        int word_len;
        int rc = 0;
+       int got_bkupuid = 0;
+       int got_bkupgid = 0;
        int got_uid = 0;
        int got_cruid = 0;
        int got_gid = 0;
-       uid_t uid, cruid;
-       gid_t gid;
+       uid_t uid, cruid = 0, bkupuid = 0;
+       gid_t gid, bkupgid = 0;
        char *ep;
        struct passwd *pw;
        struct group *gr;
+       char *saved_username = NULL;
+       bool krb5_auth = false;
        /*
         * max 32-bit uint in decimal is 4294967295 which is 10 chars wide
         * +1 for NULL, and +1 for good measure
@@ -925,19 +895,18 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                        return EX_USAGE;
                                }
                        } else {
-                               /* domain/username%password */
-                               const int max = MAX_DOMAIN_SIZE +
-                                               MAX_USERNAME_SIZE +
-                                               MOUNT_PASSWD_SIZE + 2;
-                               if (strnlen(value, max + 1) >= max + 1) {
+                               /* domain/username%password  + NULL term. */
+                               const size_t max = MAX_DOMAIN_SIZE +
+                                                  MAX_USERNAME_SIZE +
+                                                  MOUNT_PASSWD_SIZE + 2 + 1;
+                               if (strnlen(value, max) >= max) {
                                        fprintf(stderr, "username too long\n");
                                        return EX_USAGE;
                                }
-                               rc = parse_username(value, parsed_info);
-                               if (rc) {
-                                       fprintf(stderr,
-                                               "problem parsing username\n");
-                                       return rc;
+                               saved_username = strdup(value);
+                               if (!saved_username) {
+                                       fprintf(stderr, "Unable to allocate memory!\n");
+                                       return EX_SYSERR;
                                }
                                goto nocopy;
                        }
@@ -959,9 +928,12 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
 
                case OPT_SEC:
                        if (value) {
-                               if (!strncmp(value, "none", 4) ||
-                                   !strncmp(value, "krb5", 4))
+                               if (!strncmp(value, "none", 4)) {
                                        parsed_info->got_password = 1;
+                               } else if (!strncmp(value, "krb5", 4)) {
+                                       parsed_info->got_password = 1;
+                                       krb5_auth = true;
+                               }
                        }
                        break;
 
@@ -1017,22 +989,23 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                        "invalid credential file name specified\n");
                                return EX_USAGE;
                        }
-                       rc = open_cred_file(value, parsed_info);
+                       rc = open_cred_file(value, parsed_info, &saved_username);
                        if (rc) {
                                fprintf(stderr,
                                        "error %d (%s) opening credential file %s\n",
                                        rc, strerror(rc), value);
                                return rc;
                        }
-                       break;
+                       goto nocopy;
 
                case OPT_UID:
                        if (!value || !*value)
                                goto nocopy;
 
                        got_uid = 1;
+                       errno = 0;
                        uid = strtoul(value, &ep, 10);
-                       if (errno != EINVAL && *ep == '\0')
+                       if (errno == 0 && *ep == '\0')
                                goto nocopy;
 
                        pw = getpwnam(value);
@@ -1049,8 +1022,9 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                goto nocopy;
 
                        got_cruid = 1;
+                       errno = 0;
                        cruid = strtoul(value, &ep, 10);
-                       if (errno != EINVAL && *ep == '\0')
+                       if (errno == 0 && *ep == '\0')
                                goto nocopy;
 
                        pw = getpwnam(value);
@@ -1066,8 +1040,9 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                goto nocopy;
 
                        got_gid = 1;
+                       errno = 0;
                        gid = strtoul(value, &ep, 10);
-                       if (errno != EINVAL && *ep == '\0')
+                       if (errno == 0 && *ep == '\0')
                                goto nocopy;
 
                        gr = getgrnam(value);
@@ -1168,6 +1143,47 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                        break;
                case OPT_IGNORE:
                        goto nocopy;
+               case OPT_BKUPUID:
+                       if (!value || !*value)
+                               goto nocopy;
+
+                       got_bkupuid = 1;
+                       errno = 0;
+                       bkupuid = strtoul(value, &ep, 10);
+                       if (errno == 0 && *ep == '\0')
+                               goto nocopy;
+
+                       pw = getpwnam(value);
+                       if (pw == NULL) {
+                               fprintf(stderr,
+                                       "bad user name \"%s\"\n", value);
+                               return EX_USAGE;
+                       }
+
+                       bkupuid = pw->pw_uid;
+                       goto nocopy;
+               case OPT_BKUPGID:
+                       if (!value || !*value)
+                               goto nocopy;
+
+                       got_bkupgid = 1;
+                       errno = 0;
+                       bkupgid = strtoul(value, &ep, 10);
+                       if (errno == 0 && *ep == '\0')
+                               goto nocopy;
+
+                       gr = getgrnam(value);
+                       if (gr == NULL) {
+                               fprintf(stderr,
+                                       "bad group name \"%s\"\n", value);
+                               return EX_USAGE;
+                       }
+
+                       bkupgid = gr->gr_gid;
+                       goto nocopy;
+               case OPT_NOFAIL:
+                       parsed_info->nofail = 1;
+                       goto nocopy;
                }
 
                /* check size before copying option to buffer */
@@ -1195,6 +1211,22 @@ nocopy:
                data = next_keyword;
        }
 
+       if (saved_username) {
+               if (krb5_auth) {
+                       strlcpy(parsed_info->username, saved_username,
+                               sizeof(parsed_info->username));
+                       parsed_info->got_user = 1;
+               } else {
+                       rc = parse_username(saved_username, parsed_info);
+                       free(saved_username);
+                       if (rc) {
+                               fprintf(stderr, "Unable to parse username!\n");
+                               return rc;
+                       }
+               }
+       }
+
+
        /* special-case the uid and gid */
        if (got_uid) {
                word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", uid);
@@ -1243,6 +1275,38 @@ nocopy:
                }
                snprintf(out + out_len, word_len + 5, "gid=%s", txtbuf);
        }
+       if (got_bkupuid) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupuid);
+
+               /* comma + "backupuid=" + terminating NULL == 12 */
+               if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
+               }
+
+               if (out_len) {
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
+                       out_len++;
+               }
+               snprintf(out + out_len, word_len + 11, "backupuid=%s", txtbuf);
+               out_len = strlen(out);
+       }
+       if (got_bkupgid) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupgid);
+
+               /* comma + "backkupgid=" + terminating NULL == 12 */
+               if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
+               }
+
+               if (out_len) {
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
+                       out_len++;
+               }
+               snprintf(out + out_len, word_len + 11, "backupgid=%s", txtbuf);
+       }
+
        return 0;
 }
 
@@ -1271,6 +1335,7 @@ static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info
        }
 
        /* Set up "host" and "share" pointers based on UNC format. */
+       /* TODO: Remove support for NFS syntax as of cifs-utils-6.0. */
        if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
                /*
                 * check for nfs syntax (server:/share/prepath)
@@ -1287,6 +1352,9 @@ static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info
                share++;
                if (*share == '/')
                        ++share;
+               fprintf(stderr, "WARNING: using NFS syntax for mounting CIFS "
+                       "shares is deprecated and will be removed in cifs-utils"
+                       "-6.0. Please migrate to UNC syntax.\n");
        } else {
                host = unc_name + 2;
                hostlen = strcspn(host, "/\\");
@@ -1366,6 +1434,7 @@ static struct option longopts[] = {
        {"pass", 1, NULL, 'p'},
        {"credentials", 1, NULL, 'c'},
        {"port", 1, NULL, 'P'},
+       {"sloppy", 0, NULL, 's'},
        {NULL, 0, NULL, 0}
 };
 
@@ -1416,8 +1485,7 @@ static int check_newline(const char *progname, const char *name)
 static int check_mtab(const char *progname, const char *devname,
                      const char *dir)
 {
-       if (check_newline(progname, devname) == -1 ||
-           check_newline(progname, dir) == -1)
+       if (check_newline(progname, devname) || check_newline(progname, dir))
                return EX_USAGE;
        return 0;
 }
@@ -1425,10 +1493,11 @@ static int check_mtab(const char *progname, const char *devname,
 static int
 add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstype)
 {
-       int rc = 0;
+       int rc = 0, tmprc, fd;
        uid_t uid;
        char *mount_user = NULL;
        struct mntent mountent;
+       struct stat statbuf;
        FILE *pmntfile;
        sigset_t mask, oldmask;
 
@@ -1480,6 +1549,23 @@ add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstyp
                goto add_mtab_exit;
        }
 
+       fd = fileno(pmntfile);
+       if (fd < 0) {
+               fprintf(stderr, "mntent does not appear to be valid\n");
+               unlock_mtab();
+               rc = EX_FILEIO;
+               goto add_mtab_exit;
+       }
+
+       rc = fstat(fd, &statbuf);
+       if (rc != 0) {
+               fprintf(stderr, "unable to fstat open mtab\n");
+               endmntent(pmntfile);
+               unlock_mtab();
+               rc = EX_FILEIO;
+               goto add_mtab_exit;
+       }
+
        mountent.mnt_fsname = devname;
        mountent.mnt_dir = mountpoint;
        mountent.mnt_type = (char *)(void *)fstype;
@@ -1510,10 +1596,17 @@ add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstyp
        mountent.mnt_passno = 0;
        rc = addmntent(pmntfile, &mountent);
        if (rc) {
+               int ignore __attribute__((unused));
+
                fprintf(stderr, "unable to add mount entry to mtab\n");
+               ignore = ftruncate(fd, statbuf.st_size);
+               rc = EX_FILEIO;
+       }
+       tmprc = my_endmntent(pmntfile, statbuf.st_size);
+       if (tmprc) {
+               fprintf(stderr, "error %d detected on close of mtab\n", tmprc);
                rc = EX_FILEIO;
        }
-       endmntent(pmntfile);
        unlock_mtab();
        SAFE_FREE(mountent.mnt_opts);
 add_mtab_exit:
@@ -1523,6 +1616,98 @@ add_mtab_exit:
        return rc;
 }
 
+static int
+del_mtab(char *mountpoint)
+{
+       int tmprc, rc = 0;
+       FILE *mnttmp, *mntmtab;
+       struct mntent *mountent;
+       char *mtabfile, *mtabdir, *mtabtmpfile;
+
+       mtabfile = strdup(MOUNTED);
+       mtabdir = dirname(mtabfile);
+       mtabdir = realloc(mtabdir, strlen(mtabdir) + strlen(MNT_TMP_FILE) + 2);
+       if (!mtabdir) {
+               fprintf(stderr, "del_mtab: cannot determine current mtab path");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       mtabtmpfile = strcat(mtabdir, MNT_TMP_FILE);
+       if (!mtabtmpfile) {
+               fprintf(stderr, "del_mtab: cannot allocate memory to tmp file");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       atexit(unlock_mtab);
+       rc = lock_mtab();
+       if (rc) {
+               fprintf(stderr, "del_mtab: cannot lock mtab");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       mtabtmpfile = mktemp(mtabtmpfile);
+       if (!mtabtmpfile) {
+               fprintf(stderr, "del_mtab: cannot setup tmp file destination");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       mntmtab = setmntent(MOUNTED, "r");
+       if (!mntmtab) {
+               fprintf(stderr, "del_mtab: could not update mount table\n");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       mnttmp = setmntent(mtabtmpfile, "w");
+       if (!mnttmp) {
+               fprintf(stderr, "del_mtab: could not update mount table\n");
+               endmntent(mntmtab);
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       while ((mountent = getmntent(mntmtab)) != NULL) {
+               if (!strcmp(mountent->mnt_dir, mountpoint))
+                       continue;
+               rc = addmntent(mnttmp, mountent);
+               if (rc) {
+                       fprintf(stderr, "del_mtab: unable to add mount entry to mtab\n");
+                       rc = EX_FILEIO;
+                       goto del_mtab_error;
+               }
+       }
+
+       endmntent(mntmtab);
+
+       tmprc = my_endmntent(mnttmp, 0);
+       if (tmprc) {
+               fprintf(stderr, "del_mtab: error %d detected on close of tmp file\n", tmprc);
+               rc = EX_FILEIO;
+               goto del_mtab_error;
+       }
+
+       if (rename(mtabtmpfile, MOUNTED)) {
+               fprintf(stderr, "del_mtab: error %d when renaming mtab in place\n", errno);
+               rc = EX_FILEIO;
+               goto del_mtab_error;
+       }
+
+del_mtab_exit:
+       unlock_mtab();
+       free(mtabdir);
+       return rc;
+
+del_mtab_error:
+       if (unlink(mtabtmpfile))
+               fprintf(stderr, "del_mtab: failed to delete tmp file - %s\n",
+                               strerror(errno));
+       goto del_mtab_exit;
+}
+
 /* have the child drop root privileges */
 static int
 drop_child_privs(void)
@@ -1551,6 +1736,66 @@ drop_child_privs(void)
        return 0;
 }
 
+/*
+ * If systemd is running and /bin/systemd-ask-password --
+ * is available, then use that else fallback on getpass(..)
+ *
+ * Returns: @input or NULL on error
+ */
+static char*
+get_password(const char *prompt, char *input, int capacity)
+{
+#ifdef ENABLE_SYSTEMD
+       int is_systemd_running;
+       struct stat a, b;
+
+       /* We simply test whether the systemd cgroup hierarchy is
+        * mounted */
+       is_systemd_running = (lstat("/sys/fs/cgroup", &a) == 0)
+               && (lstat("/sys/fs/cgroup/systemd", &b) == 0)
+               && (a.st_dev != b.st_dev);
+
+       if (is_systemd_running) {
+               char *cmd, *ret;
+               FILE *ask_pass_fp = NULL;
+
+               cmd = ret = NULL;
+               if (asprintf(&cmd, "/bin/systemd-ask-password \"%s\"", prompt) >= 0) {
+                       ask_pass_fp = popen (cmd, "re");
+                       free (cmd);
+               }
+
+               if (ask_pass_fp) {
+                       ret = fgets(input, capacity, ask_pass_fp);
+                       pclose(ask_pass_fp);
+               }
+
+               if (ret) {
+                       int len = strlen(input);
+                       if (input[len - 1] == '\n')
+                               input[len - 1] = '\0';
+                       return input;
+               }
+       }
+#endif
+
+       /*
+        * Falling back to getpass(..)
+        * getpass is obsolete, but there's apparently nothing that replaces it
+        */
+       char *tmp_pass = getpass(prompt);
+       if (!tmp_pass)
+               return NULL;
+
+       strncpy(input, tmp_pass, capacity - 1);
+       input[capacity - 1] = '\0';
+
+       /* zero-out the static buffer */
+       memset(tmp_pass, 0, strlen(tmp_pass));
+
+       return input;
+}
+
 static int
 assemble_mountinfo(struct parsed_mount_info *parsed_info,
                   const char *thisprogram, const char *mountpoint,
@@ -1632,31 +1877,39 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info,
        }
 
        if (!parsed_info->got_password) {
-               /* getpass is obsolete, but there's apparently nothing that replaces it */
-               char *tmp_pass = getpass("Password: ");
-               if (!tmp_pass) {
+               char tmp_pass[MOUNT_PASSWD_SIZE + 1];
+               char *prompt = NULL;
+
+               if(asprintf(&prompt, "Password for %s@%s: ", parsed_info->username, orig_dev) < 0)
+                       prompt = NULL;
+
+               if (get_password(prompt ? prompt : "Password: ", tmp_pass, MOUNT_PASSWD_SIZE + 1)) {
+                       rc = set_password(parsed_info, tmp_pass);
+               } else {
                        fprintf(stderr, "Error reading password, exiting\n");
                        rc = EX_SYSERR;
-                       goto assemble_exit;
                }
-               rc = set_password(parsed_info, tmp_pass);
+
+               free(prompt);
                if (rc)
                        goto assemble_exit;
        }
 
-       /* copy in ver= string. It's not really needed, but what the hell */
-       strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
-       strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
-
        /* copy in user= string */
        if (parsed_info->got_user) {
-               strlcat(parsed_info->options, ",user=",
+               if (*parsed_info->options)
+                       strlcat(parsed_info->options, ",",
+                               sizeof(parsed_info->options));
+               strlcat(parsed_info->options, "user=",
                        sizeof(parsed_info->options));
                strlcat(parsed_info->options, parsed_info->username,
                        sizeof(parsed_info->options));
        }
 
        if (*parsed_info->domain) {
+               if (*parsed_info->options)
+                       strlcat(parsed_info->options, ",",
+                               sizeof(parsed_info->options));
                strlcat(parsed_info->options, ",domain=",
                        sizeof(parsed_info->options));
                strlcat(parsed_info->options, parsed_info->domain,
@@ -1667,6 +1920,68 @@ assemble_exit:
        return rc;
 }
 
+/*
+ * chdir() into the mountpoint and determine "realpath". We assume here that
+ * "mountpoint" is a statically allocated string and does not need to be freed.
+ */
+static int
+acquire_mountpoint(char **mountpointp)
+{
+       int rc, dacrc;
+       uid_t realuid, oldfsuid;
+       gid_t oldfsgid;
+       char *mountpoint;
+
+       /*
+        * Acquire the necessary privileges to chdir to the mountpoint. If
+        * the real uid is root, then we reacquire CAP_DAC_READ_SEARCH. If
+        * it's not, then we change the fsuid to the real uid to ensure that
+        * the mounting user actually has access to the mountpoint.
+        *
+        * The mount(8) manpage does not state that users must be able to
+        * chdir into the mountpoint in order to mount onto it, but if we
+        * allow that, then an unprivileged user could use this program to
+        * "probe" into directories to which he does not have access.
+        */
+       realuid = getuid();
+       if (realuid == 0) {
+               dacrc = toggle_dac_capability(0, 1);
+               if (dacrc)
+                       return dacrc;
+       } else {
+               oldfsuid = setfsuid(realuid);
+               oldfsgid = setfsgid(getgid());
+       }
+
+       rc = chdir(*mountpointp);
+       if (rc) {
+               fprintf(stderr, "Couldn't chdir to %s: %s\n", *mountpointp,
+                       strerror(errno));
+               rc = EX_USAGE;
+               goto restore_privs;
+       }
+
+       mountpoint = realpath(".", NULL);
+       if (!mountpoint) {
+               fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
+                       *mountpointp, strerror(errno));
+               rc = EX_SYSERR;
+       }
+
+       *mountpointp = mountpoint;
+restore_privs:
+       if (realuid == 0) {
+               dacrc = toggle_dac_capability(0, 0);
+               if (dacrc)
+                       rc = rc ? rc : dacrc;
+       } else {
+               uid_t __attribute__((unused)) uignore = setfsuid(oldfsuid);
+               gid_t __attribute__((unused)) gignore = setfsgid(oldfsgid);
+       }
+
+       return rc;
+}
+
 int main(int argc, char **argv)
 {
        int c;
@@ -1677,10 +1992,10 @@ int main(int argc, char **argv)
        char *currentaddress, *nextaddress;
        int rc = 0;
        int already_uppercased = 0;
+       int sloppy = 0;
        size_t options_size = MAX_OPTIONS_LEN;
        struct parsed_mount_info *parsed_info = NULL;
        pid_t pid;
-       const char *fstype;
 
        rc = check_setuid();
        if (rc)
@@ -1714,7 +2029,7 @@ int main(int argc, char **argv)
        }
 
        /* add sharename in opts string as unc= parm */
-       while ((c = getopt_long(argc, argv, "?fhno:rvVw",
+       while ((c = getopt_long(argc, argv, "?fhno:rsvVw",
                                longopts, NULL)) != -1) {
                switch (c) {
                case '?':
@@ -1746,6 +2061,9 @@ int main(int argc, char **argv)
                case 'f':
                        ++parsed_info->fakemnt;
                        break;
+               case 's':
+                       ++sloppy;
+                       break;
                default:
                        fprintf(stderr, "unknown command-line option: %c\n", c);
                        rc = mount_usage(stderr);
@@ -1762,25 +2080,7 @@ int main(int argc, char **argv)
        mountpoint = argv[optind + 1];
 
        /* chdir into mountpoint as soon as possible */
-       rc = toggle_dac_capability(0, 1);
-       if (rc)
-               return rc;
-       rc = chdir(mountpoint);
-       if (rc) {
-               fprintf(stderr, "Couldn't chdir to %s: %s\n", mountpoint,
-                       strerror(errno));
-               rc = EX_USAGE;
-               goto mount_exit;
-       }
-
-       mountpoint = realpath(".", NULL);
-       if (!mountpoint) {
-               fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
-                       mountpoint, strerror(errno));
-               rc = EX_SYSERR;
-               goto mount_exit;
-       }
-       rc = toggle_dac_capability(0, 0);
+       rc = acquire_mountpoint(&mountpoint);
        if (rc)
                return rc;
 
@@ -1830,7 +2130,7 @@ int main(int argc, char **argv)
 mount_retry:
        if (!currentaddress) {
                fprintf(stderr, "Unable to find suitable address.\n");
-               rc = EX_SYSERR;
+               rc = parsed_info->nofail ? 0 : EX_FAIL;
                goto mount_exit;
        }
        strlcpy(options, "ip=", options_size);
@@ -1851,6 +2151,9 @@ mount_retry:
                strlcat(options, parsed_info->prefix, options_size);
        }
 
+       if (sloppy)
+               strlcat(options, ",sloppy", options_size);
+
        if (parsed_info->verboseflag)
                fprintf(stderr, "%s kernel mount options: %s",
                        thisprogram, options);
@@ -1873,14 +2176,9 @@ mount_retry:
        if (rc)
                goto mount_exit;
 
-       if (strcmp(thisprogram, "mount.smb2") == 0)
-               fstype = smb2_fstype;
-       else
-               fstype = cifs_fstype;
-
        if (!parsed_info->fakemnt) {
                toggle_dac_capability(0, 1);
-               rc = mount(orig_dev, ".", fstype, parsed_info->flags, options);
+               rc = mount(orig_dev, ".", cifs_fstype, parsed_info->flags, options);
                toggle_dac_capability(0, 0);
                if (rc == 0)
                        goto do_mtab;
@@ -1897,7 +2195,7 @@ mount_retry:
                        goto mount_retry;
                case ENODEV:
                        fprintf(stderr,
-                               "mount error: %s filesystem not supported by the system\n", fstype);
+                               "mount error: %s filesystem not supported by the system\n", cifs_fstype);
                        break;
                case ENXIO:
                        if (!already_uppercased &&
@@ -1920,8 +2218,15 @@ mount_retry:
        }
 
 do_mtab:
-       if (!parsed_info->nomtab && !mtab_unusable())
-               rc = add_mtab(orig_dev, mountpoint, parsed_info->flags, fstype);
+       if (!parsed_info->nomtab && !mtab_unusable()) {
+               if (parsed_info->flags & MS_REMOUNT) {
+                       rc = del_mtab(mountpoint);
+                       if (rc)
+                               goto mount_exit;
+               }
+
+               rc = add_mtab(orig_dev, mountpoint, parsed_info->flags, cifs_fstype);
+       }
 
 mount_exit:
        if (parsed_info) {