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);
339 if (capng_updatev(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH, CAP_DAC_OVERRIDE, -1)) {
340 fprintf(stderr, "Unable to update capability set.\n");
343 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) {
344 fprintf(stderr, "Unable to update capability set.\n");
348 if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
349 fprintf(stderr, "Unable to update capability set.\n");
353 if (capng_apply(CAPNG_SELECT_BOTH)) {
354 fprintf(stderr, "Unable to apply new capability set.\n");
361 toggle_capability(cap_value_t capability, int enable)
363 if (capng_update(enable ? CAPNG_ADD : CAPNG_DROP, CAPNG_EFFECTIVE, capability)) {
364 fprintf(stderr, "Unable to update capability set.\n");
367 if (capng_apply(CAPNG_SELECT_CAPS)) {
368 fprintf(stderr, "Unable to apply new capability set.\n");
373 #else /* HAVE_LIBCAP_NG */
376 prune_bounding_set(void)
379 static int bounding_set_cleared;
381 if (bounding_set_cleared)
384 for (i = 0; i <= CAP_LAST_CAP && rc == 0; ++i)
385 rc = prctl(PR_CAPBSET_DROP, i);
388 fprintf(stderr, "Unable to clear capability bounding set: %d\n", rc);
392 ++bounding_set_cleared;
395 #else /* HAVE_PRCTL */
397 prune_bounding_set(void)
401 #endif /* HAVE_PRCTL */
404 drop_capabilities(int parent)
408 cap_value_t cap_list[3];
410 rc = prune_bounding_set();
414 caps = cap_get_proc();
416 fprintf(stderr, "Unable to get current capability set: %s\n",
421 if (cap_clear(caps) == -1) {
422 fprintf(stderr, "Unable to clear capability set: %s\n",
428 if (parent || getuid() == 0) {
430 cap_list[0] = CAP_DAC_READ_SEARCH;
432 cap_list[1] = CAP_DAC_OVERRIDE;
433 cap_list[2] = CAP_SYS_ADMIN;
436 if (cap_set_flag(caps, CAP_PERMITTED, ncaps, cap_list, CAP_SET) == -1) {
437 fprintf(stderr, "Unable to set permitted capabilities: %s\n",
443 cap_list[0] = CAP_SYS_ADMIN;
444 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
445 fprintf(stderr, "Unable to set effective capabilities: %s\n",
453 if (cap_set_proc(caps) != 0) {
454 fprintf(stderr, "Unable to set current process capabilities: %s\n",
464 toggle_capability(cap_value_t capability, int enable)
472 caps = cap_get_proc();
474 fprintf(stderr, "Unable to get current capability set: %s\n",
479 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &capability,
480 enable ? CAP_SET : CAP_CLEAR) == -1) {
481 fprintf(stderr, "Unable to %s effective capabilities: %s\n",
482 enable ? "set" : "clear", strerror(errno));
487 if (cap_set_proc(caps) != 0) {
488 fprintf(stderr, "Unable to set current process capabilities: %s\n",
496 #else /* HAVE_LIBCAP */
498 drop_capabilities(int parent)
504 toggle_capability(cap_value_t capability, int enable)
508 #endif /* HAVE_LIBCAP */
509 #endif /* HAVE_LIBCAP_NG */
511 static int open_cred_file(char *file_name,
512 struct parsed_mount_info *parsed_info)
515 char *temp_val, *newline;
519 i = toggle_capability(CAP_DAC_READ_SEARCH, 1);
523 i = access(file_name, R_OK);
525 toggle_capability(CAP_DAC_READ_SEARCH, 0);
529 fs = fopen(file_name, "r");
531 toggle_capability(CAP_DAC_READ_SEARCH, 0);
535 i = toggle_capability(CAP_DAC_READ_SEARCH, 0);
541 line_buf = (char *)malloc(4096);
542 if (line_buf == NULL) {
547 while (fgets(line_buf, 4096, fs)) {
548 /* parse line from credential file */
550 /* eat leading white space */
551 for (i = 0; i < 4086; i++) {
552 if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
554 /* if whitespace - skip past it */
557 /* NULL terminate at newline */
558 newline = strchr(line_buf + i, '\n');
562 if (strncasecmp("username", line_buf + i, 8) == 0) {
563 temp_val = strchr(line_buf + i, '=');
565 /* go past equals sign */
567 for (length = 0; length < 4087; length++) {
568 if ((temp_val[length] == '\n')
569 || (temp_val[length] == '\0')) {
570 temp_val[length] = '\0';
576 "mount.cifs failed due to malformed username in credentials file\n");
577 memset(line_buf, 0, 4096);
580 parsed_info->got_user = 1;
581 strlcpy(parsed_info->username, temp_val,
582 sizeof(parsed_info->username));
584 } else if (strncasecmp("password", line_buf + i, 8) == 0) {
585 temp_val = strchr(line_buf + i, '=');
589 i = set_password(parsed_info, temp_val);
592 } else if (strncasecmp("domain", line_buf + i, 6) == 0) {
593 temp_val = strchr(line_buf + i, '=');
595 /* go past equals sign */
597 if (parsed_info->verboseflag)
598 fprintf(stderr, "\nDomain %s\n",
601 for (length = 0; length < DOMAIN_SIZE + 1;
603 if ((temp_val[length] == '\n')
604 || (temp_val[length] == '\0')) {
605 temp_val[length] = '\0';
610 if (length > DOMAIN_SIZE) {
612 "mount.cifs failed: domain in credentials file too long\n");
616 strlcpy(parsed_info->domain, temp_val,
617 sizeof(parsed_info->domain));
628 get_password_from_file(int file_descript, char *filename,
629 struct parsed_mount_info *parsed_info)
632 char buf[sizeof(parsed_info->password) + 1];
634 if (filename != NULL) {
635 rc = toggle_capability(CAP_DAC_READ_SEARCH, 1);
639 rc = access(filename, R_OK);
642 "mount.cifs failed: access check of %s failed: %s\n",
643 filename, strerror(errno));
644 toggle_capability(CAP_DAC_READ_SEARCH, 0);
648 file_descript = open(filename, O_RDONLY);
649 if (file_descript < 0) {
651 "mount.cifs failed. %s attempting to open password file %s\n",
652 strerror(errno), filename);
653 toggle_capability(CAP_DAC_READ_SEARCH, 0);
657 rc = toggle_capability(CAP_DAC_READ_SEARCH, 0);
664 memset(buf, 0, sizeof(buf));
665 rc = read(file_descript, buf, sizeof(buf) - 1);
668 "mount.cifs failed. Error %s reading password file\n",
674 rc = set_password(parsed_info, buf);
677 if (filename != NULL)
678 close(file_descript);
683 parse_options(const char *data, struct parsed_mount_info *parsed_info)
685 char *value = NULL, *equals = NULL;
686 char *next_keyword = NULL;
687 char *out = parsed_info->options;
688 unsigned long *filesys_flags = &parsed_info->flags;
692 int got_uid = 0, got_gid = 0;
696 /* make sure we're starting from beginning */
699 /* BB fixme check for separator override BB */
702 snprintf(user, sizeof(user), "%u", getuid());
704 snprintf(group, sizeof(group), "%u", getgid());
711 * format is keyword,keyword2=value2,keyword3=value3...
712 * data = next keyword
713 * value = next value ie stuff after equal sign
715 while (data && *data) {
716 next_keyword = strchr(data, ','); /* BB handle sep= */
718 /* temporarily null terminate end of keyword=value pair */
722 /* temporarily null terminate keyword if there's a value */
724 if ((equals = strchr(data, '=')) != NULL) {
729 /* FIXME: turn into a token parser? */
730 if (strncmp(data, "users", 5) == 0) {
731 if (!value || !*value) {
732 *filesys_flags |= MS_USERS;
735 } else if (strncmp(data, "user_xattr", 10) == 0) {
736 /* do nothing - need to skip so not parsed as user name */
737 } else if (strncmp(data, "user", 4) == 0) {
738 if (!value || !*value) {
739 if (data[4] == '\0') {
740 *filesys_flags |= MS_USER;
744 "username specified with no parameter\n");
748 if (strnlen(value, 260) >= 260) {
749 fprintf(stderr, "username too long\n");
752 rc = parse_username(value, parsed_info);
755 "problem parsing username\n");
760 } else if (strncmp(data, "pass", 4) == 0) {
761 if (parsed_info->got_password) {
763 "password specified twice, ignoring second\n");
766 if (!value || !*value) {
767 parsed_info->got_password = 1;
770 rc = set_password(parsed_info, value);
774 } else if (strncmp(data, "sec", 3) == 0) {
776 if (!strncmp(value, "none", 4) ||
777 !strncmp(value, "krb5", 4))
778 parsed_info->got_password = 1;
780 } else if (strncmp(data, "ip", 2) == 0) {
781 if (!value || !*value) {
783 "target ip address argument missing");
784 } else if (strnlen(value, MAX_ADDRESS_LEN) <=
786 if (parsed_info->verboseflag)
788 "ip address %s override specified\n",
791 fprintf(stderr, "ip address too long\n");
794 } else if ((strncmp(data, "unc", 3) == 0)
795 || (strncmp(data, "target", 6) == 0)
796 || (strncmp(data, "path", 4) == 0)) {
797 if (!value || !*value) {
799 "invalid path to network resource\n");
800 return EX_USAGE; /* needs_arg; */
802 rc = parse_unc(value, parsed_info);
805 } else if ((strncmp(data, "dom" /* domain */ , 3) == 0)
806 || (strncmp(data, "workg", 5) == 0)) {
807 /* note this allows for synonyms of "domain"
808 such as "DOM" and "dom" and "workgroup"
809 and "WORKGRP" etc. */
810 if (!value || !*value) {
811 fprintf(stderr, "CIFS: invalid domain name\n");
814 if (strnlen(value, sizeof(parsed_info->domain)) >=
815 sizeof(parsed_info->domain)) {
816 fprintf(stderr, "domain name too long\n");
819 strlcpy(parsed_info->domain, value,
820 sizeof(parsed_info->domain));
822 } else if (strncmp(data, "cred", 4) == 0) {
823 if (value && *value) {
824 rc = open_cred_file(value, parsed_info);
827 "error %d (%s) opening credential file %s\n",
828 rc, strerror(rc), value);
833 "invalid credential file name specified\n");
836 } else if (strncmp(data, "uid", 3) == 0) {
837 if (value && *value) {
839 if (!isdigit(*value)) {
842 if (!(pw = getpwnam(value))) {
844 "bad user name \"%s\"\n",
848 snprintf(user, sizeof(user), "%u",
851 strlcpy(user, value, sizeof(user));
855 } else if (strncmp(data, "gid", 3) == 0) {
856 if (value && *value) {
858 if (!isdigit(*value)) {
861 if (!(gr = getgrnam(value))) {
863 "bad group name \"%s\"\n",
867 snprintf(group, sizeof(group), "%u",
870 strlcpy(group, value, sizeof(group));
874 /* fmask and dmask synonyms for people used to smbfs syntax */
875 } else if (strcmp(data, "file_mode") == 0
876 || strcmp(data, "fmask") == 0) {
877 if (!value || !*value) {
879 "Option '%s' requires a numerical argument\n",
884 if (value[0] != '0') {
886 "WARNING: '%s' not expressed in octal.\n",
890 if (strcmp(data, "fmask") == 0) {
892 "WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
893 data = "file_mode"; /* BB fix this */
895 } else if (strcmp(data, "dir_mode") == 0
896 || strcmp(data, "dmask") == 0) {
897 if (!value || !*value) {
899 "Option '%s' requires a numerical argument\n",
904 if (value[0] != '0') {
906 "WARNING: '%s' not expressed in octal.\n",
910 if (strcmp(data, "dmask") == 0) {
912 "WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
915 /* the following eight mount options should be
916 stripped out from what is passed into the kernel
917 since these eight options are best passed as the
918 mount flags rather than redundantly to the kernel
919 and could generate spurious warnings depending on the
920 level of the corresponding cifs vfs kernel code */
921 } else if (strncmp(data, "nosuid", 6) == 0) {
922 *filesys_flags |= MS_NOSUID;
923 } else if (strncmp(data, "suid", 4) == 0) {
924 *filesys_flags &= ~MS_NOSUID;
925 } else if (strncmp(data, "nodev", 5) == 0) {
926 *filesys_flags |= MS_NODEV;
927 } else if ((strncmp(data, "nobrl", 5) == 0) ||
928 (strncmp(data, "nolock", 6) == 0)) {
929 *filesys_flags &= ~MS_MANDLOCK;
930 } else if (strncmp(data, "dev", 3) == 0) {
931 *filesys_flags &= ~MS_NODEV;
932 } else if (strncmp(data, "noexec", 6) == 0) {
933 *filesys_flags |= MS_NOEXEC;
934 } else if (strncmp(data, "exec", 4) == 0) {
935 *filesys_flags &= ~MS_NOEXEC;
936 } else if (strncmp(data, "guest", 5) == 0) {
937 parsed_info->got_user = 1;
938 parsed_info->got_password = 1;
939 } else if (strncmp(data, "ro", 2) == 0) {
940 *filesys_flags |= MS_RDONLY;
942 } else if (strncmp(data, "rw", 2) == 0) {
943 *filesys_flags &= ~MS_RDONLY;
945 } else if (strncmp(data, "remount", 7) == 0) {
946 *filesys_flags |= MS_REMOUNT;
949 /* check size before copying option to buffer */
950 word_len = strlen(data);
952 word_len += 1 + strlen(value);
954 /* need 2 extra bytes for comma and null byte */
955 if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
956 fprintf(stderr, "Options string too long\n");
960 /* put back equals sign, if any */
964 /* go ahead and copy */
966 strlcat(out, ",", MAX_OPTIONS_LEN);
968 strlcat(out, data, MAX_OPTIONS_LEN);
969 out_len = strlen(out);
974 /* special-case the uid and gid */
976 word_len = strlen(user);
978 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
979 fprintf(stderr, "Options string too long\n");
984 strlcat(out, ",", out_len + word_len + 6);
987 snprintf(out + out_len, word_len + 5, "uid=%s", user);
988 out_len = strlen(out);
991 word_len = strlen(group);
993 if (out_len + 1 + word_len + 6 > MAX_OPTIONS_LEN) {
994 fprintf(stderr, "Options string too long\n");
999 strlcat(out, ",", out_len + word_len + 6);
1002 snprintf(out + out_len, word_len + 5, "gid=%s", group);
1003 out_len = strlen(out);
1010 * resolve "host" portion of parsed info to comma-separated list of
1013 static int resolve_host(struct parsed_mount_info *parsed_info)
1016 /* 10 for max width of decimal scopeid */
1017 char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
1020 struct addrinfo *addrlist, *addr;
1021 struct sockaddr_in *sin;
1022 struct sockaddr_in6 *sin6;
1024 rc = getaddrinfo(parsed_info->host, NULL, NULL, &addrlist);
1026 fprintf(stderr, "mount error: could not resolve address for "
1027 "%s: %s\n", parsed_info->host,
1028 rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
1029 /* FIXME: return better error based on rc? */
1035 /* skip non-TCP entries */
1036 if (addr->ai_socktype != SOCK_STREAM ||
1037 addr->ai_protocol != IPPROTO_TCP) {
1038 addr = addr->ai_next;
1042 switch (addr->ai_addr->sa_family) {
1044 sin6 = (struct sockaddr_in6 *)addr->ai_addr;
1045 ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
1050 "mount error: problem parsing address "
1051 "list: %s\n", strerror(errno));
1052 goto resolve_host_out;
1055 if (sin6->sin6_scope_id) {
1056 len = strnlen(tmpbuf, sizeof(tmpbuf));
1057 ipaddr = tmpbuf + len;
1058 snprintf(tmpbuf, sizeof(tmpbuf) - len, "%%%u",
1059 sin6->sin6_scope_id);
1063 sin = (struct sockaddr_in *)addr->ai_addr;
1064 ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
1069 "mount error: problem parsing address "
1070 "list: %s\n", strerror(errno));
1071 goto resolve_host_out;
1076 addr = addr->ai_next;
1080 if (parsed_info->addrlist[0] != '\0')
1081 strlcat(parsed_info->addrlist, ",",
1082 sizeof(parsed_info->addrlist));
1083 strlcat(parsed_info->addrlist, tmpbuf,
1084 sizeof(parsed_info->addrlist));
1085 addr = addr->ai_next;
1089 freeaddrinfo(addrlist);
1093 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
1095 int length = strnlen(unc_name, MAX_UNC_LEN);
1096 const char *host, *share, *prepath;
1097 size_t hostlen, sharelen, prepathlen;
1099 if (length > (MAX_UNC_LEN - 1)) {
1100 fprintf(stderr, "mount error: UNC name too long\n");
1105 fprintf(stderr, "mount error: UNC name too short\n");
1109 if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
1110 (strncasecmp("smb://", unc_name, 6) == 0)) {
1112 "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
1117 /* Set up "host" and "share" pointers based on UNC format. */
1118 if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
1120 * check for nfs syntax (server:/share/prepath)
1122 * FIXME: IPv6 addresses?
1125 share = strchr(host, ':');
1127 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1130 hostlen = share - host;
1135 host = unc_name + 2;
1136 hostlen = strcspn(host, "/\\");
1138 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
1141 share = host + hostlen + 1;
1144 if (hostlen + 1 > sizeof(parsed_info->host)) {
1145 fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
1149 sharelen = strcspn(share, "/\\");
1150 if (sharelen + 1 > sizeof(parsed_info->share)) {
1151 fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
1155 prepath = share + sharelen;
1156 prepathlen = strlen(prepath);
1158 if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
1159 fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
1163 /* copy pieces into their resepective buffers */
1164 memcpy(parsed_info->host, host, hostlen);
1165 memcpy(parsed_info->share, share, sharelen);
1166 memcpy(parsed_info->prefix, prepath, prepathlen);
1171 static int get_pw_from_env(struct parsed_mount_info *parsed_info)
1175 if (getenv("PASSWD"))
1176 rc = set_password(parsed_info, getenv("PASSWD"));
1177 else if (getenv("PASSWD_FD"))
1178 rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
1180 else if (getenv("PASSWD_FILE"))
1181 rc = get_password_from_file(0, getenv("PASSWD_FILE"),
1187 static struct option longopts[] = {
1188 {"all", 0, NULL, 'a'},
1189 {"help", 0, NULL, 'h'},
1190 {"move", 0, NULL, 'm'},
1191 {"bind", 0, NULL, 'b'},
1192 {"read-only", 0, NULL, 'r'},
1193 {"ro", 0, NULL, 'r'},
1194 {"verbose", 0, NULL, 'v'},
1195 {"version", 0, NULL, 'V'},
1196 {"read-write", 0, NULL, 'w'},
1197 {"rw", 0, NULL, 'w'},
1198 {"options", 1, NULL, 'o'},
1199 {"type", 1, NULL, 't'},
1200 {"uid", 1, NULL, '1'},
1201 {"gid", 1, NULL, '2'},
1202 {"user", 1, NULL, 'u'},
1203 {"username", 1, NULL, 'u'},
1204 {"dom", 1, NULL, 'd'},
1205 {"domain", 1, NULL, 'd'},
1206 {"password", 1, NULL, 'p'},
1207 {"pass", 1, NULL, 'p'},
1208 {"credentials", 1, NULL, 'c'},
1209 {"port", 1, NULL, 'P'},
1213 /* convert a string to uppercase. return false if the string
1214 * wasn't ASCII. Return success on a NULL ptr */
1215 static int uppercase_string(char *string)
1221 /* check for unicode */
1222 if ((unsigned char)string[0] & 0x80)
1224 *string = toupper((unsigned char)*string);
1231 static void print_cifs_mount_version(void)
1233 printf("mount.cifs version: %s\n", VERSION);
1237 * This function borrowed from fuse-utils...
1239 * glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
1240 * newlines embedded within the text fields. To make sure no one corrupts
1241 * the mtab, fail the mount if there are embedded newlines.
1243 static int check_newline(const char *progname, const char *name)
1246 for (s = "\n"; *s; s++) {
1247 if (strchr(name, *s)) {
1249 "%s: illegal character 0x%02x in mount entry\n",
1257 static int check_mtab(const char *progname, const char *devname,
1260 if (check_newline(progname, devname) == -1 ||
1261 check_newline(progname, dir) == -1)
1267 add_mtab(char *devname, char *mountpoint, unsigned long flags)
1271 char *mount_user = NULL;
1272 struct mntent mountent;
1274 sigset_t mask, oldmask;
1278 mount_user = getusername(uid);
1281 * Set the real uid to the effective uid. This prevents unprivileged
1282 * users from sending signals to this process, though ^c on controlling
1283 * terminal should still work.
1285 rc = setreuid(geteuid(), -1);
1287 fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
1292 rc = sigfillset(&mask);
1294 fprintf(stderr, "Unable to set filled signal mask\n");
1298 rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
1300 fprintf(stderr, "Unable to make process ignore signals\n");
1304 rc = toggle_capability(CAP_DAC_OVERRIDE, 1);
1308 atexit(unlock_mtab);
1311 fprintf(stderr, "cannot lock mtab");
1316 pmntfile = setmntent(MOUNTED, "a+");
1318 fprintf(stderr, "could not update mount table\n");
1324 mountent.mnt_fsname = devname;
1325 mountent.mnt_dir = mountpoint;
1326 mountent.mnt_type = (char *)(void *)cifs_fstype;
1327 mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
1328 if (mountent.mnt_opts) {
1329 if (flags & MS_RDONLY)
1330 strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
1332 strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
1334 if (flags & MS_MANDLOCK)
1335 strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
1336 if (flags & MS_NOEXEC)
1337 strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
1338 if (flags & MS_NOSUID)
1339 strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
1340 if (flags & MS_NODEV)
1341 strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
1342 if (flags & MS_SYNCHRONOUS)
1343 strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
1345 strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
1346 strlcat(mountent.mnt_opts, mount_user,
1350 mountent.mnt_freq = 0;
1351 mountent.mnt_passno = 0;
1352 rc = addmntent(pmntfile, &mountent);
1353 endmntent(pmntfile);
1355 SAFE_FREE(mountent.mnt_opts);
1357 toggle_capability(CAP_DAC_OVERRIDE, 0);
1358 sigprocmask(SIG_SETMASK, &oldmask, NULL);
1360 fprintf(stderr, "unable to add mount entry to mtab\n");
1367 /* have the child drop root privileges */
1369 drop_child_privs(void)
1372 uid_t uid = getuid();
1373 gid_t gid = getgid();
1378 fprintf(stderr, "Unable set group identity: %s\n",
1386 fprintf(stderr, "Unable set user identity: %s\n",
1396 assemble_mountinfo(struct parsed_mount_info *parsed_info,
1397 const char *thisprogram, const char *mountpoint,
1398 const char *orig_dev, char *orgoptions)
1402 rc = drop_capabilities(0);
1406 rc = drop_child_privs();
1411 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1416 /* enable any default user mount flags */
1417 parsed_info->flags |= CIFS_SETUID_FLAGS;
1420 rc = get_pw_from_env(parsed_info);
1425 rc = parse_options(orgoptions, parsed_info);
1431 if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
1432 fprintf(stderr, "%s: permission denied\n", thisprogram);
1438 parsed_info->flags &= ~(MS_USERS | MS_USER);
1440 rc = parse_unc(orig_dev, parsed_info);
1444 rc = resolve_host(parsed_info);
1448 if (!parsed_info->got_user) {
1450 * Note that the password will not be retrieved from the
1451 * USER env variable (ie user%password form) as there is
1452 * already a PASSWD environment varaible
1455 strlcpy(parsed_info->username, getenv("USER"),
1456 sizeof(parsed_info->username));
1458 strlcpy(parsed_info->username, getusername(getuid()),
1459 sizeof(parsed_info->username));
1460 parsed_info->got_user = 1;
1463 if (!parsed_info->got_password) {
1464 /* getpass is obsolete, but there's apparently nothing that replaces it */
1465 char *tmp_pass = getpass("Password: ");
1467 fprintf(stderr, "Error reading password, exiting\n");
1471 rc = set_password(parsed_info, tmp_pass);
1476 /* copy in ver= string. It's not really needed, but what the hell */
1477 strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
1478 strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
1480 /* copy in user= string */
1481 if (parsed_info->got_user) {
1482 strlcat(parsed_info->options, ",user=",
1483 sizeof(parsed_info->options));
1484 strlcat(parsed_info->options, parsed_info->username,
1485 sizeof(parsed_info->options));
1488 if (*parsed_info->domain) {
1489 strlcat(parsed_info->options, ",domain=",
1490 sizeof(parsed_info->options));
1491 strlcat(parsed_info->options, parsed_info->domain,
1492 sizeof(parsed_info->options));
1499 int main(int argc, char **argv)
1502 char *orgoptions = NULL;
1503 char *mountpoint = NULL;
1504 char *options = NULL;
1505 char *dev_name = NULL, *orig_dev = NULL;
1506 char *currentaddress, *nextaddress;
1508 int already_uppercased = 0;
1509 size_t options_size = MAX_OPTIONS_LEN;
1511 struct parsed_mount_info *parsed_info = NULL;
1514 rc = check_setuid();
1518 rc = drop_capabilities(1);
1522 /* setlocale(LC_ALL, "");
1523 bindtextdomain(PACKAGE, LOCALEDIR);
1524 textdomain(PACKAGE); */
1526 if (!argc || !argv) {
1527 rc = mount_cifs_usage(stderr);
1531 thisprogram = argv[0];
1532 if (thisprogram == NULL)
1533 thisprogram = "mount.cifs";
1535 /* allocate parsed_info as shared anonymous memory range */
1536 parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
1537 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
1538 if (parsed_info == (struct parsed_mount_info *) -1) {
1540 fprintf(stderr, "Unable to allocate memory: %s\n",
1545 parsed_info->flags = MS_MANDLOCK;
1547 /* add sharename in opts string as unc= parm */
1548 while ((c = getopt_long(argc, argv, "?fhno:rvVw",
1549 longopts, NULL)) != -1) {
1552 case 'h': /* help */
1553 rc = mount_cifs_usage(stdout);
1556 ++parsed_info->nomtab;
1559 orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
1565 case 'r': /* mount readonly */
1566 parsed_info->flags |= MS_RDONLY;
1569 ++parsed_info->verboseflag;
1572 print_cifs_mount_version();
1575 parsed_info->flags &= ~MS_RDONLY;
1578 ++parsed_info->fakemnt;
1581 fprintf(stderr, "unknown command-line option: %c\n", c);
1582 rc = mount_cifs_usage(stderr);
1587 if (argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL) {
1588 rc = mount_cifs_usage(stderr);
1592 orig_dev = argv[optind];
1593 mountpoint = argv[optind + 1];
1595 /* chdir into mountpoint as soon as possible */
1596 rc = chdir(mountpoint);
1598 fprintf(stderr, "Couldn't chdir to %s: %s\n", mountpoint,
1604 mountpoint = realpath(".", NULL);
1606 fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
1607 mountpoint, strerror(errno));
1613 * mount.cifs does privilege separation. Most of the code to handle
1614 * assembling the mount info is done in a child process that drops
1615 * privileges. The info is assembled in parsed_info which is a
1616 * shared, mmaped memory segment. The parent waits for the child to
1617 * exit and checks the return code. If it's anything but "0", then
1618 * the process exits without attempting anything further.
1622 fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
1627 rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
1628 orig_dev, orgoptions);
1633 if (!WIFEXITED(rc)) {
1634 fprintf(stderr, "Child process terminated abnormally.\n");
1638 rc = WEXITSTATUS(rc);
1643 options = calloc(options_size, 1);
1645 fprintf(stderr, "Unable to allocate memory.\n");
1650 dev_len = strnlen(parsed_info->host, sizeof(parsed_info->host)) +
1651 strnlen(parsed_info->share, sizeof(parsed_info->share)) +
1652 strnlen(parsed_info->prefix, sizeof(parsed_info->prefix)) +
1654 dev_name = calloc(dev_len, 1);
1660 /* rebuild device name with forward slashes */
1661 strlcpy(dev_name, "//", dev_len);
1662 strlcat(dev_name, parsed_info->host, dev_len);
1663 strlcat(dev_name, "/", dev_len);
1664 strlcat(dev_name, parsed_info->share, dev_len);
1665 strlcat(dev_name, parsed_info->prefix, dev_len);
1667 currentaddress = parsed_info->addrlist;
1668 nextaddress = strchr(currentaddress, ',');
1670 *nextaddress++ = '\0';
1673 if (!currentaddress) {
1674 fprintf(stderr, "Unable to find suitable address.\n");
1678 strlcpy(options, "ip=", options_size);
1679 strlcat(options, currentaddress, options_size);
1681 strlcat(options, ",unc=\\\\", options_size);
1682 strlcat(options, parsed_info->host, options_size);
1683 strlcat(options, "\\", options_size);
1684 strlcat(options, parsed_info->share, options_size);
1686 if (*parsed_info->options) {
1687 strlcat(options, ",", options_size);
1688 strlcat(options, parsed_info->options, options_size);
1691 if (*parsed_info->prefix) {
1692 strlcat(options, ",prefixpath=", options_size);
1693 strlcat(options, parsed_info->prefix, options_size);
1696 if (parsed_info->verboseflag)
1697 fprintf(stderr, "mount.cifs kernel mount options: %s\n",
1700 if (parsed_info->got_password) {
1702 * Commas have to be doubled, or else they will
1703 * look like the parameter separator
1705 strlcat(options, ",pass=", options_size);
1706 strlcat(options, parsed_info->password, options_size);
1707 if (parsed_info->verboseflag)
1708 fprintf(stderr, ",pass=********");
1711 if (parsed_info->verboseflag)
1712 fprintf(stderr, "\n");
1714 rc = check_mtab(thisprogram, dev_name, mountpoint);
1718 if (!parsed_info->fakemnt
1719 && mount(dev_name, ".", cifs_fstype, parsed_info->flags, options)) {
1723 currentaddress = nextaddress;
1724 nextaddress = strchr(currentaddress, ',');
1726 *nextaddress++ = '\0';
1730 "mount error: cifs filesystem not supported by the system\n");
1733 if (!already_uppercased &&
1734 uppercase_string(parsed_info->host) &&
1735 uppercase_string(parsed_info->share) &&
1736 uppercase_string(parsed_info->prefix)) {
1738 "Retrying with upper case share name\n");
1739 already_uppercased = 1;
1743 fprintf(stderr, "mount error(%d): %s\n", errno,
1746 "Refer to the mount.cifs(8) manual page (e.g. man "
1752 if (!parsed_info->nomtab)
1753 rc = add_mtab(dev_name, mountpoint, parsed_info->flags);
1757 memset(parsed_info->password, 0, sizeof(parsed_info->password));
1758 munmap(parsed_info, sizeof(*parsed_info));
1760 SAFE_FREE(dev_name);
1762 SAFE_FREE(orgoptions);