/*
* Mount helper utility for Linux CIFS VFS (virtual filesystem) client
* Copyright (C) 2003,2010 Steve French (sfrench@us.ibm.com)
* Copyright (C) 2008 Jeremy Allison (jra@samba.org)
* Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SYS_FSUID_H
#include
#endif /* HAVE_SYS_FSUID_H */
#ifdef HAVE_LIBCAP_NG
#include
#else /* HAVE_LIBCAP_NG */
#ifdef HAVE_PRCTL
#include
#endif /* HAVE_PRCTL */
#ifdef HAVE_LIBCAP
#include
#endif /* HAVE_LIBCAP */
#endif /* HAVE_LIBCAP_NG */
#include "mount.h"
#include "util.h"
#include "resolve_host.h"
#ifndef MS_MOVE
#define MS_MOVE 8192
#endif
#ifndef MS_BIND
#define MS_BIND 4096
#endif
/* private flags - clear these before passing to kernel */
#define MS_USERS 0x40000000
#define MS_USER 0x80000000
#define MAX_UNC_LEN 1024
/* I believe that the kernel limits options data to a page */
#define MAX_OPTIONS_LEN 4096
/* max length of mtab options */
#define MTAB_OPTIONS_LEN 220
/*
* Maximum length of "share" portion of a UNC. I have no idea if this is at
* all valid. According to MSDN, the typical max length of any component is
* 255, so use that here.
*/
#define MAX_SHARE_LEN 256
/* max length of username (somewhat made up here) */
#define MAX_USERNAME_SIZE 32
#ifndef SAFE_FREE
#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x = NULL; } } while (0)
#endif
#define MOUNT_PASSWD_SIZE 128
#define MAX_DOMAIN_SIZE 64
/*
* mount.cifs has been the subject of many "security" bugs that have arisen
* because of users and distributions installing it as a setuid root program
* before it had been audited for security holes. The default behavior is
* now to allow mount.cifs to be run as a setuid root program. Some admins
* may want to disable this fully, so this switch remains in place.
*/
#define CIFS_DISABLE_SETUID_CAPABILITY 0
/*
* When an unprivileged user runs a setuid mount.cifs, we set certain mount
* flags by default. These defaults can be changed here.
*/
#define CIFS_SETUID_FLAGS (MS_NOSUID|MS_NODEV)
/*
* Values for parsing a credentials file.
*/
#define CRED_UNPARSEABLE 0
#define CRED_USER 1
#define CRED_PASS 2
#define CRED_DOM 4
/*
* Values for parsing command line options.
*/
#define OPT_ERROR -1
#define OPT_IGNORE 0
#define OPT_USERS 1
#define OPT_USER 2
#define OPT_USER_XATTR 3
#define OPT_PASS 4
#define OPT_SEC 5
#define OPT_IP 6
#define OPT_UNC 7
#define OPT_CRED 8
#define OPT_UID 9
#define OPT_GID 10
#define OPT_FMASK 11
#define OPT_FILE_MODE 12
#define OPT_DMASK 13
#define OPT_DIR_MODE 14
#define OPT_DOM 15
#define OPT_NO_SUID 16
#define OPT_SUID 17
#define OPT_NO_DEV 18
#define OPT_DEV 19
#define OPT_NO_LOCK 20
#define OPT_NO_EXEC 21
#define OPT_EXEC 22
#define OPT_GUEST 23
#define OPT_RO 24
#define OPT_RW 25
#define OPT_REMOUNT 26
#define OPT_MAND 27
#define OPT_NOMAND 28
#define OPT_CRUID 29
#define OPT_BKUPUID 30
#define OPT_BKUPGID 31
#define OPT_NOFAIL 32
#define MNT_TMP_FILE "/.mtab.cifs.XXXXXX"
/* struct for holding parsed mount info for use by privleged process */
struct parsed_mount_info {
unsigned long flags;
char host[NI_MAXHOST + 1];
char share[MAX_SHARE_LEN + 1];
char prefix[PATH_MAX + 1];
char options[MAX_OPTIONS_LEN];
char domain[MAX_DOMAIN_SIZE + 1];
char username[MAX_USERNAME_SIZE + 1];
char password[MOUNT_PASSWD_SIZE + 1];
char addrlist[MAX_ADDR_LIST_LEN];
unsigned int got_user:1;
unsigned int got_password:1;
unsigned int fakemnt:1;
unsigned int nomtab:1;
unsigned int verboseflag:1;
unsigned int nofail:1;
};
const char *thisprogram;
const char *cifs_fstype = "cifs";
static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info);
static int check_setuid(void)
{
if (geteuid()) {
fprintf(stderr, "This program is not installed setuid root - "
" \"user\" CIFS mounts not supported.\n");
return EX_USAGE;
}
#if CIFS_DISABLE_SETUID_CAPABILITY
if (getuid() && !geteuid()) {
printf("This mount.cifs program has been built with the "
"ability to run as a setuid root program disabled.\n");
return EX_USAGE;
}
#endif /* CIFS_DISABLE_SETUID_CAPABILITY */
return 0;
}
static int
check_fstab(const char *progname, const char *mountpoint, const char *devname,
char **options)
{
FILE *fstab;
struct mntent *mnt;
/* make sure this mount is listed in /etc/fstab */
fstab = setmntent(_PATH_MNTTAB, "r");
if (!fstab) {
fprintf(stderr, "Couldn't open %s for reading!\n", _PATH_MNTTAB);
return EX_FILEIO;
}
while ((mnt = getmntent(fstab))) {
if (!strcmp(mountpoint, mnt->mnt_dir))
break;
}
endmntent(fstab);
if (mnt == NULL || strcmp(mnt->mnt_fsname, devname)) {
fprintf(stderr, "%s: permission denied: no match for "
"%s found in %s\n", progname, mountpoint, _PATH_MNTTAB);
return EX_USAGE;
}
/*
* 'mount' munges the options from fstab before passing them
* to us. It is non-trivial to test that we have the correct
* set of options. We don't want to trust what the user
* gave us, so just take whatever is in /etc/fstab.
*/
free(*options);
*options = strdup(mnt->mnt_opts);
return 0;
}
/* BB finish BB
cifs_umount
open nofollow - avoid symlink exposure?
get owner of dir see if matches self or if root
call system(umount argv) etc.
BB end finish BB */
static int mount_usage(FILE * stream)
{
fprintf(stream, "\nUsage: %s -o \n",
thisprogram);
fprintf(stream, "\nMount the remote target, specified as a UNC name,");
fprintf(stream, " to a local directory.\n\nOptions:\n");
fprintf(stream, "\tuser=\n\tpass=\n\tdom=\n");
fprintf(stream, "\nLess commonly used options:");
fprintf(stream,
"\n\tcredentials=,guest,perm,noperm,setuids,nosetuids,rw,ro,");
fprintf(stream,
"\n\tsep=,iocharset=,suid,nosuid,exec,noexec,serverino,");
fprintf(stream,
"\n\tmapchars,nomapchars,nolock,servernetbiosname=");
fprintf(stream,
"\n\tdirectio,nounix,cifsacl,sec=,sign,fsc");
fprintf(stream,
"\n\nOptions not needed for servers supporting CIFS Unix extensions");
fprintf(stream,
"\n\t(e.g. unneeded for mounts to most Samba versions):");
fprintf(stream,
"\n\tuid=,gid=,dir_mode=,file_mode=,sfu");
fprintf(stream, "\n\nRarely used options:");
fprintf(stream,
"\n\tport=,rsize=,wsize=,unc=,ip=,");
fprintf(stream,
"\n\tdev,nodev,nouser_xattr,netbiosname=,hard,soft,intr,");
fprintf(stream,
"\n\tnointr,ignorecase,noposixpaths,noacl,prefixpath=,nobrl");
fprintf(stream,
"\n\nOptions are described in more detail in the manual page");
fprintf(stream, "\n\tman 8 mount.cifs\n");
fprintf(stream, "\nTo display the version number of the mount helper:");
fprintf(stream, "\n\t%s -V\n", thisprogram);
if (stream == stderr)
return EX_USAGE;
return 0;
}
/*
* CIFS has to "escape" commas in the password field so that they don't
* end up getting confused for option delimiters. Copy password into pw
* field, turning any commas into double commas.
*/
static int set_password(struct parsed_mount_info *parsed_info, const char *src)
{
char *dst = parsed_info->password;
unsigned int i = 0, j = 0;
while (src[i]) {
if (src[i] == ',')
dst[j++] = ',';
dst[j++] = src[i++];
if (j > sizeof(parsed_info->password)) {
fprintf(stderr, "Converted password too long!\n");
return EX_USAGE;
}
}
dst[j] = '\0';
parsed_info->got_password = 1;
return 0;
}
#ifdef HAVE_LIBCAP_NG
static int
drop_capabilities(int parent)
{
capng_setpid(getpid());
capng_clear(CAPNG_SELECT_BOTH);
if (parent) {
if (capng_updatev(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH, CAP_DAC_OVERRIDE, -1)) {
fprintf(stderr, "Unable to update capability set.\n");
return EX_SYSERR;
}
if (capng_update(CAPNG_ADD, CAPNG_PERMITTED|CAPNG_EFFECTIVE, CAP_SYS_ADMIN)) {
fprintf(stderr, "Unable to update capability set.\n");
return EX_SYSERR;
}
} else {
if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
fprintf(stderr, "Unable to update capability set.\n");
return EX_SYSERR;
}
}
if (capng_apply(CAPNG_SELECT_BOTH)) {
fprintf(stderr, "Unable to apply new capability set.\n");
return EX_SYSERR;
}
return 0;
}
static int
toggle_dac_capability(int writable, int enable)
{
unsigned int capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
if (capng_update(enable ? CAPNG_ADD : CAPNG_DROP, CAPNG_EFFECTIVE, capability)) {
fprintf(stderr, "Unable to update capability set.\n");
return EX_SYSERR;
}
if (capng_apply(CAPNG_SELECT_CAPS)) {
fprintf(stderr, "Unable to apply new capability set.\n");
return EX_SYSERR;
}
return 0;
}
#else /* HAVE_LIBCAP_NG */
#ifdef HAVE_LIBCAP
#ifdef HAVE_PRCTL
static int
prune_bounding_set(void)
{
int i, rc = 0;
static int bounding_set_cleared;
if (bounding_set_cleared)
return 0;
for (i = 0; i <= CAP_LAST_CAP && rc == 0; ++i)
rc = prctl(PR_CAPBSET_DROP, i);
if (rc != 0) {
fprintf(stderr, "Unable to clear capability bounding set: %d\n", rc);
return EX_SYSERR;
}
++bounding_set_cleared;
return 0;
}
#else /* HAVE_PRCTL */
static int
prune_bounding_set(void)
{
return 0;
}
#endif /* HAVE_PRCTL */
static int
drop_capabilities(int parent)
{
int rc, ncaps;
cap_t caps;
cap_value_t cap_list[3];
rc = prune_bounding_set();
if (rc)
return rc;
caps = cap_get_proc();
if (caps == NULL) {
fprintf(stderr, "Unable to get current capability set: %s\n",
strerror(errno));
return EX_SYSERR;
}
if (cap_clear(caps) == -1) {
fprintf(stderr, "Unable to clear capability set: %s\n",
strerror(errno));
rc = EX_SYSERR;
goto free_caps;
}
if (parent || getuid() == 0) {
ncaps = 1;
cap_list[0] = CAP_DAC_READ_SEARCH;
if (parent) {
cap_list[1] = CAP_DAC_OVERRIDE;
cap_list[2] = CAP_SYS_ADMIN;
ncaps += 2;
}
if (cap_set_flag(caps, CAP_PERMITTED, ncaps, cap_list, CAP_SET) == -1) {
fprintf(stderr, "Unable to set permitted capabilities: %s\n",
strerror(errno));
rc = EX_SYSERR;
goto free_caps;
}
if (parent) {
cap_list[0] = CAP_SYS_ADMIN;
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
fprintf(stderr, "Unable to set effective capabilities: %s\n",
strerror(errno));
rc = EX_SYSERR;
goto free_caps;
}
}
}
if (cap_set_proc(caps) != 0) {
fprintf(stderr, "Unable to set current process capabilities: %s\n",
strerror(errno));
rc = EX_SYSERR;
}
free_caps:
cap_free(caps);
return rc;
}
static int
toggle_dac_capability(int writable, int enable)
{
int rc = 0;
cap_t caps;
cap_value_t capability = writable ? CAP_DAC_OVERRIDE : CAP_DAC_READ_SEARCH;
caps = cap_get_proc();
if (caps == NULL) {
fprintf(stderr, "Unable to get current capability set: %s\n",
strerror(errno));
return EX_SYSERR;
}
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &capability,
enable ? CAP_SET : CAP_CLEAR) == -1) {
fprintf(stderr, "Unable to %s effective capabilities: %s\n",
enable ? "set" : "clear", strerror(errno));
rc = EX_SYSERR;
goto free_caps;
}
if (cap_set_proc(caps) != 0) {
fprintf(stderr, "Unable to set current process capabilities: %s\n",
strerror(errno));
rc = EX_SYSERR;
}
free_caps:
cap_free(caps);
return rc;
}
#else /* HAVE_LIBCAP */
static int
drop_capabilities(int parent __attribute((unused)))
{
return 0;
}
static int
toggle_dac_capability(int writable __attribute((unused)), int enable __attribute((unused)))
{
return 0;
}
#endif /* HAVE_LIBCAP */
#endif /* HAVE_LIBCAP_NG */
static void null_terminate_endl(char *source)
{
char *newline = strchr(source, '\n');
if (newline)
*newline = '\0';
}
/*
* Parse a line from the credentials file. Changes target to first
* character after '=' on 'line' and returns the value type of the line
* Returns CRED_UNPARSEABLE on failure or if either parameter is NULL.
*/
static int parse_cred_line(char *line, char **target)
{
if (line == NULL || target == NULL)
goto parsing_err;
/* position target at first char of value */
*target = strchr(line, '=');
if (!*target)
goto parsing_err;
*target += 1;
/* tell the caller which value target points to */
if (strncasecmp("user", line, 4) == 0)
return CRED_USER;
if (strncasecmp("pass", line, 4) == 0)
return CRED_PASS;
if (strncasecmp("dom", line, 3) == 0)
return CRED_DOM;
parsing_err:
return CRED_UNPARSEABLE;
}
static int open_cred_file(char *file_name,
struct parsed_mount_info *parsed_info)
{
char *line_buf = NULL;
char *temp_val = NULL;
FILE *fs = NULL;
int i;
const int line_buf_size = 4096;
const int min_non_white = 10;
i = toggle_dac_capability(0, 1);
if (i)
goto return_i;
i = access(file_name, R_OK);
if (i) {
toggle_dac_capability(0, 0);
i = errno;
goto return_i;
}
fs = fopen(file_name, "r");
if (fs == NULL) {
toggle_dac_capability(0, 0);
i = errno;
goto return_i;
}
i = toggle_dac_capability(0, 0);
if (i)
goto return_i;
line_buf = (char *)malloc(line_buf_size);
if (line_buf == NULL) {
i = EX_SYSERR;
goto return_i;
}
/* parse line from credentials file */
while (fgets(line_buf, line_buf_size, fs)) {
/* eat leading white space */
for (i = 0; i < line_buf_size - min_non_white + 1; i++) {
if ((line_buf[i] != ' ') && (line_buf[i] != '\t'))
break;
}
null_terminate_endl(line_buf);
/* parse next token */
switch (parse_cred_line(line_buf + i, &temp_val)) {
case CRED_USER:
strlcpy(parsed_info->username, temp_val,
sizeof(parsed_info->username));
parsed_info->got_user = 1;
break;
case CRED_PASS:
i = set_password(parsed_info, temp_val);
if (i)
goto return_i;
break;
case CRED_DOM:
if (parsed_info->verboseflag)
fprintf(stderr, "domain=%s\n",
temp_val);
strlcpy(parsed_info->domain, temp_val,
sizeof(parsed_info->domain));
break;
case CRED_UNPARSEABLE:
if (parsed_info->verboseflag)
fprintf(stderr, "Credential formatted "
"incorrectly: %s\n",
temp_val ? temp_val : "(null)");
break;
}
}
i = 0;
return_i:
if (fs != NULL)
fclose(fs);
/* make sure passwords are scrubbed from memory */
if (line_buf != NULL)
memset(line_buf, 0, line_buf_size);
SAFE_FREE(line_buf);
return i;
}
static int
get_password_from_file(int file_descript, char *filename,
struct parsed_mount_info *parsed_info)
{
int rc = 0;
char buf[sizeof(parsed_info->password) + 1];
if (filename != NULL) {
rc = toggle_dac_capability(0, 1);
if (rc)
return rc;
rc = access(filename, R_OK);
if (rc) {
fprintf(stderr,
"mount.cifs failed: access check of %s failed: %s\n",
filename, strerror(errno));
toggle_dac_capability(0, 0);
return EX_SYSERR;
}
file_descript = open(filename, O_RDONLY);
if (file_descript < 0) {
fprintf(stderr,
"mount.cifs failed. %s attempting to open password file %s\n",
strerror(errno), filename);
toggle_dac_capability(0, 0);
return EX_SYSERR;
}
rc = toggle_dac_capability(0, 0);
if (rc) {
rc = EX_SYSERR;
goto get_pw_exit;
}
}
memset(buf, 0, sizeof(buf));
rc = read(file_descript, buf, sizeof(buf) - 1);
if (rc < 0) {
fprintf(stderr,
"mount.cifs failed. Error %s reading password file\n",
strerror(errno));
rc = EX_SYSERR;
goto get_pw_exit;
}
rc = set_password(parsed_info, buf);
get_pw_exit:
if (filename != NULL)
close(file_descript);
return rc;
}
/*
* Returns OPT_ERROR on unparsable token.
*/
static int parse_opt_token(const char *token)
{
if (token == NULL)
return OPT_ERROR;
if (strncmp(token, "users", 5) == 0)
return OPT_USERS;
if (strncmp(token, "user_xattr", 10) == 0)
return OPT_USER_XATTR;
if (strncmp(token, "user", 4) == 0)
return OPT_USER;
if (strncmp(token, "pass", 4) == 0)
return OPT_PASS;
if (strncmp(token, "sec", 3) == 0)
return OPT_SEC;
if (strncmp(token, "ip", 2) == 0)
return OPT_IP;
if (strncmp(token, "unc", 3) == 0 ||
strncmp(token, "target", 6) == 0 ||
strncmp(token, "path", 4) == 0)
return OPT_UNC;
if (strncmp(token, "dom", 3) == 0 || strncmp(token, "workg", 5) == 0)
return OPT_DOM;
if (strncmp(token, "cred", 4) == 0)
return OPT_CRED;
if (strncmp(token, "uid", 3) == 0)
return OPT_UID;
if (strncmp(token, "cruid", 5) == 0)
return OPT_CRUID;
if (strncmp(token, "gid", 3) == 0)
return OPT_GID;
if (strncmp(token, "fmask", 5) == 0)
return OPT_FMASK;
if (strncmp(token, "file_mode", 9) == 0)
return OPT_FILE_MODE;
if (strncmp(token, "dmask", 5) == 0)
return OPT_DMASK;
if (strncmp(token, "dir_mode", 4) == 0 || strncmp(token, "dirm", 4) == 0)
return OPT_DIR_MODE;
if (strncmp(token, "nosuid", 6) == 0)
return OPT_NO_SUID;
if (strncmp(token, "suid", 4) == 0)
return OPT_SUID;
if (strncmp(token, "nodev", 5) == 0)
return OPT_NO_DEV;
if (strncmp(token, "nobrl", 5) == 0 || strncmp(token, "nolock", 6) == 0)
return OPT_NO_LOCK;
if (strncmp(token, "mand", 4) == 0)
return OPT_MAND;
if (strncmp(token, "nomand", 6) == 0)
return OPT_NOMAND;
if (strncmp(token, "dev", 3) == 0)
return OPT_DEV;
if (strncmp(token, "noexec", 6) == 0)
return OPT_NO_EXEC;
if (strncmp(token, "exec", 4) == 0)
return OPT_EXEC;
if (strncmp(token, "guest", 5) == 0)
return OPT_GUEST;
if (strncmp(token, "ro", 2) == 0)
return OPT_RO;
if (strncmp(token, "rw", 2) == 0 && strlen(token) == 2)
return OPT_RW;
if (strncmp(token, "remount", 7) == 0)
return OPT_REMOUNT;
if (strncmp(token, "_netdev", 7) == 0)
return OPT_IGNORE;
if (strncmp(token, "backupuid", 9) == 0)
return OPT_BKUPUID;
if (strncmp(token, "backupgid", 9) == 0)
return OPT_BKUPGID;
if (strncmp(token, "nofail", 6) == 0)
return OPT_NOFAIL;
return OPT_ERROR;
}
static int
parse_options(const char *data, struct parsed_mount_info *parsed_info)
{
char *value = NULL;
char *equals = NULL;
char *next_keyword = NULL;
char *out = parsed_info->options;
unsigned long *filesys_flags = &parsed_info->flags;
int out_len = 0;
int word_len;
int rc = 0;
int got_bkupuid = 0;
int got_bkupgid = 0;
int got_uid = 0;
int got_cruid = 0;
int got_gid = 0;
uid_t uid, cruid = 0, bkupuid = 0;
gid_t gid, bkupgid = 0;
char *ep;
struct passwd *pw;
struct group *gr;
/*
* max 32-bit uint in decimal is 4294967295 which is 10 chars wide
* +1 for NULL, and +1 for good measure
*/
char txtbuf[12];
/* make sure we're starting from beginning */
out[0] = '\0';
/* BB fixme check for separator override BB */
uid = getuid();
if (uid != 0)
got_uid = 1;
gid = getgid();
if (gid != 0)
got_gid = 1;
if (!data)
return EX_USAGE;
/*
* format is keyword,keyword2=value2,keyword3=value3...
* data = next keyword
* value = next value ie stuff after equal sign
*/
while (data && *data) {
next_keyword = strchr(data, ','); /* BB handle sep= */
/* temporarily null terminate end of keyword=value pair */
if (next_keyword)
*next_keyword++ = 0;
/* temporarily null terminate keyword if there's a value */
value = NULL;
if ((equals = strchr(data, '=')) != NULL) {
*equals = '\0';
value = equals + 1;
}
switch(parse_opt_token(data)) {
case OPT_USERS:
if (!value || !*value) {
*filesys_flags |= MS_USERS;
goto nocopy;
}
break;
case OPT_USER:
if (!value || !*value) {
if (data[4] == '\0') {
*filesys_flags |= MS_USER;
goto nocopy;
} else {
fprintf(stderr,
"username specified with no parameter\n");
return EX_USAGE;
}
} else {
strlcpy(parsed_info->username, value,
sizeof(parsed_info->username));
parsed_info->got_user = 1;
goto nocopy;
}
case OPT_PASS:
if (parsed_info->got_password) {
fprintf(stderr,
"password specified twice, ignoring second\n");
goto nocopy;
}
if (!value || !*value) {
parsed_info->got_password = 1;
goto nocopy;
}
rc = set_password(parsed_info, value);
if (rc)
return rc;
goto nocopy;
case OPT_SEC:
if (value) {
if (!strncmp(value, "none", 4) ||
!strncmp(value, "krb5", 4))
parsed_info->got_password = 1;
}
break;
case OPT_IP:
if (!value || !*value) {
fprintf(stderr,
"target ip address argument missing\n");
} else if (strnlen(value, MAX_ADDRESS_LEN) <=
MAX_ADDRESS_LEN) {
strcpy(parsed_info->addrlist, value);
if (parsed_info->verboseflag)
fprintf(stderr,
"ip address %s override specified\n",
value);
goto nocopy;
} else {
fprintf(stderr, "ip address too long\n");
return EX_USAGE;
}
break;
/* unc || target || path */
case OPT_UNC:
if (!value || !*value) {
fprintf(stderr,
"invalid path to network resource\n");
return EX_USAGE;
}
rc = parse_unc(value, parsed_info);
if (rc)
return rc;
break;
/* dom || workgroup */
case OPT_DOM:
if (!value || !*value) {
fprintf(stderr, "CIFS: invalid domain name\n");
return EX_USAGE;
}
if (strnlen(value, sizeof(parsed_info->domain)) >=
sizeof(parsed_info->domain)) {
fprintf(stderr, "domain name too long\n");
return EX_USAGE;
}
strlcpy(parsed_info->domain, value,
sizeof(parsed_info->domain));
goto nocopy;
case OPT_CRED:
if (!value || !*value) {
fprintf(stderr,
"invalid credential file name specified\n");
return EX_USAGE;
}
rc = open_cred_file(value, parsed_info);
if (rc) {
fprintf(stderr,
"error %d (%s) opening credential file %s\n",
rc, strerror(rc), value);
return rc;
}
goto nocopy;
case OPT_UID:
if (!value || !*value)
goto nocopy;
got_uid = 1;
pw = getpwnam(value);
if (pw) {
uid = pw->pw_uid;
goto nocopy;
}
errno = 0;
uid = strtoul(value, &ep, 10);
if (errno == 0 && *ep == '\0')
goto nocopy;
fprintf(stderr, "bad option uid=\"%s\"\n", value);
return EX_USAGE;
case OPT_CRUID:
if (!value || !*value)
goto nocopy;
got_cruid = 1;
pw = getpwnam(value);
if (pw) {
cruid = pw->pw_uid;
goto nocopy;
}
errno = 0;
cruid = strtoul(value, &ep, 10);
if (errno == 0 && *ep == '\0')
goto nocopy;
fprintf(stderr, "bad option: cruid=\"%s\"\n", value);
return EX_USAGE;
case OPT_GID:
if (!value || !*value)
goto nocopy;
got_gid = 1;
gr = getgrnam(value);
if (gr) {
gid = gr->gr_gid;
goto nocopy;
}
errno = 0;
gid = strtoul(value, &ep, 10);
if (errno == 0 && *ep == '\0')
goto nocopy;
fprintf(stderr, "bad option: gid=\"%s\"\n", value);
return EX_USAGE;
/* fmask fall through to file_mode */
case OPT_FMASK:
fprintf(stderr,
"WARNING: CIFS mount option 'fmask' is\
deprecated. Use 'file_mode' instead.\n");
data = "file_mode"; /* BB fix this */
case OPT_FILE_MODE:
if (!value || !*value) {
fprintf(stderr,
"Option '%s' requires a numerical argument\n",
data);
return EX_USAGE;
}
if (value[0] != '0')
fprintf(stderr,
"WARNING: '%s' not expressed in octal.\n",
data);
break;
/* dmask falls through to dir_mode */
case OPT_DMASK:
fprintf(stderr,
"WARNING: CIFS mount option 'dmask' is\
deprecated. Use 'dir_mode' instead.\n");
data = "dir_mode";
case OPT_DIR_MODE:
if (!value || !*value) {
fprintf(stderr,
"Option '%s' requires a numerical argument\n",
data);
return EX_USAGE;
}
if (value[0] != '0')
fprintf(stderr,
"WARNING: '%s' not expressed in octal.\n",
data);
break;
case OPT_NO_SUID:
*filesys_flags |= MS_NOSUID;
goto nocopy;
case OPT_SUID:
*filesys_flags &= ~MS_NOSUID;
goto nocopy;
case OPT_NO_DEV:
*filesys_flags |= MS_NODEV;
goto nocopy;
case OPT_NO_LOCK:
*filesys_flags &= ~MS_MANDLOCK;
break;
case OPT_MAND:
*filesys_flags |= MS_MANDLOCK;
goto nocopy;
case OPT_NOMAND:
*filesys_flags &= ~MS_MANDLOCK;
goto nocopy;
case OPT_DEV:
*filesys_flags &= ~MS_NODEV;
goto nocopy;
case OPT_NO_EXEC:
*filesys_flags |= MS_NOEXEC;
goto nocopy;
case OPT_EXEC:
*filesys_flags &= ~MS_NOEXEC;
goto nocopy;
case OPT_GUEST:
parsed_info->got_user = 1;
parsed_info->got_password = 1;
goto nocopy;
case OPT_RO:
*filesys_flags |= MS_RDONLY;
goto nocopy;
case OPT_RW:
*filesys_flags &= ~MS_RDONLY;
goto nocopy;
case OPT_REMOUNT:
*filesys_flags |= MS_REMOUNT;
goto nocopy;
case OPT_IGNORE:
goto nocopy;
case OPT_BKUPUID:
if (!value || !*value)
goto nocopy;
got_bkupuid = 1;
errno = 0;
bkupuid = strtoul(value, &ep, 10);
if (errno == 0 && *ep == '\0')
goto nocopy;
pw = getpwnam(value);
if (pw == NULL) {
fprintf(stderr,
"bad user name \"%s\"\n", value);
return EX_USAGE;
}
bkupuid = pw->pw_uid;
goto nocopy;
case OPT_BKUPGID:
if (!value || !*value)
goto nocopy;
got_bkupgid = 1;
errno = 0;
bkupgid = strtoul(value, &ep, 10);
if (errno == 0 && *ep == '\0')
goto nocopy;
gr = getgrnam(value);
if (gr == NULL) {
fprintf(stderr,
"bad group name \"%s\"\n", value);
return EX_USAGE;
}
bkupgid = gr->gr_gid;
goto nocopy;
case OPT_NOFAIL:
parsed_info->nofail = 1;
goto nocopy;
}
/* check size before copying option to buffer */
word_len = strlen(data);
if (value)
word_len += 1 + strlen(value);
/* need 2 extra bytes for comma and null byte */
if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
/* put back equals sign, if any */
if (equals)
*equals = '=';
/* go ahead and copy */
if (out_len)
strlcat(out, ",", MAX_OPTIONS_LEN);
strlcat(out, data, MAX_OPTIONS_LEN);
out_len = strlen(out);
nocopy:
data = next_keyword;
}
/* special-case the uid and gid */
if (got_uid) {
word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", uid);
/* comma + "uid=" + terminating NULL == 6 */
if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
if (out_len) {
strlcat(out, ",", MAX_OPTIONS_LEN);
out_len++;
}
snprintf(out + out_len, word_len + 5, "uid=%s", txtbuf);
out_len = strlen(out);
}
if (got_cruid) {
word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", cruid);
/* comma + "cruid=" + terminating NULL == 6 */
if (out_len + word_len + 8 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
if (out_len) {
strlcat(out, ",", MAX_OPTIONS_LEN);
out_len++;
}
snprintf(out + out_len, word_len + 7, "cruid=%s", txtbuf);
out_len = strlen(out);
}
if (got_gid) {
word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", gid);
/* comma + "gid=" + terminating NULL == 6 */
if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
if (out_len) {
strlcat(out, ",", MAX_OPTIONS_LEN);
out_len++;
}
snprintf(out + out_len, word_len + 5, "gid=%s", txtbuf);
}
if (got_bkupuid) {
word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupuid);
/* comma + "backupuid=" + terminating NULL == 12 */
if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
if (out_len) {
strlcat(out, ",", MAX_OPTIONS_LEN);
out_len++;
}
snprintf(out + out_len, word_len + 11, "backupuid=%s", txtbuf);
out_len = strlen(out);
}
if (got_bkupgid) {
word_len = snprintf(txtbuf, sizeof(txtbuf), "%u", bkupgid);
/* comma + "backkupgid=" + terminating NULL == 12 */
if (out_len + word_len + 12 > MAX_OPTIONS_LEN) {
fprintf(stderr, "Options string too long\n");
return EX_USAGE;
}
if (out_len) {
strlcat(out, ",", MAX_OPTIONS_LEN);
out_len++;
}
snprintf(out + out_len, word_len + 11, "backupgid=%s", txtbuf);
}
return 0;
}
static int parse_unc(const char *unc_name, struct parsed_mount_info *parsed_info)
{
int length = strnlen(unc_name, MAX_UNC_LEN);
const char *host, *share, *prepath;
size_t hostlen, sharelen, prepathlen;
if (length > (MAX_UNC_LEN - 1)) {
fprintf(stderr, "mount error: UNC name too long\n");
return EX_USAGE;
}
if (length < 3) {
fprintf(stderr, "mount error: UNC name too short\n");
return EX_USAGE;
}
if ((strncasecmp("cifs://", unc_name, 7) == 0) ||
(strncasecmp("smb://", unc_name, 6) == 0)) {
fprintf(stderr,
"Mounting cifs URL not implemented yet. Attempt to mount %s\n",
unc_name);
return EX_USAGE;
}
if (strncmp(unc_name, "//", 2) && strncmp(unc_name, "\\\\", 2)) {
fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
return EX_USAGE;
}
host = unc_name + 2;
hostlen = strcspn(host, "/\\");
if (!hostlen) {
fprintf(stderr, "mount.cifs: bad UNC (%s)\n", unc_name);
return EX_USAGE;
}
share = host + hostlen + 1;
if (hostlen + 1 > sizeof(parsed_info->host)) {
fprintf(stderr, "mount.cifs: host portion of UNC too long\n");
return EX_USAGE;
}
sharelen = strcspn(share, "/\\");
if (sharelen + 1 > sizeof(parsed_info->share)) {
fprintf(stderr, "mount.cifs: share portion of UNC too long\n");
return EX_USAGE;
}
prepath = share + sharelen;
if (*prepath != '\0')
prepath++;
prepathlen = strlen(prepath);
if (prepathlen + 1 > sizeof(parsed_info->prefix)) {
fprintf(stderr, "mount.cifs: UNC prefixpath too long\n");
return EX_USAGE;
}
/* copy pieces into their resepective buffers */
memcpy(parsed_info->host, host, hostlen);
memcpy(parsed_info->share, share, sharelen);
memcpy(parsed_info->prefix, prepath, prepathlen);
return 0;
}
static int get_pw_from_env(struct parsed_mount_info *parsed_info)
{
int rc = 0;
if (getenv("PASSWD"))
rc = set_password(parsed_info, getenv("PASSWD"));
else if (getenv("PASSWD_FD"))
rc = get_password_from_file(atoi(getenv("PASSWD_FD")), NULL,
parsed_info);
else if (getenv("PASSWD_FILE"))
rc = get_password_from_file(0, getenv("PASSWD_FILE"),
parsed_info);
return rc;
}
static struct option longopts[] = {
{"all", 0, NULL, 'a'},
{"help", 0, NULL, 'h'},
{"move", 0, NULL, 'm'},
{"bind", 0, NULL, 'b'},
{"read-only", 0, NULL, 'r'},
{"ro", 0, NULL, 'r'},
{"verbose", 0, NULL, 'v'},
{"version", 0, NULL, 'V'},
{"read-write", 0, NULL, 'w'},
{"rw", 0, NULL, 'w'},
{"options", 1, NULL, 'o'},
{"type", 1, NULL, 't'},
{"uid", 1, NULL, '1'},
{"gid", 1, NULL, '2'},
{"user", 1, NULL, 'u'},
{"username", 1, NULL, 'u'},
{"dom", 1, NULL, 'd'},
{"domain", 1, NULL, 'd'},
{"password", 1, NULL, 'p'},
{"pass", 1, NULL, 'p'},
{"credentials", 1, NULL, 'c'},
{"port", 1, NULL, 'P'},
{"sloppy", 0, NULL, 's'},
{NULL, 0, NULL, 0}
};
/* convert a string to uppercase. return false if the string
* wasn't ASCII. Return success on a NULL ptr */
static int uppercase_string(char *string)
{
if (!string)
return 1;
while (*string) {
/* check for unicode */
if ((unsigned char)string[0] & 0x80)
return 0;
*string = toupper((unsigned char)*string);
string++;
}
return 1;
}
static void print_cifs_mount_version(void)
{
printf("mount.cifs version: %s\n", VERSION);
}
/*
* This function borrowed from fuse-utils...
*
* glibc's addmntent (at least as of 2.10 or so) doesn't properly encode
* newlines embedded within the text fields. To make sure no one corrupts
* the mtab, fail the mount if there are embedded newlines.
*/
static int check_newline(const char *progname, const char *name)
{
const char *s;
for (s = "\n"; *s; s++) {
if (strchr(name, *s)) {
fprintf(stderr,
"%s: illegal character 0x%02x in mount entry\n",
progname, *s);
return EX_USAGE;
}
}
return 0;
}
static int check_mtab(const char *progname, const char *devname,
const char *dir)
{
if (check_newline(progname, devname) || check_newline(progname, dir))
return EX_USAGE;
return 0;
}
static int
add_mtab(char *devname, char *mountpoint, unsigned long flags, const char *fstype)
{
int rc = 0, tmprc, fd;
uid_t uid;
char *mount_user = NULL;
struct mntent mountent;
struct stat statbuf;
FILE *pmntfile;
sigset_t mask, oldmask;
uid = getuid();
if (uid != 0)
mount_user = getusername(uid);
/*
* Set the real uid to the effective uid. This prevents unprivileged
* users from sending signals to this process, though ^c on controlling
* terminal should still work.
*/
rc = setreuid(geteuid(), -1);
if (rc != 0) {
fprintf(stderr, "Unable to set real uid to effective uid: %s\n",
strerror(errno));
return EX_FILEIO;
}
rc = sigfillset(&mask);
if (rc) {
fprintf(stderr, "Unable to set filled signal mask\n");
return EX_FILEIO;
}
rc = sigprocmask(SIG_SETMASK, &mask, &oldmask);
if (rc) {
fprintf(stderr, "Unable to make process ignore signals\n");
return EX_FILEIO;
}
rc = toggle_dac_capability(1, 1);
if (rc)
return EX_FILEIO;
atexit(unlock_mtab);
rc = lock_mtab();
if (rc) {
fprintf(stderr, "cannot lock mtab");
rc = EX_FILEIO;
goto add_mtab_exit;
}
pmntfile = setmntent(MOUNTED, "a+");
if (!pmntfile) {
fprintf(stderr, "could not update mount table\n");
unlock_mtab();
rc = EX_FILEIO;
goto add_mtab_exit;
}
fd = fileno(pmntfile);
if (fd < 0) {
fprintf(stderr, "mntent does not appear to be valid\n");
unlock_mtab();
rc = EX_FILEIO;
goto add_mtab_exit;
}
rc = fstat(fd, &statbuf);
if (rc != 0) {
fprintf(stderr, "unable to fstat open mtab\n");
endmntent(pmntfile);
unlock_mtab();
rc = EX_FILEIO;
goto add_mtab_exit;
}
mountent.mnt_fsname = devname;
mountent.mnt_dir = mountpoint;
mountent.mnt_type = (char *)(void *)fstype;
mountent.mnt_opts = (char *)calloc(MTAB_OPTIONS_LEN, 1);
if (mountent.mnt_opts) {
if (flags & MS_RDONLY)
strlcat(mountent.mnt_opts, "ro", MTAB_OPTIONS_LEN);
else
strlcat(mountent.mnt_opts, "rw", MTAB_OPTIONS_LEN);
if (flags & MS_MANDLOCK)
strlcat(mountent.mnt_opts, ",mand", MTAB_OPTIONS_LEN);
if (flags & MS_NOEXEC)
strlcat(mountent.mnt_opts, ",noexec", MTAB_OPTIONS_LEN);
if (flags & MS_NOSUID)
strlcat(mountent.mnt_opts, ",nosuid", MTAB_OPTIONS_LEN);
if (flags & MS_NODEV)
strlcat(mountent.mnt_opts, ",nodev", MTAB_OPTIONS_LEN);
if (flags & MS_SYNCHRONOUS)
strlcat(mountent.mnt_opts, ",sync", MTAB_OPTIONS_LEN);
if (mount_user) {
strlcat(mountent.mnt_opts, ",user=", MTAB_OPTIONS_LEN);
strlcat(mountent.mnt_opts, mount_user,
MTAB_OPTIONS_LEN);
}
}
mountent.mnt_freq = 0;
mountent.mnt_passno = 0;
rc = addmntent(pmntfile, &mountent);
if (rc) {
int ignore __attribute__((unused));
fprintf(stderr, "unable to add mount entry to mtab\n");
ignore = ftruncate(fd, statbuf.st_size);
rc = EX_FILEIO;
}
tmprc = my_endmntent(pmntfile, statbuf.st_size);
if (tmprc) {
fprintf(stderr, "error %d detected on close of mtab\n", tmprc);
rc = EX_FILEIO;
}
unlock_mtab();
SAFE_FREE(mountent.mnt_opts);
add_mtab_exit:
toggle_dac_capability(1, 0);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return rc;
}
static int
del_mtab(char *mountpoint)
{
int tmprc, rc = 0;
FILE *mnttmp, *mntmtab;
struct mntent *mountent;
char *mtabfile, *mtabdir, *mtabtmpfile;
mtabfile = strdup(MOUNTED);
mtabdir = dirname(mtabfile);
mtabdir = realloc(mtabdir, strlen(mtabdir) + strlen(MNT_TMP_FILE) + 2);
if (!mtabdir) {
fprintf(stderr, "del_mtab: cannot determine current mtab path");
rc = EX_FILEIO;
goto del_mtab_exit;
}
mtabtmpfile = strcat(mtabdir, MNT_TMP_FILE);
if (!mtabtmpfile) {
fprintf(stderr, "del_mtab: cannot allocate memory to tmp file");
rc = EX_FILEIO;
goto del_mtab_exit;
}
atexit(unlock_mtab);
rc = lock_mtab();
if (rc) {
fprintf(stderr, "del_mtab: cannot lock mtab");
rc = EX_FILEIO;
goto del_mtab_exit;
}
mtabtmpfile = mktemp(mtabtmpfile);
if (!mtabtmpfile) {
fprintf(stderr, "del_mtab: cannot setup tmp file destination");
rc = EX_FILEIO;
goto del_mtab_exit;
}
mntmtab = setmntent(MOUNTED, "r");
if (!mntmtab) {
fprintf(stderr, "del_mtab: could not update mount table\n");
rc = EX_FILEIO;
goto del_mtab_exit;
}
mnttmp = setmntent(mtabtmpfile, "w");
if (!mnttmp) {
fprintf(stderr, "del_mtab: could not update mount table\n");
endmntent(mntmtab);
rc = EX_FILEIO;
goto del_mtab_exit;
}
while ((mountent = getmntent(mntmtab)) != NULL) {
if (!strcmp(mountent->mnt_dir, mountpoint))
continue;
rc = addmntent(mnttmp, mountent);
if (rc) {
fprintf(stderr, "del_mtab: unable to add mount entry to mtab\n");
rc = EX_FILEIO;
goto del_mtab_error;
}
}
endmntent(mntmtab);
tmprc = my_endmntent(mnttmp, 0);
if (tmprc) {
fprintf(stderr, "del_mtab: error %d detected on close of tmp file\n", tmprc);
rc = EX_FILEIO;
goto del_mtab_error;
}
if (rename(mtabtmpfile, MOUNTED)) {
fprintf(stderr, "del_mtab: error %d when renaming mtab in place\n", errno);
rc = EX_FILEIO;
goto del_mtab_error;
}
del_mtab_exit:
unlock_mtab();
free(mtabdir);
return rc;
del_mtab_error:
if (unlink(mtabtmpfile))
fprintf(stderr, "del_mtab: failed to delete tmp file - %s\n",
strerror(errno));
goto del_mtab_exit;
}
/* have the child drop root privileges */
static int
drop_child_privs(void)
{
int rc;
uid_t uid = getuid();
gid_t gid = getgid();
if (gid) {
rc = setgid(gid);
if (rc) {
fprintf(stderr, "Unable set group identity: %s\n",
strerror(errno));
return EX_SYSERR;
}
}
if (uid) {
rc = setuid(uid);
if (rc) {
fprintf(stderr, "Unable set user identity: %s\n",
strerror(errno));
return EX_SYSERR;
}
}
return 0;
}
/*
* If systemd is running and /bin/systemd-ask-password --
* is available, then use that else fallback on getpass(..)
*
* Returns: @input or NULL on error
*/
static char*
get_password(const char *prompt, char *input, int capacity)
{
#ifdef ENABLE_SYSTEMD
int is_systemd_running;
struct stat a, b;
/* We simply test whether the systemd cgroup hierarchy is
* mounted */
is_systemd_running = (lstat("/sys/fs/cgroup", &a) == 0)
&& (lstat("/sys/fs/cgroup/systemd", &b) == 0)
&& (a.st_dev != b.st_dev);
if (is_systemd_running) {
char *cmd, *ret;
FILE *ask_pass_fp = NULL;
cmd = ret = NULL;
if (asprintf(&cmd, "/bin/systemd-ask-password \"%s\"", prompt) >= 0) {
ask_pass_fp = popen (cmd, "re");
free (cmd);
}
if (ask_pass_fp) {
ret = fgets(input, capacity, ask_pass_fp);
pclose(ask_pass_fp);
}
if (ret) {
int len = strlen(input);
if (input[len - 1] == '\n')
input[len - 1] = '\0';
return input;
}
}
#endif
/*
* Falling back to getpass(..)
* getpass is obsolete, but there's apparently nothing that replaces it
*/
char *tmp_pass = getpass(prompt);
if (!tmp_pass)
return NULL;
strncpy(input, tmp_pass, capacity - 1);
input[capacity - 1] = '\0';
/* zero-out the static buffer */
memset(tmp_pass, 0, strlen(tmp_pass));
return input;
}
static int
assemble_mountinfo(struct parsed_mount_info *parsed_info,
const char *thisprogram, const char *mountpoint,
const char *orig_dev, char *orgoptions)
{
int rc;
rc = drop_capabilities(0);
if (rc)
goto assemble_exit;
rc = drop_child_privs();
if (rc)
goto assemble_exit;
if (getuid()) {
rc = check_fstab(thisprogram, mountpoint, orig_dev,
&orgoptions);
if (rc)
goto assemble_exit;
/* enable any default user mount flags */
parsed_info->flags |= CIFS_SETUID_FLAGS;
}
rc = get_pw_from_env(parsed_info);
if (rc)
goto assemble_exit;
if (orgoptions) {
rc = parse_options(orgoptions, parsed_info);
if (rc)
goto assemble_exit;
}
if (getuid()) {
if (!(parsed_info->flags & (MS_USERS | MS_USER))) {
fprintf(stderr, "%s: permission denied\n", thisprogram);
rc = EX_USAGE;
goto assemble_exit;
}
}
parsed_info->flags &= ~(MS_USERS | MS_USER);
rc = parse_unc(orig_dev, parsed_info);
if (rc)
goto assemble_exit;
if (parsed_info->addrlist[0] == '\0')
rc = resolve_host(parsed_info->host, parsed_info->addrlist);
switch (rc) {
case EX_USAGE:
fprintf(stderr, "mount error: could not resolve address for "
"%s: %s\n", parsed_info->host,
rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
goto assemble_exit;
case EX_SYSERR:
fprintf(stderr, "mount error: problem parsing address "
"list: %s\n", strerror(errno));
goto assemble_exit;
}
if (!parsed_info->got_user) {
/*
* Note that the password will not be retrieved from the
* USER env variable (ie user%password form) as there is
* already a PASSWD environment varaible
*/
if (getenv("USER"))
strlcpy(parsed_info->username, getenv("USER"),
sizeof(parsed_info->username));
else
strlcpy(parsed_info->username, getusername(getuid()),
sizeof(parsed_info->username));
parsed_info->got_user = 1;
}
if (!parsed_info->got_password) {
char tmp_pass[MOUNT_PASSWD_SIZE + 1];
char *prompt = NULL;
if(asprintf(&prompt, "Password for %s@%s: ", parsed_info->username, orig_dev) < 0)
prompt = NULL;
if (get_password(prompt ? prompt : "Password: ", tmp_pass, MOUNT_PASSWD_SIZE + 1)) {
rc = set_password(parsed_info, tmp_pass);
} else {
fprintf(stderr, "Error reading password, exiting\n");
rc = EX_SYSERR;
}
free(prompt);
if (rc)
goto assemble_exit;
}
/* copy in user= string */
if (parsed_info->got_user) {
if (*parsed_info->options)
strlcat(parsed_info->options, ",",
sizeof(parsed_info->options));
strlcat(parsed_info->options, "user=",
sizeof(parsed_info->options));
strlcat(parsed_info->options, parsed_info->username,
sizeof(parsed_info->options));
}
if (*parsed_info->domain) {
if (*parsed_info->options)
strlcat(parsed_info->options, ",",
sizeof(parsed_info->options));
strlcat(parsed_info->options, ",domain=",
sizeof(parsed_info->options));
strlcat(parsed_info->options, parsed_info->domain,
sizeof(parsed_info->options));
}
assemble_exit:
return rc;
}
/*
* chdir() into the mountpoint and determine "realpath". We assume here that
* "mountpoint" is a statically allocated string and does not need to be freed.
*/
static int
acquire_mountpoint(char **mountpointp)
{
int rc, dacrc;
uid_t realuid, oldfsuid;
gid_t oldfsgid;
char *mountpoint;
/*
* Acquire the necessary privileges to chdir to the mountpoint. If
* the real uid is root, then we reacquire CAP_DAC_READ_SEARCH. If
* it's not, then we change the fsuid to the real uid to ensure that
* the mounting user actually has access to the mountpoint.
*
* The mount(8) manpage does not state that users must be able to
* chdir into the mountpoint in order to mount onto it, but if we
* allow that, then an unprivileged user could use this program to
* "probe" into directories to which he does not have access.
*/
realuid = getuid();
if (realuid == 0) {
dacrc = toggle_dac_capability(0, 1);
if (dacrc)
return dacrc;
} else {
oldfsuid = setfsuid(realuid);
oldfsgid = setfsgid(getgid());
}
rc = chdir(*mountpointp);
if (rc) {
fprintf(stderr, "Couldn't chdir to %s: %s\n", *mountpointp,
strerror(errno));
rc = EX_USAGE;
goto restore_privs;
}
mountpoint = realpath(".", NULL);
if (!mountpoint) {
fprintf(stderr, "Unable to resolve %s to canonical path: %s\n",
*mountpointp, strerror(errno));
rc = EX_SYSERR;
}
*mountpointp = mountpoint;
restore_privs:
if (realuid == 0) {
dacrc = toggle_dac_capability(0, 0);
if (dacrc)
rc = rc ? rc : dacrc;
} else {
uid_t __attribute__((unused)) uignore = setfsuid(oldfsuid);
gid_t __attribute__((unused)) gignore = setfsgid(oldfsgid);
}
return rc;
}
int main(int argc, char **argv)
{
int c;
char *orgoptions = NULL;
char *mountpoint = NULL;
char *options = NULL;
char *orig_dev = NULL;
char *currentaddress, *nextaddress;
int rc = 0;
int already_uppercased = 0;
int sloppy = 0;
size_t options_size = MAX_OPTIONS_LEN;
struct parsed_mount_info *parsed_info = NULL;
pid_t pid;
rc = check_setuid();
if (rc)
return rc;
rc = drop_capabilities(1);
if (rc)
return EX_SYSERR;
/* setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE); */
if (!argc || !argv) {
rc = mount_usage(stderr);
goto mount_exit;
}
thisprogram = basename(argv[0]);
if (thisprogram == NULL)
thisprogram = "mount.cifs";
/* allocate parsed_info as shared anonymous memory range */
parsed_info = mmap(0, sizeof(*parsed_info), PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (parsed_info == (struct parsed_mount_info *) -1) {
parsed_info = NULL;
fprintf(stderr, "Unable to allocate memory: %s\n",
strerror(errno));
return EX_SYSERR;
}
/* add sharename in opts string as unc= parm */
while ((c = getopt_long(argc, argv, "?fhno:rsvVw",
longopts, NULL)) != -1) {
switch (c) {
case '?':
case 'h': /* help */
rc = mount_usage(stdout);
goto mount_exit;
case 'n':
++parsed_info->nomtab;
break;
case 'o':
orgoptions = strndup(optarg, MAX_OPTIONS_LEN);
if (!orgoptions) {
rc = EX_SYSERR;
goto mount_exit;
}
break;
case 'r': /* mount readonly */
parsed_info->flags |= MS_RDONLY;
break;
case 'v':
++parsed_info->verboseflag;
break;
case 'V':
print_cifs_mount_version();
exit(0);
case 'w':
parsed_info->flags &= ~MS_RDONLY;
break;
case 'f':
++parsed_info->fakemnt;
break;
case 's':
++sloppy;
break;
default:
fprintf(stderr, "unknown command-line option: %c\n", c);
rc = mount_usage(stderr);
goto mount_exit;
}
}
if (argc < optind + 2) {
rc = mount_usage(stderr);
goto mount_exit;
}
orig_dev = argv[optind];
mountpoint = argv[optind + 1];
/* chdir into mountpoint as soon as possible */
rc = acquire_mountpoint(&mountpoint);
if (rc)
return rc;
/*
* mount.cifs does privilege separation. Most of the code to handle
* assembling the mount info is done in a child process that drops
* privileges. The info is assembled in parsed_info which is a
* shared, mmaped memory segment. The parent waits for the child to
* exit and checks the return code. If it's anything but "0", then
* the process exits without attempting anything further.
*/
pid = fork();
if (pid == -1) {
fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
rc = EX_SYSERR;
goto mount_exit;
} else if (!pid) {
/* child */
rc = assemble_mountinfo(parsed_info, thisprogram, mountpoint,
orig_dev, orgoptions);
return rc;
} else {
/* parent */
pid = wait(&rc);
if (!WIFEXITED(rc)) {
fprintf(stderr, "Child process terminated abnormally.\n");
rc = EX_SYSERR;
goto mount_exit;
}
rc = WEXITSTATUS(rc);
if (rc)
goto mount_exit;
}
options = calloc(options_size, 1);
if (!options) {
fprintf(stderr, "Unable to allocate memory.\n");
rc = EX_SYSERR;
goto mount_exit;
}
currentaddress = parsed_info->addrlist;
nextaddress = strchr(currentaddress, ',');
if (nextaddress)
*nextaddress++ = '\0';
mount_retry:
if (!currentaddress) {
fprintf(stderr, "Unable to find suitable address.\n");
rc = parsed_info->nofail ? 0 : EX_FAIL;
goto mount_exit;
}
strlcpy(options, "ip=", options_size);
strlcat(options, currentaddress, options_size);
strlcat(options, ",unc=\\\\", options_size);
strlcat(options, parsed_info->host, options_size);
strlcat(options, "\\", options_size);
strlcat(options, parsed_info->share, options_size);
if (*parsed_info->options) {
strlcat(options, ",", options_size);
strlcat(options, parsed_info->options, options_size);
}
if (*parsed_info->prefix) {
strlcat(options, ",prefixpath=", options_size);
strlcat(options, parsed_info->prefix, options_size);
}
if (sloppy)
strlcat(options, ",sloppy", options_size);
if (parsed_info->verboseflag)
fprintf(stderr, "%s kernel mount options: %s",
thisprogram, options);
if (parsed_info->got_password) {
/*
* Commas have to be doubled, or else they will
* look like the parameter separator
*/
strlcat(options, ",pass=", options_size);
strlcat(options, parsed_info->password, options_size);
if (parsed_info->verboseflag)
fprintf(stderr, ",pass=********");
}
if (parsed_info->verboseflag)
fprintf(stderr, "\n");
rc = check_mtab(thisprogram, orig_dev, mountpoint);
if (rc)
goto mount_exit;
if (!parsed_info->fakemnt) {
toggle_dac_capability(0, 1);
rc = mount(orig_dev, ".", cifs_fstype, parsed_info->flags, options);
toggle_dac_capability(0, 0);
if (rc == 0)
goto do_mtab;
switch (errno) {
case ECONNREFUSED:
case EHOSTUNREACH:
currentaddress = nextaddress;
if (currentaddress) {
nextaddress = strchr(currentaddress, ',');
if (nextaddress)
*nextaddress++ = '\0';
}
goto mount_retry;
case ENODEV:
fprintf(stderr,
"mount error: %s filesystem not supported by the system\n", cifs_fstype);
break;
case ENXIO:
if (!already_uppercased &&
uppercase_string(parsed_info->host) &&
uppercase_string(parsed_info->share) &&
uppercase_string(parsed_info->prefix)) {
fprintf(stderr,
"Retrying with upper case share name\n");
already_uppercased = 1;
goto mount_retry;
}
}
fprintf(stderr, "mount error(%d): %s\n", errno,
strerror(errno));
fprintf(stderr,
"Refer to the %s(8) manual page (e.g. man "
"%s)\n", thisprogram, thisprogram);
rc = EX_FAIL;
goto mount_exit;
}
do_mtab:
if (!parsed_info->nomtab && !mtab_unusable()) {
if (parsed_info->flags & MS_REMOUNT) {
rc = del_mtab(mountpoint);
if (rc)
goto mount_exit;
}
rc = add_mtab(orig_dev, mountpoint, parsed_info->flags, cifs_fstype);
}
mount_exit:
if (parsed_info) {
memset(parsed_info->password, 0, sizeof(parsed_info->password));
munmap(parsed_info, sizeof(*parsed_info));
}
SAFE_FREE(options);
SAFE_FREE(orgoptions);
return rc;
}