getcifsacl: fix raw SID printing routine
[jlayton/cifs-utils.git] / mount.cifs.c
index d33f7ba8eb904fc2fc62da241267776f263c89df..9cf58a591c31cb5593d0bceefc4e8defde5c5b97 100644 (file)
@@ -1,20 +1,22 @@
-/* 
-   Mount helper utility for Linux CIFS VFS (virtual filesystem) client
-   Copyright (C) 2003,2008 Steve French  (sfrench@us.ibm.com)
-   Copyright (C) 2008 Jeremy Allison (jra@samba.org)
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-   
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-   
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+/*
+ * Mount helper utility for Linux CIFS VFS (virtual filesystem) client
+ * Copyright (C) 2003,2010 Steve French  (sfrench@us.ibm.com)
+ * Copyright (C) 2008 Jeremy Allison (jra@samba.org)
+ * Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #include <mntent.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <fstab.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 */
+#ifdef HAVE_PRCTL
+#include <sys/prctl.h>
+#endif /* HAVE_PRCTL */
+#ifdef HAVE_LIBCAP
+#include <sys/capability.h>
+#endif /* HAVE_LIBCAP */
+#endif /* HAVE_LIBCAP_NG */
 #include "mount.h"
 #include "util.h"
+#include "resolve_host.h"
 
 #ifndef MS_MOVE 
 #define MS_MOVE 8192 
 
 #define MAX_UNC_LEN 1024
 
+/* I believe that the kernel limits options data to a page */
+#define MAX_OPTIONS_LEN        4096
+
+/* max length of mtab options */
+#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.
+ */
+#define MAX_SHARE_LEN 256
+
+/* max length of username (somewhat made up here) */
+#define MAX_USERNAME_SIZE 32
+
 #ifndef SAFE_FREE
-#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0)
+#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
 #endif
 
 #define MOUNT_PASSWD_SIZE 128
-#define DOMAIN_SIZE 64
-
-/* currently maximum length of IPv6 address string */
-#define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
+#define MAX_DOMAIN_SIZE 64
 
 /*
  * 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.
- * mount.cifs has not been audited for security. Thus, we strongly recommend
- * that it not be installed setuid root. To make that abundantly clear,
- * mount.cifs now check whether it's running setuid root and exit with an
- * error if it is. If you wish to disable this check, then set the following
- * #define to 1, but please realize that you do so at your own peril.
- */
-#define CIFS_DISABLE_SETUID_CHECK 0
-
-/*
- * By default, mount.cifs follows the conventions set forth by /bin/mount
- * for user mounts. That is, it requires that the mount be listed in
- * /etc/fstab with the "user" option when run as an unprivileged user and
- * mount.cifs is setuid root.
- *
- * Older versions of mount.cifs however were "looser" in this regard. When
- * made setuid root, a user could run mount.cifs directly and mount any share
- * on a directory owned by that user.
- *
- * The legacy behavior is now disabled by default. To reenable it, set the
- * following #define to true.
+ * because of users and distributions installing it as a setuid root program
+ * before it had been audited for security holes. The default behavior is
+ * now to allow mount.cifs to be run as a setuid root program. Some admins
+ * may want to disable this fully, so this switch remains in place.
  */
-#define CIFS_LEGACY_SETUID_CHECK 0
+#define CIFS_DISABLE_SETUID_CAPABILITY 0
 
 /*
  * When an unprivileged user runs a setuid mount.cifs, we set certain mount
  */
 #define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
 
-const char *thisprogram;
-int verboseflag = 0;
-int fakemnt = 0;
-static int got_password = 0;
-static int got_user = 0;
-static int got_domain = 0;
-static int got_ip = 0;
-static int got_unc = 0;
-static int got_uid = 0;
-static int got_gid = 0;
-static char * user_name = NULL;
-static char * mountpassword = NULL;
-char * domain_name = NULL;
-char * prefixpath = NULL;
-const char *cifs_fstype = "cifs";
-
 /*
- * If an unprivileged user is doing the mounting then we need to ensure
- * that the entry is in /etc/fstab.
+ * Values for parsing a credentials file.
  */
-static int
-check_mountpoint(const char *progname, char *mountpoint)
-{
-       int err;
-       struct stat statbuf;
-
-       /* does mountpoint exist and is it a directory? */
-       err = stat(".", &statbuf);
-       if (err) {
-               fprintf(stderr, "%s: failed to stat %s: %s\n", progname,
-                               mountpoint, strerror(errno));
-               return EX_USAGE;
-       }
+#define CRED_UNPARSEABLE 0
+#define CRED_USER        1
+#define CRED_PASS        2
+#define CRED_DOM         4
 
-       if (!S_ISDIR(statbuf.st_mode)) {
-               fprintf(stderr, "%s: %s is not a directory!", progname,
-                               mountpoint);
-               return EX_USAGE;
-       }
+/*
+ * Values for parsing command line options.
+ */
+#define OPT_ERROR       -1
+#define OPT_IGNORE      0
+#define OPT_USERS       1
+#define OPT_USER        2
+#define OPT_USER_XATTR  3
+#define OPT_PASS        4
+#define OPT_SEC         5
+#define OPT_IP          6
+#define OPT_UNC         7
+#define OPT_CRED        8
+#define OPT_UID         9
+#define OPT_GID        10
+#define OPT_FMASK      11
+#define OPT_FILE_MODE  12
+#define OPT_DMASK      13
+#define OPT_DIR_MODE   14
+#define OPT_DOM        15
+#define OPT_NO_SUID    16
+#define OPT_SUID       17
+#define OPT_NO_DEV     18
+#define OPT_DEV        19
+#define OPT_NO_LOCK    20
+#define OPT_NO_EXEC    21
+#define OPT_EXEC       22
+#define OPT_GUEST      23
+#define OPT_RO         24
+#define OPT_RW         25
+#define OPT_REMOUNT    26
+#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 {
+       unsigned long flags;
+       char host[NI_MAXHOST + 1];
+       char share[MAX_SHARE_LEN + 1];
+       char prefix[PATH_MAX + 1];
+       char options[MAX_OPTIONS_LEN];
+       char domain[MAX_DOMAIN_SIZE + 1];
+       char username[MAX_USERNAME_SIZE + 1];
+       char password[MOUNT_PASSWD_SIZE + 1];
+       char addrlist[MAX_ADDR_LIST_LEN];
+       unsigned int got_user:1;
+       unsigned int got_password:1;
+       unsigned int fakemnt:1;
+       unsigned int nomtab:1;
+       unsigned int verboseflag:1;
+       unsigned int nofail:1;
+};
 
-#if CIFS_LEGACY_SETUID_CHECK
-       /* do extra checks on mountpoint for legacy setuid behavior */
-       if (!getuid() || geteuid())
-               return 0;
+const char *thisprogram;
+const char *cifs_fstype = "cifs";
 
-       if (statbuf.st_uid != getuid()) {
-               fprintf(stderr, "%s: %s is not owned by user\n", progname,
-                       mountpoint);
-               return EX_USAGE;
-       }
+static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
 
-       if ((statbuf.st_mode & S_IRWXU) != S_IRWXU) {
-               fprintf(stderr, "%s: invalid permissions on %s\n", progname,
-                       mountpoint);
+static int check_setuid(void)
+{
+       if (geteuid()) {
+               fprintf(stderr, "This program is not installed setuid root - "
+                       " \"user\" CIFS mounts not supported.\n");
                return EX_USAGE;
        }
-#endif /* CIFS_LEGACY_SETUID_CHECK */
-
-       return 0;
-}
 
-#if CIFS_DISABLE_SETUID_CHECK
-static int
-check_setuid(void)
-{
-       return 0;
-}
-#else /* CIFS_DISABLE_SETUID_CHECK */
-static int
-check_setuid(void)
-{
+#if CIFS_DISABLE_SETUID_CAPABILITY
        if (getuid() && !geteuid()) {
                printf("This mount.cifs program has been built with the "
-                       "ability to run as a setuid root program disabled.\n"
-                       "mount.cifs has not been well audited for security "
-                       "holes. Therefore the Samba team does not recommend "
-                       "installing it as a setuid root program.\n");
-               return 1;
+                      "ability to run as a setuid root program disabled.\n");
+               return EX_USAGE;
        }
+#endif /* CIFS_DISABLE_SETUID_CAPABILITY */
 
        return 0;
 }
-#endif /* CIFS_DISABLE_SETUID_CHECK */
 
-#if CIFS_LEGACY_SETUID_CHECK
 static int
-check_fstab(const char *progname, char *mountpoint, char *devname,
-           char **options)
-{
-       return 0;
-}
-#else /* CIFS_LEGACY_SETUID_CHECK */
-static int
-check_fstab(const char *progname, char *mountpoint, char *devname,
+check_fstab(const char *progname, const char *mountpoint, const char *devname,
            char **options)
 {
        FILE *fstab;
        struct mntent *mnt;
 
        /* make sure this mount is listed in /etc/fstab */
-       fstab = setmntent(_PATH_FSTAB, "r");
+       fstab = setmntent(_PATH_MNTTAB, "r");
        if (!fstab) {
-               fprintf(stderr, "Couldn't open %s for reading!\n",
-                               _PATH_FSTAB);
+               fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_MNTTAB);
                return EX_FILEIO;
        }
 
-       while((mnt = getmntent(fstab))) {
+       while ((mnt = getmntent(fstab))) {
                if (!strcmp(mountpoint, mnt->mnt_dir))
                        break;
        }
@@ -214,8 +228,7 @@ check_fstab(const char *progname, char *mountpoint, char *devname,
 
        if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
                fprintf(stderr, "%s: permission denied: no match for "
-                               "%s found in %s\n", progname, mountpoint,
-                               _PATH_FSTAB);
+                       "%s found in %s\n", progname, mountpoint, _PATH_MNTTAB);
                return EX_USAGE;
        }
 
@@ -229,897 +242,1214 @@ check_fstab(const char *progname, char *mountpoint, char *devname,
        *options = strdup(mnt->mnt_opts);
        return 0;
 }
-#endif /* CIFS_LEGACY_SETUID_CHECK */
 
 /* BB finish BB
 
-        cifs_umount
-        open nofollow - avoid symlink exposure? 
-        get owner of dir see if matches self or if root
-        call system(umount argv) etc.
-                
-BB end finish BB */
-
-static char * check_for_domain(char **);
+       cifs_umount
+       open nofollow - avoid symlink exposure? 
+       get owner of dir see if matches self or if root
+       call system(umount argv) etc.
 
+BB end finish BB */
 
-static void mount_cifs_usage(FILE *stream)
+static int mount_usage(FILE * stream)
 {
-       fprintf(stream, "\nUsage:  %s <remotetarget> <dir> -o <options>\n", thisprogram);
+       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,setuids,nosetuids,rw,ro,");
-       fprintf(stream, "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
-       fprintf(stream, "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
-       fprintf(stream, "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign");
-       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");
+       fprintf(stream,
+               "\n\tcredentials=<filename>,guest,perm,noperm,setuids,nosetuids,rw,ro,");
+       fprintf(stream,
+               "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
+       fprintf(stream,
+               "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
+       fprintf(stream,
+               "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign,fsc");
+       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");
        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");
-       fprintf(stream, "\n\nOptions are described in more detail in the manual page");
+       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");
+       fprintf(stream,
+               "\n\nOptions are described in more detail in the manual page");
        fprintf(stream, "\n\tman 8 mount.cifs\n");
        fprintf(stream, "\nTo display the version number of the mount helper:");
-       fprintf(stream, "\n\t%s -V\n",thisprogram);
-
-       SAFE_FREE(mountpassword);
+       fprintf(stream, "\n\t%s -V\n", thisprogram);
 
        if (stream == stderr)
-               exit(EX_USAGE);
-       exit(0);
+               return EX_USAGE;
+       return 0;
+}
+
+/*
+ * 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
+ * field, turning any commas into double commas.
+ */
+static int set_password(struct parsed_mount_info *parsed_info, const char *src)
+{
+       char *dst = parsed_info->password;
+       unsigned int i = 0, j = 0;
+
+       while (src[i]) {
+               if (src[i] == ',')
+                       dst[j++] = ',';
+               dst[j++] = src[i++];
+               if (j > sizeof(parsed_info->password)) {
+                       fprintf(stderr, "Converted password too long!\n");
+                       return EX_USAGE;
+               }
+       }
+       dst[j] = '\0';
+       parsed_info->got_password = 1;
+       return 0;
 }
 
-/* caller frees username if necessary */
-static char * getusername(void) {
-       char *username = NULL;
-       struct passwd *password = getpwuid(getuid());
+/*
+ * 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) {
-               username = password->pw_name;
+               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)
+{
+       capng_setpid(getpid());
+       capng_clear(CAPNG_SELECT_BOTH);
+       if (parent) {
+               if (capng_updatev(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH, CAP_DAC_OVERRIDE, -1)) {
+                       fprintf(stderr, "Unable to update capability set.\n");
+                       return EX_SYSERR;
+               }
+               if (capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) {
+                       fprintf(stderr, "Unable to update capability set.\n");
+                       return EX_SYSERR;
+               }
+       } else {
+               if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
+                       fprintf(stderr, "Unable to update capability set.\n");
+                       return EX_SYSERR;
+               }
+       }
+       if (capng_apply(CAPNG_SELECT_BOTH)) {
+               fprintf(stderr, "Unable to apply new capability set.\n");
+               return EX_SYSERR;
        }
-       return username;
+       return 0;
 }
 
-static int open_cred_file(char * file_name)
+static int
+toggle_dac_capability(int writable, int enable)
 {
-       char * line_buf;
-       char * temp_val;
-       FILE * fs;
-       int i, length;
+       unsigned int capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
 
-       i = access(file_name, R_OK);
+       if (capng_update(enable ? CAPNG_ADD : CAPNG_DROP, CAPNG_EFFECTIVE, capability)) {
+               fprintf(stderr, "Unable to update capability set.\n");
+               return EX_SYSERR;
+       }
+       if (capng_apply(CAPNG_SELECT_CAPS)) {
+               fprintf(stderr, "Unable to apply new capability set.\n");
+               return EX_SYSERR;
+       }
+       return 0;
+}
+#else /* HAVE_LIBCAP_NG */
+#ifdef HAVE_LIBCAP
+#ifdef HAVE_PRCTL
+static int
+prune_bounding_set(void)
+{
+       int i, rc = 0;
+       static int bounding_set_cleared;
+
+       if (bounding_set_cleared)
+               return 0;
+
+       for (i = 0; i <= CAP_LAST_CAP && rc == 0; ++i)
+               rc = prctl(PR_CAPBSET_DROP, i);
+
+       if (rc != 0) {
+               fprintf(stderr, "Unable to clear capability bounding set: %d\n", rc);
+               return EX_SYSERR;
+       }
+
+       ++bounding_set_cleared;
+       return 0;
+}
+#else /* HAVE_PRCTL */
+static int
+prune_bounding_set(void)
+{
+       return 0;
+}
+#endif /* HAVE_PRCTL */
+static int
+drop_capabilities(int parent)
+{
+       int rc, ncaps;
+       cap_t caps;
+       cap_value_t cap_list[3];
+
+       rc = prune_bounding_set();
+       if (rc)
+               return rc;
+
+       caps = cap_get_proc();
+       if (caps == NULL) {
+               fprintf(stderr, "Unable to get current capability set: %s\n",
+                       strerror(errno));
+               return EX_SYSERR;
+       }
+
+       if (cap_clear(caps) == -1) {
+               fprintf(stderr, "Unable to clear capability set: %s\n",
+                       strerror(errno));
+               rc = EX_SYSERR;
+               goto free_caps;
+       }
+
+       if (parent || getuid() == 0) {
+               ncaps = 1;
+               cap_list[0] = CAP_DAC_READ_SEARCH;
+               if (parent) {
+                       cap_list[1] = CAP_DAC_OVERRIDE;
+                       cap_list[2] = CAP_SYS_ADMIN;
+                       ncaps += 2;
+               }
+               if (cap_set_flag(caps, CAP_PERMITTED, ncaps, cap_list, CAP_SET) == -1) {
+                       fprintf(stderr, "Unable to set permitted capabilities: %s\n",
+                               strerror(errno));
+                       rc = EX_SYSERR;
+                       goto free_caps;
+               }
+               if (parent) {
+                       cap_list[0] = CAP_SYS_ADMIN;
+                       if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
+                               fprintf(stderr, "Unable to set effective capabilities: %s\n",
+                                       strerror(errno));
+                               rc = EX_SYSERR;
+                               goto free_caps;
+                       }
+               }
+       }
+
+       if (cap_set_proc(caps) != 0) {
+               fprintf(stderr, "Unable to set current process capabilities: %s\n",
+                       strerror(errno));
+               rc = EX_SYSERR;
+       }
+free_caps:
+       cap_free(caps);
+       return rc;
+}
+
+static int
+toggle_dac_capability(int writable, int enable)
+{
+       int rc = 0;
+       cap_t caps;
+       cap_value_t capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
+
+       caps = cap_get_proc();
+       if (caps == NULL) {
+               fprintf(stderr, "Unable to get current capability set: %s\n",
+                       strerror(errno));
+               return EX_SYSERR;
+       }
+
+       if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &capability,
+                        enable ? CAP_SET : CAP_CLEAR) == -1) {
+               fprintf(stderr, "Unable to %s effective capabilities: %s\n",
+                       enable ? "set" : "clear", strerror(errno));
+               rc = EX_SYSERR;
+               goto free_caps;
+       }
+
+       if (cap_set_proc(caps) != 0) {
+               fprintf(stderr, "Unable to set current process capabilities: %s\n",
+                       strerror(errno));
+               rc = EX_SYSERR;
+       }
+free_caps:
+       cap_free(caps);
+       return rc;
+}
+#else /* HAVE_LIBCAP */
+static int
+drop_capabilities(int parent __attribute((unused)))
+{
+       return 0;
+}
+
+static int
+toggle_dac_capability(int writable __attribute((unused)), int enable __attribute((unused)))
+{
+       return 0;
+}
+#endif /* HAVE_LIBCAP */
+#endif /* HAVE_LIBCAP_NG */
+
+static void null_terminate_endl(char *source)
+{
+       char *newline = strchr(source, '\n');
+       if (newline)
+               *newline = '\0';
+}
+
+/*
+ * Parse a line from the credentials file.  Changes target to first
+ * character after '=' on 'line' and returns the value type of the line
+ * Returns CRED_UNPARSEABLE on failure or if either parameter is NULL.
+ */
+static int parse_cred_line(char *line, char **target)
+{
+       if (line == NULL || target == NULL)
+               goto parsing_err;
+
+       /* position target at first char of value */
+       *target = strchr(line, '=');
+       if (!*target)
+               goto parsing_err;
+       *target += 1;
+
+       /* tell the caller which value target points to */
+       if (strncasecmp("user", line, 4) == 0)
+               return CRED_USER;
+       if (strncasecmp("pass", line, 4) == 0)
+               return CRED_PASS;
+       if (strncasecmp("dom", line, 3) == 0)
+               return CRED_DOM;
+
+parsing_err:
+       return CRED_UNPARSEABLE;
+}
+
+static int open_cred_file(char *file_name,
+                       struct parsed_mount_info *parsed_info,
+                       char **saved_username)
+{
+       char *line_buf = NULL;
+       char *temp_val = NULL;
+       FILE *fs = NULL;
+       int i;
+       const int line_buf_size = 4096;
+       const int min_non_white = 10;
+
+       i = toggle_dac_capability(0, 1);
        if (i)
-               return i;
+               goto return_i;
 
-       fs = fopen(file_name,"r");
-       if(fs == NULL)
-               return errno;
-       line_buf = (char *)malloc(4096);
-       if(line_buf == NULL) {
-               fclose(fs);
-               return ENOMEM;
+       i = access(file_name, R_OK);
+       if (i) {
+               toggle_dac_capability(0, 0);
+               i = errno;
+               goto return_i;
+       }
+
+       fs = fopen(file_name, "r");
+       if (fs == NULL) {
+               toggle_dac_capability(0, 0);
+               i = errno;
+               goto return_i;
        }
 
-       while(fgets(line_buf,4096,fs)) {
-               /* parse line from credential file */
+       i = toggle_dac_capability(0, 0);
+       if (i)
+               goto return_i;
+
+       line_buf = (char *)malloc(line_buf_size);
+       if (line_buf == NULL) {
+               i = EX_SYSERR;
+               goto return_i;
+       }
 
+       /* parse line from credentials file */
+       while (fgets(line_buf, line_buf_size, fs)) {
                /* eat leading white space */
-               for(i=0;i<4086;i++) {
-                       if((line_buf[i] != ' ') && (line_buf[i] != '\t'))
+               for (i = 0; i < line_buf_size - min_non_white + 1; i++) {
+                       if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
                                break;
-                       /* if whitespace - skip past it */
                }
-               if (strncasecmp("username",line_buf+i,8) == 0) {
-                       temp_val = strchr(line_buf + i,'=');
-                       if(temp_val) {
-                               /* go past equals sign */
-                               temp_val++;
-                               for(length = 0;length<4087;length++) {
-                                       if ((temp_val[length] == '\n')
-                                           || (temp_val[length] == '\0')) {
-                                               temp_val[length] = '\0';
-                                               break;
-                                       }
-                               }
-                               if(length > 4086) {
-                                       fprintf(stderr, "mount.cifs failed due to malformed username in credentials file\n");
-                                       memset(line_buf,0,4096);
-                                       exit(EX_USAGE);
-                               } else {
-                                       got_user = 1;
-                                       user_name = (char *)calloc(1 + length,1);
-                                       /* BB adding free of user_name string before exit,
-                                               not really necessary but would be cleaner */
-                                       strlcpy(user_name,temp_val, length+1);
-                               }
-                       }
-               } else if (strncasecmp("password",line_buf+i,8) == 0) {
-                       temp_val = strchr(line_buf+i,'=');
-                       if(temp_val) {
-                               /* go past equals sign */
-                               temp_val++;
-                               for(length = 0;length<MOUNT_PASSWD_SIZE+1;length++) {
-                                       if ((temp_val[length] == '\n')
-                                           || (temp_val[length] == '\0')) {
-                                               temp_val[length] = '\0';
-                                               break;
-                                       }
-                               }
-                               if(length > MOUNT_PASSWD_SIZE) {
-                                       fprintf(stderr, "mount.cifs failed: password in credentials file too long\n");
-                                       memset(line_buf,0, 4096);
-                                       exit(EX_USAGE);
-                               } else {
-                                       if(mountpassword == NULL) {
-                                               mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-                                       } else
-                                               memset(mountpassword,0,MOUNT_PASSWD_SIZE);
-                                       if(mountpassword) {
-                                               strlcpy(mountpassword,temp_val,MOUNT_PASSWD_SIZE+1);
-                                               got_password = 1;
-                                       }
-                               }
+               null_terminate_endl(line_buf);
+
+               /* 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;
                        }
-                } else if (strncasecmp("domain",line_buf+i,6) == 0) {
-                        temp_val = strchr(line_buf+i,'=');
-                        if(temp_val) {
-                                /* go past equals sign */
-                                temp_val++;
-                               if(verboseflag)
-                                       fprintf(stderr, "\nDomain %s\n",temp_val);
-                                for(length = 0;length<DOMAIN_SIZE+1;length++) {
-                                       if ((temp_val[length] == '\n')
-                                           || (temp_val[length] == '\0')) {
-                                               temp_val[length] = '\0';
-                                               break;
-                                       }
-                                }
-                                if(length > DOMAIN_SIZE) {
-                                        fprintf(stderr, "mount.cifs failed: domain in credentials file too long\n");
-                                        exit(EX_USAGE);
-                                } else {
-                                        if(domain_name == NULL) {
-                                                domain_name = (char *)calloc(DOMAIN_SIZE+1,1);
-                                        } else
-                                                memset(domain_name,0,DOMAIN_SIZE);
-                                        if(domain_name) {
-                                                strlcpy(domain_name,temp_val,DOMAIN_SIZE+1);
-                                                got_domain = 1;
-                                        }
-                                }
-                        }
-                }
-
-       }
-       fclose(fs);
+                       break;
+               case CRED_PASS:
+                       i = set_password(parsed_info, temp_val);
+                       if (i)
+                               goto return_i;
+                       break;
+               case CRED_DOM:
+                       if (parsed_info->verboseflag)
+                               fprintf(stderr, "domain=%s\n",
+                                       temp_val);
+                       strlcpy(parsed_info->domain, temp_val,
+                               sizeof(parsed_info->domain));
+                       break;
+               case CRED_UNPARSEABLE:
+                       if (parsed_info->verboseflag)
+                               fprintf(stderr, "Credential formatted "
+                                       "incorrectly: %s\n",
+                                       temp_val ? temp_val : "(null)");
+                       break;
+               }
+       }
+       i = 0;
+return_i:
+       if (fs != NULL)
+               fclose(fs);
+
+       /* make sure passwords are scrubbed from memory */
+       if (line_buf != NULL)
+               memset(line_buf, 0, line_buf_size);
        SAFE_FREE(line_buf);
-       return 0;
+       return i;
 }
 
-static int get_password_from_file(int file_descript, char * filename)
+static int
+get_password_from_file(int file_descript, char *filename,
+                      struct parsed_mount_info *parsed_info)
 {
        int rc = 0;
-       int i;
-       char c;
-
-       if(mountpassword == NULL)
-               mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-       else 
-               memset(mountpassword, 0, MOUNT_PASSWD_SIZE);
+       char buf[sizeof(parsed_info->password) + 1];
 
-       if (mountpassword == NULL) {
-               fprintf(stderr, "malloc failed\n");
-               exit(EX_SYSERR);
-       }
+       if (filename != NULL) {
+               rc = toggle_dac_capability(0, 1);
+               if (rc)
+                       return rc;
 
-       if(filename != NULL) {
                rc = access(filename, R_OK);
                if (rc) {
-                       fprintf(stderr, "mount.cifs failed: access check of %s failed: %s\n",
-                                       filename, strerror(errno));
-                       exit(EX_SYSERR);
+                       fprintf(stderr,
+                               "mount.cifs failed: access check of %s failed: %s\n",
+                               filename, strerror(errno));
+                       toggle_dac_capability(0, 0);
+                       return EX_SYSERR;
                }
+
                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);
-                       exit(EX_SYSERR);
+               if (file_descript < 0) {
+                       fprintf(stderr,
+                               "mount.cifs failed. %s attempting to open password file %s\n",
+                               strerror(errno), filename);
+                       toggle_dac_capability(0, 0);
+                       return EX_SYSERR;
                }
-       }
-       /* else file already open and fd provided */
-
-       for(i=0;i<MOUNT_PASSWD_SIZE;i++) {
-               rc = read(file_descript,&c,1);
-               if(rc < 0) {
-                       fprintf(stderr, "mount.cifs failed. Error %s reading password file\n",strerror(errno));
-                       if(filename != NULL)
-                               close(file_descript);
-                       exit(EX_SYSERR);
-               } else if(rc == 0) {
-                       if(mountpassword[0] == 0) {
-                               if(verboseflag)
-                                       fprintf(stderr, "\nWarning: null password used since cifs password file empty");
-                       }
-                       break;
-               } else /* read valid character */ {
-                       if((c == 0) || (c == '\n')) {
-                               mountpassword[i] = '\0';
-                               break;
-                       } else 
-                               mountpassword[i] = c;
+
+               rc = toggle_dac_capability(0, 0);
+               if (rc) {
+                       rc = EX_SYSERR;
+                       goto get_pw_exit;
                }
        }
-       if((i == MOUNT_PASSWD_SIZE) && (verboseflag)) {
-               fprintf(stderr, "\nWarning: password longer than %d characters specified in cifs password file",
-                       MOUNT_PASSWD_SIZE);
-       }
-       got_password = 1;
-       if(filename != NULL) {
-               close(file_descript);
+
+       memset(buf, 0, sizeof(buf));
+       rc = read(file_descript, buf, sizeof(buf) - 1);
+       if (rc < 0) {
+               fprintf(stderr,
+                       "mount.cifs failed. Error %s reading password file\n",
+                       strerror(errno));
+               rc = EX_SYSERR;
+               goto get_pw_exit;
        }
 
+       rc = set_password(parsed_info, buf);
+
+get_pw_exit:
+       if (filename != NULL)
+               close(file_descript);
        return rc;
 }
 
-static int parse_options(char ** optionsp, unsigned long * filesys_flags)
+/*
+ * Returns OPT_ERROR on unparsable token.
+ */
+static int parse_opt_token(const char *token)
+{
+       if (token == NULL)
+               return OPT_ERROR;
+
+       if (strncmp(token, "users", 5) == 0)
+               return OPT_USERS;
+       if (strncmp(token, "user_xattr", 10) == 0)
+               return OPT_USER_XATTR;
+       if (strncmp(token, "user", 4) == 0)
+               return OPT_USER;
+       if (strncmp(token, "pass", 4) == 0)
+               return OPT_PASS;
+       if (strncmp(token, "sec", 3) == 0)
+               return OPT_SEC;
+       if (strncmp(token, "ip", 2) == 0)
+               return OPT_IP;
+       if (strncmp(token, "unc", 3) == 0 ||
+               strncmp(token, "target", 6) == 0 ||
+               strncmp(token, "path", 4) == 0)
+               return OPT_UNC;
+       if (strncmp(token, "dom", 3) == 0 || strncmp(token, "workg", 5) == 0)
+               return OPT_DOM;
+       if (strncmp(token, "cred", 4) == 0)
+               return OPT_CRED;
+       if (strncmp(token, "uid", 3) == 0)
+               return OPT_UID;
+       if (strncmp(token, "cruid", 5) == 0)
+               return OPT_CRUID;
+       if (strncmp(token, "gid", 3) == 0)
+               return OPT_GID;
+       if (strncmp(token, "fmask", 5) == 0)
+               return OPT_FMASK;
+       if (strncmp(token, "file_mode", 9) == 0)
+               return OPT_FILE_MODE;
+       if (strncmp(token, "dmask", 5) == 0)
+               return OPT_DMASK;
+       if (strncmp(token, "dir_mode", 4) == 0 || strncmp(token, "dirm", 4) == 0)
+               return OPT_DIR_MODE;
+       if (strncmp(token, "nosuid", 6) == 0)
+               return OPT_NO_SUID;
+       if (strncmp(token, "suid", 4) == 0)
+               return OPT_SUID;
+       if (strncmp(token, "nodev", 5) == 0)
+               return OPT_NO_DEV;
+       if (strncmp(token, "nobrl", 5) == 0 || strncmp(token, "nolock", 6) == 0)
+               return OPT_NO_LOCK;
+       if (strncmp(token, "mand", 4) == 0)
+               return OPT_MAND;
+       if (strncmp(token, "nomand", 6) == 0)
+               return OPT_NOMAND;
+       if (strncmp(token, "dev", 3) == 0)
+               return OPT_DEV;
+       if (strncmp(token, "noexec", 6) == 0)
+               return OPT_NO_EXEC;
+       if (strncmp(token, "exec", 4) == 0)
+               return OPT_EXEC;
+       if (strncmp(token, "guest", 5) == 0)
+               return OPT_GUEST;
+       if (strncmp(token, "ro", 2) == 0)
+               return OPT_RO;
+       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;
+}
+
+static int
+parse_options(const char *data, struct parsed_mount_info *parsed_info)
 {
-       const char * data;
-       char * percent_char = NULL;
-       char * value = NULL;
-       char * next_keyword = NULL;
-       char * out = NULL;
+       char *value = NULL;
+       char *equals = NULL;
+       char *next_keyword = NULL;
+       char *out = parsed_info->options;
+       unsigned long *filesys_flags = &parsed_info->flags;
        int out_len = 0;
        int word_len;
        int rc = 0;
-       char user[32];
-       char group[32];
+       int got_bkupuid = 0;
+       int got_bkupgid = 0;
+       int got_uid = 0;
+       int got_cruid = 0;
+       int got_gid = 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
+        */
+       char txtbuf[12];
 
-       if (!optionsp || !*optionsp)
-               return 1;
-       data = *optionsp;
+       /* make sure we're starting from beginning */
+       out[0] = '\0';
 
        /* BB fixme check for separator override BB */
-
-       if (getuid()) {
+       uid = getuid();
+       if (uid != 0)
                got_uid = 1;
-               snprintf(user,sizeof(user),"%u",getuid());
+
+       gid = getgid();
+       if (gid != 0)
                got_gid = 1;
-               snprintf(group,sizeof(group),"%u",getgid());
-       }
 
-/* while ((data = strsep(&options, ",")) != NULL) { */
-       while(data != NULL) {
-               /*  check if ends with trailing comma */
-               if(*data == 0)
-                       break;
+       if (!data)
+               return EX_USAGE;
 
-               /* format is keyword=value,keyword2=value2,keyword3=value3 etc.) */
-               /* data  = next keyword */
-               /* value = next value ie stuff after equal sign */
+       /*
+        * format is keyword,keyword2=value2,keyword3=value3... 
+        * data  = next keyword
+        * value = next value ie stuff after equal sign
+        */
+       while (data && *data) {
+               next_keyword = strchr(data, ',');       /* BB handle sep= */
 
-               next_keyword = strchr(data,','); /* BB handle sep= */
-       
                /* temporarily null terminate end of keyword=value pair */
-               if(next_keyword)
+               if (next_keyword)
                        *next_keyword++ = 0;
 
-               /* temporarily null terminate keyword to make keyword and value distinct */
-               if ((value = strchr(data, '=')) != NULL) {
-                       *value = '\0';
-                       value++;
+               /* temporarily null terminate keyword if there's a value */
+               value = NULL;
+               if ((equals = strchr(data, '=')) != NULL) {
+                       *equals = '\0';
+                       value = equals + 1;
                }
 
-               if (strncmp(data, "users",5) == 0) {
-                       if(!value || !*value) {
+               switch(parse_opt_token(data)) {
+               case OPT_USERS:
+                       if (!value || !*value) {
                                *filesys_flags |= MS_USERS;
                                goto nocopy;
                        }
-               } else if (strncmp(data, "user_xattr",10) == 0) {
-                  /* do nothing - need to skip so not parsed as user name */
-               } else if (strncmp(data, "user", 4) == 0) {
+                       break;
 
+               case OPT_USER:
                        if (!value || !*value) {
-                               if(data[4] == '\0') {
+                               if (data[4] == '\0') {
                                        *filesys_flags |= MS_USER;
                                        goto nocopy;
                                } else {
-                                       fprintf(stderr, "username specified with no parameter\n");
-                                       SAFE_FREE(out);
-                                       return 1;       /* needs_arg; */
+                                       fprintf(stderr,
+                                               "username specified with no parameter\n");
+                                       return EX_USAGE;
                                }
                        } else {
-                               if (strnlen(value, 260) < 260) {
-                                       got_user=1;
-                                       percent_char = strchr(value,'%');
-                                       if(percent_char) {
-                                               *percent_char = ',';
-                                               if(mountpassword == NULL)
-                                                       mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-                                               if(mountpassword) {
-                                                       if(got_password)
-                                                               fprintf(stderr, "\nmount.cifs warning - password specified twice\n");
-                                                       got_password = 1;
-                                                       percent_char++;
-                                                       strlcpy(mountpassword, percent_char,MOUNT_PASSWD_SIZE+1);
-                                               /*  remove password from username */
-                                                       while(*percent_char != 0) {
-                                                               *percent_char = ',';
-                                                               percent_char++;
-                                                       }
-                                               }
-                                       }
-                                       /* this is only case in which the user
-                                       name buf is not malloc - so we have to
-                                       check for domain name embedded within
-                                       the user name here since the later
-                                       call to check_for_domain will not be
-                                       invoked */
-                                       domain_name = check_for_domain(&value);
-                               } 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");
-                                       SAFE_FREE(out);
-                                       return 1;
+                                       return EX_USAGE;
                                }
+                               saved_username = strdup(value);
+                               if (!saved_username) {
+                                       fprintf(stderr, "Unable to allocate memory!\n");
+                                       return EX_SYSERR;
+                               }
+                               goto nocopy;
+                       }
+
+               case OPT_PASS:
+                       if (parsed_info->got_password) {
+                               fprintf(stderr,
+                                       "password specified twice, ignoring second\n");
+                               goto nocopy;
                        }
-               } else if (strncmp(data, "pass", 4) == 0) {
                        if (!value || !*value) {
-                               if(got_password) {
-                                       fprintf(stderr, "\npassword specified twice, ignoring second\n");
-                               } else
-                                       got_password = 1;
-                       } else if (strnlen(value, MOUNT_PASSWD_SIZE) < MOUNT_PASSWD_SIZE) {
-                               if (got_password) {
-                                       fprintf(stderr, "\nmount.cifs warning - password specified twice\n");
-                               } else {
-                                       mountpassword = strndup(value, MOUNT_PASSWD_SIZE);
-                                       if (!mountpassword) {
-                                               fprintf(stderr, "mount.cifs error: %s", strerror(ENOMEM));
-                                               SAFE_FREE(out);
-                                               return 1;
-                                       }
-                                       got_password = 1;
-                               }
-                       } else {
-                               fprintf(stderr, "password too long\n");
-                               SAFE_FREE(out);
-                               return 1;
+                               parsed_info->got_password = 1;
+                               goto nocopy;
                        }
+                       rc = set_password(parsed_info, value);
+                       if (rc)
+                               return rc;
                        goto nocopy;
-               } else if (strncmp(data, "sec", 3) == 0) {
+
+               case OPT_SEC:
                        if (value) {
-                               if (!strncmp(value, "none", 4) ||
-                                   !strncmp(value, "krb5", 4))
-                                       got_password = 1;
+                               if (!strncmp(value, "none", 4)) {
+                                       parsed_info->got_password = 1;
+                               } else if (!strncmp(value, "krb5", 4)) {
+                                       parsed_info->got_password = 1;
+                                       krb5_auth = true;
+                               }
                        }
-               } else if (strncmp(data, "ip", 2) == 0) {
+                       break;
+
+               case OPT_IP:
                        if (!value || !*value) {
-                               fprintf(stderr, "target ip address argument missing");
-                       } else if (strnlen(value, MAX_ADDRESS_LEN) <= MAX_ADDRESS_LEN) {
-                               if(verboseflag)
-                                       fprintf(stderr, "ip address %s override specified\n",value);
-                               got_ip = 1;
+                               fprintf(stderr,
+                                       "target ip address argument missing\n");
+                       } else if (strnlen(value, MAX_ADDRESS_LEN) <=
+                               MAX_ADDRESS_LEN) {
+                               strcpy(parsed_info->addrlist, value);
+                               if (parsed_info->verboseflag)
+                                       fprintf(stderr,
+                                               "ip address %s override specified\n",
+                                               value);
+                               goto nocopy;
                        } else {
                                fprintf(stderr, "ip address too long\n");
-                               SAFE_FREE(out);
-                               return 1;
+                               return EX_USAGE;
+
                        }
-               } else if ((strncmp(data, "unc", 3) == 0)
-                  || (strncmp(data, "target", 6) == 0)
-                  || (strncmp(data, "path", 4) == 0)) {
+                       break;
+
+               /* unc || target || path */
+               case OPT_UNC:
                        if (!value || !*value) {
-                               fprintf(stderr, "invalid path to network resource\n");
-                               SAFE_FREE(out);
-                               return 1;  /* needs_arg; */
-                       } else if(strnlen(value,5) < 5) {
-                               fprintf(stderr, "UNC name too short");
+                               fprintf(stderr,
+                                       "invalid path to network resource\n");
+                               return EX_USAGE;
                        }
+                       rc = parse_unc(value, parsed_info);
+                       if (rc)
+                               return rc;
+                       break;
 
-                       if (strnlen(value, 300) < 300) {
-                               got_unc = 1;
-                               if (strncmp(value, "//", 2) == 0) {
-                                       if(got_unc)
-                                               fprintf(stderr, "unc name specified twice, ignoring second\n");
-                                       else
-                                               got_unc = 1;
-                               } else if (strncmp(value, "\\\\", 2) != 0) {                       
-                                       fprintf(stderr, "UNC Path does not begin with // or \\\\ \n");
-                                       SAFE_FREE(out);
-                                       return 1;
-                               } else {
-                                       if(got_unc)
-                                               fprintf(stderr, "unc name specified twice, ignoring second\n");
-                                       else
-                                               got_unc = 1;
-                               }
-                       } else {
-                               fprintf(stderr, "CIFS: UNC name too long\n");
-                               SAFE_FREE(out);
-                               return 1;
-                       }
-               } else if ((strncmp(data, "dom" /* domain */, 3) == 0)
-                          || (strncmp(data, "workg", 5) == 0)) {
-                       /* note this allows for synonyms of "domain"
-                          such as "DOM" and "dom" and "workgroup"
-                          and "WORKGRP" etc. */
+               /* dom || workgroup */
+               case OPT_DOM:
                        if (!value || !*value) {
                                fprintf(stderr, "CIFS: invalid domain name\n");
-                               SAFE_FREE(out);
-                               return 1;       /* needs_arg; */
+                               return EX_USAGE;
                        }
-                       if (strnlen(value, DOMAIN_SIZE+1) < DOMAIN_SIZE+1) {
-                               got_domain = 1;
-                       } else {
+                       if (strnlen(value, sizeof(parsed_info->domain)) >=
+                           sizeof(parsed_info->domain)) {
                                fprintf(stderr, "domain name too long\n");
-                               SAFE_FREE(out);
-                               return 1;
+                               return EX_USAGE;
                        }
-               } else if (strncmp(data, "cred", 4) == 0) {
-                       if (value && *value) {
-                               rc = open_cred_file(value);
-                               if(rc) {
-                                       fprintf(stderr, "error %d (%s) opening credential file %s\n",
-                                               rc, strerror(rc), value);
-                                       SAFE_FREE(out);
-                                       return 1;
-                               }
-                       } else {
-                               fprintf(stderr, "invalid credential file name specified\n");
-                               SAFE_FREE(out);
-                               return 1;
+                       strlcpy(parsed_info->domain, value,
+                               sizeof(parsed_info->domain));
+                       goto nocopy;
+
+               case OPT_CRED:
+                       if (!value || !*value) {
+                               fprintf(stderr,
+                                       "invalid credential file name specified\n");
+                               return EX_USAGE;
                        }
-               } else if (strncmp(data, "uid", 3) == 0) {
-                       if (value && *value) {
-                               got_uid = 1;
-                               if (!isdigit(*value)) {
-                                       struct passwd *pw;
-
-                                       if (!(pw = getpwnam(value))) {
-                                               fprintf(stderr, "bad user name \"%s\"\n", value);
-                                               exit(EX_USAGE);
-                                       }
-                                       snprintf(user, sizeof(user), "%u", pw->pw_uid);
-                               } else {
-                                       strlcpy(user,value,sizeof(user));
-                               }
+                       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;
                        }
                        goto nocopy;
-               } else if (strncmp(data, "gid", 3) == 0) {
-                       if (value && *value) {
-                               got_gid = 1;
-                               if (!isdigit(*value)) {
-                                       struct group *gr;
-
-                                       if (!(gr = getgrnam(value))) {
-                                               fprintf(stderr, "bad group name \"%s\"\n", value);
-                                               exit(EX_USAGE);
-                                       }
-                                       snprintf(group, sizeof(group), "%u", gr->gr_gid);
-                               } else {
-                                       strlcpy(group,value,sizeof(group));
-                               }
+
+               case OPT_UID:
+                       if (!value || !*value)
+                               goto nocopy;
+
+                       got_uid = 1;
+                       errno = 0;
+                       uid = 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;
                        }
+
+                       uid = pw->pw_uid;
                        goto nocopy;
-       /* fmask and dmask synonyms for people used to smbfs syntax */
-               } else if (strcmp(data, "file_mode") == 0 || strcmp(data, "fmask")==0) {
-                       if (!value || !*value) {
-                               fprintf(stderr, "Option '%s' requires a numerical argument\n", data);
-                               SAFE_FREE(out);
-                               return 1;
-                       }
 
-                       if (value[0] != '0') {
-                               fprintf(stderr, "WARNING: '%s' not expressed in octal.\n", data);
+               case OPT_CRUID:
+                       if (!value || !*value)
+                               goto nocopy;
+
+                       got_cruid = 1;
+                       errno = 0;
+                       cruid = 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;
                        }
+                       cruid = pw->pw_uid;
+                       goto nocopy;
+
+               case OPT_GID:
+                       if (!value || !*value)
+                               goto nocopy;
 
-                       if (strcmp (data, "fmask") == 0) {
-                               fprintf(stderr, "WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
-                               data = "file_mode"; /* BB fix this */
+                       got_gid = 1;
+                       errno = 0;
+                       gid = 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;
                        }
-               } else if (strcmp(data, "dir_mode") == 0 || strcmp(data, "dmask")==0) {
+
+                       gid = gr->gr_gid;
+                       goto nocopy;
+
+               /* fmask fall 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 */
+               case OPT_FILE_MODE:
                        if (!value || !*value) {
-                               fprintf(stderr, "Option '%s' requires a numerical argument\n", data);
-                               SAFE_FREE(out);
-                               return 1;
+                               fprintf(stderr,
+                                       "Option '%s' requires a numerical argument\n",
+                                       data);
+                               return EX_USAGE;
                        }
 
-                       if (value[0] != '0') {
-                               fprintf(stderr, "WARNING: '%s' not expressed in octal.\n", data);
-                       }
+                       if (value[0] != '0')
+                               fprintf(stderr,
+                                       "WARNING: '%s' not expressed in octal.\n",
+                                       data);
+                       break;
 
-                       if (strcmp (data, "dmask") == 0) {
-                               fprintf(stderr, "WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
-                               data = "dir_mode";
+               /* dmask falls through to dir_mode */
+               case OPT_DMASK:
+                       fprintf(stderr,
+                               "WARNING: CIFS mount option 'dmask' is\
+                                deprecated. Use 'dir_mode' instead.\n");
+                       data = "dir_mode";
+               case OPT_DIR_MODE:
+                       if (!value || !*value) {
+                               fprintf(stderr,
+                                       "Option '%s' requires a numerical argument\n",
+                                       data);
+                               return EX_USAGE;
                        }
-                       /* the following eight mount options should be
-                       stripped out from what is passed into the kernel
-                       since these eight options are best passed as the
-                       mount flags rather than redundantly to the kernel 
-                       and could generate spurious warnings depending on the
-                       level of the corresponding cifs vfs kernel code */
-               } else if (strncmp(data, "nosuid", 6) == 0) {
+
+                       if (value[0] != '0')
+                               fprintf(stderr,
+                                       "WARNING: '%s' not expressed in octal.\n",
+                                       data);
+                       break;
+
+               /* the following mount options should be
+                  stripped out from what is passed into the kernel
+                  since these options are best passed as the
+                  mount flags rather than redundantly to the kernel
+                  and could generate spurious warnings depending on the
+                  level of the corresponding cifs vfs kernel code */
+               case OPT_NO_SUID:
                        *filesys_flags |= MS_NOSUID;
-               } else if (strncmp(data, "suid", 4) == 0) {
+                       break;
+               case OPT_SUID:
                        *filesys_flags &= ~MS_NOSUID;
-               } else if (strncmp(data, "nodev", 5) == 0) {
+                       break;
+               case OPT_NO_DEV:
                        *filesys_flags |= MS_NODEV;
-               } else if ((strncmp(data, "nobrl", 5) == 0) || 
-                          (strncmp(data, "nolock", 6) == 0)) {
+                       break;
+               /* nolock || nobrl */
+               case OPT_NO_LOCK:
                        *filesys_flags &= ~MS_MANDLOCK;
-               } else if (strncmp(data, "dev", 3) == 0) {
+                       break;
+               case OPT_MAND:
+                       *filesys_flags |= MS_MANDLOCK;
+                       goto nocopy;
+               case OPT_NOMAND:
+                       *filesys_flags &= ~MS_MANDLOCK;
+                       goto nocopy;
+               case OPT_DEV:
                        *filesys_flags &= ~MS_NODEV;
-               } else if (strncmp(data, "noexec", 6) == 0) {
+                       break;
+               case OPT_NO_EXEC:
                        *filesys_flags |= MS_NOEXEC;
-               } else if (strncmp(data, "exec", 4) == 0) {
+                       break;
+               case OPT_EXEC:
                        *filesys_flags &= ~MS_NOEXEC;
-               } else if (strncmp(data, "guest", 5) == 0) {
-                       user_name = (char *)calloc(1, 1);
-                       got_user = 1;
-                       got_password = 1;
-               } else if (strncmp(data, "ro", 2) == 0) {
+                       break;
+               case OPT_GUEST:
+                       parsed_info->got_user = 1;
+                       parsed_info->got_password = 1;
+                       break;
+               case OPT_RO:
                        *filesys_flags |= MS_RDONLY;
                        goto nocopy;
-               } else if (strncmp(data, "rw", 2) == 0) {
+               case OPT_RW:
                        *filesys_flags &= ~MS_RDONLY;
                        goto nocopy;
-                } else if (strncmp(data, "remount", 7) == 0) {
-                        *filesys_flags |= MS_REMOUNT;
-               } /* else if (strnicmp(data, "port", 4) == 0) {
-                       if (value && *value) {
-                               vol->port =
-                                       simple_strtoul(value, &value, 0);
-                       }
-               } else if (strnicmp(data, "rsize", 5) == 0) {
-                       if (value && *value) {
-                               vol->rsize =
-                                       simple_strtoul(value, &value, 0);
+               case OPT_REMOUNT:
+                       *filesys_flags |= MS_REMOUNT;
+                       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;
                        }
-               } else if (strnicmp(data, "wsize", 5) == 0) {
-                       if (value && *value) {
-                               vol->wsize =
-                                       simple_strtoul(value, &value, 0);
+
+                       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;
                        }
-               } else if (strnicmp(data, "version", 3) == 0) {
-               } else {
-                       fprintf(stderr, "CIFS: Unknown mount option %s\n",data);
-               } */ /* nothing to do on those four mount options above.
-                       Just pass to kernel and ignore them here */
 
-               /* Copy (possibly modified) option to out */
+                       bkupgid = gr->gr_gid;
+                       goto nocopy;
+               case OPT_NOFAIL:
+                       parsed_info->nofail = 1;
+                       goto nocopy;
+               }
+
+               /* check size before copying option to buffer */
                word_len = strlen(data);
                if (value)
                        word_len += 1 + strlen(value);
 
-               out = (char *)realloc(out, out_len + word_len + 2);
-               if (out == NULL) {
-                       perror("malloc");
-                       exit(EX_SYSERR);
+               /* need 2 extra bytes for comma and null byte */
+               if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
                }
 
-               if (out_len) {
-                       strlcat(out, ",", out_len + word_len + 2);
-                       out_len++;
-               }
+               /* put back equals sign, if any */
+               if (equals)
+                       *equals = '=';
 
-               if (value)
-                       snprintf(out + out_len, word_len + 1, "%s=%s", data, value);
-               else
-                       snprintf(out + out_len, word_len + 1, "%s", data);
-               out_len = strlen(out);
+               /* go ahead and copy */
+               if (out_len)
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
 
+               strlcat(out, data, MAX_OPTIONS_LEN);
+               out_len = strlen(out);
 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 = strlen(user);
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", uid);
 
-               out = (char *)realloc(out, out_len + word_len + 6);
-               if (out == NULL) {
-                       perror("malloc");
-                       exit(EX_SYSERR);
+               /* comma + "uid=" + terminating NULL == 6 */
+               if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
                }
 
                if (out_len) {
-                       strlcat(out, ",", out_len + word_len + 6);
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
                        out_len++;
                }
-               snprintf(out + out_len, word_len + 5, "uid=%s", user);
+               snprintf(out + out_len, word_len + 5, "uid=%s", txtbuf);
                out_len = strlen(out);
        }
-       if (got_gid) {
-               word_len = strlen(group);
+       if (got_cruid) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", cruid);
 
-               out = (char *)realloc(out, out_len + 1 + word_len + 6);
-               if (out == NULL) {
-               perror("malloc");
-                       exit(EX_SYSERR);
+               /* comma + "cruid=" + terminating NULL == 6 */
+               if (out_len + word_len + 8 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
                }
 
                if (out_len) {
-                       strlcat(out, ",", out_len + word_len + 6);
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
                        out_len++;
                }
-               snprintf(out + out_len, word_len + 5, "gid=%s", group);
+               snprintf(out + out_len, word_len + 7, "cruid=%s", txtbuf);
                out_len = strlen(out);
        }
+       if (got_gid) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", gid);
 
-       SAFE_FREE(*optionsp);
-       *optionsp = out;
-       return 0;
-}
-
-/* replace all (one or more) commas with double commas */
-static void check_for_comma(char ** ppasswrd)
-{
-       char *new_pass_buf;
-       char *pass;
-       int i,j;
-       int number_of_commas = 0;
-       int len;
-
-       if(ppasswrd == NULL)
-               return;
-       else 
-               (pass = *ppasswrd);
-
-       len = strlen(pass);
-
-       for(i=0;i<len;i++)  {
-               if(pass[i] == ',')
-                       number_of_commas++;
-       }
-
-       if(number_of_commas == 0)
-               return;
-       if(number_of_commas > MOUNT_PASSWD_SIZE) {
-               /* would otherwise overflow the mount options buffer */
-               fprintf(stderr, "\nInvalid password. Password contains too many commas.\n");
-               return;
-       }
-
-       new_pass_buf = (char *)malloc(len+number_of_commas+1);
-       if(new_pass_buf == NULL)
-               return;
-
-       for(i=0,j=0;i<len;i++,j++) {
-               new_pass_buf[j] = pass[i];
-               if(pass[i] == ',') {
-                       j++;
-                       new_pass_buf[j] = pass[i];
+               /* comma + "gid=" + terminating NULL == 6 */
+               if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
                }
-       }
-       new_pass_buf[len+number_of_commas] = 0;
 
-       SAFE_FREE(*ppasswrd);
-       *ppasswrd = new_pass_buf;
-       
-       return;
-}
-
-/* Usernames can not have backslash in them and we use
-   [BB check if usernames can have forward slash in them BB] 
-   backslash as domain\user separator character
-*/
-static char * check_for_domain(char **ppuser)
-{
-       char * original_string;
-       char * usernm;
-       char * domainnm;
-       int    original_len;
-       int    len;
-       int    i;
-
-       if(ppuser == NULL)
-               return NULL;
-
-       original_string = *ppuser;
-
-       if (original_string == NULL)
-               return NULL;
-       
-       original_len = strlen(original_string);
-
-       usernm = strchr(*ppuser,'/');
-       if (usernm == NULL) {
-               usernm = strchr(*ppuser,'\\');
-               if (usernm == NULL)
-                       return NULL;
-       }
-
-       if(got_domain) {
-               fprintf(stderr, "Domain name specified twice. Username probably malformed\n");
-               return NULL;
-       }
-
-       usernm[0] = 0;
-       domainnm = *ppuser;
-       if (domainnm[0] != 0) {
-               got_domain = 1;
-       } else {
-               fprintf(stderr, "null domain\n");
+               if (out_len) {
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
+                       out_len++;
+               }
+               snprintf(out + out_len, word_len + 5, "gid=%s", txtbuf);
        }
-       len = strlen(domainnm);
-       /* reset domainm to new buffer, and copy
-       domain name into it */
-       domainnm = (char *)malloc(len+1);
-       if(domainnm == NULL)
-               return NULL;
-
-       strlcpy(domainnm,*ppuser,len+1);
-
-/*     move_string(*ppuser, usernm+1) */
-       len = strlen(usernm+1);
+       if (got_bkupuid) {
+               word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupuid);
 
-       if(len >= original_len) {
-               /* should not happen */
-               return domainnm;
-       }
+               /* comma + "backupuid=" + terminating NULL == 12 */
+               if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
+               }
 
-       for(i=0;i<original_len;i++) {
-               if(i<len)
-                       original_string[i] = usernm[i+1];
-               else /* stuff with commas to remove last parm */
-                       original_string[i] = ',';
+               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);
 
-       /* BB add check for more than one slash? 
-         strchr(*ppuser,'/');
-         strchr(*ppuser,'\\') 
-       */
-       
-       return domainnm;
-}
+               /* comma + "backkupgid=" + terminating NULL == 12 */
+               if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
+                       fprintf(stderr, "Options string too long\n");
+                       return EX_USAGE;
+               }
 
-/* replace all occurances of "from" in a string with "to" */
-static void replace_char(char *string, char from, char to, int maxlen)
-{
-       char *lastchar = string + maxlen;
-       while (string) {
-               string = strchr(string, from);
-               if (string) {
-                       *string = to;
-                       if (string >= lastchar)
-                               return;
+               if (out_len) {
+                       strlcat(out, ",", MAX_OPTIONS_LEN);
+                       out_len++;
                }
+               snprintf(out + out_len, word_len + 11, "backupgid=%s", txtbuf);
        }
+
+       return 0;
 }
 
-/* Note that caller frees the returned buffer if necessary */
-static struct addrinfo *
-parse_server(char **punc_name)
+static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
 {
-       char *unc_name = *punc_name;
        int length = strnlen(unc_name, MAX_UNC_LEN);
-       char *share;
-       struct addrinfo *addrlist;
-       int rc;
+       const char *host, *share, *prepath;
+       size_t hostlen, sharelen, prepathlen;
 
-       if(length > (MAX_UNC_LEN - 1)) {
+       if (length > (MAX_UNC_LEN - 1)) {
                fprintf(stderr, "mount error: UNC name too long\n");
-               return NULL;
+               return EX_USAGE;
        }
 
-       if(length < 3) {
+       if (length < 3) {
                fprintf(stderr, "mount error: UNC name too short\n");
-               return NULL;
+               return EX_USAGE;
        }
 
        if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
            (strncasecmp("smb://", unc_name, 6) == 0)) {
-               fprintf(stderr, "Mounting cifs URL not implemented yet. Attempt to mount %s\n", unc_name);
-               return NULL;
+               fprintf(stderr,
+                       "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
+                       unc_name);
+               return EX_USAGE;
        }
 
-       if(strncmp(unc_name,"//",2) && strncmp(unc_name,"\\\\",2)) {
-               /* check for nfs syntax ie server:share */
-               share = strchr(unc_name,':');
-               if(!share) {
-                       fprintf(stderr, "mount error: improperly formatted UNC name.");
-                       fprintf(stderr, " %s does not begin with \\\\ or //\n",unc_name);
-                       return NULL;
+       /* 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;
                }
-
-               *punc_name = (char *)malloc(length + 3);
-               if(*punc_name == NULL) {
-                       *punc_name = unc_name;
-                       return NULL;
+               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 = '/';
-               strlcpy((*punc_name)+2, unc_name, length + 1);
-               SAFE_FREE(unc_name);
-               unc_name = *punc_name;
-               unc_name[length+2] = 0;
+               share = host + hostlen + 1;
        }
 
-       unc_name[0] = '/';
-       unc_name[1] = '/';
-       unc_name += 2;
-
-       /*
-        * allow for either delimiter between host and sharename
-        * If there's not one, then the UNC is malformed
-        */
-       if (!(share = strpbrk(unc_name, "/\\"))) {
-               fprintf(stderr, "mount error: Malformed UNC\n");
-               return NULL;
+       if (hostlen + 1 > sizeof(parsed_info->host)) {
+               fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
+               return EX_USAGE;
        }
 
-       *share = 0;  /* temporarily terminate the string */
-       share += 1;
-       if(got_ip == 0) {
-               rc = getaddrinfo(unc_name, NULL, NULL, &addrlist);
-               if (rc != 0) {
-                       fprintf(stderr, "mount error: could not resolve address for %s: %s\n",
-                               unc_name, gai_strerror(rc));
-                       addrlist = NULL;
-               }
+       sharelen = strcspn(share, "/\\");
+       if (sharelen + 1 > sizeof(parsed_info->share)) {
+               fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
+               return EX_USAGE;
        }
-       *(share - 1) = '/'; /* put delimiter back */
 
-       /* we don't convert the prefixpath delimiters since '\\' is a valid char in posix paths */
-       if ((prefixpath = strpbrk(share, "/\\"))) {
-               *prefixpath = 0;  /* permanently terminate the string */
-               if (!strlen(++prefixpath))
-                       prefixpath = NULL; /* this needs to be done explicitly */
-       }
-       if(got_ip) {
-               if(verboseflag)
-                       fprintf(stderr, "ip address specified explicitly\n");
-               return NULL;
+       prepath = share + sharelen;
+       if (*prepath != '\0')
+               prepath++;
+
+       prepathlen = strlen(prepath);
+
+       if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
+               fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
+               return EX_USAGE;
        }
-       /* BB should we pass an alternate version of the share name as Unicode */
 
-       return addrlist;
+       /* copy pieces into their resepective buffers */
+       memcpy(parsed_info->host, host, hostlen);
+       memcpy(parsed_info->share, share, sharelen);
+       memcpy(parsed_info->prefix, prepath, prepathlen);
+
+       return 0;
+}
+
+static int get_pw_from_env(struct parsed_mount_info *parsed_info)
+{
+       int rc = 0;
+
+       if (getenv("PASSWD"))
+               rc = set_password(parsed_info, getenv("PASSWD"));
+       else if (getenv("PASSWD_FD"))
+               rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
+                                           parsed_info);
+       else if (getenv("PASSWD_FILE"))
+               rc = get_password_from_file(0, getenv("PASSWD_FILE"),
+                                           parsed_info);
+
+       return rc;
 }
 
 static struct option longopts[] = {
-       { "all", 0, NULL, 'a' },
-       { "help",0, NULL, 'h' },
-       { "move",0, NULL, 'm' },
-       { "bind",0, NULL, 'b' },
-       { "read-only", 0, NULL, 'r' },
-       { "ro", 0, NULL, 'r' },
-       { "verbose", 0, NULL, 'v' },
-       { "version", 0, NULL, 'V' },
-       { "read-write", 0, NULL, 'w' },
-       { "rw", 0, NULL, 'w' },
-       { "options", 1, NULL, 'o' },
-       { "type", 1, NULL, 't' },
-       { "rsize",1, NULL, 'R' },
-       { "wsize",1, NULL, 'W' },
-       { "uid", 1, NULL, '1'},
-       { "gid", 1, NULL, '2'},
-       { "user",1,NULL,'u'},
-       { "username",1,NULL,'u'},
-       { "dom",1,NULL,'d'},
-       { "domain",1,NULL,'d'},
-       { "password",1,NULL,'p'},
-       { "pass",1,NULL,'p'},
-       { "credentials",1,NULL,'c'},
-       { "port",1,NULL,'P'},
-       /* { "uuid",1,NULL,'U'}, */ /* BB unimplemented */
-       { NULL, 0, NULL, 0 }
+       {"all", 0, NULL, 'a'},
+       {"help", 0, NULL, 'h'},
+       {"move", 0, NULL, 'm'},
+       {"bind", 0, NULL, 'b'},
+       {"read-only", 0, NULL, 'r'},
+       {"ro", 0, NULL, 'r'},
+       {"verbose", 0, NULL, 'v'},
+       {"version", 0, NULL, 'V'},
+       {"read-write", 0, NULL, 'w'},
+       {"rw", 0, NULL, 'w'},
+       {"options", 1, NULL, 'o'},
+       {"type", 1, NULL, 't'},
+       {"uid", 1, NULL, '1'},
+       {"gid", 1, NULL, '2'},
+       {"user", 1, NULL, 'u'},
+       {"username", 1, NULL, 'u'},
+       {"dom", 1, NULL, 'd'},
+       {"domain", 1, NULL, 'd'},
+       {"password", 1, NULL, 'p'},
+       {"pass", 1, NULL, 'p'},
+       {"credentials", 1, NULL, 'c'},
+       {"port", 1, NULL, 'P'},
+       {"sloppy", 0, NULL, 's'},
+       {NULL, 0, NULL, 0}
 };
 
 /* convert a string to uppercase. return false if the string
  * wasn't ASCII. Return success on a NULL ptr */
-static int
-uppercase_string(char *string)
+static int uppercase_string(char *string)
 {
        if (!string)
                return 1;
 
        while (*string) {
                /* check for unicode */
-               if ((unsigned char) string[0] & 0x80)
+               if ((unsigned char)string[0] & 0x80)
                        return 0;
-               *string = toupper((unsigned char) *string);
+               *string = toupper((unsigned char)*string);
                string++;
        }
 
@@ -1140,546 +1470,770 @@ static void print_cifs_mount_version(void)
  */
 static int check_newline(const char *progname, const char *name)
 {
-    const char *s;
-    for (s = "\n"; *s; s++) {
-        if (strchr(name, *s)) {
-            fprintf(stderr, "%s: illegal character 0x%02x in mount entry\n",
-                    progname, *s);
-            return EX_USAGE;
-        }
-    }
-    return 0;
+       const char *s;
+       for (s = "\n"; *s; s++) {
+               if (strchr(name, *s)) {
+                       fprintf(stderr,
+                               "%s: illegal character 0x%02x in mount entry\n",
+                               progname, *s);
+                       return EX_USAGE;
+               }
+       }
+       return 0;
 }
 
 static int check_mtab(const char *progname, const char *devname,
-                       const char *dir)
+                     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;
 }
 
-
-int main(int argc, char ** argv)
+static int
+add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstype)
 {
-       int c;
-       unsigned long flags = MS_MANDLOCK;
-       char * orgoptions = NULL;
-       char * share_name = NULL;
-       const char * ipaddr = NULL;
-       char * uuid = NULL;
-       char * mountpoint = NULL;
-       char * options = NULL;
-       char * optionstail;
-       char * resolved_path = NULL;
-       char * temp;
-       char * dev_name = NULL;
-       int rc = 0;
-       int rsize = 0;
-       int wsize = 0;
-       int nomtab = 0;
-       int uid = 0;
-       int gid = 0;
-       int optlen = 0;
-       int orgoptlen = 0;
-       size_t options_size = 0;
-       size_t current_len;
-       int retry = 0; /* set when we have to retry mount with uppercase */
-       struct addrinfo *addrhead = NULL, *addr;
+       int rc = 0, tmprc, fd;
+       uid_t uid;
+       char *mount_user = NULL;
        struct mntent mountent;
-       struct sockaddr_in *addr4 = NULL;
-       struct sockaddr_in6 *addr6 = NULL;
-       FILE * pmntfile;
+       struct stat statbuf;
+       FILE *pmntfile;
+       sigset_t mask, oldmask;
 
-       if (check_setuid())
-               return EX_USAGE;
+       uid = getuid();
+       if (uid != 0)
+               mount_user = getusername(uid);
 
-       /* setlocale(LC_ALL, "");
-       bindtextdomain(PACKAGE, LOCALEDIR);
-       textdomain(PACKAGE); */
+       /*
+        * Set the real uid to the effective uid. This prevents unprivileged
+        * users from sending signals to this process, though ^c on controlling
+        * terminal should still work.
+        */
+       rc = setreuid(geteuid(), -1);
+       if (rc != 0) {
+               fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
+                               strerror(errno));
+               return EX_FILEIO;
+       }
 
-       if(argc && argv)
-               thisprogram = argv[0];
-       else
-               mount_cifs_usage(stderr);
+       rc = sigfillset(&mask);
+       if (rc) {
+               fprintf(stderr, "Unable to set filled signal mask\n");
+               return EX_FILEIO;
+       }
 
-       if(thisprogram == NULL)
-               thisprogram = "mount.cifs";
+       rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
+       if (rc) {
+               fprintf(stderr, "Unable to make process ignore signals\n");
+               return EX_FILEIO;
+       }
 
-       /* add sharename in opts string as unc= parm */
-       while ((c = getopt_long (argc, argv, "afFhilL:no:O:rsSU:vVwt:",
-                        longopts, NULL)) != -1) {
-               switch (c) {
-/* No code to do the following  options yet */
-/*     case 'l':
-               list_with_volumelabel = 1;
-               break;
-       case 'L':
-               volumelabel = optarg;
-               break; */
-/*     case 'a':              
-               ++mount_all;
-               break; */
+       rc = toggle_dac_capability(1, 1);
+       if (rc)
+               return EX_FILEIO;
 
-               case '?':
-               case 'h':        /* help */
-                       mount_cifs_usage(stdout);
-               case 'n':
-                       ++nomtab;
-                       break;
-               case 'b':
-#ifdef MS_BIND
-                       flags |= MS_BIND;
-#else
-                       fprintf(stderr,
-                               "option 'b' (MS_BIND) not supported\n");
-#endif
-                       break;
-               case 'm':
-#ifdef MS_MOVE               
-                       flags |= MS_MOVE;
-#else
-                       fprintf(stderr,
-                               "option 'm' (MS_MOVE) not supported\n");
-#endif
-                       break;
-               case 'o':
-                       orgoptions = strdup(optarg);
-                   break;
-               case 'r':  /* mount readonly */
-                       flags |= MS_RDONLY;
-                       break;
-               case 'U':
-                       uuid = optarg;
-                       break;
-               case 'v':
-                       ++verboseflag;
-                       break;
-               case 'V':
-                       print_cifs_mount_version();
-                       exit (0);
-               case 'w':
-                       flags &= ~MS_RDONLY;
-                       break;
-               case 'R':
-                       rsize = atoi(optarg) ;
-                       break;
-               case 'W':
-                       wsize = atoi(optarg);
-                       break;
-               case '1':
-                       if (isdigit(*optarg)) {
-                               char *ep;
-
-                               uid = strtoul(optarg, &ep, 10);
-                               if (*ep) {
-                                       fprintf(stderr, "bad uid value \"%s\"\n", optarg);
-                                       exit(EX_USAGE);
-                               }
-                       } else {
-                               struct passwd *pw;
+       atexit(unlock_mtab);
+       rc = lock_mtab();
+       if (rc) {
+               fprintf(stderr, "cannot lock mtab");
+               rc = EX_FILEIO;
+               goto add_mtab_exit;
+       }
 
-                               if (!(pw = getpwnam(optarg))) {
-                                       fprintf(stderr, "bad user name \"%s\"\n", optarg);
-                                       exit(EX_USAGE);
-                               }
-                               uid = pw->pw_uid;
-                               endpwent();
-                       }
-                       break;
-               case '2':
-                       if (isdigit(*optarg)) {
-                               char *ep;
-
-                               gid = strtoul(optarg, &ep, 10);
-                               if (*ep) {
-                                       fprintf(stderr, "bad gid value \"%s\"\n", optarg);
-                                       exit(EX_USAGE);
-                               }
-                       } else {
-                               struct group *gr;
+       pmntfile = setmntent(MOUNTED, "a+");
+       if (!pmntfile) {
+               fprintf(stderr, "could not update mount table\n");
+               unlock_mtab();
+               rc = EX_FILEIO;
+               goto add_mtab_exit;
+       }
 
-                               if (!(gr = getgrnam(optarg))) {
-                                       fprintf(stderr, "bad user name \"%s\"\n", optarg);
-                                       exit(EX_USAGE);
-                               }
-                               gid = gr->gr_gid;
-                               endpwent();
-                       }
-                       break;
-               case 'u':
-                       got_user = 1;
-                       user_name = optarg;
-                       break;
-               case 'd':
-                       domain_name = optarg; /* BB fix this - currently ignored */
-                       got_domain = 1;
-                       break;
-               case 'p':
-                       if(mountpassword == NULL)
-                               mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-                       if(mountpassword) {
-                               got_password = 1;
-                               strlcpy(mountpassword,optarg,MOUNT_PASSWD_SIZE+1);
-                       }
-                       break;
-               case 'S':
-                       get_password_from_file(0 /* stdin */,NULL);
-                       break;
-               case 't':
-                       break;
-               case 'f':
-                       ++fakemnt;
-                       break;
-               default:
-                       fprintf(stderr, "unknown mount option %c\n",c);
-                       mount_cifs_usage(stderr);
+       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;
+       mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
+       if (mountent.mnt_opts) {
+               if (flags & MS_RDONLY)
+                       strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
+               else
+                       strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
+
+               if (flags & MS_MANDLOCK)
+                       strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
+               if (flags & MS_NOEXEC)
+                       strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
+               if (flags & MS_NOSUID)
+                       strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
+               if (flags & MS_NODEV)
+                       strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
+               if (flags & MS_SYNCHRONOUS)
+                       strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
+               if (mount_user) {
+                       strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
+                       strlcat(mountent.mnt_opts, mount_user,
+                               MTAB_OPTIONS_LEN);
                }
        }
+       mountent.mnt_freq = 0;
+       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;
+       }
+       unlock_mtab();
+       SAFE_FREE(mountent.mnt_opts);
+add_mtab_exit:
+       toggle_dac_capability(1, 0);
+       sigprocmask(SIG_SETMASK, &oldmask, NULL);
 
-       if(argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL)
-               mount_cifs_usage(stderr);
+       return rc;
+}
 
-       dev_name = argv[optind];
-       share_name = strndup(argv[optind], MAX_UNC_LEN);
-       if (share_name == NULL) {
-               fprintf(stderr, "%s: %s", thisprogram, strerror(ENOMEM));
-               exit(EX_SYSERR);
+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;
        }
-       mountpoint = argv[optind + 1];
 
-       /* make sure mountpoint is legit */
-       rc = chdir(mountpoint);
+       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, "Couldn't chdir to %s: %s\n", mountpoint,
+               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));
-               rc = EX_USAGE;
-               goto mount_exit;
+       goto del_mtab_exit;
+}
+
+/* have the child drop root privileges */
+static int
+drop_child_privs(void)
+{
+       int rc;
+       uid_t uid = getuid();
+       gid_t gid = getgid();
+
+       if (gid) {
+               rc = setgid(gid);
+               if (rc) {
+                       fprintf(stderr, "Unable set group identity: %s\n",
+                                       strerror(errno));
+                       return EX_SYSERR;
+               }
+       }
+       if (uid) {
+               rc = setuid(uid);
+               if (rc) {
+                       fprintf(stderr, "Unable set user identity: %s\n",
+                                       strerror(errno));
+                       return EX_SYSERR;
+               }
+       }
+
+       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
 
-       rc = check_mountpoint(thisprogram, mountpoint);
+       /*
+        * 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,
+                  const char *orig_dev, char *orgoptions)
+{
+       int rc;
+
+       rc = drop_capabilities(0);
        if (rc)
-               goto mount_exit;
+               goto assemble_exit;
+
+       rc = drop_child_privs();
+       if (rc)
+               goto assemble_exit;
 
-       /* sanity check for unprivileged mounts */
        if (getuid()) {
-               rc = check_fstab(thisprogram, mountpoint, dev_name,
+               rc = check_fstab(thisprogram, mountpoint, orig_dev,
                                 &orgoptions);
                if (rc)
-                       goto mount_exit;
+                       goto assemble_exit;
 
                /* enable any default user mount flags */
-               flags |= CIFS_SETUID_FLAGS;
+               parsed_info->flags |= CIFS_SETUID_FLAGS;
        }
 
-       if (getenv("PASSWD")) {
-               if(mountpassword == NULL)
-                       mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-               if(mountpassword) {
-                       strlcpy(mountpassword,getenv("PASSWD"),MOUNT_PASSWD_SIZE+1);
-                       got_password = 1;
-               }
-       } else if (getenv("PASSWD_FD")) {
-               get_password_from_file(atoi(getenv("PASSWD_FD")),NULL);
-       } else if (getenv("PASSWD_FILE")) {
-               get_password_from_file(0, getenv("PASSWD_FILE"));
-       }
+       rc = get_pw_from_env(parsed_info);
+       if (rc)
+               goto assemble_exit;
 
-        if (orgoptions && parse_options(&orgoptions, &flags)) {
-                rc = EX_USAGE;
-               goto mount_exit;
+       if (orgoptions) {
+               rc = parse_options(orgoptions, parsed_info);
+               if (rc)
+                       goto assemble_exit;
        }
 
        if (getuid()) {
-#if !CIFS_LEGACY_SETUID_CHECK
-               if (!(flags & (MS_USERS|MS_USER))) {
+               if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
                        fprintf(stderr, "%s: permission denied\n", thisprogram);
                        rc = EX_USAGE;
-                       goto mount_exit;
+                       goto assemble_exit;
                }
-#endif /* !CIFS_LEGACY_SETUID_CHECK */
-               
-               if (geteuid()) {
-                       fprintf(stderr, "%s: not installed setuid - \"user\" "
-                                       "CIFS mounts not supported.",
-                                       thisprogram);
-                       rc = EX_FAIL;
-                       goto mount_exit;
+       }
+
+       parsed_info->flags &= ~(MS_USERS | MS_USER);
+
+       rc = parse_unc(orig_dev, parsed_info);
+       if (rc)
+               goto assemble_exit;
+
+       if (parsed_info->addrlist[0] == '\0')
+               rc = resolve_host(parsed_info->host, parsed_info->addrlist);
+
+       switch (rc) {
+       case EX_USAGE:
+               fprintf(stderr, "mount error: could not resolve address for "
+                       "%s: %s\n", parsed_info->host,
+                       rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
+               goto assemble_exit;
+
+       case EX_SYSERR:
+               fprintf(stderr, "mount error: problem parsing address "
+                       "list: %s\n", strerror(errno));
+               goto assemble_exit;
+       }
+
+       if (!parsed_info->got_user) {
+               /*
+                * Note that the password will not be retrieved from the
+                * USER env variable (ie user%password form) as there is
+                * already a PASSWD environment varaible
+                */
+               if (getenv("USER"))
+                       strlcpy(parsed_info->username, getenv("USER"),
+                               sizeof(parsed_info->username));
+               else
+                       strlcpy(parsed_info->username, getusername(getuid()),
+                               sizeof(parsed_info->username));
+               parsed_info->got_user = 1;
+       }
+
+       if (!parsed_info->got_password) {
+               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;
                }
+
+               free(prompt);
+               if (rc)
+                       goto assemble_exit;
        }
 
-       flags &= ~(MS_USERS|MS_USER);
+       /* copy in user= string */
+       if (parsed_info->got_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));
+       }
 
-       addrhead = addr = parse_server(&share_name);
-       if((addrhead == NULL) && (got_ip == 0)) {
-               fprintf(stderr, "No ip address specified and hostname not found\n");
-               rc = EX_USAGE;
-               goto mount_exit;
+       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,
+                       sizeof(parsed_info->options));
        }
-       
-       /* BB save off path and pop after mount returns? */
-       resolved_path = (char *)malloc(PATH_MAX+1);
-       if (!resolved_path) {
-               fprintf(stderr, "Unable to allocate memory.\n");
-               rc = EX_SYSERR;
-               goto mount_exit;
+
+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());
        }
 
-       /* Note that if we can not canonicalize the name, we get
-          another chance to see if it is valid when we chdir to it */
-       if(!realpath(".", resolved_path)) {
+       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",
-                               mountpoint, strerror(errno));
+                       *mountpointp, strerror(errno));
                rc = EX_SYSERR;
-               goto mount_exit;
        }
 
-       mountpoint = resolved_path; 
-
-       if(got_user == 0) {
-               /* Note that the password will not be retrieved from the
-                  USER env variable (ie user%password form) as there is
-                  already a PASSWD environment varaible */
-               if (getenv("USER"))
-                       user_name = strdup(getenv("USER"));
-               if (user_name == NULL)
-                       user_name = getusername();
-               got_user = 1;
-       }
-       
-       if(got_password == 0) {
-               char *tmp_pass = getpass("Password: "); /* BB obsolete sys call but
-                                                          no good replacement yet. */
-               mountpassword = (char *)calloc(MOUNT_PASSWD_SIZE+1,1);
-               if (!tmp_pass || !mountpassword) {
-                       fprintf(stderr, "Password not entered, exiting\n");
-                       exit(EX_USAGE);
-               }
-               strlcpy(mountpassword, tmp_pass, MOUNT_PASSWD_SIZE+1);
-               got_password = 1;
-       }
-       /* FIXME launch daemon (handles dfs name resolution and credential change) 
-          remember to clear parms and overwrite password field before launching */
-       if(orgoptions) {
-               optlen = strlen(orgoptions);
-               orgoptlen = optlen;
-       } else
-               optlen = 0;
-       if(share_name)
-               optlen += strlen(share_name) + 4;
-       else {
-               fprintf(stderr, "No server share name specified\n");
-               fprintf(stderr, "\nMounting the DFS root for server not implemented yet\n");
-                exit(EX_USAGE);
-       }
-       if(user_name)
-               optlen += strlen(user_name) + 6;
-       optlen += MAX_ADDRESS_LEN + 4;
-       if(mountpassword)
-               optlen += strlen(mountpassword) + 6;
-mount_retry:
-       SAFE_FREE(options);
-       options_size = optlen + 10 + DOMAIN_SIZE;
-       options = (char *)malloc(options_size /* space for commas in password */ + 8 /* space for domain=  , domain name itself was counted as part of the length username string above */);
-
-       if(options == NULL) {
-               fprintf(stderr, "Could not allocate memory for mount options\n");
-               exit(EX_SYSERR);
-       }
-
-       strlcpy(options, "unc=", options_size);
-       strlcat(options,share_name,options_size);
-       /* scan backwards and reverse direction of slash */
-       temp = strrchr(options, '/');
-       if(temp > options + 6)
-               *temp = '\\';
-       if(user_name) {
-               /* check for syntax like user=domain\user */
-               if(got_domain == 0)
-                       domain_name = check_for_domain(&user_name);
-               strlcat(options,",user=",options_size);
-               strlcat(options,user_name,options_size);
-       }
-       if(retry == 0) {
-               if(domain_name) {
-                       /* extra length accounted for in option string above */
-                       strlcat(options,",domain=",options_size);
-                       strlcat(options,domain_name,options_size);
-               }
+       *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);
        }
 
-       strlcat(options,",ver=",options_size);
-       strlcat(options,VERSION,options_size);
+       return rc;
+}
 
-       if(orgoptions) {
-               strlcat(options,",",options_size);
-               strlcat(options,orgoptions,options_size);
-       }
-       if(prefixpath) {
-               strlcat(options,",prefixpath=",options_size);
-               strlcat(options,prefixpath,options_size); /* no need to cat the / */
+int main(int argc, char **argv)
+{
+       int c;
+       char *orgoptions = NULL;
+       char *mountpoint = NULL;
+       char *options = NULL;
+       char *orig_dev = NULL;
+       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;
+
+       rc = check_setuid();
+       if (rc)
+               return rc;
+
+       rc = drop_capabilities(1);
+       if (rc)
+               return EX_SYSERR;
+
+       /* setlocale(LC_ALL, "");
+          bindtextdomain(PACKAGE, LOCALEDIR);
+          textdomain(PACKAGE); */
+
+       if (!argc || !argv) {
+               rc = mount_usage(stderr);
+               goto mount_exit;
        }
 
-       /* convert all '\\' to '/' in share portion so that /proc/mounts looks pretty */
-       replace_char(dev_name, '\\', '/', strlen(share_name));
+       thisprogram = basename(argv[0]);
+       if (thisprogram == NULL)
+               thisprogram = "mount.cifs";
+
+       /* allocate parsed_info as shared anonymous memory range */
+       parsed_info = mmap(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;
+               fprintf(stderr, "Unable to allocate memory: %s\n",
+                               strerror(errno));
+               return EX_SYSERR;
+       }
 
-       if (!got_ip && addr) {
-               strlcat(options, ",ip=", options_size);
-               current_len = strnlen(options, options_size);
-               optionstail = options + current_len;
-               switch (addr->ai_addr->sa_family) {
-               case AF_INET6:
-                       addr6 = (struct sockaddr_in6 *) addr->ai_addr;
-                       ipaddr = inet_ntop(AF_INET6, &addr6->sin6_addr, optionstail,
-                                          options_size - current_len);
+       /* add sharename in opts string as unc= parm */
+       while ((c = getopt_long(argc, argv, "?fhno:rsvVw",
+                               longopts, NULL)) != -1) {
+               switch (c) {
+               case '?':
+               case 'h':       /* help */
+                       rc = mount_usage(stdout);
+                       goto mount_exit;
+               case 'n':
+                       ++parsed_info->nomtab;
+                       break;
+               case 'o':
+                       orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
+                       if (!orgoptions) {
+                               rc = EX_SYSERR;
+                               goto mount_exit;
+                       }
                        break;
-               case AF_INET:
-                       addr4 = (struct sockaddr_in *) addr->ai_addr;
-                       ipaddr = inet_ntop(AF_INET, &addr4->sin_addr, optionstail,
-                                          options_size - current_len);
+               case 'r':       /* mount readonly */
+                       parsed_info->flags |= MS_RDONLY;
+                       break;
+               case 'v':
+                       ++parsed_info->verboseflag;
+                       break;
+               case 'V':
+                       print_cifs_mount_version();
+                       exit(0);
+               case 'w':
+                       parsed_info->flags &= ~MS_RDONLY;
+                       break;
+               case 'f':
+                       ++parsed_info->fakemnt;
+                       break;
+               case 's':
+                       ++sloppy;
                        break;
                default:
-                       ipaddr = NULL;
+                       fprintf(stderr, "unknown command-line option: %c\n", c);
+                       rc = mount_usage(stderr);
+                       goto mount_exit;
                }
+       }
 
-               /* if the address looks bogus, try the next one */
-               if (!ipaddr) {
-                       addr = addr->ai_next;
-                       if (addr)
-                               goto mount_retry;
+       if (argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL) {
+               rc = mount_usage(stderr);
+               goto mount_exit;
+       }
+
+       orig_dev = argv[optind];
+       mountpoint = argv[optind + 1];
+
+       /* chdir into mountpoint as soon as possible */
+       rc = acquire_mountpoint(&mountpoint);
+       if (rc)
+               return rc;
+
+       /*
+        * mount.cifs does privilege separation. Most of the code to handle
+        * assembling the mount info is done in a child process that drops
+        * privileges. The info is assembled in parsed_info which is a
+        * shared, mmaped memory segment. The parent waits for the child to
+        * exit and checks the return code. If it's anything but "0", then
+        * the process exits without attempting anything further.
+        */
+       pid = fork();
+       if (pid == -1) {
+               fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
+               rc = EX_SYSERR;
+               goto mount_exit;
+       } else if (!pid) {
+               /* child */
+               rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
+                                       orig_dev, orgoptions);
+               return rc;
+       } else {
+               /* parent */
+               pid = wait(&rc);
+               if (!WIFEXITED(rc)) {
+                       fprintf(stderr, "Child process terminated abnormally.\n");
                        rc = EX_SYSERR;
                        goto mount_exit;
                }
+               rc = WEXITSTATUS(rc);
+               if (rc)
+                       goto mount_exit;
        }
 
-       if (addr && addr->ai_addr->sa_family == AF_INET6 && addr6->sin6_scope_id) {
-               strlcat(options, "%", options_size);
-               current_len = strnlen(options, options_size);
-               optionstail = options + current_len;
-               snprintf(optionstail, options_size - current_len, "%u",
-                        addr6->sin6_scope_id);
+       options = calloc(options_size, 1);
+       if (!options) {
+               fprintf(stderr, "Unable to allocate memory.\n");
+               rc = EX_SYSERR;
+               goto mount_exit;
+       }
+
+       currentaddress = parsed_info->addrlist;
+       nextaddress = strchr(currentaddress, ',');
+       if (nextaddress)
+               *nextaddress++ = '\0';
+
+mount_retry:
+       if (!currentaddress) {
+               fprintf(stderr, "Unable to find suitable address.\n");
+               rc = parsed_info->nofail ? 0 : EX_FAIL;
+               goto mount_exit;
        }
+       strlcpy(options, "ip=", options_size);
+       strlcat(options, currentaddress, options_size);
 
-       if(verboseflag)
-               fprintf(stderr, "\nmount.cifs kernel mount options: %s", options);
+       strlcat(options, ",unc=\\\\", options_size);
+       strlcat(options, parsed_info->host, options_size);
+       strlcat(options, "\\", options_size);
+       strlcat(options, parsed_info->share, options_size);
 
-       if (mountpassword) {
+       if (*parsed_info->options) {
+               strlcat(options, ",", options_size);
+               strlcat(options, parsed_info->options, options_size);
+       }
+
+       if (*parsed_info->prefix) {
+               strlcat(options, ",prefixpath=", options_size);
+               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);
+
+       if (parsed_info->got_password) {
                /*
                 * Commas have to be doubled, or else they will
                 * look like the parameter separator
                 */
-               if(retry == 0)
-                       check_for_comma(&mountpassword);
-               strlcat(options,",pass=",options_size);
-               strlcat(options,mountpassword,options_size);
-               if (verboseflag)
+               strlcat(options, ",pass=", options_size);
+               strlcat(options, parsed_info->password, options_size);
+               if (parsed_info->verboseflag)
                        fprintf(stderr, ",pass=********");
        }
 
-       if (verboseflag)
+       if (parsed_info->verboseflag)
                fprintf(stderr, "\n");
 
-       rc = check_mtab(thisprogram, dev_name, mountpoint);
+       rc = check_mtab(thisprogram, orig_dev, mountpoint);
        if (rc)
                goto mount_exit;
 
-       if (!fakemnt && mount(dev_name, ".", cifs_fstype, flags, options)) {
+       if (!parsed_info->fakemnt) {
+               toggle_dac_capability(0, 1);
+               rc = mount(orig_dev, ".", cifs_fstype, parsed_info->flags, options);
+               toggle_dac_capability(0, 0);
+               if (rc == 0)
+                       goto do_mtab;
+
                switch (errno) {
                case ECONNREFUSED:
                case EHOSTUNREACH:
-                       if (addr) {
-                               addr = addr->ai_next;
-                               if (addr)
-                                       goto mount_retry;
+                       currentaddress = nextaddress;
+                       if (currentaddress) {
+                               nextaddress = strchr(currentaddress, ',');
+                               if (nextaddress)
+                                       *nextaddress++ = '\0';
                        }
-                       break;
+                       goto mount_retry;
                case ENODEV:
-                       fprintf(stderr, "mount error: cifs filesystem not supported by the system\n");
+                       fprintf(stderr,
+                               "mount error: %s filesystem not supported by the system\n", cifs_fstype);
                        break;
                case ENXIO:
-                       if(retry == 0) {
-                               retry = 1;
-                               if (uppercase_string(dev_name) &&
-                                   uppercase_string(share_name) &&
-                                   uppercase_string(prefixpath)) {
-                                       fprintf(stderr, "retrying with upper case share name\n");
-                                       goto mount_retry;
-                               }
+                       if (!already_uppercased &&
+                           uppercase_string(parsed_info->host) &&
+                           uppercase_string(parsed_info->share) &&
+                           uppercase_string(parsed_info->prefix)) {
+                               fprintf(stderr,
+                                       "Retrying with upper case share name\n");
+                               already_uppercased = 1;
+                               goto mount_retry;
                        }
                }
-               fprintf(stderr, "mount error(%d): %s\n", errno, strerror(errno));
-               fprintf(stderr, "Refer to the mount.cifs(8) manual page (e.g. man "
-                      "mount.cifs)\n");
+               fprintf(stderr, "mount error(%d): %s\n", errno,
+                       strerror(errno));
+               fprintf(stderr,
+                       "Refer to the %s(8) manual page (e.g. man "
+                       "%s)\n", thisprogram, thisprogram);
                rc = EX_FAIL;
                goto mount_exit;
        }
 
-       if (nomtab)
-               goto mount_exit;
-       atexit(unlock_mtab);
-       rc = lock_mtab();
-       if (rc) {
-               fprintf(stderr, "cannot lock mtab");
-               goto mount_exit;
-       }
-       pmntfile = setmntent(MOUNTED, "a+");
-       if (!pmntfile) {
-               fprintf(stderr, "could not update mount table\n");
-               unlock_mtab();
-               rc = EX_FILEIO;
-               goto mount_exit;
-       }
-       mountent.mnt_fsname = dev_name;
-       mountent.mnt_dir = mountpoint;
-       mountent.mnt_type = (char *)(void *)cifs_fstype;
-       mountent.mnt_opts = (char *)malloc(220);
-       if(mountent.mnt_opts) {
-               char * mount_user = getusername();
-               memset(mountent.mnt_opts,0,200);
-               if(flags & MS_RDONLY)
-                       strlcat(mountent.mnt_opts,"ro",220);
-               else
-                       strlcat(mountent.mnt_opts,"rw",220);
-               if(flags & MS_MANDLOCK)
-                       strlcat(mountent.mnt_opts,",mand",220);
-               if(flags & MS_NOEXEC)
-                       strlcat(mountent.mnt_opts,",noexec",220);
-               if(flags & MS_NOSUID)
-                       strlcat(mountent.mnt_opts,",nosuid",220);
-               if(flags & MS_NODEV)
-                       strlcat(mountent.mnt_opts,",nodev",220);
-               if(flags & MS_SYNCHRONOUS)
-                       strlcat(mountent.mnt_opts,",sync",220);
-               if(mount_user) {
-                       if(getuid() != 0) {
-                               strlcat(mountent.mnt_opts,
-                                       ",user=", 220);
-                               strlcat(mountent.mnt_opts,
-                                       mount_user, 220);
-                       }
+do_mtab:
+       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);
        }
-       mountent.mnt_freq = 0;
-       mountent.mnt_passno = 0;
-       rc = addmntent(pmntfile,&mountent);
-       endmntent(pmntfile);
-       unlock_mtab();
-       SAFE_FREE(mountent.mnt_opts);
-       if (rc)
-               rc = EX_FILEIO;
+
 mount_exit:
-       if(mountpassword) {
-               int len = strlen(mountpassword);
-               memset(mountpassword,0,len);
-               SAFE_FREE(mountpassword);
+       if (parsed_info) {
+               memset(parsed_info->password, 0, sizeof(parsed_info->password));
+               munmap(parsed_info, sizeof(*parsed_info));
        }
-
-       if (addrhead)
-               freeaddrinfo(addrhead);
        SAFE_FREE(options);
        SAFE_FREE(orgoptions);
-       SAFE_FREE(resolved_path);
-       SAFE_FREE(share_name);
-       exit(rc);
+       return rc;
 }