2 * Mount helper utility for Linux CIFS VFS (virtual filesystem) client
3 * Copyright (C) 2003,2010 Steve French (sfrench@us.ibm.com)
4 * Copyright (C) 2008 Jeremy Allison (jra@samba.org)
5 * Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #endif /* HAVE_CONFIG_H */
31 #include <sys/types.h>
32 #include <sys/mount.h>
34 #include <sys/utsname.h>
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
49 #ifdef HAVE_SYS_FSUID_H
50 #include <sys/fsuid.h>
51 #endif /* HAVE_SYS_FSUID_H */
54 #else /* HAVE_LIBCAP_NG */
56 #include <sys/prctl.h>
57 #endif /* HAVE_PRCTL */
59 #include <sys/capability.h>
60 #endif /* HAVE_LIBCAP */
61 #endif /* HAVE_LIBCAP_NG */
64 #include "resolve_host.h"
74 /* private flags - clear these before passing to kernel */
75 #define MS_USERS 0x40000000
76 #define MS_USER 0x80000000
78 #define MAX_UNC_LEN 1024
80 /* I believe that the kernel limits options data to a page */
81 #define MAX_OPTIONS_LEN 4096
83 /* max length of mtab options */
84 #define MTAB_OPTIONS_LEN 220
87 * Maximum length of "share" portion of a UNC. I have no idea if this is at
88 * all valid. According to MSDN, the typical max length of any component is
89 * 255, so use that here.
91 #define MAX_SHARE_LEN 256
93 /* max length of username (somewhat made up here) */
94 #define MAX_USERNAME_SIZE 32
97 #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
100 #define MOUNT_PASSWD_SIZE 128
101 #define MAX_DOMAIN_SIZE 64
104 * mount.cifs has been the subject of many "security" bugs that have arisen
105 * because of users and distributions installing it as a setuid root program
106 * before it had been audited for security holes. The default behavior is
107 * now to allow mount.cifs to be run as a setuid root program. Some admins
108 * may want to disable this fully, so this switch remains in place.
110 #define CIFS_DISABLE_SETUID_CAPABILITY 0
113 * When an unprivileged user runs a setuid mount.cifs, we set certain mount
114 * flags by default. These defaults can be changed here.
116 #define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
119 * Values for parsing a credentials file.
121 #define CRED_UNPARSEABLE 0
127 * Values for parsing command line options.
133 #define OPT_USER_XATTR 3
142 #define OPT_FILE_MODE 12
144 #define OPT_DIR_MODE 14
146 #define OPT_NO_SUID 16
148 #define OPT_NO_DEV 18
150 #define OPT_NO_LOCK 20
151 #define OPT_NO_EXEC 21
156 #define OPT_REMOUNT 26
158 #define OPT_NOMAND 28
160 #define OPT_BKUPUID 30
161 #define OPT_BKUPGID 31
162 #define OPT_NOFAIL 32
164 #define MNT_TMP_FILE "/.mtab.cifs.XXXXXX"
166 /* struct for holding parsed mount info for use by privleged process */
167 struct parsed_mount_info {
169 char host[NI_MAXHOST + 1];
170 char share[MAX_SHARE_LEN + 1];
171 char prefix[PATH_MAX + 1];
172 char options[MAX_OPTIONS_LEN];
173 char domain[MAX_DOMAIN_SIZE + 1];
174 char username[MAX_USERNAME_SIZE + 1];
175 char password[MOUNT_PASSWD_SIZE + 1];
176 char addrlist[MAX_ADDR_LIST_LEN];
177 unsigned int got_user:1;
178 unsigned int got_password:1;
179 unsigned int fakemnt:1;
180 unsigned int nomtab:1;
181 unsigned int verboseflag:1;
182 unsigned int nofail:1;
185 const char *thisprogram;
186 const char *cifs_fstype = "cifs";
188 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
190 static int check_setuid(void)
193 fprintf(stderr, "This program is not installed setuid root - "
194 " \"user\" CIFS mounts not supported.\n");
198 #if CIFS_DISABLE_SETUID_CAPABILITY
199 if (getuid() && !geteuid()) {
200 printf("This mount.cifs program has been built with the "
201 "ability to run as a setuid root program disabled.\n");
204 #endif /* CIFS_DISABLE_SETUID_CAPABILITY */
210 check_fstab(const char *progname, const char *mountpoint, const char *devname,
216 /* make sure this mount is listed in /etc/fstab */
217 fstab = setmntent(_PATH_MNTTAB, "r");
219 fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_MNTTAB);
223 while ((mnt = getmntent(fstab))) {
224 if (!strcmp(mountpoint, mnt->mnt_dir))
229 if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
230 fprintf(stderr, "%s: permission denied: no match for "
231 "%s found in %s\n", progname, mountpoint, _PATH_MNTTAB);
236 * 'mount' munges the options from fstab before passing them
237 * to us. It is non-trivial to test that we have the correct
238 * set of options. We don't want to trust what the user
239 * gave us, so just take whatever is in /etc/fstab.
242 *options = strdup(mnt->mnt_opts);
249 open nofollow - avoid symlink exposure?
250 get owner of dir see if matches self or if root
251 call system(umount argv) etc.
255 static int mount_usage(FILE * stream)
257 fprintf(stream, "\nUsage: %s <remotetarget> <dir> -o <options>\n",
259 fprintf(stream, "\nMount the remote target, specified as a UNC name,");
260 fprintf(stream, " to a local directory.\n\nOptions:\n");
261 fprintf(stream, "\tuser=<arg>\n\tpass=<arg>\n\tdom=<arg>\n");
262 fprintf(stream, "\nLess commonly used options:");
264 "\n\tcredentials=<filename>,guest,perm,noperm,setuids,nosetuids,rw,ro,");
266 "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
268 "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
270 "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign,fsc");
272 "\n\nOptions not needed for servers supporting CIFS Unix extensions");
274 "\n\t(e.g. unneeded for mounts to most Samba versions):");
276 "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu");
277 fprintf(stream, "\n\nRarely used options:");
279 "\n\tport=<tcpport>,rsize=<size>,wsize=<size>,unc=<unc_name>,ip=<ip_address>,");
281 "\n\tdev,nodev,nouser_xattr,netbiosname=<OUR_RFC1001NAME>,hard,soft,intr,");
283 "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl");
285 "\n\nOptions are described in more detail in the manual page");
286 fprintf(stream, "\n\tman 8 mount.cifs\n");
287 fprintf(stream, "\nTo display the version number of the mount helper:");
288 fprintf(stream, "\n\t%s -V\n", thisprogram);
290 if (stream == stderr)
296 * CIFS has to "escape" commas in the password field so that they don't
297 * end up getting confused for option delimiters. Copy password into pw
298 * field, turning any commas into double commas.
300 static int set_password(struct parsed_mount_info *parsed_info, const char *src)
302 char *dst = parsed_info->password;
303 unsigned int i = 0, j = 0;
309 if (j > sizeof(parsed_info->password)) {
310 fprintf(stderr, "Converted password too long!\n");
315 parsed_info->got_password = 1;
319 #ifdef HAVE_LIBCAP_NG
321 drop_capabilities(int parent)
323 capng_setpid(getpid());
324 capng_clear(CAPNG_SELECT_BOTH);
326 if (capng_updatev(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH, CAP_DAC_OVERRIDE, -1)) {
327 fprintf(stderr, "Unable to update capability set.\n");
330 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) {
331 fprintf(stderr, "Unable to update capability set.\n");
335 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
336 fprintf(stderr, "Unable to update capability set.\n");
340 if (capng_apply(CAPNG_SELECT_BOTH)) {
341 fprintf(stderr, "Unable to apply new capability set.\n");
348 toggle_dac_capability(int writable, int enable)
350 unsigned int capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
352 if (capng_update(enable ? CAPNG_ADD : CAPNG_DROP, CAPNG_EFFECTIVE, capability)) {
353 fprintf(stderr, "Unable to update capability set.\n");
356 if (capng_apply(CAPNG_SELECT_CAPS)) {
357 fprintf(stderr, "Unable to apply new capability set.\n");
362 #else /* HAVE_LIBCAP_NG */
366 prune_bounding_set(void)
369 static int bounding_set_cleared;
371 if (bounding_set_cleared)
374 for (i = 0; i <= CAP_LAST_CAP && rc == 0; ++i)
375 rc = prctl(PR_CAPBSET_DROP, i);
378 fprintf(stderr, "Unable to clear capability bounding set: %d\n", rc);
382 ++bounding_set_cleared;
385 #else /* HAVE_PRCTL */
387 prune_bounding_set(void)
391 #endif /* HAVE_PRCTL */
393 drop_capabilities(int parent)
397 cap_value_t cap_list[3];
399 rc = prune_bounding_set();
403 caps = cap_get_proc();
405 fprintf(stderr, "Unable to get current capability set: %s\n",
410 if (cap_clear(caps) == -1) {
411 fprintf(stderr, "Unable to clear capability set: %s\n",
417 if (parent || getuid() == 0) {
419 cap_list[0] = CAP_DAC_READ_SEARCH;
421 cap_list[1] = CAP_DAC_OVERRIDE;
422 cap_list[2] = CAP_SYS_ADMIN;
425 if (cap_set_flag(caps, CAP_PERMITTED, ncaps, cap_list, CAP_SET) == -1) {
426 fprintf(stderr, "Unable to set permitted capabilities: %s\n",
432 cap_list[0] = CAP_SYS_ADMIN;
433 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
434 fprintf(stderr, "Unable to set effective capabilities: %s\n",
442 if (cap_set_proc(caps) != 0) {
443 fprintf(stderr, "Unable to set current process capabilities: %s\n",
453 toggle_dac_capability(int writable, int enable)
457 cap_value_t capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
459 caps = cap_get_proc();
461 fprintf(stderr, "Unable to get current capability set: %s\n",
466 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &capability,
467 enable ? CAP_SET : CAP_CLEAR) == -1) {
468 fprintf(stderr, "Unable to %s effective capabilities: %s\n",
469 enable ? "set" : "clear", strerror(errno));
474 if (cap_set_proc(caps) != 0) {
475 fprintf(stderr, "Unable to set current process capabilities: %s\n",
483 #else /* HAVE_LIBCAP */
485 drop_capabilities(int parent __attribute((unused)))
491 toggle_dac_capability(int writable __attribute((unused)), int enable __attribute((unused)))
495 #endif /* HAVE_LIBCAP */
496 #endif /* HAVE_LIBCAP_NG */
498 static void null_terminate_endl(char *source)
500 char *newline = strchr(source, '\n');
506 * Parse a line from the credentials file. Changes target to first
507 * character after '=' on 'line' and returns the value type of the line
508 * Returns CRED_UNPARSEABLE on failure or if either parameter is NULL.
510 static int parse_cred_line(char *line, char **target)
512 if (line == NULL || target == NULL)
515 /* position target at first char of value */
516 *target = strchr(line, '=');
521 /* tell the caller which value target points to */
522 if (strncasecmp("user", line, 4) == 0)
524 if (strncasecmp("pass", line, 4) == 0)
526 if (strncasecmp("dom", line, 3) == 0)
530 return CRED_UNPARSEABLE;
533 static int open_cred_file(char *file_name,
534 struct parsed_mount_info *parsed_info)
536 char *line_buf = NULL;
537 char *temp_val = NULL;
540 const int line_buf_size = 4096;
541 const int min_non_white = 10;
543 i = toggle_dac_capability(0, 1);
547 i = access(file_name, R_OK);
549 toggle_dac_capability(0, 0);
554 fs = fopen(file_name, "r");
556 toggle_dac_capability(0, 0);
561 i = toggle_dac_capability(0, 0);
565 line_buf = (char *)malloc(line_buf_size);
566 if (line_buf == NULL) {
571 /* parse line from credentials file */
572 while (fgets(line_buf, line_buf_size, fs)) {
573 /* eat leading white space */
574 for (i = 0; i < line_buf_size - min_non_white + 1; i++) {
575 if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
578 null_terminate_endl(line_buf);
580 /* parse next token */
581 switch (parse_cred_line(line_buf + i, &temp_val)) {
583 strlcpy(parsed_info->username, temp_val,
584 sizeof(parsed_info->username));
585 parsed_info->got_user = 1;
588 i = set_password(parsed_info, temp_val);
593 if (parsed_info->verboseflag)
594 fprintf(stderr, "domain=%s\n",
596 strlcpy(parsed_info->domain, temp_val,
597 sizeof(parsed_info->domain));
599 case CRED_UNPARSEABLE:
600 if (parsed_info->verboseflag)
601 fprintf(stderr, "Credential formatted "
603 temp_val ? temp_val : "(null)");
612 /* make sure passwords are scrubbed from memory */
613 if (line_buf != NULL)
614 memset(line_buf, 0, line_buf_size);
620 get_password_from_file(int file_descript, char *filename,
621 struct parsed_mount_info *parsed_info)
624 char buf[sizeof(parsed_info->password) + 1];
626 if (filename != NULL) {
627 rc = toggle_dac_capability(0, 1);
631 rc = access(filename, R_OK);
634 "mount.cifs failed: access check of %s failed: %s\n",
635 filename, strerror(errno));
636 toggle_dac_capability(0, 0);
640 file_descript = open(filename, O_RDONLY);
641 if (file_descript < 0) {
643 "mount.cifs failed. %s attempting to open password file %s\n",
644 strerror(errno), filename);
645 toggle_dac_capability(0, 0);
649 rc = toggle_dac_capability(0, 0);
656 memset(buf, 0, sizeof(buf));
657 rc = read(file_descript, buf, sizeof(buf) - 1);
660 "mount.cifs failed. Error %s reading password file\n",
666 rc = set_password(parsed_info, buf);
669 if (filename != NULL)
670 close(file_descript);
675 * Returns OPT_ERROR on unparsable token.
677 static int parse_opt_token(const char *token)
682 if (strncmp(token, "users", 5) == 0)
684 if (strncmp(token, "user_xattr", 10) == 0)
685 return OPT_USER_XATTR;
686 if (strncmp(token, "user", 4) == 0)
688 if (strncmp(token, "pass", 4) == 0)
690 if (strncmp(token, "sec", 3) == 0)
692 if (strncmp(token, "ip", 2) == 0)
694 if (strncmp(token, "unc", 3) == 0 ||
695 strncmp(token, "target", 6) == 0 ||
696 strncmp(token, "path", 4) == 0)
698 if (strncmp(token, "dom", 3) == 0 || strncmp(token, "workg", 5) == 0)
700 if (strncmp(token, "cred", 4) == 0)
702 if (strncmp(token, "uid", 3) == 0)
704 if (strncmp(token, "cruid", 5) == 0)
706 if (strncmp(token, "gid", 3) == 0)
708 if (strncmp(token, "fmask", 5) == 0)
710 if (strncmp(token, "file_mode", 9) == 0)
711 return OPT_FILE_MODE;
712 if (strncmp(token, "dmask", 5) == 0)
714 if (strncmp(token, "dir_mode", 4) == 0 || strncmp(token, "dirm", 4) == 0)
716 if (strncmp(token, "nosuid", 6) == 0)
718 if (strncmp(token, "suid", 4) == 0)
720 if (strncmp(token, "nodev", 5) == 0)
722 if (strncmp(token, "nobrl", 5) == 0 || strncmp(token, "nolock", 6) == 0)
724 if (strncmp(token, "mand", 4) == 0)
726 if (strncmp(token, "nomand", 6) == 0)
728 if (strncmp(token, "dev", 3) == 0)
730 if (strncmp(token, "noexec", 6) == 0)
732 if (strncmp(token, "exec", 4) == 0)
734 if (strncmp(token, "guest", 5) == 0)
736 if (strncmp(token, "ro", 2) == 0)
738 if (strncmp(token, "rw", 2) == 0 && strlen(token) == 2)
740 if (strncmp(token, "remount", 7) == 0)
742 if (strncmp(token, "_netdev", 7) == 0)
744 if (strncmp(token, "backupuid", 9) == 0)
746 if (strncmp(token, "backupgid", 9) == 0)
748 if (strncmp(token, "nofail", 6) == 0)
755 parse_options(const char *data, struct parsed_mount_info *parsed_info)
759 char *next_keyword = NULL;
760 char *out = parsed_info->options;
761 unsigned long *filesys_flags = &parsed_info->flags;
770 uid_t uid, cruid = 0, bkupuid = 0;
771 gid_t gid, bkupgid = 0;
776 * max 32-bit uint in decimal is 4294967295 which is 10 chars wide
777 * +1 for NULL, and +1 for good measure
781 /* make sure we're starting from beginning */
784 /* BB fixme check for separator override BB */
797 * format is keyword,keyword2=value2,keyword3=value3...
798 * data = next keyword
799 * value = next value ie stuff after equal sign
801 while (data && *data) {
802 next_keyword = strchr(data, ','); /* BB handle sep= */
804 /* temporarily null terminate end of keyword=value pair */
808 /* temporarily null terminate keyword if there's a value */
810 if ((equals = strchr(data, '=')) != NULL) {
815 switch(parse_opt_token(data)) {
817 if (!value || !*value) {
818 *filesys_flags |= MS_USERS;
824 if (!value || !*value) {
825 if (data[4] == '\0') {
826 *filesys_flags |= MS_USER;
830 "username specified with no parameter\n");
834 strlcpy(parsed_info->username, value,
835 sizeof(parsed_info->username));
836 parsed_info->got_user = 1;
841 if (parsed_info->got_password) {
843 "password specified twice, ignoring second\n");
846 if (!value || !*value) {
847 parsed_info->got_password = 1;
850 rc = set_password(parsed_info, value);
857 if (!strncmp(value, "none", 4) ||
858 !strncmp(value, "krb5", 4))
859 parsed_info->got_password = 1;
864 if (!value || !*value) {
866 "target ip address argument missing\n");
867 } else if (strnlen(value, MAX_ADDRESS_LEN) <=
869 strcpy(parsed_info->addrlist, value);
870 if (parsed_info->verboseflag)
872 "ip address %s override specified\n",
876 fprintf(stderr, "ip address too long\n");
882 /* unc || target || path */
884 if (!value || !*value) {
886 "invalid path to network resource\n");
889 rc = parse_unc(value, parsed_info);
894 /* dom || workgroup */
896 if (!value || !*value) {
897 fprintf(stderr, "CIFS: invalid domain name\n");
900 if (strnlen(value, sizeof(parsed_info->domain)) >=
901 sizeof(parsed_info->domain)) {
902 fprintf(stderr, "domain name too long\n");
905 strlcpy(parsed_info->domain, value,
906 sizeof(parsed_info->domain));
910 if (!value || !*value) {
912 "invalid credential file name specified\n");
915 rc = open_cred_file(value, parsed_info);
918 "error %d (%s) opening credential file %s\n",
919 rc, strerror(rc), value);
925 if (!value || !*value)
929 pw = getpwnam(value);
936 uid = strtoul(value, &ep, 10);
937 if (errno == 0 && *ep == '\0')
940 fprintf(stderr, "bad option uid=\"%s\"\n", value);
943 if (!value || !*value)
947 pw = getpwnam(value);
954 cruid = strtoul(value, &ep, 10);
955 if (errno == 0 && *ep == '\0')
958 fprintf(stderr, "bad option: cruid=\"%s\"\n", value);
961 if (!value || !*value)
965 gr = getgrnam(value);
972 gid = strtoul(value, &ep, 10);
973 if (errno == 0 && *ep == '\0')
976 fprintf(stderr, "bad option: gid=\"%s\"\n", value);
978 /* fmask falls through to file_mode */
981 "WARNING: CIFS mount option 'fmask' is\
982 deprecated. Use 'file_mode' instead.\n");
983 data = "file_mode"; /* BB fix this */
985 if (!value || !*value) {
987 "Option '%s' requires a numerical argument\n",
994 "WARNING: '%s' not expressed in octal.\n",
998 /* dmask falls through to dir_mode */
1001 "WARNING: CIFS mount option 'dmask' is\
1002 deprecated. Use 'dir_mode' instead.\n");
1005 if (!value || !*value) {
1007 "Option '%s' requires a numerical argument\n",
1012 if (value[0] != '0')
1014 "WARNING: '%s' not expressed in octal.\n",
1018 *filesys_flags |= MS_NOSUID;
1021 *filesys_flags &= ~MS_NOSUID;
1024 *filesys_flags |= MS_NODEV;
1027 *filesys_flags &= ~MS_MANDLOCK;
1030 *filesys_flags |= MS_MANDLOCK;
1033 *filesys_flags &= ~MS_MANDLOCK;
1036 *filesys_flags &= ~MS_NODEV;
1039 *filesys_flags |= MS_NOEXEC;
1042 *filesys_flags &= ~MS_NOEXEC;
1045 parsed_info->got_user = 1;
1046 parsed_info->got_password = 1;
1049 *filesys_flags |= MS_RDONLY;
1052 *filesys_flags &= ~MS_RDONLY;
1055 *filesys_flags |= MS_REMOUNT;
1060 if (!value || !*value)
1065 bkupuid = strtoul(value, &ep, 10);
1066 if (errno == 0 && *ep == '\0')
1069 pw = getpwnam(value);
1072 "bad user name \"%s\"\n", value);
1076 bkupuid = pw->pw_uid;
1079 if (!value || !*value)
1084 bkupgid = strtoul(value, &ep, 10);
1085 if (errno == 0 && *ep == '\0')
1088 gr = getgrnam(value);
1091 "bad group name \"%s\"\n", value);
1095 bkupgid = gr->gr_gid;
1098 parsed_info->nofail = 1;
1102 /* check size before copying option to buffer */
1103 word_len = strlen(data);
1105 word_len += 1 + strlen(value);
1107 /* need 2 extra bytes for comma and null byte */
1108 if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
1109 fprintf(stderr, "Options string too long\n");
1113 /* put back equals sign, if any */
1117 /* go ahead and copy */
1119 strlcat(out, ",", MAX_OPTIONS_LEN);
1121 strlcat(out, data, MAX_OPTIONS_LEN);
1122 out_len = strlen(out);
1124 data = next_keyword;
1128 /* special-case the uid and gid */
1130 word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", uid);
1132 /* comma + "uid=" + terminating NULL == 6 */
1133 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
1134 fprintf(stderr, "Options string too long\n");
1139 strlcat(out, ",", MAX_OPTIONS_LEN);
1142 snprintf(out + out_len, word_len + 5, "uid=%s", txtbuf);
1143 out_len = strlen(out);
1146 word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", cruid);
1148 /* comma + "cruid=" + terminating NULL == 8 */
1149 if (out_len + word_len + 8 > MAX_OPTIONS_LEN) {
1150 fprintf(stderr, "Options string too long\n");
1155 strlcat(out, ",", MAX_OPTIONS_LEN);
1158 snprintf(out + out_len, word_len + 7, "cruid=%s", txtbuf);
1159 out_len = strlen(out);
1162 word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", gid);
1164 /* comma + "gid=" + terminating NULL == 6 */
1165 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
1166 fprintf(stderr, "Options string too long\n");
1171 strlcat(out, ",", MAX_OPTIONS_LEN);
1174 snprintf(out + out_len, word_len + 5, "gid=%s", txtbuf);
1177 word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupuid);
1179 /* comma + "backupuid=" + terminating NULL == 12 */
1180 if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
1181 fprintf(stderr, "Options string too long\n");
1186 strlcat(out, ",", MAX_OPTIONS_LEN);
1189 snprintf(out + out_len, word_len + 11, "backupuid=%s", txtbuf);
1190 out_len = strlen(out);
1193 word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupgid);
1195 /* comma + "backkupgid=" + terminating NULL == 12 */
1196 if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
1197 fprintf(stderr, "Options string too long\n");
1202 strlcat(out, ",", MAX_OPTIONS_LEN);
1205 snprintf(out + out_len, word_len + 11, "backupgid=%s", txtbuf);
1211 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
1213 int length = strnlen(unc_name, MAX_UNC_LEN);
1214 const char *host, *share, *prepath;
1215 size_t hostlen, sharelen, prepathlen;
1217 if (length > (MAX_UNC_LEN - 1)) {
1218 fprintf(stderr, "mount error: UNC name too long\n");
1223 fprintf(stderr, "mount error: UNC name too short\n");
1227 if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
1228 (strncasecmp("smb://", unc_name, 6) == 0)) {
1230 "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
1235 if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
1236 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1240 host = unc_name + 2;
1241 hostlen = strcspn(host, "/\\");
1243 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1246 share = host + hostlen + 1;
1248 if (hostlen + 1 > sizeof(parsed_info->host)) {
1249 fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
1253 sharelen = strcspn(share, "/\\");
1254 if (sharelen + 1 > sizeof(parsed_info->share)) {
1255 fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
1259 prepath = share + sharelen;
1260 if (*prepath != '\0')
1263 prepathlen = strlen(prepath);
1265 if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
1266 fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
1270 /* copy pieces into their resepective buffers */
1271 memcpy(parsed_info->host, host, hostlen);
1272 memcpy(parsed_info->share, share, sharelen);
1273 memcpy(parsed_info->prefix, prepath, prepathlen);
1278 static int get_pw_from_env(struct parsed_mount_info *parsed_info)
1282 if (getenv("PASSWD"))
1283 rc = set_password(parsed_info, getenv("PASSWD"));
1284 else if (getenv("PASSWD_FD"))
1285 rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
1287 else if (getenv("PASSWD_FILE"))
1288 rc = get_password_from_file(0, getenv("PASSWD_FILE"),
1294 static struct option longopts[] = {
1295 {"all", 0, NULL, 'a'},
1296 {"help", 0, NULL, 'h'},
1297 {"move", 0, NULL, 'm'},
1298 {"bind", 0, NULL, 'b'},
1299 {"read-only", 0, NULL, 'r'},
1300 {"ro", 0, NULL, 'r'},
1301 {"verbose", 0, NULL, 'v'},
1302 {"version", 0, NULL, 'V'},
1303 {"read-write", 0, NULL, 'w'},
1304 {"rw", 0, NULL, 'w'},
1305 {"options", 1, NULL, 'o'},
1306 {"type", 1, NULL, 't'},
1307 {"uid", 1, NULL, '1'},
1308 {"gid", 1, NULL, '2'},
1309 {"user", 1, NULL, 'u'},
1310 {"username", 1, NULL, 'u'},
1311 {"dom", 1, NULL, 'd'},
1312 {"domain", 1, NULL, 'd'},
1313 {"password", 1, NULL, 'p'},
1314 {"pass", 1, NULL, 'p'},
1315 {"credentials", 1, NULL, 'c'},
1316 {"port", 1, NULL, 'P'},
1317 {"sloppy", 0, NULL, 's'},
1321 /* convert a string to uppercase. return false if the string
1322 * wasn't ASCII. Return success on a NULL ptr */
1323 static int uppercase_string(char *string)
1329 /* check for unicode */
1330 if ((unsigned char)string[0] & 0x80)
1332 *string = toupper((unsigned char)*string);
1339 static void print_cifs_mount_version(void)
1341 printf("mount.cifs version: %s\n", VERSION);
1345 * This function borrowed from fuse-utils...
1347 * glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
1348 * newlines embedded within the text fields. To make sure no one corrupts
1349 * the mtab, fail the mount if there are embedded newlines.
1351 static int check_newline(const char *progname, const char *name)
1354 for (s = "\n"; *s; s++) {
1355 if (strchr(name, *s)) {
1357 "%s: illegal character 0x%02x in mount entry\n",
1365 static int check_mtab(const char *progname, const char *devname,
1368 if (check_newline(progname, devname) || check_newline(progname, dir))
1374 add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstype)
1376 int rc = 0, tmprc, fd;
1378 char *mount_user = NULL;
1379 struct mntent mountent;
1380 struct stat statbuf;
1382 sigset_t mask, oldmask;
1386 mount_user = getusername(uid);
1389 * Set the real uid to the effective uid. This prevents unprivileged
1390 * users from sending signals to this process, though ^c on controlling
1391 * terminal should still work.
1393 rc = setreuid(geteuid(), -1);
1395 fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
1400 rc = sigfillset(&mask);
1402 fprintf(stderr, "Unable to set filled signal mask\n");
1406 rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
1408 fprintf(stderr, "Unable to make process ignore signals\n");
1412 rc = toggle_dac_capability(1, 1);
1416 atexit(unlock_mtab);
1419 fprintf(stderr, "cannot lock mtab");
1424 pmntfile = setmntent(MOUNTED, "a+");
1426 fprintf(stderr, "could not update mount table\n");
1432 fd = fileno(pmntfile);
1434 fprintf(stderr, "mntent does not appear to be valid\n");
1440 rc = fstat(fd, &statbuf);
1442 fprintf(stderr, "unable to fstat open mtab\n");
1443 endmntent(pmntfile);
1449 mountent.mnt_fsname = devname;
1450 mountent.mnt_dir = mountpoint;
1451 mountent.mnt_type = (char *)(void *)fstype;
1452 mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
1453 if (mountent.mnt_opts) {
1454 if (flags & MS_RDONLY)
1455 strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
1457 strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
1459 if (flags & MS_MANDLOCK)
1460 strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
1461 if (flags & MS_NOEXEC)
1462 strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
1463 if (flags & MS_NOSUID)
1464 strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
1465 if (flags & MS_NODEV)
1466 strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
1467 if (flags & MS_SYNCHRONOUS)
1468 strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
1470 strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
1471 strlcat(mountent.mnt_opts, mount_user,
1475 mountent.mnt_freq = 0;
1476 mountent.mnt_passno = 0;
1477 rc = addmntent(pmntfile, &mountent);
1479 int ignore __attribute__((unused));
1481 fprintf(stderr, "unable to add mount entry to mtab\n");
1482 ignore = ftruncate(fd, statbuf.st_size);
1485 tmprc = my_endmntent(pmntfile, statbuf.st_size);
1487 fprintf(stderr, "error %d detected on close of mtab\n", tmprc);
1491 SAFE_FREE(mountent.mnt_opts);
1493 toggle_dac_capability(1, 0);
1494 sigprocmask(SIG_SETMASK, &oldmask, NULL);
1500 del_mtab(char *mountpoint)
1503 FILE *mnttmp, *mntmtab;
1504 struct mntent *mountent;
1505 char *mtabfile, *mtabdir, *mtabtmpfile;
1507 mtabfile = strdup(MOUNTED);
1508 mtabdir = dirname(mtabfile);
1509 mtabdir = realloc(mtabdir, strlen(mtabdir) + strlen(MNT_TMP_FILE) + 2);
1511 fprintf(stderr, "del_mtab: cannot determine current mtab path");
1516 mtabtmpfile = strcat(mtabdir, MNT_TMP_FILE);
1518 fprintf(stderr, "del_mtab: cannot allocate memory to tmp file");
1523 atexit(unlock_mtab);
1526 fprintf(stderr, "del_mtab: cannot lock mtab");
1531 mtabtmpfile = mktemp(mtabtmpfile);
1533 fprintf(stderr, "del_mtab: cannot setup tmp file destination");
1538 mntmtab = setmntent(MOUNTED, "r");
1540 fprintf(stderr, "del_mtab: could not update mount table\n");
1545 mnttmp = setmntent(mtabtmpfile, "w");
1547 fprintf(stderr, "del_mtab: could not update mount table\n");
1553 while ((mountent = getmntent(mntmtab)) != NULL) {
1554 if (!strcmp(mountent->mnt_dir, mountpoint))
1556 rc = addmntent(mnttmp, mountent);
1558 fprintf(stderr, "del_mtab: unable to add mount entry to mtab\n");
1560 goto del_mtab_error;
1566 tmprc = my_endmntent(mnttmp, 0);
1568 fprintf(stderr, "del_mtab: error %d detected on close of tmp file\n", tmprc);
1570 goto del_mtab_error;
1573 if (rename(mtabtmpfile, MOUNTED)) {
1574 fprintf(stderr, "del_mtab: error %d when renaming mtab in place\n", errno);
1576 goto del_mtab_error;
1585 if (unlink(mtabtmpfile))
1586 fprintf(stderr, "del_mtab: failed to delete tmp file - %s\n",
1591 /* have the child drop root privileges */
1593 drop_child_privs(void)
1596 uid_t uid = getuid();
1597 gid_t gid = getgid();
1602 fprintf(stderr, "Unable set group identity: %s\n",
1610 fprintf(stderr, "Unable set user identity: %s\n",
1620 * If systemd is running and /bin/systemd-ask-password --
1621 * is available, then use that else fallback on getpass(..)
1623 * Returns: @input or NULL on error
1626 get_password(const char *prompt, char *input, int capacity)
1628 #ifdef ENABLE_SYSTEMD
1629 int is_systemd_running;
1632 /* We simply test whether the systemd cgroup hierarchy is
1634 is_systemd_running = (lstat("/sys/fs/cgroup", &a) == 0)
1635 && (lstat("/sys/fs/cgroup/systemd", &b) == 0)
1636 && (a.st_dev != b.st_dev);
1638 if (is_systemd_running) {
1640 FILE *ask_pass_fp = NULL;
1643 if (asprintf(&cmd, "/bin/systemd-ask-password \"%s\"", prompt) >= 0) {
1644 ask_pass_fp = popen (cmd, "re");
1649 ret = fgets(input, capacity, ask_pass_fp);
1650 pclose(ask_pass_fp);
1654 int len = strlen(input);
1655 if (input[len - 1] == '\n')
1656 input[len - 1] = '\0';
1663 * Falling back to getpass(..)
1664 * getpass is obsolete, but there's apparently nothing that replaces it
1666 char *tmp_pass = getpass(prompt);
1670 strncpy(input, tmp_pass, capacity - 1);
1671 input[capacity - 1] = '\0';
1673 /* zero-out the static buffer */
1674 memset(tmp_pass, 0, strlen(tmp_pass));
1680 assemble_mountinfo(struct parsed_mount_info *parsed_info,
1681 const char *thisprogram, const char *mountpoint,
1682 const char *orig_dev, char *orgoptions)
1686 rc = drop_capabilities(0);
1690 rc = drop_child_privs();
1695 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1700 /* enable any default user mount flags */
1701 parsed_info->flags |= CIFS_SETUID_FLAGS;
1704 rc = get_pw_from_env(parsed_info);
1709 rc = parse_options(orgoptions, parsed_info);
1715 if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
1716 fprintf(stderr, "%s: permission denied\n", thisprogram);
1722 parsed_info->flags &= ~(MS_USERS | MS_USER);
1724 rc = parse_unc(orig_dev, parsed_info);
1728 if (parsed_info->addrlist[0] == '\0')
1729 rc = resolve_host(parsed_info->host, parsed_info->addrlist);
1733 fprintf(stderr, "mount error: could not resolve address for "
1734 "%s: %s\n", parsed_info->host,
1735 rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
1739 fprintf(stderr, "mount error: problem parsing address "
1740 "list: %s\n", strerror(errno));
1744 if (!parsed_info->got_user) {
1746 * Note that the password will not be retrieved from the
1747 * USER env variable (ie user%password form) as there is
1748 * already a PASSWD environment varaible
1751 strlcpy(parsed_info->username, getenv("USER"),
1752 sizeof(parsed_info->username));
1754 strlcpy(parsed_info->username, getusername(getuid()),
1755 sizeof(parsed_info->username));
1756 parsed_info->got_user = 1;
1759 if (!parsed_info->got_password) {
1760 char tmp_pass[MOUNT_PASSWD_SIZE + 1];
1761 char *prompt = NULL;
1763 if(asprintf(&prompt, "Password for %s@%s: ", parsed_info->username, orig_dev) < 0)
1766 if (get_password(prompt ? prompt : "Password: ", tmp_pass, MOUNT_PASSWD_SIZE + 1)) {
1767 rc = set_password(parsed_info, tmp_pass);
1769 fprintf(stderr, "Error reading password, exiting\n");
1778 /* copy in user= string */
1779 if (parsed_info->got_user) {
1780 if (*parsed_info->options)
1781 strlcat(parsed_info->options, ",",
1782 sizeof(parsed_info->options));
1783 strlcat(parsed_info->options, "user=",
1784 sizeof(parsed_info->options));
1785 strlcat(parsed_info->options, parsed_info->username,
1786 sizeof(parsed_info->options));
1789 if (*parsed_info->domain) {
1790 if (*parsed_info->options)
1791 strlcat(parsed_info->options, ",",
1792 sizeof(parsed_info->options));
1793 strlcat(parsed_info->options, ",domain=",
1794 sizeof(parsed_info->options));
1795 strlcat(parsed_info->options, parsed_info->domain,
1796 sizeof(parsed_info->options));
1804 * chdir() into the mountpoint and determine "realpath". We assume here that
1805 * "mountpoint" is a statically allocated string and does not need to be freed.
1808 acquire_mountpoint(char **mountpointp)
1811 uid_t realuid, oldfsuid;
1816 * Acquire the necessary privileges to chdir to the mountpoint. If
1817 * the real uid is root, then we reacquire CAP_DAC_READ_SEARCH. If
1818 * it's not, then we change the fsuid to the real uid to ensure that
1819 * the mounting user actually has access to the mountpoint.
1821 * The mount(8) manpage does not state that users must be able to
1822 * chdir into the mountpoint in order to mount onto it, but if we
1823 * allow that, then an unprivileged user could use this program to
1824 * "probe" into directories to which he does not have access.
1828 dacrc = toggle_dac_capability(0, 1);
1832 oldfsuid = setfsuid(realuid);
1833 oldfsgid = setfsgid(getgid());
1836 rc = chdir(*mountpointp);
1838 fprintf(stderr, "Couldn't chdir to %s: %s\n", *mountpointp,
1844 mountpoint = realpath(".", NULL);
1846 fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
1847 *mountpointp, strerror(errno));
1851 *mountpointp = mountpoint;
1854 dacrc = toggle_dac_capability(0, 0);
1856 rc = rc ? rc : dacrc;
1858 uid_t __attribute__((unused)) uignore = setfsuid(oldfsuid);
1859 gid_t __attribute__((unused)) gignore = setfsgid(oldfsgid);
1865 int main(int argc, char **argv)
1868 char *orgoptions = NULL;
1869 char *mountpoint = NULL;
1870 char *options = NULL;
1871 char *orig_dev = NULL;
1872 char *currentaddress, *nextaddress;
1874 int already_uppercased = 0;
1876 size_t options_size = MAX_OPTIONS_LEN;
1877 struct parsed_mount_info *parsed_info = NULL;
1880 rc = check_setuid();
1884 rc = drop_capabilities(1);
1888 /* setlocale(LC_ALL, "");
1889 bindtextdomain(PACKAGE, LOCALEDIR);
1890 textdomain(PACKAGE); */
1892 if (!argc || !argv) {
1893 rc = mount_usage(stderr);
1897 thisprogram = basename(argv[0]);
1898 if (thisprogram == NULL)
1899 thisprogram = "mount.cifs";
1901 /* allocate parsed_info as shared anonymous memory range */
1902 parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
1903 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
1904 if (parsed_info == (struct parsed_mount_info *) -1) {
1906 fprintf(stderr, "Unable to allocate memory: %s\n",
1911 /* add sharename in opts string as unc= parm */
1912 while ((c = getopt_long(argc, argv, "?fhno:rsvVw",
1913 longopts, NULL)) != -1) {
1916 case 'h': /* help */
1917 rc = mount_usage(stdout);
1920 ++parsed_info->nomtab;
1923 orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
1929 case 'r': /* mount readonly */
1930 parsed_info->flags |= MS_RDONLY;
1933 ++parsed_info->verboseflag;
1936 print_cifs_mount_version();
1939 parsed_info->flags &= ~MS_RDONLY;
1942 ++parsed_info->fakemnt;
1948 fprintf(stderr, "unknown command-line option: %c\n", c);
1949 rc = mount_usage(stderr);
1954 if (argc < optind + 2) {
1955 rc = mount_usage(stderr);
1959 orig_dev = argv[optind];
1960 mountpoint = argv[optind + 1];
1962 /* chdir into mountpoint as soon as possible */
1963 rc = acquire_mountpoint(&mountpoint);
1968 * mount.cifs does privilege separation. Most of the code to handle
1969 * assembling the mount info is done in a child process that drops
1970 * privileges. The info is assembled in parsed_info which is a
1971 * shared, mmaped memory segment. The parent waits for the child to
1972 * exit and checks the return code. If it's anything but "0", then
1973 * the process exits without attempting anything further.
1977 fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
1982 rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
1983 orig_dev, orgoptions);
1988 if (!WIFEXITED(rc)) {
1989 fprintf(stderr, "Child process terminated abnormally.\n");
1993 rc = WEXITSTATUS(rc);
1998 options = calloc(options_size, 1);
2000 fprintf(stderr, "Unable to allocate memory.\n");
2005 currentaddress = parsed_info->addrlist;
2006 nextaddress = strchr(currentaddress, ',');
2008 *nextaddress++ = '\0';
2011 if (!currentaddress) {
2012 fprintf(stderr, "Unable to find suitable address.\n");
2013 rc = parsed_info->nofail ? 0 : EX_FAIL;
2016 strlcpy(options, "ip=", options_size);
2017 strlcat(options, currentaddress, options_size);
2019 strlcat(options, ",unc=\\\\", options_size);
2020 strlcat(options, parsed_info->host, options_size);
2021 strlcat(options, "\\", options_size);
2022 strlcat(options, parsed_info->share, options_size);
2024 if (*parsed_info->options) {
2025 strlcat(options, ",", options_size);
2026 strlcat(options, parsed_info->options, options_size);
2029 if (*parsed_info->prefix) {
2030 strlcat(options, ",prefixpath=", options_size);
2031 strlcat(options, parsed_info->prefix, options_size);
2035 strlcat(options, ",sloppy", options_size);
2037 if (parsed_info->verboseflag)
2038 fprintf(stderr, "%s kernel mount options: %s",
2039 thisprogram, options);
2041 if (parsed_info->got_password) {
2043 * Commas have to be doubled, or else they will
2044 * look like the parameter separator
2046 strlcat(options, ",pass=", options_size);
2047 strlcat(options, parsed_info->password, options_size);
2048 if (parsed_info->verboseflag)
2049 fprintf(stderr, ",pass=********");
2052 if (parsed_info->verboseflag)
2053 fprintf(stderr, "\n");
2055 rc = check_mtab(thisprogram, orig_dev, mountpoint);
2059 if (!parsed_info->fakemnt) {
2060 toggle_dac_capability(0, 1);
2061 rc = mount(orig_dev, ".", cifs_fstype, parsed_info->flags, options);
2062 toggle_dac_capability(0, 0);
2069 currentaddress = nextaddress;
2070 if (currentaddress) {
2071 nextaddress = strchr(currentaddress, ',');
2073 *nextaddress++ = '\0';
2078 "mount error: %s filesystem not supported by the system\n", cifs_fstype);
2081 if (!already_uppercased &&
2082 uppercase_string(parsed_info->host) &&
2083 uppercase_string(parsed_info->share) &&
2084 uppercase_string(parsed_info->prefix)) {
2086 "Retrying with upper case share name\n");
2087 already_uppercased = 1;
2091 fprintf(stderr, "mount error(%d): %s\n", errno,
2094 "Refer to the %s(8) manual page (e.g. man "
2095 "%s)\n", thisprogram, thisprogram);
2101 if (!parsed_info->nomtab && !mtab_unusable()) {
2102 if (parsed_info->flags & MS_REMOUNT) {
2103 rc = del_mtab(mountpoint);
2108 rc = add_mtab(orig_dev, mountpoint, parsed_info->flags, cifs_fstype);
2113 memset(parsed_info->password, 0, sizeof(parsed_info->password));
2114 munmap(parsed_info, sizeof(*parsed_info));
2117 SAFE_FREE(orgoptions);