blob: f4ab29a0465a524f89c651f5cbe6584b1484388f [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "smbfs/samba_interface_impl.h"
#include <sys/stat.h>
#include <memory>
#include <utility>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/strings/string_util.h>
namespace smbfs {
namespace {
// Default that is consistent with common filesystems (ie. ext3, ext4, NFTS).
constexpr int kMaxShareFilenameLength = 255;
void SambaLog(void* private_ptr, int level, const char* msg) {
VLOG(level) << "libsmbclient: " << msg;
}
void CopyCredential(const std::string& cred, char* out, int out_len) {
DCHECK_GT(out_len, 0);
if (cred.size() > out_len - 1) {
LOG(ERROR) << "Credential string longer than buffer provided";
}
base::strlcpy(out, cred.c_str(), out_len);
}
void CopyPassword(const password_provider::Password& password,
char* out,
int out_len) {
DCHECK_GT(out_len, 0);
if (password.size() > out_len - 1) {
LOG(ERROR) << "Password string longer than buffer provided";
}
base::strlcpy(out, password.GetRaw(), out_len);
}
} // namespace
SambaInterfaceImpl::SambaInterfaceImpl(
std::unique_ptr<SmbCredential> credentials, bool allow_ntlm)
: credentials_(std::move(credentials)) {
context_ = smbc_new_context();
CHECK(context_);
CHECK(smbc_init_context(context_));
smbc_setLogCallback(context_, nullptr, &SambaLog);
int vlog_level = logging::GetVlogVerbosity();
if (vlog_level > 0) {
smbc_setDebug(context_, vlog_level);
}
smbc_setOptionUserData(context_, this);
smbc_setOptionUseKerberos(context_, 1);
// Allow fallback to NTLMv2 authentication if Kerberos fails. This does not
// prevent fallback to anonymous auth if authentication fails.
smbc_setOptionFallbackAfterKerberos(context_, allow_ntlm);
LOG_IF(WARNING, !allow_ntlm) << "NTLM protocol is disabled";
smbc_setFunctionAuthDataWithContext(context_,
&SambaInterfaceImpl::GetUserAuth);
smbc_close_ctx_ = smbc_getFunctionClose(context_);
smbc_closedir_ctx_ = smbc_getFunctionClosedir(context_);
smbc_ftruncate_ctx_ = smbc_getFunctionFtruncate(context_);
smbc_lseek_ctx_ = smbc_getFunctionLseek(context_);
smbc_lseekdir_ctx_ = smbc_getFunctionLseekdir(context_);
smbc_mkdir_ctx_ = smbc_getFunctionMkdir(context_);
smbc_open_ctx_ = smbc_getFunctionOpen(context_);
smbc_opendir_ctx_ = smbc_getFunctionOpendir(context_);
smbc_read_ctx_ = smbc_getFunctionRead(context_);
smbc_readdirplus_ctx_ = smbc_getFunctionReaddirPlus(context_);
smbc_rename_ctx_ = smbc_getFunctionRename(context_);
smbc_rmdir_ctx_ = smbc_getFunctionRmdir(context_);
smbc_stat_ctx_ = smbc_getFunctionStat(context_);
smbc_statvfs_ctx_ = smbc_getFunctionStatVFS(context_);
smbc_telldir_ctx_ = smbc_getFunctionTelldir(context_);
smbc_unlink_ctx_ = smbc_getFunctionUnlink(context_);
smbc_utimes_ctx_ = smbc_getFunctionUtimes(context_);
smbc_write_ctx_ = smbc_getFunctionWrite(context_);
}
SambaInterfaceImpl::SambaInterfaceImpl() = default;
SambaInterfaceImpl::~SambaInterfaceImpl() {
if (context_) {
smbc_free_context(context_, 1 /* shutdown_ctx */);
}
}
void SambaInterfaceImpl::UpdateCredentials(
std::unique_ptr<SmbCredential> credentials) {
base::AutoLock l(lock_);
credentials_ = std::move(credentials);
}
SambaInterface::WeakPtr SambaInterfaceImpl::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// static
void SambaInterfaceImpl::GetUserAuth(SMBCCTX* context,
const char* server,
const char* share,
char* workgroup,
int workgroup_len,
char* username,
int username_len,
char* password,
int password_len) {
SambaInterfaceImpl* samba_impl =
static_cast<SambaInterfaceImpl*>(smbc_getOptionUserData(context));
DCHECK(samba_impl);
base::AutoLock l(samba_impl->lock_);
// Credentials can be omitted during mounts manually initiated from the
// command line.
if (!samba_impl->credentials_) {
return;
}
CopyCredential(samba_impl->credentials_->workgroup, workgroup, workgroup_len);
CopyCredential(samba_impl->credentials_->username, username, username_len);
password[0] = 0;
if (samba_impl->credentials_->password) {
CopyPassword(*samba_impl->credentials_->password, password, password_len);
}
}
int SambaInterfaceImpl::StatVfs(const std::string& path,
struct statvfs* out_statvfs) {
DCHECK(out_statvfs);
// libsmbclient's statvfs() takes a non-const char* as path, hence the
// address-of-first-element pattern/hack.
std::string stat_path = path;
if (smbc_statvfs_ctx_(context_, &stat_path[0], out_statvfs) < 0) {
return errno;
}
if ((out_statvfs->f_flag & SMBC_VFS_FEATURE_NO_UNIXCIFS) &&
out_statvfs->f_frsize) {
// If the server does not support the UNIX CIFS extensions, libsmbclient
// incorrectly fills out the value of f_frsize. Instead of providing the
// size in bytes, it provides it as a multiple of f_bsize. See the
// implementation of SMBC_fstatvfs_ctx() in the Samba source tree for
// details.
out_statvfs->f_frsize *= out_statvfs->f_bsize;
}
// libsmbclient can return 0 for this but some clients require it to be set.
if (!out_statvfs->f_namemax) {
out_statvfs->f_namemax = kMaxShareFilenameLength;
}
return 0;
}
int SambaInterfaceImpl::OpenFile(const std::string& file_path,
int flags,
mode_t mode,
SMBCFILE** out_file) {
DCHECK(out_file);
*out_file = smbc_open_ctx_(context_, file_path.c_str(), flags, mode);
if (!*out_file) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::CloseFile(SMBCFILE* file) {
DCHECK(file);
if (smbc_close_ctx_(context_, file) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::SeekFile(SMBCFILE* file, off_t offset, int whence) {
DCHECK(file);
off_t actual_offset = smbc_lseek_ctx_(context_, file, offset, whence);
if (actual_offset < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::ReadFile(SMBCFILE* file,
void* buf,
size_t count,
size_t* out_bytes_read) {
DCHECK(file);
DCHECK(buf);
DCHECK(out_bytes_read);
ssize_t bytes_read = smbc_read_ctx_(context_, file, buf, count);
if (bytes_read < 0) {
return errno;
}
*out_bytes_read = static_cast<size_t>(bytes_read);
return 0;
}
int SambaInterfaceImpl::WriteFile(SMBCFILE* file,
const void* buf,
size_t count,
size_t* out_bytes_written) {
DCHECK(file);
DCHECK(buf);
DCHECK(out_bytes_written);
ssize_t bytes_written = smbc_write_ctx_(context_, file, buf, count);
if (bytes_written < 0) {
return errno;
}
*out_bytes_written = static_cast<size_t>(bytes_written);
return 0;
}
int SambaInterfaceImpl::TruncateFile(SMBCFILE* file, off_t size) {
DCHECK(file);
if (smbc_ftruncate_ctx_(context_, file, size) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::Stat(const std::string& path, struct stat* out_stat) {
DCHECK(out_stat);
if (smbc_stat_ctx_(context_, path.c_str(), out_stat) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::SetUtimes(const std::string& path,
const struct timespec& atime,
const struct timespec& mtime) {
struct timeval packed_times[2];
packed_times[0].tv_sec = atime.tv_sec;
packed_times[0].tv_usec = 0; // per libsmbclient, tv_usec is ignored
packed_times[1].tv_sec = mtime.tv_sec;
packed_times[1].tv_usec = 0; // per libsmbclient, tv_usec is ignored
if (smbc_utimes_ctx_(context_, path.c_str(), packed_times) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::Rename(const std::string& old_path,
const std::string& new_path) {
if (smbc_rename_ctx_(context_, old_path.c_str(), context_, new_path.c_str()) <
0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::UnlinkFile(const std::string& file_path) {
if (smbc_unlink_ctx_(context_, file_path.c_str()) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::CreateDirectory(const std::string& dir_path,
mode_t mode) {
if (smbc_mkdir_ctx_(context_, dir_path.c_str(), mode) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::OpenDirectory(const std::string& dir_path,
SMBCFILE** out_dir) {
DCHECK(out_dir);
*out_dir = smbc_opendir_ctx_(context_, dir_path.c_str());
if (!*out_dir) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::CloseDirectory(SMBCFILE* dir) {
DCHECK(dir);
if (smbc_closedir_ctx_(context_, dir) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::SeekDirectory(SMBCFILE* dir, off_t offset) {
DCHECK(dir);
if (smbc_lseekdir_ctx_(context_, dir, offset) < 0) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::TellDirectory(SMBCFILE* dir, off_t* out_offset) {
DCHECK(dir);
DCHECK(out_offset);
// Explicitly set |errno| to 0 to detect error cases. On 32-bit platforms,
// smbc_telldir_ctx_() can return <0 in normal cases since internally,
// libsmbclient does a signed cast of a pointer to off_t.
errno = 0;
*out_offset = smbc_telldir_ctx_(context_, dir);
if (*out_offset < 0 && errno) {
return errno;
}
return 0;
}
int SambaInterfaceImpl::ReadDirectory(
SMBCFILE* dir,
const struct libsmb_file_info** out_file_info,
struct stat* out_stat) {
DCHECK(dir);
DCHECK(out_file_info);
DCHECK(out_stat);
*out_stat = {0};
*out_file_info = nullptr;
// Explicitly set |errno| to 0 to detect EOF vs. error cases.
errno = 0;
*out_file_info = smbc_readdirplus_ctx_(context_, dir);
if (!*out_file_info) {
// Differentiate between error and no more files to read.
if (errno) {
return errno;
}
// EOF.
return 0;
}
// TODO(crbug.com/1054711): The mapping of DOS attributes to a mode_t can
// be removed when struct stat is available from smbc_readdirplus2.
out_stat->st_mode =
MakeStatModeBitsFromDOSAttributes((*out_file_info)->attrs);
// TODO(crbug.com/1054711): structure synthesis can be removed once
// smbc_readdirplus2 is available.
out_stat->st_atim = (*out_file_info)->atime_ts;
out_stat->st_ctim = (*out_file_info)->ctime_ts;
out_stat->st_mtim = (*out_file_info)->mtime_ts;
out_stat->st_size = (*out_file_info)->size;
// TODO(crbug.com/1075758): rounding of modification time can be removed once
// libsmbclient is up-revved to include nanosecond precision in SMBC_stat_ctx
if (out_stat->st_mtim.tv_nsec > 500000000) {
out_stat->st_mtim.tv_sec += 1;
}
out_stat->st_mtim.tv_nsec = 0;
return 0;
}
int SambaInterfaceImpl::RemoveDirectory(const std::string& dir_path) {
if (smbc_rmdir_ctx_(context_, dir_path.c_str()) < 0) {
return errno;
}
return 0;
}
mode_t SambaInterfaceImpl::MakeStatModeBitsFromDOSAttributes(
uint16_t attrs) const {
mode_t mode = 0;
if (attrs & SMBC_DOS_MODE_DIRECTORY) {
mode = S_IFDIR;
} else {
mode = S_IFREG;
}
// All files and directories are read / write unless read only.
if (attrs & SMBC_DOS_MODE_READONLY) {
mode |= S_IRUSR;
} else {
mode |= S_IRUSR | S_IWUSR;
}
return mode;
}
} // namespace smbfs