summaryrefslogtreecommitdiff
path: root/source3/libsmb/libsmb_server.c
diff options
context:
space:
mode:
authorDerrell Lipman <derrell.lipman@unwireduniverse.com>2008-02-28 11:23:20 -0500
committerDerrell Lipman <derrell.lipman@unwireduniverse.com>2008-03-01 20:47:22 -0500
commit257b7b09298f7cb983b2f31b87fc5e46e0f62f0c (patch)
tree696951497f7fc012ff06349de9a867bda22d78bd /source3/libsmb/libsmb_server.c
parent422af9a516dd36fa291a28fd5753a05c139aaecb (diff)
downloadsamba-257b7b09298f7cb983b2f31b87fc5e46e0f62f0c.tar.gz
samba-257b7b09298f7cb983b2f31b87fc5e46e0f62f0c.tar.bz2
samba-257b7b09298f7cb983b2f31b87fc5e46e0f62f0c.zip
Initial revamp of the libsmbclient interface.
The libsmbclient interface has suffered from difficulty of improvement and feature enrichment without causing ABI breakage. Although there were a number of issues, the primary ones were: (a) the user of the library would manually manipulate the context structure members, meaning that nothing in the context structure could change other than adding stuff at the end; (b) there were three methods of setting options: setting bits in a flags field within the context structure, setting explicit options variables within an options structure in the context structure, and by calling the smbc_option_set() function; (c) the authentication callback did not traditionally provide enough information to the callee which required adding an option for a callback with a different signature, and now there are requests for even more information at the callback, requiring yet a third signature and option to set it (if we implement that feature). This commit provides a reorganization of the code which fixes (a) and (b). The context structure is now entirely opaque, and there are setter and getter functions for manipulating it. This makes maintaining ABI consistency much, much easier. Additionally, the options setting/getting has been unified into a single mechanism using smbc_option_set() and smbc_option_get(). Yet to be completed is a refactoring of the authentication callback (c). The test programs in examples/libsmbclient have been modified (if necessary; some applications require no changes at all) for the new API and a few have been minimally tested. Derrell (This used to be commit d4b4bae8ded824d06ad5ab0e219f71187ee5c771)
Diffstat (limited to 'source3/libsmb/libsmb_server.c')
-rw-r--r--source3/libsmb/libsmb_server.c675
1 files changed, 675 insertions, 0 deletions
diff --git a/source3/libsmb/libsmb_server.c b/source3/libsmb/libsmb_server.c
new file mode 100644
index 0000000000..978a843d30
--- /dev/null
+++ b/source3/libsmb/libsmb_server.c
@@ -0,0 +1,675 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/*
+ * Check a server for being alive and well.
+ * returns 0 if the server is in shape. Returns 1 on error
+ *
+ * Also useable outside libsmbclient to enable external cache
+ * to do some checks too.
+ */
+int
+SMBC_check_server(SMBCCTX * context,
+ SMBCSRV * server)
+{
+ socklen_t size;
+ struct sockaddr addr;
+
+ size = sizeof(addr);
+ return (getpeername(server->cli->fd, &addr, &size) == -1);
+}
+
+/*
+ * Remove a server from the cached server list it's unused.
+ * On success, 0 is returned. 1 is returned if the server could not be removed.
+ *
+ * Also useable outside libsmbclient
+ */
+int
+SMBC_remove_unused_server(SMBCCTX * context,
+ SMBCSRV * srv)
+{
+ SMBCFILE * file;
+
+ /* are we being fooled ? */
+ if (!context || !context->initialized || !srv) {
+ return 1;
+ }
+
+ /* Check all open files/directories for a relation with this server */
+ for (file = context->files; file; file = file->next) {
+ if (file->srv == srv) {
+ /* Still used */
+ DEBUG(3, ("smbc_remove_usused_server: "
+ "%p still used by %p.\n",
+ srv, file));
+ return 1;
+ }
+ }
+
+ DLIST_REMOVE(context->servers, srv);
+
+ cli_shutdown(srv->cli);
+ srv->cli = NULL;
+
+ DEBUG(3, ("smbc_remove_usused_server: %p removed.\n", srv));
+
+ (context->cache.remove_cached_server_fn)(context, srv);
+
+ SAFE_FREE(srv);
+ return 0;
+}
+
+/****************************************************************
+ * Call the auth_fn with fixed size (fstring) buffers.
+ ***************************************************************/
+void
+SMBC_call_auth_fn(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ fstring workgroup;
+ fstring username;
+ fstring password;
+
+ strlcpy(workgroup, *pp_workgroup, sizeof(workgroup));
+ strlcpy(username, *pp_username, sizeof(username));
+ strlcpy(password, *pp_password, sizeof(password));
+
+ (context->server.get_auth_data_fn)(
+ server, share,
+ workgroup, sizeof(workgroup),
+ username, sizeof(username),
+ password, sizeof(password));
+
+ TALLOC_FREE(*pp_workgroup);
+ TALLOC_FREE(*pp_username);
+ TALLOC_FREE(*pp_password);
+
+ *pp_workgroup = talloc_strdup(ctx, workgroup);
+ *pp_username = talloc_strdup(ctx, username);
+ *pp_password = talloc_strdup(ctx, password);
+}
+
+
+void
+SMBC_get_auth_data(const char *server, const char *share,
+ char *workgroup_buf, int workgroup_buf_len,
+ char *username_buf, int username_buf_len,
+ char *password_buf, int password_buf_len)
+{
+ /* Default function just uses provided data. Nothing to do. */
+}
+
+
+
+SMBCSRV *
+SMBC_find_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ SMBCSRV *srv;
+ int auth_called = 0;
+
+ check_server_cache:
+
+ srv = (context->cache.get_cached_server_fn)(context,
+ server, share,
+ *pp_workgroup,
+ *pp_username);
+
+ if (!auth_called && !srv && (!*pp_username || !(*pp_username)[0] ||
+ !*pp_password || !(*pp_password)[0])) {
+ SMBC_call_auth_fn(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+
+ if (!pp_workgroup || !pp_username || !pp_password) {
+ return NULL;
+ }
+
+ /*
+ * However, smbc_auth_fn may have picked up info relating to
+ * an existing connection, so try for an existing connection
+ * again ...
+ */
+ auth_called = 1;
+ goto check_server_cache;
+
+ }
+
+ if (srv) {
+ if ((context->server.check_server_fn)(context, srv)) {
+ /*
+ * This server is no good anymore
+ * Try to remove it and check for more possible
+ * servers in the cache
+ */
+ if ((context->server.remove_unused_server_fn)(context,
+ srv)) {
+ /*
+ * We could not remove the server completely,
+ * remove it from the cache so we will not get
+ * it again. It will be removed when the last
+ * file/dir is closed.
+ */
+ (context->cache.remove_cached_server_fn)(context,
+ srv);
+ }
+
+ /*
+ * Maybe there are more cached connections to this
+ * server
+ */
+ goto check_server_cache;
+ }
+
+ return srv;
+ }
+
+ return NULL;
+}
+
+/*
+ * Connect to a server, possibly on an existing connection
+ *
+ * Here, what we want to do is: If the server and username
+ * match an existing connection, reuse that, otherwise, establish a
+ * new connection.
+ *
+ * If we have to create a new connection, call the auth_fn to get the
+ * info we need, unless the username and password were passed in.
+ */
+
+SMBCSRV *
+SMBC_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ bool connect_if_not_found,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ SMBCSRV *srv=NULL;
+ struct cli_state *c;
+ struct nmb_name called, calling;
+ const char *server_n = server;
+ struct sockaddr_storage ss;
+ int tried_reverse = 0;
+ int port_try_first;
+ int port_try_next;
+ const char *username_used;
+ NTSTATUS status;
+
+ zero_addr(&ss);
+ ZERO_STRUCT(c);
+
+ if (server[0] == 0) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ /* Look for a cached connection */
+ srv = SMBC_find_server(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+
+ /*
+ * If we found a connection and we're only allowed one share per
+ * server...
+ */
+ if (srv && *share != '\0' && context->one_share_per_server) {
+
+ /*
+ * ... then if there's no current connection to the share,
+ * connect to it. SMBC_find_server(), or rather the function
+ * pointed to by context->cache.get_cached_srv_fn which
+ * was called by SMBC_find_server(), will have issued a tree
+ * disconnect if the requested share is not the same as the
+ * one that was already connected.
+ */
+ if (srv->cli->cnum == (uint16) -1) {
+ /* Ensure we have accurate auth info */
+ SMBC_call_auth_fn(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ cli_shutdown(srv->cli);
+ srv->cli = NULL;
+ (context->cache.remove_cached_server_fn)(context,
+ srv);
+ return NULL;
+ }
+
+ /*
+ * We don't need to renegotiate encryption
+ * here as the encryption context is not per
+ * tid.
+ */
+
+ if (!cli_send_tconX(srv->cli, share, "?????",
+ *pp_password,
+ strlen(*pp_password)+1)) {
+
+ errno = SMBC_errno(context, srv->cli);
+ cli_shutdown(srv->cli);
+ srv->cli = NULL;
+ (context->cache.remove_cached_server_fn)(context,
+ srv);
+ srv = NULL;
+ }
+
+ /*
+ * Regenerate the dev value since it's based on both
+ * server and share
+ */
+ if (srv) {
+ srv->dev = (dev_t)(str_checksum(server) ^
+ str_checksum(share));
+ }
+ }
+ }
+
+ /* If we have a connection... */
+ if (srv) {
+
+ /* ... then we're done here. Give 'em what they came for. */
+ return srv;
+ }
+
+ /* If we're not asked to connect when a connection doesn't exist... */
+ if (! connect_if_not_found) {
+ /* ... then we're done here. */
+ return NULL;
+ }
+
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ make_nmb_name(&calling, context->netbios_name, 0x0);
+ make_nmb_name(&called , server, 0x20);
+
+ DEBUG(4,("SMBC_server: server_n=[%s] server=[%s]\n", server_n, server));
+
+ DEBUG(4,(" -> server_n=[%s] server=[%s]\n", server_n, server));
+
+ again:
+
+ zero_addr(&ss);
+
+ /* have to open a new connection */
+ if ((c = cli_initialise()) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (context->use_kerberos) {
+ c->use_kerberos = True;
+ }
+ if (context->fallback_after_kerberos) {
+ c->fallback_after_kerberos = True;
+ }
+
+ c->timeout = context->timeout;
+
+ /*
+ * Force use of port 139 for first try if share is $IPC, empty, or
+ * null, so browse lists can work
+ */
+ if (share == NULL || *share == '\0' || strcmp(share, "IPC$") == 0) {
+ port_try_first = 139;
+ port_try_next = 445;
+ } else {
+ port_try_first = 445;
+ port_try_next = 139;
+ }
+
+ c->port = port_try_first;
+
+ status = cli_connect(c, server_n, &ss);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ /* First connection attempt failed. Try alternate port. */
+ c->port = port_try_next;
+
+ status = cli_connect(c, server_n, &ss);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(c);
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+ }
+
+ if (!cli_session_request(c, &calling, &called)) {
+ cli_shutdown(c);
+ if (strcmp(called.name, "*SMBSERVER")) {
+ make_nmb_name(&called , "*SMBSERVER", 0x20);
+ goto again;
+ } else { /* Try one more time, but ensure we don't loop */
+
+ /* Only try this if server is an IP address ... */
+
+ if (is_ipaddress(server) && !tried_reverse) {
+ fstring remote_name;
+ struct sockaddr_storage rem_ss;
+
+ if (!interpret_string_addr(&rem_ss, server,
+ NI_NUMERICHOST)) {
+ DEBUG(4, ("Could not convert IP address "
+ "%s to struct sockaddr_storage\n",
+ server));
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+
+ tried_reverse++; /* Yuck */
+
+ if (name_status_find("*", 0, 0, &rem_ss, remote_name)) {
+ make_nmb_name(&called, remote_name, 0x20);
+ goto again;
+ }
+ }
+ }
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+
+ DEBUG(4,(" session request ok\n"));
+
+ if (!cli_negprot(c)) {
+ cli_shutdown(c);
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+
+ username_used = *pp_username;
+
+ if (!NT_STATUS_IS_OK(cli_session_setup(c, username_used,
+ *pp_password, strlen(*pp_password),
+ *pp_password, strlen(*pp_password),
+ *pp_workgroup))) {
+
+ /* Failed. Try an anonymous login, if allowed by flags. */
+ username_used = "";
+
+ if (context->no_auto_anonymous_login ||
+ !NT_STATUS_IS_OK(cli_session_setup(c, username_used,
+ *pp_password, 1,
+ *pp_password, 0,
+ *pp_workgroup))) {
+
+ cli_shutdown(c);
+ errno = EPERM;
+ return NULL;
+ }
+ }
+
+ DEBUG(4,(" session setup ok\n"));
+
+ if (!cli_send_tconX(c, share, "?????",
+ *pp_password, strlen(*pp_password)+1)) {
+ errno = SMBC_errno(context, c);
+ cli_shutdown(c);
+ return NULL;
+ }
+
+ DEBUG(4,(" tconx ok\n"));
+
+ if (context->smb_encryption_level) {
+ /* Attempt UNIX smb encryption. */
+ if (!NT_STATUS_IS_OK(cli_force_encryption(c,
+ username_used,
+ *pp_password,
+ *pp_workgroup))) {
+
+ /*
+ * context->smb_encryption_level == 1
+ * means don't fail if encryption can't be negotiated,
+ * == 2 means fail if encryption can't be negotiated.
+ */
+
+ DEBUG(4,(" SMB encrypt failed\n"));
+
+ if (context->smb_encryption_level == 2) {
+ cli_shutdown(c);
+ errno = EPERM;
+ return NULL;
+ }
+ }
+ DEBUG(4,(" SMB encrypt ok\n"));
+ }
+
+ /*
+ * Ok, we have got a nice connection
+ * Let's allocate a server structure.
+ */
+
+ srv = SMB_MALLOC_P(SMBCSRV);
+ if (!srv) {
+ errno = ENOMEM;
+ goto failed;
+ }
+
+ ZERO_STRUCTP(srv);
+ srv->cli = c;
+ srv->dev = (dev_t)(str_checksum(server) ^ str_checksum(share));
+ srv->no_pathinfo = False;
+ srv->no_pathinfo2 = False;
+ srv->no_nt_session = False;
+
+ /* now add it to the cache (internal or external) */
+ /* Let the cache function set errno if it wants to */
+ errno = 0;
+ if ((context->cache.add_cached_server_fn)(context, srv,
+ server, share,
+ *pp_workgroup,
+ *pp_username)) {
+ int saved_errno = errno;
+ DEBUG(3, (" Failed to add server to cache\n"));
+ errno = saved_errno;
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ goto failed;
+ }
+
+ DEBUG(2, ("Server connect ok: //%s/%s: %p\n",
+ server, share, srv));
+
+ DLIST_ADD(context->servers, srv);
+ return srv;
+
+ failed:
+ cli_shutdown(c);
+ if (!srv) {
+ return NULL;
+ }
+
+ SAFE_FREE(srv);
+ return NULL;
+}
+
+/*
+ * Connect to a server for getting/setting attributes, possibly on an existing
+ * connection. This works similarly to SMBC_server().
+ */
+SMBCSRV *
+SMBC_attr_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ int flags;
+ struct sockaddr_storage ss;
+ struct cli_state *ipc_cli;
+ struct rpc_pipe_client *pipe_hnd;
+ NTSTATUS nt_status;
+ SMBCSRV *ipc_srv=NULL;
+
+ /*
+ * See if we've already created this special connection. Reference
+ * our "special" share name '*IPC$', which is an impossible real share
+ * name due to the leading asterisk.
+ */
+ ipc_srv = SMBC_find_server(ctx, context, server, "*IPC$",
+ pp_workgroup, pp_username, pp_password);
+ if (!ipc_srv) {
+
+ /* We didn't find a cached connection. Get the password */
+ if (!*pp_password || (*pp_password)[0] == '\0') {
+ /* ... then retrieve it now. */
+ SMBC_call_auth_fn(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ flags = 0;
+ if (context->use_kerberos) {
+ flags |= CLI_FULL_CONNECTION_USE_KERBEROS;
+ }
+
+ zero_addr(&ss);
+ nt_status = cli_full_connection(&ipc_cli,
+ global_myname(), server,
+ &ss, 0, "IPC$", "?????",
+ *pp_username,
+ *pp_workgroup,
+ *pp_password,
+ flags,
+ Undefined, NULL);
+ if (! NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1,("cli_full_connection failed! (%s)\n",
+ nt_errstr(nt_status)));
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ if (context->smb_encryption_level) {
+ /* Attempt UNIX smb encryption. */
+ if (!NT_STATUS_IS_OK(cli_force_encryption(ipc_cli,
+ *pp_username,
+ *pp_password,
+ *pp_workgroup))) {
+
+ /*
+ * context->smb_encryption_level ==
+ * 1 means don't fail if encryption can't be
+ * negotiated, == 2 means fail if encryption
+ * can't be negotiated.
+ */
+
+ DEBUG(4,(" SMB encrypt failed on IPC$\n"));
+
+ if (context->smb_encryption_level == 2) {
+ cli_shutdown(ipc_cli);
+ errno = EPERM;
+ return NULL;
+ }
+ }
+ DEBUG(4,(" SMB encrypt ok on IPC$\n"));
+ }
+
+ ipc_srv = SMB_MALLOC_P(SMBCSRV);
+ if (!ipc_srv) {
+ errno = ENOMEM;
+ cli_shutdown(ipc_cli);
+ return NULL;
+ }
+
+ ZERO_STRUCTP(ipc_srv);
+ ipc_srv->cli = ipc_cli;
+
+ pipe_hnd = cli_rpc_pipe_open_noauth(ipc_srv->cli,
+ PI_LSARPC,
+ &nt_status);
+ if (!pipe_hnd) {
+ DEBUG(1, ("cli_nt_session_open fail!\n"));
+ errno = ENOTSUP;
+ cli_shutdown(ipc_srv->cli);
+ free(ipc_srv);
+ return NULL;
+ }
+
+ /*
+ * Some systems don't support
+ * SEC_RIGHTS_MAXIMUM_ALLOWED, but NT sends 0x2000000
+ * so we might as well do it too.
+ */
+
+ nt_status = rpccli_lsa_open_policy(
+ pipe_hnd,
+ talloc_tos(),
+ True,
+ GENERIC_EXECUTE_ACCESS,
+ &ipc_srv->pol);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ errno = SMBC_errno(context, ipc_srv->cli);
+ cli_shutdown(ipc_srv->cli);
+ return NULL;
+ }
+
+ /* now add it to the cache (internal or external) */
+
+ errno = 0; /* let cache function set errno if it likes */
+ if ((context->cache.add_cached_server_fn)(context, ipc_srv,
+ server,
+ "*IPC$",
+ *pp_workgroup,
+ *pp_username)) {
+ DEBUG(3, (" Failed to add server to cache\n"));
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ cli_shutdown(ipc_srv->cli);
+ free(ipc_srv);
+ return NULL;
+ }
+
+ DLIST_ADD(context->servers, ipc_srv);
+ }
+
+ return ipc_srv;
+}