#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/dirent.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <CoreServices/CoreServices.h>
#include <NetFS/URLMount.h>
#include <NetFS/URLMountPrivate.h>
#include <NetFS/URLMountPlugin.h>
#include "smb_netfs.h"
#include <syslog.h>
#define CIFSMediaType ((VolumeType)('cifs'))
#define CIFSURLSCHEME "cifs"
static int CIFS_AttemptMount(const char *urlPtr,
const char *mountPointPtr,
const char *username,
const void *authenticator,
size_t authenticatorlength,
MountProgressHandle **progressHandle,
u_int32_t options,
u_int32_t internalFlags);
static int writestring(int fd, const char *string, size_t stringlen);
static size_t CIFS_RemountInfoSize(struct statfs *mountpointinfo);
#define CIFS_MOUNT_COMMAND "/sbin/mount_cifs"
#define CIFS_PREFIX "cifs://"
#define UNIQUEDIGITS 5
#define SLASH '/'
#define BACKSLASH '\\'
#define REMOUNTINFOMAJORVERSION 1
#define REMOUNTINFOMINORVERSION 0
enum {
kMountWithoutUI = 0x00000001
};
#if PRAGMA_STRUCT_ALIGN
#pragma options align=mac68k
#elif PRAGMA_STRUCT_PACKPUSH
#pragma pack(push, 2)
#elif PRAGMA_STRUCT_PACK
#pragma pack(2)
#endif
struct CIFSRemountInfoHeader {
unsigned char majorVersion;
unsigned char minorVersion;
unsigned char reserved;
unsigned char flags;
};
struct CIFSRemountInfoRecord {
struct CIFSRemountInfoHeader header;
char urlstring[0];
};
#if PRAGMA_STRUCT_ALIGN
#pragma options align=reset
#elif PRAGMA_STRUCT_PACKPUSH
#pragma pack(pop)
#elif PRAGMA_STRUCT_PACK
#pragma pack()
#endif
static char gTargetPrefix[] = "//";
static URLScheme gCIFSURLScheme = CIFSURLSCHEME;
#define kCIFSNetFSInterfaceFactoryID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x92, 0xd4, 0xef, 0xef, 0xf5, 0xaa, 0x11, 0xd5, 0xa1, 0xee, 0x00, 0x30, 0x65, 0xa0, 0xe6, 0xde))
static int
CIFS_MountURL (void *this,
const char *url,
const char *mountdir,
size_t maxlength,
char *mountpoint,
const char *username,
const void *authenticator,
size_t authenticatorlength,
MountProgressHandle **progressHandle,
u_int32_t options,
u_int32_t internalFlags)
{
#pragma unused(this)
const char * namestart;
const char * urlstring;
const char * lastnamestart;
char * slash;
char *at;
char basename[MAXNAMLEN];
char uniquename[MAXNAMLEN];
char realmountdir[MAXPATHLEN];
int error;
size_t namelen;
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: Mounting '%s' in '%s' (uid = %ld)...\n", url, mountdir, geteuid());
#endif
if (realpath(mountdir, realmountdir) == NULL) {
error = errno;
goto errorexit;
}
if ((strlen(realmountdir)) + 1 > maxlength) {
error = ENAMETOOLONG;
goto errorexit;
}
namestart = url;
if ((strncasecmp (url, CIFS_PREFIX, sizeof(CIFS_PREFIX)-1) == 0) ||
(strncasecmp (url, SMB_PREFIX, sizeof(SMB_PREFIX)-1) == 0)) {
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: stripping off URL scheme prefix...\n");
#endif
slash = strchr(url, SLASH);
if (slash) {
namestart = slash + 1;
while (*namestart == SLASH) {
++namestart;
}
}
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: resulting name = '%s'...\n", namestart);
#endif
}
urlstring = namestart;
if (options & kMountAtMountdir) {
strlcpy(mountpoint, realmountdir, maxlength);
} else {
lastnamestart = namestart;
namelen = strlen(namestart);
while ((namelen > 0) && (slash = strchr (namestart, SLASH))) {
#if DEBUG_TRACE
syslog(LOG_DEBUG, "CIFS_MountURL: found name component starting with '/': %s...", slash);
#endif
namelen -= (slash - namestart) + 1;
namestart = slash + 1;
if ((namelen > 0) && (*namestart != SLASH))
lastnamestart = namestart;
#if DEBUG_TRACE
syslog(LOG_DEBUG, "CIFS_MountURL: namestart = '%s'; lastnamestart = '%s'; namelen = %zu...", namestart, lastnamestart, namelen);
#endif
}
namestart = lastnamestart;
if ((slash = strchr (namestart, SLASH))) {
namelen = slash - namestart;
} else {
namelen = strlen(namestart);
}
at = strchr(namestart, '@');
if (at && (at < (namestart + namelen))) {
namelen -= (at - namestart) + 1;
namestart = at + 1;
#if DEBUG_TRACE
syslog(LOG_DEBUG, "CIFS_MountURL: removed auth. info; trimmed name = '%s', namelen = %zu...", namestart, namelen);
#endif
}
if (namelen + 1 > MAXNAMLEN) {
return (ENAMETOOLONG);
}
(void) strncpy(basename, namestart, namelen);
basename[namelen] = (char)0;
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: basename is '%s' (length = %zu)...\n", basename, namelen);
#endif
if ((error = URLM_CreateUniqueMountpointFromEscapedName(realmountdir, basename, 0700,
MAXNAMLEN, uniquename, maxlength, mountpoint))) {
return (error);
}
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: uniquename = '%s'...\n", uniquename);
#endif
}
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: CIFS_AttemptMount('%s', '%s')...\n", urlstring, mountpoint);
#endif
if ((error = CIFS_AttemptMount(urlstring, mountpoint, username,
authenticator, authenticatorlength, progressHandle, options,
internalFlags))) {
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_MountURL: CIFS_AttemptMount returned %d; deleting '%s'...\n", error, mountpoint);
#endif
goto delete;
};
return(0);
delete:
if ((options & kMountAtMountdir) == 0) {
URLM_RemoveMountpoint(mountpoint);
}
errorexit:
return(error);
}
static int
CIFS_MountCompleteURL (void *this,
const char* url,
const char* mountdir,
size_t maxlength,
char* mountpoint,
MountProgressHandle **progressHandle,
u_int32_t options)
{
return CIFS_MountURL(this, url, mountdir, maxlength, mountpoint,
NULL, NULL, 0, progressHandle, options, kMountWithoutUI);
}
static int
CIFS_MountServerURL (void *this,
const char * url,
const char * mountdir,
size_t maxlength,
char * mountpoint,
MountProgressHandle **progressHandle,
u_int32_t options)
{
return (CIFS_MountURL(this, url, mountdir, maxlength, mountpoint,
NULL, NULL, 0, progressHandle, options, 0));
}
static int
CIFS_MountURLWithAuthentication(void *this,
const char *url,
const char *mountdir,
size_t maxlength,
char *mountpoint,
const char *authenticationdomain,
const char *username,
const char *authenticationmethod,
const void *authenticator,
size_t authenticatorlength,
MountProgressHandle **progressHandle,
u_int32_t options)
{
#pragma unused(authenticationdomain, authenticationmethod)
const char *url_scheme_end, *url_end;
const char *password_begin, *password_end, *p;
char c;
ptrdiff_t password_len;
size_t url_len;
char *stripped_url;
int ret;
if (authenticator == NULL) {
goto no_transform;
}
url_scheme_end = strchr(url, ':');
if (url_scheme_end == NULL || url_scheme_end[1] != '/' ||
url_scheme_end[2] != '/') {
goto no_transform;
}
p = &url_scheme_end[3];
password_begin = NULL;
password_end = NULL;
while ((c = *p) != '\0' && c != '/') {
if (c == ';') {
password_begin = NULL;
password_end = NULL;
} else if (c == ':') {
password_begin = p;
} else if (c == '@') {
if (password_begin != NULL)
password_end = p;
break;
}
p++;
}
if (password_begin == NULL || password_end == NULL) {
goto no_transform;
}
password_len = password_end - password_begin;
url_len = strlen(url);
url_end = url + strlen(url) + 1;
stripped_url = malloc(url_len - password_len + 1);
memcpy(stripped_url, url, password_begin - url);
memcpy(stripped_url + (password_begin - url), password_end,
url_end - password_end);
ret = CIFS_MountURL(this, stripped_url, mountdir, maxlength, mountpoint,
username, authenticator, authenticatorlength, progressHandle,
options, kMountWithoutUI);
free(stripped_url);
return ret;
no_transform:
return (CIFS_MountURL(this, url, mountdir, maxlength, mountpoint,
username, authenticator, authenticatorlength, progressHandle,
options, kMountWithoutUI));
}
static int
CIFS_UnmountServerURL(void *this, const char *mountpoint, u_int32_t options)
{
#pragma unused(this, mountpoint, options)
return 0;
}
static int
CIFS_GetCompleteMountURL (void *this, const char * mountpath, size_t maxlength, char * full_url, u_int32_t options)
{
#pragma unused(this, options)
struct statfs statbuf;
int error;
char *sharepointname;
if ((error = statfs (mountpath, &statbuf)) == -1) {
return(error);
}
sharepointname = statbuf.f_mntfromname;
while ((*sharepointname == SLASH) || (*sharepointname == BACKSLASH)) {
++sharepointname;
}
if (maxlength <= (sizeof(SMB_PREFIX) + strlen(sharepointname))) {
return ENAMETOOLONG;
}
strlcpy(full_url, SMB_PREFIX, maxlength);
full_url[maxlength - 1] = (char)0;
strlcat(full_url, sharepointname, maxlength);
return(0);
}
static int
CIFS_GetURLFromURLRemountInfo(void *this, URLRemountInfo *remountinfo,
size_t maxURLlength,
char *complete_URL,
u_int32_t options)
{
#pragma unused(this, options)
struct CIFSRemountInfoRecord *fsremountinfo = (struct CIFSRemountInfoRecord *)(&remountinfo->media_specific);
if ((remountinfo->media != CIFSMediaType) ||
(fsremountinfo->header.majorVersion > REMOUNTINFOMAJORVERSION) ||
(fsremountinfo->header.reserved != 0) ||
(fsremountinfo->header.flags != 0)) {
return ( EINVAL );
}
if (strlen(fsremountinfo->urlstring) >= maxURLlength) {
return ( ENAMETOOLONG );
}
strlcpy(complete_URL, fsremountinfo->urlstring, maxURLlength);
return ( 0 );
}
#define CFENVFORMATSTRING "__CF_USER_TEXT_ENCODING=0x%X:0:0"
static int
CIFS_AttemptMount(const char *urlPtr,
const char *mountPointPtr,
const char *username,
const void *authenticator,
size_t authenticatorlength,
MountProgressHandle **progressHandle,
u_int32_t options,
u_int32_t internalFlags)
{
pid_t pid, terminated_pid;
int result;
union wait status;
char *mounttarget;
size_t mounttarget_len;
char fdnum[11+1];
int p_option = FALSE;
int sp[2];
if (progressHandle)
*progressHandle = (MountProgressHandle)NULL;
while ((*urlPtr == SLASH) || (*urlPtr == BACKSLASH)) {
++urlPtr;
}
mounttarget_len = strlen(urlPtr) + sizeof(gTargetPrefix);
mounttarget = malloc(mounttarget_len);
if (mounttarget == NULL) return (errno ? errno : ENOMEM);
strlcpy(mounttarget, gTargetPrefix, mounttarget_len);
strlcat(mounttarget, urlPtr, mounttarget_len);
if (username || authenticator) {
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sp) == -1) {
result = errno;
goto Return;
}
}
pid = fork();
if (pid == 0) {
uid_t effective_uid;
uid_t real_uid;
char CFUserTextEncodingEnvSetting[sizeof(CFENVFORMATSTRING) + 20];
const char *env[] = {CFUserTextEncodingEnvSetting, "", NULL };
real_uid = getuid();
effective_uid = geteuid();
if ( (real_uid == 0) && (effective_uid != 0) ) {
setuid(real_uid);
setuid(effective_uid);
}
snprintf(CFUserTextEncodingEnvSetting, sizeof(CFUserTextEncodingEnvSetting), CFENVFORMATSTRING, geteuid());
if (username || authenticator) {
p_option = TRUE;
snprintf(fdnum, sizeof fdnum, "%d", sp[0]);
close(sp[1]);
}
if ((internalFlags & kMountWithoutUI) && p_option) {
result = execle(CIFS_MOUNT_COMMAND, CIFS_MOUNT_COMMAND,
"-o", (options & kMarkAutomounted) ? "automounted" : "noautomounted",
"-o", (options & kMarkDontBrowse) ? "nobrowse" : "browse",
"-N",
"-p", fdnum,
mounttarget, mountPointPtr, NULL, env);
} else if (internalFlags & kMountWithoutUI) {
result = execle(CIFS_MOUNT_COMMAND, CIFS_MOUNT_COMMAND,
"-o", (options & kMarkAutomounted) ? "automounted" : "noautomounted",
"-o", (options & kMarkDontBrowse) ? "nobrowse" : "browse",
"-N",
mounttarget, mountPointPtr, NULL, env);
} else if (p_option) {
result = execle(CIFS_MOUNT_COMMAND, CIFS_MOUNT_COMMAND,
"-o", (options & kMarkAutomounted) ? "automounted" : "noautomounted",
"-o", (options & kMarkDontBrowse) ? "nobrowse" : "browse",
"-p", fdnum,
mounttarget, mountPointPtr, NULL, env);
} else {
result = execle(CIFS_MOUNT_COMMAND, CIFS_MOUNT_COMMAND,
"-o", (options & kMarkAutomounted) ? "automounted" : "noautomounted",
"-o", (options & kMarkDontBrowse) ? "nobrowse" : "browse",
mounttarget, mountPointPtr, NULL, env);
}
exit(result ? result : ECHILD);
}
if (pid == -1) {
result = -1;
close(sp[0]);
close(sp[1]);
goto Return;
}
if (username || authenticator) {
int on = 1;
close(sp[0]);
if (setsockopt(sp[1], SOL_SOCKET, SO_NOSIGPIPE, &on, (socklen_t)sizeof (on)) == -1) {
result = errno;
goto Return;
}
result = writestring(sp[1], username,
username == NULL ? 0 : strlen(username));
if (result) {
close(sp[1]);
goto Return;
}
result = writestring(sp[1], authenticator, authenticatorlength);
if (result) {
close(sp[1]);
goto Return;
}
close(sp[1]);
}
if (progressHandle) {
result = URLM_InitStdProgressHandle(progressHandle, pid, &gCIFSURLScheme, mounttarget, mountPointPtr, options);
goto Return;
}
#if DEBUG_TRACE
syslog(LOG_INFO, "CIFS_AttemptMount: waiting for exit of process %ld...\n", pid);
#endif
while ( (terminated_pid = wait4(pid, (int *)&status, 0, NULL)) < 0 ) {
if ( errno != EINTR ) {
break;
}
}
if ((terminated_pid == pid) && (WIFEXITED(status))) {
result = WEXITSTATUS(status);
} else {
result = -1;
}
Return:
if (mounttarget) free(mounttarget);
return result;
}
static int
writestring(int fd, const char *string, size_t stringlen)
{
uint32_t stringlen_be;
ssize_t bytes_written;
if (string == NULL) {
stringlen_be = 0xFFFFFFFF;
bytes_written = write(fd, &stringlen_be, sizeof stringlen_be);
if (bytes_written == -1)
return (errno);
} else {
stringlen_be = htonl((uint32_t)stringlen);
bytes_written = write(fd, &stringlen_be, sizeof stringlen_be);
if (bytes_written == -1)
return (errno);
bytes_written = write(fd, string, stringlen);
if (bytes_written == -1)
return (errno);
}
return (0);
}
static int
CIFS_GetURLRemountInfoSize(void *this, const char *mountpoint, size_t *remountinfosize, u_int32_t options)
{
#pragma unused(this, options)
struct statfs mountinfo;
int result;
if ((result = statfs (mountpoint, &mountinfo)) != 0) return errno;
*remountinfosize = CIFS_RemountInfoSize(&mountinfo);
return 0;
}
static int
CIFS_GetURLRemountInfo(void *this, const char *mountpoint, size_t maxinfosize, URLRemountInfo *remountinfo, u_int32_t options)
{
#pragma unused(this, options)
struct statfs mountinfo;
int result;
struct CIFSRemountInfoRecord *fsremountinfo;
size_t spaceremaining;
char *sharepointname;
if ((result = statfs (mountpoint, &mountinfo)) != 0) return errno;
if (CIFS_RemountInfoSize(&mountinfo) > maxinfosize) return EOVERFLOW;
spaceremaining = maxinfosize;
if ( spaceremaining < offsetof(URLRemountInfo, media_specific) )
return EOVERFLOW;
remountinfo->length = CIFS_RemountInfoSize(&mountinfo);
remountinfo->media = CIFSMediaType;
remountinfo->flags = 0;
fsremountinfo = (struct CIFSRemountInfoRecord *)(&remountinfo->media_specific);
spaceremaining -= offsetof(URLRemountInfo, media_specific);
if (spaceremaining < sizeof(fsremountinfo->header)) return EOVERFLOW;
fsremountinfo->header.majorVersion = REMOUNTINFOMAJORVERSION;
fsremountinfo->header.minorVersion = REMOUNTINFOMINORVERSION;
fsremountinfo->header.reserved = 0;
fsremountinfo->header.flags = 0;
spaceremaining -= sizeof(fsremountinfo->header);
if (spaceremaining < sizeof(CIFS_PREFIX)) return EOVERFLOW;
strncpy(fsremountinfo->urlstring, CIFS_PREFIX, spaceremaining - 1);
fsremountinfo->urlstring[spaceremaining - 1] = (char)0;
spaceremaining -= sizeof(CIFS_PREFIX);
sharepointname = mountinfo.f_mntfromname;
while ((*sharepointname == SLASH) || (*sharepointname == BACKSLASH)) {
++sharepointname;
}
if (spaceremaining <= strlen(sharepointname)) return EOVERFLOW;
strncat(fsremountinfo->urlstring, sharepointname, spaceremaining);
return 0;
}
static int
CIFS_RemountServerURL(void *this, URLRemountInfo *remountinfo, const char *mountdir, size_t maxlength, char *mountpoint, MountProgressHandle **progressHandle, u_int32_t options) {
struct CIFSRemountInfoRecord *fsremountinfo = (struct CIFSRemountInfoRecord *)(&remountinfo->media_specific);
if ((remountinfo->media != CIFSMediaType) ||
(fsremountinfo->header.majorVersion > REMOUNTINFOMAJORVERSION) ||
(fsremountinfo->header.reserved != 0) ||
(fsremountinfo->header.flags != 0)) {
return EINVAL;
}
return CIFS_MountServerURL(this, fsremountinfo->urlstring, mountdir, maxlength, mountpoint, progressHandle, options);
}
static size_t
CIFS_RemountInfoSize(struct statfs *mountpointinfo)
{
char *sharepointname;
sharepointname = mountpointinfo->f_mntfromname;
while ((*sharepointname == SLASH) || (*sharepointname == BACKSLASH)) {
++sharepointname;
}
return sizeof(URLRemountInfo) +
sizeof(struct CIFSRemountInfoRecord) +
sizeof(CIFS_PREFIX) +
strlen(sharepointname) + 1;
};
static URLMountGeneralInterface gCIFSGeneralInterfaceFTbl = {
NULL,
URLM_GeneralQueryInterface,
NetFSInterface_AddRef,
NetFSInterface_Release,
CIFS_MountServerURL,
CIFS_RemountServerURL
};
static URLMountDirectInterface gCIFSDirectInterfaceFTbl = {
NULL,
URLM_DirectQueryInterface,
NetFSInterface_AddRef,
NetFSInterface_Release,
CIFS_MountCompleteURL,
CIFS_MountURLWithAuthentication,
URLM_StdGetURLMountProgressInfo,
URLM_StdCancelURLMount,
URLM_StdCompleteURLMount,
CIFS_UnmountServerURL,
CIFS_GetURLRemountInfoSize,
CIFS_GetURLRemountInfo,
CIFS_GetCompleteMountURL,
CIFS_GetURLFromURLRemountInfo
};
static NetFSMountInterface_V1 gCIFSNetFSMountInterfaceFTbl = {
NULL,
NetFSQueryInterface,
NetFSInterface_AddRef,
NetFSInterface_Release,
SMB_CreateSessionRef,
SMB_GetServerInfo,
SMB_ParseURL,
SMB_CreateURL,
SMB_OpenSession,
SMB_EnumerateShares,
SMB_Mount,
SMB_Cancel,
SMB_CloseSession,
SMB_GetMountInfo,
};
void * CIFSNetFSInterfaceFactory(CFAllocatorRef allocator, CFUUIDRef typeID);
void *
CIFSNetFSInterfaceFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
{
#pragma unused(allocator)
if (CFEqual(typeID, kGeneralURLMounterTypeID)) {
NetFSInterface *result = NetFS_CreateInterface(kCIFSNetFSInterfaceFactoryID, &gCIFSGeneralInterfaceFTbl);
return result;
} else if (CFEqual(typeID, kDirectURLMounterTypeID)) {
NetFSInterface *result = NetFS_CreateInterface(kCIFSNetFSInterfaceFactoryID, &gCIFSDirectInterfaceFTbl);
return result;
} else if (CFEqual(typeID, kNetFSTypeID)) {
NetFSInterface *result = NetFS_CreateInterface(kCIFSNetFSInterfaceFactoryID, &gCIFSNetFSMountInterfaceFTbl);
return result;
} else {
return NULL;
}
}