CVE-2020-14342: mount.cifs: fix shell command injection
[cifs-utils.git] / mount.cifs.c
index 869af35f0cab799ffc4a9b0c38859c3b3d09e5d5..4feb397a1439ca3e19491d4d77b9c09f7f90fce1 100644 (file)
@@ -43,9 +43,9 @@
 #include <limits.h>
 #include <paths.h>
 #include <libgen.h>
+#include <time.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 */
 #define MTAB_OPTIONS_LEN 220
 
 /*
- * Maximum length of "share" portion of a UNC. I have no idea if this is at
- * all valid. According to MSDN, the typical max length of any component is
- * 255, so use that here.
+ * Max share name, username, password and domain sizes match the kernel's
+ * allowances for these string sizes which in turn match Microsoft's
+ * documentation.
  */
+
+/* Max length of the share name portion of a UNC. Share names over 80
+ * characters cannot be accessed via commandline in Windows 2000/XP. */
 #define MAX_SHARE_LEN 256
 
-/* max length of username (somewhat made up here) */
-#define MAX_USERNAME_SIZE 32
+/* Max user name length. */
+#define MAX_USERNAME_SIZE 256
 
-#ifndef SAFE_FREE
-#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
-#endif
+/* Max domain size. */
+#define MAX_DOMAIN_SIZE 256
 
-#define MOUNT_PASSWD_SIZE 128
-#define MAX_DOMAIN_SIZE 64
+/* Max password size. */
+#define MOUNT_PASSWD_SIZE 512
 
 /*
  * mount.cifs has been the subject of many "security" bugs that have arisen
 #define OPT_BKUPUID    30
 #define OPT_BKUPGID    31
 #define OPT_NOFAIL     32
+#define OPT_SNAPSHOT   33
 
 #define MNT_TMP_FILE "/.mtab.cifs.XXXXXX"
 
-/* struct for holding parsed mount info for use by privleged process */
+#define GMT_NAME_LEN 24 /* length of a @GMT- name */
+#define GMT_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+
+#define NTFS_TIME_OFFSET ((unsigned long long)(369*365 + 89) * 24 * 3600 * 10000000)
+
+/* struct for holding parsed mount info for use by privileged process */
 struct parsed_mount_info {
        unsigned long flags;
        char host[NI_MAXHOST + 1];
@@ -180,12 +188,13 @@ struct parsed_mount_info {
        unsigned int nomtab:1;
        unsigned int verboseflag:1;
        unsigned int nofail:1;
+       unsigned int got_domain:1;
 };
 
-const char *thisprogram;
-const char *cifs_fstype = "cifs";
+static const char *thisprogram;
+static const char *cifs_fstype;
 
-static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
+static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info, const char *progname);
 
 static int check_setuid(void)
 {
@@ -197,7 +206,7 @@ static int check_setuid(void)
 
 #if CIFS_DISABLE_SETUID_CAPABILITY
        if (getuid() && !geteuid()) {
-               printf("This mount.cifs program has been built with the "
+               printf("This program has been built with the "
                       "ability to run as a setuid root program disabled.\n");
                return EX_USAGE;
        }
@@ -238,7 +247,6 @@ check_fstab(const char *progname, const char *mountpoint, const char *devname,
         * set of options. We don't want to trust what the user
         * gave us, so just take whatever is in /etc/fstab.
         */
-       free(*options);
        *options = strdup(mnt->mnt_opts);
        return 0;
 }
@@ -265,25 +273,35 @@ static int mount_usage(FILE * stream)
        fprintf(stream,
                "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
        fprintf(stream,
-               "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
+               "\n\tnoserverino,mapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
+       fprintf(stream,
+               "\n\tcache=<strict|none|loose>,nounix,cifsacl,sec=<authentication mechanism>,");
        fprintf(stream,
-               "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign,fsc");
+               "\n\tsign,seal,fsc,snapshot=<token|time>,nosharesock,persistenthandles,");
+       fprintf(stream,
+               "\n\tresilienthandles,rdma,vers=<smb_dialect>,cruid");
        fprintf(stream,
                "\n\nOptions not needed for servers supporting CIFS Unix extensions");
        fprintf(stream,
                "\n\t(e.g. unneeded for mounts to most Samba versions):");
        fprintf(stream,
-               "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu");
+               "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu,");
+       fprintf(stream,
+               "\n\tmfsymlinks,idsfromsid");
        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,nouser_xattr,netbiosname=<OUR_RFC1001NAME>,hard,soft,intr,");
        fprintf(stream,
-               "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl");
+               "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl,");
+       fprintf(stream,
+               "\n\techo_interval=<seconds>,actimeo=<seconds>,max_credits=<credits>,");
+       fprintf(stream,
+               "\n\tbsize=<size>");
        fprintf(stream,
                "\n\nOptions are described in more detail in the manual page");
-       fprintf(stream, "\n\tman 8 mount.cifs\n");
+       fprintf(stream, "\n\tman 8 %s\n", thisprogram);
        fprintf(stream, "\nTo display the version number of the mount helper:");
        fprintf(stream, "\n\t%s -V\n", thisprogram);
 
@@ -316,65 +334,6 @@ static int set_password(struct parsed_mount_info *parsed_info, const char *src)
        return 0;
 }
 
-/*
- * Parse a username string into parsed_mount_info fields. The format is:
- *
- * DOMAIN\username%password
- *
- * ...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;
-               *password = '\0';
-       }
-
-       /* everything after first '/' or '\' is a username */
-       user = strchr(rawuser, '/');
-       if (!user)
-               user = strchr(rawuser, '\\');
-
-       /* everything before that slash is a domain */
-       if (user) {
-               warn = true;
-               slash = *user;
-               *user = '\0';
-               strlcpy(parsed_info->domain, rawuser,
-                       sizeof(parsed_info->domain));
-               *(user++) = slash;
-       } else {
-               user = rawuser;
-       }
-
-       strlcpy(parsed_info->username, user, sizeof(parsed_info->username));
-       parsed_info->got_user = 1;
-       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;
-}
-
 #ifdef HAVE_LIBCAP_NG
 static int
 drop_capabilities(int parent)
@@ -590,8 +549,7 @@ parsing_err:
 }
 
 static int open_cred_file(char *file_name,
-                       struct parsed_mount_info *parsed_info,
-                       char **saved_username)
+                       struct parsed_mount_info *parsed_info)
 {
        char *line_buf = NULL;
        char *temp_val = NULL;
@@ -640,11 +598,9 @@ static int open_cred_file(char *file_name,
                /* parse next token */
                switch (parse_cred_line(line_buf + i, &temp_val)) {
                case CRED_USER:
-                       *saved_username = strdup(temp_val);
-                       if (!*saved_username) {
-                               i = EX_SYSERR;
-                               goto return_i;
-                       }
+                       strlcpy(parsed_info->username, temp_val,
+                               sizeof(parsed_info->username));
+                       parsed_info->got_user = 1;
                        break;
                case CRED_PASS:
                        i = set_password(parsed_info, temp_val);
@@ -674,13 +630,13 @@ return_i:
        /* make sure passwords are scrubbed from memory */
        if (line_buf != NULL)
                memset(line_buf, 0, line_buf_size);
-       SAFE_FREE(line_buf);
+       free(line_buf);
        return i;
 }
 
 static int
 get_password_from_file(int file_descript, char *filename,
-                      struct parsed_mount_info *parsed_info)
+                      struct parsed_mount_info *parsed_info, const char *program)
 {
        int rc = 0;
        char buf[sizeof(parsed_info->password) + 1];
@@ -693,8 +649,8 @@ get_password_from_file(int file_descript, char *filename,
                rc = access(filename, R_OK);
                if (rc) {
                        fprintf(stderr,
-                               "mount.cifs failed: access check of %s failed: %s\n",
-                               filename, strerror(errno));
+                               "%s failed: access check of %s failed: %s\n",
+                               program, filename, strerror(errno));
                        toggle_dac_capability(0, 0);
                        return EX_SYSERR;
                }
@@ -702,8 +658,8 @@ get_password_from_file(int file_descript, char *filename,
                file_descript = open(filename, O_RDONLY);
                if (file_descript < 0) {
                        fprintf(stderr,
-                               "mount.cifs failed. %s attempting to open password file %s\n",
-                               strerror(errno), filename);
+                               "%s failed. %s attempting to open password file %s\n",
+                               program, strerror(errno), filename);
                        toggle_dac_capability(0, 0);
                        return EX_SYSERR;
                }
@@ -719,8 +675,8 @@ get_password_from_file(int file_descript, char *filename,
        rc = read(file_descript, buf, sizeof(buf) - 1);
        if (rc < 0) {
                fprintf(stderr,
-                       "mount.cifs failed. Error %s reading password file\n",
-                       strerror(errno));
+                       "%s failed. Error %s reading password file\n",
+                       program, strerror(errno));
                rc = EX_SYSERR;
                goto get_pw_exit;
        }
@@ -741,74 +697,90 @@ static int parse_opt_token(const char *token)
        if (token == NULL)
                return OPT_ERROR;
 
-       if (strncmp(token, "users", 5) == 0)
+       /*
+        * token is NULL terminated and contains exactly the
+        * keyword so we can match exactly
+        */
+       if (strcmp(token, "users") == 0)
                return OPT_USERS;
-       if (strncmp(token, "user_xattr", 10) == 0)
+       if (strcmp(token, "user_xattr") == 0)
                return OPT_USER_XATTR;
-       if (strncmp(token, "user", 4) == 0)
+       if (strcmp(token, "user") == 0 ||
+               strcmp(token, "username") == 0)
                return OPT_USER;
-       if (strncmp(token, "pass", 4) == 0)
+       if (strcmp(token, "pass") == 0 ||
+               strcmp(token, "password") == 0)
                return OPT_PASS;
-       if (strncmp(token, "sec", 3) == 0)
+       if (strcmp(token, "sec") == 0)
                return OPT_SEC;
-       if (strncmp(token, "ip", 2) == 0)
+       if (strcmp(token, "ip") == 0 ||
+               strcmp(token, "addr") == 0)
                return OPT_IP;
-       if (strncmp(token, "unc", 3) == 0 ||
-               strncmp(token, "target", 6) == 0 ||
-               strncmp(token, "path", 4) == 0)
+       if (strcmp(token, "unc") == 0 ||
+               strcmp(token, "target") == 0 ||
+               strcmp(token, "path") == 0)
                return OPT_UNC;
-       if (strncmp(token, "dom", 3) == 0 || strncmp(token, "workg", 5) == 0)
+       if (strcmp(token, "dom") == 0 ||
+               strcmp(token, "domain") == 0 ||
+               strcmp(token, "workgroup") == 0)
                return OPT_DOM;
-       if (strncmp(token, "cred", 4) == 0)
+       if (strcmp(token, "cred") == 0 || /* undocumented */
+               strcmp(token, "credentials") == 0)
                return OPT_CRED;
-       if (strncmp(token, "uid", 3) == 0)
+       if (strcmp(token, "uid") == 0)
                return OPT_UID;
-       if (strncmp(token, "cruid", 5) == 0)
+       if (strcmp(token, "cruid") == 0)
                return OPT_CRUID;
-       if (strncmp(token, "gid", 3) == 0)
+       if (strcmp(token, "gid") == 0)
                return OPT_GID;
-       if (strncmp(token, "fmask", 5) == 0)
+       if (strcmp(token, "fmask") == 0)
                return OPT_FMASK;
-       if (strncmp(token, "file_mode", 9) == 0)
+       if (strcmp(token, "file_mode") == 0)
                return OPT_FILE_MODE;
-       if (strncmp(token, "dmask", 5) == 0)
+       if (strcmp(token, "dmask") == 0)
                return OPT_DMASK;
-       if (strncmp(token, "dir_mode", 4) == 0 || strncmp(token, "dirm", 4) == 0)
+       if (strcmp(token, "dir_mode") == 0 ||
+               strcmp(token, "dirm") == 0)
                return OPT_DIR_MODE;
-       if (strncmp(token, "nosuid", 6) == 0)
+       if (strcmp(token, "nosuid") == 0)
                return OPT_NO_SUID;
-       if (strncmp(token, "suid", 4) == 0)
+       if (strcmp(token, "suid") == 0)
                return OPT_SUID;
-       if (strncmp(token, "nodev", 5) == 0)
+       if (strcmp(token, "nodev") == 0)
                return OPT_NO_DEV;
-       if (strncmp(token, "nobrl", 5) == 0 || strncmp(token, "nolock", 6) == 0)
+       if (strcmp(token, "nobrl") == 0 ||
+               strcmp(token, "nolock") == 0)
                return OPT_NO_LOCK;
-       if (strncmp(token, "mand", 4) == 0)
+       if (strcmp(token, "mand") == 0)
                return OPT_MAND;
-       if (strncmp(token, "nomand", 6) == 0)
+       if (strcmp(token, "nomand") == 0)
                return OPT_NOMAND;
-       if (strncmp(token, "dev", 3) == 0)
+       if (strcmp(token, "dev") == 0)
                return OPT_DEV;
-       if (strncmp(token, "noexec", 6) == 0)
+       if (strcmp(token, "noexec") == 0)
                return OPT_NO_EXEC;
-       if (strncmp(token, "exec", 4) == 0)
+       if (strcmp(token, "exec") == 0)
                return OPT_EXEC;
-       if (strncmp(token, "guest", 5) == 0)
+       if (strcmp(token, "guest") == 0)
                return OPT_GUEST;
-       if (strncmp(token, "ro", 2) == 0)
+       if (strcmp(token, "ro") == 0)
                return OPT_RO;
-       if (strncmp(token, "rw", 2) == 0 && strlen(token) == 2)
+       if (strcmp(token, "rw") == 0)
                return OPT_RW;
-       if (strncmp(token, "remount", 7) == 0)
+       if (strcmp(token, "remount") == 0)
                return OPT_REMOUNT;
-       if (strncmp(token, "_netdev", 7) == 0)
+       if (strcmp(token, "_netdev") == 0)
                return OPT_IGNORE;
-       if (strncmp(token, "backupuid", 9) == 0)
+       if (strcmp(token, "backupuid") == 0)
                return OPT_BKUPUID;
-       if (strncmp(token, "backupgid", 9) == 0)
+       if (strcmp(token, "backupgid") == 0)
                return OPT_BKUPGID;
-       if (strncmp(token, "nofail", 6) == 0)
+       if (strcmp(token, "nofail") == 0)
                return OPT_NOFAIL;
+       if (strncmp(token, "x-", 2) == 0)
+               return OPT_IGNORE;
+       if (strncmp(token, "snapshot", 8) == 0)
+               return OPT_SNAPSHOT;
 
        return OPT_ERROR;
 }
@@ -829,18 +801,19 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
        int got_uid = 0;
        int got_cruid = 0;
        int got_gid = 0;
+       int got_snapshot = 0;
        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
+        * max 64-bit uint in decimal is 18446744073709551615 which is 20 chars
+        * wide +1 for NULL, and +1 for good measure
         */
-       char txtbuf[12];
+       char txtbuf[22];
+       unsigned long long snapshot;
+       struct tm tm;
 
        /* make sure we're starting from beginning */
        out[0] = '\0';
@@ -895,19 +868,9 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                        return EX_USAGE;
                                }
                        } else {
-                               /* 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;
-                               }
-                               saved_username = strdup(value);
-                               if (!saved_username) {
-                                       fprintf(stderr, "Unable to allocate memory!\n");
-                                       return EX_SYSERR;
-                               }
+                               strlcpy(parsed_info->username, value,
+                                       sizeof(parsed_info->username));
+                               parsed_info->got_user = 1;
                                goto nocopy;
                        }
 
@@ -928,12 +891,9 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
 
                case OPT_SEC:
                        if (value) {
-                               if (!strncmp(value, "none", 4)) {
+                               if (!strncmp(value, "none", 4) ||
+                                   !strncmp(value, "krb5", 4))
                                        parsed_info->got_password = 1;
-                               } else if (!strncmp(value, "krb5", 4)) {
-                                       parsed_info->got_password = 1;
-                                       krb5_auth = true;
-                               }
                        }
                        break;
 
@@ -963,16 +923,21 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                        "invalid path to network resource\n");
                                return EX_USAGE;
                        }
-                       rc = parse_unc(value, parsed_info);
+                       rc = parse_unc(value, parsed_info, thisprogram);
                        if (rc)
                                return rc;
                        break;
 
                /* dom || workgroup */
                case OPT_DOM:
-                       if (!value || !*value) {
-                               fprintf(stderr, "CIFS: invalid domain name\n");
-                               return EX_USAGE;
+                       if (!value) {
+                               /*
+                                * An empty domain has been passed
+                                */
+                               /* not necessary but better safe than.. */
+                               parsed_info->domain[0] = '\0';
+                               parsed_info->got_domain = 1;
+                               goto nocopy;
                        }
                        if (strnlen(value, sizeof(parsed_info->domain)) >=
                            sizeof(parsed_info->domain)) {
@@ -989,7 +954,7 @@ 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, &saved_username);
+                       rc = open_cred_file(value, parsed_info);
                        if (rc) {
                                fprintf(stderr,
                                        "error %d (%s) opening credential file %s\n",
@@ -1052,12 +1017,13 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
 
                        fprintf(stderr, "bad option: gid=\"%s\"\n", value);
                        return EX_USAGE;
-               /* fmask fall through to file_mode */
+               /* fmask falls through to file_mode */
                case OPT_FMASK:
                        fprintf(stderr,
                                "WARNING: CIFS mount option 'fmask' is\
                                 deprecated. Use 'file_mode' instead.\n");
                        data = "file_mode";     /* BB fix this */
+                       /* Fallthrough */
                case OPT_FILE_MODE:
                        if (!value || !*value) {
                                fprintf(stderr,
@@ -1078,6 +1044,7 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                                "WARNING: CIFS mount option 'dmask' is\
                                 deprecated. Use 'dir_mode' instead.\n");
                        data = "dir_mode";
+                       /* Fallthrough */
                case OPT_DIR_MODE:
                        if (!value || !*value) {
                                fprintf(stderr,
@@ -1174,6 +1141,19 @@ parse_options(const char *data, struct parsed_mount_info *parsed_info)
                case OPT_NOFAIL:
                        parsed_info->nofail = 1;
                        goto nocopy;
+               case OPT_SNAPSHOT:
+                       if (!value || !*value)
+                               goto nocopy;
+                       if (strncmp(value, "@GMT-", 5))
+                               break;
+                       if ((strlen(value) != GMT_NAME_LEN) ||
+                           (strptime(value, GMT_FORMAT, &tm) == NULL)) {
+                               fprintf(stderr, "bad snapshot token\n");
+                               return EX_USAGE;
+                       }
+                       snapshot = timegm(&tm) * 10000000 + NTFS_TIME_OFFSET;
+                       got_snapshot = 1;
+                       goto nocopy;
                }
 
                /* check size before copying option to buffer */
@@ -1201,21 +1181,6 @@ 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) {
@@ -1237,7 +1202,7 @@ nocopy:
        if (got_cruid) {
                word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", cruid);
 
-               /* comma + "cruid=" + terminating NULL == 6 */
+               /* comma + "cruid=" + terminating NULL == 8 */
                if (out_len + word_len + 8 > MAX_OPTIONS_LEN) {
                        fprintf(stderr, "Options string too long\n");
                        return EX_USAGE;
@@ -1284,7 +1249,7 @@ nocopy:
        if (got_bkupgid) {
                word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupgid);
 
-               /* comma + "backkupgid=" + terminating NULL == 12 */
+               /* comma + "backupgid=" + terminating NULL == 12 */
                if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
                        fprintf(stderr, "Options string too long\n");
                        return EX_USAGE;
@@ -1296,11 +1261,26 @@ nocopy:
                }
                snprintf(out + out_len, word_len + 11, "backupgid=%s", txtbuf);
        }
+       if (got_snapshot) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%llu", snapshot);
+
+               /* comma + "snapshot=" + terminating NULL == 11 */
+               if (out_len + word_len + 11 > 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, "snapshot=%s", txtbuf);
+       }
 
        return 0;
 }
 
-static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
+static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info, const char *progname)
 {
        int length = strnlen(unc_name, MAX_UNC_LEN);
        const char *host, *share, *prepath;
@@ -1324,45 +1304,27 @@ static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info
                return EX_USAGE;
        }
 
-       /* 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)
-                *
-                * FIXME: IPv6 addresses?
-                */
-               host = unc_name;
-               share = strchr(host, ':');
-               if (!share) {
-                       fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
-                       return EX_USAGE;
-               }
-               hostlen = share - host;
-               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, "/\\");
-               if (!hostlen) {
-                       fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
-                       return EX_USAGE;
-               }
-               share = host + hostlen + 1;
+               fprintf(stderr, "%s: bad UNC (%s)\n", progname, unc_name);
+               return EX_USAGE;
        }
 
+       host = unc_name + 2;
+       hostlen = strcspn(host, "/\\");
+       if (!hostlen) {
+               fprintf(stderr, "%s: bad UNC (%s)\n", progname, unc_name);
+               return EX_USAGE;
+       }
+       share = host + hostlen + 1;
+
        if (hostlen + 1 > sizeof(parsed_info->host)) {
-               fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
+               fprintf(stderr, "%s: host portion of UNC too long\n", progname);
                return EX_USAGE;
        }
 
        sharelen = strcspn(share, "/\\");
        if (sharelen + 1 > sizeof(parsed_info->share)) {
-               fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
+               fprintf(stderr, "%s: share portion of UNC too long\n", progname);
                return EX_USAGE;
        }
 
@@ -1373,7 +1335,7 @@ static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info
        prepathlen = strlen(prepath);
 
        if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
-               fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
+               fprintf(stderr, "%s: UNC prefixpath too long\n", progname);
                return EX_USAGE;
        }
 
@@ -1385,7 +1347,7 @@ static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info
        return 0;
 }
 
-static int get_pw_from_env(struct parsed_mount_info *parsed_info)
+static int get_pw_from_env(struct parsed_mount_info *parsed_info, const char *program)
 {
        int rc = 0;
 
@@ -1393,10 +1355,10 @@ static int get_pw_from_env(struct parsed_mount_info *parsed_info)
                rc = set_password(parsed_info, getenv("PASSWD"));
        else if (getenv("PASSWD_FD"))
                rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
-                                           parsed_info);
+                                           parsed_info, program);
        else if (getenv("PASSWD_FILE"))
                rc = get_password_from_file(0, getenv("PASSWD_FILE"),
-                                           parsed_info);
+                                           parsed_info, program);
 
        return rc;
 }
@@ -1446,9 +1408,9 @@ static int uppercase_string(char *string)
        return 1;
 }
 
-static void print_cifs_mount_version(void)
+static void print_cifs_mount_version(const char *progname)
 {
-       printf("mount.cifs version: %s\n", VERSION);
+       printf("%s version: %s\n", progname, VERSION);
 }
 
 /*
@@ -1598,7 +1560,7 @@ add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstyp
                rc = EX_FILEIO;
        }
        unlock_mtab();
-       SAFE_FREE(mountent.mnt_opts);
+       free(mountent.mnt_opts);
 add_mtab_exit:
        toggle_dac_capability(1, 0);
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
@@ -1609,23 +1571,29 @@ add_mtab_exit:
 static int
 del_mtab(char *mountpoint)
 {
-       int tmprc, rc = 0;
+       int len, tmprc, rc = 0;
        FILE *mnttmp, *mntmtab;
        struct mntent *mountent;
-       char *mtabfile, *mtabdir, *mtabtmpfile;
+       char *mtabfile, *mtabdir, *mtabtmpfile = NULL;
 
        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");
+       if (!mtabfile) {
+               fprintf(stderr, "del_mtab: cannot strdup MOUNTED\n");
                rc = EX_FILEIO;
                goto del_mtab_exit;
        }
 
-       mtabtmpfile = strcat(mtabdir, MNT_TMP_FILE);
+       mtabdir = dirname(mtabfile);
+       len = strlen(mtabdir) + strlen(MNT_TMP_FILE);
+       mtabtmpfile = malloc(len + 1);
        if (!mtabtmpfile) {
-               fprintf(stderr, "del_mtab: cannot allocate memory to tmp file");
+               fprintf(stderr, "del_mtab: cannot allocate memory to tmp file\n");
+               rc = EX_FILEIO;
+               goto del_mtab_exit;
+       }
+
+       if (sprintf(mtabtmpfile, "%s%s", mtabdir, MNT_TMP_FILE) != len) {
+               fprintf(stderr, "del_mtab: error writing new string\n");
                rc = EX_FILEIO;
                goto del_mtab_exit;
        }
@@ -1633,14 +1601,14 @@ del_mtab(char *mountpoint)
        atexit(unlock_mtab);
        rc = lock_mtab();
        if (rc) {
-               fprintf(stderr, "del_mtab: cannot lock mtab");
+               fprintf(stderr, "del_mtab: cannot lock mtab\n");
                rc = EX_FILEIO;
                goto del_mtab_exit;
        }
 
        mtabtmpfile = mktemp(mtabtmpfile);
        if (!mtabtmpfile) {
-               fprintf(stderr, "del_mtab: cannot setup tmp file destination");
+               fprintf(stderr, "del_mtab: cannot setup tmp file destination\n");
                rc = EX_FILEIO;
                goto del_mtab_exit;
        }
@@ -1688,7 +1656,8 @@ del_mtab(char *mountpoint)
 
 del_mtab_exit:
        unlock_mtab();
-       free(mtabdir);
+       free(mtabtmpfile);
+       free(mtabfile);
        return rc;
 
 del_mtab_error:
@@ -1726,8 +1695,75 @@ drop_child_privs(void)
        return 0;
 }
 
+#ifdef ENABLE_SYSTEMD
+static int get_passwd_by_systemd(const char *prompt, char *input, int capacity)
+{
+       int fd[2];
+       pid_t pid;
+       int offs = 0;
+       int rc = 1;
+
+       if (pipe(fd) == -1) {
+               fprintf(stderr, "Failed to create pipe: %s\n", strerror(errno));
+               return 1;
+       }
+
+       pid = fork();
+       if (pid == -1) {
+               fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
+               close(fd[0]);
+               close(fd[1]);
+               return 1;
+       }
+       if (pid == 0) {
+               close(fd[0]);
+               dup2(fd[1], STDOUT_FILENO);
+               if (execlp("systemd-ask-password", "systemd-ask-password", prompt, NULL) == -1) {
+                       fprintf(stderr, "Failed to execute systemd-ask-password: %s\n",
+                               strerror(errno));
+               }
+               exit(1);
+       }
+
+       close(fd[1]);
+       for (;;) {
+               if (offs+1 >= capacity) {
+                       fprintf(stderr, "Password too long.\n");
+                       kill(pid, SIGTERM);
+                       rc = 1;
+                       break;
+               }
+               rc = read(fd[0], input + offs, capacity - offs);
+               if (rc == -1) {
+                       fprintf(stderr, "Failed to read from pipe: %s\n", strerror(errno));
+                       rc = 1;
+                       break;
+               }
+               if (!rc)
+                       break;
+               offs += rc;
+               input[offs] = '\0';
+       }
+       if (wait(&rc) == -1) {
+               fprintf(stderr, "Failed to wait child: %s\n", strerror(errno));
+               rc = 1;
+               goto out;
+       }
+       if (!WIFEXITED(rc) || WEXITSTATUS(rc)) {
+               rc = 1;
+               goto out;
+       }
+
+       rc = 0;
+
+out:
+       close(fd[0]);
+       return rc;
+}
+#endif
+
 /*
- * If systemd is running and /bin/systemd-ask-password --
+ * If systemd is running and systemd-ask-password --
  * is available, then use that else fallback on getpass(..)
  *
  * Returns: @input or NULL on error
@@ -1739,35 +1775,22 @@ get_password(const char *prompt, char *input, int capacity)
        int is_systemd_running;
        struct stat a, b;
 
+       memset(input, 0, capacity);
+
        /* 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;
-               }
+       if (is_systemd_running && !get_passwd_by_systemd(prompt, input, capacity)) {
+               int len = strlen(input);
+               if (input[len - 1] == '\n')
+                       input[len - 1] = '\0';
+               return input;
        }
 #endif
+       memset(input, 0, capacity);
 
        /*
         * Falling back to getpass(..)
@@ -1792,6 +1815,7 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info,
                   const char *orig_dev, char *orgoptions)
 {
        int rc;
+       char *newopts = NULL;
 
        rc = drop_capabilities(0);
        if (rc)
@@ -1803,15 +1827,16 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info,
 
        if (getuid()) {
                rc = check_fstab(thisprogram, mountpoint, orig_dev,
-                                &orgoptions);
+                                &newopts);
                if (rc)
                        goto assemble_exit;
 
+               orgoptions = newopts;
                /* enable any default user mount flags */
                parsed_info->flags |= CIFS_SETUID_FLAGS;
        }
 
-       rc = get_pw_from_env(parsed_info);
+       rc = get_pw_from_env(parsed_info, thisprogram);
        if (rc)
                goto assemble_exit;
 
@@ -1831,7 +1856,7 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info,
 
        parsed_info->flags &= ~(MS_USERS | MS_USER);
 
-       rc = parse_unc(orig_dev, parsed_info);
+       rc = parse_unc(orig_dev, parsed_info, thisprogram);
        if (rc)
                goto assemble_exit;
 
@@ -1900,13 +1925,17 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info,
                if (*parsed_info->options)
                        strlcat(parsed_info->options, ",",
                                sizeof(parsed_info->options));
-               strlcat(parsed_info->options, ",domain=",
+               strlcat(parsed_info->options, "domain=",
                        sizeof(parsed_info->options));
                strlcat(parsed_info->options, parsed_info->domain,
                        sizeof(parsed_info->options));
+       } else if (parsed_info->got_domain) {
+               strlcat(parsed_info->options, ",domain=",
+                       sizeof(parsed_info->options));
        }
 
 assemble_exit:
+       free(newopts);
        return rc;
 }
 
@@ -1920,7 +1949,7 @@ acquire_mountpoint(char **mountpointp)
        int rc, dacrc;
        uid_t realuid, oldfsuid;
        gid_t oldfsgid;
-       char *mountpoint;
+       char *mountpoint = NULL;
 
        /*
         * Acquire the necessary privileges to chdir to the mountpoint. If
@@ -1969,6 +1998,9 @@ restore_privs:
                gid_t __attribute__((unused)) gignore = setfsgid(oldfsgid);
        }
 
+       if (rc)
+               free(mountpoint);
+
        return rc;
 }
 
@@ -2008,8 +2040,14 @@ int main(int argc, char **argv)
        if (thisprogram == NULL)
                thisprogram = "mount.cifs";
 
+       if(strcmp(thisprogram, "mount.cifs") == 0)
+               cifs_fstype = "cifs";
+
+       if(strcmp(thisprogram, "mount.smb3") == 0)
+               cifs_fstype = "smb3";
+
        /* allocate parsed_info as shared anonymous memory range */
-       parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
+       parsed_info = mmap((void *)0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
                           MAP_ANONYMOUS | MAP_SHARED, -1, 0);
        if (parsed_info == (struct parsed_mount_info *) -1) {
                parsed_info = NULL;
@@ -2043,7 +2081,7 @@ int main(int argc, char **argv)
                        ++parsed_info->verboseflag;
                        break;
                case 'V':
-                       print_cifs_mount_version();
+                       print_cifs_mount_version(thisprogram);
                        exit(0);
                case 'w':
                        parsed_info->flags &= ~MS_RDONLY;
@@ -2071,8 +2109,10 @@ int main(int argc, char **argv)
 
        /* chdir into mountpoint as soon as possible */
        rc = acquire_mountpoint(&mountpoint);
-       if (rc)
+       if (rc) {
+               free(orgoptions);
                return rc;
+       }
 
        /*
         * mount.cifs does privilege separation. Most of the code to handle
@@ -2091,6 +2131,8 @@ int main(int argc, char **argv)
                /* child */
                rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
                                        orig_dev, orgoptions);
+               free(orgoptions);
+               free(mountpoint);
                return rc;
        } else {
                /* parent */
@@ -2176,6 +2218,10 @@ mount_retry:
                switch (errno) {
                case ECONNREFUSED:
                case EHOSTUNREACH:
+                       if (currentaddress) {
+                               fprintf(stderr, "mount error(%d): could not connect to %s",
+                                       errno, currentaddress);
+                       }
                        currentaddress = nextaddress;
                        if (currentaddress) {
                                nextaddress = strchr(currentaddress, ',');
@@ -2187,11 +2233,18 @@ mount_retry:
                        fprintf(stderr,
                                "mount error: %s filesystem not supported by the system\n", cifs_fstype);
                        break;
+               case EHOSTDOWN:
+                       fprintf(stderr,
+                               "mount error: Server abruptly closed the connection.\n"
+                               "This can happen if the server does not support the SMB version you are trying to use.\n"
+                               "The default SMB version recently changed from SMB1 to SMB2.1 and above. Try mounting with vers=1.0.\n");
+                       break;
                case ENXIO:
                        if (!already_uppercased &&
                            uppercase_string(parsed_info->host) &&
                            uppercase_string(parsed_info->share) &&
-                           uppercase_string(parsed_info->prefix)) {
+                           uppercase_string(parsed_info->prefix) &&
+                           uppercase_string(orig_dev)) {
                                fprintf(stderr,
                                        "Retrying with upper case share name\n");
                                already_uppercased = 1;
@@ -2202,7 +2255,7 @@ mount_retry:
                        strerror(errno));
                fprintf(stderr,
                        "Refer to the %s(8) manual page (e.g. man "
-                       "%s)\n", thisprogram, thisprogram);
+                       "%s) and kernel log messages (dmesg)\n", thisprogram, thisprogram);
                rc = EX_FAIL;
                goto mount_exit;
        }
@@ -2223,7 +2276,8 @@ mount_exit:
                memset(parsed_info->password, 0, sizeof(parsed_info->password));
                munmap(parsed_info, sizeof(*parsed_info));
        }
-       SAFE_FREE(options);
-       SAFE_FREE(orgoptions);
+       free(options);
+       free(orgoptions);
+       free(mountpoint);
        return rc;
 }