libsmb: Add "flags" to cli_smb2_close_fnum_send()
[samba.git] / source3 / libsmb / clidfs.c
index a2c6f5fe5eca18ac9fe92b043d1cc50347ec731b..1437844b4271836958c074875361624a6b2f676e 100644 (file)
@@ -27,6 +27,7 @@
 #include "libsmb/nmblib.h"
 #include "../libcli/smb/smbXcli_base.h"
 #include "auth/credentials/credentials.h"
+#include "lib/param/param.h"
 
 /********************************************************************
  Important point.
@@ -50,6 +51,7 @@ static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
        uint16_t major, minor;
        uint32_t caplow, caphigh;
        NTSTATUS status;
+       bool temp_ipc = false;
 
        if (smbXcli_conn_protocol(c->conn) >= PROTOCOL_SMB2_02) {
                status = smb2cli_session_encryption_on(c->smb2.session);
@@ -72,12 +74,26 @@ static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
                return NT_STATUS_NOT_SUPPORTED;
        }
 
+       if (c->smb1.tcon == NULL) {
+               status = cli_tree_connect_creds(c, "IPC$", "IPC", creds);
+               if (!NT_STATUS_IS_OK(status)) {
+                       d_printf("Encryption required and "
+                               "can't connect to IPC$ to check "
+                               "UNIX CIFS extensions.\n");
+                       return NT_STATUS_UNKNOWN_REVISION;
+               }
+               temp_ipc = true;
+       }
+
        status = cli_unix_extensions_version(c, &major, &minor, &caplow,
                                             &caphigh);
        if (!NT_STATUS_IS_OK(status)) {
                d_printf("Encryption required and "
                        "can't get UNIX CIFS extensions "
                        "version from server.\n");
+               if (temp_ipc) {
+                       cli_tdis(c);
+               }
                return NT_STATUS_UNKNOWN_REVISION;
        }
 
@@ -85,6 +101,9 @@ static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
                d_printf("Encryption required and "
                        "share %s doesn't support "
                        "encryption.\n", sharename);
+               if (temp_ipc) {
+                       cli_tdis(c);
+               }
                return NT_STATUS_UNSUPPORTED_COMPRESSION;
        }
 
@@ -93,9 +112,15 @@ static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
                d_printf("Encryption required and "
                        "setup failed with error %s.\n",
                        nt_errstr(status));
+               if (temp_ipc) {
+                       cli_tdis(c);
+               }
                return status;
        }
 
+       if (temp_ipc) {
+               cli_tdis(c);
+       }
        return NT_STATUS_OK;
 }
 
@@ -107,7 +132,6 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
                                        const char *server,
                                        const char *share,
                                        struct cli_credentials *creds,
-                                       int max_protocol,
                                        const struct sockaddr_storage *dest_ss,
                                        int port,
                                        int name_type,
@@ -158,7 +182,7 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
 
        if (!NT_STATUS_IS_OK(status)) {
                if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
-                       DBG_ERR("NetBIOS support disabled, unable to connect");
+                       DBG_ERR("NetBIOS support disabled, unable to connect\n");
                }
 
                DBG_WARNING("Connection to %s failed (Error %s)\n",
@@ -167,17 +191,27 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
                return status;
        }
 
-       if (max_protocol == 0) {
-               max_protocol = PROTOCOL_LATEST;
-       }
        DEBUG(4,(" session request ok\n"));
 
-       status = smbXcli_negprot(c->conn, c->timeout,
+       status = smbXcli_negprot(c->conn,
+                                c->timeout,
                                 lp_client_min_protocol(),
-                                max_protocol);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               d_printf("protocol negotiation failed: %s\n",
+                                lp_client_max_protocol(),
+                                NULL,
+                                NULL,
+                                NULL);
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+               d_printf("Protocol negotiation (with timeout %d ms) timed out against server %s\n",
+                        c->timeout,
+                        smbXcli_conn_remote_name(c->conn));
+               cli_shutdown(c);
+               return status;
+       } else if (!NT_STATUS_IS_OK(status)) {
+               d_printf("Protocol negotiation to server %s (for a protocol between %s and %s) failed: %s\n",
+                        smbXcli_conn_remote_name(c->conn),
+                        lpcfg_get_smb_protocol(lp_client_min_protocol()),
+                        lpcfg_get_smb_protocol(lp_client_max_protocol()),
                         nt_errstr(status));
                cli_shutdown(c);
                return status;
@@ -221,6 +255,22 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
 
        DEBUG(4,(" session setup ok\n"));
 
+       if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+               status = cli_cm_force_encryption_creds(c,
+                                                      creds,
+                                                      sharename);
+               if (!NT_STATUS_IS_OK(status)) {
+                       switch (encryption_state) {
+                       case SMB_ENCRYPTION_DESIRED:
+                               break;
+                       case SMB_ENCRYPTION_REQUIRED:
+                       default:
+                               cli_shutdown(c);
+                               return status;
+                       }
+               }
+       }
+
        /* here's the fun part....to support 'msdfs proxy' shares
           (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL
           here before trying to connect to the original share.
@@ -233,7 +283,6 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
                cli_shutdown(c);
                return do_connect(ctx, newserver,
                                newshare, creds,
-                               max_protocol,
                                NULL, port, name_type, pcli);
        }
 
@@ -246,43 +295,11 @@ static NTSTATUS do_connect(TALLOC_CTX *ctx,
                return status;
        }
 
-       if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
-               status = cli_cm_force_encryption_creds(c,
-                                                      creds,
-                                                      sharename);
-               if (!NT_STATUS_IS_OK(status)) {
-                       switch (encryption_state) {
-                       case SMB_ENCRYPTION_DESIRED:
-                               break;
-                       case SMB_ENCRYPTION_REQUIRED:
-                       default:
-                               cli_shutdown(c);
-                               return status;
-                       }
-               }
-       }
-
        DEBUG(4,(" tconx ok\n"));
        *pcli = c;
        return NT_STATUS_OK;
 }
 
-/****************************************************************************
-****************************************************************************/
-
-static void cli_set_mntpoint(struct cli_state *cli, const char *mnt)
-{
-       TALLOC_CTX *frame = talloc_stackframe();
-       char *name = clean_name(frame, mnt);
-       if (!name) {
-               TALLOC_FREE(frame);
-               return;
-       }
-       TALLOC_FREE(cli->dfs_mountpoint);
-       cli->dfs_mountpoint = talloc_strdup(cli, name);
-       TALLOC_FREE(frame);
-}
-
 /********************************************************************
  Add a new connection to the list.
  referring_cli == NULL means a new initial connection.
@@ -293,7 +310,6 @@ static NTSTATUS cli_cm_connect(TALLOC_CTX *ctx,
                               const char *server,
                               const char *share,
                               struct cli_credentials *creds,
-                              int max_protocol,
                               const struct sockaddr_storage *dest_ss,
                               int port,
                               int name_type,
@@ -304,7 +320,6 @@ static NTSTATUS cli_cm_connect(TALLOC_CTX *ctx,
 
        status = do_connect(ctx, server, share,
                                creds,
-                               max_protocol,
                                dest_ss, port, name_type, &cli);
 
        if (!NT_STATUS_IS_OK(status)) {
@@ -384,19 +399,17 @@ static struct cli_state *cli_cm_find(struct cli_state *cli,
 ****************************************************************************/
 
 NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
-                               struct cli_state *referring_cli,
-                               const char *server,
-                               const char *share,
-                               const struct user_auth_info *auth_info,
-                               int max_protocol,
-                               const struct sockaddr_storage *dest_ss,
-                               int port,
-                               int name_type,
-                               struct cli_state **pcli)
+                    struct cli_state *referring_cli,
+                    const char *server,
+                    const char *share,
+                    struct cli_credentials *creds,
+                    const struct sockaddr_storage *dest_ss,
+                    int port,
+                    int name_type,
+                    struct cli_state **pcli)
 {
        /* Try to reuse an existing connection in this list. */
        struct cli_state *c = cli_cm_find(referring_cli, server, share);
-       struct cli_credentials *creds = get_cmdline_auth_info_creds(auth_info);
        NTSTATUS status;
 
        if (c) {
@@ -404,11 +417,11 @@ NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
                return NT_STATUS_OK;
        }
 
-       if (auth_info == NULL) {
+       if (creds == NULL) {
                /* Can't do a new connection
                 * without auth info. */
                d_printf("cli_cm_open() Unable to open connection [\\%s\\%s] "
-                       "without auth info\n",
+                       "without client credentials\n",
                        server, share );
                return NT_STATUS_INVALID_PARAMETER;
        }
@@ -418,7 +431,6 @@ NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
                                server,
                                share,
                                creds,
-                               max_protocol,
                                dest_ss,
                                port,
                                name_type,
@@ -443,29 +455,6 @@ void cli_cm_display(struct cli_state *cli)
        }
 }
 
-/****************************************************************************
-****************************************************************************/
-
-/****************************************************************************
-****************************************************************************/
-
-#if 0
-void cli_cm_set_credentials(struct user_auth_info *auth_info)
-{
-       SAFE_FREE(cm_creds.username);
-       cm_creds.username = SMB_STRDUP(get_cmdline_auth_info_username(
-                                              auth_info));
-
-       if (get_cmdline_auth_info_got_pass(auth_info)) {
-               cm_set_password(get_cmdline_auth_info_password(auth_info));
-       }
-
-       cm_creds.use_kerberos = get_cmdline_auth_info_use_kerberos(auth_info);
-       cm_creds.fallback_after_kerberos = false;
-       cm_creds.signing_state = get_cmdline_auth_info_signing_state(auth_info);
-}
-#endif
-
 /**********************************************************************
  split a dfs path into the server, share name, and extrapath components
 **********************************************************************/
@@ -614,21 +603,40 @@ static char *cli_dfs_make_full_path(TALLOC_CTX *ctx,
 }
 
 /********************************************************************
- check for dfs referral
+ Check if a path has already been converted to DFS.
 ********************************************************************/
 
-static bool cli_dfs_check_error(struct cli_state *cli, NTSTATUS expected,
-                               NTSTATUS status)
+bool cli_dfs_is_already_full_path(struct cli_state *cli, const char *path)
 {
-       /* only deal with DS when we negotiated NT_STATUS codes and UNICODE */
-
-       if (!(smbXcli_conn_use_unicode(cli->conn))) {
+       const char *server = smbXcli_conn_remote_name(cli->conn);
+       size_t server_len = strlen(server);
+       bool found_server = false;
+       const char *share = cli->share;
+       size_t share_len = strlen(share);
+       bool found_share = false;
+
+       if (!IS_DIRECTORY_SEP(path[0])) {
                return false;
        }
-       if (!(smb1cli_conn_capabilities(cli->conn) & CAP_STATUS32)) {
+       path++;
+       found_server = (strncasecmp_m(path, server, server_len) == 0);
+       if (!found_server) {
                return false;
        }
-       if (NT_STATUS_EQUAL(status, expected)) {
+       path += server_len;
+       if (!IS_DIRECTORY_SEP(path[0])) {
+               return false;
+       }
+       path++;
+       found_share = (strncasecmp_m(path, share, share_len) == 0);
+       if (!found_share) {
+               return false;
+       }
+       path += share_len;
+       if (path[0] == '\0') {
+               return true;
+       }
+       if (IS_DIRECTORY_SEP(path[0])) {
                return true;
        }
        return false;
@@ -850,6 +858,31 @@ NTSTATUS cli_dfs_get_referral(TALLOC_CTX *ctx,
                                consumed);
 }
 
+static bool cli_conn_have_dfs(struct cli_state *cli)
+{
+       struct smbXcli_conn *conn = cli->conn;
+       struct smbXcli_tcon *tcon = NULL;
+       bool ok;
+
+       if (smbXcli_conn_protocol(conn) < PROTOCOL_SMB2_02) {
+               uint32_t capabilities = smb1cli_conn_capabilities(conn);
+
+               if ((capabilities & CAP_STATUS32) == 0) {
+                       return false;
+               }
+               if ((capabilities & CAP_UNICODE) == 0) {
+                       return false;
+               }
+
+               tcon = cli->smb1.tcon;
+       } else {
+               tcon = cli->smb2.tcon;
+       }
+
+       ok = smbXcli_tcon_is_dfs_share(tcon);
+       return ok;
+}
+
 /********************************************************************
 ********************************************************************/
 struct cli_dfs_path_split {
@@ -860,7 +893,7 @@ struct cli_dfs_path_split {
 
 NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
                          const char *mountpt,
-                         const struct user_auth_info *dfs_auth_info,
+                         struct cli_credentials *creds,
                          struct cli_state *rootcli,
                          const char *path,
                          struct cli_state **targetcli,
@@ -883,23 +916,15 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
        SMB_STRUCT_STAT sbuf;
        uint32_t attributes;
        NTSTATUS status;
-       struct smbXcli_tcon *root_tcon = NULL;
        struct smbXcli_tcon *target_tcon = NULL;
        struct cli_dfs_path_split *dfs_refs = NULL;
-       struct cli_credentials *creds = get_cmdline_auth_info_creds(dfs_auth_info);
+       bool ok;
+       bool is_already_dfs = false;
 
        if ( !rootcli || !path || !targetcli ) {
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       /* Don't do anything if this is not a DFS root. */
-
-       if (smbXcli_conn_protocol(rootcli->conn) >= PROTOCOL_SMB2_02) {
-               root_tcon = rootcli->smb2.tcon;
-       } else {
-               root_tcon = rootcli->smb1.tcon;
-       }
-
        /*
         * Avoid more than one leading directory separator
         */
@@ -907,7 +932,8 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
                path++;
        }
 
-       if (!smbXcli_tcon_is_dfs_share(root_tcon)) {
+       ok = cli_conn_have_dfs(rootcli);
+       if (!ok) {
                *targetcli = rootcli;
                *pp_targetpath = talloc_strdup(ctx, path);
                if (!*pp_targetpath) {
@@ -918,6 +944,24 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
 
        *targetcli = NULL;
 
+       is_already_dfs = cli_dfs_is_already_full_path(rootcli, path);
+       if (is_already_dfs) {
+               const char *localpath = NULL;
+               /*
+                * Given path is already converted to DFS.
+                * Convert to a local path so clean_path()
+                * can correctly strip any wildcards.
+                */
+               status = cli_dfs_target_check(ctx,
+                                             rootcli,
+                                             path,
+                                             &localpath);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+               path = localpath;
+       }
+
        /* Send a trans2_query_path_info to check for a referral. */
 
        cleanpath = clean_path(ctx, path);
@@ -943,8 +987,7 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
 
        /* Special case where client asked for a path that does not exist */
 
-       if (cli_dfs_check_error(rootcli, NT_STATUS_OBJECT_NAME_NOT_FOUND,
-                               status)) {
+       if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
                *targetcli = rootcli;
                *pp_targetpath = talloc_strdup(ctx, path);
                if (!*pp_targetpath) {
@@ -955,8 +998,7 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
 
        /* We got an error, check for DFS referral. */
 
-       if (!cli_dfs_check_error(rootcli, NT_STATUS_PATH_NOT_COVERED,
-                                status)) {
+       if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
                return status;
        }
 
@@ -966,8 +1008,7 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
                             rootcli,
                             smbXcli_conn_remote_name(rootcli->conn),
                             "IPC$",
-                            dfs_auth_info,
-                            smbXcli_conn_protocol(rootcli->conn),
+                            creds,
                             NULL, /* dest_ss not needed, we reuse the transport */
                             0,
                             0x20,
@@ -987,7 +1028,7 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
        }
 
        /*
-        * Bug#10123 - DFS referal entries can be provided in a random order,
+        * Bug#10123 - DFS referral entries can be provided in a random order,
         * so check the connection cache for each item to avoid unnecessary
         * reconnections.
         */
@@ -1024,7 +1065,6 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
                                dfs_refs[count].server,
                                dfs_refs[count].share,
                                creds,
-                               smbXcli_conn_protocol(rootcli->conn),
                                NULL, /* dest_ss */
                                0, /* port */
                                0x20,
@@ -1070,7 +1110,7 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
 
        if (extrapath && strlen(extrapath) > 0) {
                /* EMC Celerra NAS version 5.6.50 (at least) doesn't appear to */
-               /* put the trailing \ on the path, so to be save we put one in if needed */
+               /* put the trailing \ on the path, so to be safe we put one in if needed */
                if (extrapath[strlen(extrapath)-1] != '\\' && **pp_targetpath != '\\') {
                        *pp_targetpath = talloc_asprintf(ctx,
                                                  "%s\\%s",
@@ -1123,15 +1163,13 @@ NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
                return NT_STATUS_NOT_FOUND;
        }
 
-       cli_set_mntpoint(*targetcli, newmount);
-
        /* Check for another dfs referral, note that we are not
           checking for loops here. */
 
        if (!strequal(*pp_targetpath, "\\") && !strequal(*pp_targetpath, "/")) {
                status = cli_resolve_path(ctx,
                                          newmount,
-                                         dfs_auth_info,
+                                         creds,
                                          *targetcli,
                                          *pp_targetpath,
                                          &newcli,
@@ -1191,6 +1229,7 @@ bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
        char *fullpath = NULL;
        bool res;
        struct smbXcli_tcon *orig_tcon = NULL;
+       char *orig_share = NULL;
        char *newextrapath = NULL;
        NTSTATUS status;
        const char *remote_name;
@@ -1218,16 +1257,13 @@ bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
 
        /* Store tcon state. */
        if (cli_state_has_tcon(cli)) {
-               orig_tcon = cli_state_save_tcon(cli);
-               if (orig_tcon == NULL) {
-                       return false;
-               }
+               cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
        }
 
        /* check for the referral */
 
        if (!NT_STATUS_IS_OK(cli_tree_connect(cli, "IPC$", "IPC", NULL))) {
-               cli_state_restore_tcon(cli, orig_tcon);
+               cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
                return false;
        }
 
@@ -1239,7 +1275,16 @@ bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
                                break;
                        case SMB_ENCRYPTION_REQUIRED:
                        default:
-                               cli_state_restore_tcon(cli, orig_tcon);
+                               /*
+                                * Failed to set up encryption.
+                                * Disconnect the temporary IPC$
+                                * tcon before restoring the original
+                                * tcon so we don't leak it.
+                                */
+                               cli_tdis(cli);
+                               cli_state_restore_tcon_share(cli,
+                                                            orig_tcon,
+                                                            orig_share);
                                return false;
                        }
                }
@@ -1251,7 +1296,7 @@ bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
 
        status = cli_tdis(cli);
 
-       cli_state_restore_tcon(cli, orig_tcon);
+       cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
 
        if (!NT_STATUS_IS_OK(status)) {
                return false;
@@ -1279,3 +1324,109 @@ bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
 
        return true;
 }
+
+/********************************************************************
+ Windows and NetApp (and arguably the SMB1/2/3 specs) expect a non-DFS
+ path for the targets of rename and hardlink. If we have been given
+ a DFS path for these calls, convert it back into a local path by
+ stripping off the DFS prefix.
+********************************************************************/
+
+NTSTATUS cli_dfs_target_check(TALLOC_CTX *mem_ctx,
+                       struct cli_state *cli,
+                       const char *fname_dst,
+                       const char **fname_dst_out)
+{
+       char *dfs_prefix = NULL;
+       size_t prefix_len = 0;
+       struct smbXcli_tcon *tcon = NULL;
+
+       if (!smbXcli_conn_dfs_supported(cli->conn)) {
+               goto copy_fname_out;
+       }
+       if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+               tcon = cli->smb2.tcon;
+       } else {
+               tcon = cli->smb1.tcon;
+       }
+       if (!smbXcli_tcon_is_dfs_share(tcon)) {
+               goto copy_fname_out;
+       }
+       dfs_prefix = cli_dfs_make_full_path(mem_ctx, cli, "");
+       if (dfs_prefix == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       prefix_len = strlen(dfs_prefix);
+       if (strncmp(fname_dst, dfs_prefix, prefix_len) != 0) {
+               /*
+                * Prefix doesn't match. Assume it was
+                * already stripped or not added in the
+                * first place.
+                */
+               goto copy_fname_out;
+       }
+       /* Return the trailing name after the prefix. */
+       *fname_dst_out = &fname_dst[prefix_len];
+       TALLOC_FREE(dfs_prefix);
+       return NT_STATUS_OK;
+
+  copy_fname_out:
+
+       /*
+        * No change to the destination name. Just
+        * point it at the incoming destination name.
+        */
+       *fname_dst_out = fname_dst;
+       TALLOC_FREE(dfs_prefix);
+       return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Convert a pathname into a DFS path if it hasn't already been converted.
+ Always returns a talloc'ed path, makes it easy to pass const paths in.
+********************************************************************/
+
+char *smb1_dfs_share_path(TALLOC_CTX *ctx,
+                         struct cli_state *cli,
+                         const char *path)
+{
+       bool is_dfs = smbXcli_conn_dfs_supported(cli->conn) &&
+                       smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+       bool is_already_dfs_path = false;
+       bool posix = (cli->requested_posix_capabilities &
+                       CIFS_UNIX_POSIX_PATHNAMES_CAP);
+       char sepchar = (posix ? '/' : '\\');
+
+       if (!is_dfs) {
+               return talloc_strdup(ctx, path);
+       }
+       is_already_dfs_path = cli_dfs_is_already_full_path(cli, path);
+       if (is_already_dfs_path) {
+               return talloc_strdup(ctx, path);
+       }
+       /*
+        * We don't use cli_dfs_make_full_path() as,
+        * when given a null path, cli_dfs_make_full_path
+        * deliberately adds a trailing '\\' (this is by
+        * design to check for an existing DFS prefix match).
+        */
+       if (path[0] == '\0') {
+               return talloc_asprintf(ctx,
+                                      "%c%s%c%s",
+                                      sepchar,
+                                      smbXcli_conn_remote_name(cli->conn),
+                                      sepchar,
+                                      cli->share);
+       }
+       while (*path == sepchar) {
+               path++;
+       }
+       return talloc_asprintf(ctx,
+                              "%c%s%c%s%c%s",
+                              sepchar,
+                              smbXcli_conn_remote_name(cli->conn),
+                              sepchar,
+                              cli->share,
+                              sepchar,
+                              path);
+}