/* ------------------------------------------------------------------
 * This file is part of libsmbclient-php: Samba bindings for PHP.
 * Libsmbclient-php is licensed under the BSD 2-clause license:
 * ------------------------------------------------------------------
 *
 * Copyright (c) 2003,        Matthew Sachs
 *               2009 - 2014, Eduardo Bacchi Kienetz
 *               2013 - 2015, Alfred Klomp
 *               2015,        Remi Collet
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * ------------------------------------------------------------------
 */

#define IS_EXT_MODULE

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_smbclient.h"

ZEND_DECLARE_MODULE_GLOBALS(smbclient)

#define PHP_SMBCLIENT_STATE_NAME "smbclient state"
#define PHP_SMBCLIENT_FILE_NAME "smbclient file"

static int le_smbclient_state;
static int le_smbclient_file;

typedef size_t strsize_t;

enum {
	SMBCLIENT_OPT_OPEN_SHAREMODE = 1,
	SMBCLIENT_OPT_ENCRYPT_LEVEL = 2,
	SMBCLIENT_OPT_CASE_SENSITIVE = 3,
	SMBCLIENT_OPT_BROWSE_MAX_LMB_COUNT = 4,
	SMBCLIENT_OPT_URLENCODE_READDIR_ENTRIES = 5,
	/* Ignore OneSharePerServer, not relevant to us. */
	SMBCLIENT_OPT_USE_KERBEROS = 6,
	SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS = 7,
	/* Reverse the sense of this option, the original
	 * is the confusing "NoAutoAnonymousLogin": */
	SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN = 8,
	SMBCLIENT_OPT_USE_CCACHE = 9,
	SMBCLIENT_OPT_USE_NT_HASH = 10,
	SMBCLIENT_OPT_NETBIOS_NAME = 11,
	SMBCLIENT_OPT_WORKGROUP = 12,
	SMBCLIENT_OPT_USER = 13,
	SMBCLIENT_OPT_PORT = 14,
	SMBCLIENT_OPT_TIMEOUT = 15,
}
php_smbclient_options;

static char *
find_char (char *start, char *last, char q)
{
	char *c;
	for (c = start; c <= last; c++) {
		if (*c == q) {
			return c;
		}
	}
	return NULL;
}

static char *
find_second_char (char *start, char *last, char q)
{
	char *c;
	if ((c = find_char(start, last, q)) == NULL) {
		return NULL;
	}
	return find_char(c + 1, last, q);
}

static void
astfill (char *start, char *last)
{
	char *c;
	for (c = start; c <= last; c++) {
		*c = '*';
	}
}

static void
hide_password (char *url, int len)
{
	/* URL should have the form:
	 *   smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
	 * Replace everything after the second colon and before the next @
	 * with asterisks. */
	char *last = (url + len) - 1;
	char *second_colon;
	char *slash;
	char *at_sign;

	if (len <= 0) {
		return;
	}
	if ((second_colon = find_second_char(url, last, ':')) == NULL) {
		return;
	}
	if ((slash = find_char(second_colon + 1, last, '/')) == NULL) {
		slash = last + 1;
	}
	if ((at_sign = find_char(second_colon + 1, last, '@')) == NULL) {
		astfill(second_colon + 1, slash - 1);
		return;
	}
	if (at_sign > slash) {
		at_sign = slash;
	}
	astfill(second_colon + 1, at_sign - 1);
}

#if PHP_VERSION_ID < 80000
#include "smbclient_legacy_arginfo.h"
#else
#include "smbclient_arginfo.h"
#endif

zend_module_entry smbclient_module_entry =
	{ STANDARD_MODULE_HEADER
	, "smbclient"		/* name                  */
	, ext_functions		/* functions             */
	, PHP_MINIT(smbclient)		/* module_startup_func   */
	, PHP_MSHUTDOWN(smbclient)	/* module_shutdown_func  */
	, PHP_RINIT(smbclient)		/* request_startup_func  */
	, PHP_RSHUTDOWN(smbclient)	/* request_shutdown_func */
	, PHP_MINFO(smbclient)		/* info_func             */
	, PHP_SMBCLIENT_VERSION		/* version               */
	, PHP_MODULE_GLOBALS(smbclient)
	, PHP_GINIT(smbclient)		/* globals ctor */
	, NULL						/* globals dtor */
	, NULL						/* post_deactivate_func */
	, STANDARD_MODULE_PROPERTIES_EX
	} ;

zend_module_entry old_module_entry = {
	STANDARD_MODULE_HEADER,
	"libsmbclient",
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	PHP_SMBCLIENT_VERSION,
	STANDARD_MODULE_PROPERTIES,
};


#ifdef COMPILE_DL_SMBCLIENT
ZEND_GET_MODULE(smbclient)
#endif

static void
auth_copy (char *dst, char *src, size_t srclen, size_t maxlen)
{
	if (dst == NULL || maxlen == 0) {
		return;
	}
	if (src == NULL || srclen == 0) {
		*dst = '\0';
		return;
	}
	if (srclen < maxlen) {
		memcpy(dst, src, srclen);
		dst[srclen] = '\0';
		return;
	}
	memcpy(dst, src, maxlen - 1);
	dst[maxlen - 1] = '\0';
}

static void
smbclient_auth_func (SMBCCTX *ctx, const char *server, const char *share, char *wrkg, int wrkglen, char *user, int userlen, char *pass, int passlen)
{
	/* Given context, server and share, return workgroup, username and password.
	 * String lengths are the max allowable lengths. */

	php_smbclient_state *state;

	if (ctx == NULL || (state = smbc_getOptionUserData(ctx)) == NULL) {
		return;
	}
	auth_copy(wrkg, state->wrkg, (size_t)state->wrkglen, (size_t)wrkglen);
	auth_copy(user, state->user, (size_t)state->userlen, (size_t)userlen);
	auth_copy(pass, state->pass, (size_t)state->passlen, (size_t)passlen);
}

void
php_smbclient_state_free (php_smbclient_state *state TSRMLS_DC)
{
	/* TODO: if smbc_free_context() returns 0, PHP will leak the handle: */
	if (state->ctx != NULL && smbc_free_context(state->ctx, 1) != 0) {
		switch (errno) {
			case EBUSY: php_error(E_WARNING, "Couldn't destroy SMB context: connection in use"); break;
			case EBADF: php_error(E_WARNING, "Couldn't destroy SMB context: invalid handle"); break;
			default: php_error(E_WARNING, "Couldn't destroy SMB context: unknown error (%d)", errno); break;
		}
	}
	if (state->wrkg != NULL) {
		memset(state->wrkg, 0, state->wrkglen);
		efree(state->wrkg);
	}
	if (state->user != NULL) {
		memset(state->user, 0, state->userlen);
		efree(state->user);
	}
	if (state->pass != NULL) {
		memset(state->pass, 0, state->passlen);
		efree(state->pass);
	}
	efree(state);
}

static inline void
smbclient_state_dtor (
#if PHP_MAJOR_VERSION >= 7
	zend_resource *rsrc
#else
	zend_rsrc_list_entry *rsrc
#endif
	TSRMLS_DC)
{
	php_smbclient_state_free((php_smbclient_state *)rsrc->ptr TSRMLS_CC);
}

static void
smbclient_file_dtor (
	zend_resource *rsrc
	TSRMLS_DC)
{
	/* Because libsmbclient's file/dir close functions require a pointer to
	 * a context which we don't have, we cannot reliably destroy a file
	 * resource. One way of obtaining the context pointer could be to save
	 * it in a structure along with the file context, but the pointer could
	 * grow stale or otherwise spark a race condition. So it seems that the
	 * best we can do is nothing. The PHP programmer can either manually
	 * free the file resources, or wait for them to be cleaned up when the
	 * associated context is destroyed. */
}

PHP_GINIT_FUNCTION(smbclient)
{
	smbclient_globals->pool_first = NULL;
}

PHP_MINIT_FUNCTION(smbclient)
{
	/* Constants for smbclient_setxattr: */
	REGISTER_LONG_CONSTANT("SMBCLIENT_XATTR_CREATE", SMBC_XATTR_FLAG_CREATE, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_XATTR_REPLACE", SMBC_XATTR_FLAG_REPLACE, CONST_PERSISTENT | CONST_CS);

	/* Constants for getting/setting options: */
	#define SMBCLIENT_CONST(x) REGISTER_LONG_CONSTANT(#x, x, CONST_PERSISTENT | CONST_CS);
	SMBCLIENT_CONST(SMBCLIENT_OPT_OPEN_SHAREMODE);
	SMBCLIENT_CONST(SMBCLIENT_OPT_ENCRYPT_LEVEL);
	SMBCLIENT_CONST(SMBCLIENT_OPT_CASE_SENSITIVE);
	SMBCLIENT_CONST(SMBCLIENT_OPT_BROWSE_MAX_LMB_COUNT);
	SMBCLIENT_CONST(SMBCLIENT_OPT_URLENCODE_READDIR_ENTRIES);
	SMBCLIENT_CONST(SMBCLIENT_OPT_USE_KERBEROS);
	SMBCLIENT_CONST(SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS);
	SMBCLIENT_CONST(SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN);
	SMBCLIENT_CONST(SMBCLIENT_OPT_USE_CCACHE);
	SMBCLIENT_CONST(SMBCLIENT_OPT_USE_NT_HASH);
	SMBCLIENT_CONST(SMBCLIENT_OPT_NETBIOS_NAME);
	SMBCLIENT_CONST(SMBCLIENT_OPT_WORKGROUP);
	SMBCLIENT_CONST(SMBCLIENT_OPT_USER);
	SMBCLIENT_CONST(SMBCLIENT_OPT_PORT);
	SMBCLIENT_CONST(SMBCLIENT_OPT_TIMEOUT);
	#undef SMBCLIENT_CONST

	/* Constants for use with SMBCLIENT_OPT_OPENSHAREMODE: */
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_DOS", SMBC_SHAREMODE_DENY_DOS, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_ALL", SMBC_SHAREMODE_DENY_ALL, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_WRITE", SMBC_SHAREMODE_DENY_WRITE, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_READ", SMBC_SHAREMODE_DENY_READ, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_NONE", SMBC_SHAREMODE_DENY_NONE, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_SHAREMODE_DENY_FCB", SMBC_SHAREMODE_DENY_FCB, CONST_PERSISTENT | CONST_CS);

	/* Constants for use with SMBCLIENT_OPT_ENCRYPTLEVEL: */
	REGISTER_LONG_CONSTANT("SMBCLIENT_ENCRYPTLEVEL_NONE", SMBC_ENCRYPTLEVEL_NONE, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_ENCRYPTLEVEL_REQUEST", SMBC_ENCRYPTLEVEL_REQUEST, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_ENCRYPTLEVEL_REQUIRE", SMBC_ENCRYPTLEVEL_REQUIRE, CONST_PERSISTENT | CONST_CS);

	/* Constants for the VFS functions: */
	REGISTER_LONG_CONSTANT("SMBCLIENT_VFS_RDONLY", SMBC_VFS_FEATURE_RDONLY, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_VFS_DFS", SMBC_VFS_FEATURE_DFS, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_VFS_CASE_INSENSITIVE", SMBC_VFS_FEATURE_CASE_INSENSITIVE, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("SMBCLIENT_VFS_NO_UNIXCIFS", SMBC_VFS_FEATURE_NO_UNIXCIFS, CONST_PERSISTENT | CONST_CS);

	le_smbclient_state = zend_register_list_destructors_ex(smbclient_state_dtor, NULL, PHP_SMBCLIENT_STATE_NAME, module_number);
	le_smbclient_file = zend_register_list_destructors_ex(smbclient_file_dtor, NULL, PHP_SMBCLIENT_FILE_NAME, module_number);

	php_register_url_stream_wrapper("smb", &php_stream_smb_wrapper TSRMLS_CC);

	/* register with old name for code using extension_loaded(libsmbclient) */
	zend_register_internal_module(&old_module_entry TSRMLS_CC);

	return SUCCESS;
}

PHP_RINIT_FUNCTION(smbclient)
{
	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(smbclient)
{
	return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(smbclient)
{
	php_smb_pool_cleanup(TSRMLS_C);
	return SUCCESS;
}

PHP_MINFO_FUNCTION(smbclient)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "smbclient Support", "enabled");
	php_info_print_table_row(2, "smbclient extension Version", PHP_SMBCLIENT_VERSION);
	php_info_print_table_row(2, "libsmbclient library Version", smbc_version());
	php_info_print_table_end();
}

static int
ctx_init_getauth (zval *z, char **dest, int *destlen, char *varname)
{
	if (*dest != NULL) {
		efree(*dest);
		*dest = NULL;
	}
	*destlen = 0;

	if (z == NULL) {
		return 1;
	}
	switch (Z_TYPE_P(z))
	{
		case IS_NULL:
			return 1;

#if PHP_MAJOR_VERSION >= 7
		case IS_TRUE:
			php_error(E_WARNING, "invalid value for %s", varname);
			return 0;

		case IS_FALSE:
			return 1;
#else
		case IS_BOOL:
			if (Z_LVAL_P(z) == 1) {
				php_error(E_WARNING, "invalid value for %s", varname);
				return 0;
			}
			return 1;
#endif

		case IS_STRING:
			*destlen = Z_STRLEN_P(z);
			*dest = estrndup(Z_STRVAL_P(z), *destlen);
			return 1;

		default:
			php_error(E_WARNING, "invalid datatype for %s", varname);
			return 0;
	}
}

php_smbclient_state *
php_smbclient_state_new (php_stream_context *context, int init TSRMLS_DC)
{
	php_smbclient_state *state;
	SMBCCTX *ctx;

	if ((ctx = smbc_new_context()) == NULL) {
		switch (errno) {
			case ENOMEM: php_error(E_WARNING, "Couldn't create smbclient state: insufficient memory"); break;
			default: php_error(E_WARNING, "Couldn't create smbclient state: unknown error (%d)", errno); break;
		};
		return NULL;
	}
	state = emalloc(sizeof(php_smbclient_state));
	state->ctx = ctx;
	state->wrkg = NULL;
	state->user = NULL;
	state->pass = NULL;
	state->wrkglen = 0;
	state->userlen = 0;
	state->passlen = 0;
	state->err = 0;

	smbc_setFunctionAuthDataWithContext(state->ctx, smbclient_auth_func);

	/* Must also save a pointer to the state object inside the context, to
	 * find the state from the context in the auth function: */
	smbc_setOptionUserData(ctx, (void *)state);

	/* Force full, modern timenames when getting xattrs: */
	smbc_setOptionFullTimeNames(state->ctx, 1);

	if (context) {
#if PHP_MAJOR_VERSION >= 7
		zval *tmpzval;

		if (NULL != (tmpzval = php_stream_context_get_option(context, "smb", "workgroup"))) {
			if (ctx_init_getauth(tmpzval, &state->wrkg, &state->wrkglen, "workgroup") == 0) {
				php_smbclient_state_free(state);
				return NULL;
			}
		}
		if (NULL != (tmpzval = php_stream_context_get_option(context, "smb", "username"))) {
			if (ctx_init_getauth(tmpzval, &state->user, &state->userlen, "username") == 0) {
				php_smbclient_state_free(state);
				return NULL;
			}
		}
		if (NULL != (tmpzval = php_stream_context_get_option(context, "smb", "password"))) {
			if (ctx_init_getauth(tmpzval, &state->pass, &state->passlen, "password") == 0) {
				php_smbclient_state_free(state);
				return NULL;
			}
		}
#if HAVE_SMBC_SETOPTIONPROTOCOLS
		if (NULL != (tmpzval = php_stream_context_get_option(context, "smb", "minproto"))) {
			smbc_setOptionProtocols(state->ctx, Z_STRVAL_P(tmpzval), NULL);
		}
		if (NULL != (tmpzval = php_stream_context_get_option(context, "smb", "maxproto"))) {
			smbc_setOptionProtocols(state->ctx, NULL, Z_STRVAL_P(tmpzval));
		}
#endif
#else
		zval **tmpzval;

		if (php_stream_context_get_option(context, "smb", "workgroup", &tmpzval) == SUCCESS) {
			if (ctx_init_getauth(*tmpzval, &state->wrkg, &state->wrkglen, "workgroup") == 0) {
				php_smbclient_state_free(state TSRMLS_CC);
				return NULL;
			}
		}
		if (php_stream_context_get_option(context, "smb", "username", &tmpzval) == SUCCESS) {
			if (ctx_init_getauth(*tmpzval, &state->user, &state->userlen, "username") == 0) {
				php_smbclient_state_free(state TSRMLS_CC);
				return NULL;
			}
		}
		if (php_stream_context_get_option(context, "smb", "password", &tmpzval) == SUCCESS) {
			if (ctx_init_getauth(*tmpzval, &state->pass, &state->passlen, "password") == 0) {
				php_smbclient_state_free(state TSRMLS_CC);
				return NULL;
			}
		}
#if HAVE_SMBC_SETOPTIONPROTOCOLS
		if (php_stream_context_get_option(context, "smb", "minproto", &tmpzval) == SUCCESS) {
			smbc_setOptionProtocols(state->ctx, Z_STRVAL_PP(tmpzval), NULL);
		}
		if (php_stream_context_get_option(context, "smb", "maxproto", &tmpzval) == SUCCESS) {
			smbc_setOptionProtocols(state->ctx, NULL, Z_STRVAL_PP(tmpzval));
		}
#endif
#endif
	}
	if (init) {
		if (php_smbclient_state_init(state TSRMLS_CC)) {
			php_smbclient_state_free(state TSRMLS_CC);
			return NULL;
		}
	}
	return state;
}

PHP_FUNCTION(smbclient_state_new)
{
	php_smbclient_state *state;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_FALSE;
	}
	if ((state = php_smbclient_state_new(NULL, 0 TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}
#if PHP_MAJOR_VERSION >= 7
	ZVAL_RES(return_value, zend_register_resource(state, le_smbclient_state));
#else
	ZEND_REGISTER_RESOURCE(return_value, state, le_smbclient_state);
#endif
}

PHP_FUNCTION(smbclient_version)
{
	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_FALSE;
	}
#if PHP_MAJOR_VERSION >= 7
	RETURN_STRING(PHP_SMBCLIENT_VERSION);
#else
	RETURN_STRING(PHP_SMBCLIENT_VERSION, 1);
#endif
}

PHP_FUNCTION(smbclient_library_version)
{
	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_FALSE;
	}
#if PHP_MAJOR_VERSION >= 7
	RETURN_STRING(smbc_version());
#else
	RETURN_STRING(smbc_version(), 1);
#endif
}

int
php_smbclient_state_init (php_smbclient_state *state TSRMLS_DC)
{
	SMBCCTX *ctx;

	if ((ctx = smbc_init_context(state->ctx)) != NULL) {
		state->ctx = ctx;
		return 0;
	}
	switch (state->err = errno) {
		case EBADF: php_error(E_WARNING, "Couldn't init SMB context: null context given"); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't init SMB context: insufficient memory"); break;
		case ENOENT: php_error(E_WARNING, "Couldn't init SMB context: cannot load smb.conf"); break;
		default: php_error(E_WARNING, "Couldn't init SMB context: unknown error (%d)", errno); break;
	}
	return 1;
}

#if PHP_MAJOR_VERSION >= 7

#define SMB_FETCH_RESOURCE(_state, _type, _zval, _name, _le) \
	if ((_state = (_type)zend_fetch_resource(Z_RES_P(*_zval), _name, _le)) == NULL) { \
		RETURN_FALSE; \
	}
#else

#define SMB_FETCH_RESOURCE(_state, _type, _zval, _name, _le) \
	ZEND_FETCH_RESOURCE(_state, _type, _zval, -1, _name, _le);

#endif

#define STATE_FROM_ZSTATE \
	SMB_FETCH_RESOURCE(state, php_smbclient_state *, &zstate, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state); \
	if (state == NULL || state->ctx == NULL) { \
		php_error(E_WARNING, PHP_SMBCLIENT_STATE_NAME " not found"); \
		RETURN_FALSE; \
	}

#define FILE_FROM_ZFILE \
	SMB_FETCH_RESOURCE(file, SMBCFILE *, &zfile, PHP_SMBCLIENT_FILE_NAME, le_smbclient_file); \
	if (file == NULL) { \
		php_error(E_WARNING, PHP_SMBCLIENT_FILE_NAME " not found"); \
		RETURN_FALSE; \
	}

PHP_FUNCTION(smbclient_state_init)
{
	zval *zstate;
	zval *zwrkg = NULL;
	zval *zuser = NULL;
	zval *zpass = NULL;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|zzz", &zstate, &zwrkg, &zuser, &zpass) != SUCCESS) {
		RETURN_FALSE;
	}
	SMB_FETCH_RESOURCE(state, php_smbclient_state *, &zstate, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state);

	if (state->ctx == NULL) {
		php_error(E_WARNING, "Couldn't init SMB context: context is null");
		RETURN_FALSE;
	}
	if (ctx_init_getauth(zwrkg, &state->wrkg, &state->wrkglen, "workgroup") == 0) {
		RETURN_FALSE;
	}
	if (ctx_init_getauth(zuser, &state->user, &state->userlen, "username") == 0) {
		RETURN_FALSE;
	}
	if (ctx_init_getauth(zpass, &state->pass, &state->passlen, "password") == 0) {
		RETURN_FALSE;
	}
	if (php_smbclient_state_init(state TSRMLS_CC)) {
		RETURN_FALSE;
	}
	RETURN_TRUE;
}

PHP_FUNCTION(smbclient_state_errno)
{
	zval *zstate;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstate) != SUCCESS) {
		RETURN_LONG(0);
	}
	SMB_FETCH_RESOURCE(state, php_smbclient_state *, &zstate, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state);
	RETURN_LONG(state->err);
}

PHP_FUNCTION(smbclient_state_free)
{
	zval *zstate;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstate) != SUCCESS) {
		RETURN_FALSE;
	}
	SMB_FETCH_RESOURCE(state, php_smbclient_state *, &zstate, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state);

	if (state->ctx == NULL) {
#if PHP_MAJOR_VERSION >= 7
		zend_list_close(Z_RES_P(zstate));
#else
		zend_list_delete(Z_LVAL_P(zstate));
#endif
		RETURN_TRUE;
	}
	if (smbc_free_context(state->ctx, 1) == 0) {
		state->ctx = NULL;
#if PHP_MAJOR_VERSION >= 7
		zend_list_close(Z_RES_P(zstate));
#else
		zend_list_delete(Z_LVAL_P(zstate));
#endif
		RETURN_TRUE;
	}
	switch (state->err = errno) {
		case EBUSY: php_error(E_WARNING, "Couldn't destroy smbclient state: connection in use"); break;
		case EBADF: php_error(E_WARNING, "Couldn't destroy smbclient state: invalid handle"); break;
		default: php_error(E_WARNING, "Couldn't destroy smbclient state: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_opendir)
{
	char *path;
	strsize_t path_len;
	zval *zstate;
	SMBCFILE *dir;
	smbc_opendir_fn smbc_opendir;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &path, &path_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_opendir = smbc_getFunctionOpendir(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if ((dir = smbc_opendir(state->ctx, path)) != NULL) {
#if PHP_MAJOR_VERSION >= 7
		ZVAL_RES(return_value, zend_register_resource(dir, le_smbclient_file));
#else
		ZEND_REGISTER_RESOURCE(return_value, dir, le_smbclient_file);
#endif
		return;
	}
	hide_password(path, path_len);
	switch (state->err = errno) {
		case EACCES: php_error(E_WARNING, "Couldn't open SMB directory %s: Permission denied", path); break;
		case EINVAL: php_error(E_WARNING, "Couldn't open SMB directory %s: Invalid URL", path); break;
		case ENOENT: php_error(E_WARNING, "Couldn't open SMB directory %s: Path does not exist", path); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't open SMB directory %s: Insufficient memory", path); break;
		case ENOTDIR: php_error(E_WARNING, "Couldn't open SMB directory %s: Not a directory", path); break;
		case EPERM: php_error(E_WARNING, "Couldn't open SMB directory %s: Workgroup not found", path); break;
		case ENODEV: php_error(E_WARNING, "Couldn't open SMB directory %s: Workgroup or server not found", path); break;
		default: php_error(E_WARNING, "Couldn't open SMB directory %s: unknown error (%d)", path, errno); break;
	}
	RETURN_FALSE;
}

static char *
type_to_string (unsigned int type)
{
	switch (type) {
		case SMBC_WORKGROUP: return "workgroup";
		case SMBC_SERVER: return "server";
		case SMBC_FILE_SHARE: return "file share";
		case SMBC_PRINTER_SHARE: return "printer share";
		case SMBC_COMMS_SHARE: return "communication share";
		case SMBC_IPC_SHARE: return "IPC share";
		case SMBC_DIR: return "directory";
		case SMBC_FILE: return "file";
		case SMBC_LINK: return "link";
	}
	return "unknown";
}

PHP_FUNCTION(smbclient_readdir)
{
	struct smbc_dirent *dirent;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_readdir_fn smbc_readdir;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &zstate, &zfile) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_readdir = smbc_getFunctionReaddir(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	errno = 0;
	if ((dirent = smbc_readdir(state->ctx, file)) == NULL) {
		switch (state->err = errno) {
			case 0: RETURN_FALSE;
			case EBADF: php_error(E_WARNING, "Couldn't read " PHP_SMBCLIENT_FILE_NAME ": Not a directory resource"); break;
			case EINVAL: php_error(E_WARNING, "Couldn't read " PHP_SMBCLIENT_FILE_NAME ": State resource not initialized"); break;
			default: php_error(E_WARNING, "Couldn't read " PHP_SMBCLIENT_FILE_NAME ": unknown error (%d)", errno); break;
		}
		RETURN_FALSE;
	}
	array_init(return_value);
#if PHP_MAJOR_VERSION >= 7
	add_assoc_string(return_value, "type", type_to_string(dirent->smbc_type));
	add_assoc_stringl(return_value, "comment", dirent->comment, dirent->commentlen);
	add_assoc_stringl(return_value, "name", dirent->name, dirent->namelen);
#else
	add_assoc_string(return_value, "type", type_to_string(dirent->smbc_type), 1);
	add_assoc_stringl(return_value, "comment", dirent->comment, dirent->commentlen, 1);
	add_assoc_stringl(return_value, "name", dirent->name, dirent->namelen, 1);
#endif
}

PHP_FUNCTION(smbclient_closedir)
{
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_closedir_fn smbc_closedir;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &zstate, &zfile) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_closedir = smbc_getFunctionClosedir(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_closedir(state->ctx, file) == 0) {
#if PHP_MAJOR_VERSION >= 7
		zend_list_close(Z_RES_P(zfile));
#else
		zend_list_delete(Z_LVAL_P(zfile));
#endif
		RETURN_TRUE;
	}
	switch (state->err = errno) {
		case EBADF: php_error(E_WARNING, "Couldn't close " PHP_SMBCLIENT_FILE_NAME ": Not a directory resource"); break;
		default: php_error(E_WARNING, "Couldn't close " PHP_SMBCLIENT_FILE_NAME ": unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_rename)
{
	char *ourl, *nurl;
	strsize_t ourl_len, nurl_len;
	zval *zstate_old;
	zval *zstate_new;
	smbc_rename_fn smbc_rename;
	php_smbclient_state *state_old;
	php_smbclient_state *state_new;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsrs", &zstate_old, &ourl, &ourl_len, &zstate_new, &nurl, &nurl_len) == FAILURE) {
		return;
	}
	SMB_FETCH_RESOURCE(state_old, php_smbclient_state *, &zstate_old, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state);
	SMB_FETCH_RESOURCE(state_new, php_smbclient_state *, &zstate_new, PHP_SMBCLIENT_STATE_NAME, le_smbclient_state);

	if (state_old == NULL || state_old->ctx == NULL) {
		php_error(E_WARNING, "old " PHP_SMBCLIENT_STATE_NAME " is null");
		RETURN_FALSE;
	}
	if (state_new == NULL || state_new->ctx == NULL) {
		php_error(E_WARNING, "new " PHP_SMBCLIENT_STATE_NAME " is null");
		RETURN_FALSE;
	}
	if ((smbc_rename = smbc_getFunctionRename(state_old->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_rename(state_old->ctx, ourl, state_new->ctx, nurl) == 0) {
		RETURN_TRUE;
	}
	hide_password(ourl, ourl_len);
	switch (state_old->err = errno) {
		case EISDIR: php_error(E_WARNING, "Couldn't rename SMB directory %s: existing url is not a directory", ourl); break;
		case EACCES: php_error(E_WARNING, "Couldn't open SMB directory %s: Permission denied", ourl); break;
		case EINVAL: php_error(E_WARNING, "Couldn't open SMB directory %s: Invalid URL", ourl); break;
		case ENOENT: php_error(E_WARNING, "Couldn't open SMB directory %s: Path does not exist", ourl); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't open SMB directory %s: Insufficient memory", ourl); break;
		case ENOTDIR: php_error(E_WARNING, "Couldn't open SMB directory %s: Not a directory", ourl); break;
		case EPERM: php_error(E_WARNING, "Couldn't open SMB directory %s: Workgroup not found", ourl); break;
		case EXDEV: php_error(E_WARNING, "Couldn't open SMB directory %s: Workgroup or server not found", ourl); break;
		case EEXIST: php_error(E_WARNING, "Couldn't rename SMB directory %s: new name already exists", ourl); break;
		default: php_error(E_WARNING, "Couldn't open SMB directory %s: unknown error (%d)", ourl, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_unlink)
{
	char *url;
	strsize_t url_len;
	zval *zstate;
	smbc_unlink_fn smbc_unlink;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &url, &url_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_unlink = smbc_getFunctionUnlink(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_unlink(state->ctx, url) == 0) {
		RETURN_TRUE;
	}
	hide_password(url, url_len);
	switch (state->err = errno) {
		case EACCES: php_error(E_WARNING, "Couldn't delete %s: Permission denied", url); break;
		case EINVAL: php_error(E_WARNING, "Couldn't delete %s: Invalid URL", url); break;
		case ENOENT: php_error(E_WARNING, "Couldn't delete %s: Path does not exist", url); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't delete %s: Insufficient memory", url); break;
		case EPERM: php_error(E_WARNING, "Couldn't delete %s: Workgroup not found", url); break;
		case EISDIR: php_error(E_WARNING, "Couldn't delete %s: It is a Directory (use rmdir instead)", url); break;
		case EBUSY: php_error(E_WARNING, "Couldn't delete %s: Device or resource busy", url); break;
		default: php_error(E_WARNING, "Couldn't delete %s: unknown error (%d)", url, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_mkdir)
{
	char *path = NULL;
	strsize_t path_len;
	zend_long mode = 0777;	/* Same as PHP's native mkdir() */
	zval *zstate;
	smbc_mkdir_fn smbc_mkdir;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|l", &zstate, &path, &path_len, &mode) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_mkdir = smbc_getFunctionMkdir(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_mkdir(state->ctx, path, (mode_t)mode) == 0) {
		RETURN_TRUE;
	}
	hide_password(path, path_len);
	switch (state->err = errno) {
		case EACCES: php_error(E_WARNING, "Couldn't create SMB directory %s: Permission denied", path); break;
		case EINVAL: php_error(E_WARNING, "Couldn't create SMB directory %s: Invalid URL", path); break;
		case ENOENT: php_error(E_WARNING, "Couldn't create SMB directory %s: Path does not exist", path); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't create SMB directory %s: Insufficient memory", path); break;
		case EEXIST: php_error(E_WARNING, "Couldn't create SMB directory %s: Directory already exists", path); break;
		default: php_error(E_WARNING, "Couldn't create SMB directory %s: unknown error (%d)", path, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_rmdir)
{
	char *url;
	strsize_t url_len;
	zval *zstate;
	smbc_rmdir_fn smbc_rmdir;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &url, &url_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_rmdir = smbc_getFunctionRmdir(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_rmdir(state->ctx, url) == 0) {
		RETURN_TRUE;
	}
	hide_password(url, url_len);
	switch (state->err = errno) {
		case EACCES: php_error(E_WARNING, "Couldn't delete %s: Permission denied", url); break;
		case EINVAL: php_error(E_WARNING, "Couldn't delete %s: Invalid URL", url); break;
		case ENOENT: php_error(E_WARNING, "Couldn't delete %s: Path does not exist", url); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't delete %s: Insufficient memory", url); break;
		case EPERM: php_error(E_WARNING, "Couldn't delete %s: Workgroup not found", url); break;
		case ENOTEMPTY: php_error(E_WARNING, "Couldn't delete %s: It is not empty", url); break;
		default: php_error(E_WARNING, "Couldn't delete %s: unknown error (%d)", url, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_stat)
{
	char *file;
	struct stat statbuf;
	strsize_t file_len;
	zval *zstate;
	smbc_stat_fn smbc_stat;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &file, &file_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_stat = smbc_getFunctionStat(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_stat(state->ctx, file, &statbuf) < 0) {
		hide_password(file, file_len);
		switch (state->err = errno) {
			case ENOENT: php_error(E_WARNING, "Couldn't stat %s: Does not exist", file); break;
			case EINVAL: php_error(E_WARNING, "Couldn't stat: null URL or smbc_init failed"); break;
			case EACCES: php_error(E_WARNING, "Couldn't stat %s: Permission denied", file); break;
			case ENOMEM: php_error(E_WARNING, "Couldn't stat %s: Out of memory", file); break;
			case ENOTDIR: php_error(E_WARNING, "Couldn't stat %s: Not a directory", file); break;
			default: php_error(E_WARNING, "Couldn't stat %s: unknown error (%d)", file, errno); break;
		}
		RETURN_FALSE;
	}
	array_init(return_value);
	add_index_long(return_value, 0, statbuf.st_dev);
	add_index_long(return_value, 1, statbuf.st_ino);
	add_index_long(return_value, 2, statbuf.st_mode);
	add_index_long(return_value, 3, statbuf.st_nlink);
	add_index_long(return_value, 4, statbuf.st_uid);
	add_index_long(return_value, 5, statbuf.st_gid);
	add_index_long(return_value, 6, statbuf.st_rdev);
	add_index_long(return_value, 7, statbuf.st_size);
	add_index_long(return_value, 8, statbuf.st_atime);
	add_index_long(return_value, 9, statbuf.st_mtime);
	add_index_long(return_value, 10, statbuf.st_ctime);
	add_index_long(return_value, 11, statbuf.st_blksize);
	add_index_long(return_value, 12, statbuf.st_blocks);

	add_assoc_long(return_value, "dev", statbuf.st_dev);
	add_assoc_long(return_value, "ino", statbuf.st_ino);
	add_assoc_long(return_value, "mode", statbuf.st_mode);
	add_assoc_long(return_value, "nlink", statbuf.st_nlink);
	add_assoc_long(return_value, "uid", statbuf.st_uid);
	add_assoc_long(return_value, "gid", statbuf.st_gid);
	add_assoc_long(return_value, "rdev", statbuf.st_rdev);
	add_assoc_long(return_value, "size", statbuf.st_size);
	add_assoc_long(return_value, "atime", statbuf.st_atime);
	add_assoc_long(return_value, "mtime", statbuf.st_mtime);
	add_assoc_long(return_value, "ctime", statbuf.st_ctime);
	add_assoc_long(return_value, "blksize", statbuf.st_blksize);
	add_assoc_long(return_value, "blocks", statbuf.st_blocks);
}

PHP_FUNCTION(smbclient_fstat)
{
	struct stat statbuf;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_fstat_fn smbc_fstat;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &zstate, &zfile) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_fstat = smbc_getFunctionFstat(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_fstat(state->ctx, file, &statbuf) < 0) {
		switch (state->err = errno) {
			case ENOENT: php_error(E_WARNING, "Couldn't fstat " PHP_SMBCLIENT_FILE_NAME ": Does not exist"); break;
			case EINVAL: php_error(E_WARNING, "Couldn't fstat: null resource or smbc_init failed"); break;
			case EACCES: php_error(E_WARNING, "Couldn't fstat " PHP_SMBCLIENT_FILE_NAME ": Permission denied"); break;
			case ENOMEM: php_error(E_WARNING, "Couldn't fstat " PHP_SMBCLIENT_FILE_NAME ": Out of memory"); break;
			case ENOTDIR: php_error(E_WARNING, "Couldn't fstat " PHP_SMBCLIENT_FILE_NAME ": Not a directory"); break;
			default: php_error(E_WARNING, "Couldn't fstat " PHP_SMBCLIENT_FILE_NAME ": unknown error (%d)", errno); break;
		}
		RETURN_FALSE;
	}
	array_init(return_value);
	add_index_long(return_value, 0, statbuf.st_dev);
	add_index_long(return_value, 1, statbuf.st_ino);
	add_index_long(return_value, 2, statbuf.st_mode);
	add_index_long(return_value, 3, statbuf.st_nlink);
	add_index_long(return_value, 4, statbuf.st_uid);
	add_index_long(return_value, 5, statbuf.st_gid);
	add_index_long(return_value, 6, statbuf.st_rdev);
	add_index_long(return_value, 7, statbuf.st_size);
	add_index_long(return_value, 8, statbuf.st_atime);
	add_index_long(return_value, 9, statbuf.st_mtime);
	add_index_long(return_value, 10, statbuf.st_ctime);
	add_index_long(return_value, 11, statbuf.st_blksize);
	add_index_long(return_value, 12, statbuf.st_blocks);

	add_assoc_long(return_value, "dev", statbuf.st_dev);
	add_assoc_long(return_value, "ino", statbuf.st_ino);
	add_assoc_long(return_value, "mode", statbuf.st_mode);
	add_assoc_long(return_value, "nlink", statbuf.st_nlink);
	add_assoc_long(return_value, "uid", statbuf.st_uid);
	add_assoc_long(return_value, "gid", statbuf.st_gid);
	add_assoc_long(return_value, "rdev", statbuf.st_rdev);
	add_assoc_long(return_value, "size", statbuf.st_size);
	add_assoc_long(return_value, "atime", statbuf.st_atime);
	add_assoc_long(return_value, "mtime", statbuf.st_mtime);
	add_assoc_long(return_value, "ctime", statbuf.st_ctime);
	add_assoc_long(return_value, "blksize", statbuf.st_blksize);
	add_assoc_long(return_value, "blocks", statbuf.st_blocks);
}

int
flagstring_to_smbflags (const char *flags, int flags_len, int *retval TSRMLS_DC)
{
	/* Returns 0 on failure, or 1 on success with *retval filled. */
	if (flags_len != 1 && flags_len != 2) {
		goto err;
	}
	if (flags_len == 2 && flags[1] != '+') {
		goto err;
	}
	/* For both lengths, add the "core business" flags.
	 * See php_stream_parse_fopen_modes() in PHP's /main/streams/plain_wrapper.c
	 * for how PHP's native fopen() translates these flags: */
	switch (flags[0]) {
		case 'r': *retval = 0; break;
		case 'w': *retval = O_CREAT | O_TRUNC; break;
		case 'a': *retval = O_CREAT | O_APPEND; break;
		case 'x': *retval = O_CREAT | O_EXCL; break;
		case 'c': *retval = O_CREAT; break;
		default: goto err;
	}
	/* If length is 1, enforce read-only or write-only: */
	if (flags_len == 1) {
		*retval |= (*retval == 0) ? O_RDONLY : O_WRONLY;
		return 1;
	}
	/* Length is 2 and this is a '+' mode, so read/write everywhere: */
	*retval |= O_RDWR;
	return 1;

err:
	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid flag string '%s'", flags);
	return 0;
}

PHP_FUNCTION(smbclient_open)
{
	char *file, *flags;
	strsize_t file_len, flags_len;
	int smbflags;
	zend_long mode = 0666;
	zval *zstate;
	SMBCFILE *handle;
	smbc_open_fn smbc_open;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss|l", &zstate, &file, &file_len, &flags, &flags_len, &mode) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	/* The flagstring is in the same format as the native fopen() uses, so
	 * one of the characters r, w, a, x, c, optionally followed by a plus.
	 * Need to translate this to an integer value for smbc_open: */
	if (flagstring_to_smbflags(flags, flags_len, &smbflags TSRMLS_CC) == 0) {
		RETURN_FALSE;
	}
	if ((smbc_open = smbc_getFunctionOpen(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if ((handle = smbc_open(state->ctx, file, smbflags, mode)) != NULL) {
#if PHP_MAJOR_VERSION >= 7
		ZVAL_RES(return_value, zend_register_resource(handle, le_smbclient_file));
#else
		ZEND_REGISTER_RESOURCE(return_value, handle, le_smbclient_file);
#endif
		return;
	}
	hide_password(file, file_len);
	switch (state->err = errno) {
		case ENOMEM: php_error(E_WARNING, "Couldn't open %s: Out of memory", file); break;
		case EINVAL: php_error(E_WARNING, "Couldn't open %s: No file?", file); break;
		case EEXIST: php_error(E_WARNING, "Couldn't open %s: Pathname already exists and O_CREAT and O_EXECL were specified", file); break;
		case EISDIR: php_error(E_WARNING, "Couldn't open %s: Can't write to a directory", file); break;
		case EACCES: php_error(E_WARNING, "Couldn't open %s: Access denied", file); break;
		case ENODEV: php_error(E_WARNING, "Couldn't open %s: Requested share does not exist", file); break;
		case ENOTDIR: php_error(E_WARNING, "Couldn't open %s: Path component isn't a directory", file); break;
		case ENOENT: php_error(E_WARNING, "Couldn't open %s: Directory in path doesn't exist", file); break;
		default: php_error(E_WARNING, "Couldn't open %s: unknown error (%d)", file, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_creat)
{
	char *file;
	strsize_t file_len;
	zend_long mode = 0666;
	zval *zstate;
	SMBCFILE *handle;
	smbc_creat_fn smbc_creat;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|l", &zstate, &file, &file_len, &mode) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_creat = smbc_getFunctionCreat(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if ((handle = smbc_creat(state->ctx, file, (mode_t)mode)) != NULL) {
#if PHP_MAJOR_VERSION >= 7
		ZVAL_RES(return_value, zend_register_resource(handle, le_smbclient_file));
#else
		ZEND_REGISTER_RESOURCE(return_value, handle, le_smbclient_file);
#endif

		return;
	}
	hide_password(file, file_len);
	switch (state->err = errno) {
		case ENOMEM: php_error(E_WARNING, "Couldn't create %s: Out of memory", file); break;
		case EINVAL: php_error(E_WARNING, "Couldn't create %s: No file?", file); break;
		case EEXIST: php_error(E_WARNING, "Couldn't create %s: Pathname already exists and O_CREAT and O_EXECL were specified", file); break;
		case EISDIR: php_error(E_WARNING, "Couldn't create %s: Can't write to a directory", file); break;
		case EACCES: php_error(E_WARNING, "Couldn't create %s: Access denied", file); break;
		case ENODEV: php_error(E_WARNING, "Couldn't create %s: Requested share does not exist", file); break;
		case ENOENT: php_error(E_WARNING, "Couldn't create %s: Directory in path doesn't exist", file); break;
		default: php_error(E_WARNING, "Couldn't create %s: unknown error (%d)", file, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_read)
{
	zend_long count;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_read_fn smbc_read;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rrl", &zstate, &zfile, &count) == FAILURE) {
		return;
	}
	if (count < 0) {
		php_error(E_WARNING, "Negative byte count: " ZEND_LONG_FMT, count);
		RETURN_FALSE;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_read = smbc_getFunctionRead(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
#if PHP_MAJOR_VERSION >= 7
	zend_string *buf = zend_string_alloc(count, 0);

	if ((ZSTR_LEN(buf) = smbc_read(state->ctx, file, ZSTR_VAL(buf), count)) >= 0) {
		RETURN_STR(buf);
	}
	zend_string_release(buf);
#else
	void *buf = emalloc(count);
	ssize_t nbytes;

	if ((nbytes = smbc_read(state->ctx, file, buf, count)) >= 0) {
		RETURN_STRINGL(buf, nbytes, 0);
	}
	efree(buf);
#endif
	switch (state->err = errno) {
		case EISDIR: php_error(E_WARNING, "Read error: Is a directory"); break;
		case EBADF: php_error(E_WARNING, "Read error: Not a valid file resource or not open for reading"); break;
		case EINVAL: php_error(E_WARNING, "Read error: Object not suitable for reading or bad buffer"); break;
		default: php_error(E_WARNING, "Read error: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_write)
{
	zend_long count = 0;
	strsize_t str_len;
	char * str;
	size_t nwrite;
	ssize_t nbytes;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_write_fn smbc_write;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rrs|l", &zstate, &zfile, &str, &str_len, &count) == FAILURE) {
		return;
	}
	if (count < 0) {
		php_error(E_WARNING, "Negative byte count: " ZEND_LONG_FMT, count);
		RETURN_FALSE;
	}
	if (count == 0 || count > str_len) {
		nwrite = str_len;
	}
	else {
		nwrite = count;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_write = smbc_getFunctionWrite(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if ((nbytes = smbc_write(state->ctx, file, str, nwrite)) >= 0) {
		RETURN_LONG(nbytes);
	}
	switch (state->err = errno) {
		case EISDIR: php_error(E_WARNING, "Write error: Is a directory"); break;
		case EBADF: php_error(E_WARNING, "Write error: Not a valid file resource or not open for reading"); break;
		case EINVAL: php_error(E_WARNING, "Write error: Object not suitable for reading or bad buffer"); break;
		case EACCES: php_error(E_WARNING, "Write error: Permission denied"); break;
		default: php_error(E_WARNING, "Write error: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_lseek)
{
	zend_long offset, whence, ret;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_lseek_fn smbc_lseek;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rrll", &zstate, &zfile, &offset, &whence) == FAILURE) {
		return;
	}
	if ((int)whence != SEEK_SET && (int)whence != SEEK_CUR && (int)whence != SEEK_END) {
		php_error(E_WARNING, "Invalid argument for whence");
		RETURN_FALSE;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_lseek = smbc_getFunctionLseek(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if ((ret = smbc_lseek(state->ctx, file, (off_t)offset, (int)whence)) > -1) {
		RETURN_LONG(ret);
	}
	switch (state->err = errno) {
		case EBADF: php_error(E_WARNING, "Couldn't lseek: resource is invalid"); break;
		case EINVAL: php_error(E_WARNING, "Couldn't lseek: invalid parameters or not initialized"); break;
		default: php_error(E_WARNING, "Couldn't lseek: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_ftruncate)
{
	zend_long offset;
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_ftruncate_fn smbc_ftruncate;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rrl", &zstate, &zfile, &offset) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_ftruncate = smbc_getFunctionFtruncate(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_ftruncate(state->ctx, file, offset) == 0) {
		RETURN_TRUE;
	}
	switch (state->err = errno) {
		case EBADF: php_error(E_WARNING, "Couldn't ftruncate: resource is invalid"); break;
		case EACCES: php_error(E_WARNING, "Couldn't ftruncate: permission denied"); break;
		case EINVAL: php_error(E_WARNING, "Couldn't ftruncate: invalid parameters or not initialized"); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't ftruncate: out of memory"); break;
		default: php_error(E_WARNING, "Couldn't ftruncate: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_close)
{
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	smbc_close_fn smbc_close;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &zstate, &zfile) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_close = smbc_getFunctionClose(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_close(state->ctx, file) == 0) {
#if PHP_MAJOR_VERSION >= 7
		zend_list_close(Z_RES_P(zfile));
#else
		zend_list_delete(Z_LVAL_P(zfile));
#endif
		RETURN_TRUE;
	}
	switch (state->err = errno) {
		case EBADF: php_error(E_WARNING, "Close error: Not a valid file resource or not open for reading"); break;
		case EINVAL: php_error(E_WARNING, "Close error: State resource not initialized"); break;
		default: php_error(E_WARNING, "Close error: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_chmod)
{
	char *file;
	strsize_t file_len;
	zend_long mode;
	zval *zstate;
	smbc_chmod_fn smbc_chmod;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsl", &zstate, &file, &file_len, &mode) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_chmod = smbc_getFunctionChmod(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_chmod(state->ctx, file, (mode_t)mode) == 0) {
		RETURN_TRUE;
	}
	hide_password(file, file_len);
	switch (state->err = errno) {
		case EPERM: php_error(E_WARNING, "Couldn't chmod %s: the effective UID does not match the owner of the file, and is not zero", file); break;
		case ENOENT: php_error(E_WARNING, "Couldn't chmod %s: file or directory does not exist", file); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't chmod %s: insufficient memory", file); break;
		default: php_error(E_WARNING, "Couldn't chmod %s: unknown error (%d)", file, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_utimes)
{
	char *file;
	strsize_t file_len;
	zend_long mtime = -1, atime = -1;
	zval *zstate;
	struct timeval times[2];
	smbc_utimes_fn smbc_utimes;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|ll", &zstate, &file, &file_len, &mtime, &atime) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	times[0].tv_usec = 0;	/* times[0] = access time (atime) */
	times[1].tv_usec = 0;	/* times[1] = write time (mtime) */

	/* TODO: we are a bit lazy here about the optional arguments and assume
	 * that if they are negative, they were omitted. This makes it
	 * impossible to use legitimate negative timestamps - a rare use-case. */
	times[1].tv_sec = (mtime < 0) ? time(NULL) : mtime;

	/* If not given, atime defaults to value of mtime: */
	times[0].tv_sec = (atime < 0) ? times[1].tv_sec : atime;

	if ((smbc_utimes = smbc_getFunctionUtimes(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_utimes(state->ctx, file, times) == 0) {
		RETURN_TRUE;
	}
	hide_password(file, file_len);
	switch (state->err = errno) {
		case EINVAL: php_error(E_WARNING, "Couldn't set times on %s: the client library is not properly initialized", file); break;
		case EPERM: php_error(E_WARNING, "Couldn't set times on %s: permission was denied", file); break;
		default: php_error(E_WARNING, "Couldn't set times on %s: unknown error (%d)", file, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_listxattr)
{
	char *url, *s, *c;
	strsize_t url_len;
	char values[1000];
	zval *zstate;
	smbc_listxattr_fn smbc_listxattr;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &url, &url_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_listxattr = smbc_getFunctionListxattr(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	/* This is a bit of a bogus function. Looking in the Samba source, it
	 * always returns all possible attribute names, regardless of what the
	 * file system supports or which attributes the file actually has.
	 * Because this list is static, we can get away with using a fixed
	 * buffer size.*/
	if (smbc_listxattr(state->ctx, url, values, sizeof(values)) >= 0) {
		array_init(return_value);
		/* Each attribute is null-separated, the list itself terminates
		 * with an empty element (i.e. two null bytes in a row). */
		for (s = c = values; c < values + sizeof(values); c++) {
			if (*c != '\0') {
				continue;
			}
			/* c and s identical: last element */
			if (s == c) {
				break;
			}
#if PHP_MAJOR_VERSION >= 7
			add_next_index_stringl(return_value, s, c - s);
#else
			add_next_index_stringl(return_value, s, c - s, 1);
#endif
			s = c + 1;
		}
		return;
	}
	switch (state->err = errno) {
		case EINVAL: php_error(E_WARNING, "Couldn't get xattrs: library not initialized"); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't get xattrs: out of memory"); break;
		case EPERM: php_error(E_WARNING, "Couldn't get xattrs: permission denied"); break;
		case ENOTSUP: php_error(E_WARNING, "Couldn't get xattrs: file system does not support extended attributes"); break;
		default: php_error(E_WARNING, "Couldn't get xattrs: unknown error (%d)", errno); break;
	}
	RETURN_FALSE;
}


/* loop from 16K to 256M */
#define DEFAULT_BUFFER_SIZE   (16 << 10)
#define MAXIMUM_BUFFER_SIZE  (256 << 20)

PHP_FUNCTION(smbclient_getxattr)
{
	char *url, *name;
	strsize_t url_len, name_len;
	int retsize;
	int xattr_size;
	zval *zstate;
	smbc_getxattr_fn smbc_getxattr;
	php_smbclient_state *state;
	char *values = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss", &zstate, &url, &url_len, &name, &name_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_getxattr = smbc_getFunctionGetxattr(state->ctx)) == NULL) {
		RETURN_FALSE;
	}

	xattr_size = smbc_getxattr(state->ctx, url, name, NULL, 0);

	if (xattr_size < 0) {
		goto fail;
	}

	if (xattr_size == 0) {
		/* since version 4.16.9 and 4.17.5 this means success :(
		 * so there is no way to compute the buffer size
		 * see https://bugzilla.samba.org/show_bug.cgi?id=14808
		 * This regression is fixed in 4.18.7 and 4.19.0
		 */
		xattr_size = DEFAULT_BUFFER_SIZE;
		do {
			if (values) {
				efree(values);
				xattr_size *= 4;
			}
			values = emalloc(xattr_size + 1);
			retsize = smbc_getxattr(state->ctx, url, name, values, xattr_size + 1);
		} while (retsize < 0 && xattr_size < MAXIMUM_BUFFER_SIZE);
	} else {
		values = emalloc(xattr_size + 1);
		retsize = smbc_getxattr(state->ctx, url, name, values, xattr_size + 1);
	}

	if (retsize == 0) { /* success, since 4.16.9 and 4.17.5 */
		retsize = strlen(values);
	} else if (retsize > xattr_size) { /* time-of-check, time-of-use error, never happen as recent versions return -1 */
		retsize = xattr_size;
	} else if (retsize < 0) {
		efree(values);
		goto fail;
	}
	/* realloc the string to its real size */
#if PHP_MAJOR_VERSION >= 7
	RETVAL_STRINGL(values, retsize);
#else
	RETVAL_STRINGL(values, retsize, 1);
#endif
	efree(values);
	return;

fail:
	hide_password(url, url_len);
	switch (state->err = errno) {
		case EINVAL: php_error(E_WARNING, "Couldn't get xattr for %s: library not initialized or incorrect parameter", url); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't get xattr for %s: out of memory", url); break;
		case EPERM: php_error(E_WARNING, "Couldn't get xattr for %s: permission denied", url); break;
		case ENOTSUP: php_error(E_WARNING, "Couldn't get xattr for %s: file system does not support extended attributes", url); break;
		default:
			if (xattr_size == MAXIMUM_BUFFER_SIZE) {
				php_error(E_WARNING, "Couldn't get xattr for %s: internal buffer is too small", url); break;
			} else {
				php_error(E_WARNING, "Couldn't get xattr for %s: unknown error (%d)", url, errno); break;
			}
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_setxattr)
{
	char *url, *name, *val;
	strsize_t url_len, name_len, val_len;
	zend_long flags = 0;
	zval *zstate;
	smbc_setxattr_fn smbc_setxattr;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsss|l", &zstate, &url, &url_len, &name, &name_len, &val, &val_len, &flags) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_setxattr = smbc_getFunctionSetxattr(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_setxattr(state->ctx, url, name, val, val_len, flags) == 0) {
		RETURN_TRUE;
	}
	hide_password(url, url_len);
	switch (state->err = errno) {
		case EINVAL: php_error(E_WARNING, "Couldn't set attribute on %s: client library not properly initialized", url); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't set attribute on %s: out of memory", url); break;
		case EEXIST: php_error(E_WARNING, "Couldn't set attribute on %s: attribute already exists", url); break;
		case ENOATTR: php_error(E_WARNING, "Couldn't set attribute on %s: attribute does not exist", url); break;
		case ENOTSUP: php_error(E_WARNING, "Couldn't set attribute on %s: not supported by filesystem", url); break;
		case EPERM: php_error(E_WARNING, "Couldn't set attribute on %s: permission denied", url); break;
		default: php_error(E_WARNING, "Couldn't set attribute on %s: unknown error (%d)", url, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_removexattr)
{
	char *url, *name;
	strsize_t url_len, name_len;
	zval *zstate;
	smbc_removexattr_fn smbc_removexattr;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss", &zstate, &url, &url_len, &name, &name_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_removexattr = smbc_getFunctionRemovexattr(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_removexattr(state->ctx, url, name) == 0) {
		RETURN_TRUE;
	}
	hide_password(url, url_len);
	switch (state->err = errno) {
		case EINVAL: php_error(E_WARNING, "Couldn't remove attribute on %s: client library not properly initialized", url); break;
		case ENOMEM: php_error(E_WARNING, "Couldn't remove attribute on %s: out of memory", url); break;
		case ENOTSUP: php_error(E_WARNING, "Couldn't remove attribute on %s: not supported by filesystem", url); break;
		case EPERM: php_error(E_WARNING, "Couldn't remove attribute on %s: permission denied", url); break;
		default: php_error(E_WARNING, "Couldn't remove attribute on %s: unknown error (%d)", url, errno); break;
	}
	RETURN_FALSE;
}

PHP_FUNCTION(smbclient_option_get)
{
	zend_long option;
	const char *ret;
	zval *zstate;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &zstate, &option) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	switch (option)
	{
	case SMBCLIENT_OPT_OPEN_SHAREMODE:
		RETURN_LONG(smbc_getOptionOpenShareMode(state->ctx));

	case SMBCLIENT_OPT_ENCRYPT_LEVEL:
		RETURN_LONG(smbc_getOptionSmbEncryptionLevel(state->ctx));

	case SMBCLIENT_OPT_CASE_SENSITIVE:
		RETURN_BOOL(smbc_getOptionCaseSensitive(state->ctx));

	case SMBCLIENT_OPT_BROWSE_MAX_LMB_COUNT:
		RETURN_LONG(smbc_getOptionBrowseMaxLmbCount(state->ctx));

	case SMBCLIENT_OPT_URLENCODE_READDIR_ENTRIES:
		RETURN_BOOL(smbc_getOptionUrlEncodeReaddirEntries(state->ctx));

	case SMBCLIENT_OPT_USE_KERBEROS:
		RETURN_BOOL(smbc_getOptionUseKerberos(state->ctx));

	case SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS:
		RETURN_BOOL(smbc_getOptionFallbackAfterKerberos(state->ctx));

	/* Reverse the sense of this option, the original is confusing: */
	case SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN:
		RETURN_BOOL(!(smbc_getOptionNoAutoAnonymousLogin(state->ctx)));

	case SMBCLIENT_OPT_USE_CCACHE:
		RETURN_BOOL(smbc_getOptionUseCCache(state->ctx));

	#ifdef HAVE_SMBC_SETOPTIONUSENTHASH
	case SMBCLIENT_OPT_USE_NT_HASH:
		RETURN_BOOL(smbc_getOptionUseNTHash(state->ctx));
	#endif

	#ifdef HAVE_SMBC_SETPORT
	case SMBCLIENT_OPT_PORT:
		RETURN_LONG(smbc_getPort(state->ctx));
	#endif

	case SMBCLIENT_OPT_TIMEOUT:
		RETURN_LONG(smbc_getTimeout(state->ctx));

	case SMBCLIENT_OPT_NETBIOS_NAME:
		if ((ret = smbc_getNetbiosName(state->ctx)) == NULL) {
			RETURN_EMPTY_STRING();
		}
		if (strlen(ret) == 0) {
			RETURN_EMPTY_STRING();
		}
#if PHP_MAJOR_VERSION >= 7
		RETURN_STRING(ret);
#else
		RETURN_STRING(ret, 1);
#endif

	case SMBCLIENT_OPT_WORKGROUP:
		if ((ret = smbc_getWorkgroup(state->ctx)) == NULL) {
			RETURN_EMPTY_STRING();
		}
		if (strlen(ret) == 0) {
			RETURN_EMPTY_STRING();
		}
#if PHP_MAJOR_VERSION >= 7
		RETURN_STRING(ret);
#else
		RETURN_STRING(ret, 1);
#endif
	case SMBCLIENT_OPT_USER:
		if ((ret = smbc_getUser(state->ctx)) == NULL) {
			RETURN_EMPTY_STRING();
		}
		if (strlen(ret) == 0) {
			RETURN_EMPTY_STRING();
		}
#if PHP_MAJOR_VERSION >= 7
		RETURN_STRING(ret);
#else
		RETURN_STRING(ret, 1);
#endif

	}
	RETURN_NULL();
}

PHP_FUNCTION(smbclient_option_set)
{
	zend_long option, vbool = 0;
	zval *zstate;
	zval *zvalue;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlz", &zstate, &option, &zvalue) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	switch (Z_TYPE_P(zvalue))
	{
#if PHP_MAJOR_VERSION >= 7
	case IS_TRUE:
		vbool = 1;
		/* no break, fallthrough */
	case IS_FALSE:
#else
	case IS_BOOL:
		vbool = Z_BVAL_P(zvalue);
#endif
		switch (option)
		{
		case SMBCLIENT_OPT_CASE_SENSITIVE:
			smbc_setOptionCaseSensitive(state->ctx, vbool);
			RETURN_TRUE;

		case SMBCLIENT_OPT_URLENCODE_READDIR_ENTRIES:
			smbc_setOptionUrlEncodeReaddirEntries(state->ctx, vbool);
			RETURN_TRUE;

		case SMBCLIENT_OPT_USE_KERBEROS:
			smbc_setOptionUseKerberos(state->ctx, vbool);
			RETURN_TRUE;

		case SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS:
			smbc_setOptionFallbackAfterKerberos(state->ctx, vbool);
			RETURN_TRUE;

		/* Reverse the sense of this option: */
		case SMBCLIENT_OPT_AUTO_ANONYMOUS_LOGIN:
			smbc_setOptionNoAutoAnonymousLogin(state->ctx, !(vbool));
			RETURN_TRUE;

		case SMBCLIENT_OPT_USE_CCACHE:
			smbc_setOptionUseCCache(state->ctx, vbool);
			RETURN_TRUE;

		#ifdef HAVE_SMBC_SETOPTIONUSENTHASH
		case SMBCLIENT_OPT_USE_NT_HASH:
			smbc_setOptionUseNTHash(state->ctx, vbool);
			RETURN_TRUE;
		#endif
		}
		break;

	case IS_LONG:

		switch (option)
		{
		case SMBCLIENT_OPT_OPEN_SHAREMODE:
			smbc_setOptionOpenShareMode(state->ctx, Z_LVAL_P(zvalue));
			RETURN_TRUE;

		case SMBCLIENT_OPT_ENCRYPT_LEVEL:
			smbc_setOptionSmbEncryptionLevel(state->ctx, Z_LVAL_P(zvalue));
			RETURN_TRUE;

		case SMBCLIENT_OPT_BROWSE_MAX_LMB_COUNT:
			smbc_setOptionBrowseMaxLmbCount(state->ctx, Z_LVAL_P(zvalue));
			RETURN_TRUE;

		#ifdef HAVE_SMBC_SETPORT
		case SMBCLIENT_OPT_PORT:
			smbc_setPort(state->ctx, Z_LVAL_P(zvalue));
			RETURN_TRUE;
		#endif

		case SMBCLIENT_OPT_TIMEOUT:
			smbc_setTimeout(state->ctx, Z_LVAL_P(zvalue));
			RETURN_TRUE;
		}
		break;

	case IS_STRING:

		switch (option)
		{
		case SMBCLIENT_OPT_NETBIOS_NAME:
			smbc_setNetbiosName(state->ctx, Z_STRVAL_P(zvalue));
			RETURN_TRUE;

		/* For the next two options, update our state object as well: */
		case SMBCLIENT_OPT_WORKGROUP:
			if (ctx_init_getauth(zvalue, &state->wrkg, &state->wrkglen, "workgroup") == 0) {
				RETURN_FALSE;
			}
			smbc_setWorkgroup(state->ctx, Z_STRVAL_P(zvalue));
			RETURN_TRUE;

		case SMBCLIENT_OPT_USER:
			if (ctx_init_getauth(zvalue, &state->user, &state->userlen, "username") == 0) {
				RETURN_FALSE;
			}
			smbc_setUser(state->ctx, Z_STRVAL_P(zvalue));
			RETURN_TRUE;
		}
		break;
	}
	RETURN_FALSE;
}

#if HAVE_SMBC_SETOPTIONPROTOCOLS
PHP_FUNCTION(smbclient_client_protocols)
{
	zval *zstate;
	char *minproto = NULL, *maxproto = NULL;
	strsize_t minproto_len, maxproto_len;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|s!s!", &zstate, &minproto, &minproto_len, &maxproto, &maxproto_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	RETURN_BOOL(smbc_setOptionProtocols(state->ctx, minproto, maxproto));
}
#endif

PHP_FUNCTION(smbclient_statvfs)
{
	char *url;
	strsize_t url_len;
	zval *zstate;
	struct statvfs st;
	smbc_statvfs_fn smbc_statvfs;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zstate, &url, &url_len) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;

	if ((smbc_statvfs = smbc_getFunctionStatVFS(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_statvfs(state->ctx, url, &st) != 0) {
		hide_password(url, url_len);
		switch (state->err = errno) {
			case EBADF: php_error(E_WARNING, "Couldn't statvfs %s: bad file descriptor", url); break;
			case EACCES: php_error(E_WARNING, "Couldn't statvfs %s: permission denied", url); break;
			case EINVAL: php_error(E_WARNING, "Couldn't statvfs %s: library not initalized or otherwise invalid", url); break;
			case ENOMEM: php_error(E_WARNING, "Couldn't statvfs %s: out of memory", url); break;
			default: php_error(E_WARNING, "Couldn't statvfs %s: unknown error (%d)", url, errno); break;
		}
		RETURN_FALSE;
	}
	array_init(return_value);
	add_assoc_long(return_value, "bsize",   st.f_bsize);
	add_assoc_long(return_value, "frsize",  st.f_frsize);
	add_assoc_long(return_value, "blocks",  st.f_blocks);
	add_assoc_long(return_value, "bfree",   st.f_bfree);
	add_assoc_long(return_value, "bavail",  st.f_bavail);
	add_assoc_long(return_value, "files",   st.f_files);
	add_assoc_long(return_value, "ffree",   st.f_ffree);
	add_assoc_long(return_value, "favail",  st.f_favail);
	add_assoc_long(return_value, "fsid",    st.f_fsid);
	add_assoc_long(return_value, "flag",    st.f_flag);
	add_assoc_long(return_value, "namemax", st.f_namemax);
}

PHP_FUNCTION(smbclient_fstatvfs)
{
	zval *zstate;
	zval *zfile;
	SMBCFILE *file;
	struct statvfs st;
	smbc_fstatvfs_fn smbc_fstatvfs;
	php_smbclient_state *state;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rr", &zstate, &zfile) == FAILURE) {
		return;
	}
	STATE_FROM_ZSTATE;
	FILE_FROM_ZFILE;

	if ((smbc_fstatvfs = smbc_getFunctionFstatVFS(state->ctx)) == NULL) {
		RETURN_FALSE;
	}
	if (smbc_fstatvfs(state->ctx, file, &st) != 0) {
		switch (state->err = errno) {
			case EBADF: php_error(E_WARNING, "Couldn't fstatvfs: bad file descriptor"); break;
			case EACCES: php_error(E_WARNING, "Couldn't fstatvfs: permission denied"); break;
			case EINVAL: php_error(E_WARNING, "Couldn't fstatvfs: library not initalized or otherwise invalid"); break;
			case ENOMEM: php_error(E_WARNING, "Couldn't fstatvfs: out of memory"); break;
			default: php_error(E_WARNING, "Couldn't fstatvfs: unknown error (%d)", errno); break;
		}
		RETURN_FALSE;
	}
	array_init(return_value);
	add_assoc_long(return_value, "bsize",   st.f_bsize);
	add_assoc_long(return_value, "frsize",  st.f_frsize);
	add_assoc_long(return_value, "blocks",  st.f_blocks);
	add_assoc_long(return_value, "bfree",   st.f_bfree);
	add_assoc_long(return_value, "bavail",  st.f_bavail);
	add_assoc_long(return_value, "files",   st.f_files);
	add_assoc_long(return_value, "ffree",   st.f_ffree);
	add_assoc_long(return_value, "favail",  st.f_favail);
	add_assoc_long(return_value, "fsid",    st.f_fsid);
	add_assoc_long(return_value, "flag",    st.f_flag);
	add_assoc_long(return_value, "namemax", st.f_namemax);
}