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>
48 #include <sys/capability.h>
49 #endif /* HAVE_LIBCAP */
61 /* private flags - clear these before passing to kernel */
62 #define MS_USERS 0x40000000
63 #define MS_USER 0x80000000
65 #define MAX_UNC_LEN 1024
67 /* I believe that the kernel limits options data to a page */
68 #define MAX_OPTIONS_LEN 4096
70 /* max length of mtab options */
71 #define MTAB_OPTIONS_LEN 220
74 * Maximum length of "share" portion of a UNC. I have no idea if this is at
75 * all valid. According to MSDN, the typical max length of any component is
76 * 255, so use that here.
78 #define MAX_SHARE_LEN 256
80 /* max length of username (somewhat made up here) */
81 #define MAX_USERNAME_SIZE 32
83 /* currently maximum length of IPv6 address string */
84 #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
86 /* limit list of addresses to 16 max-size addrs */
87 #define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16)
90 #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
93 #define MOUNT_PASSWD_SIZE 128
94 #define DOMAIN_SIZE 64
97 * value of the ver= option that gets passed to the kernel. Used to indicate
98 * behavioral changes introduced in the mount helper.
100 #define OPTIONS_VERSION "1"
103 * mount.cifs has been the subject of many "security" bugs that have arisen
104 * because of users and distributions installing it as a setuid root program
105 * before it had been audited for security holes. The default behavior is
106 * now to allow mount.cifs to be run as a setuid root program. Some admins
107 * may want to disable this fully, so this switch remains in place.
109 #define CIFS_DISABLE_SETUID_CAPABILITY 0
112 * When an unprivileged user runs a setuid mount.cifs, we set certain mount
113 * flags by default. These defaults can be changed here.
115 #define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
117 /* struct for holding parsed mount info for use by privleged process */
118 struct parsed_mount_info {
120 char host[NI_MAXHOST + 1];
121 char share[MAX_SHARE_LEN + 1];
122 char prefix[PATH_MAX + 1];
123 char options[MAX_OPTIONS_LEN];
124 char domain[DOMAIN_SIZE + 1];
125 char username[MAX_USERNAME_SIZE + 1];
126 char password[MOUNT_PASSWD_SIZE + 1];
127 char addrlist[MAX_ADDR_LIST_LEN];
128 unsigned int got_user:1;
129 unsigned int got_password:1;
130 unsigned int fakemnt:1;
131 unsigned int nomtab:1;
132 unsigned int verboseflag:1;
135 const char *thisprogram;
136 const char *cifs_fstype = "cifs";
138 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
140 static int check_setuid(void)
143 fprintf(stderr, "This program is not installed setuid root - "
144 " \"user\" CIFS mounts not supported.\n");
148 #if CIFS_DISABLE_SETUID_CAPABILITY
149 if (getuid() && !geteuid()) {
150 printf("This mount.cifs program has been built with the "
151 "ability to run as a setuid root program disabled.\n");
154 #endif /* CIFS_DISABLE_SETUID_CAPABILITY */
160 check_fstab(const char *progname, const char *mountpoint, const char *devname,
166 /* make sure this mount is listed in /etc/fstab */
167 fstab = setmntent(_PATH_FSTAB, "r");
169 fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_FSTAB);
173 while ((mnt = getmntent(fstab))) {
174 if (!strcmp(mountpoint, mnt->mnt_dir))
179 if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
180 fprintf(stderr, "%s: permission denied: no match for "
181 "%s found in %s\n", progname, mountpoint, _PATH_FSTAB);
186 * 'mount' munges the options from fstab before passing them
187 * to us. It is non-trivial to test that we have the correct
188 * set of options. We don't want to trust what the user
189 * gave us, so just take whatever is in /etc/fstab.
192 *options = strdup(mnt->mnt_opts);
199 open nofollow - avoid symlink exposure?
200 get owner of dir see if matches self or if root
201 call system(umount argv) etc.
205 static int mount_cifs_usage(FILE * stream)
207 fprintf(stream, "\nUsage: %s <remotetarget> <dir> -o <options>\n",
209 fprintf(stream, "\nMount the remote target, specified as a UNC name,");
210 fprintf(stream, " to a local directory.\n\nOptions:\n");
211 fprintf(stream, "\tuser=<arg>\n\tpass=<arg>\n\tdom=<arg>\n");
212 fprintf(stream, "\nLess commonly used options:");
214 "\n\tcredentials=<filename>,guest,perm,noperm,setuids,nosetuids,rw,ro,");
216 "\n\tsep=<char>,iocharset=<codepage>,suid,nosuid,exec,noexec,serverino,");
218 "\n\tmapchars,nomapchars,nolock,servernetbiosname=<SRV_RFC1001NAME>");
220 "\n\tdirectio,nounix,cifsacl,sec=<authentication mechanism>,sign");
222 "\n\nOptions not needed for servers supporting CIFS Unix extensions");
224 "\n\t(e.g. unneeded for mounts to most Samba versions):");
226 "\n\tuid=<uid>,gid=<gid>,dir_mode=<mode>,file_mode=<mode>,sfu");
227 fprintf(stream, "\n\nRarely used options:");
229 "\n\tport=<tcpport>,rsize=<size>,wsize=<size>,unc=<unc_name>,ip=<ip_address>,");
231 "\n\tdev,nodev,nouser_xattr,netbiosname=<OUR_RFC1001NAME>,hard,soft,intr,");
233 "\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=<path>,nobrl");
235 "\n\nOptions are described in more detail in the manual page");
236 fprintf(stream, "\n\tman 8 mount.cifs\n");
237 fprintf(stream, "\nTo display the version number of the mount helper:");
238 fprintf(stream, "\n\t%s -V\n", thisprogram);
240 if (stream == stderr)
246 * CIFS has to "escape" commas in the password field so that they don't
247 * end up getting confused for option delimiters. Copy password into pw
248 * field, turning any commas into double commas.
250 static int set_password(struct parsed_mount_info *parsed_info, const char *src)
252 char *dst = parsed_info->password;
253 unsigned int i = 0, j = 0;
259 if (j > sizeof(parsed_info->password)) {
260 fprintf(stderr, "Converted password too long!\n");
265 parsed_info->got_password = 1;
269 /* caller frees username if necessary */
270 static char *getusername(uid_t uid)
272 char *username = NULL;
273 struct passwd *password = getpwuid(uid);
276 username = password->pw_name;
281 * Parse a username string into parsed_mount_info fields. The format is:
283 * DOMAIN\username%password
285 * ...obviously the only required component is "username". The source string
286 * is modified in the process, but it should remain unchanged at the end.
288 static int parse_username(char *rawuser, struct parsed_mount_info *parsed_info)
290 char *user, *password, slash;
293 /* everything after first % sign is a password */
294 password = strchr(rawuser, '%');
296 rc = set_password(parsed_info, password);
301 /* everything after first '/' or '\' is a username */
302 user = strchr(rawuser, '/');
304 user = strchr(rawuser, '\\');
306 /* everything before that slash is a domain */
310 strlcpy(parsed_info->domain, rawuser,
311 sizeof(parsed_info->domain));
317 strlcpy(parsed_info->username, user, sizeof(parsed_info->username));
318 parsed_info->got_user = 1;
325 static int open_cred_file(char *file_name,
326 struct parsed_mount_info *parsed_info)
329 char *temp_val, *newline;
333 i = access(file_name, R_OK);
337 fs = fopen(file_name, "r");
340 line_buf = (char *)malloc(4096);
341 if (line_buf == NULL) {
346 while (fgets(line_buf, 4096, fs)) {
347 /* parse line from credential file */
349 /* eat leading white space */
350 for (i = 0; i < 4086; i++) {
351 if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
353 /* if whitespace - skip past it */
356 /* NULL terminate at newline */
357 newline = strchr(line_buf + i, '\n');
361 if (strncasecmp("username", line_buf + i, 8) == 0) {
362 temp_val = strchr(line_buf + i, '=');
364 /* go past equals sign */
366 for (length = 0; length < 4087; length++) {
367 if ((temp_val[length] == '\n')
368 || (temp_val[length] == '\0')) {
369 temp_val[length] = '\0';
375 "mount.cifs failed due to malformed username in credentials file\n");
376 memset(line_buf, 0, 4096);
379 parsed_info->got_user = 1;
380 strlcpy(parsed_info->username, temp_val,
381 sizeof(parsed_info->username));
383 } else if (strncasecmp("password", line_buf + i, 8) == 0) {
384 temp_val = strchr(line_buf + i, '=');
388 i = set_password(parsed_info, temp_val);
391 } else if (strncasecmp("domain", line_buf + i, 6) == 0) {
392 temp_val = strchr(line_buf + i, '=');
394 /* go past equals sign */
396 if (parsed_info->verboseflag)
397 fprintf(stderr, "\nDomain %s\n",
400 for (length = 0; length < DOMAIN_SIZE + 1;
402 if ((temp_val[length] == '\n')
403 || (temp_val[length] == '\0')) {
404 temp_val[length] = '\0';
409 if (length > DOMAIN_SIZE) {
411 "mount.cifs failed: domain in credentials file too long\n");
415 strlcpy(parsed_info->domain, temp_val,
416 sizeof(parsed_info->domain));
427 get_password_from_file(int file_descript, char *filename,
428 struct parsed_mount_info *parsed_info)
431 char buf[sizeof(parsed_info->password) + 1];
433 if (filename != NULL) {
434 rc = access(filename, R_OK);
437 "mount.cifs failed: access check of %s failed: %s\n",
438 filename, strerror(errno));
441 file_descript = open(filename, O_RDONLY);
442 if (file_descript < 0) {
444 "mount.cifs failed. %s attempting to open password file %s\n",
445 strerror(errno), filename);
450 memset(buf, 0, sizeof(buf));
451 rc = read(file_descript, buf, sizeof(buf) - 1);
454 "mount.cifs failed. Error %s reading password file\n",
460 rc = set_password(parsed_info, buf);
463 if (filename != NULL) {
464 close(file_descript);
470 parse_options(const char *data, struct parsed_mount_info *parsed_info)
472 char *value = NULL, *equals = NULL;
473 char *next_keyword = NULL;
474 char *out = parsed_info->options;
475 unsigned long *filesys_flags = &parsed_info->flags;
479 int got_uid = 0, got_gid = 0;
483 /* make sure we're starting from beginning */
486 /* BB fixme check for separator override BB */
489 snprintf(user, sizeof(user), "%u", getuid());
491 snprintf(group, sizeof(group), "%u", getgid());
498 * format is keyword,keyword2=value2,keyword3=value3...
499 * data = next keyword
500 * value = next value ie stuff after equal sign
502 while (data && *data) {
503 next_keyword = strchr(data, ','); /* BB handle sep= */
505 /* temporarily null terminate end of keyword=value pair */
509 /* temporarily null terminate keyword if there's a value */
511 if ((equals = strchr(data, '=')) != NULL) {
516 /* FIXME: turn into a token parser? */
517 if (strncmp(data, "users", 5) == 0) {
518 if (!value || !*value) {
519 *filesys_flags |= MS_USERS;
522 } else if (strncmp(data, "user_xattr", 10) == 0) {
523 /* do nothing - need to skip so not parsed as user name */
524 } else if (strncmp(data, "user", 4) == 0) {
525 if (!value || !*value) {
526 if (data[4] == '\0') {
527 *filesys_flags |= MS_USER;
531 "username specified with no parameter\n");
535 if (strnlen(value, 260) >= 260) {
536 fprintf(stderr, "username too long\n");
539 rc = parse_username(value, parsed_info);
542 "problem parsing username\n");
547 } else if (strncmp(data, "pass", 4) == 0) {
548 if (parsed_info->got_password) {
550 "password specified twice, ignoring second\n");
553 if (!value || !*value) {
554 parsed_info->got_password = 1;
557 rc = set_password(parsed_info, value);
561 } else if (strncmp(data, "sec", 3) == 0) {
563 if (!strncmp(value, "none", 4) ||
564 !strncmp(value, "krb5", 4))
565 parsed_info->got_password = 1;
567 } else if (strncmp(data, "ip", 2) == 0) {
568 if (!value || !*value) {
570 "target ip address argument missing");
571 } else if (strnlen(value, MAX_ADDRESS_LEN) <=
573 if (parsed_info->verboseflag)
575 "ip address %s override specified\n",
578 fprintf(stderr, "ip address too long\n");
581 } else if ((strncmp(data, "unc", 3) == 0)
582 || (strncmp(data, "target", 6) == 0)
583 || (strncmp(data, "path", 4) == 0)) {
584 if (!value || !*value) {
586 "invalid path to network resource\n");
587 return EX_USAGE; /* needs_arg; */
589 rc = parse_unc(value, parsed_info);
592 } else if ((strncmp(data, "dom" /* domain */ , 3) == 0)
593 || (strncmp(data, "workg", 5) == 0)) {
594 /* note this allows for synonyms of "domain"
595 such as "DOM" and "dom" and "workgroup"
596 and "WORKGRP" etc. */
597 if (!value || !*value) {
598 fprintf(stderr, "CIFS: invalid domain name\n");
601 if (strnlen(value, sizeof(parsed_info->domain)) >=
602 sizeof(parsed_info->domain)) {
603 fprintf(stderr, "domain name too long\n");
606 strlcpy(parsed_info->domain, value,
607 sizeof(parsed_info->domain));
609 } else if (strncmp(data, "cred", 4) == 0) {
610 if (value && *value) {
611 rc = open_cred_file(value, parsed_info);
614 "error %d (%s) opening credential file %s\n",
615 rc, strerror(rc), value);
620 "invalid credential file name specified\n");
623 } else if (strncmp(data, "uid", 3) == 0) {
624 if (value && *value) {
626 if (!isdigit(*value)) {
629 if (!(pw = getpwnam(value))) {
631 "bad user name \"%s\"\n",
635 snprintf(user, sizeof(user), "%u",
638 strlcpy(user, value, sizeof(user));
642 } else if (strncmp(data, "gid", 3) == 0) {
643 if (value && *value) {
645 if (!isdigit(*value)) {
648 if (!(gr = getgrnam(value))) {
650 "bad group name \"%s\"\n",
654 snprintf(group, sizeof(group), "%u",
657 strlcpy(group, value, sizeof(group));
661 /* fmask and dmask synonyms for people used to smbfs syntax */
662 } else if (strcmp(data, "file_mode") == 0
663 || strcmp(data, "fmask") == 0) {
664 if (!value || !*value) {
666 "Option '%s' requires a numerical argument\n",
671 if (value[0] != '0') {
673 "WARNING: '%s' not expressed in octal.\n",
677 if (strcmp(data, "fmask") == 0) {
679 "WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
680 data = "file_mode"; /* BB fix this */
682 } else if (strcmp(data, "dir_mode") == 0
683 || strcmp(data, "dmask") == 0) {
684 if (!value || !*value) {
686 "Option '%s' requires a numerical argument\n",
691 if (value[0] != '0') {
693 "WARNING: '%s' not expressed in octal.\n",
697 if (strcmp(data, "dmask") == 0) {
699 "WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
702 /* the following eight mount options should be
703 stripped out from what is passed into the kernel
704 since these eight options are best passed as the
705 mount flags rather than redundantly to the kernel
706 and could generate spurious warnings depending on the
707 level of the corresponding cifs vfs kernel code */
708 } else if (strncmp(data, "nosuid", 6) == 0) {
709 *filesys_flags |= MS_NOSUID;
710 } else if (strncmp(data, "suid", 4) == 0) {
711 *filesys_flags &= ~MS_NOSUID;
712 } else if (strncmp(data, "nodev", 5) == 0) {
713 *filesys_flags |= MS_NODEV;
714 } else if ((strncmp(data, "nobrl", 5) == 0) ||
715 (strncmp(data, "nolock", 6) == 0)) {
716 *filesys_flags &= ~MS_MANDLOCK;
717 } else if (strncmp(data, "dev", 3) == 0) {
718 *filesys_flags &= ~MS_NODEV;
719 } else if (strncmp(data, "noexec", 6) == 0) {
720 *filesys_flags |= MS_NOEXEC;
721 } else if (strncmp(data, "exec", 4) == 0) {
722 *filesys_flags &= ~MS_NOEXEC;
723 } else if (strncmp(data, "guest", 5) == 0) {
724 parsed_info->got_user = 1;
725 parsed_info->got_password = 1;
726 } else if (strncmp(data, "ro", 2) == 0) {
727 *filesys_flags |= MS_RDONLY;
729 } else if (strncmp(data, "rw", 2) == 0) {
730 *filesys_flags &= ~MS_RDONLY;
732 } else if (strncmp(data, "remount", 7) == 0) {
733 *filesys_flags |= MS_REMOUNT;
736 /* check size before copying option to buffer */
737 word_len = strlen(data);
739 word_len += 1 + strlen(value);
741 /* need 2 extra bytes for comma and null byte */
742 if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
743 fprintf(stderr, "Options string too long\n");
747 /* put back equals sign, if any */
751 /* go ahead and copy */
753 strlcat(out, ",", MAX_OPTIONS_LEN);
755 strlcat(out, data, MAX_OPTIONS_LEN);
756 out_len = strlen(out);
761 /* special-case the uid and gid */
763 word_len = strlen(user);
765 if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
766 fprintf(stderr, "Options string too long\n");
771 strlcat(out, ",", out_len + word_len + 6);
774 snprintf(out + out_len, word_len + 5, "uid=%s", user);
775 out_len = strlen(out);
778 word_len = strlen(group);
780 if (out_len + 1 + word_len + 6 > MAX_OPTIONS_LEN) {
781 fprintf(stderr, "Options string too long\n");
786 strlcat(out, ",", out_len + word_len + 6);
789 snprintf(out + out_len, word_len + 5, "gid=%s", group);
790 out_len = strlen(out);
797 * resolve "host" portion of parsed info to comma-separated list of
800 static int resolve_host(struct parsed_mount_info *parsed_info)
803 /* 10 for max width of decimal scopeid */
804 char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
807 struct addrinfo *addrlist, *addr;
808 struct sockaddr_in *sin;
809 struct sockaddr_in6 *sin6;
811 rc = getaddrinfo(parsed_info->host, NULL, NULL, &addrlist);
813 fprintf(stderr, "mount error: could not resolve address for "
814 "%s: %s\n", parsed_info->host,
815 rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
816 /* FIXME: return better error based on rc? */
822 /* skip non-TCP entries */
823 if (addr->ai_socktype != SOCK_STREAM ||
824 addr->ai_protocol != IPPROTO_TCP) {
825 addr = addr->ai_next;
829 switch (addr->ai_addr->sa_family) {
831 sin6 = (struct sockaddr_in6 *)addr->ai_addr;
832 ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
837 "mount error: problem parsing address "
838 "list: %s\n", strerror(errno));
839 goto resolve_host_out;
842 if (sin6->sin6_scope_id) {
843 len = strnlen(tmpbuf, sizeof(tmpbuf));
844 ipaddr = tmpbuf + len;
845 snprintf(tmpbuf, sizeof(tmpbuf) - len, "%%%u",
846 sin6->sin6_scope_id);
850 sin = (struct sockaddr_in *)addr->ai_addr;
851 ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
856 "mount error: problem parsing address "
857 "list: %s\n", strerror(errno));
858 goto resolve_host_out;
863 addr = addr->ai_next;
867 if (parsed_info->addrlist[0] != '\0')
868 strlcat(parsed_info->addrlist, ",",
869 sizeof(parsed_info->addrlist));
870 strlcat(parsed_info->addrlist, tmpbuf,
871 sizeof(parsed_info->addrlist));
872 addr = addr->ai_next;
876 freeaddrinfo(addrlist);
880 static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
882 int length = strnlen(unc_name, MAX_UNC_LEN);
883 const char *host, *share, *prepath;
884 size_t hostlen, sharelen, prepathlen;
886 if (length > (MAX_UNC_LEN - 1)) {
887 fprintf(stderr, "mount error: UNC name too long\n");
892 fprintf(stderr, "mount error: UNC name too short\n");
896 if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
897 (strncasecmp("smb://", unc_name, 6) == 0)) {
899 "Mounting cifs URL not implemented yet. Attempt to mount %s\n",
904 /* Set up "host" and "share" pointers based on UNC format. */
905 if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
907 * check for nfs syntax (server:/share/prepath)
909 * FIXME: IPv6 addresses?
912 share = strchr(host, ':');
914 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
917 hostlen = share - host;
923 hostlen = strcspn(host, "/\\");
925 fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
928 share = host + hostlen + 1;
931 if (hostlen + 1 > sizeof(parsed_info->host)) {
932 fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
936 sharelen = strcspn(share, "/\\");
937 if (sharelen + 1 > sizeof(parsed_info->share)) {
938 fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
942 prepath = share + sharelen;
943 prepathlen = strlen(prepath);
945 if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
946 fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
950 /* copy pieces into their resepective buffers */
951 memcpy(parsed_info->host, host, hostlen);
952 memcpy(parsed_info->share, share, sharelen);
953 memcpy(parsed_info->prefix, prepath, prepathlen);
958 static int get_pw_from_env(struct parsed_mount_info *parsed_info)
962 if (getenv("PASSWD"))
963 rc = set_password(parsed_info, getenv("PASSWD"));
964 else if (getenv("PASSWD_FD"))
965 rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
967 else if (getenv("PASSWD_FILE"))
968 rc = get_password_from_file(0, getenv("PASSWD_FILE"),
974 static struct option longopts[] = {
975 {"all", 0, NULL, 'a'},
976 {"help", 0, NULL, 'h'},
977 {"move", 0, NULL, 'm'},
978 {"bind", 0, NULL, 'b'},
979 {"read-only", 0, NULL, 'r'},
980 {"ro", 0, NULL, 'r'},
981 {"verbose", 0, NULL, 'v'},
982 {"version", 0, NULL, 'V'},
983 {"read-write", 0, NULL, 'w'},
984 {"rw", 0, NULL, 'w'},
985 {"options", 1, NULL, 'o'},
986 {"type", 1, NULL, 't'},
987 {"uid", 1, NULL, '1'},
988 {"gid", 1, NULL, '2'},
989 {"user", 1, NULL, 'u'},
990 {"username", 1, NULL, 'u'},
991 {"dom", 1, NULL, 'd'},
992 {"domain", 1, NULL, 'd'},
993 {"password", 1, NULL, 'p'},
994 {"pass", 1, NULL, 'p'},
995 {"credentials", 1, NULL, 'c'},
996 {"port", 1, NULL, 'P'},
1000 /* convert a string to uppercase. return false if the string
1001 * wasn't ASCII. Return success on a NULL ptr */
1002 static int uppercase_string(char *string)
1008 /* check for unicode */
1009 if ((unsigned char)string[0] & 0x80)
1011 *string = toupper((unsigned char)*string);
1018 static void print_cifs_mount_version(void)
1020 printf("mount.cifs version: %s\n", VERSION);
1024 * This function borrowed from fuse-utils...
1026 * glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
1027 * newlines embedded within the text fields. To make sure no one corrupts
1028 * the mtab, fail the mount if there are embedded newlines.
1030 static int check_newline(const char *progname, const char *name)
1033 for (s = "\n"; *s; s++) {
1034 if (strchr(name, *s)) {
1036 "%s: illegal character 0x%02x in mount entry\n",
1044 static int check_mtab(const char *progname, const char *devname,
1047 if (check_newline(progname, devname) == -1 ||
1048 check_newline(progname, dir) == -1)
1054 add_mtab(char *devname, char *mountpoint, unsigned long flags)
1058 char *mount_user = NULL;
1059 struct mntent mountent;
1061 sigset_t mask, oldmask;
1065 mount_user = getusername(uid);
1068 * Set the real uid to the effective uid. This prevents unprivileged
1069 * users from sending signals to this process, though ^c on controlling
1070 * terminal should still work.
1072 rc = setreuid(geteuid(), -1);
1074 fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
1079 rc = sigfillset(&mask);
1081 fprintf(stderr, "Unable to set filled signal mask\n");
1085 rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
1087 fprintf(stderr, "Unable to make process ignore signals\n");
1091 atexit(unlock_mtab);
1094 fprintf(stderr, "cannot lock mtab");
1099 pmntfile = setmntent(MOUNTED, "a+");
1101 fprintf(stderr, "could not update mount table\n");
1107 mountent.mnt_fsname = devname;
1108 mountent.mnt_dir = mountpoint;
1109 mountent.mnt_type = (char *)(void *)cifs_fstype;
1110 mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
1111 if (mountent.mnt_opts) {
1112 if (flags & MS_RDONLY)
1113 strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
1115 strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
1117 if (flags & MS_MANDLOCK)
1118 strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
1119 if (flags & MS_NOEXEC)
1120 strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
1121 if (flags & MS_NOSUID)
1122 strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
1123 if (flags & MS_NODEV)
1124 strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
1125 if (flags & MS_SYNCHRONOUS)
1126 strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
1128 strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
1129 strlcat(mountent.mnt_opts, mount_user,
1133 mountent.mnt_freq = 0;
1134 mountent.mnt_passno = 0;
1135 rc = addmntent(pmntfile, &mountent);
1136 endmntent(pmntfile);
1138 SAFE_FREE(mountent.mnt_opts);
1140 sigprocmask(SIG_SETMASK, &oldmask, NULL);
1142 fprintf(stderr, "unable to add mount entry to mtab\n");
1151 drop_capabilities(int parent)
1155 cap_value_t cap_list[2];
1157 caps = cap_get_proc();
1159 fprintf(stderr, "Unable to get current capability set: %s\n",
1164 if (cap_clear(caps) == -1) {
1165 fprintf(stderr, "Unable to clear capability set: %s\n",
1171 /* parent needs to keep some capabilities */
1173 cap_list[0] = CAP_SYS_ADMIN;
1174 cap_list[1] = CAP_DAC_OVERRIDE;
1175 if (cap_set_flag(caps, CAP_PERMITTED, 2, cap_list, CAP_SET) == -1) {
1176 fprintf(stderr, "Unable to set permitted capabilities: %s\n",
1181 if (cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_list, CAP_SET) == -1) {
1182 fprintf(stderr, "Unable to set effective capabilities: %s\n",
1189 if (cap_set_proc(caps) != 0) {
1190 fprintf(stderr, "Unable to set current process capabilities: %s\n",
1198 #else /* HAVE_LIBCAP */
1200 drop_capabilities(int parent)
1204 #endif /* HAVE_LIBCAP */
1206 /* have the child drop root privileges */
1208 drop_child_privs(void)
1211 uid_t uid = getuid();
1212 gid_t gid = getgid();
1217 fprintf(stderr, "Unable set group identity: %s\n",
1225 fprintf(stderr, "Unable set user identity: %s\n",
1235 assemble_mountinfo(struct parsed_mount_info *parsed_info,
1236 const char *thisprogram, const char *mountpoint,
1237 const char *orig_dev, char *orgoptions)
1241 rc = drop_capabilities(0);
1245 rc = drop_child_privs();
1250 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1255 /* enable any default user mount flags */
1256 parsed_info->flags |= CIFS_SETUID_FLAGS;
1259 rc = get_pw_from_env(parsed_info);
1264 rc = parse_options(orgoptions, parsed_info);
1270 if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
1271 fprintf(stderr, "%s: permission denied\n", thisprogram);
1277 parsed_info->flags &= ~(MS_USERS | MS_USER);
1279 rc = parse_unc(orig_dev, parsed_info);
1283 rc = resolve_host(parsed_info);
1287 if (!parsed_info->got_user) {
1289 * Note that the password will not be retrieved from the
1290 * USER env variable (ie user%password form) as there is
1291 * already a PASSWD environment varaible
1294 strlcpy(parsed_info->username, getenv("USER"),
1295 sizeof(parsed_info->username));
1297 strlcpy(parsed_info->username, getusername(getuid()),
1298 sizeof(parsed_info->username));
1299 parsed_info->got_user = 1;
1302 if (!parsed_info->got_password) {
1303 /* getpass is obsolete, but there's apparently nothing that replaces it */
1304 char *tmp_pass = getpass("Password: ");
1306 fprintf(stderr, "Error reading password, exiting\n");
1310 rc = set_password(parsed_info, tmp_pass);
1315 /* copy in ver= string. It's not really needed, but what the hell */
1316 strlcat(parsed_info->options, ",ver=", sizeof(parsed_info->options));
1317 strlcat(parsed_info->options, OPTIONS_VERSION, sizeof(parsed_info->options));
1319 /* copy in user= string */
1320 if (parsed_info->got_user) {
1321 strlcat(parsed_info->options, ",user=",
1322 sizeof(parsed_info->options));
1323 strlcat(parsed_info->options, parsed_info->username,
1324 sizeof(parsed_info->options));
1327 if (*parsed_info->domain) {
1328 strlcat(parsed_info->options, ",domain=",
1329 sizeof(parsed_info->options));
1330 strlcat(parsed_info->options, parsed_info->domain,
1331 sizeof(parsed_info->options));
1338 int main(int argc, char **argv)
1341 char *orgoptions = NULL;
1342 char *mountpoint = NULL;
1343 char *options = NULL;
1344 char *dev_name = NULL, *orig_dev = NULL;
1345 char *currentaddress, *nextaddress;
1347 int already_uppercased = 0;
1348 size_t options_size = MAX_OPTIONS_LEN;
1350 struct parsed_mount_info *parsed_info = NULL;
1353 rc = check_setuid();
1357 rc = drop_capabilities(1);
1361 /* setlocale(LC_ALL, "");
1362 bindtextdomain(PACKAGE, LOCALEDIR);
1363 textdomain(PACKAGE); */
1365 if (!argc || !argv) {
1366 rc = mount_cifs_usage(stderr);
1370 thisprogram = argv[0];
1371 if (thisprogram == NULL)
1372 thisprogram = "mount.cifs";
1374 /* allocate parsed_info as shared anonymous memory range */
1375 parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
1376 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
1377 if (parsed_info == (struct parsed_mount_info *) -1) {
1379 fprintf(stderr, "Unable to allocate memory: %s\n",
1384 parsed_info->flags = MS_MANDLOCK;
1386 /* add sharename in opts string as unc= parm */
1387 while ((c = getopt_long(argc, argv, "?fhno:rvVw",
1388 longopts, NULL)) != -1) {
1391 case 'h': /* help */
1392 rc = mount_cifs_usage(stdout);
1395 ++parsed_info->nomtab;
1398 orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
1404 case 'r': /* mount readonly */
1405 parsed_info->flags |= MS_RDONLY;
1408 ++parsed_info->verboseflag;
1411 print_cifs_mount_version();
1414 parsed_info->flags &= ~MS_RDONLY;
1417 ++parsed_info->fakemnt;
1420 fprintf(stderr, "unknown command-line option: %c\n", c);
1421 rc = mount_cifs_usage(stderr);
1426 if (argc < 3 || argv[optind] == NULL || argv[optind + 1] == NULL) {
1427 rc = mount_cifs_usage(stderr);
1431 orig_dev = argv[optind];
1432 mountpoint = argv[optind + 1];
1434 /* chdir into mountpoint as soon as possible */
1435 rc = chdir(mountpoint);
1437 fprintf(stderr, "Couldn't chdir to %s: %s\n", mountpoint,
1443 mountpoint = realpath(".", NULL);
1445 fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
1446 mountpoint, strerror(errno));
1452 * mount.cifs does privilege separation. Most of the code to handle
1453 * assembling the mount info is done in a child process that drops
1454 * privileges. The info is assembled in parsed_info which is a
1455 * shared, mmaped memory segment. The parent waits for the child to
1456 * exit and checks the return code. If it's anything but "0", then
1457 * the process exits without attempting anything further.
1461 fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
1466 rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
1467 orig_dev, orgoptions);
1472 if (!WIFEXITED(rc)) {
1473 fprintf(stderr, "Child process terminated abnormally.\n");
1477 rc = WEXITSTATUS(rc);
1482 options = calloc(options_size, 1);
1484 fprintf(stderr, "Unable to allocate memory.\n");
1489 dev_len = strnlen(parsed_info->host, sizeof(parsed_info->host)) +
1490 strnlen(parsed_info->share, sizeof(parsed_info->share)) +
1491 strnlen(parsed_info->prefix, sizeof(parsed_info->prefix)) +
1493 dev_name = calloc(dev_len, 1);
1499 /* rebuild device name with forward slashes */
1500 strlcpy(dev_name, "//", dev_len);
1501 strlcat(dev_name, parsed_info->host, dev_len);
1502 strlcat(dev_name, "/", dev_len);
1503 strlcat(dev_name, parsed_info->share, dev_len);
1504 strlcat(dev_name, parsed_info->prefix, dev_len);
1506 currentaddress = parsed_info->addrlist;
1507 nextaddress = strchr(currentaddress, ',');
1509 *nextaddress++ = '\0';
1512 if (!currentaddress) {
1513 fprintf(stderr, "Unable to find suitable address.\n");
1517 strlcpy(options, "ip=", options_size);
1518 strlcat(options, currentaddress, options_size);
1520 strlcat(options, ",unc=\\\\", options_size);
1521 strlcat(options, parsed_info->host, options_size);
1522 strlcat(options, "\\", options_size);
1523 strlcat(options, parsed_info->share, options_size);
1525 if (*parsed_info->options) {
1526 strlcat(options, ",", options_size);
1527 strlcat(options, parsed_info->options, options_size);
1530 if (*parsed_info->prefix) {
1531 strlcat(options, ",prefixpath=", options_size);
1532 strlcat(options, parsed_info->prefix, options_size);
1535 if (parsed_info->verboseflag)
1536 fprintf(stderr, "mount.cifs kernel mount options: %s\n",
1539 if (parsed_info->got_password) {
1541 * Commas have to be doubled, or else they will
1542 * look like the parameter separator
1544 strlcat(options, ",pass=", options_size);
1545 strlcat(options, parsed_info->password, options_size);
1546 if (parsed_info->verboseflag)
1547 fprintf(stderr, ",pass=********");
1550 if (parsed_info->verboseflag)
1551 fprintf(stderr, "\n");
1553 rc = check_mtab(thisprogram, dev_name, mountpoint);
1557 if (!parsed_info->fakemnt
1558 && mount(dev_name, ".", cifs_fstype, parsed_info->flags, options)) {
1562 currentaddress = nextaddress;
1563 nextaddress = strchr(currentaddress, ',');
1565 *nextaddress++ = '\0';
1569 "mount error: cifs filesystem not supported by the system\n");
1572 if (!already_uppercased &&
1573 uppercase_string(parsed_info->host) &&
1574 uppercase_string(parsed_info->share) &&
1575 uppercase_string(parsed_info->prefix)) {
1577 "Retrying with upper case share name\n");
1578 already_uppercased = 1;
1582 fprintf(stderr, "mount error(%d): %s\n", errno,
1585 "Refer to the mount.cifs(8) manual page (e.g. man "
1591 if (!parsed_info->nomtab)
1592 rc = add_mtab(dev_name, mountpoint, parsed_info->flags);
1596 memset(parsed_info->password, 0, sizeof(parsed_info->password));
1597 munmap(parsed_info, sizeof(*parsed_info));
1599 SAFE_FREE(dev_name);
1601 SAFE_FREE(orgoptions);