/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Functions to open and close named pipes. These functions are * described in the CIFS 1.0 Protocol Specification (December 19, 1997). */ #include <alloca.h> #include <pthread.h> #include <string.h> #include <strings.h> #include <syslog.h> #include <synch.h> #include <smbsrv/libsmbrdr.h> #include <smbsrv/ntstatus.h> #include <smbrdr.h> static int smbrdr_close(struct sdb_ofile *); static DWORD smbrdr_ntcreatex(struct sdb_ofile *); static struct sdb_ofile *smbrdr_ofile_alloc(struct sdb_netuse *, char *); static void smbrdr_ofile_clear(struct sdb_ofile *ofile) { bzero(ofile, sizeof (struct sdb_ofile) - sizeof (mutex_t)); } static void smbrdr_ofile_free(struct sdb_ofile *ofile) { smbrdr_ofile_clear(ofile); (void) mutex_unlock(&ofile->mtx); } /* * The ofile table. */ static struct sdb_ofile ofile_table[N_OFILE_TABLE]; static int smbrdr_pipe_recon_wait = 50; static int smbrdr_pipe_recon_tries = 3; /* * smbrdr_open_pipe * * Open an RPC pipe on hostname. On success, return the fid. Otherwise * returns a -ve error code. */ int smbrdr_open_pipe(char *hostname, char *domain, char *username, char *pipename) { struct sdb_netuse *netuse; struct sdb_ofile *ofile; unsigned short tid; DWORD status; int retry; struct timespec st; int i; if (smbrdr_logon(hostname, domain, username) != 0) return (-1); /* * If a stale session is detected, we will attempt to establish a new * session. */ for (i = 0; i < 2; i++) { status = smbrdr_tree_connect(hostname, username, "IPC$", &tid); if (i == 0 && status == NT_STATUS_UNEXPECTED_NETWORK_ERROR) { if (smbrdr_logon(hostname, domain, username) != 0) return (-1); continue; } if (status != NT_STATUS_SUCCESS) { syslog(LOG_DEBUG, "smbrdr: (open) %s %s %s %s %s", hostname, domain, username, pipename, xlate_nt_status(status)); return (-1); } break; } netuse = smbrdr_netuse_get(tid); if (netuse == NULL) { syslog(LOG_DEBUG, "smbrdr: (open) %s %s %s %s %s", hostname, domain, username, pipename, xlate_nt_status(NT_STATUS_CONNECTION_INVALID)); return (-1); } if ((ofile = smbrdr_ofile_alloc(netuse, pipename)) == 0) { syslog(LOG_DEBUG, "smbrdr: (open) %s %s %s %s %s", hostname, domain, username, pipename, xlate_nt_status(NT_STATUS_INSUFFICIENT_RESOURCES)); (void) smbrdr_tdcon(netuse); smbrdr_netuse_put(netuse); return (-1); } status = NT_STATUS_OPEN_FAILED; for (retry = 0; retry < smbrdr_pipe_recon_tries; retry++) { status = smbrdr_ntcreatex(ofile); switch (status) { case NT_STATUS_SUCCESS: (void) mutex_unlock(&ofile->mtx); smbrdr_netuse_put(netuse); return (ofile->fid); case NT_STATUS_PIPE_NOT_AVAILABLE: case NT_STATUS_PIPE_BUSY: /* * The server might return this error if it is * temporarily busy or unable to create a pipe. * We wait here before trying again to see if * the pipe becomes available. */ st.tv_sec = 0; st.tv_nsec = smbrdr_pipe_recon_wait * 1000000; (void) nanosleep(&st, 0); break; default: /* * Something else went wrong: no more retries. */ retry = smbrdr_pipe_recon_tries; break; } } syslog(LOG_DEBUG, "smbrdr: (open) %s %s %s %s %s", hostname, domain, username, pipename, xlate_nt_status(status)); smbrdr_ofile_free(ofile); (void) smbrdr_tdcon(netuse); smbrdr_netuse_put(netuse); return (-1); } /* * smbrdr_close_pipe * * Close the named pipe represented by fid. */ int smbrdr_close_pipe(int fid) { struct sdb_ofile *ofile; unsigned short tid; int rc; if ((ofile = smbrdr_ofile_get(fid)) == NULL) return (-1); tid = ofile->tid; rc = smbrdr_close(ofile); smbrdr_ofile_put(ofile); (void) smbrdr_tree_disconnect(tid); return (rc); } /* * smbrdr_ofile_put * * Unlock given ofile structure. */ void smbrdr_ofile_put(struct sdb_ofile *ofile) { if (ofile) (void) mutex_unlock(&ofile->mtx); } /* * smbrdr_ofile_get * * Locate the ofile for the specified fid. Just to be safe, ensure that * the netuse pointer is valid. Return a pointer to the ofile structure. * Return a null pointer if a valid ofile cannot be found. */ struct sdb_ofile * smbrdr_ofile_get(int fid) { struct sdb_session *session; struct sdb_netuse *netuse; struct sdb_ofile *ofile; int i; for (i = 0; i < N_OFILE_TABLE; ++i) { ofile = &ofile_table[i]; (void) mutex_lock(&ofile->mtx); if (ofile->fid == fid) { session = ofile->session; netuse = ofile->netuse; /* * status check: * make sure all the structures are in the right state */ if (session && netuse && (ofile->state == SDB_FSTATE_OPEN) && (netuse->state == SDB_NSTATE_CONNECTED) && (session->logon.state == SDB_LSTATE_SETUP) && (session->state == SDB_SSTATE_NEGOTIATED)) { /* sanity check */ if ((ofile->sid == session->sid) && (ofile->uid == session->logon.uid) && (ofile->tid == netuse->tid)) { return (ofile); } else { /* invalid structure */ smbrdr_ofile_clear(ofile); } } } (void) mutex_unlock(&ofile->mtx); } return (NULL); } /* * smbrdr_ofile_end_of_share * * This function can be used when closing a share to ensure that all * ofiles resources are released. Don't call smbrdr_close_pipe because * that will disconnect the tree and we don't know what state * the share is in. The server will probably close all files anyway. * We are more interested in releasing the ofile resources. */ void smbrdr_ofile_end_of_share(unsigned short tid) { struct sdb_ofile *ofile; int i; for (i = 0; i < N_OFILE_TABLE; ++i) { ofile = &ofile_table[i]; (void) mutex_lock(&ofile->mtx); if (ofile->tid == tid) (void) smbrdr_close(ofile); (void) mutex_unlock(&ofile->mtx); } } /* * smbrdr_dump_ofiles * * Dump the open files table. */ void smbrdr_dump_ofiles() { struct sdb_ofile *ofile; struct sdb_netuse *netuse; int i; for (i = 0; i < N_OFILE_TABLE; ++i) { ofile = &ofile_table[i]; (void) mutex_lock(&ofile->mtx); netuse = ofile->netuse; if (netuse) { syslog(LOG_DEBUG, "file[%d]: %s (fid=%d)", i, ofile->path, ofile->fid); syslog(LOG_DEBUG, "file[%d]: session(%d), user(%d), tree(%d)", i, netuse->session->sock, netuse->uid, netuse->tid); } (void) mutex_unlock(&ofile->mtx); } } /* * Private Functions */ /* * smbrdr_close * * Send SMBClose request for the given open file. */ static int smbrdr_close(struct sdb_ofile *ofile) { struct sdb_session *session; struct sdb_netuse *netuse; struct sdb_logon *logon; smbrdr_handle_t srh; smb_hdr_t smb_hdr; DWORD status; int fid; int rc; if (ofile == NULL) return (0); ofile->state = SDB_FSTATE_CLOSING; if ((session = ofile->session) == NULL) { smbrdr_ofile_clear(ofile); return (0); } if ((session->state != SDB_SSTATE_NEGOTIATED) && (session->state != SDB_SSTATE_DISCONNECTING)) { smbrdr_ofile_clear(ofile); return (0); } fid = ofile->fid; netuse = ofile->netuse; logon = &session->logon; status = smbrdr_request_init(&srh, SMB_COM_CLOSE, session, logon, netuse); if (status != NT_STATUS_SUCCESS) { smbrdr_ofile_clear(ofile); return (-1); } rc = smb_msgbuf_encode(&srh.srh_mbuf, "bwlw.", 3, fid, 0x00000000ul, 0); if (rc <= 0) { smbrdr_handle_free(&srh); smbrdr_ofile_clear(ofile); return (-1); } status = smbrdr_exchange(&srh, &smb_hdr, 0); if (status != NT_STATUS_SUCCESS) syslog(LOG_DEBUG, "smbrdr_close: %s", xlate_nt_status(status)); smbrdr_handle_free(&srh); smbrdr_ofile_clear(ofile); return (0); } /* * smbrdr_ofile_alloc * * Allocate an ofile for the specified name. File info is associated * with a share so we need a valid share before calling this function. * If a slot is already allocated to the specified file, a pointer to * that slot is returned. Otherwise we allocate and initialize a new * slot in the table. If the table is full, a null pointer will be * returned. */ static struct sdb_ofile * smbrdr_ofile_alloc(struct sdb_netuse *netuse, char *name) { struct sdb_ofile *ofile; int i; for (i = 0; i < N_OFILE_TABLE; ++i) { ofile = &ofile_table[i]; (void) mutex_lock(&ofile->mtx); if (ofile->netuse == 0) { ofile->session = netuse->session; ofile->netuse = netuse; ofile->sid = netuse->session->sid; ofile->uid = netuse->session->logon.uid; ofile->tid = netuse->tid; ofile->fid = 0; (void) strcpy(ofile->path, name); ofile->state = SDB_FSTATE_INIT; return (ofile); } (void) mutex_unlock(&ofile->mtx); } return (NULL); } /* * smbrdr_ntcreatex * * This will do an SMB_COM_NT_CREATE_ANDX with lots of default values. * All of the underlying session and share data should already be set * up before we get here. If everything works we'll get a valid fid. */ static DWORD smbrdr_ntcreatex(struct sdb_ofile *ofile) { struct sdb_logon *logon; struct sdb_netuse *netuse; struct sdb_session *sess; smbrdr_handle_t srh; smb_hdr_t smb_hdr; smb_msgbuf_t *mb; char *path; unsigned path_len; int data_bytes; int rc; unsigned short fid; int null_size; DWORD status; netuse = ofile->netuse; sess = netuse->session; logon = &sess->logon; /* * If this was a general purpose interface, we should support * full UNC semantics but we only use this for RPC over named * pipes with well-known endpoints. */ path_len = strlen(ofile->path) + 2; path = alloca(path_len); if (ofile->path[0] != '\\') (void) snprintf(path, path_len, "\\%s", ofile->path); else (void) strcpy(path, ofile->path); if (sess->remote_caps & CAP_UNICODE) { path_len = smb_wcequiv_strlen(path); null_size = sizeof (smb_wchar_t); } else { path_len = strlen(path); null_size = sizeof (char); } syslog(LOG_DEBUG, "smbrdr_ntcreatex: %d %s", path_len, path); status = smbrdr_request_init(&srh, SMB_COM_NT_CREATE_ANDX, sess, logon, netuse); if (status != NT_STATUS_SUCCESS) { syslog(LOG_DEBUG, "smbrdr_ntcreatex: %s", xlate_nt_status(status)); return (NT_STATUS_INVALID_PARAMETER_1); } mb = &srh.srh_mbuf; data_bytes = path_len + null_size; rc = smb_msgbuf_encode(mb, "(wct)b (andx)b1.w (resv). (nlen)w (flg)l" "(rdf)l (dacc)l (allo)q (efa)l (shr)l (cdisp)l (copt)l (impl)l" "(secf)b (bcc)w (name)u", 24, /* smb_wct */ 0xff, /* AndXCommand (none) */ 0x0000, /* AndXOffset */ path_len, /* Unicode NameLength */ 0x00000006ul, /* Flags (oplocks) */ 0, /* RootDirectoryFid */ 0x0002019Ful, /* DesiredAccess */ 0x0ull, /* AllocationSize */ 0x00000000ul, /* ExtFileAttributes */ 0x00000003ul, /* ShareAccess (RW) */ 0x00000001ul, /* CreateDisposition (OpenExisting) */ 0x00000000ul, /* CreateOptions */ 0x00000002ul, /* ImpersonationLevel */ 0x01u, /* SecurityFlags */ data_bytes, /* smb_bcc */ path); /* Name */ if (rc <= 0) { smbrdr_handle_free(&srh); return (NT_STATUS_INVALID_PARAMETER_1); } status = smbrdr_exchange(&srh, &smb_hdr, 0); if (status != NT_STATUS_SUCCESS) { smbrdr_handle_free(&srh); return (NT_SC_VALUE(status)); } rc = smb_msgbuf_decode(mb, "(wct). (andx)4. (opl)1. (fid)w", &fid); if (rc <= 0) { smbrdr_handle_free(&srh); return (NT_STATUS_INVALID_PARAMETER_2); } ofile->fid = fid; ofile->state = SDB_FSTATE_OPEN; syslog(LOG_DEBUG, "SmbRdrNtCreate: fid=%d", ofile->fid); smbrdr_handle_free(&srh); return (NT_STATUS_SUCCESS); }