2 * Mount helper utility for Linux CIFS VFS (virtual filesystem) client
3 * Copyright (C) 2003,2008 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 #else /* HAVE_LIBCAP_NG */
51 #include <sys/prctl.h>
52 #endif /* HAVE_PRCTL */
54 #include <sys/capability.h>
55 #endif /* HAVE_LIBCAP */
56 #endif /* HAVE_LIBCAP_NG */
68 /* private flags - clear these before passing to kernel */
69 #define MS_USERS 0x40000000
70 #define MS_USER 0x80000000
72 #define MAX_UNC_LEN 1024
74 /* I believe that the kernel limits options data to a page */
75 #define MAX_OPTIONS_LEN 4096
77 /* max length of mtab options */
78 #define MTAB_OPTIONS_LEN 220
81 * Maximum length of "share" portion of a UNC. I have no idea if this is at
82 * all valid. According to MSDN, the typical max length of any component is
83 * 255, so use that here.
85 #define MAX_SHARE_LEN 256
87 /* max length of username (somewhat made up here) */
88 #define MAX_USERNAME_SIZE 32
90 /* currently maximum length of IPv6 address string */
91 #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
93 /* limit list of addresses to 16 max-size addrs */
94 #define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16)
97 #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
100 #define MOUNT_PASSWD_SIZE 128
101 #define DOMAIN_SIZE 64
104 * value of the ver= option that gets passed to the kernel. Used to indicate
105 * behavioral changes introduced in the mount helper.
107 #define OPTIONS_VERSION "1"
110 * mount.cifs has been the subject of many "security" bugs that have arisen
111 * because of users and distributions installing it as a setuid root program
112 * before it had been audited for security holes. The default behavior is
113 * now to allow mount.cifs to be run as a setuid root program. Some admins
114 * may want to disable this fully, so this switch remains in place.
116 #define CIFS_DISABLE_SETUID_CAPABILITY 0
119 * When an unprivileged user runs a setuid mount.cifs, we set certain mount
120 * flags by default. These defaults can be changed here.
122 #define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
124 /* struct for holding parsed mount info for use by privleged process */
125 struct parsed_mount_info {
127 char host[NI_MAXHOST + 1];
128 char share[MAX_SHARE_LEN + 1];
129 char prefix[PATH_MAX + 1];
130 char options[MAX_OPTIONS_LEN];
131 char domain[DOMAIN_SIZE + 1];
132 char username[MAX_USERNAME_SIZE + 1];
133 char password[MOUNT_PASSWD_SIZE + 1];
134 char addrlist[MAX_ADDR_LIST_LEN];
135 unsigned int got_user:1;
136 unsigned int got_password:1;
137 unsigned int fakemnt:1;
138 unsigned int nomtab:1;
139 unsigned int verboseflag:1;
142 const char *thisprogram;
143 const char *cifs_fstype = "cifs";
145 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
147 static int check_setuid(void)
150 fprintf(stderr, "This program is not installed setuid root - "
151 " \"user\" CIFS mounts not supported.\n");
155 #if CIFS_DISABLE_SETUID_CAPABILITY
156 if (getuid() && !geteuid()) {
157 printf("This mount.cifs program has been built with the "
158 "ability to run as a setuid root program disabled.\n");
161 #endif /* CIFS_DISABLE_SETUID_CAPABILITY */
167 check_fstab(const char *progname, const char *mountpoint, const char *devname,
173 /* make sure this mount is listed in /etc/fstab */
174 fstab = setmntent(_PATH_FSTAB, "r");
176 fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_FSTAB);
180 while ((mnt = getmntent(fstab))) {
181 if (!strcmp(mountpoint, mnt->mnt_dir))
186 if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
187 fprintf(stderr, "%s: permission denied: no match for "
188 "%s found in %s\n", progname, mountpoint, _PATH_FSTAB);
193 * 'mount' munges the options from fstab before passing them
194 * to us. It is non-trivial to test that we have the correct
195 * set of options. We don't want to trust what the user
196 * gave us, so just take whatever is in /etc/fstab.
199 *options = strdup(mnt->mnt_opts);
206 open nofollow - avoid symlink exposure?
207 get owner of dir see if matches self or if root
208 call system(umount argv) etc.
212 static int mount_cifs_usage(FILE * stream)
214 fprintf(stream, "\nUsage: %s <remotetarget> <dir> -o <options>\n",
216 fprintf(stream, "\nMount the remote target, specified as a UNC name,");
217 fprintf(stream, " to a local directory.\n\nOptions:\n");
218 fprintf(stream, "\tuser=<arg>\n\tpass=<arg>\n\tdom=<arg>\n");
219 fprintf(stream, "\nLess commonly used options:");
221 "\n\tcredentials=<filename>,guest,perm,noperm,setuids,nosetuids,rw,ro,");
223 "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
225 "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
227 "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign");
229 "\n\nOptions not needed for servers supporting CIFS Unix extensions");
231 "\n\t(e.g. unneeded for mounts to most Samba versions):");
233 "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu");
234 fprintf(stream, "\n\nRarely used options:");
236 "\n\tport=<tcpport>,rsize=<size>,wsize=<size>,unc=<unc_name>,ip=<ip_address>,");
238 "\n\tdev,nodev,nouser_xattr,netbiosname=<OUR_RFC1001NAME>,hard,soft,intr,");
240 "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl");
242 "\n\nOptions are described in more detail in the manual page");
243 fprintf(stream, "\n\tman 8 mount.cifs\n");
244 fprintf(stream, "\nTo display the version number of the mount helper:");
245 fprintf(stream, "\n\t%s -V\n", thisprogram);
247 if (stream == stderr)
253 * CIFS has to "escape" commas in the password field so that they don't
254 * end up getting confused for option delimiters. Copy password into pw
255 * field, turning any commas into double commas.
257 static int set_password(struct parsed_mount_info *parsed_info, const char *src)
259 char *dst = parsed_info->password;
260 unsigned int i = 0, j = 0;
266 if (j > sizeof(parsed_info->password)) {
267 fprintf(stderr, "Converted password too long!\n");
272 parsed_info->got_password = 1;
276 /* caller frees username if necessary */
277 static char *getusername(uid_t uid)
279 char *username = NULL;
280 struct passwd *password = getpwuid(uid);
283 username = password->pw_name;
288 * Parse a username string into parsed_mount_info fields. The format is:
290 * DOMAIN\username%password
292 * ...obviously the only required component is "username". The source string
293 * is modified in the process, but it should remain unchanged at the end.
295 static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
297 char *user, *password, slash;
300 /* everything after first % sign is a password */
301 password = strchr(rawuser, '%');
303 rc = set_password(parsed_info, password);
308 /* everything after first '/' or '\' is a username */
309 user = strchr(rawuser, '/');
311 user = strchr(rawuser, '\\');
313 /* everything before that slash is a domain */
317 strlcpy(parsed_info->domain, rawuser,
318 sizeof(parsed_info->domain));
324 strlcpy(parsed_info->username, user, sizeof(parsed_info->username));
325 parsed_info->got_user = 1;
332 #ifdef HAVE_LIBCAP_NG
334 drop_capabilities(int parent)
336 capng_setpid(getpid());
337 capng_clear(CAPNG_SELECT_BOTH);
338 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_OVERRIDE)) {
339 fprintf(stderr, "Unable to update capability set.\n");
344 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) {
345 fprintf(stderr, "Unable to update capability set.\n");
349 if (capng_apply(CAPNG_SELECT_BOTH)) {
350 fprintf(stderr, "Unable to apply new capability set.\n");
357 toggle_cap_dac_override(int enable)
359 if (capng_update(enable ? CAPNG_ADD : CAPNG_DROP, CAPNG_EFFECTIVE, CAP_DAC_OVERRIDE)) {
360 fprintf(stderr, "Unable to update capability set.\n");
363 if (capng_apply(CAPNG_SELECT_CAPS)) {
364 fprintf(stderr, "Unable to apply new capability set.\n");
369 #else /* HAVE_LIBCAP_NG */
372 prune_bounding_set(void)
375 static int bounding_set_cleared;
377 if (bounding_set_cleared)
380 for (i = 0; i <= CAP_LAST_CAP && rc == 0; ++i)
381 rc = prctl(PR_CAPBSET_DROP, i);
384 fprintf(stderr, "Unable to clear capability bounding set: %d\n", rc);
388 ++bounding_set_cleared;
391 #else /* HAVE_PRCTL */
393 prune_bounding_set(void)
397 #endif /* HAVE_PRCTL */
400 drop_capabilities(int parent)
404 cap_value_t cap_list[2];
406 rc = prune_bounding_set();
410 caps = cap_get_proc();
412 fprintf(stderr, "Unable to get current capability set: %s\n",
417 if (cap_clear(caps) == -1) {
418 fprintf(stderr, "Unable to clear capability set: %s\n",
424 if (parent || getuid() == 0) {
426 cap_list[0] = CAP_DAC_OVERRIDE;
428 cap_list[1] = CAP_SYS_ADMIN;
431 if (cap_set_flag(caps, CAP_PERMITTED, ncaps, cap_list, CAP_SET) == -1) {
432 fprintf(stderr, "Unable to set permitted capabilities: %s\n",
438 cap_list[0] = CAP_SYS_ADMIN;
439 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
440 fprintf(stderr, "Unable to set effective capabilities: %s\n",
448 if (cap_set_proc(caps) != 0) {
449 fprintf(stderr, "Unable to set current process capabilities: %s\n",
459 toggle_cap_dac_override(int enable)
463 cap_value_t cap_list;
468 caps = cap_get_proc();
470 fprintf(stderr, "Unable to get current capability set: %s\n",
475 cap_list = CAP_DAC_OVERRIDE;
476 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap_list,
477 enable ? CAP_SET : CAP_CLEAR) == -1) {
478 fprintf(stderr, "Unable to %s effective capabilities: %s\n",
479 enable ? "set" : "clear", strerror(errno));
484 if (cap_set_proc(caps) != 0) {
485 fprintf(stderr, "Unable to set current process capabilities: %s\n",
493 #else /* HAVE_LIBCAP */
495 drop_capabilities(int parent)
501 toggle_cap_dac_override(int enable)
505 #endif /* HAVE_LIBCAP */
506 #endif /* HAVE_LIBCAP_NG */
508 static int open_cred_file(char *file_name,
509 struct parsed_mount_info *parsed_info)
512 char *temp_val, *newline;
516 i = toggle_cap_dac_override(1);
520 i = access(file_name, R_OK);
522 toggle_cap_dac_override(0);
526 fs = fopen(file_name, "r");
528 toggle_cap_dac_override(0);
532 i = toggle_cap_dac_override(0);
538 line_buf = (char *)malloc(4096);
539 if (line_buf == NULL) {
544 while (fgets(line_buf, 4096, fs)) {
545 /* parse line from credential file */
547 /* eat leading white space */
548 for (i = 0; i < 4086; i++) {
549 if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
551 /* if whitespace - skip past it */
554 /* NULL terminate at newline */
555 newline = strchr(line_buf + i, '\n');
559 if (strncasecmp("username", line_buf + i, 8) == 0) {
560 temp_val = strchr(line_buf + i, '=');
562 /* go past equals sign */
564 for (length = 0; length < 4087; length++) {
565 if ((temp_val[length] == '\n')
566 || (temp_val[length] == '\0')) {
567 temp_val[length] = '\0';
573 "mount.cifs failed due to malformed username in credentials file\n");
574 memset(line_buf, 0, 4096);
577 parsed_info->got_user = 1;
578 strlcpy(parsed_info->username, temp_val,
579 sizeof(parsed_info->username));
581 } else if (strncasecmp("password", line_buf + i, 8) == 0) {
582 temp_val = strchr(line_buf + i, '=');
586 i = set_password(parsed_info, temp_val);
589 } else if (strncasecmp("domain", line_buf + i, 6) == 0) {
590 temp_val = strchr(line_buf + i, '=');
592 /* go past equals sign */
594 if (parsed_info->verboseflag)
595 fprintf(stderr, "\nDomain %s\n",
598 for (length = 0; length < DOMAIN_SIZE + 1;
600 if ((temp_val[length] == '\n')
601 || (temp_val[length] == '\0')) {
602 temp_val[length] = '\0';
607 if (length > DOMAIN_SIZE) {
609 "mount.cifs failed: domain in credentials file too long\n");
613 strlcpy(parsed_info->domain, temp_val,
614 sizeof(parsed_info->domain));
625 get_password_from_file(int file_descript, char *filename,
626 struct parsed_mount_info *parsed_info)
629 char buf[sizeof(parsed_info->password) + 1];
631 if (filename != NULL) {
632 rc = toggle_cap_dac_override(1);
636 rc = access(filename, R_OK);
639 "mount.cifs failed: access check of %s failed: %s\n",
640 filename, strerror(errno));
641 toggle_cap_dac_override(0);
645 file_descript = open(filename, O_RDONLY);
646 if (file_descript < 0) {
648 "mount.cifs failed. %s attempting to open password file %s\n",
649 strerror(errno), filename);
650 toggle_cap_dac_override(0);
654 rc = toggle_cap_dac_override(0);
661 memset(buf, 0, sizeof(buf));
662 rc = read(file_descript, buf, sizeof(buf) - 1);
665 "mount.cifs failed. Error %s reading password file\n",
671 rc = set_password(parsed_info, buf);
674 if (filename != NULL)
675 close(file_descript);
680 parse_options(const char *data, struct parsed_mount_info *parsed_info)
682 char *value = NULL, *equals = NULL;
683 char *next_keyword = NULL;
684 char *out = parsed_info->options;
685 unsigned long *filesys_flags = &parsed_info->flags;
689 int got_uid = 0, got_gid = 0;
693 /* make sure we're starting from beginning */
696 /* BB fixme check for separator override BB */
699 snprintf(user, sizeof(user), "%u", getuid());
701 snprintf(group, sizeof(group), "%u", getgid());
708 * format is keyword,keyword2=value2,keyword3=value3...
709 * data = next keyword
710 * value = next value ie stuff after equal sign
712 while (data && *data) {
713 next_keyword = strchr(data, ','); /* BB handle sep= */
715 /* temporarily null terminate end of keyword=value pair */
719 /* temporarily null terminate keyword if there's a value */
721 if ((equals = strchr(data, '=')) != NULL) {
726 /* FIXME: turn into a token parser? */
727 if (strncmp(data, "users", 5) == 0) {
728 if (!value || !*value) {
729 *filesys_flags |= MS_USERS;
732 } else if (strncmp(data, "user_xattr", 10) == 0) {
733 /* do nothing - need to skip so not parsed as user name */
734 } else if (strncmp(data, "user", 4) == 0) {
735 if (!value || !*value) {
736 if (data[4] == '\0') {
737 *filesys_flags |= MS_USER;
741 "username specified with no parameter\n");
745 if (strnlen(value, 260) >= 260) {
746 fprintf(stderr, "username too long\n");
749 rc = parse_username(value, parsed_info);
752 "problem parsing username\n");
757 } else if (strncmp(data, "pass", 4) == 0) {
758 if (parsed_info->got_password) {
760 "password specified twice, ignoring second\n");
763 if (!value || !*value) {
764 parsed_info->got_password = 1;
767 rc = set_password(parsed_info, value);
771 } else if (strncmp(data, "sec", 3) == 0) {
773 if (!strncmp(value, "none", 4) ||
774 !strncmp(value, "krb5", 4))
775 parsed_info->got_password = 1;
777 } else if (strncmp(data, "ip", 2) == 0) {
778 if (!value || !*value) {
780 "target ip address argument missing");
781 } else if (strnlen(value, MAX_ADDRESS_LEN) <=
783 if (parsed_info->verboseflag)
785 "ip address %s override specified\n",
788 fprintf(stderr, "ip address too long\n");
791 } else if ((strncmp(data, "unc", 3) == 0)
792 || (strncmp(data, "target", 6) == 0)
793 || (strncmp(data, "path", 4) == 0)) {
794 if (!value || !*value) {
796 "invalid path to network resource\n");
797 return EX_USAGE; /* needs_arg; */
799 rc = parse_unc(value, parsed_info);
802 } else if ((strncmp(data, "dom" /* domain */ , 3) == 0)
803 || (strncmp(data, "workg", 5) == 0)) {
804 /* note this allows for synonyms of "domain"
805 such as "DOM" and "dom" and "workgroup"
806 and "WORKGRP" etc. */
807 if (!value || !*value) {
808 fprintf(stderr, "CIFS: invalid domain name\n");
811 if (strnlen(value, sizeof(parsed_info->domain)) >=
812 sizeof(parsed_info->domain)) {
813 fprintf(stderr, "domain name too long\n");
816 strlcpy(parsed_info->domain, value,
817 sizeof(parsed_info->domain));
819 } else if (strncmp(data, "cred", 4) == 0) {
820 if (value && *value) {
821 rc = open_cred_file(value, parsed_info);
824 "error %d (%s) opening credential file %s\n",
825 rc, strerror(rc), value);
830 "invalid credential file name specified\n");
833 } else if (strncmp(data, "uid", 3) == 0) {
834 if (value && *value) {
836 if (!isdigit(*value)) {
839 if (!(pw = getpwnam(value))) {
841 "bad user name \"%s\"\n",
845 snprintf(user, sizeof(user), "%u",
848 strlcpy(user, value, sizeof(user));
852 } else if (strncmp(data, "gid", 3) == 0) {
853 if (value && *value) {
855 if (!isdigit(*value)) {
858 if (!(gr = getgrnam(value))) {
860 "bad group name \"%s\"\n",
864 snprintf(group, sizeof(group), "%u",
867 strlcpy(group, value, sizeof(group));
871 /* fmask and dmask synonyms for people used to smbfs syntax */
872 } else if (strcmp(data, "file_mode") == 0
873 || strcmp(data, "fmask") == 0) {
874 if (!value || !*value) {
876 "Option '%s' requires a numerical argument\n",
881 if (value[0] != '0') {
883 "WARNING: '%s' not expressed in octal.\n",
887 if (strcmp(data, "fmask") == 0) {
889 "WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
890 data = "file_mode"; /* BB fix this */
892 } else if (strcmp(data, "dir_mode") == 0
893 || strcmp(data, "dmask") == 0) {
894 if (!value || !*value) {
896 "Option '%s' requires a numerical argument\n",
901 if (value[0] != '0') {
903 "WARNING: '%s' not expressed in octal.\n",
907 if (strcmp(data, "dmask") == 0) {
909 "WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
912 /* the following eight mount options should be
913 stripped out from what is passed into the kernel
914 since these eight options are best passed as the
915 mount flags rather than redundantly to the kernel
916 and could generate spurious warnings depending on the
917 level of the corresponding cifs vfs kernel code */
918 } else if (strncmp(data, "nosuid", 6) == 0) {
919 *filesys_flags |= MS_NOSUID;
920 } else if (strncmp(data, "suid", 4) == 0) {
921 *filesys_flags &= ~MS_NOSUID;
922 } else if (strncmp(data, "nodev", 5) == 0) {
923 *filesys_flags |= MS_NODEV;
924 } else if ((strncmp(data, "nobrl", 5) == 0) ||
925 (strncmp(data, "nolock", 6) == 0)) {
926 *filesys_flags &= ~MS_MANDLOCK;
927 } else if (strncmp(data, "dev", 3) == 0) {
928 *filesys_flags &= ~MS_NODEV;
929 } else if (strncmp(data, "noexec", 6) == 0) {
930 *filesys_flags |= MS_NOEXEC;
931 } else if (strncmp(data, "exec", 4) == 0) {
932 *filesys_flags &= ~MS_NOEXEC;
933 } else if (strncmp(data, "guest", 5) == 0) {
934 parsed_info->got_user = 1;
935 parsed_info->got_password = 1;
936 } else if (strncmp(data, "ro", 2) == 0) {
937 *filesys_flags |= MS_RDONLY;
939 } else if (strncmp(data, "rw", 2) == 0) {
940 *filesys_flags &= ~MS_RDONLY;
942 } else if (strncmp(data, "remount", 7) == 0) {
943 *filesys_flags |= MS_REMOUNT;
946 /* check size before copying option to buffer */
947 word_len = strlen(data);
949 word_len += 1 + strlen(value);
951 /* need 2 extra bytes for comma and null byte */
952 if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
953 fprintf(stderr, "Options string too long\n");
957 /* put back equals sign, if any */
961 /* go ahead and copy */
963 strlcat(out, ",", MAX_OPTIONS_LEN);
965 strlcat(out, data, MAX_OPTIONS_LEN);
966 out_len = strlen(out);
971 /* special-case the uid and gid */
973 word_len = strlen(user);
975 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
976 fprintf(stderr, "Options string too long\n");
981 strlcat(out, ",", out_len + word_len + 6);
984 snprintf(out + out_len, word_len + 5, "uid=%s", user);
985 out_len = strlen(out);
988 word_len = strlen(group);
990 if (out_len + 1 + word_len + 6 > MAX_OPTIONS_LEN) {
991 fprintf(stderr, "Options string too long\n");
996 strlcat(out, ",", out_len + word_len + 6);
999 snprintf(out + out_len, word_len + 5, "gid=%s", group);
1000 out_len = strlen(out);
1007 * resolve "host" portion of parsed info to comma-separated list of
1010 static int resolve_host(struct parsed_mount_info *parsed_info)
1013 /* 10 for max width of decimal scopeid */
1014 char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
1017 struct addrinfo *addrlist, *addr;
1018 struct sockaddr_in *sin;
1019 struct sockaddr_in6 *sin6;
1021 rc = getaddrinfo(parsed_info->host, NULL, NULL, &addrlist);
1023 fprintf(stderr, "mount error: could not resolve address for "
1024 "%s: %s\n", parsed_info->host,
1025 rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
1026 /* FIXME: return better error based on rc? */
1032 /* skip non-TCP entries */
1033 if (addr->ai_socktype != SOCK_STREAM ||
1034 addr->ai_protocol != IPPROTO_TCP) {
1035 addr = addr->ai_next;
1039 switch (addr->ai_addr->sa_family) {
1041 sin6 = (struct sockaddr_in6 *)addr->ai_addr;
1042 ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
1047 "mount error: problem parsing address "
1048 "list: %s\n", strerror(errno));
1049 goto resolve_host_out;
1052 if (sin6->sin6_scope_id) {
1053 len = strnlen(tmpbuf, sizeof(tmpbuf));
1054 ipaddr = tmpbuf + len;
1055 snprintf(tmpbuf, sizeof(tmpbuf) - len, "%%%u",
1056 sin6->sin6_scope_id);
1060 sin = (struct sockaddr_in *)addr->ai_addr;
1061 ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
1066 "mount error: problem parsing address "
1067 "list: %s\n", strerror(errno));
1068 goto resolve_host_out;
1073 addr = addr->ai_next;
1077 if (parsed_info->addrlist[0] != '\0')
1078 strlcat(parsed_info->addrlist, ",",
1079 sizeof(parsed_info->addrlist));
1080 strlcat(parsed_info->addrlist, tmpbuf,
1081 sizeof(parsed_info->addrlist));
1082 addr = addr->ai_next;
1086 freeaddrinfo(addrlist);
1090 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
1092 int length = strnlen(unc_name, MAX_UNC_LEN);
1093 const char *host, *share, *prepath;
1094 size_t hostlen, sharelen, prepathlen;
1096 if (length > (MAX_UNC_LEN - 1)) {
1097 fprintf(stderr, "mount error: UNC name too long\n");
1102 fprintf(stderr, "mount error: UNC name too short\n");
1106 if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
1107 (strncasecmp("smb://", unc_name, 6) == 0)) {
1109 "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
1114 /* Set up "host" and "share" pointers based on UNC format. */
1115 if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
1117 * check for nfs syntax (server:/share/prepath)
1119 * FIXME: IPv6 addresses?
1122 share = strchr(host, ':');
1124 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1127 hostlen = share - host;
1132 host = unc_name + 2;
1133 hostlen = strcspn(host, "/\\");
1135 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1138 share = host + hostlen + 1;
1141 if (hostlen + 1 > sizeof(parsed_info->host)) {
1142 fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
1146 sharelen = strcspn(share, "/\\");
1147 if (sharelen + 1 > sizeof(parsed_info->share)) {
1148 fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
1152 prepath = share + sharelen;
1153 prepathlen = strlen(prepath);
1155 if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
1156 fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
1160 /* copy pieces into their resepective buffers */
1161 memcpy(parsed_info->host, host, hostlen);
1162 memcpy(parsed_info->share, share, sharelen);
1163 memcpy(parsed_info->prefix, prepath, prepathlen);
1168 static int get_pw_from_env(struct parsed_mount_info *parsed_info)
1172 if (getenv("PASSWD"))
1173 rc = set_password(parsed_info, getenv("PASSWD"));
1174 else if (getenv("PASSWD_FD"))
1175 rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
1177 else if (getenv("PASSWD_FILE"))
1178 rc = get_password_from_file(0, getenv("PASSWD_FILE"),
1184 static struct option longopts[] = {
1185 {"all", 0, NULL, 'a'},
1186 {"help", 0, NULL, 'h'},
1187 {"move", 0, NULL, 'm'},
1188 {"bind", 0, NULL, 'b'},
1189 {"read-only", 0, NULL, 'r'},
1190 {"ro", 0, NULL, 'r'},
1191 {"verbose", 0, NULL, 'v'},
1192 {"version", 0, NULL, 'V'},
1193 {"read-write", 0, NULL, 'w'},
1194 {"rw", 0, NULL, 'w'},
1195 {"options", 1, NULL, 'o'},
1196 {"type", 1, NULL, 't'},
1197 {"uid", 1, NULL, '1'},
1198 {"gid", 1, NULL, '2'},
1199 {"user", 1, NULL, 'u'},
1200 {"username", 1, NULL, 'u'},
1201 {"dom", 1, NULL, 'd'},
1202 {"domain", 1, NULL, 'd'},
1203 {"password", 1, NULL, 'p'},
1204 {"pass", 1, NULL, 'p'},
1205 {"credentials", 1, NULL, 'c'},
1206 {"port", 1, NULL, 'P'},
1210 /* convert a string to uppercase. return false if the string
1211 * wasn't ASCII. Return success on a NULL ptr */
1212 static int uppercase_string(char *string)
1218 /* check for unicode */
1219 if ((unsigned char)string[0] & 0x80)
1221 *string = toupper((unsigned char)*string);
1228 static void print_cifs_mount_version(void)
1230 printf("mount.cifs version: %s\n", VERSION);
1234 * This function borrowed from fuse-utils...
1236 * glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
1237 * newlines embedded within the text fields. To make sure no one corrupts
1238 * the mtab, fail the mount if there are embedded newlines.
1240 static int check_newline(const char *progname, const char *name)
1243 for (s = "\n"; *s; s++) {
1244 if (strchr(name, *s)) {
1246 "%s: illegal character 0x%02x in mount entry\n",
1254 static int check_mtab(const char *progname, const char *devname,
1257 if (check_newline(progname, devname) == -1 ||
1258 check_newline(progname, dir) == -1)
1264 add_mtab(char *devname, char *mountpoint, unsigned long flags)
1268 char *mount_user = NULL;
1269 struct mntent mountent;
1271 sigset_t mask, oldmask;
1275 mount_user = getusername(uid);
1278 * Set the real uid to the effective uid. This prevents unprivileged
1279 * users from sending signals to this process, though ^c on controlling
1280 * terminal should still work.
1282 rc = setreuid(geteuid(), -1);
1284 fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
1289 rc = sigfillset(&mask);
1291 fprintf(stderr, "Unable to set filled signal mask\n");
1295 rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
1297 fprintf(stderr, "Unable to make process ignore signals\n");
1301 rc = toggle_cap_dac_override(1);
1305 atexit(unlock_mtab);
1308 fprintf(stderr, "cannot lock mtab");
1313 pmntfile = setmntent(MOUNTED, "a+");
1315 fprintf(stderr, "could not update mount table\n");
1321 mountent.mnt_fsname = devname;
1322 mountent.mnt_dir = mountpoint;
1323 mountent.mnt_type = (char *)(void *)cifs_fstype;
1324 mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
1325 if (mountent.mnt_opts) {
1326 if (flags & MS_RDONLY)
1327 strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
1329 strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
1331 if (flags & MS_MANDLOCK)
1332 strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
1333 if (flags & MS_NOEXEC)
1334 strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
1335 if (flags & MS_NOSUID)
1336 strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
1337 if (flags & MS_NODEV)
1338 strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
1339 if (flags & MS_SYNCHRONOUS)
1340 strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
1342 strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
1343 strlcat(mountent.mnt_opts, mount_user,
1347 mountent.mnt_freq = 0;
1348 mountent.mnt_passno = 0;
1349 rc = addmntent(pmntfile, &mountent);
1350 endmntent(pmntfile);
1352 SAFE_FREE(mountent.mnt_opts);
1354 toggle_cap_dac_override(0);
1355 sigprocmask(SIG_SETMASK, &oldmask, NULL);
1357 fprintf(stderr, "unable to add mount entry to mtab\n");
1364 /* have the child drop root privileges */
1366 drop_child_privs(void)
1369 uid_t uid = getuid();
1370 gid_t gid = getgid();
1375 fprintf(stderr, "Unable set group identity: %s\n",
1383 fprintf(stderr, "Unable set user identity: %s\n",
1393 assemble_mountinfo(struct parsed_mount_info *parsed_info,
1394 const char *thisprogram, const char *mountpoint,
1395 const char *orig_dev, char *orgoptions)
1399 rc = drop_capabilities(0);
1403 rc = drop_child_privs();
1408 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1413 /* enable any default user mount flags */
1414 parsed_info->flags |= CIFS_SETUID_FLAGS;
1417 rc = get_pw_from_env(parsed_info);
1422 rc = parse_options(orgoptions, parsed_info);
1428 if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
1429 fprintf(stderr, "%s: permission denied\n", thisprogram);
1435 parsed_info->flags &= ~(MS_USERS | MS_USER);
1437 rc = parse_unc(orig_dev, parsed_info);
1441 rc = resolve_host(parsed_info);
1445 if (!parsed_info->got_user) {
1447 * Note that the password will not be retrieved from the
1448 * USER env variable (ie user%password form) as there is
1449 * already a PASSWD environment varaible
1452 strlcpy(parsed_info->username, getenv("USER"),
1453 sizeof(parsed_info->username));
1455 strlcpy(parsed_info->username, getusername(getuid()),
1456 sizeof(parsed_info->username));
1457 parsed_info->got_user = 1;
1460 if (!parsed_info->got_password) {
1461 /* getpass is obsolete, but there's apparently nothing that replaces it */
1462 char *tmp_pass = getpass("Password: ");
1464 fprintf(stderr, "Error reading password, exiting\n");
1468 rc = set_password(parsed_info, tmp_pass);
1473 /* copy in ver= string. It's not really needed, but what the hell */
1474 strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
1475 strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
1477 /* copy in user= string */
1478 if (parsed_info->got_user) {
1479 strlcat(parsed_info->options, ",user=",
1480 sizeof(parsed_info->options));
1481 strlcat(parsed_info->options, parsed_info->username,
1482 sizeof(parsed_info->options));
1485 if (*parsed_info->domain) {
1486 strlcat(parsed_info->options, ",domain=",
1487 sizeof(parsed_info->options));
1488 strlcat(parsed_info->options, parsed_info->domain,
1489 sizeof(parsed_info->options));
1496 int main(int argc, char **argv)
1499 char *orgoptions = NULL;
1500 char *mountpoint = NULL;
1501 char *options = NULL;
1502 char *dev_name = NULL, *orig_dev = NULL;
1503 char *currentaddress, *nextaddress;
1505 int already_uppercased = 0;
1506 size_t options_size = MAX_OPTIONS_LEN;
1508 struct parsed_mount_info *parsed_info = NULL;
1511 rc = check_setuid();
1515 rc = drop_capabilities(1);
1519 /* setlocale(LC_ALL, "");
1520 bindtextdomain(PACKAGE, LOCALEDIR);
1521 textdomain(PACKAGE); */
1523 if (!argc || !argv) {
1524 rc = mount_cifs_usage(stderr);
1528 thisprogram = argv[0];
1529 if (thisprogram == NULL)
1530 thisprogram = "mount.cifs";
1532 /* allocate parsed_info as shared anonymous memory range */
1533 parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
1534 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
1535 if (parsed_info == (struct parsed_mount_info *) -1) {
1537 fprintf(stderr, "Unable to allocate memory: %s\n",
1542 parsed_info->flags = MS_MANDLOCK;
1544 /* add sharename in opts string as unc= parm */
1545 while ((c = getopt_long(argc, argv, "?fhno:rvVw",
1546 longopts, NULL)) != -1) {
1549 case 'h': /* help */
1550 rc = mount_cifs_usage(stdout);
1553 ++parsed_info->nomtab;
1556 orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
1562 case 'r': /* mount readonly */
1563 parsed_info->flags |= MS_RDONLY;
1566 ++parsed_info->verboseflag;
1569 print_cifs_mount_version();
1572 parsed_info->flags &= ~MS_RDONLY;
1575 ++parsed_info->fakemnt;
1578 fprintf(stderr, "unknown command-line option: %c\n", c);
1579 rc = mount_cifs_usage(stderr);
1584 if (argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL) {
1585 rc = mount_cifs_usage(stderr);
1589 orig_dev = argv[optind];
1590 mountpoint = argv[optind + 1];
1592 /* chdir into mountpoint as soon as possible */
1593 rc = chdir(mountpoint);
1595 fprintf(stderr, "Couldn't chdir to %s: %s\n", mountpoint,
1601 mountpoint = realpath(".", NULL);
1603 fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
1604 mountpoint, strerror(errno));
1610 * mount.cifs does privilege separation. Most of the code to handle
1611 * assembling the mount info is done in a child process that drops
1612 * privileges. The info is assembled in parsed_info which is a
1613 * shared, mmaped memory segment. The parent waits for the child to
1614 * exit and checks the return code. If it's anything but "0", then
1615 * the process exits without attempting anything further.
1619 fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
1624 rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
1625 orig_dev, orgoptions);
1630 if (!WIFEXITED(rc)) {
1631 fprintf(stderr, "Child process terminated abnormally.\n");
1635 rc = WEXITSTATUS(rc);
1640 options = calloc(options_size, 1);
1642 fprintf(stderr, "Unable to allocate memory.\n");
1647 dev_len = strnlen(parsed_info->host, sizeof(parsed_info->host)) +
1648 strnlen(parsed_info->share, sizeof(parsed_info->share)) +
1649 strnlen(parsed_info->prefix, sizeof(parsed_info->prefix)) +
1651 dev_name = calloc(dev_len, 1);
1657 /* rebuild device name with forward slashes */
1658 strlcpy(dev_name, "//", dev_len);
1659 strlcat(dev_name, parsed_info->host, dev_len);
1660 strlcat(dev_name, "/", dev_len);
1661 strlcat(dev_name, parsed_info->share, dev_len);
1662 strlcat(dev_name, parsed_info->prefix, dev_len);
1664 currentaddress = parsed_info->addrlist;
1665 nextaddress = strchr(currentaddress, ',');
1667 *nextaddress++ = '\0';
1670 if (!currentaddress) {
1671 fprintf(stderr, "Unable to find suitable address.\n");
1675 strlcpy(options, "ip=", options_size);
1676 strlcat(options, currentaddress, options_size);
1678 strlcat(options, ",unc=\\\\", options_size);
1679 strlcat(options, parsed_info->host, options_size);
1680 strlcat(options, "\\", options_size);
1681 strlcat(options, parsed_info->share, options_size);
1683 if (*parsed_info->options) {
1684 strlcat(options, ",", options_size);
1685 strlcat(options, parsed_info->options, options_size);
1688 if (*parsed_info->prefix) {
1689 strlcat(options, ",prefixpath=", options_size);
1690 strlcat(options, parsed_info->prefix, options_size);
1693 if (parsed_info->verboseflag)
1694 fprintf(stderr, "mount.cifs kernel mount options: %s\n",
1697 if (parsed_info->got_password) {
1699 * Commas have to be doubled, or else they will
1700 * look like the parameter separator
1702 strlcat(options, ",pass=", options_size);
1703 strlcat(options, parsed_info->password, options_size);
1704 if (parsed_info->verboseflag)
1705 fprintf(stderr, ",pass=********");
1708 if (parsed_info->verboseflag)
1709 fprintf(stderr, "\n");
1711 rc = check_mtab(thisprogram, dev_name, mountpoint);
1715 if (!parsed_info->fakemnt
1716 && mount(dev_name, ".", cifs_fstype, parsed_info->flags, options)) {
1720 currentaddress = nextaddress;
1721 nextaddress = strchr(currentaddress, ',');
1723 *nextaddress++ = '\0';
1727 "mount error: cifs filesystem not supported by the system\n");
1730 if (!already_uppercased &&
1731 uppercase_string(parsed_info->host) &&
1732 uppercase_string(parsed_info->share) &&
1733 uppercase_string(parsed_info->prefix)) {
1735 "Retrying with upper case share name\n");
1736 already_uppercased = 1;
1740 fprintf(stderr, "mount error(%d): %s\n", errno,
1743 "Refer to the mount.cifs(8) manual page (e.g. man "
1749 if (!parsed_info->nomtab)
1750 rc = add_mtab(dev_name, mountpoint, parsed_info->flags);
1754 memset(parsed_info->password, 0, sizeof(parsed_info->password));
1755 munmap(parsed_info, sizeof(*parsed_info));
1757 SAFE_FREE(dev_name);
1759 SAFE_FREE(orgoptions);