summaryrefslogtreecommitdiff
path: root/source3/smbd
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd')
-rw-r--r--source3/smbd/aio.c832
-rw-r--r--source3/smbd/blocking.c931
-rw-r--r--source3/smbd/change_trust_pw.c101
-rw-r--r--source3/smbd/chgpasswd.c1208
-rw-r--r--source3/smbd/close.c772
-rw-r--r--source3/smbd/conn.c329
-rw-r--r--source3/smbd/connection.c332
-rw-r--r--source3/smbd/dfree.c223
-rw-r--r--source3/smbd/dir.c1316
-rw-r--r--source3/smbd/dmapi.c340
-rw-r--r--source3/smbd/dnsregister.c212
-rw-r--r--source3/smbd/dosmode.c666
-rw-r--r--source3/smbd/error.c169
-rw-r--r--source3/smbd/fake_file.c161
-rw-r--r--source3/smbd/file_access.c222
-rw-r--r--source3/smbd/fileio.c955
-rw-r--r--source3/smbd/filename.c956
-rw-r--r--source3/smbd/files.c547
-rw-r--r--source3/smbd/ipc.c813
-rw-r--r--source3/smbd/lanman.c4642
-rw-r--r--source3/smbd/mangle.c152
-rw-r--r--source3/smbd/mangle_hash.c695
-rw-r--r--source3/smbd/mangle_hash2.c761
-rw-r--r--source3/smbd/map_username.c215
-rw-r--r--source3/smbd/message.c309
-rw-r--r--source3/smbd/msdfs.c1724
-rw-r--r--source3/smbd/negprot.c691
-rw-r--r--source3/smbd/noquotas.c37
-rw-r--r--source3/smbd/notify.c524
-rw-r--r--source3/smbd/notify_inotify.c434
-rw-r--r--source3/smbd/notify_internal.c690
-rw-r--r--source3/smbd/ntquotas.c247
-rw-r--r--source3/smbd/nttrans.c2864
-rw-r--r--source3/smbd/open.c3093
-rw-r--r--source3/smbd/oplock.c897
-rw-r--r--source3/smbd/oplock_irix.c301
-rw-r--r--source3/smbd/oplock_linux.c249
-rw-r--r--source3/smbd/password.c829
-rw-r--r--source3/smbd/pipes.c320
-rw-r--r--source3/smbd/posix_acls.c4322
-rw-r--r--source3/smbd/process.c2113
-rw-r--r--source3/smbd/quotas.c1539
-rw-r--r--source3/smbd/reply.c7184
-rw-r--r--source3/smbd/seal.c742
-rw-r--r--source3/smbd/sec_ctx.c476
-rw-r--r--source3/smbd/server.c1471
-rw-r--r--source3/smbd/service.c1353
-rw-r--r--source3/smbd/session.c329
-rw-r--r--source3/smbd/sesssetup.c1820
-rw-r--r--source3/smbd/share_access.c280
-rw-r--r--source3/smbd/srvstr.c77
-rw-r--r--source3/smbd/statcache.c391
-rw-r--r--source3/smbd/statvfs.c149
-rw-r--r--source3/smbd/trans2.c7846
-rw-r--r--source3/smbd/uid.c449
-rw-r--r--source3/smbd/utmp.c630
-rw-r--r--source3/smbd/vfs.c968
57 files changed, 62898 insertions, 0 deletions
diff --git a/source3/smbd/aio.c b/source3/smbd/aio.c
new file mode 100644
index 0000000000..74275368bd
--- /dev/null
+++ b/source3/smbd/aio.c
@@ -0,0 +1,832 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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"
+
+#if defined(WITH_AIO)
+
+/* The signal we'll use to signify aio done. */
+#ifndef RT_SIGNAL_AIO
+#define RT_SIGNAL_AIO (SIGRTMIN+3)
+#endif
+
+/****************************************************************************
+ The buffer we keep around whilst an aio request is in process.
+*****************************************************************************/
+
+struct aio_extra {
+ struct aio_extra *next, *prev;
+ SMB_STRUCT_AIOCB acb;
+ files_struct *fsp;
+ bool read_req;
+ uint16 mid;
+ char *inbuf;
+ char *outbuf;
+};
+
+static struct aio_extra *aio_list_head;
+
+/****************************************************************************
+ Create the extended aio struct we must keep around for the lifetime
+ of the aio_read call.
+*****************************************************************************/
+
+static struct aio_extra *create_aio_ex_read(files_struct *fsp, size_t buflen,
+ uint16 mid)
+{
+ struct aio_extra *aio_ex = SMB_MALLOC_P(struct aio_extra);
+
+ if (!aio_ex) {
+ return NULL;
+ }
+ ZERO_STRUCTP(aio_ex);
+ /* The output buffer stored in the aio_ex is the start of
+ the smb return buffer. The buffer used in the acb
+ is the start of the reply data portion of that buffer. */
+ aio_ex->outbuf = SMB_MALLOC_ARRAY(char, buflen);
+ if (!aio_ex->outbuf) {
+ SAFE_FREE(aio_ex);
+ return NULL;
+ }
+ DLIST_ADD(aio_list_head, aio_ex);
+ aio_ex->fsp = fsp;
+ aio_ex->read_req = True;
+ aio_ex->mid = mid;
+ return aio_ex;
+}
+
+/****************************************************************************
+ Create the extended aio struct we must keep around for the lifetime
+ of the aio_write call.
+*****************************************************************************/
+
+static struct aio_extra *create_aio_ex_write(files_struct *fsp,
+ size_t inbuflen,
+ size_t outbuflen,
+ uint16 mid)
+{
+ struct aio_extra *aio_ex = SMB_MALLOC_P(struct aio_extra);
+
+ if (!aio_ex) {
+ return NULL;
+ }
+ ZERO_STRUCTP(aio_ex);
+
+ /* We need space for an output reply of outbuflen bytes. */
+ aio_ex->outbuf = SMB_MALLOC_ARRAY(char, outbuflen);
+ if (!aio_ex->outbuf) {
+ SAFE_FREE(aio_ex);
+ return NULL;
+ }
+
+ if (!(aio_ex->inbuf = SMB_MALLOC_ARRAY(char, inbuflen))) {
+ SAFE_FREE(aio_ex->outbuf);
+ SAFE_FREE(aio_ex);
+ return NULL;
+ }
+
+ DLIST_ADD(aio_list_head, aio_ex);
+ aio_ex->fsp = fsp;
+ aio_ex->read_req = False;
+ aio_ex->mid = mid;
+ return aio_ex;
+}
+
+/****************************************************************************
+ Delete the extended aio struct.
+*****************************************************************************/
+
+static void delete_aio_ex(struct aio_extra *aio_ex)
+{
+ DLIST_REMOVE(aio_list_head, aio_ex);
+ SAFE_FREE(aio_ex->inbuf);
+ SAFE_FREE(aio_ex->outbuf);
+ SAFE_FREE(aio_ex);
+}
+
+/****************************************************************************
+ Given the aiocb struct find the extended aio struct containing it.
+*****************************************************************************/
+
+static struct aio_extra *find_aio_ex(uint16 mid)
+{
+ struct aio_extra *p;
+
+ for( p = aio_list_head; p; p = p->next) {
+ if (mid == p->mid) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ We can have these many aio buffers in flight.
+*****************************************************************************/
+
+static int aio_pending_size;
+static sig_atomic_t signals_received;
+static int outstanding_aio_calls;
+static uint16 *aio_pending_array;
+
+/****************************************************************************
+ Signal handler when an aio request completes.
+*****************************************************************************/
+
+void aio_request_done(uint16_t mid)
+{
+ if (signals_received < aio_pending_size) {
+ aio_pending_array[signals_received] = mid;
+ signals_received++;
+ }
+ /* Else signal is lost. */
+}
+
+static void signal_handler(int sig, siginfo_t *info, void *unused)
+{
+ aio_request_done(info->si_value.sival_int);
+ sys_select_signal(RT_SIGNAL_AIO);
+}
+
+/****************************************************************************
+ Is there a signal waiting ?
+*****************************************************************************/
+
+bool aio_finished(void)
+{
+ return (signals_received != 0);
+}
+
+/****************************************************************************
+ Initialize the signal handler for aio read/write.
+*****************************************************************************/
+
+void initialize_async_io_handler(void)
+{
+ struct sigaction act;
+
+ aio_pending_size = lp_maxmux();
+ aio_pending_array = SMB_MALLOC_ARRAY(uint16, aio_pending_size);
+ SMB_ASSERT(aio_pending_array != NULL);
+
+ ZERO_STRUCT(act);
+ act.sa_sigaction = signal_handler;
+ act.sa_flags = SA_SIGINFO;
+ sigemptyset( &act.sa_mask );
+ if (sigaction(RT_SIGNAL_AIO, &act, NULL) != 0) {
+ DEBUG(0,("Failed to setup RT_SIGNAL_AIO handler\n"));
+ }
+
+ /* the signal can start off blocked due to a bug in bash */
+ BlockSignals(False, RT_SIGNAL_AIO);
+}
+
+/****************************************************************************
+ Set up an aio request from a SMBreadX call.
+*****************************************************************************/
+
+bool schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, SMB_OFF_T startpos,
+ size_t smb_maxcnt)
+{
+ struct aio_extra *aio_ex;
+ SMB_STRUCT_AIOCB *a;
+ size_t bufsize;
+ size_t min_aio_read_size = lp_aio_read_size(SNUM(conn));
+
+ if (fsp->base_fsp != NULL) {
+ /* No AIO on streams yet */
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return false;
+ }
+
+ if ((!min_aio_read_size || (smb_maxcnt < min_aio_read_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a read for aio request. */
+ DEBUG(10,("schedule_aio_read_and_X: read size (%u) too small "
+ "for minimum aio_read of %u\n",
+ (unsigned int)smb_maxcnt,
+ (unsigned int)min_aio_read_size ));
+ return False;
+ }
+
+ /* Only do this on non-chained and non-chaining reads not using the
+ * write cache. */
+ if (chain_size !=0 || (CVAL(req->inbuf,smb_vwv0) != 0xFF)
+ || (lp_write_cache_size(SNUM(conn)) != 0) ) {
+ return False;
+ }
+
+ if (outstanding_aio_calls >= aio_pending_size) {
+ DEBUG(10,("schedule_aio_read_and_X: Already have %d aio "
+ "activities outstanding.\n",
+ outstanding_aio_calls ));
+ return False;
+ }
+
+ /* The following is safe from integer wrap as we've already checked
+ smb_maxcnt is 128k or less. Wct is 12 for read replies */
+
+ bufsize = smb_size + 12 * 2 + smb_maxcnt;
+
+ if ((aio_ex = create_aio_ex_read(fsp, bufsize, req->mid)) == NULL) {
+ DEBUG(10,("schedule_aio_read_and_X: malloc fail.\n"));
+ return False;
+ }
+
+ construct_reply_common((char *)req->inbuf, aio_ex->outbuf);
+ srv_set_message(aio_ex->outbuf, 12, 0, True);
+ SCVAL(aio_ex->outbuf,smb_vwv0,0xFF); /* Never a chained reply. */
+
+ a = &aio_ex->acb;
+
+ /* Now set up the aio record for the read call. */
+
+ a->aio_fildes = fsp->fh->fd;
+ a->aio_buf = smb_buf(aio_ex->outbuf);
+ a->aio_nbytes = smb_maxcnt;
+ a->aio_offset = startpos;
+ a->aio_sigevent.sigev_notify = SIGEV_SIGNAL;
+ a->aio_sigevent.sigev_signo = RT_SIGNAL_AIO;
+ a->aio_sigevent.sigev_value.sival_int = aio_ex->mid;
+
+ become_root();
+ if (SMB_VFS_AIO_READ(fsp,a) == -1) {
+ DEBUG(0,("schedule_aio_read_and_X: aio_read failed. "
+ "Error %s\n", strerror(errno) ));
+ delete_aio_ex(aio_ex);
+ unbecome_root();
+ return False;
+ }
+ unbecome_root();
+
+ DEBUG(10,("schedule_aio_read_and_X: scheduled aio_read for file %s, "
+ "offset %.0f, len = %u (mid = %u)\n",
+ fsp->fsp_name, (double)startpos, (unsigned int)smb_maxcnt,
+ (unsigned int)aio_ex->mid ));
+
+ srv_defer_sign_response(aio_ex->mid);
+ outstanding_aio_calls++;
+ return True;
+}
+
+/****************************************************************************
+ Set up an aio request from a SMBwriteX call.
+*****************************************************************************/
+
+bool schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, char *data,
+ SMB_OFF_T startpos,
+ size_t numtowrite)
+{
+ struct aio_extra *aio_ex;
+ SMB_STRUCT_AIOCB *a;
+ size_t inbufsize, outbufsize;
+ bool write_through = BITSETW(req->inbuf+smb_vwv7,0);
+ size_t min_aio_write_size = lp_aio_write_size(SNUM(conn));
+
+ if (fsp->base_fsp != NULL) {
+ /* No AIO on streams yet */
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return false;
+ }
+
+ if ((!min_aio_write_size || (numtowrite < min_aio_write_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a write for aio request. */
+ DEBUG(10,("schedule_aio_write_and_X: write size (%u) too "
+ "small for minimum aio_write of %u\n",
+ (unsigned int)numtowrite,
+ (unsigned int)min_aio_write_size ));
+ return False;
+ }
+
+ /* Only do this on non-chained and non-chaining reads not using the
+ * write cache. */
+ if (chain_size !=0 || (CVAL(req->inbuf,smb_vwv0) != 0xFF)
+ || (lp_write_cache_size(SNUM(conn)) != 0) ) {
+ return False;
+ }
+
+ if (outstanding_aio_calls >= aio_pending_size) {
+ DEBUG(3,("schedule_aio_write_and_X: Already have %d aio "
+ "activities outstanding.\n",
+ outstanding_aio_calls ));
+ DEBUG(10,("schedule_aio_write_and_X: failed to schedule "
+ "aio_write for file %s, offset %.0f, len = %u "
+ "(mid = %u)\n",
+ fsp->fsp_name, (double)startpos,
+ (unsigned int)numtowrite,
+ (unsigned int)req->mid ));
+ return False;
+ }
+
+ inbufsize = smb_len(req->inbuf) + 4;
+ reply_outbuf(req, 6, 0);
+ outbufsize = smb_len(req->outbuf) + 4;
+ if (!(aio_ex = create_aio_ex_write(fsp, inbufsize, outbufsize,
+ req->mid))) {
+ DEBUG(0,("schedule_aio_write_and_X: malloc fail.\n"));
+ return False;
+ }
+
+ /* Copy the SMB header already setup in outbuf. */
+ memcpy(aio_ex->inbuf, req->inbuf, inbufsize);
+
+ /* Copy the SMB header already setup in outbuf. */
+ memcpy(aio_ex->outbuf, req->outbuf, outbufsize);
+ TALLOC_FREE(req->outbuf);
+ SCVAL(aio_ex->outbuf,smb_vwv0,0xFF); /* Never a chained reply. */
+
+ a = &aio_ex->acb;
+
+ /* Now set up the aio record for the write call. */
+
+ a->aio_fildes = fsp->fh->fd;
+ a->aio_buf = aio_ex->inbuf + (PTR_DIFF(data, req->inbuf));
+ a->aio_nbytes = numtowrite;
+ a->aio_offset = startpos;
+ a->aio_sigevent.sigev_notify = SIGEV_SIGNAL;
+ a->aio_sigevent.sigev_signo = RT_SIGNAL_AIO;
+ a->aio_sigevent.sigev_value.sival_int = aio_ex->mid;
+
+ become_root();
+ if (SMB_VFS_AIO_WRITE(fsp,a) == -1) {
+ DEBUG(3,("schedule_aio_wrote_and_X: aio_write failed. "
+ "Error %s\n", strerror(errno) ));
+ delete_aio_ex(aio_ex);
+ unbecome_root();
+ return False;
+ }
+ unbecome_root();
+
+ release_level_2_oplocks_on_change(fsp);
+
+ if (!write_through && !lp_syncalways(SNUM(fsp->conn))
+ && fsp->aio_write_behind) {
+ /* Lie to the client and immediately claim we finished the
+ * write. */
+ SSVAL(aio_ex->outbuf,smb_vwv2,numtowrite);
+ SSVAL(aio_ex->outbuf,smb_vwv4,(numtowrite>>16)&1);
+ show_msg(aio_ex->outbuf);
+ if (!srv_send_smb(smbd_server_fd(),aio_ex->outbuf,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_write: srv_send_smb "
+ "failed.");
+ }
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write "
+ "behind for file %s\n", fsp->fsp_name ));
+ } else {
+ srv_defer_sign_response(aio_ex->mid);
+ }
+ outstanding_aio_calls++;
+
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write for file "
+ "%s, offset %.0f, len = %u (mid = %u) "
+ "outstanding_aio_calls = %d\n",
+ fsp->fsp_name, (double)startpos, (unsigned int)numtowrite,
+ (unsigned int)aio_ex->mid, outstanding_aio_calls ));
+
+ return True;
+}
+
+
+/****************************************************************************
+ Complete the read and return the data or error back to the client.
+ Returns errno or zero if all ok.
+*****************************************************************************/
+
+static int handle_aio_read_complete(struct aio_extra *aio_ex)
+{
+ int ret = 0;
+ int outsize;
+ char *outbuf = aio_ex->outbuf;
+ char *data = smb_buf(outbuf);
+ ssize_t nread = SMB_VFS_AIO_RETURN(aio_ex->fsp,&aio_ex->acb);
+
+ if (nread < 0) {
+ /* We're relying here on the fact that if the fd is
+ closed then the aio will complete and aio_return
+ will return an error. Hopefully this is
+ true.... JRA. */
+
+ /* If errno is ECANCELED then don't return anything to the
+ * client. */
+ if (errno == ECANCELED) {
+ srv_cancel_sign_response(aio_ex->mid);
+ return 0;
+ }
+
+ DEBUG( 3,( "handle_aio_read_complete: file %s nread == -1. "
+ "Error = %s\n",
+ aio_ex->fsp->fsp_name, strerror(errno) ));
+
+ ret = errno;
+ ERROR_NT(map_nt_error_from_unix(ret));
+ outsize = srv_set_message(outbuf,0,0,true);
+ } else {
+ outsize = srv_set_message(outbuf,12,nread,False);
+ SSVAL(outbuf,smb_vwv2,0xFFFF); /* Remaining - must be * -1. */
+ SSVAL(outbuf,smb_vwv5,nread);
+ SSVAL(outbuf,smb_vwv6,smb_offset(data,outbuf));
+ SSVAL(outbuf,smb_vwv7,((nread >> 16) & 1));
+ SSVAL(smb_buf(outbuf),-2,nread);
+
+ aio_ex->fsp->fh->pos = aio_ex->acb.aio_offset + nread;
+ aio_ex->fsp->fh->position_information = aio_ex->fsp->fh->pos;
+
+ DEBUG( 3, ( "handle_aio_read_complete file %s max=%d "
+ "nread=%d\n",
+ aio_ex->fsp->fsp_name,
+ (int)aio_ex->acb.aio_nbytes, (int)nread ) );
+
+ }
+ smb_setlen(outbuf,outsize - 4);
+ show_msg(outbuf);
+ if (!srv_send_smb(smbd_server_fd(),outbuf,
+ IS_CONN_ENCRYPTED(aio_ex->fsp->conn))) {
+ exit_server_cleanly("handle_aio_read_complete: srv_send_smb "
+ "failed.");
+ }
+
+ DEBUG(10,("handle_aio_read_complete: scheduled aio_read completed "
+ "for file %s, offset %.0f, len = %u\n",
+ aio_ex->fsp->fsp_name, (double)aio_ex->acb.aio_offset,
+ (unsigned int)nread ));
+
+ return ret;
+}
+
+/****************************************************************************
+ Complete the write and return the data or error back to the client.
+ Returns errno or zero if all ok.
+*****************************************************************************/
+
+static int handle_aio_write_complete(struct aio_extra *aio_ex)
+{
+ int ret = 0;
+ files_struct *fsp = aio_ex->fsp;
+ char *outbuf = aio_ex->outbuf;
+ ssize_t numtowrite = aio_ex->acb.aio_nbytes;
+ ssize_t nwritten = SMB_VFS_AIO_RETURN(fsp,&aio_ex->acb);
+
+ if (fsp->aio_write_behind) {
+ if (nwritten != numtowrite) {
+ if (nwritten == -1) {
+ DEBUG(5,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Error %s\n",
+ fsp->fsp_name, strerror(errno) ));
+ ret = errno;
+ } else {
+ DEBUG(0,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Wanted %u bytes but "
+ "only wrote %d\n", fsp->fsp_name,
+ (unsigned int)numtowrite,
+ (int)nwritten ));
+ ret = EIO;
+ }
+ } else {
+ DEBUG(10,("handle_aio_write_complete: "
+ "aio_write_behind completed for file %s\n",
+ fsp->fsp_name ));
+ }
+ return 0;
+ }
+
+ /* We don't need outsize or set_message here as we've already set the
+ fixed size length when we set up the aio call. */
+
+ if(nwritten == -1) {
+ DEBUG( 3,( "handle_aio_write: file %s wanted %u bytes. "
+ "nwritten == %d. Error = %s\n",
+ fsp->fsp_name, (unsigned int)numtowrite,
+ (int)nwritten, strerror(errno) ));
+
+ /* If errno is ECANCELED then don't return anything to the
+ * client. */
+ if (errno == ECANCELED) {
+ srv_cancel_sign_response(aio_ex->mid);
+ return 0;
+ }
+
+ ret = errno;
+ ERROR_BOTH(map_nt_error_from_unix(ret), ERRHRD, ERRdiskfull);
+ srv_set_message(outbuf,0,0,true);
+ } else {
+ bool write_through = BITSETW(aio_ex->inbuf+smb_vwv7,0);
+ NTSTATUS status;
+
+ SSVAL(outbuf,smb_vwv2,nwritten);
+ SSVAL(outbuf,smb_vwv4,(nwritten>>16)&1);
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(outbuf,smb_rcls,ERRHRD);
+ SSVAL(outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3,("handle_aio_write: fnum=%d num=%d wrote=%d\n",
+ fsp->fnum, (int)numtowrite, (int)nwritten));
+ status = sync_file(fsp->conn,fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = errno;
+ ERROR_BOTH(map_nt_error_from_unix(ret),
+ ERRHRD, ERRdiskfull);
+ srv_set_message(outbuf,0,0,true);
+ DEBUG(5,("handle_aio_write: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ }
+
+ aio_ex->fsp->fh->pos = aio_ex->acb.aio_offset + nwritten;
+ }
+
+ show_msg(outbuf);
+ if (!srv_send_smb(smbd_server_fd(),outbuf,IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_write: srv_send_smb failed.");
+ }
+
+ DEBUG(10,("handle_aio_write_complete: scheduled aio_write completed "
+ "for file %s, offset %.0f, requested %u, written = %u\n",
+ fsp->fsp_name, (double)aio_ex->acb.aio_offset,
+ (unsigned int)numtowrite, (unsigned int)nwritten ));
+
+ return ret;
+}
+
+/****************************************************************************
+ Handle any aio completion. Returns True if finished (and sets *perr if err
+ was non-zero), False if not.
+*****************************************************************************/
+
+static bool handle_aio_completed(struct aio_extra *aio_ex, int *perr)
+{
+ int err;
+
+ /* Ensure the operation has really completed. */
+ if (SMB_VFS_AIO_ERROR(aio_ex->fsp, &aio_ex->acb) == EINPROGRESS) {
+ DEBUG(10,( "handle_aio_completed: operation mid %u still in "
+ "process for file %s\n",
+ aio_ex->mid, aio_ex->fsp->fsp_name ));
+ return False;
+ }
+
+ if (aio_ex->read_req) {
+ err = handle_aio_read_complete(aio_ex);
+ } else {
+ err = handle_aio_write_complete(aio_ex);
+ }
+
+ if (err) {
+ *perr = err; /* Only save non-zero errors. */
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Handle any aio completion inline.
+ Returns non-zero errno if fail or zero if all ok.
+*****************************************************************************/
+
+int process_aio_queue(void)
+{
+ int i;
+ int ret = 0;
+
+ BlockSignals(True, RT_SIGNAL_AIO);
+
+ DEBUG(10,("process_aio_queue: signals_received = %d\n",
+ (int)signals_received));
+ DEBUG(10,("process_aio_queue: outstanding_aio_calls = %d\n",
+ outstanding_aio_calls));
+
+ if (!signals_received) {
+ BlockSignals(False, RT_SIGNAL_AIO);
+ return 0;
+ }
+
+ /* Drain all the complete aio_reads. */
+ for (i = 0; i < signals_received; i++) {
+ uint16 mid = aio_pending_array[i];
+ files_struct *fsp = NULL;
+ struct aio_extra *aio_ex = find_aio_ex(mid);
+
+ if (!aio_ex) {
+ DEBUG(3,("process_aio_queue: Can't find record to "
+ "match mid %u.\n", (unsigned int)mid));
+ srv_cancel_sign_response(mid);
+ continue;
+ }
+
+ fsp = aio_ex->fsp;
+ if (fsp == NULL) {
+ /* file was closed whilst I/O was outstanding. Just
+ * ignore. */
+ DEBUG( 3,( "process_aio_queue: file closed whilst "
+ "aio outstanding.\n"));
+ srv_cancel_sign_response(mid);
+ continue;
+ }
+
+ if (!handle_aio_completed(aio_ex, &ret)) {
+ continue;
+ }
+
+ delete_aio_ex(aio_ex);
+ }
+
+ outstanding_aio_calls -= signals_received;
+ signals_received = 0;
+ BlockSignals(False, RT_SIGNAL_AIO);
+ return ret;
+}
+
+/****************************************************************************
+ We're doing write behind and the client closed the file. Wait up to 30
+ seconds (my arbitrary choice) for the aio to complete. Return 0 if all writes
+ completed, errno to return if not.
+*****************************************************************************/
+
+#define SMB_TIME_FOR_AIO_COMPLETE_WAIT 29
+
+int wait_for_aio_completion(files_struct *fsp)
+{
+ struct aio_extra *aio_ex;
+ const SMB_STRUCT_AIOCB **aiocb_list;
+ int aio_completion_count = 0;
+ time_t start_time = time(NULL);
+ int seconds_left;
+
+ for (seconds_left = SMB_TIME_FOR_AIO_COMPLETE_WAIT;
+ seconds_left >= 0;) {
+ int err = 0;
+ int i;
+ struct timespec ts;
+
+ aio_completion_count = 0;
+ for( aio_ex = aio_list_head; aio_ex; aio_ex = aio_ex->next) {
+ if (aio_ex->fsp == fsp) {
+ aio_completion_count++;
+ }
+ }
+
+ if (!aio_completion_count) {
+ return 0;
+ }
+
+ DEBUG(3,("wait_for_aio_completion: waiting for %d aio events "
+ "to complete.\n", aio_completion_count ));
+
+ aiocb_list = SMB_MALLOC_ARRAY(const SMB_STRUCT_AIOCB *,
+ aio_completion_count);
+ if (!aiocb_list) {
+ return ENOMEM;
+ }
+
+ for( i = 0, aio_ex = aio_list_head;
+ aio_ex;
+ aio_ex = aio_ex->next) {
+ if (aio_ex->fsp == fsp) {
+ aiocb_list[i++] = &aio_ex->acb;
+ }
+ }
+
+ /* Now wait up to seconds_left for completion. */
+ ts.tv_sec = seconds_left;
+ ts.tv_nsec = 0;
+
+ DEBUG(10,("wait_for_aio_completion: %d events, doing a wait "
+ "of %d seconds.\n",
+ aio_completion_count, seconds_left ));
+
+ err = SMB_VFS_AIO_SUSPEND(fsp, aiocb_list,
+ aio_completion_count, &ts);
+
+ DEBUG(10,("wait_for_aio_completion: returned err = %d, "
+ "errno = %s\n", err, strerror(errno) ));
+
+ if (err == -1 && errno == EAGAIN) {
+ DEBUG(0,("wait_for_aio_completion: aio_suspend timed "
+ "out waiting for %d events after a wait of "
+ "%d seconds\n", aio_completion_count,
+ seconds_left));
+ /* Timeout. */
+ cancel_aio_by_fsp(fsp);
+ SAFE_FREE(aiocb_list);
+ return EIO;
+ }
+
+ /* One or more events might have completed - process them if
+ * so. */
+ for( i = 0; i < aio_completion_count; i++) {
+ uint16 mid = aiocb_list[i]->aio_sigevent.sigev_value.sival_int;
+
+ aio_ex = find_aio_ex(mid);
+
+ if (!aio_ex) {
+ DEBUG(0, ("wait_for_aio_completion: mid %u "
+ "doesn't match an aio record\n",
+ (unsigned int)mid ));
+ continue;
+ }
+
+ if (!handle_aio_completed(aio_ex, &err)) {
+ continue;
+ }
+ delete_aio_ex(aio_ex);
+ }
+
+ SAFE_FREE(aiocb_list);
+ seconds_left = SMB_TIME_FOR_AIO_COMPLETE_WAIT
+ - (time(NULL) - start_time);
+ }
+
+ /* We timed out - we don't know why. Return ret if already an error,
+ * else EIO. */
+ DEBUG(10,("wait_for_aio_completion: aio_suspend timed out waiting "
+ "for %d events\n",
+ aio_completion_count));
+
+ return EIO;
+}
+
+/****************************************************************************
+ Cancel any outstanding aio requests. The client doesn't care about the reply.
+*****************************************************************************/
+
+void cancel_aio_by_fsp(files_struct *fsp)
+{
+ struct aio_extra *aio_ex;
+
+ for( aio_ex = aio_list_head; aio_ex; aio_ex = aio_ex->next) {
+ if (aio_ex->fsp == fsp) {
+ /* Don't delete the aio_extra record as we may have
+ completed and don't yet know it. Just do the
+ aio_cancel call and return. */
+ SMB_VFS_AIO_CANCEL(fsp, &aio_ex->acb);
+ aio_ex->fsp = NULL; /* fsp will be closed when we
+ * return. */
+ }
+ }
+}
+
+#else
+bool aio_finished(void)
+{
+ return False;
+}
+
+void initialize_async_io_handler(void)
+{
+}
+
+int process_aio_queue(void)
+{
+ return False;
+}
+
+bool schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, SMB_OFF_T startpos,
+ size_t smb_maxcnt)
+{
+ return False;
+}
+
+bool schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, char *data,
+ SMB_OFF_T startpos,
+ size_t numtowrite)
+{
+ return False;
+}
+
+void cancel_aio_by_fsp(files_struct *fsp)
+{
+}
+
+int wait_for_aio_completion(files_struct *fsp)
+{
+ return ENOSYS;
+}
+#endif
diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c
new file mode 100644
index 0000000000..479361a8c1
--- /dev/null
+++ b/source3/smbd/blocking.c
@@ -0,0 +1,931 @@
+/*
+ Unix SMB/CIFS implementation.
+ Blocking Locking functions
+ Copyright (C) Jeremy Allison 1998-2003
+
+ 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"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ This is the structure to queue to implement blocking locks.
+ notify. It consists of the requesting SMB and the expiry time.
+*****************************************************************************/
+
+typedef struct _blocking_lock_record {
+ struct _blocking_lock_record *next;
+ struct _blocking_lock_record *prev;
+ int com_type;
+ files_struct *fsp;
+ struct timeval expire_time;
+ int lock_num;
+ SMB_BIG_UINT offset;
+ SMB_BIG_UINT count;
+ uint32 lock_pid;
+ uint32 blocking_pid; /* PID that blocks us. */
+ enum brl_flavour lock_flav;
+ enum brl_type lock_type;
+ char *inbuf;
+ int length;
+ bool encrypted;
+} blocking_lock_record;
+
+/* dlink list we store pending lock records on. */
+static blocking_lock_record *blocking_lock_queue;
+
+/* dlink list we move cancelled lock records onto. */
+static blocking_lock_record *blocking_lock_cancelled_queue;
+
+/* The event that makes us process our blocking lock queue */
+static struct timed_event *brl_timeout;
+
+/****************************************************************************
+ Destructor for the above structure.
+****************************************************************************/
+
+static void free_blocking_lock_record(blocking_lock_record *blr)
+{
+ SAFE_FREE(blr->inbuf);
+ SAFE_FREE(blr);
+}
+
+/****************************************************************************
+ Determine if this is a secondary element of a chained SMB.
+ **************************************************************************/
+
+static bool in_chained_smb(void)
+{
+ return (chain_size != 0);
+}
+
+static void received_unlock_msg(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+static void process_blocking_lock_queue(void);
+
+static void brl_timeout_fn(struct event_context *event_ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ SMB_ASSERT(brl_timeout == te);
+ TALLOC_FREE(brl_timeout);
+
+ change_to_root_user(); /* TODO: Possibly run all timed events as
+ * root */
+
+ process_blocking_lock_queue();
+}
+
+/****************************************************************************
+ After a change to blocking_lock_queue, recalculate the timed_event for the
+ next processing.
+****************************************************************************/
+
+static bool recalc_brl_timeout(void)
+{
+ blocking_lock_record *brl;
+ struct timeval next_timeout;
+
+ TALLOC_FREE(brl_timeout);
+
+ next_timeout = timeval_zero();
+
+ for (brl = blocking_lock_queue; brl; brl = brl->next) {
+ if (timeval_is_zero(&brl->expire_time)) {
+ /*
+ * If we're blocked on pid 0xFFFFFFFF this is
+ * a POSIX lock, so calculate a timeout of
+ * 10 seconds into the future.
+ */
+ if (brl->blocking_pid == 0xFFFFFFFF) {
+ struct timeval psx_to = timeval_current_ofs(10, 0);
+ next_timeout = timeval_min(&next_timeout, &psx_to);
+ }
+
+ continue;
+ }
+
+ if (timeval_is_zero(&next_timeout)) {
+ next_timeout = brl->expire_time;
+ }
+ else {
+ next_timeout = timeval_min(&next_timeout,
+ &brl->expire_time);
+ }
+ }
+
+ if (timeval_is_zero(&next_timeout)) {
+ return True;
+ }
+
+ if (!(brl_timeout = event_add_timed(smbd_event_context(), NULL,
+ next_timeout, "brl_timeout",
+ brl_timeout_fn, NULL))) {
+ return False;
+ }
+
+ return True;
+}
+
+
+/****************************************************************************
+ Function to push a blocking lock request onto the lock queue.
+****************************************************************************/
+
+bool push_blocking_lock_request( struct byte_range_lock *br_lck,
+ const struct smb_request *req,
+ files_struct *fsp,
+ int lock_timeout,
+ int lock_num,
+ uint32 lock_pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ SMB_BIG_UINT offset,
+ SMB_BIG_UINT count,
+ uint32 blocking_pid)
+{
+ static bool set_lock_msg;
+ size_t length = smb_len(req->inbuf)+4;
+ blocking_lock_record *blr;
+ NTSTATUS status;
+
+ if(in_chained_smb() ) {
+ DEBUG(0,("push_blocking_lock_request: cannot queue a chained request (currently).\n"));
+ return False;
+ }
+
+ /*
+ * Now queue an entry on the blocking lock queue. We setup
+ * the expiration time here.
+ */
+
+ if((blr = SMB_MALLOC_P(blocking_lock_record)) == NULL) {
+ DEBUG(0,("push_blocking_lock_request: Malloc fail !\n" ));
+ return False;
+ }
+
+ blr->next = NULL;
+ blr->prev = NULL;
+
+ if((blr->inbuf = (char *)SMB_MALLOC(length)) == NULL) {
+ DEBUG(0,("push_blocking_lock_request: Malloc fail (2)!\n" ));
+ SAFE_FREE(blr);
+ return False;
+ }
+
+ blr->com_type = CVAL(req->inbuf,smb_com);
+ blr->fsp = fsp;
+ if (lock_timeout == -1) {
+ blr->expire_time.tv_sec = 0;
+ blr->expire_time.tv_usec = 0; /* Never expire. */
+ } else {
+ blr->expire_time = timeval_current_ofs(lock_timeout/1000,
+ (lock_timeout % 1000) * 1000);
+ }
+ blr->lock_num = lock_num;
+ blr->lock_pid = lock_pid;
+ blr->blocking_pid = blocking_pid;
+ blr->lock_flav = lock_flav;
+ blr->lock_type = lock_type;
+ blr->offset = offset;
+ blr->count = count;
+ memcpy(blr->inbuf, req->inbuf, length);
+ blr->length = length;
+ blr->encrypted = req->encrypted;
+
+ /* Add a pending lock record for this. */
+ status = brl_lock(smbd_messaging_context(), br_lck,
+ lock_pid,
+ procid_self(),
+ offset,
+ count,
+ lock_type == READ_LOCK ? PENDING_READ_LOCK : PENDING_WRITE_LOCK,
+ blr->lock_flav,
+ lock_timeout ? True : False, /* blocking_lock. */
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("push_blocking_lock_request: failed to add PENDING_LOCK record.\n"));
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ return False;
+ }
+
+ DLIST_ADD_END(blocking_lock_queue, blr, blocking_lock_record *);
+ recalc_brl_timeout();
+
+ /* Ensure we'll receive messages when this is unlocked. */
+ if (!set_lock_msg) {
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_UNLOCK, received_unlock_msg);
+ set_lock_msg = True;
+ }
+
+ DEBUG(3,("push_blocking_lock_request: lock request length=%u blocked with "
+ "expiry time (%u sec. %u usec) (+%d msec) for fnum = %d, name = %s\n",
+ (unsigned int)length, (unsigned int)blr->expire_time.tv_sec,
+ (unsigned int)blr->expire_time.tv_usec, lock_timeout,
+ blr->fsp->fnum, blr->fsp->fsp_name ));
+
+ /* Push the MID of this packet on the signing queue. */
+ srv_defer_sign_response(SVAL(req->inbuf,smb_mid));
+
+ return True;
+}
+
+/****************************************************************************
+ Return a lockingX success SMB.
+*****************************************************************************/
+
+static void reply_lockingX_success(blocking_lock_record *blr)
+{
+ struct smb_request *req;
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("Could not allocate smb_request");
+ }
+
+ init_smb_request(req, (uint8 *)blr->inbuf, 0, blr->encrypted);
+ reply_outbuf(req, 2, 0);
+
+ /*
+ * As this message is a lockingX call we must handle
+ * any following chained message correctly.
+ * This is normally handled in construct_reply(),
+ * but as that calls switch_message, we can't use
+ * that here and must set up the chain info manually.
+ */
+
+ chain_reply(req);
+
+ if (!srv_send_smb(smbd_server_fd(),
+ (char *)req->outbuf,
+ IS_CONN_ENCRYPTED(blr->fsp->conn))) {
+ exit_server_cleanly("send_blocking_reply: srv_send_smb failed.");
+ }
+}
+
+/****************************************************************************
+ Return a generic lock fail error blocking call.
+*****************************************************************************/
+
+static void generic_blocking_lock_error(blocking_lock_record *blr, NTSTATUS status)
+{
+ char outbuf[smb_size];
+ char *inbuf = blr->inbuf;
+
+ construct_reply_common(inbuf, outbuf);
+
+ /* whenever a timeout is given w2k maps LOCK_NOT_GRANTED to
+ FILE_LOCK_CONFLICT! (tridge) */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ status = NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ /* Store the last lock error. */
+ files_struct *fsp = blr->fsp;
+
+ if (fsp) {
+ fsp->last_lock_failure.context.smbpid = blr->lock_pid;
+ fsp->last_lock_failure.context.tid = fsp->conn->cnum;
+ fsp->last_lock_failure.context.pid = procid_self();
+ fsp->last_lock_failure.start = blr->offset;
+ fsp->last_lock_failure.size = blr->count;
+ fsp->last_lock_failure.fnum = fsp->fnum;
+ fsp->last_lock_failure.lock_type = READ_LOCK; /* Don't care. */
+ fsp->last_lock_failure.lock_flav = blr->lock_flav;
+ }
+ }
+
+ ERROR_NT(status);
+ if (!srv_send_smb(smbd_server_fd(),outbuf, blr->encrypted)) {
+ exit_server_cleanly("generic_blocking_lock_error: srv_send_smb failed.");
+ }
+}
+
+/****************************************************************************
+ Return a lock fail error for a lockingX call. Undo all the locks we have
+ obtained first.
+*****************************************************************************/
+
+static void reply_lockingX_error(blocking_lock_record *blr, NTSTATUS status)
+{
+ char *inbuf = blr->inbuf;
+ files_struct *fsp = blr->fsp;
+ uint16 num_ulocks = SVAL(inbuf,smb_vwv6);
+ SMB_BIG_UINT count = (SMB_BIG_UINT)0, offset = (SMB_BIG_UINT) 0;
+ uint32 lock_pid;
+ unsigned char locktype = CVAL(inbuf,smb_vwv3);
+ bool large_file_format = (locktype & LOCKING_ANDX_LARGE_FILES);
+ char *data;
+ int i;
+
+ data = smb_buf(inbuf) + ((large_file_format ? 20 : 10)*num_ulocks);
+
+ /*
+ * Data now points at the beginning of the list
+ * of smb_lkrng structs.
+ */
+
+ /*
+ * Ensure we don't do a remove on the lock that just failed,
+ * as under POSIX rules, if we have a lock already there, we
+ * will delete it (and we shouldn't) .....
+ */
+
+ for(i = blr->lock_num - 1; i >= 0; i--) {
+ bool err;
+
+ lock_pid = get_lock_pid( data, i, large_file_format);
+ count = get_lock_count( data, i, large_file_format);
+ offset = get_lock_offset( data, i, large_file_format, &err);
+
+ /*
+ * We know err cannot be set as if it was the lock
+ * request would never have been queued. JRA.
+ */
+
+ do_unlock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ WINDOWS_LOCK);
+ }
+
+ generic_blocking_lock_error(blr, status);
+}
+
+/****************************************************************************
+ Return a lock fail error.
+*****************************************************************************/
+
+static void blocking_lock_reply_error(blocking_lock_record *blr, NTSTATUS status)
+{
+ switch(blr->com_type) {
+ case SMBlockingX:
+ reply_lockingX_error(blr, status);
+ break;
+ case SMBtrans2:
+ case SMBtranss2:
+ {
+ char outbuf[smb_size];
+ char *inbuf = blr->inbuf;
+ construct_reply_common(inbuf, outbuf);
+ /* construct_reply_common has done us the favor to pre-fill the
+ * command field with SMBtranss2 which is wrong :-)
+ */
+ SCVAL(outbuf,smb_com,SMBtrans2);
+ ERROR_NT(status);
+ if (!srv_send_smb(smbd_server_fd(),
+ outbuf,
+ IS_CONN_ENCRYPTED(blr->fsp->conn))) {
+ exit_server_cleanly("blocking_lock_reply_error: srv_send_smb failed.");
+ }
+ break;
+ }
+ default:
+ DEBUG(0,("blocking_lock_reply_error: PANIC - unknown type on blocking lock queue - exiting.!\n"));
+ exit_server("PANIC - unknown type on blocking lock queue");
+ }
+}
+
+/****************************************************************************
+ Attempt to finish off getting all pending blocking locks for a lockingX call.
+ Returns True if we want to be removed from the list.
+*****************************************************************************/
+
+static bool process_lockingX(blocking_lock_record *blr)
+{
+ char *inbuf = blr->inbuf;
+ unsigned char locktype = CVAL(inbuf,smb_vwv3);
+ files_struct *fsp = blr->fsp;
+ uint16 num_ulocks = SVAL(inbuf,smb_vwv6);
+ uint16 num_locks = SVAL(inbuf,smb_vwv7);
+ SMB_BIG_UINT count = (SMB_BIG_UINT)0, offset = (SMB_BIG_UINT)0;
+ uint32 lock_pid;
+ bool large_file_format = (locktype & LOCKING_ANDX_LARGE_FILES);
+ char *data;
+ NTSTATUS status = NT_STATUS_OK;
+
+ data = smb_buf(inbuf) + ((large_file_format ? 20 : 10)*num_ulocks);
+
+ /*
+ * Data now points at the beginning of the list
+ * of smb_lkrng structs.
+ */
+
+ for(; blr->lock_num < num_locks; blr->lock_num++) {
+ struct byte_range_lock *br_lck = NULL;
+ bool err;
+
+ lock_pid = get_lock_pid( data, blr->lock_num, large_file_format);
+ count = get_lock_count( data, blr->lock_num, large_file_format);
+ offset = get_lock_offset( data, blr->lock_num, large_file_format, &err);
+
+ /*
+ * We know err cannot be set as if it was the lock
+ * request would never have been queued. JRA.
+ */
+ errno = 0;
+ br_lck = do_lock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ ((locktype & LOCKING_ANDX_SHARED_LOCK) ?
+ READ_LOCK : WRITE_LOCK),
+ WINDOWS_LOCK,
+ True,
+ &status,
+ &blr->blocking_pid);
+
+ TALLOC_FREE(br_lck);
+
+ if (NT_STATUS_IS_ERR(status)) {
+ break;
+ }
+ }
+
+ if(blr->lock_num == num_locks) {
+ /*
+ * Success - we got all the locks.
+ */
+
+ DEBUG(3,("process_lockingX file = %s, fnum=%d type=%d num_locks=%d\n",
+ fsp->fsp_name, fsp->fnum, (unsigned int)locktype, num_locks) );
+
+ reply_lockingX_success(blr);
+ return True;
+ } else if (!NT_STATUS_EQUAL(status,NT_STATUS_LOCK_NOT_GRANTED) &&
+ !NT_STATUS_EQUAL(status,NT_STATUS_FILE_LOCK_CONFLICT)) {
+ /*
+ * We have other than a "can't get lock"
+ * error. Free any locks we had and return an error.
+ * Return True so we get dequeued.
+ */
+
+ blocking_lock_reply_error(blr, status);
+ return True;
+ }
+
+ /*
+ * Still can't get all the locks - keep waiting.
+ */
+
+ DEBUG(10,("process_lockingX: only got %d locks of %d needed for file %s, fnum = %d. \
+Waiting....\n",
+ blr->lock_num, num_locks, fsp->fsp_name, fsp->fnum));
+
+ return False;
+}
+
+/****************************************************************************
+ Attempt to get the posix lock request from a SMBtrans2 call.
+ Returns True if we want to be removed from the list.
+*****************************************************************************/
+
+static bool process_trans2(blocking_lock_record *blr)
+{
+ struct smb_request *req;
+ char params[2];
+ NTSTATUS status;
+ struct byte_range_lock *br_lck = do_lock(smbd_messaging_context(),
+ blr->fsp,
+ blr->lock_pid,
+ blr->count,
+ blr->offset,
+ blr->lock_type,
+ blr->lock_flav,
+ True,
+ &status,
+ &blr->blocking_pid);
+ TALLOC_FREE(br_lck);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (ERROR_WAS_LOCK_DENIED(status)) {
+ /* Still can't get the lock, just keep waiting. */
+ return False;
+ }
+ /*
+ * We have other than a "can't get lock"
+ * error. Send an error and return True so we get dequeued.
+ */
+ blocking_lock_reply_error(blr, status);
+ return True;
+ }
+
+ /* We finally got the lock, return success. */
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ blocking_lock_reply_error(blr, NT_STATUS_NO_MEMORY);
+ return True;
+ }
+
+ init_smb_request(req, (uint8 *)blr->inbuf, 0, blr->encrypted);
+
+ SCVAL(req->inbuf, smb_com, SMBtrans2);
+ SSVAL(params,0,0);
+ /* Fake up max_data_bytes here - we know it fits. */
+ send_trans2_replies(blr->fsp->conn, req, params, 2, NULL, 0, 0xffff);
+ return True;
+}
+
+
+/****************************************************************************
+ Process a blocking lock SMB.
+ Returns True if we want to be removed from the list.
+*****************************************************************************/
+
+static bool blocking_lock_record_process(blocking_lock_record *blr)
+{
+ switch(blr->com_type) {
+ case SMBlockingX:
+ return process_lockingX(blr);
+ case SMBtrans2:
+ case SMBtranss2:
+ return process_trans2(blr);
+ default:
+ DEBUG(0,("blocking_lock_record_process: PANIC - unknown type on blocking lock queue - exiting.!\n"));
+ exit_server("PANIC - unknown type on blocking lock queue");
+ }
+ return False; /* Keep compiler happy. */
+}
+
+/****************************************************************************
+ Cancel entries by fnum from the blocking lock pending queue.
+*****************************************************************************/
+
+void cancel_pending_lock_requests_by_fid(files_struct *fsp, struct byte_range_lock *br_lck)
+{
+ blocking_lock_record *blr, *next = NULL;
+
+ for(blr = blocking_lock_queue; blr; blr = next) {
+ next = blr->next;
+ if(blr->fsp->fnum == fsp->fnum) {
+ unsigned char locktype = 0;
+
+ if (blr->com_type == SMBlockingX) {
+ locktype = CVAL(blr->inbuf,smb_vwv3);
+ }
+
+ if (br_lck) {
+ DEBUG(10,("remove_pending_lock_requests_by_fid - removing request type %d for \
+file %s fnum = %d\n", blr->com_type, fsp->fsp_name, fsp->fnum ));
+
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+
+ blocking_lock_cancel(fsp,
+ blr->lock_pid,
+ blr->offset,
+ blr->count,
+ blr->lock_flav,
+ locktype,
+ NT_STATUS_RANGE_NOT_LOCKED);
+ }
+ /* We're closing the file fsp here, so ensure
+ * we don't have a dangling pointer. */
+ blr->fsp = NULL;
+ }
+ }
+}
+
+/****************************************************************************
+ Delete entries by mid from the blocking lock pending queue. Always send reply.
+*****************************************************************************/
+
+void remove_pending_lock_requests_by_mid(int mid)
+{
+ blocking_lock_record *blr, *next = NULL;
+
+ for(blr = blocking_lock_queue; blr; blr = next) {
+ next = blr->next;
+ if(SVAL(blr->inbuf,smb_mid) == mid) {
+ files_struct *fsp = blr->fsp;
+ struct byte_range_lock *br_lck = brl_get_locks(talloc_tos(), fsp);
+
+ if (br_lck) {
+ DEBUG(10,("remove_pending_lock_requests_by_mid - removing request type %d for \
+file %s fnum = %d\n", blr->com_type, fsp->fsp_name, fsp->fnum ));
+
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+ TALLOC_FREE(br_lck);
+ }
+
+ blocking_lock_reply_error(blr,NT_STATUS_FILE_LOCK_CONFLICT);
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ }
+ }
+}
+
+/****************************************************************************
+ Is this mid a blocking lock request on the queue ?
+*****************************************************************************/
+
+bool blocking_lock_was_deferred(int mid)
+{
+ blocking_lock_record *blr, *next = NULL;
+
+ for(blr = blocking_lock_queue; blr; blr = next) {
+ next = blr->next;
+ if(SVAL(blr->inbuf,smb_mid) == mid) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Set a flag as an unlock request affects one of our pending locks.
+*****************************************************************************/
+
+static void received_unlock_msg(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(10,("received_unlock_msg\n"));
+ process_blocking_lock_queue();
+}
+
+/****************************************************************************
+ Process the blocking lock queue. Note that this is only called as root.
+*****************************************************************************/
+
+static void process_blocking_lock_queue(void)
+{
+ struct timeval tv_curr = timeval_current();
+ blocking_lock_record *blr, *next = NULL;
+ bool recalc_timeout = False;
+
+ /*
+ * Go through the queue and see if we can get any of the locks.
+ */
+
+ for (blr = blocking_lock_queue; blr; blr = next) {
+ connection_struct *conn = NULL;
+ uint16 vuid;
+ files_struct *fsp = NULL;
+
+ next = blr->next;
+
+ /*
+ * Ensure we don't have any old chain_fsp values
+ * sitting around....
+ */
+ chain_size = 0;
+ file_chain_reset();
+ fsp = blr->fsp;
+
+ conn = conn_find(SVAL(blr->inbuf,smb_tid));
+ vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID :
+ SVAL(blr->inbuf,smb_uid);
+
+ DEBUG(5,("process_blocking_lock_queue: examining pending lock fnum = %d for file %s\n",
+ fsp->fnum, fsp->fsp_name ));
+
+ if(!change_to_user(conn,vuid)) {
+ struct byte_range_lock *br_lck = brl_get_locks(talloc_tos(), fsp);
+
+ /*
+ * Remove the entry and return an error to the client.
+ */
+
+ if (br_lck) {
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+ TALLOC_FREE(br_lck);
+ }
+
+ DEBUG(0,("process_blocking_lock_queue: Unable to become user vuid=%d.\n",
+ vuid ));
+ blocking_lock_reply_error(blr,NT_STATUS_ACCESS_DENIED);
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ recalc_timeout = True;
+ continue;
+ }
+
+ if(!set_current_service(conn,SVAL(blr->inbuf,smb_flg),True)) {
+ struct byte_range_lock *br_lck = brl_get_locks(talloc_tos(), fsp);
+
+ /*
+ * Remove the entry and return an error to the client.
+ */
+
+ if (br_lck) {
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+ TALLOC_FREE(br_lck);
+ }
+
+ DEBUG(0,("process_blocking_lock_queue: Unable to become service Error was %s.\n", strerror(errno) ));
+ blocking_lock_reply_error(blr,NT_STATUS_ACCESS_DENIED);
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ recalc_timeout = True;
+ change_to_root_user();
+ continue;
+ }
+
+ /*
+ * Go through the remaining locks and try and obtain them.
+ * The call returns True if all locks were obtained successfully
+ * and False if we still need to wait.
+ */
+
+ if(blocking_lock_record_process(blr)) {
+ struct byte_range_lock *br_lck = brl_get_locks(talloc_tos(), fsp);
+
+ if (br_lck) {
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+ TALLOC_FREE(br_lck);
+ }
+
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ recalc_timeout = True;
+ change_to_root_user();
+ continue;
+ }
+
+ change_to_root_user();
+
+ /*
+ * We couldn't get the locks for this record on the list.
+ * If the time has expired, return a lock error.
+ */
+
+ if (!timeval_is_zero(&blr->expire_time) && timeval_compare(&blr->expire_time, &tv_curr) <= 0) {
+ struct byte_range_lock *br_lck = brl_get_locks(talloc_tos(), fsp);
+
+ /*
+ * Lock expired - throw away all previously
+ * obtained locks and return lock error.
+ */
+
+ if (br_lck) {
+ DEBUG(5,("process_blocking_lock_queue: pending lock fnum = %d for file %s timed out.\n",
+ fsp->fnum, fsp->fsp_name ));
+
+ brl_lock_cancel(br_lck,
+ blr->lock_pid,
+ procid_self(),
+ blr->offset,
+ blr->count,
+ blr->lock_flav);
+ TALLOC_FREE(br_lck);
+ }
+
+ blocking_lock_reply_error(blr,NT_STATUS_FILE_LOCK_CONFLICT);
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ free_blocking_lock_record(blr);
+ recalc_timeout = True;
+ }
+ }
+
+ if (recalc_timeout) {
+ recalc_brl_timeout();
+ }
+}
+
+/****************************************************************************
+ Handle a cancel message. Lock already moved onto the cancel queue.
+*****************************************************************************/
+
+#define MSG_BLOCKING_LOCK_CANCEL_SIZE (sizeof(blocking_lock_record *) + sizeof(NTSTATUS))
+
+static void process_blocking_lock_cancel_message(struct messaging_context *ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ NTSTATUS err;
+ const char *msg = (const char *)data->data;
+ blocking_lock_record *blr;
+
+ if (data->data == NULL) {
+ smb_panic("process_blocking_lock_cancel_message: null msg");
+ }
+
+ if (data->length != MSG_BLOCKING_LOCK_CANCEL_SIZE) {
+ DEBUG(0, ("process_blocking_lock_cancel_message: "
+ "Got invalid msg len %d\n", (int)data->length));
+ smb_panic("process_blocking_lock_cancel_message: bad msg");
+ }
+
+ memcpy(&blr, msg, sizeof(blr));
+ memcpy(&err, &msg[sizeof(blr)], sizeof(NTSTATUS));
+
+ DEBUG(10,("process_blocking_lock_cancel_message: returning error %s\n",
+ nt_errstr(err) ));
+
+ blocking_lock_reply_error(blr, err);
+ DLIST_REMOVE(blocking_lock_cancelled_queue, blr);
+ free_blocking_lock_record(blr);
+}
+
+/****************************************************************************
+ Send ourselves a blocking lock cancelled message. Handled asynchronously above.
+*****************************************************************************/
+
+bool blocking_lock_cancel(files_struct *fsp,
+ uint32 lock_pid,
+ SMB_BIG_UINT offset,
+ SMB_BIG_UINT count,
+ enum brl_flavour lock_flav,
+ unsigned char locktype,
+ NTSTATUS err)
+{
+ static bool initialized;
+ char msg[MSG_BLOCKING_LOCK_CANCEL_SIZE];
+ blocking_lock_record *blr;
+
+ if (!initialized) {
+ /* Register our message. */
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_BLOCKING_LOCK_CANCEL,
+ process_blocking_lock_cancel_message);
+
+ initialized = True;
+ }
+
+ for (blr = blocking_lock_queue; blr; blr = blr->next) {
+ if (fsp == blr->fsp &&
+ lock_pid == blr->lock_pid &&
+ offset == blr->offset &&
+ count == blr->count &&
+ lock_flav == blr->lock_flav) {
+ break;
+ }
+ }
+
+ if (!blr) {
+ return False;
+ }
+
+ /* Check the flags are right. */
+ if (blr->com_type == SMBlockingX &&
+ (locktype & LOCKING_ANDX_LARGE_FILES) !=
+ (CVAL(blr->inbuf,smb_vwv3) & LOCKING_ANDX_LARGE_FILES)) {
+ return False;
+ }
+
+ /* Move to cancelled queue. */
+ DLIST_REMOVE(blocking_lock_queue, blr);
+ DLIST_ADD(blocking_lock_cancelled_queue, blr);
+
+ /* Create the message. */
+ memcpy(msg, &blr, sizeof(blr));
+ memcpy(&msg[sizeof(blr)], &err, sizeof(NTSTATUS));
+
+ messaging_send_buf(smbd_messaging_context(), procid_self(),
+ MSG_SMB_BLOCKING_LOCK_CANCEL,
+ (uint8 *)&msg, sizeof(msg));
+
+ return True;
+}
diff --git a/source3/smbd/change_trust_pw.c b/source3/smbd/change_trust_pw.c
new file mode 100644
index 0000000000..72a72a78b5
--- /dev/null
+++ b/source3/smbd/change_trust_pw.c
@@ -0,0 +1,101 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Periodic Trust account password changing.
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997.
+ * Copyright (C) Jeremy Allison 1998.
+ * Copyright (C) Andrew Bartlett 2001.
+ *
+ * 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"
+
+/************************************************************************
+ Change the trust account password for a domain.
+************************************************************************/
+
+NTSTATUS change_trust_account_password( const char *domain, const char *remote_machine)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct sockaddr_storage pdc_ss;
+ fstring dc_name;
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+
+ DEBUG(5,("change_trust_account_password: Attempting to change trust account password in domain %s....\n",
+ domain));
+
+ if (remote_machine == NULL || !strcmp(remote_machine, "*")) {
+ /* Use the PDC *only* for this */
+
+ if ( !get_pdc_ip(domain, &pdc_ss) ) {
+ DEBUG(0,("Can't get IP for PDC for domain %s\n", domain));
+ goto failed;
+ }
+
+ if ( !name_status_find( domain, 0x1b, 0x20, &pdc_ss, dc_name) )
+ goto failed;
+ } else {
+ /* supoport old deprecated "smbpasswd -j DOMAIN -r MACHINE" behavior */
+ fstrcpy( dc_name, remote_machine );
+ }
+
+ /* if this next call fails, then give up. We can't do
+ password changes on BDC's --jerry */
+
+ if (!NT_STATUS_IS_OK(cli_full_connection(&cli, global_myname(), dc_name,
+ NULL, 0,
+ "IPC$", "IPC",
+ "", "",
+ "", 0, Undefined, NULL))) {
+ DEBUG(0,("modify_trust_password: Connection to %s failed!\n", dc_name));
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto failed;
+ }
+
+ /*
+ * Ok - we have an anonymous connection to the IPC$ share.
+ * Now start the NT Domain stuff :-).
+ */
+
+ /* Shouldn't we open this with schannel ? JRA. */
+
+ nt_status = cli_rpc_pipe_open_noauth(
+ cli, &ndr_table_netlogon.syntax_id, &netlogon_pipe);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("modify_trust_password: unable to open the domain client session to machine %s. Error was : %s.\n",
+ dc_name, nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ cli = NULL;
+ goto failed;
+ }
+
+ nt_status = trust_pw_find_change_and_store_it(
+ netlogon_pipe, netlogon_pipe, domain);
+
+ cli_shutdown(cli);
+ cli = NULL;
+
+failed:
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("%s : change_trust_account_password: Failed to change password for domain %s.\n",
+ current_timestring(debug_ctx(), False), domain));
+ }
+ else
+ DEBUG(5,("change_trust_account_password: sucess!\n"));
+
+ return nt_status;
+}
diff --git a/source3/smbd/chgpasswd.c b/source3/smbd/chgpasswd.c
new file mode 100644
index 0000000000..2596e73380
--- /dev/null
+++ b/source3/smbd/chgpasswd.c
@@ -0,0 +1,1208 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001-2004
+
+ 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/>.
+*/
+
+/* These comments regard the code to change the user's unix password: */
+
+/* fork a child process to exec passwd and write to its
+ * tty to change a users password. This is running as the
+ * user who is attempting to change the password.
+ */
+
+/*
+ * This code was copied/borrowed and stolen from various sources.
+ * The primary source was the poppasswd.c from the authors of POPMail. This software
+ * was included as a client to change passwords using the 'passwd' program
+ * on the remote machine.
+ *
+ * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
+ * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
+ * and rights to modify, distribute or incorporate this change to the CAP suite or
+ * using it for any other reason are granted, so long as this disclaimer is left intact.
+ */
+
+/*
+ This code was hacked considerably for inclusion in Samba, primarily
+ by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
+ of the "password chat" option, which allows the easy runtime
+ specification of the expected sequence of events to change a
+ password.
+ */
+
+#include "includes.h"
+
+extern struct passdb_ops pdb_ops;
+
+static NTSTATUS check_oem_password(const char *user,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ struct samu **hnd,
+ char **pp_new_passwd);
+
+#if ALLOW_CHANGE_PASSWORD
+
+static int findpty(char **slave)
+{
+ int master = -1;
+ char *line = NULL;
+ SMB_STRUCT_DIR *dirp = NULL;
+ const char *dpname;
+
+ *slave = NULL;
+
+#if defined(HAVE_GRANTPT)
+ /* Try to open /dev/ptmx. If that fails, fall through to old method. */
+ if ((master = sys_open("/dev/ptmx", O_RDWR, 0)) >= 0) {
+ grantpt(master);
+ unlockpt(master);
+ line = (char *)ptsname(master);
+ if (line) {
+ *slave = SMB_STRDUP(line);
+ }
+
+ if (*slave == NULL) {
+ DEBUG(0,
+ ("findpty: Unable to create master/slave pty pair.\n"));
+ /* Stop fd leak on error. */
+ close(master);
+ return -1;
+ } else {
+ DEBUG(10,
+ ("findpty: Allocated slave pty %s\n", *slave));
+ return (master);
+ }
+ }
+#endif /* HAVE_GRANTPT */
+
+ line = SMB_STRDUP("/dev/ptyXX");
+ if (!line) {
+ return (-1);
+ }
+
+ dirp = sys_opendir("/dev");
+ if (!dirp) {
+ SAFE_FREE(line);
+ return (-1);
+ }
+
+ while ((dpname = readdirname(dirp)) != NULL) {
+ if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
+ DEBUG(3,
+ ("pty: try to open %s, line was %s\n", dpname,
+ line));
+ line[8] = dpname[3];
+ line[9] = dpname[4];
+ if ((master = sys_open(line, O_RDWR, 0)) >= 0) {
+ DEBUG(3, ("pty: opened %s\n", line));
+ line[5] = 't';
+ *slave = line;
+ sys_closedir(dirp);
+ return (master);
+ }
+ }
+ }
+ sys_closedir(dirp);
+ SAFE_FREE(line);
+ return (-1);
+}
+
+static int dochild(int master, const char *slavedev, const struct passwd *pass,
+ const char *passwordprogram, bool as_root)
+{
+ int slave;
+ struct termios stermios;
+ gid_t gid;
+ uid_t uid;
+ char * const eptrs[1] = { NULL };
+
+ if (pass == NULL)
+ {
+ DEBUG(0,
+ ("dochild: user doesn't exist in the UNIX password database.\n"));
+ return False;
+ }
+
+ gid = pass->pw_gid;
+ uid = pass->pw_uid;
+
+ gain_root_privilege();
+
+ /* Start new session - gets rid of controlling terminal. */
+ if (setsid() < 0)
+ {
+ DEBUG(3,
+ ("Weirdness, couldn't let go of controlling terminal\n"));
+ return (False);
+ }
+
+ /* Open slave pty and acquire as new controlling terminal. */
+ if ((slave = sys_open(slavedev, O_RDWR, 0)) < 0)
+ {
+ DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
+ return (False);
+ }
+#if defined(TIOCSCTTY) && !defined(SUNOS5)
+ /*
+ * On patched Solaris 10 TIOCSCTTY is defined but seems not to work,
+ * see the discussion under
+ * https://bugzilla.samba.org/show_bug.cgi?id=5366.
+ */
+ if (ioctl(slave, TIOCSCTTY, 0) < 0)
+ {
+ DEBUG(3, ("Error in ioctl call for slave pty\n"));
+ /* return(False); */
+ }
+#elif defined(I_PUSH) && defined(I_FIND)
+ if (ioctl(slave, I_FIND, "ptem") == 0) {
+ ioctl(slave, I_PUSH, "ptem");
+ }
+ if (ioctl(slave, I_FIND, "ldterm") == 0) {
+ ioctl(slave, I_PUSH, "ldterm");
+ }
+#endif
+
+ /* Close master. */
+ close(master);
+
+ /* Make slave stdin/out/err of child. */
+
+ if (sys_dup2(slave, STDIN_FILENO) != STDIN_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stdin\n"));
+ return (False);
+ }
+ if (sys_dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stdout\n"));
+ return (False);
+ }
+ if (sys_dup2(slave, STDERR_FILENO) != STDERR_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stderr\n"));
+ return (False);
+ }
+ if (slave > 2)
+ close(slave);
+
+ /* Set proper terminal attributes - no echo, canonical input processing,
+ no map NL to CR/NL on output. */
+
+ if (tcgetattr(0, &stermios) < 0)
+ {
+ DEBUG(3,
+ ("could not read default terminal attributes on pty\n"));
+ return (False);
+ }
+ stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ stermios.c_lflag |= ICANON;
+#ifdef ONLCR
+ stermios.c_oflag &= ~(ONLCR);
+#endif
+ if (tcsetattr(0, TCSANOW, &stermios) < 0)
+ {
+ DEBUG(3, ("could not set attributes of pty\n"));
+ return (False);
+ }
+
+ /* make us completely into the right uid */
+ if (!as_root)
+ {
+ become_user_permanently(uid, gid);
+ }
+
+ DEBUG(10,
+ ("Invoking '%s' as password change program.\n",
+ passwordprogram));
+
+ /* execl() password-change application */
+ if (execle("/bin/sh", "sh", "-c", passwordprogram, NULL, eptrs) < 0)
+ {
+ DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
+ return (False);
+ }
+ return (True);
+}
+
+static int expect(int master, char *issue, char *expected)
+{
+ char buffer[1024];
+ int attempts, timeout, nread;
+ size_t len;
+ bool match = False;
+
+ for (attempts = 0; attempts < 2; attempts++) {
+ NTSTATUS status;
+ if (!strequal(issue, ".")) {
+ if (lp_passwd_chat_debug())
+ DEBUG(100, ("expect: sending [%s]\n", issue));
+
+ if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) {
+ DEBUG(2,("expect: (short) write returned %d\n",
+ (int)len ));
+ return False;
+ }
+ }
+
+ if (strequal(expected, "."))
+ return True;
+
+ /* Initial timeout. */
+ timeout = lp_passwd_chat_timeout() * 1000;
+ nread = 0;
+ buffer[nread] = 0;
+
+ while (True) {
+ status = read_socket_with_timeout(
+ master, buffer + nread, 1,
+ sizeof(buffer) - nread - 1,
+ timeout, &len);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ nread += len;
+ buffer[nread] = 0;
+
+ {
+ /* Eat leading/trailing whitespace before match. */
+ char *str = SMB_STRDUP(buffer);
+ if (!str) {
+ DEBUG(2,("expect: ENOMEM\n"));
+ return False;
+ }
+ trim_char(str, ' ', ' ');
+
+ if ((match = unix_wild_match(expected, str)) == True) {
+ /* Now data has started to return, lower timeout. */
+ timeout = lp_passwd_chat_timeout() * 100;
+ }
+ SAFE_FREE(str);
+ }
+ }
+
+ if (lp_passwd_chat_debug())
+ DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
+ expected, buffer, match ? "yes" : "no" ));
+
+ if (match)
+ break;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("expect: %s\n", nt_errstr(status)));
+ return False;
+ }
+ }
+
+ DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
+ return match;
+}
+
+static void pwd_sub(char *buf)
+{
+ all_string_sub(buf, "\\n", "\n", 0);
+ all_string_sub(buf, "\\r", "\r", 0);
+ all_string_sub(buf, "\\s", " ", 0);
+ all_string_sub(buf, "\\t", "\t", 0);
+}
+
+static int talktochild(int master, const char *seq)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int count = 0;
+ char *issue;
+ char *expected;
+
+ issue = talloc_strdup(frame, ".");
+ if (!issue) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ while (next_token_talloc(frame, &seq, &expected, NULL)) {
+ pwd_sub(expected);
+ count++;
+
+ if (!expect(master, issue, expected)) {
+ DEBUG(3, ("Response %d incorrect\n", count));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!next_token_talloc(frame, &seq, &issue, NULL)) {
+ issue = talloc_strdup(frame, ".");
+ if (!issue) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+ pwd_sub(issue);
+ }
+
+ if (!strequal(issue, ".")) {
+ /* we have one final issue to send */
+ expected = talloc_strdup(frame, ".");
+ if (!expected) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ if (!expect(master, issue, expected)) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ }
+ TALLOC_FREE(frame);
+ return (count > 0);
+}
+
+static bool chat_with_program(char *passwordprogram, const struct passwd *pass,
+ char *chatsequence, bool as_root)
+{
+ char *slavedev = NULL;
+ int master;
+ pid_t pid, wpid;
+ int wstat;
+ bool chstat = False;
+
+ if (pass == NULL) {
+ DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
+ return False;
+ }
+
+ /* allocate a pseudo-terminal device */
+ if ((master = findpty(&slavedev)) < 0) {
+ DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name));
+ return (False);
+ }
+
+ /*
+ * We need to temporarily stop CatchChild from eating
+ * SIGCLD signals as it also eats the exit status code. JRA.
+ */
+
+ CatchChildLeaveStatus();
+
+ if ((pid = sys_fork()) < 0) {
+ DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name));
+ SAFE_FREE(slavedev);
+ close(master);
+ CatchChild();
+ return (False);
+ }
+
+ /* we now have a pty */
+ if (pid > 0) { /* This is the parent process */
+ /* Don't need this anymore in parent. */
+ SAFE_FREE(slavedev);
+
+ if ((chstat = talktochild(master, chatsequence)) == False) {
+ DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name));
+ kill(pid, SIGKILL); /* be sure to end this process */
+ }
+
+ while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
+ if (errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ break;
+ }
+
+ if (wpid < 0) {
+ DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n"));
+ close(master);
+ CatchChild();
+ return (False);
+ }
+
+ /*
+ * Go back to ignoring children.
+ */
+ CatchChild();
+
+ close(master);
+
+ if (pid != wpid) {
+ DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n"));
+ return (False);
+ }
+ if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) {
+ DEBUG(3, ("chat_with_program: The process exited with status %d \
+while we were waiting\n", WEXITSTATUS(wstat)));
+ return (False);
+ }
+#if defined(WIFSIGNALLED) && defined(WTERMSIG)
+ else if (WIFSIGNALLED(wstat)) {
+ DEBUG(3, ("chat_with_program: The process was killed by signal %d \
+while we were waiting\n", WTERMSIG(wstat)));
+ return (False);
+ }
+#endif
+ } else {
+ /* CHILD */
+
+ /*
+ * Lose any elevated privileges.
+ */
+ drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
+ drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ /* make sure it doesn't freeze */
+ alarm(20);
+
+ if (as_root)
+ become_root();
+
+ DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name,
+ (int)getuid(), (int)getgid(), BOOLSTR(as_root) ));
+ chstat = dochild(master, slavedev, pass, passwordprogram, as_root);
+
+ if (as_root)
+ unbecome_root();
+
+ /*
+ * The child should never return from dochild() ....
+ */
+
+ DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat));
+ exit(1);
+ }
+
+ if (chstat)
+ DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n",
+ (chstat ? "" : "un"), pass->pw_name));
+ return (chstat);
+}
+
+bool chgpasswd(const char *name, const struct passwd *pass,
+ const char *oldpass, const char *newpass, bool as_root)
+{
+ char *passwordprogram = NULL;
+ char *chatsequence = NULL;
+ size_t i;
+ size_t len;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!oldpass) {
+ oldpass = "";
+ }
+
+ DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
+#endif
+
+ /* Take the passed information and test it for minimum criteria */
+
+ /* Password is same as old password */
+ if (strcmp(oldpass, newpass) == 0) {
+ /* don't allow same password */
+ DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */
+ return (False); /* inform the user */
+ }
+
+ /*
+ * Check the old and new passwords don't contain any control
+ * characters.
+ */
+
+ len = strlen(oldpass);
+ for (i = 0; i < len; i++) {
+ if (iscntrl((int)oldpass[i])) {
+ DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
+ return False;
+ }
+ }
+
+ len = strlen(newpass);
+ for (i = 0; i < len; i++) {
+ if (iscntrl((int)newpass[i])) {
+ DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
+ return False;
+ }
+ }
+
+#ifdef WITH_PAM
+ if (lp_pam_password_change()) {
+ bool ret;
+#ifdef HAVE_SETLOCALE
+ const char *prevlocale = setlocale(LC_ALL, "C");
+#endif
+
+ if (as_root)
+ become_root();
+
+ if (pass) {
+ ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
+ } else {
+ ret = smb_pam_passchange(name, oldpass, newpass);
+ }
+
+ if (as_root)
+ unbecome_root();
+
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_ALL, prevlocale);
+#endif
+
+ return ret;
+ }
+#endif
+
+ /* A non-PAM password change just doen't make sense without a valid local user */
+
+ if (pass == NULL) {
+ DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
+ return false;
+ }
+
+ passwordprogram = talloc_strdup(ctx, lp_passwd_program());
+ if (!passwordprogram || !*passwordprogram) {
+ DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
+ return false;
+ }
+ chatsequence = talloc_strdup(ctx, lp_passwd_chat());
+ if (!chatsequence || !*chatsequence) {
+ DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
+ return false;
+ }
+
+ if (as_root) {
+ /* The password program *must* contain the user name to work. Fail if not. */
+ if (strstr_m(passwordprogram, "%u") == NULL) {
+ DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
+the string %%u, and the given string %s does not.\n", passwordprogram ));
+ return false;
+ }
+ }
+
+ passwordprogram = talloc_string_sub(ctx, passwordprogram, "%u", name);
+ if (!passwordprogram) {
+ return false;
+ }
+
+ /* note that we do NOT substitute the %o and %n in the password program
+ as this would open up a security hole where the user could use
+ a new password containing shell escape characters */
+
+ chatsequence = talloc_string_sub(ctx, chatsequence, "%u", name);
+ if (!chatsequence) {
+ return false;
+ }
+ chatsequence = talloc_all_string_sub(ctx,
+ chatsequence,
+ "%o",
+ oldpass);
+ if (!chatsequence) {
+ return false;
+ }
+ chatsequence = talloc_all_string_sub(ctx,
+ chatsequence,
+ "%n",
+ newpass);
+ return chat_with_program(passwordprogram,
+ pass,
+ chatsequence,
+ as_root);
+}
+
+#else /* ALLOW_CHANGE_PASSWORD */
+
+bool chgpasswd(const char *name, const struct passwd *pass,
+ const char *oldpass, const char *newpass, bool as_root)
+{
+ DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
+ return (False);
+}
+#endif /* ALLOW_CHANGE_PASSWORD */
+
+/***********************************************************
+ Code to check the lanman hashed password.
+************************************************************/
+
+bool check_lanman_password(char *user, uchar * pass1,
+ uchar * pass2, struct samu **hnd)
+{
+ uchar unenc_new_pw[16];
+ uchar unenc_old_pw[16];
+ struct samu *sampass = NULL;
+ uint32 acct_ctrl;
+ const uint8 *lanman_pw;
+ bool ret;
+
+ if ( !(sampass = samu_new(NULL)) ) {
+ DEBUG(0, ("samu_new() failed!\n"));
+ return False;
+ }
+
+ become_root();
+ ret = pdb_getsampwnam(sampass, user);
+ unbecome_root();
+
+ if (ret == False) {
+ DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
+ TALLOC_FREE(sampass);
+ return False;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl (sampass);
+ lanman_pw = pdb_get_lanman_passwd (sampass);
+
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
+ TALLOC_FREE(sampass);
+ return False;
+ }
+
+ if (lanman_pw == NULL) {
+ if (acct_ctrl & ACB_PWNOTREQ) {
+ /* this saves the pointer for the caller */
+ *hnd = sampass;
+ return True;
+ } else {
+ DEBUG(0, ("check_lanman_password: no lanman password !\n"));
+ TALLOC_FREE(sampass);
+ return False;
+ }
+ }
+
+ /* Get the new lanman hash. */
+ D_P16(lanman_pw, pass2, unenc_new_pw);
+
+ /* Use this to get the old lanman hash. */
+ D_P16(unenc_new_pw, pass1, unenc_old_pw);
+
+ /* Check that the two old passwords match. */
+ if (memcmp(lanman_pw, unenc_old_pw, 16)) {
+ DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
+ TALLOC_FREE(sampass);
+ return False;
+ }
+
+ /* this saves the pointer for the caller */
+ *hnd = sampass;
+ return True;
+}
+
+/***********************************************************
+ Code to change the lanman hashed password.
+ It nulls out the NT hashed password as it will
+ no longer be valid.
+ NOTE this function is designed to be called as root. Check the old password
+ is correct before calling. JRA.
+************************************************************/
+
+bool change_lanman_password(struct samu *sampass, uchar *pass2)
+{
+ static uchar null_pw[16];
+ uchar unenc_new_pw[16];
+ bool ret;
+ uint32 acct_ctrl;
+ const uint8 *pwd;
+
+ if (sampass == NULL) {
+ DEBUG(0,("change_lanman_password: no smb password entry.\n"));
+ return False;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ pwd = pdb_get_lanman_passwd(sampass);
+
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(0,("change_lanman_password: account %s disabled.\n",
+ pdb_get_username(sampass)));
+ return False;
+ }
+
+ if (pwd == NULL) {
+ if (acct_ctrl & ACB_PWNOTREQ) {
+ uchar no_pw[14];
+ memset(no_pw, '\0', 14);
+ E_P16(no_pw, null_pw);
+
+ /* Get the new lanman hash. */
+ D_P16(null_pw, pass2, unenc_new_pw);
+ } else {
+ DEBUG(0,("change_lanman_password: no lanman password !\n"));
+ return False;
+ }
+ } else {
+ /* Get the new lanman hash. */
+ D_P16(pwd, pass2, unenc_new_pw);
+ }
+
+ if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
+ return False;
+ }
+
+ if (!pdb_set_nt_passwd (sampass, NULL, PDB_CHANGED)) {
+ return False; /* We lose the NT hash. Sorry. */
+ }
+
+ if (!pdb_set_pass_last_set_time (sampass, time(NULL), PDB_CHANGED)) {
+ TALLOC_FREE(sampass);
+ /* Not quite sure what this one qualifies as, but this will do */
+ return False;
+ }
+
+ /* Now flush the sam_passwd struct to persistent storage */
+ ret = NT_STATUS_IS_OK(pdb_update_sam_account (sampass));
+
+ return ret;
+}
+
+/***********************************************************
+ Code to check and change the OEM hashed password.
+************************************************************/
+
+NTSTATUS pass_oem_change(char *user,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ uint32 *reject_reason)
+{
+ char *new_passwd = NULL;
+ struct samu *sampass = NULL;
+ NTSTATUS nt_status = check_oem_password(user,
+ password_encrypted_with_lm_hash,
+ old_lm_hash_encrypted,
+ password_encrypted_with_nt_hash,
+ old_nt_hash_encrypted,
+ &sampass,
+ &new_passwd);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /* We've already checked the old password here.... */
+ become_root();
+ nt_status = change_oem_password(sampass, NULL, new_passwd, True, reject_reason);
+ unbecome_root();
+
+ memset(new_passwd, 0, strlen(new_passwd));
+
+ TALLOC_FREE(sampass);
+
+ return nt_status;
+}
+
+/***********************************************************
+ Decrypt and verify a user password change.
+
+ The 516 byte long buffers are encrypted with the old NT and
+ old LM passwords, and if the NT passwords are present, both
+ buffers contain a unicode string.
+
+ After decrypting the buffers, check the password is correct by
+ matching the old hashed passwords with the passwords in the passdb.
+
+************************************************************/
+
+static NTSTATUS check_oem_password(const char *user,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ struct samu **hnd,
+ char **pp_new_passwd)
+{
+ static uchar null_pw[16];
+ static uchar null_ntpw[16];
+ struct samu *sampass = NULL;
+ uint8 *password_encrypted;
+ const uint8 *encryption_key;
+ const uint8 *lanman_pw, *nt_pw;
+ uint32 acct_ctrl;
+ uint32 new_pw_len;
+ uchar new_nt_hash[16];
+ uchar new_lm_hash[16];
+ uchar verifier[16];
+ char no_pw[2];
+ bool ret;
+
+ bool nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
+ bool lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
+
+ *hnd = NULL;
+
+ if ( !(sampass = samu_new( NULL )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwnam(sampass, user);
+ unbecome_root();
+
+ if (ret == False) {
+ DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) {
+ /* construct a null password (in case one is needed */
+ no_pw[0] = 0;
+ no_pw[1] = 0;
+ nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
+ lanman_pw = null_pw;
+ nt_pw = null_pw;
+
+ } else {
+ /* save pointers to passwords so we don't have to keep looking them up */
+ if (lp_lanman_auth()) {
+ lanman_pw = pdb_get_lanman_passwd(sampass);
+ } else {
+ lanman_pw = NULL;
+ }
+ nt_pw = pdb_get_nt_passwd(sampass);
+ }
+
+ if (nt_pw && nt_pass_set) {
+ /* IDEAL Case: passwords are in unicode, and we can
+ * read use the password encrypted with the NT hash
+ */
+ password_encrypted = password_encrypted_with_nt_hash;
+ encryption_key = nt_pw;
+ } else if (lanman_pw && lm_pass_set) {
+ /* password may still be in unicode, but use LM hash version */
+ password_encrypted = password_encrypted_with_lm_hash;
+ encryption_key = lanman_pw;
+ } else if (nt_pass_set) {
+ DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n",
+ user));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ } else if (lm_pass_set) {
+ if (lp_lanman_auth()) {
+ DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n",
+ user));
+ } else {
+ DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n",
+ user));
+ }
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ } else {
+ DEBUG(1, ("password change requested for user %s, but no password supplied!\n",
+ user));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * Decrypt the password with the key
+ */
+ SamOEMhash( password_encrypted, encryption_key, 516);
+
+ if (!decode_pw_buffer(talloc_tos(),
+ password_encrypted,
+ pp_new_passwd,
+ &new_pw_len,
+ nt_pass_set ? STR_UNICODE : STR_ASCII)) {
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * To ensure we got the correct new password, hash it and
+ * use it as a key to test the passed old password.
+ */
+
+ if (nt_pass_set) {
+ /* NT passwords, verify the NT hash. */
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ memset(new_nt_hash, '\0', 16);
+ E_md4hash(*pp_new_passwd, new_nt_hash);
+
+ if (nt_pw) {
+ /*
+ * check the NT verifier
+ */
+ E_old_pw_hash(new_nt_hash, nt_pw, verifier);
+ if (memcmp(verifier, old_nt_hash_encrypted, 16)) {
+ DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /* We could check the LM password here, but there is
+ * little point, we already know the password is
+ * correct, and the LM password might not even be
+ * present. */
+
+ /* Further, LM hash generation algorithms
+ * differ with charset, so we could
+ * incorrectly fail a perfectly valid password
+ * change */
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ *hnd = sampass;
+ return NT_STATUS_OK;
+ }
+
+ if (lanman_pw) {
+ /*
+ * check the lm verifier
+ */
+ E_old_pw_hash(new_nt_hash, lanman_pw, verifier);
+ if (memcmp(verifier, old_lm_hash_encrypted, 16)) {
+ DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ *hnd = sampass;
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (lanman_pw && lm_pass_set) {
+
+ E_deshash(*pp_new_passwd, new_lm_hash);
+
+ /*
+ * check the lm verifier
+ */
+ E_old_pw_hash(new_lm_hash, lanman_pw, verifier);
+ if (memcmp(verifier, old_lm_hash_encrypted, 16)) {
+ DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ *hnd = sampass;
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ TALLOC_FREE(sampass);
+ return NT_STATUS_WRONG_PASSWORD;
+}
+
+/***********************************************************
+ This routine takes the given password and checks it against
+ the password history. Returns True if this password has been
+ found in the history list.
+************************************************************/
+
+static bool check_passwd_history(struct samu *sampass, const char *plaintext)
+{
+ uchar new_nt_p16[NT_HASH_LEN];
+ uchar zero_md5_nt_pw[SALTED_MD5_HASH_LEN];
+ const uint8 *nt_pw;
+ const uint8 *pwhistory;
+ bool found = False;
+ int i;
+ uint32 pwHisLen, curr_pwHisLen;
+
+ pdb_get_account_policy(AP_PASSWORD_HISTORY, &pwHisLen);
+ if (pwHisLen == 0) {
+ return False;
+ }
+
+ pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen);
+ if (!pwhistory || curr_pwHisLen == 0) {
+ return False;
+ }
+
+ /* Only examine the minimum of the current history len and
+ the stored history len. Avoids race conditions. */
+ pwHisLen = MIN(pwHisLen,curr_pwHisLen);
+
+ nt_pw = pdb_get_nt_passwd(sampass);
+
+ E_md4hash(plaintext, new_nt_p16);
+
+ if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) {
+ DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n",
+ pdb_get_username(sampass) ));
+ return True;
+ }
+
+ dump_data(100, new_nt_p16, NT_HASH_LEN);
+ dump_data(100, pwhistory, PW_HISTORY_ENTRY_LEN*pwHisLen);
+
+ memset(zero_md5_nt_pw, '\0', SALTED_MD5_HASH_LEN);
+ for (i=0; i<pwHisLen; i++) {
+ uchar new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN];
+ const uchar *current_salt = &pwhistory[i*PW_HISTORY_ENTRY_LEN];
+ const uchar *old_nt_pw_salted_md5_hash = &pwhistory[(i*PW_HISTORY_ENTRY_LEN)+
+ PW_HISTORY_SALT_LEN];
+ if (!memcmp(zero_md5_nt_pw, old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) {
+ /* Ignore zero valued entries. */
+ continue;
+ }
+ /* Create salted versions of new to compare. */
+ E_md5hash(current_salt, new_nt_p16, new_nt_pw_salted_md5_hash);
+
+ if (!memcmp(new_nt_pw_salted_md5_hash, old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) {
+ DEBUG(1,("check_passwd_history: proposed new password for user %s found in history list !\n",
+ pdb_get_username(sampass) ));
+ found = True;
+ break;
+ }
+ }
+ return found;
+}
+
+/***********************************************************
+ Code to change the oem password. Changes both the lanman
+ and NT hashes. Old_passwd is almost always NULL.
+ NOTE this function is designed to be called as root. Check the old password
+ is correct before calling. JRA.
+************************************************************/
+
+NTSTATUS change_oem_password(struct samu *hnd, char *old_passwd, char *new_passwd, bool as_root, uint32 *samr_reject_reason)
+{
+ uint32 min_len;
+ uint32 refuse;
+ struct passwd *pass = NULL;
+ const char *username = pdb_get_username(hnd);
+ time_t can_change_time = pdb_get_pass_can_change_time(hnd);
+
+ if (samr_reject_reason) {
+ *samr_reject_reason = Undefined;
+ }
+
+ /* check to see if the secdesc has previously been set to disallow */
+ if (!pdb_get_pass_can_change(hnd)) {
+ DEBUG(1, ("user %s does not have permissions to change password\n", username));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+
+ /* check to see if it is a Machine account and if the policy
+ * denies machines to change the password. *
+ * Should we deny also SRVTRUST and/or DOMSTRUST ? .SSS. */
+ if (pdb_get_acct_ctrl(hnd) & ACB_WSTRUST) {
+ if (pdb_get_account_policy(AP_REFUSE_MACHINE_PW_CHANGE, &refuse) && refuse) {
+ DEBUG(1, ("Machine %s cannot change password now, "
+ "denied by Refuse Machine Password Change policy\n",
+ username));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+ }
+
+ /* removed calculation here, becuase passdb now calculates
+ based on policy. jmcd */
+ if ((can_change_time != 0) && (time(NULL) < can_change_time)) {
+ DEBUG(1, ("user %s cannot change password now, must "
+ "wait until %s\n", username,
+ http_timestring(can_change_time)));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_OTHER;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+
+ if (pdb_get_account_policy(AP_MIN_PASSWORD_LEN, &min_len) && (str_charnum(new_passwd) < min_len)) {
+ DEBUG(1, ("user %s cannot change password - password too short\n",
+ username));
+ DEBUGADD(1, (" account policy min password len = %d\n", min_len));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_TOO_SHORT;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+/* return NT_STATUS_PWD_TOO_SHORT; */
+ }
+
+ if (check_passwd_history(hnd,new_passwd)) {
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ pass = Get_Pwnam_alloc(talloc_tos(), username);
+ if (!pass) {
+ DEBUG(1, ("change_oem_password: Username %s does not exist in system !?!\n", username));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Use external script to check password complexity */
+ if (lp_check_password_script() && *(lp_check_password_script())) {
+ int check_ret;
+
+ check_ret = smbrunsecret(lp_check_password_script(), new_passwd);
+ DEBUG(5, ("change_oem_password: check password script (%s) returned [%d]\n", lp_check_password_script(), check_ret));
+
+ if (check_ret != 0) {
+ DEBUG(1, ("change_oem_password: check password script said new password is not good enough!\n"));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAMR_REJECT_COMPLEXITY;
+ }
+ TALLOC_FREE(pass);
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+ /*
+ * If unix password sync was requested, attempt to change
+ * the /etc/passwd database first. Return failure if this cannot
+ * be done.
+ *
+ * This occurs before the oem change, because we don't want to
+ * update it if chgpasswd failed.
+ *
+ * Conditional on lp_unix_password_sync() because we don't want
+ * to touch the unix db unless we have admin permission.
+ */
+
+ if(lp_unix_password_sync() &&
+ !chgpasswd(username, pass, old_passwd, new_passwd, as_root)) {
+ TALLOC_FREE(pass);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ TALLOC_FREE(pass);
+
+ if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Now write it into the file. */
+ return pdb_update_sam_account (hnd);
+}
diff --git a/source3/smbd/close.c b/source3/smbd/close.c
new file mode 100644
index 0000000000..818b4c70a8
--- /dev/null
+++ b/source3/smbd/close.c
@@ -0,0 +1,772 @@
+/*
+ Unix SMB/CIFS implementation.
+ file closing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2005
+
+ 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"
+
+extern struct current_user current_user;
+
+/****************************************************************************
+ Run a file if it is a magic script.
+****************************************************************************/
+
+static void check_magic(struct files_struct *fsp)
+{
+ int ret;
+ const char *magic_output = NULL;
+ SMB_STRUCT_STAT st;
+ int tmp_fd, outfd;
+ TALLOC_CTX *ctx = NULL;
+ const char *p;
+ struct connection_struct *conn = fsp->conn;
+
+ if (!*lp_magicscript(SNUM(conn))) {
+ return;
+ }
+
+ DEBUG(5,("checking magic for %s\n",fsp->fsp_name));
+
+ if (!(p = strrchr_m(fsp->fsp_name,'/'))) {
+ p = fsp->fsp_name;
+ } else {
+ p++;
+ }
+
+ if (!strequal(lp_magicscript(SNUM(conn)),p)) {
+ return;
+ }
+
+ ctx = talloc_stackframe();
+
+ if (*lp_magicoutput(SNUM(conn))) {
+ magic_output = lp_magicoutput(SNUM(conn));
+ } else {
+ magic_output = talloc_asprintf(ctx,
+ "%s.out",
+ fsp->fsp_name);
+ }
+ if (!magic_output) {
+ TALLOC_FREE(ctx);
+ return;
+ }
+
+ chmod(fsp->fsp_name,0755);
+ ret = smbrun(fsp->fsp_name,&tmp_fd);
+ DEBUG(3,("Invoking magic command %s gave %d\n",
+ fsp->fsp_name,ret));
+
+ unlink(fsp->fsp_name);
+ if (ret != 0 || tmp_fd == -1) {
+ if (tmp_fd != -1) {
+ close(tmp_fd);
+ }
+ TALLOC_FREE(ctx);
+ return;
+ }
+ outfd = open(magic_output, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (outfd == -1) {
+ close(tmp_fd);
+ TALLOC_FREE(ctx);
+ return;
+ }
+
+ if (sys_fstat(tmp_fd,&st) == -1) {
+ close(tmp_fd);
+ close(outfd);
+ return;
+ }
+
+ transfer_file(tmp_fd,outfd,(SMB_OFF_T)st.st_size);
+ close(tmp_fd);
+ close(outfd);
+ TALLOC_FREE(ctx);
+}
+
+/****************************************************************************
+ Common code to close a file or a directory.
+****************************************************************************/
+
+static NTSTATUS close_filestruct(files_struct *fsp)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ connection_struct *conn = fsp->conn;
+
+ if (fsp->fh->fd != -1) {
+ if(flush_write_cache(fsp, CLOSE_FLUSH) == -1) {
+ status = map_nt_error_from_unix(errno);
+ }
+ delete_write_cache(fsp);
+ }
+
+ conn->num_files_open--;
+ return status;
+}
+
+/****************************************************************************
+ If any deferred opens are waiting on this close, notify them.
+****************************************************************************/
+
+static void notify_deferred_opens(struct share_mode_lock *lck)
+{
+ int i;
+
+ for (i=0; i<lck->num_share_modes; i++) {
+ struct share_mode_entry *e = &lck->share_modes[i];
+
+ if (!is_deferred_open_entry(e)) {
+ continue;
+ }
+
+ if (procid_is_me(&e->pid)) {
+ /*
+ * We need to notify ourself to retry the open. Do
+ * this by finding the queued SMB record, moving it to
+ * the head of the queue and changing the wait time to
+ * zero.
+ */
+ schedule_deferred_open_smb_message(e->op_mid);
+ } else {
+ char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+ share_mode_entry_to_message(msg, e);
+
+ messaging_send_buf(smbd_messaging_context(),
+ e->pid, MSG_SMB_OPEN_RETRY,
+ (uint8 *)msg,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ }
+ }
+}
+
+/****************************************************************************
+ Delete all streams
+****************************************************************************/
+
+static NTSTATUS delete_all_streams(connection_struct *conn, const char *fname)
+{
+ struct stream_struct *stream_info;
+ int i;
+ unsigned int num_streams;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = SMB_VFS_STREAMINFO(conn, NULL, fname, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("SMB_VFS_STREAMINFO failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("delete_all_streams found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ int res;
+ char *streamname;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ continue;
+ }
+
+ streamname = talloc_asprintf(talloc_tos(), "%s%s", fname,
+ stream_info[i].name);
+
+ if (streamname == NULL) {
+ DEBUG(0, ("talloc_aprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ res = SMB_VFS_UNLINK(conn, streamname);
+
+ TALLOC_FREE(streamname);
+
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10, ("Could not delete stream %s: %s\n",
+ streamname, strerror(errno)));
+ break;
+ }
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Deal with removing a share mode on last close.
+****************************************************************************/
+
+static NTSTATUS close_remove_share_mode(files_struct *fsp,
+ enum file_close_type close_type)
+{
+ connection_struct *conn = fsp->conn;
+ bool delete_file = false;
+ bool changed_user = false;
+ struct share_mode_lock *lck;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+ struct file_id id;
+
+ /*
+ * Lock the share entries, and determine if we should delete
+ * on close. If so delete whilst the lock is still in effect.
+ * This prevents race conditions with the file being created. JRA.
+ */
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+
+ if (lck == NULL) {
+ DEBUG(0, ("close_remove_share_mode: Could not get share mode "
+ "lock for file %s\n", fsp->fsp_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp->write_time_forced) {
+ set_close_write_time(fsp, lck->changed_write_time);
+ }
+
+ if (!del_share_mode(lck, fsp)) {
+ DEBUG(0, ("close_remove_share_mode: Could not delete share "
+ "entry for file %s\n", fsp->fsp_name));
+ }
+
+ if (fsp->initial_delete_on_close && (lck->delete_token == NULL)) {
+ bool became_user = False;
+
+ /* Initial delete on close was set and no one else
+ * wrote a real delete on close. */
+
+ if (current_user.vuid != fsp->vuid) {
+ become_user(conn, fsp->vuid);
+ became_user = True;
+ }
+ set_delete_on_close_lck(lck, True, &current_user.ut);
+ if (became_user) {
+ unbecome_user();
+ }
+ }
+
+ delete_file = lck->delete_on_close;
+
+ if (delete_file) {
+ int i;
+ /* See if others still have the file open. If this is the
+ * case, then don't delete. If all opens are POSIX delete now. */
+ for (i=0; i<lck->num_share_modes; i++) {
+ struct share_mode_entry *e = &lck->share_modes[i];
+ if (is_valid_share_mode_entry(e)) {
+ if (fsp->posix_open && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) {
+ continue;
+ }
+ delete_file = False;
+ break;
+ }
+ }
+ }
+
+ /* Notify any deferred opens waiting on this close. */
+ notify_deferred_opens(lck);
+ reply_to_oplock_break_requests(fsp);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ if (!(close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE)
+ || !delete_file
+ || (lck->delete_token == NULL)) {
+ TALLOC_FREE(lck);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Ok, we have to delete the file
+ */
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set "
+ "- deleting file.\n", fsp->fsp_name));
+
+ /*
+ * Don't try to update the write time when we delete the file
+ */
+ fsp->update_write_time_on_close = false;
+
+ if (!unix_token_equal(lck->delete_token, &current_user.ut)) {
+ /* Become the user who requested the delete. */
+
+ DEBUG(5,("close_remove_share_mode: file %s. "
+ "Change user to uid %u\n",
+ fsp->fsp_name,
+ (unsigned int)lck->delete_token->uid));
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_remove_share_mode: file %s. failed to push "
+ "sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck->delete_token->uid,
+ lck->delete_token->gid,
+ lck->delete_token->ngroups,
+ lck->delete_token->groups,
+ NULL);
+
+ changed_user = true;
+ }
+
+ /* We can only delete the file if the name we have is still valid and
+ hasn't been renamed. */
+
+ if (fsp->posix_open) {
+ ret = SMB_VFS_LSTAT(conn,fsp->fsp_name,&sbuf);
+ } else {
+ ret = SMB_VFS_STAT(conn,fsp->fsp_name,&sbuf);
+ }
+
+ if (ret != 0) {
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and stat failed with error %s\n",
+ fsp->fsp_name, strerror(errno) ));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ id = vfs_file_id_from_sbuf(conn, &sbuf);
+
+ if (!file_id_equal(&fsp->file_id, &id)) {
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and dev and/or inode does not match\n",
+ fsp->fsp_name ));
+ DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, "
+ "stat file_id %s\n",
+ fsp->fsp_name,
+ file_id_string_tos(&fsp->file_id),
+ file_id_string_tos(&id)));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && !is_ntfs_stream_name(fsp->fsp_name)) {
+
+ status = delete_all_streams(conn, fsp->fsp_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("delete_all_streams failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+
+ if (SMB_VFS_UNLINK(conn,fsp->fsp_name) != 0) {
+ /*
+ * This call can potentially fail as another smbd may
+ * have had the file open with delete on close set and
+ * deleted it when its last reference to this file
+ * went away. Hence we log this but not at debug level
+ * zero.
+ */
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and unlink failed with error %s\n",
+ fsp->fsp_name, strerror(errno) ));
+
+ status = map_nt_error_from_unix(errno);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ fsp->fsp_name);
+
+ /* As we now have POSIX opens which can unlink
+ * with other open files we may have taken
+ * this code path with more than one share mode
+ * entry - ensure we only delete once by resetting
+ * the delete on close flag. JRA.
+ */
+
+ set_delete_on_close_lck(lck, False, NULL);
+
+ done:
+
+ if (changed_user) {
+ /* unbecome user. */
+ pop_sec_ctx();
+ }
+
+ TALLOC_FREE(lck);
+ return status;
+}
+
+void set_close_write_time(struct files_struct *fsp, struct timespec ts)
+{
+ DEBUG(6,("close_write_time: %s" , time_to_asc(convert_timespec_to_time_t(ts))));
+
+ if (null_timespec(ts)) {
+ return;
+ }
+ /*
+ * if the write time on close is explict set, then don't
+ * need to fix it up to the value in the locking db
+ */
+ fsp->write_time_forced = false;
+
+ fsp->update_write_time_on_close = true;
+ fsp->close_write_time = ts;
+}
+
+static NTSTATUS update_write_time_on_close(struct files_struct *fsp)
+{
+ SMB_STRUCT_STAT sbuf;
+ struct timespec ts[2];
+ NTSTATUS status;
+
+ ZERO_STRUCT(sbuf);
+ ZERO_STRUCT(ts);
+
+ if (!fsp->update_write_time_on_close) {
+ return NT_STATUS_OK;
+ }
+
+ if (null_timespec(fsp->close_write_time)) {
+ fsp->close_write_time = timespec_current();
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ if (fsp->fh->fd != -1) {
+ if (SMB_VFS_FSTAT(fsp, &sbuf) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+ if (SMB_VFS_STAT(fsp->conn,fsp->fsp_name,&sbuf) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ if (!VALID_STAT(sbuf)) {
+ /* if it doesn't seem to be a real file */
+ return NT_STATUS_OK;
+ }
+
+ ts[1] = fsp->close_write_time;
+ status = smb_set_file_time(fsp->conn, fsp, fsp->fsp_name,
+ &sbuf, ts, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Close a file.
+
+ close_type can be NORMAL_CLOSE=0,SHUTDOWN_CLOSE,ERROR_CLOSE.
+ printing and magic scripts are only run on normal close.
+ delete on close is done on normal and shutdown close.
+****************************************************************************/
+
+static NTSTATUS close_normal_file(files_struct *fsp, enum file_close_type close_type)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS saved_status1 = NT_STATUS_OK;
+ NTSTATUS saved_status2 = NT_STATUS_OK;
+ NTSTATUS saved_status3 = NT_STATUS_OK;
+ NTSTATUS saved_status4 = NT_STATUS_OK;
+ connection_struct *conn = fsp->conn;
+
+ if (fsp->aio_write_behind) {
+ /*
+ * If we're finishing write behind on a close we can get a write
+ * error here, we must remember this.
+ */
+ int ret = wait_for_aio_completion(fsp);
+ if (ret) {
+ saved_status1 = map_nt_error_from_unix(ret);
+ }
+ } else {
+ cancel_aio_by_fsp(fsp);
+ }
+
+ /*
+ * If we're flushing on a close we can get a write
+ * error here, we must remember this.
+ */
+
+ saved_status2 = close_filestruct(fsp);
+
+ if (fsp->print_file) {
+ print_fsp_end(fsp, close_type);
+ file_free(fsp);
+ return NT_STATUS_OK;
+ }
+
+ /* If this is an old DOS or FCB open and we have multiple opens on
+ the same handle we only have one share mode. Ensure we only remove
+ the share mode on the last close. */
+
+ if (fsp->fh->ref_count == 1) {
+ /* Should we return on error here... ? */
+ saved_status3 = close_remove_share_mode(fsp, close_type);
+ }
+
+ if(fsp->oplock_type) {
+ release_file_oplock(fsp);
+ }
+
+ locking_close_file(smbd_messaging_context(), fsp);
+
+ status = fd_close(fsp);
+
+ /* check for magic scripts */
+ if (close_type == NORMAL_CLOSE) {
+ check_magic(fsp);
+ }
+
+ /*
+ * Ensure pending modtime is set after close.
+ */
+
+ saved_status4 = update_write_time_on_close(fsp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_IS_OK(saved_status1)) {
+ status = saved_status1;
+ } else if (!NT_STATUS_IS_OK(saved_status2)) {
+ status = saved_status2;
+ } else if (!NT_STATUS_IS_OK(saved_status3)) {
+ status = saved_status3;
+ } else if (!NT_STATUS_IS_OK(saved_status4)) {
+ status = saved_status4;
+ }
+ }
+
+ DEBUG(2,("%s closed file %s (numopen=%d) %s\n",
+ conn->server_info->unix_name,fsp->fsp_name,
+ conn->num_files_open,
+ nt_errstr(status) ));
+
+ file_free(fsp);
+ return status;
+}
+
+/****************************************************************************
+ Close a directory opened by an NT SMB call.
+****************************************************************************/
+
+static NTSTATUS close_directory(files_struct *fsp, enum file_close_type close_type)
+{
+ struct share_mode_lock *lck = 0;
+ bool delete_dir = False;
+ NTSTATUS status = NT_STATUS_OK;
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a directory also.
+ */
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+
+ if (lck == NULL) {
+ DEBUG(0, ("close_directory: Could not get share mode lock for %s\n", fsp->fsp_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!del_share_mode(lck, fsp)) {
+ DEBUG(0, ("close_directory: Could not delete share entry for %s\n", fsp->fsp_name));
+ }
+
+ if (fsp->initial_delete_on_close) {
+ bool became_user = False;
+
+ /* Initial delete on close was set - for
+ * directories we don't care if anyone else
+ * wrote a real delete on close. */
+
+ if (current_user.vuid != fsp->vuid) {
+ become_user(fsp->conn, fsp->vuid);
+ became_user = True;
+ }
+ send_stat_cache_delete_message(fsp->fsp_name);
+ set_delete_on_close_lck(lck, True, &current_user.ut);
+ if (became_user) {
+ unbecome_user();
+ }
+ }
+
+ delete_dir = lck->delete_on_close;
+
+ if (delete_dir) {
+ int i;
+ /* See if others still have the dir open. If this is the
+ * case, then don't delete. If all opens are POSIX delete now. */
+ for (i=0; i<lck->num_share_modes; i++) {
+ struct share_mode_entry *e = &lck->share_modes[i];
+ if (is_valid_share_mode_entry(e)) {
+ if (fsp->posix_open && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) {
+ continue;
+ }
+ delete_dir = False;
+ break;
+ }
+ }
+ }
+
+ if ((close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) &&
+ delete_dir &&
+ lck->delete_token) {
+
+ /* Become the user who requested the delete. */
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_directory: failed to push sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck->delete_token->uid,
+ lck->delete_token->gid,
+ lck->delete_token->ngroups,
+ lck->delete_token->groups,
+ NULL);
+
+ TALLOC_FREE(lck);
+
+ status = rmdir_internals(talloc_tos(),
+ fsp->conn, fsp->fsp_name);
+
+ DEBUG(5,("close_directory: %s. Delete on close was set - "
+ "deleting directory returned %s.\n",
+ fsp->fsp_name, nt_errstr(status)));
+
+ /* unbecome user. */
+ pop_sec_ctx();
+
+ /*
+ * Ensure we remove any change notify requests that would
+ * now fail as the directory has been deleted.
+ */
+
+ if(NT_STATUS_IS_OK(status)) {
+ remove_pending_change_notify_requests_by_fid(fsp, NT_STATUS_DELETE_PENDING);
+ }
+ } else {
+ TALLOC_FREE(lck);
+ remove_pending_change_notify_requests_by_fid(
+ fsp, NT_STATUS_OK);
+ }
+
+ /*
+ * Do the code common to files and directories.
+ */
+ close_filestruct(fsp);
+ file_free(fsp);
+ return status;
+}
+
+/****************************************************************************
+ Close a files_struct.
+****************************************************************************/
+
+NTSTATUS close_file(files_struct *fsp, enum file_close_type close_type)
+{
+ NTSTATUS status;
+ struct files_struct *base_fsp = fsp->base_fsp;
+
+ if(fsp->is_directory) {
+ status = close_directory(fsp, close_type);
+ } else if (fsp->fake_file_handle != NULL) {
+ status = close_fake_file(fsp);
+ } else {
+ status = close_normal_file(fsp, close_type);
+ }
+
+ if ((base_fsp != NULL) && (close_type != SHUTDOWN_CLOSE)) {
+
+ /*
+ * fsp was a stream, the base fsp can't be a stream as well
+ *
+ * For SHUTDOWN_CLOSE this is not possible here, because
+ * SHUTDOWN_CLOSE only happens from files.c which walks the
+ * complete list of files. If we mess with more than one fsp
+ * those loops will become confused.
+ */
+
+ SMB_ASSERT(base_fsp->base_fsp == NULL);
+ close_file(base_fsp, close_type);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with an (authorized) message to close a file given the share mode
+ entry.
+****************************************************************************/
+
+void msg_close_file(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ files_struct *fsp = NULL;
+ struct share_mode_entry e;
+
+ message_to_share_mode_entry(&e, (char *)data->data);
+
+ if(DEBUGLVL(10)) {
+ char *sm_str = share_mode_str(NULL, 0, &e);
+ if (!sm_str) {
+ smb_panic("talloc failed");
+ }
+ DEBUG(10,("msg_close_file: got request to close share mode "
+ "entry %s\n", sm_str));
+ TALLOC_FREE(sm_str);
+ }
+
+ fsp = file_find_dif(e.id, e.share_file_id);
+ if (!fsp) {
+ DEBUG(10,("msg_close_file: failed to find file.\n"));
+ return;
+ }
+ close_file(fsp, NORMAL_CLOSE);
+}
diff --git a/source3/smbd/conn.c b/source3/smbd/conn.c
new file mode 100644
index 0000000000..b9433bb965
--- /dev/null
+++ b/source3/smbd/conn.c
@@ -0,0 +1,329 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+
+ 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"
+
+/* The connections bitmap is expanded in increments of BITMAP_BLOCK_SZ. The
+ * maximum size of the bitmap is the largest positive integer, but you will hit
+ * the "max connections" limit, looong before that.
+ */
+#define BITMAP_BLOCK_SZ 128
+
+static connection_struct *Connections;
+
+/* number of open connections */
+static struct bitmap *bmap;
+static int num_open;
+
+/****************************************************************************
+init the conn structures
+****************************************************************************/
+void conn_init(void)
+{
+ bmap = bitmap_allocate(BITMAP_BLOCK_SZ);
+}
+
+/****************************************************************************
+return the number of open connections
+****************************************************************************/
+int conn_num_open(void)
+{
+ return num_open;
+}
+
+
+/****************************************************************************
+check if a snum is in use
+****************************************************************************/
+bool conn_snum_used(int snum)
+{
+ connection_struct *conn;
+ for (conn=Connections;conn;conn=conn->next) {
+ if (conn->params->service == snum) {
+ return(True);
+ }
+ }
+ return(False);
+}
+
+/****************************************************************************
+ Find a conn given a cnum.
+****************************************************************************/
+
+connection_struct *conn_find(unsigned cnum)
+{
+ int count=0;
+ connection_struct *conn;
+
+ for (conn=Connections;conn;conn=conn->next,count++) {
+ if (conn->cnum == cnum) {
+ if (count > 10) {
+ DLIST_PROMOTE(Connections, conn);
+ }
+ return conn;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ find first available connection slot, starting from a random position.
+The randomisation stops problems with the server dieing and clients
+thinking the server is still available.
+****************************************************************************/
+connection_struct *conn_new(void)
+{
+ connection_struct *conn;
+ int i;
+ int find_offset = 1;
+
+find_again:
+ i = bitmap_find(bmap, find_offset);
+
+ if (i == -1) {
+ /* Expand the connections bitmap. */
+ int oldsz = bmap->n;
+ int newsz = bmap->n + BITMAP_BLOCK_SZ;
+ struct bitmap * nbmap;
+
+ if (newsz <= oldsz) {
+ /* Integer wrap. */
+ DEBUG(0,("ERROR! Out of connection structures\n"));
+ return NULL;
+ }
+
+ DEBUG(4,("resizing connections bitmap from %d to %d\n",
+ oldsz, newsz));
+
+ nbmap = bitmap_allocate(newsz);
+ if (!nbmap) {
+ DEBUG(0,("ERROR! malloc fail.\n"));
+ return NULL;
+ }
+
+ bitmap_copy(nbmap, bmap);
+ bitmap_free(bmap);
+
+ bmap = nbmap;
+ find_offset = oldsz; /* Start next search in the new portion. */
+
+ goto find_again;
+ }
+
+ /* The bitmap position is used below as the connection number
+ * conn->cnum). This ends up as the TID field in the SMB header,
+ * which is limited to 16 bits (we skip 0xffff which is the
+ * NULL TID).
+ */
+ if (i > 65534) {
+ DEBUG(0, ("Maximum connection limit reached\n"));
+ return NULL;
+ }
+
+ if (!(conn=TALLOC_ZERO_P(NULL, connection_struct)) ||
+ !(conn->params = TALLOC_P(conn, struct share_params))) {
+ DEBUG(0,("TALLOC_ZERO() failed!\n"));
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cnum = i;
+
+ bitmap_set(bmap, i);
+
+ num_open++;
+
+ string_set(&conn->dirpath,"");
+ string_set(&conn->connectpath,"");
+ string_set(&conn->origpath,"");
+
+ DLIST_ADD(Connections, conn);
+
+ return conn;
+}
+
+/****************************************************************************
+ Close all conn structures.
+return true if any were closed
+****************************************************************************/
+bool conn_close_all(void)
+{
+ connection_struct *conn, *next;
+ bool ret = false;
+ for (conn=Connections;conn;conn=next) {
+ next=conn->next;
+ set_current_service(conn, 0, True);
+ close_cnum(conn, conn->vuid);
+ ret = true;
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Idle inactive connections.
+****************************************************************************/
+
+bool conn_idle_all(time_t t)
+{
+ int deadtime = lp_deadtime()*60;
+ pipes_struct *plist = NULL;
+ connection_struct *conn;
+
+ if (deadtime <= 0)
+ deadtime = DEFAULT_SMBD_TIMEOUT;
+
+ for (conn=Connections;conn;conn=conn->next) {
+
+ time_t age = t - conn->lastused;
+
+ /* Update if connection wasn't idle. */
+ if (conn->lastused != conn->lastused_count) {
+ conn->lastused = t;
+ conn->lastused_count = t;
+ }
+
+ /* close dirptrs on connections that are idle */
+ if (age > DPTR_IDLE_TIMEOUT) {
+ dptr_idlecnum(conn);
+ }
+
+ if (conn->num_files_open > 0 || age < deadtime) {
+ return False;
+ }
+ }
+
+ /*
+ * Check all pipes for any open handles. We cannot
+ * idle with a handle open.
+ */
+
+ for (plist = get_first_internal_pipe(); plist;
+ plist = get_next_internal_pipe(plist)) {
+ if (plist->pipe_handles && plist->pipe_handles->count) {
+ return False;
+ }
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Clear a vuid out of the validity cache, and as the 'owner' of a connection.
+****************************************************************************/
+
+void conn_clear_vuid_caches(uint16_t vuid)
+{
+ connection_struct *conn;
+
+ for (conn=Connections;conn;conn=conn->next) {
+ if (conn->vuid == vuid) {
+ conn->vuid = UID_FIELD_INVALID;
+ }
+ conn_clear_vuid_cache(conn, vuid);
+ }
+}
+
+/****************************************************************************
+ Free a conn structure - internal part.
+****************************************************************************/
+
+void conn_free_internal(connection_struct *conn)
+{
+ vfs_handle_struct *handle = NULL, *thandle = NULL;
+ struct trans_state *state = NULL;
+
+ /* Free vfs_connection_struct */
+ handle = conn->vfs_handles;
+ while(handle) {
+ DLIST_REMOVE(conn->vfs_handles, handle);
+ thandle = handle->next;
+ if (handle->free_data)
+ handle->free_data(&handle->data);
+ handle = thandle;
+ }
+
+ /* Free any pending transactions stored on this conn. */
+ for (state = conn->pending_trans; state; state = state->next) {
+ /* state->setup is a talloc child of state. */
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ }
+
+ free_namearray(conn->veto_list);
+ free_namearray(conn->hide_list);
+ free_namearray(conn->veto_oplock_list);
+ free_namearray(conn->aio_write_behind_list);
+
+ string_free(&conn->dirpath);
+ string_free(&conn->connectpath);
+ string_free(&conn->origpath);
+
+ ZERO_STRUCTP(conn);
+ talloc_destroy(conn);
+}
+
+/****************************************************************************
+ Free a conn structure.
+****************************************************************************/
+
+void conn_free(connection_struct *conn)
+{
+ DLIST_REMOVE(Connections, conn);
+
+ bitmap_clear(bmap, conn->cnum);
+
+ SMB_ASSERT(num_open > 0);
+ num_open--;
+
+ conn_free_internal(conn);
+}
+
+/****************************************************************************
+receive a smbcontrol message to forcibly unmount a share
+the message contains just a share name and all instances of that
+share are unmounted
+the special sharename '*' forces unmount of all shares
+****************************************************************************/
+void msg_force_tdis(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ connection_struct *conn, *next;
+ fstring sharename;
+
+ fstrcpy(sharename, (const char *)data->data);
+
+ if (strcmp(sharename, "*") == 0) {
+ DEBUG(1,("Forcing close of all shares\n"));
+ conn_close_all();
+ return;
+ }
+
+ for (conn=Connections;conn;conn=next) {
+ next=conn->next;
+ if (strequal(lp_servicename(SNUM(conn)), sharename)) {
+ DEBUG(1,("Forcing close of share %s cnum=%d\n",
+ sharename, conn->cnum));
+ close_cnum(conn, (uint16)-1);
+ }
+ }
+}
diff --git a/source3/smbd/connection.c b/source3/smbd/connection.c
new file mode 100644
index 0000000000..8dd5964f5f
--- /dev/null
+++ b/source3/smbd/connection.c
@@ -0,0 +1,332 @@
+/*
+ Unix SMB/CIFS implementation.
+ connection claim routines
+ Copyright (C) Andrew Tridgell 1998
+
+ 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"
+
+/****************************************************************************
+ Delete a connection record.
+****************************************************************************/
+
+bool yield_connection(connection_struct *conn, const char *name)
+{
+ struct db_record *rec;
+ NTSTATUS status;
+
+ DEBUG(3,("Yielding connection to %s\n",name));
+
+ if (!(rec = connections_fetch_entry(NULL, conn, name))) {
+ DEBUG(0, ("connections_fetch_entry failed\n"));
+ return False;
+ }
+
+ status = rec->delete_rec(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG( NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) ? 3 : 0,
+ ("deleting connection record returned %s\n",
+ nt_errstr(status)));
+ }
+
+ TALLOC_FREE(rec);
+ return NT_STATUS_IS_OK(status);
+}
+
+struct count_stat {
+ pid_t mypid;
+ int curr_connections;
+ const char *name;
+ bool Clear;
+};
+
+/****************************************************************************
+ Count the entries belonging to a service in the connection db.
+****************************************************************************/
+
+static int count_fn(struct db_record *rec,
+ const struct connections_key *ckey,
+ const struct connections_data *crec,
+ void *udp)
+{
+ struct count_stat *cs = (struct count_stat *)udp;
+
+ if (crec->cnum == -1) {
+ return 0;
+ }
+
+ /* If the pid was not found delete the entry from connections.tdb */
+
+ if (cs->Clear && !process_exists(crec->pid) && (errno == ESRCH)) {
+ NTSTATUS status;
+ DEBUG(2,("pid %s doesn't exist - deleting connections %d [%s]\n",
+ procid_str_static(&crec->pid), crec->cnum,
+ crec->servicename));
+
+ status = rec->delete_rec(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("count_fn: tdb_delete failed with error %s\n",
+ nt_errstr(status)));
+ }
+ return 0;
+ }
+
+ if (strequal(crec->servicename, cs->name))
+ cs->curr_connections++;
+
+ return 0;
+}
+
+/****************************************************************************
+ Claim an entry in the connections database.
+****************************************************************************/
+
+int count_current_connections( const char *sharename, bool clear )
+{
+ struct count_stat cs;
+
+ cs.mypid = sys_getpid();
+ cs.curr_connections = 0;
+ cs.name = sharename;
+ cs.Clear = clear;
+
+ /*
+ * This has a race condition, but locking the chain before hand is worse
+ * as it leads to deadlock.
+ */
+
+ if (connections_forall(count_fn, &cs) == -1) {
+ DEBUG(0,("count_current_connections: traverse of "
+ "connections.tdb failed\n"));
+ return False;
+ }
+
+ return cs.curr_connections;
+}
+
+/****************************************************************************
+ Count the number of connections open across all shares.
+****************************************************************************/
+
+int count_all_current_connections(void)
+{
+ return count_current_connections(NULL, True /* clear stale entries */);
+}
+
+/****************************************************************************
+ Claim an entry in the connections database.
+****************************************************************************/
+
+bool claim_connection(connection_struct *conn, const char *name,
+ uint32 msg_flags)
+{
+ struct db_record *rec;
+ struct connections_data crec;
+ TDB_DATA dbuf;
+ NTSTATUS status;
+ char addr[INET6_ADDRSTRLEN];
+
+ DEBUG(5,("claiming [%s]\n", name));
+
+ if (!(rec = connections_fetch_entry(talloc_tos(), conn, name))) {
+ DEBUG(0, ("connections_fetch_entry failed\n"));
+ return False;
+ }
+
+ /* fill in the crec */
+ ZERO_STRUCT(crec);
+ crec.magic = 0x280267;
+ crec.pid = procid_self();
+ crec.cnum = conn?conn->cnum:-1;
+ if (conn) {
+ crec.uid = conn->server_info->utok.uid;
+ crec.gid = conn->server_info->utok.gid;
+ strlcpy(crec.servicename, lp_servicename(SNUM(conn)),
+ sizeof(crec.servicename));
+ }
+ crec.start = time(NULL);
+ crec.bcast_msg_flags = msg_flags;
+
+ strlcpy(crec.machine,get_remote_machine_name(),sizeof(crec.machine));
+ strlcpy(crec.addr,conn?conn->client_address:
+ client_addr(get_client_fd(),addr,sizeof(addr)),
+ sizeof(crec.addr));
+
+ dbuf.dptr = (uint8 *)&crec;
+ dbuf.dsize = sizeof(crec);
+
+ status = rec->store(rec, dbuf, TDB_REPLACE);
+
+ TALLOC_FREE(rec);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("claim_connection: tdb_store failed with error %s.\n",
+ nt_errstr(status)));
+ return False;
+ }
+
+ return True;
+}
+
+bool register_message_flags(bool doreg, uint32 msg_flags)
+{
+ struct db_record *rec;
+ struct connections_data *pcrec;
+ NTSTATUS status;
+
+ DEBUG(10,("register_message_flags: %s flags 0x%x\n",
+ doreg ? "adding" : "removing",
+ (unsigned int)msg_flags ));
+
+ if (!(rec = connections_fetch_entry(NULL, NULL, ""))) {
+ DEBUG(0, ("connections_fetch_entry failed\n"));
+ return False;
+ }
+
+ if (rec->value.dsize != sizeof(struct connections_data)) {
+ DEBUG(0,("register_message_flags: Got wrong record size\n"));
+ TALLOC_FREE(rec);
+ return False;
+ }
+
+ pcrec = (struct connections_data *)rec->value.dptr;
+ if (doreg)
+ pcrec->bcast_msg_flags |= msg_flags;
+ else
+ pcrec->bcast_msg_flags &= ~msg_flags;
+
+ status = rec->store(rec, rec->value, TDB_REPLACE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("register_message_flags: tdb_store failed: %s.\n",
+ nt_errstr(status)));
+ TALLOC_FREE(rec);
+ return False;
+ }
+
+ DEBUG(10,("register_message_flags: new flags 0x%x\n",
+ (unsigned int)pcrec->bcast_msg_flags ));
+
+ TALLOC_FREE(rec);
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static TDB_DATA* make_pipe_rec_key( struct pipe_open_rec *prec )
+{
+ TDB_DATA *kbuf = NULL;
+ fstring key_string;
+
+ if ( !prec )
+ return NULL;
+
+ if ( (kbuf = TALLOC_P(prec, TDB_DATA)) == NULL ) {
+ return NULL;
+ }
+
+ snprintf( key_string, sizeof(key_string), "%s/%d/%d",
+ prec->name, procid_to_pid(&prec->pid), prec->pnum );
+
+ *kbuf = string_term_tdb_data(talloc_strdup(prec, key_string));
+ if (kbuf->dptr == NULL )
+ return NULL;
+
+ return kbuf;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static void fill_pipe_open_rec( struct pipe_open_rec *prec, smb_np_struct *p )
+{
+ prec->pid = pid_to_procid(sys_getpid());
+ prec->pnum = p->pnum;
+ prec->uid = geteuid();
+ fstrcpy( prec->name, p->name );
+
+ return;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+bool store_pipe_opendb( smb_np_struct *p )
+{
+ struct db_record *dbrec;
+ struct pipe_open_rec *prec;
+ TDB_DATA *key;
+ TDB_DATA data;
+ bool ret = False;
+
+ if ( (prec = TALLOC_P( NULL, struct pipe_open_rec)) == NULL ) {
+ DEBUG(0,("store_pipe_opendb: talloc failed!\n"));
+ return False;
+ }
+
+ fill_pipe_open_rec( prec, p );
+ if ( (key = make_pipe_rec_key( prec )) == NULL ) {
+ goto done;
+ }
+
+ data.dptr = (uint8 *)prec;
+ data.dsize = sizeof(struct pipe_open_rec);
+
+ if (!(dbrec = connections_fetch_record(prec, *key))) {
+ DEBUG(0, ("connections_fetch_record failed\n"));
+ goto done;
+ }
+
+ ret = NT_STATUS_IS_OK(dbrec->store(dbrec, data, TDB_REPLACE));
+
+done:
+ TALLOC_FREE( prec );
+ return ret;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+bool delete_pipe_opendb( smb_np_struct *p )
+{
+ struct db_record *dbrec;
+ struct pipe_open_rec *prec;
+ TDB_DATA *key;
+ bool ret = False;
+
+ if ( (prec = TALLOC_P( NULL, struct pipe_open_rec)) == NULL ) {
+ DEBUG(0,("store_pipe_opendb: talloc failed!\n"));
+ return False;
+ }
+
+ fill_pipe_open_rec( prec, p );
+ if ( (key = make_pipe_rec_key( prec )) == NULL ) {
+ goto done;
+ }
+
+ if (!(dbrec = connections_fetch_record(prec, *key))) {
+ DEBUG(0, ("connections_fetch_record failed\n"));
+ goto done;
+ }
+
+ ret = NT_STATUS_IS_OK(dbrec->delete_rec(dbrec));
+
+done:
+ TALLOC_FREE( prec );
+ return ret;
+}
diff --git a/source3/smbd/dfree.c b/source3/smbd/dfree.c
new file mode 100644
index 0000000000..1ddcd48d40
--- /dev/null
+++ b/source3/smbd/dfree.c
@@ -0,0 +1,223 @@
+/*
+ Unix SMB/CIFS implementation.
+ functions to calculate the free disk space
+ Copyright (C) Andrew Tridgell 1998
+
+ 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"
+
+/****************************************************************************
+ Normalise for DOS usage.
+****************************************************************************/
+
+static void disk_norm(bool small_query, SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+{
+ /* check if the disk is beyond the max disk size */
+ SMB_BIG_UINT maxdisksize = lp_maxdisksize();
+ if (maxdisksize) {
+ /* convert to blocks - and don't overflow */
+ maxdisksize = ((maxdisksize*1024)/(*bsize))*1024;
+ if (*dsize > maxdisksize) *dsize = maxdisksize;
+ if (*dfree > maxdisksize) *dfree = maxdisksize-1;
+ /* the -1 should stop applications getting div by 0
+ errors */
+ }
+
+ if(small_query) {
+ while (*dfree > WORDMAX || *dsize > WORDMAX || *bsize < 512) {
+ *dfree /= 2;
+ *dsize /= 2;
+ *bsize *= 2;
+ /*
+ * Force max to fit in 16 bit fields.
+ */
+ if (*bsize > (WORDMAX*512)) {
+ *bsize = (WORDMAX*512);
+ if (*dsize > WORDMAX)
+ *dsize = WORDMAX;
+ if (*dfree > WORDMAX)
+ *dfree = WORDMAX;
+ break;
+ }
+ }
+ }
+}
+
+
+
+/****************************************************************************
+ Return number of 1K blocks available on a path and total number.
+****************************************************************************/
+
+SMB_BIG_UINT sys_disk_free(connection_struct *conn, const char *path, bool small_query,
+ SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+{
+ SMB_BIG_UINT dfree_retval;
+ SMB_BIG_UINT dfree_q = 0;
+ SMB_BIG_UINT bsize_q = 0;
+ SMB_BIG_UINT dsize_q = 0;
+ const char *dfree_command;
+
+ (*dfree) = (*dsize) = 0;
+ (*bsize) = 512;
+
+ /*
+ * If external disk calculation specified, use it.
+ */
+
+ dfree_command = lp_dfree_command(SNUM(conn));
+ if (dfree_command && *dfree_command) {
+ const char *p;
+ char **lines = NULL;
+ char *syscmd = NULL;
+
+ syscmd = talloc_asprintf(talloc_tos(),
+ "%s %s",
+ dfree_command,
+ path);
+
+ if (!syscmd) {
+ return (SMB_BIG_UINT)-1;
+ }
+
+ DEBUG (3, ("disk_free: Running command %s\n", syscmd));
+
+ lines = file_lines_pload(syscmd, NULL);
+ if (lines) {
+ char *line = lines[0];
+
+ DEBUG (3, ("Read input from dfree, \"%s\"\n", line));
+
+ *dsize = STR_TO_SMB_BIG_UINT(line, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *dfree = STR_TO_SMB_BIG_UINT(p, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *bsize = STR_TO_SMB_BIG_UINT(p, NULL);
+ else
+ *bsize = 1024;
+ file_lines_free(lines);
+ DEBUG (3, ("Parsed output of dfree, dsize=%u, dfree=%u, bsize=%u\n",
+ (unsigned int)*dsize, (unsigned int)*dfree, (unsigned int)*bsize));
+
+ if (!*dsize)
+ *dsize = 2048;
+ if (!*dfree)
+ *dfree = 1024;
+ } else {
+ DEBUG (0, ("disk_free: sys_popen() failed for command %s. Error was : %s\n",
+ syscmd, strerror(errno) ));
+ if (sys_fsusage(path, dfree, dsize) != 0) {
+ DEBUG (0, ("disk_free: sys_fsusage() failed. Error was : %s\n",
+ strerror(errno) ));
+ return (SMB_BIG_UINT)-1;
+ }
+ }
+ } else {
+ if (sys_fsusage(path, dfree, dsize) != 0) {
+ DEBUG (0, ("disk_free: sys_fsusage() failed. Error was : %s\n",
+ strerror(errno) ));
+ return (SMB_BIG_UINT)-1;
+ }
+ }
+
+ if (disk_quotas(path, &bsize_q, &dfree_q, &dsize_q)) {
+ (*bsize) = bsize_q;
+ (*dfree) = MIN(*dfree,dfree_q);
+ (*dsize) = MIN(*dsize,dsize_q);
+ }
+
+ /* FIXME : Any reason for this assumption ? */
+ if (*bsize < 256) {
+ DEBUG(5,("disk_free:Warning: bsize == %d < 256 . Changing to assumed correct bsize = 512\n",(int)*bsize));
+ *bsize = 512;
+ }
+
+ if ((*dsize)<1) {
+ static bool done = false;
+ if (!done) {
+ DEBUG(0,("WARNING: dfree is broken on this system\n"));
+ done=true;
+ }
+ *dsize = 20*1024*1024/(*bsize);
+ *dfree = MAX(1,*dfree);
+ }
+
+ disk_norm(small_query,bsize,dfree,dsize);
+
+ if ((*bsize) < 1024) {
+ dfree_retval = (*dfree)/(1024/(*bsize));
+ } else {
+ dfree_retval = ((*bsize)/1024)*(*dfree);
+ }
+
+ return(dfree_retval);
+}
+
+/****************************************************************************
+ Potentially returned cached dfree info.
+****************************************************************************/
+
+SMB_BIG_UINT get_dfree_info(connection_struct *conn,
+ const char *path,
+ bool small_query,
+ SMB_BIG_UINT *bsize,
+ SMB_BIG_UINT *dfree,
+ SMB_BIG_UINT *dsize)
+{
+ int dfree_cache_time = lp_dfree_cache_time(SNUM(conn));
+ struct dfree_cached_info *dfc = conn->dfree_info;
+ SMB_BIG_UINT dfree_ret;
+
+ if (!dfree_cache_time) {
+ return SMB_VFS_DISK_FREE(conn,path,small_query,bsize,dfree,dsize);
+ }
+
+ if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) {
+ /* Return cached info. */
+ *bsize = dfc->bsize;
+ *dfree = dfc->dfree;
+ *dsize = dfc->dsize;
+ return dfc->dfree_ret;
+ }
+
+ dfree_ret = SMB_VFS_DISK_FREE(conn,path,small_query,bsize,dfree,dsize);
+
+ if (dfree_ret == (SMB_BIG_UINT)-1) {
+ /* Don't cache bad data. */
+ return dfree_ret;
+ }
+
+ /* No cached info or time to refresh. */
+ if (!dfc) {
+ dfc = TALLOC_P(conn, struct dfree_cached_info);
+ if (!dfc) {
+ return dfree_ret;
+ }
+ conn->dfree_info = dfc;
+ }
+
+ dfc->bsize = *bsize;
+ dfc->dfree = *dfree;
+ dfc->dsize = *dsize;
+ dfc->dfree_ret = dfree_ret;
+ dfc->last_dfree_time = conn->lastused;
+
+ return dfree_ret;
+}
diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c
new file mode 100644
index 0000000000..c2735c032a
--- /dev/null
+++ b/source3/smbd/dir.c
@@ -0,0 +1,1316 @@
+/*
+ Unix SMB/CIFS implementation.
+ Directory handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007
+
+ 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"
+
+/*
+ This module implements directory related functions for Samba.
+*/
+
+/* "Special" directory offsets. */
+#define END_OF_DIRECTORY_OFFSET ((long)-1)
+#define START_OF_DIRECTORY_OFFSET ((long)0)
+#define DOT_DOT_DIRECTORY_OFFSET ((long)0x80000000)
+
+/* Make directory handle internals available. */
+
+struct name_cache_entry {
+ char *name;
+ long offset;
+};
+
+struct smb_Dir {
+ connection_struct *conn;
+ SMB_STRUCT_DIR *dir;
+ long offset;
+ char *dir_path;
+ size_t name_cache_size;
+ struct name_cache_entry *name_cache;
+ unsigned int name_cache_index;
+ unsigned int file_number;
+};
+
+struct dptr_struct {
+ struct dptr_struct *next, *prev;
+ int dnum;
+ uint16 spid;
+ struct connection_struct *conn;
+ struct smb_Dir *dir_hnd;
+ bool expect_close;
+ char *wcard;
+ uint32 attr;
+ char *path;
+ bool has_wild; /* Set to true if the wcard entry has MS wildcard characters in it. */
+ bool did_stat; /* Optimisation for non-wcard searches. */
+};
+
+static struct bitmap *dptr_bmap;
+static struct dptr_struct *dirptrs;
+static int dirhandles_open = 0;
+
+#define INVALID_DPTR_KEY (-3)
+
+/****************************************************************************
+ Make a dir struct.
+****************************************************************************/
+
+bool make_dir_struct(TALLOC_CTX *ctx,
+ char *buf,
+ const char *mask,
+ const char *fname,
+ SMB_OFF_T size,
+ uint32 mode,
+ time_t date,
+ bool uc)
+{
+ char *p;
+ char *mask2 = talloc_strdup(ctx, mask);
+
+ if (!mask2) {
+ return False;
+ }
+
+ if ((mode & aDIR) != 0) {
+ size = 0;
+ }
+
+ memset(buf+1,' ',11);
+ if ((p = strchr_m(mask2,'.')) != NULL) {
+ *p = 0;
+ push_ascii(buf+1,mask2,8, 0);
+ push_ascii(buf+9,p+1,3, 0);
+ *p = '.';
+ } else {
+ push_ascii(buf+1,mask2,11, 0);
+ }
+
+ memset(buf+21,'\0',DIR_STRUCT_SIZE-21);
+ SCVAL(buf,21,mode);
+ srv_put_dos_date(buf,22,date);
+ SSVAL(buf,26,size & 0xFFFF);
+ SSVAL(buf,28,(size >> 16)&0xFFFF);
+ /* We only uppercase if FLAGS2_LONG_PATH_COMPONENTS is zero in the input buf.
+ Strange, but verified on W2K3. Needed for OS/2. JRA. */
+ push_ascii(buf+30,fname,12, uc ? STR_UPPER : 0);
+ DEBUG(8,("put name [%s] from [%s] into dir struct\n",buf+30, fname));
+ return True;
+}
+
+/****************************************************************************
+ Initialise the dir bitmap.
+****************************************************************************/
+
+void init_dptrs(void)
+{
+ static bool dptrs_init=False;
+
+ if (dptrs_init)
+ return;
+
+ dptr_bmap = bitmap_allocate(MAX_DIRECTORY_HANDLES);
+
+ if (!dptr_bmap)
+ exit_server("out of memory in init_dptrs");
+
+ dptrs_init = True;
+}
+
+/****************************************************************************
+ Idle a dptr - the directory is closed but the control info is kept.
+****************************************************************************/
+
+static void dptr_idle(struct dptr_struct *dptr)
+{
+ if (dptr->dir_hnd) {
+ DEBUG(4,("Idling dptr dnum %d\n",dptr->dnum));
+ TALLOC_FREE(dptr->dir_hnd);
+ }
+}
+
+/****************************************************************************
+ Idle the oldest dptr.
+****************************************************************************/
+
+static void dptr_idleoldest(void)
+{
+ struct dptr_struct *dptr;
+
+ /*
+ * Go to the end of the list.
+ */
+ for(dptr = dirptrs; dptr && dptr->next; dptr = dptr->next)
+ ;
+
+ if(!dptr) {
+ DEBUG(0,("No dptrs available to idle ?\n"));
+ return;
+ }
+
+ /*
+ * Idle the oldest pointer.
+ */
+
+ for(; dptr; dptr = dptr->prev) {
+ if (dptr->dir_hnd) {
+ dptr_idle(dptr);
+ return;
+ }
+ }
+}
+
+/****************************************************************************
+ Get the struct dptr_struct for a dir index.
+****************************************************************************/
+
+static struct dptr_struct *dptr_get(int key, bool forclose)
+{
+ struct dptr_struct *dptr;
+
+ for(dptr = dirptrs; dptr; dptr = dptr->next) {
+ if(dptr->dnum == key) {
+ if (!forclose && !dptr->dir_hnd) {
+ if (dirhandles_open >= MAX_OPEN_DIRECTORIES)
+ dptr_idleoldest();
+ DEBUG(4,("dptr_get: Reopening dptr key %d\n",key));
+ if (!(dptr->dir_hnd = OpenDir(
+ NULL, dptr->conn, dptr->path,
+ dptr->wcard, dptr->attr))) {
+ DEBUG(4,("dptr_get: Failed to open %s (%s)\n",dptr->path,
+ strerror(errno)));
+ return False;
+ }
+ }
+ DLIST_PROMOTE(dirptrs,dptr);
+ return dptr;
+ }
+ }
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir path for a dir index.
+****************************************************************************/
+
+char *dptr_path(int key)
+{
+ struct dptr_struct *dptr = dptr_get(key, False);
+ if (dptr)
+ return(dptr->path);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir wcard for a dir index.
+****************************************************************************/
+
+char *dptr_wcard(int key)
+{
+ struct dptr_struct *dptr = dptr_get(key, False);
+ if (dptr)
+ return(dptr->wcard);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir attrib for a dir index.
+****************************************************************************/
+
+uint16 dptr_attr(int key)
+{
+ struct dptr_struct *dptr = dptr_get(key, False);
+ if (dptr)
+ return(dptr->attr);
+ return(0);
+}
+
+/****************************************************************************
+ Close a dptr (internal func).
+****************************************************************************/
+
+static void dptr_close_internal(struct dptr_struct *dptr)
+{
+ DEBUG(4,("closing dptr key %d\n",dptr->dnum));
+
+ DLIST_REMOVE(dirptrs, dptr);
+
+ /*
+ * Free the dnum in the bitmap. Remember the dnum value is always
+ * biased by one with respect to the bitmap.
+ */
+
+ if(bitmap_query( dptr_bmap, dptr->dnum - 1) != True) {
+ DEBUG(0,("dptr_close_internal : Error - closing dnum = %d and bitmap not set !\n",
+ dptr->dnum ));
+ }
+
+ bitmap_clear(dptr_bmap, dptr->dnum - 1);
+
+ TALLOC_FREE(dptr->dir_hnd);
+
+ /* Lanman 2 specific code */
+ SAFE_FREE(dptr->wcard);
+ string_set(&dptr->path,"");
+ SAFE_FREE(dptr);
+}
+
+/****************************************************************************
+ Close a dptr given a key.
+****************************************************************************/
+
+void dptr_close(int *key)
+{
+ struct dptr_struct *dptr;
+
+ if(*key == INVALID_DPTR_KEY)
+ return;
+
+ /* OS/2 seems to use -1 to indicate "close all directories" */
+ if (*key == -1) {
+ struct dptr_struct *next;
+ for(dptr = dirptrs; dptr; dptr = next) {
+ next = dptr->next;
+ dptr_close_internal(dptr);
+ }
+ *key = INVALID_DPTR_KEY;
+ return;
+ }
+
+ dptr = dptr_get(*key, True);
+
+ if (!dptr) {
+ DEBUG(0,("Invalid key %d given to dptr_close\n", *key));
+ return;
+ }
+
+ dptr_close_internal(dptr);
+
+ *key = INVALID_DPTR_KEY;
+}
+
+/****************************************************************************
+ Close all dptrs for a cnum.
+****************************************************************************/
+
+void dptr_closecnum(connection_struct *conn)
+{
+ struct dptr_struct *dptr, *next;
+ for(dptr = dirptrs; dptr; dptr = next) {
+ next = dptr->next;
+ if (dptr->conn == conn)
+ dptr_close_internal(dptr);
+ }
+}
+
+/****************************************************************************
+ Idle all dptrs for a cnum.
+****************************************************************************/
+
+void dptr_idlecnum(connection_struct *conn)
+{
+ struct dptr_struct *dptr;
+ for(dptr = dirptrs; dptr; dptr = dptr->next) {
+ if (dptr->conn == conn && dptr->dir_hnd)
+ dptr_idle(dptr);
+ }
+}
+
+/****************************************************************************
+ Close a dptr that matches a given path, only if it matches the spid also.
+****************************************************************************/
+
+void dptr_closepath(char *path,uint16 spid)
+{
+ struct dptr_struct *dptr, *next;
+ for(dptr = dirptrs; dptr; dptr = next) {
+ next = dptr->next;
+ if (spid == dptr->spid && strequal(dptr->path,path))
+ dptr_close_internal(dptr);
+ }
+}
+
+/****************************************************************************
+ Try and close the oldest handle not marked for
+ expect close in the hope that the client has
+ finished with that one.
+****************************************************************************/
+
+static void dptr_close_oldest(bool old)
+{
+ struct dptr_struct *dptr;
+
+ /*
+ * Go to the end of the list.
+ */
+ for(dptr = dirptrs; dptr && dptr->next; dptr = dptr->next)
+ ;
+
+ if(!dptr) {
+ DEBUG(0,("No old dptrs available to close oldest ?\n"));
+ return;
+ }
+
+ /*
+ * If 'old' is true, close the oldest oldhandle dnum (ie. 1 < dnum < 256) that
+ * does not have expect_close set. If 'old' is false, close
+ * one of the new dnum handles.
+ */
+
+ for(; dptr; dptr = dptr->prev) {
+ if ((old && (dptr->dnum < 256) && !dptr->expect_close) ||
+ (!old && (dptr->dnum > 255))) {
+ dptr_close_internal(dptr);
+ return;
+ }
+ }
+}
+
+/****************************************************************************
+ Create a new dir ptr. If the flag old_handle is true then we must allocate
+ from the bitmap range 0 - 255 as old SMBsearch directory handles are only
+ one byte long. If old_handle is false we allocate from the range
+ 256 - MAX_DIRECTORY_HANDLES. We bias the number we return by 1 to ensure
+ a directory handle is never zero.
+ wcard must not be zero.
+****************************************************************************/
+
+NTSTATUS dptr_create(connection_struct *conn, const char *path, bool old_handle, bool expect_close,uint16 spid,
+ const char *wcard, bool wcard_has_wild, uint32 attr, struct dptr_struct **dptr_ret)
+{
+ struct dptr_struct *dptr = NULL;
+ struct smb_Dir *dir_hnd;
+ NTSTATUS status;
+
+ DEBUG(5,("dptr_create dir=%s\n", path));
+
+ if (!wcard) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = check_name(conn,path);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dir_hnd = OpenDir(NULL, conn, path, wcard, attr);
+ if (!dir_hnd) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ string_set(&conn->dirpath,path);
+
+ if (dirhandles_open >= MAX_OPEN_DIRECTORIES) {
+ dptr_idleoldest();
+ }
+
+ dptr = SMB_MALLOC_P(struct dptr_struct);
+ if(!dptr) {
+ DEBUG(0,("malloc fail in dptr_create.\n"));
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(dptr);
+
+ if(old_handle) {
+
+ /*
+ * This is an old-style SMBsearch request. Ensure the
+ * value we return will fit in the range 1-255.
+ */
+
+ dptr->dnum = bitmap_find(dptr_bmap, 0);
+
+ if(dptr->dnum == -1 || dptr->dnum > 254) {
+
+ /*
+ * Try and close the oldest handle not marked for
+ * expect close in the hope that the client has
+ * finished with that one.
+ */
+
+ dptr_close_oldest(True);
+
+ /* Now try again... */
+ dptr->dnum = bitmap_find(dptr_bmap, 0);
+ if(dptr->dnum == -1 || dptr->dnum > 254) {
+ DEBUG(0,("dptr_create: returned %d: Error - all old dirptrs in use ?\n", dptr->dnum));
+ SAFE_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ }
+ } else {
+
+ /*
+ * This is a new-style trans2 request. Allocate from
+ * a range that will return 256 - MAX_DIRECTORY_HANDLES.
+ */
+
+ dptr->dnum = bitmap_find(dptr_bmap, 255);
+
+ if(dptr->dnum == -1 || dptr->dnum < 255) {
+
+ /*
+ * Try and close the oldest handle close in the hope that
+ * the client has finished with that one. This will only
+ * happen in the case of the Win98 client bug where it leaks
+ * directory handles.
+ */
+
+ dptr_close_oldest(False);
+
+ /* Now try again... */
+ dptr->dnum = bitmap_find(dptr_bmap, 255);
+
+ if(dptr->dnum == -1 || dptr->dnum < 255) {
+ DEBUG(0,("dptr_create: returned %d: Error - all new dirptrs in use ?\n", dptr->dnum));
+ SAFE_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ }
+ }
+
+ bitmap_set(dptr_bmap, dptr->dnum);
+
+ dptr->dnum += 1; /* Always bias the dnum by one - no zero dnums allowed. */
+
+ string_set(&dptr->path,path);
+ dptr->conn = conn;
+ dptr->dir_hnd = dir_hnd;
+ dptr->spid = spid;
+ dptr->expect_close = expect_close;
+ dptr->wcard = SMB_STRDUP(wcard);
+ if (!dptr->wcard) {
+ bitmap_clear(dptr_bmap, dptr->dnum - 1);
+ SAFE_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (lp_posix_pathnames() || (wcard[0] == '.' && wcard[1] == 0)) {
+ dptr->has_wild = True;
+ } else {
+ dptr->has_wild = wcard_has_wild;
+ }
+
+ dptr->attr = attr;
+
+ DLIST_ADD(dirptrs, dptr);
+
+ DEBUG(3,("creating new dirptr %d for path %s, expect_close = %d\n",
+ dptr->dnum,path,expect_close));
+
+ *dptr_ret = dptr;
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+ Wrapper functions to access the lower level directory handles.
+****************************************************************************/
+
+int dptr_CloseDir(struct dptr_struct *dptr)
+{
+ DLIST_REMOVE(dirptrs, dptr);
+ TALLOC_FREE(dptr->dir_hnd);
+ return 0;
+}
+
+void dptr_SeekDir(struct dptr_struct *dptr, long offset)
+{
+ SeekDir(dptr->dir_hnd, offset);
+}
+
+long dptr_TellDir(struct dptr_struct *dptr)
+{
+ return TellDir(dptr->dir_hnd);
+}
+
+bool dptr_has_wild(struct dptr_struct *dptr)
+{
+ return dptr->has_wild;
+}
+
+int dptr_dnum(struct dptr_struct *dptr)
+{
+ return dptr->dnum;
+}
+
+/****************************************************************************
+ Return the next visible file name, skipping veto'd and invisible files.
+****************************************************************************/
+
+static const char *dptr_normal_ReadDirName(struct dptr_struct *dptr, long *poffset, SMB_STRUCT_STAT *pst)
+{
+ /* Normal search for the next file. */
+ const char *name;
+ while ((name = ReadDirName(dptr->dir_hnd, poffset)) != NULL) {
+ if (is_visible_file(dptr->conn, dptr->path, name, pst, True)) {
+ return name;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return the next visible file name, skipping veto'd and invisible files.
+****************************************************************************/
+
+const char *dptr_ReadDirName(TALLOC_CTX *ctx,
+ struct dptr_struct *dptr,
+ long *poffset,
+ SMB_STRUCT_STAT *pst)
+{
+ SET_STAT_INVALID(*pst);
+
+ if (dptr->has_wild) {
+ return dptr_normal_ReadDirName(dptr, poffset, pst);
+ }
+
+ /* If poffset is -1 then we know we returned this name before and we have
+ no wildcards. We're at the end of the directory. */
+ if (*poffset == END_OF_DIRECTORY_OFFSET) {
+ return NULL;
+ }
+
+ if (!dptr->did_stat) {
+ char *pathreal = NULL;
+
+ /* We know the stored wcard contains no wildcard characters. See if we can match
+ with a stat call. If we can't, then set did_stat to true to
+ ensure we only do this once and keep searching. */
+
+ dptr->did_stat = True;
+
+ /* First check if it should be visible. */
+ if (!is_visible_file(dptr->conn, dptr->path, dptr->wcard, pst, True)) {
+ /* This only returns False if the file was found, but
+ is explicitly not visible. Set us to end of directory,
+ but return NULL as we know we can't ever find it. */
+ dptr->dir_hnd->offset = *poffset = END_OF_DIRECTORY_OFFSET;
+ return NULL;
+ }
+
+ if (VALID_STAT(*pst)) {
+ /* We need to set the underlying dir_hnd offset to -1 also as
+ this function is usually called with the output from TellDir. */
+ dptr->dir_hnd->offset = *poffset = END_OF_DIRECTORY_OFFSET;
+ return dptr->wcard;
+ }
+
+ pathreal = talloc_asprintf(ctx,
+ "%s/%s",
+ dptr->path,
+ dptr->wcard);
+ if (!pathreal) {
+ return NULL;
+ }
+
+ if (SMB_VFS_STAT(dptr->conn,pathreal,pst) == 0) {
+ /* We need to set the underlying dir_hnd offset to -1 also as
+ this function is usually called with the output from TellDir. */
+ dptr->dir_hnd->offset = *poffset = END_OF_DIRECTORY_OFFSET;
+ TALLOC_FREE(pathreal);
+ return dptr->wcard;
+ } else {
+ /* If we get any other error than ENOENT or ENOTDIR
+ then the file exists we just can't stat it. */
+ if (errno != ENOENT && errno != ENOTDIR) {
+ /* We need to set the underlying dir_hdn offset to -1 also as
+ this function is usually called with the output from TellDir. */
+ dptr->dir_hnd->offset = *poffset = END_OF_DIRECTORY_OFFSET;
+ TALLOC_FREE(pathreal);
+ return dptr->wcard;
+ }
+ }
+
+ TALLOC_FREE(pathreal);
+
+ /* Stat failed. We know this is authoratiative if we are
+ * providing case sensitive semantics or the underlying
+ * filesystem is case sensitive.
+ */
+
+ if (dptr->conn->case_sensitive ||
+ !(dptr->conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
+ /* We need to set the underlying dir_hnd offset to -1 also as
+ this function is usually called with the output from TellDir. */
+ dptr->dir_hnd->offset = *poffset = END_OF_DIRECTORY_OFFSET;
+ return NULL;
+ }
+ }
+ return dptr_normal_ReadDirName(dptr, poffset, pst);
+}
+
+/****************************************************************************
+ Search for a file by name, skipping veto'ed and not visible files.
+****************************************************************************/
+
+bool dptr_SearchDir(struct dptr_struct *dptr, const char *name, long *poffset, SMB_STRUCT_STAT *pst)
+{
+ SET_STAT_INVALID(*pst);
+
+ if (!dptr->has_wild && (dptr->dir_hnd->offset == END_OF_DIRECTORY_OFFSET)) {
+ /* This is a singleton directory and we're already at the end. */
+ *poffset = END_OF_DIRECTORY_OFFSET;
+ return False;
+ }
+
+ return SearchDir(dptr->dir_hnd, name, poffset);
+}
+
+/****************************************************************************
+ Add the name we're returning into the underlying cache.
+****************************************************************************/
+
+void dptr_DirCacheAdd(struct dptr_struct *dptr, const char *name, long offset)
+{
+ DirCacheAdd(dptr->dir_hnd, name, offset);
+}
+
+/****************************************************************************
+ Fill the 5 byte server reserved dptr field.
+****************************************************************************/
+
+bool dptr_fill(char *buf1,unsigned int key)
+{
+ unsigned char *buf = (unsigned char *)buf1;
+ struct dptr_struct *dptr = dptr_get(key, False);
+ uint32 offset;
+ if (!dptr) {
+ DEBUG(1,("filling null dirptr %d\n",key));
+ return(False);
+ }
+ offset = (uint32)TellDir(dptr->dir_hnd);
+ DEBUG(6,("fill on key %u dirptr 0x%lx now at %d\n",key,
+ (long)dptr->dir_hnd,(int)offset));
+ buf[0] = key;
+ SIVAL(buf,1,offset);
+ return(True);
+}
+
+/****************************************************************************
+ Fetch the dir ptr and seek it given the 5 byte server field.
+****************************************************************************/
+
+struct dptr_struct *dptr_fetch(char *buf,int *num)
+{
+ unsigned int key = *(unsigned char *)buf;
+ struct dptr_struct *dptr = dptr_get(key, False);
+ uint32 offset;
+ long seekoff;
+
+ if (!dptr) {
+ DEBUG(3,("fetched null dirptr %d\n",key));
+ return(NULL);
+ }
+ *num = key;
+ offset = IVAL(buf,1);
+ if (offset == (uint32)-1) {
+ seekoff = END_OF_DIRECTORY_OFFSET;
+ } else {
+ seekoff = (long)offset;
+ }
+ SeekDir(dptr->dir_hnd,seekoff);
+ DEBUG(3,("fetching dirptr %d for path %s at offset %d\n",
+ key,dptr_path(key),(int)seekoff));
+ return(dptr);
+}
+
+/****************************************************************************
+ Fetch the dir ptr.
+****************************************************************************/
+
+struct dptr_struct *dptr_fetch_lanman2(int dptr_num)
+{
+ struct dptr_struct *dptr = dptr_get(dptr_num, False);
+
+ if (!dptr) {
+ DEBUG(3,("fetched null dirptr %d\n",dptr_num));
+ return(NULL);
+ }
+ DEBUG(3,("fetching dirptr %d for path %s\n",dptr_num,dptr_path(dptr_num)));
+ return(dptr);
+}
+
+/****************************************************************************
+ Check that a file matches a particular file type.
+****************************************************************************/
+
+bool dir_check_ftype(connection_struct *conn, uint32 mode, uint32 dirtype)
+{
+ uint32 mask;
+
+ /* Check the "may have" search bits. */
+ if (((mode & ~dirtype) & (aHIDDEN | aSYSTEM | aDIR)) != 0)
+ return False;
+
+ /* Check the "must have" bits, which are the may have bits shifted eight */
+ /* If must have bit is set, the file/dir can not be returned in search unless the matching
+ file attribute is set */
+ mask = ((dirtype >> 8) & (aDIR|aARCH|aRONLY|aHIDDEN|aSYSTEM)); /* & 0x37 */
+ if(mask) {
+ if((mask & (mode & (aDIR|aARCH|aRONLY|aHIDDEN|aSYSTEM))) == mask) /* check if matching attribute present */
+ return True;
+ else
+ return False;
+ }
+
+ return True;
+}
+
+static bool mangle_mask_match(connection_struct *conn,
+ const char *filename,
+ const char *mask)
+{
+ char mname[13];
+
+ if (!name_to_8_3(filename,mname,False,conn->params)) {
+ return False;
+ }
+ return mask_match_search(mname,mask,False);
+}
+
+/****************************************************************************
+ Get an 8.3 directory entry.
+****************************************************************************/
+
+bool get_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *mask,
+ uint32 dirtype,
+ char **pp_fname_out,
+ SMB_OFF_T *size,
+ uint32 *mode,
+ time_t *date,
+ bool check_descend,
+ bool ask_sharemode)
+{
+ const char *dname = NULL;
+ bool found = False;
+ SMB_STRUCT_STAT sbuf;
+ char *pathreal = NULL;
+ const char *filename = NULL;
+ bool needslash;
+
+ *pp_fname_out = NULL;
+
+ needslash = ( conn->dirpath[strlen(conn->dirpath) -1] != '/');
+
+ if (!conn->dirptr) {
+ return(False);
+ }
+
+ while (!found) {
+ long curoff = dptr_TellDir(conn->dirptr);
+ dname = dptr_ReadDirName(ctx, conn->dirptr, &curoff, &sbuf);
+
+ DEBUG(6,("readdir on dirptr 0x%lx now at offset %ld\n",
+ (long)conn->dirptr,TellDir(conn->dirptr->dir_hnd)));
+
+ if (dname == NULL) {
+ return(False);
+ }
+
+ filename = dname;
+
+ /* notice the special *.* handling. This appears to be the only difference
+ between the wildcard handling in this routine and in the trans2 routines.
+ see masktest for a demo
+ */
+ if ((strcmp(mask,"*.*") == 0) ||
+ mask_match_search(filename,mask,False) ||
+ mangle_mask_match(conn,filename,mask)) {
+ char mname[13];
+
+ if (!mangle_is_8_3(filename, False, conn->params)) {
+ if (!name_to_8_3(filename,mname,False,
+ conn->params)) {
+ continue;
+ }
+ filename = mname;
+ }
+
+ if (needslash) {
+ pathreal = talloc_asprintf(ctx,
+ "%s/%s",
+ conn->dirpath,
+ dname);
+ } else {
+ pathreal = talloc_asprintf(ctx,
+ "%s%s",
+ conn->dirpath,
+ dname);
+ }
+ if (!pathreal) {
+ return False;
+ }
+
+ if (!VALID_STAT(sbuf) && (SMB_VFS_STAT(conn, pathreal, &sbuf)) != 0) {
+ DEBUG(5,("Couldn't stat 1 [%s]. Error = %s\n",
+ pathreal, strerror(errno) ));
+ TALLOC_FREE(pathreal);
+ continue;
+ }
+
+ *mode = dos_mode(conn,pathreal,&sbuf);
+
+ if (!dir_check_ftype(conn,*mode,dirtype)) {
+ DEBUG(5,("[%s] attribs 0x%x didn't match 0x%x\n",filename,(unsigned int)*mode,(unsigned int)dirtype));
+ TALLOC_FREE(pathreal);
+ continue;
+ }
+
+ *size = sbuf.st_size;
+ *date = sbuf.st_mtime;
+
+ if (ask_sharemode) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ get_file_infos(fileid, NULL, &write_time_ts);
+ if (!null_timespec(write_time_ts)) {
+ *date = convert_timespec_to_time_t(write_time_ts);
+ }
+ }
+
+ DEBUG(3,("get_dir_entry mask=[%s] found %s "
+ "fname=%s (%s)\n",
+ mask,
+ pathreal,
+ dname,
+ filename));
+
+ found = True;
+
+ *pp_fname_out = talloc_strdup(ctx, filename);
+ if (!*pp_fname_out) {
+ return False;
+ }
+
+ DirCacheAdd(conn->dirptr->dir_hnd, dname, curoff);
+ TALLOC_FREE(pathreal);
+ }
+ }
+
+ return(found);
+}
+
+/*******************************************************************
+ Check to see if a user can read a file. This is only approximate,
+ it is used as part of the "hide unreadable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_read_file(connection_struct *conn, char *name)
+{
+ /*
+ * If user is a member of the Admin group
+ * we never hide files from them.
+ */
+
+ if (conn->admin_user) {
+ return True;
+ }
+
+ return can_access_file_acl(conn, name, FILE_READ_DATA);
+}
+
+/*******************************************************************
+ Check to see if a user can write a file (and only files, we do not
+ check dirs on this one). This is only approximate,
+ it is used as part of the "hide unwriteable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_write_file(connection_struct *conn, char *name, SMB_STRUCT_STAT *pst)
+{
+ /*
+ * If user is a member of the Admin group
+ * we never hide files from them.
+ */
+
+ if (conn->admin_user) {
+ return True;
+ }
+
+ SMB_ASSERT(VALID_STAT(*pst));
+
+ /* Pseudo-open the file */
+
+ if(S_ISDIR(pst->st_mode)) {
+ return True;
+ }
+
+ return can_write_to_file(conn, name, pst);
+}
+
+/*******************************************************************
+ Is a file a "special" type ?
+********************************************************************/
+
+static bool file_is_special(connection_struct *conn, char *name, SMB_STRUCT_STAT *pst)
+{
+ /*
+ * If user is a member of the Admin group
+ * we never hide files from them.
+ */
+
+ if (conn->admin_user)
+ return False;
+
+ SMB_ASSERT(VALID_STAT(*pst));
+
+ if (S_ISREG(pst->st_mode) || S_ISDIR(pst->st_mode) || S_ISLNK(pst->st_mode))
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+ Should the file be seen by the client ? NOTE: A successful return
+ is no guarantee of the file's existence ... you also have to check
+ whether pst is valid.
+********************************************************************/
+
+bool is_visible_file(connection_struct *conn, const char *dir_path, const char *name, SMB_STRUCT_STAT *pst, bool use_veto)
+{
+ bool hide_unreadable = lp_hideunreadable(SNUM(conn));
+ bool hide_unwriteable = lp_hideunwriteable_files(SNUM(conn));
+ bool hide_special = lp_hide_special_files(SNUM(conn));
+
+ SET_STAT_INVALID(*pst);
+
+ if ((strcmp(".",name) == 0) || (strcmp("..",name) == 0)) {
+ return True; /* . and .. are always visible. */
+ }
+
+ /* If it's a vetoed file, pretend it doesn't even exist */
+ if (use_veto && IS_VETO_PATH(conn, name)) {
+ DEBUG(10,("is_visible_file: file %s is vetoed.\n", name ));
+ return False;
+ }
+
+ if (hide_unreadable || hide_unwriteable || hide_special) {
+ char *entry = NULL;
+
+ if (asprintf(&entry, "%s/%s", dir_path, name) == -1) {
+ return False;
+ }
+
+ /* If it's a dfs symlink, ignore _hide xxxx_ options */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn)) &&
+ is_msdfs_link(conn, entry, NULL)) {
+ SAFE_FREE(entry);
+ return True;
+ }
+
+ /* If the file name does not exist, there's no point checking
+ * the configuration options. We succeed, on the basis that the
+ * checks *might* have passed if the file was present.
+ */
+ if (SMB_VFS_STAT(conn, entry, pst) != 0) {
+ SAFE_FREE(entry);
+ return True;
+ }
+
+ /* Honour _hide unreadable_ option */
+ if (hide_unreadable && !user_can_read_file(conn, entry)) {
+ DEBUG(10,("is_visible_file: file %s is unreadable.\n", entry ));
+ SAFE_FREE(entry);
+ return False;
+ }
+ /* Honour _hide unwriteable_ option */
+ if (hide_unwriteable && !user_can_write_file(conn, entry, pst)) {
+ DEBUG(10,("is_visible_file: file %s is unwritable.\n", entry ));
+ SAFE_FREE(entry);
+ return False;
+ }
+ /* Honour _hide_special_ option */
+ if (hide_special && file_is_special(conn, entry, pst)) {
+ DEBUG(10,("is_visible_file: file %s is special.\n", entry ));
+ SAFE_FREE(entry);
+ return False;
+ }
+ SAFE_FREE(entry);
+ }
+ return True;
+}
+
+static int smb_Dir_destructor(struct smb_Dir *dirp)
+{
+ if (dirp->dir) {
+ SMB_VFS_CLOSEDIR(dirp->conn,dirp->dir);
+ }
+ dirhandles_open--;
+ return 0;
+}
+
+/*******************************************************************
+ Open a directory.
+********************************************************************/
+
+struct smb_Dir *OpenDir(TALLOC_CTX *mem_ctx, connection_struct *conn,
+ const char *name, const char *mask, uint32 attr)
+{
+ struct smb_Dir *dirp = TALLOC_ZERO_P(mem_ctx, struct smb_Dir);
+
+ if (!dirp) {
+ return NULL;
+ }
+
+ dirp->conn = conn;
+ dirp->name_cache_size = lp_directory_name_cache_size(SNUM(conn));
+
+ dirp->dir_path = talloc_strdup(dirp, name);
+ if (!dirp->dir_path) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ dirhandles_open++;
+ talloc_set_destructor(dirp, smb_Dir_destructor);
+
+ dirp->dir = SMB_VFS_OPENDIR(conn, dirp->dir_path, mask, attr);
+ if (!dirp->dir) {
+ DEBUG(5,("OpenDir: Can't open %s. %s\n", dirp->dir_path,
+ strerror(errno) ));
+ goto fail;
+ }
+
+ return dirp;
+
+ fail:
+ TALLOC_FREE(dirp);
+ return NULL;
+}
+
+/*******************************************************************
+ Read from a directory. Also return current offset.
+ Don't check for veto or invisible files.
+********************************************************************/
+
+const char *ReadDirName(struct smb_Dir *dirp, long *poffset)
+{
+ const char *n;
+ connection_struct *conn = dirp->conn;
+
+ /* Cheat to allow . and .. to be the first entries returned. */
+ if (((*poffset == START_OF_DIRECTORY_OFFSET) || (*poffset == DOT_DOT_DIRECTORY_OFFSET)) && (dirp->file_number < 2)) {
+ if (dirp->file_number == 0) {
+ n = ".";
+ *poffset = dirp->offset = START_OF_DIRECTORY_OFFSET;
+ } else {
+ *poffset = dirp->offset = DOT_DOT_DIRECTORY_OFFSET;
+ n = "..";
+ }
+ dirp->file_number++;
+ return n;
+ } else if (*poffset == END_OF_DIRECTORY_OFFSET) {
+ *poffset = dirp->offset = END_OF_DIRECTORY_OFFSET;
+ return NULL;
+ } else {
+ /* A real offset, seek to it. */
+ SeekDir(dirp, *poffset);
+ }
+
+ while ((n = vfs_readdirname(conn, dirp->dir))) {
+ /* Ignore . and .. - we've already returned them. */
+ if (*n == '.') {
+ if ((n[1] == '\0') || (n[1] == '.' && n[2] == '\0')) {
+ continue;
+ }
+ }
+ *poffset = dirp->offset = SMB_VFS_TELLDIR(conn, dirp->dir);
+ dirp->file_number++;
+ return n;
+ }
+ *poffset = dirp->offset = END_OF_DIRECTORY_OFFSET;
+ return NULL;
+}
+
+/*******************************************************************
+ Rewind to the start.
+********************************************************************/
+
+void RewindDir(struct smb_Dir *dirp, long *poffset)
+{
+ SMB_VFS_REWINDDIR(dirp->conn, dirp->dir);
+ dirp->file_number = 0;
+ dirp->offset = START_OF_DIRECTORY_OFFSET;
+ *poffset = START_OF_DIRECTORY_OFFSET;
+}
+
+/*******************************************************************
+ Seek a dir.
+********************************************************************/
+
+void SeekDir(struct smb_Dir *dirp, long offset)
+{
+ if (offset != dirp->offset) {
+ if (offset == START_OF_DIRECTORY_OFFSET) {
+ RewindDir(dirp, &offset);
+ /*
+ * Ok we should really set the file number here
+ * to 1 to enable ".." to be returned next. Trouble
+ * is I'm worried about callers using SeekDir(dirp,0)
+ * as equivalent to RewindDir(). So leave this alone
+ * for now.
+ */
+ } else if (offset == DOT_DOT_DIRECTORY_OFFSET) {
+ RewindDir(dirp, &offset);
+ /*
+ * Set the file number to 2 - we want to get the first
+ * real file entry (the one we return after "..")
+ * on the next ReadDir.
+ */
+ dirp->file_number = 2;
+ } else if (offset == END_OF_DIRECTORY_OFFSET) {
+ ; /* Don't seek in this case. */
+ } else {
+ SMB_VFS_SEEKDIR(dirp->conn, dirp->dir, offset);
+ }
+ dirp->offset = offset;
+ }
+}
+
+/*******************************************************************
+ Tell a dir position.
+********************************************************************/
+
+long TellDir(struct smb_Dir *dirp)
+{
+ return(dirp->offset);
+}
+
+/*******************************************************************
+ Add an entry into the dcache.
+********************************************************************/
+
+void DirCacheAdd(struct smb_Dir *dirp, const char *name, long offset)
+{
+ struct name_cache_entry *e;
+
+ if (dirp->name_cache_size == 0) {
+ return;
+ }
+
+ if (dirp->name_cache == NULL) {
+ dirp->name_cache = TALLOC_ZERO_ARRAY(
+ dirp, struct name_cache_entry, dirp->name_cache_size);
+
+ if (dirp->name_cache == NULL) {
+ return;
+ }
+ }
+
+ dirp->name_cache_index = (dirp->name_cache_index+1) %
+ dirp->name_cache_size;
+ e = &dirp->name_cache[dirp->name_cache_index];
+ TALLOC_FREE(e->name);
+ e->name = talloc_strdup(dirp, name);
+ e->offset = offset;
+}
+
+/*******************************************************************
+ Find an entry by name. Leave us at the offset after it.
+ Don't check for veto or invisible files.
+********************************************************************/
+
+bool SearchDir(struct smb_Dir *dirp, const char *name, long *poffset)
+{
+ int i;
+ const char *entry;
+ connection_struct *conn = dirp->conn;
+
+ /* Search back in the name cache. */
+ if (dirp->name_cache_size && dirp->name_cache) {
+ for (i = dirp->name_cache_index; i >= 0; i--) {
+ struct name_cache_entry *e = &dirp->name_cache[i];
+ if (e->name && (conn->case_sensitive ? (strcmp(e->name, name) == 0) : strequal(e->name, name))) {
+ *poffset = e->offset;
+ SeekDir(dirp, e->offset);
+ return True;
+ }
+ }
+ for (i = dirp->name_cache_size - 1; i > dirp->name_cache_index; i--) {
+ struct name_cache_entry *e = &dirp->name_cache[i];
+ if (e->name && (conn->case_sensitive ? (strcmp(e->name, name) == 0) : strequal(e->name, name))) {
+ *poffset = e->offset;
+ SeekDir(dirp, e->offset);
+ return True;
+ }
+ }
+ }
+
+ /* Not found in the name cache. Rewind directory and start from scratch. */
+ SMB_VFS_REWINDDIR(conn, dirp->dir);
+ dirp->file_number = 0;
+ *poffset = START_OF_DIRECTORY_OFFSET;
+ while ((entry = ReadDirName(dirp, poffset))) {
+ if (conn->case_sensitive ? (strcmp(entry, name) == 0) : strequal(entry, name)) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/*****************************************************************
+ Is this directory empty ?
+*****************************************************************/
+
+NTSTATUS can_delete_directory(struct connection_struct *conn,
+ const char *dirname)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ long dirpos = 0;
+ const char *dname;
+ struct smb_Dir *dir_hnd = OpenDir(talloc_tos(), conn, dirname,
+ NULL, 0);
+
+ if (!dir_hnd) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ while ((dname = ReadDirName(dir_hnd,&dirpos))) {
+ SMB_STRUCT_STAT st;
+
+ /* Quick check for "." and ".." */
+ if (dname[0] == '.') {
+ if (!dname[1] || (dname[1] == '.' && !dname[2])) {
+ continue;
+ }
+ }
+
+ if (!is_visible_file(conn, dirname, dname, &st, True)) {
+ continue;
+ }
+
+ DEBUG(10,("can_delete_directory: got name %s - can't delete\n", dname ));
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+ TALLOC_FREE(dir_hnd);
+
+ return status;
+}
diff --git a/source3/smbd/dmapi.c b/source3/smbd/dmapi.c
new file mode 100644
index 0000000000..1049c95a39
--- /dev/null
+++ b/source3/smbd/dmapi.c
@@ -0,0 +1,340 @@
+/*
+ Unix SMB/CIFS implementation.
+ DMAPI Support routines
+
+ Copyright (C) James Peach 2006
+
+ 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"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DMAPI
+
+#ifndef USE_DMAPI
+
+uint32 dmapi_file_flags(const char * const path) { return 0; }
+bool dmapi_have_session(void) { return False; }
+const void * dmapi_get_current_session(void) { return NULL; }
+
+#else /* USE_DMAPI */
+
+#ifdef HAVE_XFS_DMAPI_H
+#include <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+#define DMAPI_SESSION_NAME "samba"
+#define DMAPI_TRACE 10
+
+static dm_sessid_t samba_dmapi_session = DM_NO_SESSION;
+static unsigned session_num;
+
+/*
+ Initialise DMAPI session. The session is persistant kernel state,
+ so it might already exist, in which case we merely want to
+ reconnect to it. This function should be called as root.
+*/
+static int dmapi_init_session(void)
+{
+ char buf[DM_SESSION_INFO_LEN];
+ size_t buflen;
+ uint nsessions = 5;
+ dm_sessid_t *sessions = NULL;
+ char *version;
+ char *session_name;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+ int i, err;
+
+ if (session_num == 0) {
+ session_name = DMAPI_SESSION_NAME;
+ } else {
+ session_name = talloc_asprintf(tmp_ctx, "%s%u", DMAPI_SESSION_NAME,
+ session_num);
+ }
+
+ if (session_name == NULL) {
+ DEBUG(0,("Out of memory in dmapi_init_session\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+
+ if (dm_init_service(&version) < 0) {
+ DEBUG(0, ("dm_init_service failed - disabling DMAPI\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ ZERO_STRUCT(buf);
+
+ /* Fetch kernel DMAPI sessions until we get any of them */
+ do {
+ dm_sessid_t *new_sessions;
+ nsessions *= 2;
+ new_sessions = TALLOC_REALLOC_ARRAY(tmp_ctx, sessions,
+ dm_sessid_t, nsessions);
+ if (new_sessions == NULL) {
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ sessions = new_sessions;
+ err = dm_getall_sessions(nsessions, sessions, &nsessions);
+ } while (err == -1 && errno == E2BIG);
+
+ if (err == -1) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to retrieve DMAPI sessions: %s\n",
+ strerror(errno)));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ /* Look through existing kernel DMAPI sessions to find out ours */
+ for (i = 0; i < nsessions; ++i) {
+ err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
+ buf[sizeof(buf) - 1] = '\0';
+ if (err == 0 && strcmp(session_name, buf) == 0) {
+ samba_dmapi_session = sessions[i];
+ DEBUGADD(DMAPI_TRACE,
+ ("attached to existing DMAPI session "
+ "named '%s'\n", buf));
+ break;
+ }
+ }
+
+ /* No session already defined. */
+ if (samba_dmapi_session == DM_NO_SESSION) {
+ err = dm_create_session(DM_NO_SESSION,
+ session_name,
+ &samba_dmapi_session);
+ if (err < 0) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to create new DMAPI session: %s\n",
+ strerror(errno)));
+ samba_dmapi_session = DM_NO_SESSION;
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ DEBUG(0, ("created new DMAPI session named '%s' for %s\n",
+ session_name, version));
+ }
+
+ if (samba_dmapi_session != DM_NO_SESSION) {
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+ }
+
+ /*
+ Note that we never end the DMAPI session. It gets re-used if possiblie.
+ DMAPI session is a kernel resource that is usually lives until server reboot
+ and doesn't get destroed when an application finishes.
+
+ However, we free list of references to DMAPI sessions we've got from the kernel
+ as it is not needed anymore once we have found (or created) our session.
+ */
+
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+/*
+ Return a pointer to our DMAPI session, if available.
+ This assumes that you have called dmapi_have_session() first.
+*/
+const void *dmapi_get_current_session(void)
+{
+ if (samba_dmapi_session == DM_NO_SESSION) {
+ return NULL;
+ }
+
+ return (void *)&samba_dmapi_session;
+}
+
+/*
+ dmapi_have_session() must be the first DMAPI call you make in Samba. It will
+ initialize DMAPI, if available, and tell you if you can get a DMAPI session.
+ This should be called in the client-specific child process.
+*/
+
+bool dmapi_have_session(void)
+{
+ static bool initialized;
+ if (!initialized) {
+ initialized = true;
+
+ become_root();
+ dmapi_init_session();
+ unbecome_root();
+
+ }
+
+ return samba_dmapi_session != DM_NO_SESSION;
+}
+
+/*
+ only call this when you get back an EINVAL error indicating that the
+ session you are using is invalid. This destroys the existing session
+ and creates a new one.
+ */
+bool dmapi_new_session(void)
+{
+ if (dmapi_have_session()) {
+ /* try to destroy the old one - this may not succeed */
+ dm_destroy_session(samba_dmapi_session);
+ }
+ samba_dmapi_session = DM_NO_SESSION;
+ become_root();
+ session_num++;
+ dmapi_init_session();
+ unbecome_root();
+ return samba_dmapi_session != DM_NO_SESSION;
+}
+
+/*
+ only call this when exiting from master smbd process. DMAPI sessions
+ are long-lived kernel resources we ought to share across smbd processes.
+ However, we must free them when all smbd processes are finished to
+ allow other subsystems clean up properly. Not freeing DMAPI session
+ blocks certain HSM implementations from proper shutdown.
+*/
+bool dmapi_destroy_session(void)
+{
+ if (samba_dmapi_session != DM_NO_SESSION) {
+ become_root();
+ if (0 == dm_destroy_session(samba_dmapi_session)) {
+ session_num--;
+ samba_dmapi_session = DM_NO_SESSION;
+ } else {
+ DEBUG(0,("Couldn't destroy DMAPI session: %s\n",
+ strerror(errno)));
+ }
+ unbecome_root();
+ }
+ return samba_dmapi_session == DM_NO_SESSION;
+}
+
+
+/*
+ This is default implementation of dmapi_file_flags() that is
+ called from VFS is_offline() call to know whether file is offline.
+ For GPFS-specific version see modules/vfs_tsmsm.c. It might be
+ that approach on quering existence of a specific attribute that
+ is used in vfs_tsmsm.c will work with other DMAPI-based HSM
+ implementations as well.
+*/
+uint32 dmapi_file_flags(const char * const path)
+{
+ int err;
+ dm_eventset_t events = {0};
+ uint nevents;
+
+ dm_sessid_t dmapi_session;
+ const void *dmapi_session_ptr;
+ void *dm_handle = NULL;
+ size_t dm_handle_len = 0;
+
+ uint32 flags = 0;
+
+ dmapi_session_ptr = dmapi_get_current_session();
+ if (dmapi_session_ptr == NULL) {
+ return 0;
+ }
+
+ dmapi_session = *(dm_sessid_t *)dmapi_session_ptr;
+ if (dmapi_session == DM_NO_SESSION) {
+ return 0;
+ }
+
+ /* AIX has DMAPI but no POSIX capablities support. In this case,
+ * we need to be root to do DMAPI manipulations.
+ */
+#ifndef HAVE_POSIX_CAPABILITIES
+ become_root();
+#endif
+
+ err = dm_path_to_handle(CONST_DISCARD(char *, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+
+ if (errno != EPERM) {
+ goto done;
+ }
+
+ /* Linux capabilities are broken in that changing our
+ * user ID will clobber out effective capabilities irrespective
+ * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
+ * capabilities are not removed from our permitted set, so we
+ * can re-acquire them if necessary.
+ */
+
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ err = dm_path_to_handle(CONST_DISCARD(char *, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE,
+ ("retrying dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+ goto done;
+ }
+ }
+
+ err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
+ DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
+ path, strerror(errno)));
+ dm_handle_free(dm_handle, dm_handle_len);
+ goto done;
+ }
+
+ /* We figure that the only reason a DMAPI application would be
+ * interested in trapping read events is that part of the file is
+ * offline.
+ */
+ DEBUG(DMAPI_TRACE, ("DMAPI event list for %s\n", path));
+ if (DMEV_ISSET(DM_EVENT_READ, events)) {
+ flags = FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ dm_handle_free(dm_handle, dm_handle_len);
+
+ if (flags & FILE_ATTRIBUTE_OFFLINE) {
+ DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
+ }
+
+done:
+
+#ifndef HAVE_POSIX_CAPABILITIES
+ unbecome_root();
+#endif
+
+ return flags;
+}
+
+
+#endif /* USE_DMAPI */
diff --git a/source3/smbd/dnsregister.c b/source3/smbd/dnsregister.c
new file mode 100644
index 0000000000..2319097ca5
--- /dev/null
+++ b/source3/smbd/dnsregister.c
@@ -0,0 +1,212 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS-SD registration
+ Copyright (C) Rishi Srivatsavai 2007
+
+ 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>
+
+/* Uses DNS service discovery (libdns_sd) to
+ * register the SMB service. SMB service is registered
+ * on ".local" domain via Multicast DNS & any
+ * other unicast DNS domains available.
+ *
+ * Users use the smbclient -B (Browse) option to
+ * browse for advertised SMB services.
+ */
+
+#define DNS_REG_RETRY_INTERVAL (5*60) /* in seconds */
+
+#ifdef WITH_DNSSD_SUPPORT
+
+#include <dns_sd.h>
+
+struct dns_reg_state {
+ DNSServiceRef srv_ref;
+ struct timed_event *retry_handler;
+};
+
+void dns_register_close(struct dns_reg_state **dns_state_ptr)
+{
+ struct dns_reg_state *dns_state = *dns_state_ptr;
+
+ if (dns_state == NULL) {
+ return;
+ }
+
+ if (dns_state->srv_ref != NULL) {
+ /* Close connection to the mDNS daemon */
+ DNSServiceRefDeallocate(dns_state->srv_ref);
+ dns_state->srv_ref = NULL;
+ }
+
+ /* Clear event handler */
+ if (dns_state->retry_handler != NULL) {
+ TALLOC_FREE(dns_state->retry_handler);
+ dns_state->retry_handler = NULL;
+ }
+
+ talloc_free(dns_state);
+ *dns_state_ptr = NULL;
+}
+
+static void dns_register_smbd_retry(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct dns_reg_state *dns_state = (struct dns_reg_state *)private_data;
+
+ /* Clear previous registration state to force new
+ * registration attempt. Clears event handler.
+ */
+ dns_register_close(&dns_state);
+}
+
+static void schedule_dns_register_smbd_retry(struct dns_reg_state *dns_state,
+ struct timeval *timeout)
+{
+ struct timed_event * event;
+
+ dns_state->srv_ref = NULL;
+ event= event_add_timed(smbd_event_context(),
+ NULL,
+ timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0),
+ "DNS registration handler",
+ dns_register_smbd_retry,
+ dns_state);
+
+ dns_state->retry_handler = event;
+ get_timed_events_timeout(smbd_event_context(), timeout);
+}
+
+/* Kick off a mDNS request to register the "_smb._tcp" on the specified port.
+ * We really ought to register on all the ports we are listening on. This will
+ * have to be an exercise for some-one who knows the DNS registration API a bit
+ * better.
+ */
+void dns_register_smbd(struct dns_reg_state ** dns_state_ptr,
+ unsigned port,
+ int *maxfd,
+ fd_set *listen_set,
+ struct timeval *timeout)
+{
+ int mdnsd_conn_fd;
+ DNSServiceErrorType err;
+ struct dns_reg_state *dns_state = *dns_state_ptr;
+
+ if (dns_state == NULL) {
+ *dns_state_ptr = dns_state = talloc(NULL, struct dns_reg_state);
+ if (dns_state == NULL) {
+ return;
+ }
+ }
+
+ /* Quit if a re-try attempt has been scheduled. */
+ if (dns_state->retry_handler != NULL) {
+ return;
+ }
+
+ /* If a registration is active add conn
+ * fd to select listen_set and return
+ */
+ if (dns_state->srv_ref != NULL) {
+ mdnsd_conn_fd = DNSServiceRefSockFD(dns_state->srv_ref);
+ FD_SET(mdnsd_conn_fd, listen_set);
+ return;
+ }
+
+ DEBUG(6, ("registering _smb._tcp service on port %d\n", port));
+
+ /* Register service with DNS. Connects with the mDNS
+ * daemon running on the local system to perform DNS
+ * service registration.
+ */
+ err = DNSServiceRegister(&dns_state->srv_ref, 0 /* flags */,
+ kDNSServiceInterfaceIndexAny,
+ NULL /* service name */,
+ "_smb._tcp" /* service type */,
+ NULL /* domain */,
+ "" /* SRV target host name */,
+ htons(port),
+ 0 /* TXT record len */,
+ NULL /* TXT record data */,
+ NULL /* callback func */,
+ NULL /* callback context */);
+
+ if (err != kDNSServiceErr_NoError) {
+ /* Failed to register service. Schedule a re-try attempt.
+ */
+ DEBUG(3, ("unable to register with mDNS (err %d)\n", err));
+ schedule_dns_register_smbd_retry(dns_state, timeout);
+ return;
+ }
+
+ mdnsd_conn_fd = DNSServiceRefSockFD(dns_state->srv_ref);
+ FD_SET(mdnsd_conn_fd, listen_set);
+ *maxfd = MAX(*maxfd, mdnsd_conn_fd);
+ *timeout = timeval_zero();
+
+}
+
+/* Processes reply from mDNS daemon. Returns true if a reply was received */
+bool dns_register_smbd_reply(struct dns_reg_state *dns_state,
+ fd_set *lfds, struct timeval *timeout)
+{
+ int mdnsd_conn_fd = -1;
+
+ if (dns_state->srv_ref == NULL) {
+ return false;
+ }
+
+ mdnsd_conn_fd = DNSServiceRefSockFD(dns_state->srv_ref);
+
+ /* Process reply from daemon. Handles any errors. */
+ if ((mdnsd_conn_fd != -1) && (FD_ISSET(mdnsd_conn_fd,lfds)) ) {
+ DNSServiceErrorType err;
+
+ err = DNSServiceProcessResult(dns_state->srv_ref);
+ if (err != kDNSServiceErr_NoError) {
+ DEBUG(3, ("failed to process mDNS result (err %d), re-trying\n",
+ err));
+ schedule_dns_register_smbd_retry(dns_state, timeout);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+#else /* WITH_DNSSD_SUPPORT */
+
+ void dns_register_smbd(struct dns_reg_state ** dns_state_ptr,
+ unsigned port,
+ int *maxfd,
+ fd_set *listen_set,
+ struct timeval *timeout)
+{}
+
+ void dns_register_close(struct dns_reg_state ** dns_state_ptr)
+{}
+
+ bool dns_register_smbd_reply(struct dns_reg_state *dns_state,
+ fd_set *lfds, struct timeval *timeout)
+{
+ return false;
+}
+
+#endif /* WITH_DNSSD_SUPPORT */
diff --git a/source3/smbd/dosmode.c b/source3/smbd/dosmode.c
new file mode 100644
index 0000000000..88c6a51770
--- /dev/null
+++ b/source3/smbd/dosmode.c
@@ -0,0 +1,666 @@
+/*
+ Unix SMB/CIFS implementation.
+ dos mode handling functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) James Peach 2006
+
+ 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"
+
+static int set_sparse_flag(const SMB_STRUCT_STAT * const sbuf)
+{
+#if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
+ if (sbuf->st_size > sbuf->st_blocks * (SMB_OFF_T)STAT_ST_BLOCKSIZE) {
+ return FILE_ATTRIBUTE_SPARSE;
+ }
+#endif
+ return 0;
+}
+
+/****************************************************************************
+ Change a dos mode to a unix mode.
+ Base permission for files:
+ if creating file and inheriting (i.e. parent_dir != NULL)
+ apply read/write bits from parent directory.
+ else
+ everybody gets read bit set
+ dos readonly is represented in unix by removing everyone's write bit
+ dos archive is represented in unix by the user's execute bit
+ dos system is represented in unix by the group's execute bit
+ dos hidden is represented in unix by the other's execute bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+ Base permission for directories:
+ dos directory is represented in unix by unix's dir bit and the exec bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+****************************************************************************/
+
+mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname,
+ const char *inherit_from_dir)
+{
+ mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ mode_t dir_mode = 0; /* Mode of the inherit_from directory if
+ * inheriting. */
+
+ if (!lp_store_dos_attributes(SNUM(conn)) && IS_DOS_READONLY(dosmode)) {
+ result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ }
+
+ if (fname && (inherit_from_dir != NULL)
+ && lp_inherit_perms(SNUM(conn))) {
+ SMB_STRUCT_STAT sbuf;
+
+ DEBUG(2, ("unix_mode(%s) inheriting from %s\n", fname,
+ inherit_from_dir));
+ if (SMB_VFS_STAT(conn, inherit_from_dir, &sbuf) != 0) {
+ DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n", fname,
+ inherit_from_dir, strerror(errno)));
+ return(0); /* *** shouldn't happen! *** */
+ }
+
+ /* Save for later - but explicitly remove setuid bit for safety. */
+ dir_mode = sbuf.st_mode & ~S_ISUID;
+ DEBUG(2,("unix_mode(%s) inherit mode %o\n",fname,(int)dir_mode));
+ /* Clear "result" */
+ result = 0;
+ }
+
+ if (IS_DOS_DIR(dosmode)) {
+ /* We never make directories read only for the owner as under DOS a user
+ can always create a file in a read-only directory. */
+ result |= (S_IFDIR | S_IWUSR);
+
+ if (dir_mode) {
+ /* Inherit mode of parent directory. */
+ result |= dir_mode;
+ } else {
+ /* Provisionally add all 'x' bits */
+ result |= (S_IXUSR | S_IXGRP | S_IXOTH);
+
+ /* Apply directory mask */
+ result &= lp_dir_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_dir_mode(SNUM(conn));
+ }
+ } else {
+ if (lp_map_archive(SNUM(conn)) && IS_DOS_ARCHIVE(dosmode))
+ result |= S_IXUSR;
+
+ if (lp_map_system(SNUM(conn)) && IS_DOS_SYSTEM(dosmode))
+ result |= S_IXGRP;
+
+ if (lp_map_hidden(SNUM(conn)) && IS_DOS_HIDDEN(dosmode))
+ result |= S_IXOTH;
+
+ if (dir_mode) {
+ /* Inherit 666 component of parent directory mode */
+ result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ } else {
+ /* Apply mode mask */
+ result &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_create_mode(SNUM(conn));
+ }
+ }
+
+ DEBUG(3,("unix_mode(%s) returning 0%o\n",fname,(int)result ));
+ return(result);
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+
+static uint32 dos_mode_from_sbuf(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf)
+{
+ int result = 0;
+ enum mapreadonly_options ro_opts = (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
+
+ if (ro_opts == MAP_READONLY_YES) {
+ /* Original Samba method - map inverse of user "w" bit. */
+ if ((sbuf->st_mode & S_IWUSR) == 0) {
+ result |= aRONLY;
+ }
+ } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
+ /* Check actual permissions for read-only. */
+ if (!can_write_to_file(conn, path, sbuf)) {
+ result |= aRONLY;
+ }
+ } /* Else never set the readonly bit. */
+
+ if (MAP_ARCHIVE(conn) && ((sbuf->st_mode & S_IXUSR) != 0))
+ result |= aARCH;
+
+ if (MAP_SYSTEM(conn) && ((sbuf->st_mode & S_IXGRP) != 0))
+ result |= aSYSTEM;
+
+ if (MAP_HIDDEN(conn) && ((sbuf->st_mode & S_IXOTH) != 0))
+ result |= aHIDDEN;
+
+ if (S_ISDIR(sbuf->st_mode))
+ result = aDIR | (result & aRONLY);
+
+ result |= set_sparse_flag(sbuf);
+
+#ifdef S_ISLNK
+#if LINKS_READ_ONLY
+ if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode))
+ result |= aRONLY;
+#endif
+#endif
+
+ DEBUG(8,("dos_mode_from_sbuf returning "));
+
+ if (result & aHIDDEN) DEBUG(8, ("h"));
+ if (result & aRONLY ) DEBUG(8, ("r"));
+ if (result & aSYSTEM) DEBUG(8, ("s"));
+ if (result & aDIR ) DEBUG(8, ("d"));
+ if (result & aARCH ) DEBUG(8, ("a"));
+
+ DEBUG(8,("\n"));
+ return result;
+}
+
+/****************************************************************************
+ Get DOS attributes from an EA.
+****************************************************************************/
+
+static bool get_ea_dos_attribute(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf, uint32 *pattr)
+{
+ ssize_t sizeret;
+ fstring attrstr;
+ unsigned int dosattr;
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return False;
+ }
+
+ /* Don't reset pattr to zero as we may already have filename-based attributes we
+ need to preserve. */
+
+ sizeret = SMB_VFS_GETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, sizeof(attrstr));
+ if (sizeret == -1) {
+#if defined(ENOTSUP) && defined(ENOATTR)
+ if ((errno != ENOTSUP) && (errno != ENOATTR) && (errno != EACCES) && (errno != EPERM)) {
+ DEBUG(1,("get_ea_dos_attributes: Cannot get attribute from EA on file %s: Error = %s\n",
+ path, strerror(errno) ));
+ set_store_dos_attributes(SNUM(conn), False);
+ }
+#endif
+ return False;
+ }
+ /* Null terminate string. */
+ attrstr[sizeret] = 0;
+ DEBUG(10,("get_ea_dos_attribute: %s attrstr = %s\n", path, attrstr));
+
+ if (sizeret < 2 || attrstr[0] != '0' || attrstr[1] != 'x' ||
+ sscanf(attrstr, "%x", &dosattr) != 1) {
+ DEBUG(1,("get_ea_dos_attributes: Badly formed DOSATTRIB on file %s - %s\n", path, attrstr));
+ return False;
+ }
+
+ if (S_ISDIR(sbuf->st_mode)) {
+ dosattr |= aDIR;
+ }
+ *pattr = (uint32)(dosattr & SAMBA_ATTRIBUTES_MASK);
+
+ DEBUG(8,("get_ea_dos_attribute returning (0x%x)", dosattr));
+
+ if (dosattr & aHIDDEN) DEBUG(8, ("h"));
+ if (dosattr & aRONLY ) DEBUG(8, ("r"));
+ if (dosattr & aSYSTEM) DEBUG(8, ("s"));
+ if (dosattr & aDIR ) DEBUG(8, ("d"));
+ if (dosattr & aARCH ) DEBUG(8, ("a"));
+
+ DEBUG(8,("\n"));
+
+ return True;
+}
+
+/****************************************************************************
+ Set DOS attributes in an EA.
+****************************************************************************/
+
+static bool set_ea_dos_attribute(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf, uint32 dosmode)
+{
+ fstring attrstr;
+ files_struct *fsp = NULL;
+ bool ret = False;
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return False;
+ }
+
+ snprintf(attrstr, sizeof(attrstr)-1, "0x%x", dosmode & SAMBA_ATTRIBUTES_MASK);
+ if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == -1) {
+ if((errno != EPERM) && (errno != EACCES)) {
+ if (errno == ENOSYS
+#if defined(ENOTSUP)
+ || errno == ENOTSUP) {
+#else
+ ) {
+#endif
+ set_store_dos_attributes(SNUM(conn), False);
+ }
+ return False;
+ }
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ /* Check if we have write access. */
+ if(!CAN_WRITE(conn) || !lp_dos_filemode(SNUM(conn)))
+ return False;
+
+ /*
+ * We need to open the file with write access whilst
+ * still in our current user context. This ensures we
+ * are not violating security in doing the setxattr.
+ */
+
+ if (!NT_STATUS_IS_OK(open_file_fchmod(conn,path,sbuf,&fsp)))
+ return ret;
+ become_root();
+ if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == 0) {
+ ret = True;
+ }
+ unbecome_root();
+ close_file_fchmod(fsp);
+ return ret;
+ }
+ DEBUG(10,("set_ea_dos_attribute: set EA %s on file %s\n", attrstr, path));
+ return True;
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode for an ms dfs link.
+****************************************************************************/
+
+uint32 dos_mode_msdfs(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
+{
+ uint32 result = 0;
+
+ DEBUG(8,("dos_mode_msdfs: %s\n", path));
+
+ if (!VALID_STAT(*sbuf)) {
+ return 0;
+ }
+
+ /* First do any modifications that depend on the path name. */
+ /* hide files with a name starting with a . */
+ if (lp_hide_dot_files(SNUM(conn))) {
+ const char *p = strrchr_m(path,'/');
+ if (p) {
+ p++;
+ } else {
+ p = path;
+ }
+
+ if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
+ result |= aHIDDEN;
+ }
+ }
+
+ result |= dos_mode_from_sbuf(conn, path, sbuf);
+
+ /* Optimization : Only call is_hidden_path if it's not already
+ hidden. */
+ if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
+ result |= aHIDDEN;
+ }
+
+ DEBUG(8,("dos_mode_msdfs returning "));
+
+ if (result & aHIDDEN) DEBUG(8, ("h"));
+ if (result & aRONLY ) DEBUG(8, ("r"));
+ if (result & aSYSTEM) DEBUG(8, ("s"));
+ if (result & aDIR ) DEBUG(8, ("d"));
+ if (result & aARCH ) DEBUG(8, ("a"));
+ if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
+
+ DEBUG(8,("\n"));
+
+ return(result);
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+
+uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
+{
+ uint32 result = 0;
+ bool offline;
+
+ DEBUG(8,("dos_mode: %s\n", path));
+
+ if (!VALID_STAT(*sbuf)) {
+ return 0;
+ }
+
+ /* First do any modifications that depend on the path name. */
+ /* hide files with a name starting with a . */
+ if (lp_hide_dot_files(SNUM(conn))) {
+ const char *p = strrchr_m(path,'/');
+ if (p) {
+ p++;
+ } else {
+ p = path;
+ }
+
+ if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
+ result |= aHIDDEN;
+ }
+ }
+
+ /* Get the DOS attributes from an EA by preference. */
+ if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
+ result |= set_sparse_flag(sbuf);
+ } else {
+ result |= dos_mode_from_sbuf(conn, path, sbuf);
+ }
+
+
+ offline = SMB_VFS_IS_OFFLINE(conn, path, sbuf);
+ if (S_ISREG(sbuf->st_mode) && offline) {
+ result |= FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ /* Optimization : Only call is_hidden_path if it's not already
+ hidden. */
+ if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
+ result |= aHIDDEN;
+ }
+
+ DEBUG(8,("dos_mode returning "));
+
+ if (result & aHIDDEN) DEBUG(8, ("h"));
+ if (result & aRONLY ) DEBUG(8, ("r"));
+ if (result & aSYSTEM) DEBUG(8, ("s"));
+ if (result & aDIR ) DEBUG(8, ("d"));
+ if (result & aARCH ) DEBUG(8, ("a"));
+ if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
+
+ DEBUG(8,("\n"));
+
+ return(result);
+}
+
+/*******************************************************************
+ chmod a file - but preserve some bits.
+********************************************************************/
+
+int file_set_dosmode(connection_struct *conn, const char *fname,
+ uint32 dosmode, SMB_STRUCT_STAT *st,
+ const char *parent_dir,
+ bool newfile)
+{
+ SMB_STRUCT_STAT st1;
+ int mask=0;
+ mode_t tmp;
+ mode_t unixmode;
+ int ret = -1, lret = -1;
+ uint32_t old_mode;
+
+ /* We only allow READONLY|HIDDEN|SYSTEM|DIRECTORY|ARCHIVE here. */
+ dosmode &= (SAMBA_ATTRIBUTES_MASK | FILE_ATTRIBUTE_OFFLINE);
+
+ DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, fname));
+
+ if (st == NULL) {
+ SET_STAT_INVALID(st1);
+ st = &st1;
+ }
+
+ if (!VALID_STAT(*st)) {
+ if (SMB_VFS_STAT(conn,fname,st))
+ return(-1);
+ }
+
+ unixmode = st->st_mode;
+
+ get_acl_group_bits(conn, fname, &st->st_mode);
+
+ if (S_ISDIR(st->st_mode))
+ dosmode |= aDIR;
+ else
+ dosmode &= ~aDIR;
+
+ old_mode = dos_mode(conn,fname,st);
+
+ if (dosmode & FILE_ATTRIBUTE_OFFLINE) {
+ if (!(old_mode & FILE_ATTRIBUTE_OFFLINE)) {
+ lret = SMB_VFS_SET_OFFLINE(conn, fname);
+ if (lret == -1) {
+ DEBUG(0, ("set_dos_mode: client has asked to set "
+ "FILE_ATTRIBUTE_OFFLINE to %s/%s but there was "
+ "an error while setting it or it is not supported.\n",
+ parent_dir, fname));
+ }
+ }
+ }
+
+ dosmode &= ~FILE_ATTRIBUTE_OFFLINE;
+ old_mode &= ~FILE_ATTRIBUTE_OFFLINE;
+
+ if (old_mode == dosmode) {
+ st->st_mode = unixmode;
+ return(0);
+ }
+
+ /* Store the DOS attributes in an EA by preference. */
+ if (set_ea_dos_attribute(conn, fname, st, dosmode)) {
+ if (!newfile) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
+ }
+ st->st_mode = unixmode;
+ return 0;
+ }
+
+ unixmode = unix_mode(conn,dosmode,fname, parent_dir);
+
+ /* preserve the s bits */
+ mask |= (S_ISUID | S_ISGID);
+
+ /* preserve the t bit */
+#ifdef S_ISVTX
+ mask |= S_ISVTX;
+#endif
+
+ /* possibly preserve the x bits */
+ if (!MAP_ARCHIVE(conn))
+ mask |= S_IXUSR;
+ if (!MAP_SYSTEM(conn))
+ mask |= S_IXGRP;
+ if (!MAP_HIDDEN(conn))
+ mask |= S_IXOTH;
+
+ unixmode |= (st->st_mode & mask);
+
+ /* if we previously had any r bits set then leave them alone */
+ if ((tmp = st->st_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
+ unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
+ unixmode |= tmp;
+ }
+
+ /* if we previously had any w bits set then leave them alone
+ whilst adding in the new w bits, if the new mode is not rdonly */
+ if (!IS_DOS_READONLY(dosmode)) {
+ unixmode |= (st->st_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
+ }
+
+ ret = SMB_VFS_CHMOD(conn, fname, unixmode);
+ if (ret == 0) {
+ if(!newfile || (lret != -1)) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
+ }
+ st->st_mode = unixmode;
+ return 0;
+ }
+
+ if((errno != EPERM) && (errno != EACCES))
+ return -1;
+
+ if(!lp_dos_filemode(SNUM(conn)))
+ return -1;
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ /* Check if we have write access. */
+ if (CAN_WRITE(conn)) {
+ /*
+ * We need to open the file with write access whilst
+ * still in our current user context. This ensures we
+ * are not violating security in doing the fchmod.
+ * This file open does *not* break any oplocks we are
+ * holding. We need to review this.... may need to
+ * break batch oplocks open by others. JRA.
+ */
+ files_struct *fsp;
+ if (!NT_STATUS_IS_OK(open_file_fchmod(conn,fname,st,&fsp)))
+ return -1;
+ become_root();
+ ret = SMB_VFS_FCHMOD(fsp, unixmode);
+ unbecome_root();
+ close_file_fchmod(fsp);
+ if (!newfile) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
+ }
+ if (ret == 0) {
+ st->st_mode = unixmode;
+ }
+ }
+
+ return( ret );
+}
+
+/*******************************************************************
+ Wrapper around the VFS ntimes that possibly allows DOS semantics rather
+ than POSIX.
+*******************************************************************/
+
+int file_ntimes(connection_struct *conn, const char *fname, const struct timespec ts[2])
+{
+ SMB_STRUCT_STAT sbuf;
+ int ret = -1;
+
+ errno = 0;
+ ZERO_STRUCT(sbuf);
+
+ DEBUG(6, ("file_ntime: actime: %s",
+ time_to_asc(convert_timespec_to_time_t(ts[0]))));
+ DEBUG(6, ("file_ntime: modtime: %s",
+ time_to_asc(convert_timespec_to_time_t(ts[1]))));
+
+ /* Don't update the time on read-only shares */
+ /* We need this as set_filetime (which can be called on
+ close and other paths) can end up calling this function
+ without the NEED_WRITE protection. Found by :
+ Leo Weppelman <leo@wau.mis.ah.nl>
+ */
+
+ if (!CAN_WRITE(conn)) {
+ return 0;
+ }
+
+ if(SMB_VFS_NTIMES(conn, fname, ts) == 0) {
+ return 0;
+ }
+
+ if((errno != EPERM) && (errno != EACCES)) {
+ return -1;
+ }
+
+ if(!lp_dos_filetimes(SNUM(conn))) {
+ return -1;
+ }
+
+ /* We have permission (given by the Samba admin) to
+ break POSIX semantics and allow a user to change
+ the time on a file they don't own but can write to
+ (as DOS does).
+ */
+
+ /* Check if we have write access. */
+ if (can_write_to_file(conn, fname, &sbuf)) {
+ /* We are allowed to become root and change the filetime. */
+ become_root();
+ ret = SMB_VFS_NTIMES(conn, fname, ts);
+ unbecome_root();
+ }
+
+ return ret;
+}
+
+/******************************************************************
+ Force a "sticky" write time on a pathname. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_path(connection_struct *conn, const char *fname,
+ struct file_id fileid, const struct timespec mtime)
+{
+ if (null_timespec(mtime)) {
+ return true;
+ }
+
+ if (!set_sticky_write_time(fileid, mtime)) {
+ return false;
+ }
+
+ return true;
+}
+
+/******************************************************************
+ Force a "sticky" write time on an fsp. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_fsp(struct files_struct *fsp, const struct timespec mtime)
+{
+ fsp->write_time_forced = true;
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ return set_sticky_write_time_path(fsp->conn, fsp->fsp_name,
+ fsp->file_id, mtime);
+}
+
+/******************************************************************
+ Update a write time immediately, without the 2 second delay.
+******************************************************************/
+
+bool update_write_time(struct files_struct *fsp)
+{
+ if (!set_write_time(fsp->file_id, timespec_current())) {
+ return false;
+ }
+
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_LAST_WRITE, fsp->fsp_name);
+
+ return true;
+}
diff --git a/source3/smbd/error.c b/source3/smbd/error.c
new file mode 100644
index 0000000000..de2de088ec
--- /dev/null
+++ b/source3/smbd/error.c
@@ -0,0 +1,169 @@
+/*
+ Unix SMB/CIFS implementation.
+ error packet handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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"
+
+/* From lib/error.c */
+extern struct unix_error_map unix_dos_nt_errmap[];
+
+extern uint32 global_client_caps;
+
+bool use_nt_status(void)
+{
+ return lp_nt_status_support() && (global_client_caps & CAP_STATUS32);
+}
+
+/****************************************************************************
+ Create an error packet. Normally called using the ERROR() macro.
+ Setting eclass and ecode only and status to NT_STATUS_OK forces DOS errors.
+ Setting status only and eclass and ecode to zero forces NT errors.
+ If the override errors are set they take precedence over any passed in values.
+****************************************************************************/
+
+void error_packet_set(char *outbuf, uint8 eclass, uint32 ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ bool force_nt_status = False;
+ bool force_dos_status = False;
+
+ if (eclass == (uint8)-1) {
+ force_nt_status = True;
+ } else if (NT_STATUS_IS_DOS(ntstatus)) {
+ force_dos_status = True;
+ }
+
+ if (force_nt_status || (!force_dos_status && lp_nt_status_support() && (global_client_caps & CAP_STATUS32))) {
+ /* We're returning an NT error. */
+ if (NT_STATUS_V(ntstatus) == 0 && eclass) {
+ ntstatus = dos_to_ntstatus(eclass, ecode);
+ }
+ SIVAL(outbuf,smb_rcls,NT_STATUS_V(ntstatus));
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)|FLAGS2_32_BIT_ERROR_CODES);
+ DEBUG(3,("error packet at %s(%d) cmd=%d (%s) %s\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+ smb_fn_name(CVAL(outbuf,smb_com)),
+ nt_errstr(ntstatus)));
+ } else {
+ /* We're returning a DOS error only. */
+ if (NT_STATUS_IS_DOS(ntstatus)) {
+ eclass = NT_STATUS_DOS_CLASS(ntstatus);
+ ecode = NT_STATUS_DOS_CODE(ntstatus);
+ } else if (eclass == 0 && NT_STATUS_V(ntstatus)) {
+ ntstatus_to_dos(ntstatus, &eclass, &ecode);
+ }
+
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)&~FLAGS2_32_BIT_ERROR_CODES);
+ SSVAL(outbuf,smb_rcls,eclass);
+ SSVAL(outbuf,smb_err,ecode);
+
+ DEBUG(3,("error packet at %s(%d) cmd=%d (%s) eclass=%d ecode=%d\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+ smb_fn_name(CVAL(outbuf,smb_com)),
+ eclass,
+ ecode));
+ }
+}
+
+int error_packet(char *outbuf, uint8 eclass, uint32 ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ int outsize = srv_set_message(outbuf,0,0,True);
+ error_packet_set(outbuf, eclass, ecode, ntstatus, line, file);
+ return outsize;
+}
+
+void reply_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, 0, 0, ntstatus, line, file);
+}
+
+void reply_force_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, -1, -1, ntstatus, line, file);
+}
+
+void reply_dos_error(struct smb_request *req, uint8 eclass, uint32 ecode,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, eclass, ecode, NT_STATUS_OK, line,
+ file);
+}
+
+void reply_both_error(struct smb_request *req, uint8 eclass, uint32 ecode,
+ NTSTATUS status, int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, eclass, ecode, status,
+ line, file);
+}
+
+void reply_openerror(struct smb_request *req, NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * We hit an existing file, and if we're returning DOS
+ * error codes OBJECT_NAME_COLLISION would map to
+ * ERRDOS/183, we need to return ERRDOS/80, see bug
+ * 4852.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_COLLISION,
+ ERRDOS, ERRfilexists);
+ } else {
+ reply_nterror(req, status);
+ }
+}
+
+void reply_unix_error(struct smb_request *req, uint8 defclass, uint32 defcode,
+ NTSTATUS defstatus, int line, const char *file)
+{
+ int eclass=defclass;
+ int ecode=defcode;
+ NTSTATUS ntstatus = defstatus;
+ int i=0;
+
+ TALLOC_FREE(req->outbuf);
+ reply_outbuf(req, 0, 0);
+
+ if (errno != 0) {
+ DEBUG(3,("unix_error_packet: error string = %s\n",
+ strerror(errno)));
+
+ while (unix_dos_nt_errmap[i].dos_class != 0) {
+ if (unix_dos_nt_errmap[i].unix_error == errno) {
+ eclass = unix_dos_nt_errmap[i].dos_class;
+ ecode = unix_dos_nt_errmap[i].dos_code;
+ ntstatus = unix_dos_nt_errmap[i].nt_error;
+ break;
+ }
+ i++;
+ }
+ }
+
+ error_packet_set((char *)req->outbuf, eclass, ecode, ntstatus,
+ line, file);
+}
diff --git a/source3/smbd/fake_file.c b/source3/smbd/fake_file.c
new file mode 100644
index 0000000000..8dd9abee1a
--- /dev/null
+++ b/source3/smbd/fake_file.c
@@ -0,0 +1,161 @@
+/*
+ Unix SMB/CIFS implementation.
+ FAKE FILE suppport, for faking up special files windows want access to
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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"
+
+struct fake_file_type {
+ const char *name;
+ enum FAKE_FILE_TYPE type;
+ void *(*init_pd)(TALLOC_CTX *mem_ctx);
+};
+
+static struct fake_file_type fake_files[] = {
+#ifdef WITH_QUOTAS
+ {FAKE_FILE_NAME_QUOTA_UNIX, FAKE_FILE_TYPE_QUOTA, init_quota_handle},
+#endif /* WITH_QUOTAS */
+ {NULL, FAKE_FILE_TYPE_NONE, NULL}
+};
+
+/****************************************************************************
+ Create a fake file handle
+****************************************************************************/
+
+static struct fake_file_handle *init_fake_file_handle(enum FAKE_FILE_TYPE type)
+{
+ struct fake_file_handle *fh = NULL;
+ int i;
+
+ for (i=0; fake_files[i].name!=NULL; i++) {
+ if (fake_files[i].type==type) {
+ break;
+ }
+ }
+
+ if (fake_files[i].name == NULL) {
+ return NULL;
+ }
+
+ DEBUG(5,("init_fake_file_handle: for [%s]\n",fake_files[i].name));
+
+ fh = talloc(NULL, struct fake_file_handle);
+ if (fh == NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed.\n"));
+ return NULL;
+ }
+
+ fh->type = type;
+
+ if (fake_files[i].init_pd) {
+ fh->private_data = fake_files[i].init_pd(fh);
+ }
+ return fh;
+}
+
+/****************************************************************************
+ Does this name match a fake filename ?
+****************************************************************************/
+
+enum FAKE_FILE_TYPE is_fake_file(const char *fname)
+{
+#ifdef HAVE_SYS_QUOTAS
+ int i;
+#endif
+
+ if (!fname) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ for (i=0;fake_files[i].name!=NULL;i++) {
+ if (strncmp(fname,fake_files[i].name,strlen(fake_files[i].name))==0) {
+ DEBUG(5,("is_fake_file: [%s] is a fake file\n",fname));
+ return fake_files[i].type;
+ }
+ }
+#endif
+
+ return FAKE_FILE_TYPE_NONE;
+}
+
+
+/****************************************************************************
+ Open a fake quota file with a share mode.
+****************************************************************************/
+
+NTSTATUS open_fake_file(connection_struct *conn,
+ uint16_t current_vuid,
+ enum FAKE_FILE_TYPE fake_file_type,
+ const char *fname,
+ uint32 access_mask,
+ files_struct **result)
+{
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ /* access check */
+ if (conn->server_info->utok.uid != 0) {
+ DEBUG(3, ("open_fake_file_shared: access_denied to "
+ "service[%s] file[%s] user[%s]\n",
+ lp_servicename(SNUM(conn)), fname,
+ conn->server_info->unix_name));
+ return NT_STATUS_ACCESS_DENIED;
+
+ }
+
+ status = file_new(conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("open_fake_file_shared: fname = %s, FID = %d, access_mask = 0x%x\n",
+ fname, fsp->fnum, (unsigned int)access_mask));
+
+ fsp->conn = conn;
+ fsp->fh->fd = -1;
+ fsp->vuid = current_vuid;
+ fsp->fh->pos = -1;
+ fsp->can_lock = False; /* Should this be true ? - No, JRA */
+ fsp->access_mask = access_mask;
+ string_set(&fsp->fsp_name,fname);
+
+ fsp->fake_file_handle = init_fake_file_handle(fake_file_type);
+
+ if (fsp->fake_file_handle==NULL) {
+ file_free(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ conn->num_files_open++;
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+void destroy_fake_file_handle(struct fake_file_handle **fh)
+{
+ if (!fh) {
+ return;
+ }
+ TALLOC_FREE(*fh);
+}
+
+NTSTATUS close_fake_file(files_struct *fsp)
+{
+ file_free(fsp);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/file_access.c b/source3/smbd/file_access.c
new file mode 100644
index 0000000000..84c993d06b
--- /dev/null
+++ b/source3/smbd/file_access.c
@@ -0,0 +1,222 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access to files based on security descriptors.
+ Copyright (C) Jeremy Allison 2005-2006.
+ Copyright (C) Michael Adam 2007.
+
+ 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"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/**
+ * Security descriptor / NT Token level access check function.
+ */
+bool can_access_file_acl(struct connection_struct *conn,
+ const char * fname,
+ uint32_t access_mask)
+{
+ bool result;
+ NTSTATUS status;
+ uint32_t access_granted;
+ struct security_descriptor *secdesc = NULL;
+
+ status = SMB_VFS_GET_NT_ACL(conn, fname,
+ (OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION),
+ &secdesc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Could not get acl: %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ result = se_access_check(secdesc, conn->server_info->ptok,
+ access_mask, &access_granted, &status);
+ TALLOC_FREE(secdesc);
+ return result;
+}
+
+/****************************************************************************
+ Actually emulate the in-kernel access checking for delete access. We need
+ this to successfully return ACCESS_DENIED on a file open for delete access.
+****************************************************************************/
+
+bool can_delete_file_in_directory(connection_struct *conn, const char *fname)
+{
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *dname = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ return False;
+ }
+
+ /* Get the parent directory permission mask and owners. */
+ if (!parent_dirname_talloc(ctx,
+ fname,
+ &dname,
+ NULL)) {
+ return False;
+ }
+ if(SMB_VFS_STAT(conn, dname, &sbuf) != 0) {
+ return False;
+ }
+
+ /* fast paths first */
+
+ if (!S_ISDIR(sbuf.st_mode)) {
+ return False;
+ }
+ if (conn->server_info->utok.uid == 0 || conn->admin_user) {
+ /* I'm sorry sir, I didn't know you were root... */
+ return True;
+ }
+
+#ifdef S_ISVTX
+ /* sticky bit means delete only by owner or root. */
+ if (sbuf.st_mode & S_ISVTX) {
+ SMB_STRUCT_STAT sbuf_file;
+ if(SMB_VFS_STAT(conn, fname, &sbuf_file) != 0) {
+ if (errno == ENOENT) {
+ /* If the file doesn't already exist then
+ * yes we'll be able to delete it. */
+ return True;
+ }
+ return False;
+ }
+ /*
+ * Patch from SATOH Fumiyasu <fumiyas@miraclelinux.com>
+ * for bug #3348. Don't assume owning sticky bit
+ * directory means write access allowed.
+ */
+ if (conn->server_info->utok.uid != sbuf_file.st_uid) {
+ return False;
+ }
+ }
+#endif
+
+ /* now for ACL checks */
+
+ /*
+ * There's two ways to get the permission to delete a file: First by
+ * having the DELETE bit on the file itself and second if that does
+ * not help, by the DELETE_CHILD bit on the containing directory.
+ *
+ * Here we check the other way round because with just posix
+ * permissions looking at the file itself will never grant DELETE, so
+ * by looking at the directory first we save one get_acl call.
+ */
+
+ if (can_access_file_acl(conn, dname, FILE_DELETE_CHILD)) {
+ return true;
+ }
+
+ return can_access_file_acl(conn, fname, DELETE_ACCESS);
+}
+
+/****************************************************************************
+ Actually emulate the in-kernel access checking for read/write access. We need
+ this to successfully check for ability to write for dos filetimes.
+ Note this doesn't take into account share write permissions.
+****************************************************************************/
+
+bool can_access_file_data(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf, uint32 access_mask)
+{
+ if (!(access_mask & (FILE_READ_DATA|FILE_WRITE_DATA))) {
+ return False;
+ }
+ access_mask &= (FILE_READ_DATA|FILE_WRITE_DATA);
+
+ /* some fast paths first */
+
+ DEBUG(10,("can_access_file_data: requesting 0x%x on file %s\n",
+ (unsigned int)access_mask, fname ));
+
+ if (conn->server_info->utok.uid == 0 || conn->admin_user) {
+ /* I'm sorry sir, I didn't know you were root... */
+ return True;
+ }
+
+ if (!VALID_STAT(*psbuf)) {
+ /* Get the file permission mask and owners. */
+ if(SMB_VFS_STAT(conn, fname, psbuf) != 0) {
+ return False;
+ }
+ }
+
+ /* Check primary owner access. */
+ if (conn->server_info->utok.uid == psbuf->st_uid) {
+ switch (access_mask) {
+ case FILE_READ_DATA:
+ return (psbuf->st_mode & S_IRUSR) ? True : False;
+
+ case FILE_WRITE_DATA:
+ return (psbuf->st_mode & S_IWUSR) ? True : False;
+
+ default: /* FILE_READ_DATA|FILE_WRITE_DATA */
+
+ if ((psbuf->st_mode & (S_IWUSR|S_IRUSR)) == (S_IWUSR|S_IRUSR)) {
+ return True;
+ } else {
+ return False;
+ }
+ }
+ }
+
+ /* now for ACL checks */
+
+ return can_access_file_acl(conn, fname, access_mask);
+}
+
+/****************************************************************************
+ Userspace check for write access.
+ Note this doesn't take into account share write permissions.
+****************************************************************************/
+
+bool can_write_to_file(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf)
+{
+ return can_access_file_data(conn, fname, psbuf, FILE_WRITE_DATA);
+}
+
+/****************************************************************************
+ Check for an existing default Windows ACL on a directory.
+****************************************************************************/
+
+bool directory_has_default_acl(connection_struct *conn, const char *fname)
+{
+ /* returns talloced off tos. */
+ struct security_descriptor *secdesc = NULL;
+ unsigned int i;
+ NTSTATUS status = SMB_VFS_GET_NT_ACL(conn, fname,
+ DACL_SECURITY_INFORMATION, &secdesc);
+
+ if (!NT_STATUS_IS_OK(status) || secdesc == NULL) {
+ return false;
+ }
+
+ for (i = 0; i < secdesc->dacl->num_aces; i++) {
+ struct security_ace *psa = &secdesc->dacl->aces[i];
+ if (psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ TALLOC_FREE(secdesc);
+ return true;
+ }
+ }
+ TALLOC_FREE(secdesc);
+ return false;
+}
diff --git a/source3/smbd/fileio.c b/source3/smbd/fileio.c
new file mode 100644
index 0000000000..60aeeef1e2
--- /dev/null
+++ b/source3/smbd/fileio.c
@@ -0,0 +1,955 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ read/write to a files_struct
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2000-2002. - write cache.
+
+ 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"
+
+static bool setup_write_cache(files_struct *, SMB_OFF_T);
+
+/****************************************************************************
+ Read from write cache if we can.
+****************************************************************************/
+
+static bool read_from_write_cache(files_struct *fsp,char *data,SMB_OFF_T pos,size_t n)
+{
+ write_cache *wcp = fsp->wcp;
+
+ if(!wcp) {
+ return False;
+ }
+
+ if( n > wcp->data_size || pos < wcp->offset || pos + n > wcp->offset + wcp->data_size) {
+ return False;
+ }
+
+ memcpy(data, wcp->data + (pos - wcp->offset), n);
+
+ DO_PROFILE_INC(writecache_read_hits);
+
+ return True;
+}
+
+/****************************************************************************
+ Read from a file.
+****************************************************************************/
+
+ssize_t read_file(files_struct *fsp,char *data,SMB_OFF_T pos,size_t n)
+{
+ ssize_t ret=0,readret;
+
+ /* you can't read from print files */
+ if (fsp->print_file) {
+ return -1;
+ }
+
+ /*
+ * Serve from write cache if we can.
+ */
+
+ if(read_from_write_cache(fsp, data, pos, n)) {
+ fsp->fh->pos = pos + n;
+ fsp->fh->position_information = fsp->fh->pos;
+ return n;
+ }
+
+ flush_write_cache(fsp, READ_FLUSH);
+
+ fsp->fh->pos = pos;
+
+ if (n > 0) {
+#ifdef DMF_FIX
+ int numretries = 3;
+tryagain:
+ readret = SMB_VFS_PREAD(fsp,data,n,pos);
+
+ if (readret == -1) {
+ if ((errno == EAGAIN) && numretries) {
+ DEBUG(3,("read_file EAGAIN retry in 10 seconds\n"));
+ (void)sleep(10);
+ --numretries;
+ goto tryagain;
+ }
+ return -1;
+ }
+#else /* NO DMF fix. */
+ readret = SMB_VFS_PREAD(fsp,data,n,pos);
+
+ if (readret == -1) {
+ return -1;
+ }
+#endif
+ if (readret > 0) {
+ ret += readret;
+ }
+ }
+
+ DEBUG(10,("read_file (%s): pos = %.0f, size = %lu, returned %lu\n",
+ fsp->fsp_name, (double)pos, (unsigned long)n, (long)ret ));
+
+ fsp->fh->pos += ret;
+ fsp->fh->position_information = fsp->fh->pos;
+
+ return(ret);
+}
+
+/* how many write cache buffers have been allocated */
+static unsigned int allocated_write_caches;
+
+/****************************************************************************
+ *Really* write to a file.
+****************************************************************************/
+
+static ssize_t real_write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ SMB_OFF_T pos,
+ size_t n)
+{
+ ssize_t ret;
+
+ if (pos == -1) {
+ ret = vfs_write_data(req, fsp, data, n);
+ } else {
+ fsp->fh->pos = pos;
+ if (pos && lp_strict_allocate(SNUM(fsp->conn))) {
+ if (vfs_fill_sparse(fsp, pos) == -1) {
+ return -1;
+ }
+ }
+ ret = vfs_pwrite_data(req, fsp, data, n, pos);
+ }
+
+ DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
+ fsp->fsp_name, (double)pos, (unsigned long)n, (long)ret ));
+
+ if (ret != -1) {
+ fsp->fh->pos += ret;
+
+/* Yes - this is correct - writes don't update this. JRA. */
+/* Found by Samba4 tests. */
+#if 0
+ fsp->position_information = fsp->pos;
+#endif
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ File size cache change.
+ Updates size on disk but doesn't flush the cache.
+****************************************************************************/
+
+static int wcp_file_size_change(files_struct *fsp)
+{
+ int ret;
+ write_cache *wcp = fsp->wcp;
+
+ wcp->file_size = wcp->offset + wcp->data_size;
+ ret = SMB_VFS_FTRUNCATE(fsp, wcp->file_size);
+ if (ret == -1) {
+ DEBUG(0,("wcp_file_size_change (%s): ftruncate of size %.0f error %s\n",
+ fsp->fsp_name, (double)wcp->file_size, strerror(errno) ));
+ }
+ return ret;
+}
+
+static void update_write_time_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+
+ /* Remove the timed event handler. */
+ TALLOC_FREE(fsp->update_write_time_event);
+ DEBUG(5, ("Update write time on %s\n", fsp->fsp_name));
+
+ /* change the write time if not already changed by someone else */
+ update_write_time(fsp);
+}
+
+/*********************************************************
+ Schedule a write time update for WRITE_TIME_UPDATE_USEC_DELAY
+ in the future.
+*********************************************************/
+
+void trigger_write_time_update(struct files_struct *fsp)
+{
+ int delay;
+
+ if (fsp->write_time_forced) {
+ /* No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ if (fsp->update_write_time_triggered) {
+ /*
+ * We only update the write time
+ * on the first write. After that
+ * no other writes affect this.
+ */
+ return;
+ }
+ fsp->update_write_time_triggered = true;
+
+ delay = lp_parm_int(SNUM(fsp->conn),
+ "smbd", "writetimeupdatedelay",
+ WRITE_TIME_UPDATE_USEC_DELAY);
+
+ /* trigger the update 2 seconds later */
+ fsp->update_write_time_on_close = true;
+ fsp->update_write_time_event =
+ event_add_timed(smbd_event_context(), NULL,
+ timeval_current_ofs(0, delay),
+ "update_write_time_handler",
+ update_write_time_handler, fsp);
+}
+
+void trigger_write_time_update_immediate(struct files_struct *fsp)
+{
+ if (fsp->write_time_forced) {
+ /*
+ * No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ TALLOC_FREE(fsp->update_write_time_event);
+ DEBUG(5, ("Update write time immediate on %s\n", fsp->fsp_name));
+
+ fsp->update_write_time_triggered = true;
+
+ fsp->update_write_time_on_close = false;
+ update_write_time(fsp);
+}
+
+/****************************************************************************
+ Write to a file.
+****************************************************************************/
+
+ssize_t write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ SMB_OFF_T pos,
+ size_t n)
+{
+ write_cache *wcp = fsp->wcp;
+ ssize_t total_written = 0;
+ int write_path = -1;
+
+ if (fsp->print_file) {
+ fstring sharename;
+ uint32 jobid;
+
+ if (!rap_to_pjobid(fsp->rap_print_jobid, sharename, &jobid)) {
+ DEBUG(3,("write_file: Unable to map RAP jobid %u to jobid.\n",
+ (unsigned int)fsp->rap_print_jobid ));
+ errno = EBADF;
+ return -1;
+ }
+
+ return print_job_write(SNUM(fsp->conn), jobid, data, pos, n);
+ }
+
+ if (!fsp->can_write) {
+ errno = EPERM;
+ return -1;
+ }
+
+ if (!fsp->modified) {
+ SMB_STRUCT_STAT st;
+ fsp->modified = True;
+
+ if (SMB_VFS_FSTAT(fsp, &st) == 0) {
+ int dosmode;
+ trigger_write_time_update(fsp);
+ dosmode = dos_mode(fsp->conn,fsp->fsp_name,&st);
+ if ((lp_store_dos_attributes(SNUM(fsp->conn)) ||
+ MAP_ARCHIVE(fsp->conn)) &&
+ !IS_DOS_ARCHIVE(dosmode)) {
+ file_set_dosmode(fsp->conn,fsp->fsp_name,
+ dosmode | aARCH,&st,
+ NULL,
+ false);
+ }
+
+ /*
+ * If this is the first write and we have an exclusive oplock then setup
+ * the write cache.
+ */
+
+ if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && !wcp) {
+ setup_write_cache(fsp, st.st_size);
+ wcp = fsp->wcp;
+ }
+ }
+ }
+
+#ifdef WITH_PROFILE
+ DO_PROFILE_INC(writecache_total_writes);
+ if (!fsp->oplock_type) {
+ DO_PROFILE_INC(writecache_non_oplock_writes);
+ }
+#endif
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ release_level_2_oplocks_on_change(fsp);
+
+#ifdef WITH_PROFILE
+ if (profile_p && profile_p->writecache_total_writes % 500 == 0) {
+ DEBUG(3,("WRITECACHE: initwrites=%u abutted=%u total=%u \
+nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
+ profile_p->writecache_init_writes,
+ profile_p->writecache_abutted_writes,
+ profile_p->writecache_total_writes,
+ profile_p->writecache_non_oplock_writes,
+ profile_p->writecache_allocated_write_caches,
+ profile_p->writecache_num_write_caches,
+ profile_p->writecache_direct_writes,
+ profile_p->writecache_num_perfect_writes,
+ profile_p->writecache_read_hits ));
+
+ DEBUG(3,("WRITECACHE: Flushes SEEK=%d, READ=%d, WRITE=%d, READRAW=%d, OPLOCK=%d, CLOSE=%d, SYNC=%d\n",
+ profile_p->writecache_flushed_writes[SEEK_FLUSH],
+ profile_p->writecache_flushed_writes[READ_FLUSH],
+ profile_p->writecache_flushed_writes[WRITE_FLUSH],
+ profile_p->writecache_flushed_writes[READRAW_FLUSH],
+ profile_p->writecache_flushed_writes[OPLOCK_RELEASE_FLUSH],
+ profile_p->writecache_flushed_writes[CLOSE_FLUSH],
+ profile_p->writecache_flushed_writes[SYNC_FLUSH] ));
+ }
+#endif
+
+ if (wcp && req->unread_bytes) {
+ /* If we're using receivefile don't
+ * deal with a write cache.
+ */
+ flush_write_cache(fsp, WRITE_FLUSH);
+ delete_write_cache(fsp);
+ wcp = NULL;
+ }
+
+ if(!wcp) {
+ DO_PROFILE_INC(writecache_direct_writes);
+ total_written = real_write_file(req, fsp, data, pos, n);
+ return total_written;
+ }
+
+ DEBUG(9,("write_file (%s)(fd=%d pos=%.0f size=%u) wcp->offset=%.0f wcp->data_size=%u\n",
+ fsp->fsp_name, fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (unsigned int)wcp->data_size));
+
+ fsp->fh->pos = pos + n;
+
+ /*
+ * If we have active cache and it isn't contiguous then we flush.
+ * NOTE: There is a small problem with running out of disk ....
+ */
+
+ if (wcp->data_size) {
+ bool cache_flush_needed = False;
+
+ if ((pos >= wcp->offset) && (pos <= wcp->offset + wcp->data_size)) {
+
+ /* ASCII art.... JRA.
+
+ +--------------+-----
+ | Cached data | Rest of allocated cache buffer....
+ +--------------+-----
+
+ +-------------------+
+ | Data to write |
+ +-------------------+
+
+ */
+
+ /*
+ * Start of write overlaps or abutts the existing data.
+ */
+
+ size_t data_used = MIN((wcp->alloc_size - (pos - wcp->offset)), n);
+
+ memcpy(wcp->data + (pos - wcp->offset), data, data_used);
+
+ /*
+ * Update the current buffer size with the new data.
+ */
+
+ if(pos + data_used > wcp->offset + wcp->data_size) {
+ wcp->data_size = pos + data_used - wcp->offset;
+ }
+
+ /*
+ * Update the file size if changed.
+ */
+
+ if (wcp->offset + wcp->data_size > wcp->file_size) {
+ if (wcp_file_size_change(fsp) == -1) {
+ return -1;
+ }
+ }
+
+ /*
+ * If we used all the data then
+ * return here.
+ */
+
+ if(n == data_used) {
+ return n;
+ } else {
+ cache_flush_needed = True;
+ }
+ /*
+ * Move the start of data forward by the amount used,
+ * cut down the amount left by the same amount.
+ */
+
+ data += data_used;
+ pos += data_used;
+ n -= data_used;
+
+ DO_PROFILE_INC(writecache_abutted_writes);
+ total_written = data_used;
+
+ write_path = 1;
+
+ } else if ((pos < wcp->offset) && (pos + n > wcp->offset) &&
+ (pos + n <= wcp->offset + wcp->alloc_size)) {
+
+ /* ASCII art.... JRA.
+
+ +---------------+
+ | Cache buffer |
+ +---------------+
+
+ +-------------------+
+ | Data to write |
+ +-------------------+
+
+ */
+
+ /*
+ * End of write overlaps the existing data.
+ */
+
+ size_t data_used = pos + n - wcp->offset;
+
+ memcpy(wcp->data, data + n - data_used, data_used);
+
+ /*
+ * Update the current buffer size with the new data.
+ */
+
+ if(pos + n > wcp->offset + wcp->data_size) {
+ wcp->data_size = pos + n - wcp->offset;
+ }
+
+ /*
+ * Update the file size if changed.
+ */
+
+ if (wcp->offset + wcp->data_size > wcp->file_size) {
+ if (wcp_file_size_change(fsp) == -1) {
+ return -1;
+ }
+ }
+
+ /*
+ * We don't need to move the start of data, but we
+ * cut down the amount left by the amount used.
+ */
+
+ n -= data_used;
+
+ /*
+ * We cannot have used all the data here.
+ */
+
+ cache_flush_needed = True;
+
+ DO_PROFILE_INC(writecache_abutted_writes);
+ total_written = data_used;
+
+ write_path = 2;
+
+ } else if ( (pos >= wcp->file_size) &&
+ (wcp->offset + wcp->data_size == wcp->file_size) &&
+ (pos > wcp->offset + wcp->data_size) &&
+ (pos < wcp->offset + wcp->alloc_size) ) {
+
+ /* ASCII art.... JRA.
+
+ End of file ---->|
+
+ +---------------+---------------+
+ | Cached data | Cache buffer |
+ +---------------+---------------+
+
+ +-------------------+
+ | Data to write |
+ +-------------------+
+
+ */
+
+ /*
+ * Non-contiguous write part of which fits within
+ * the cache buffer and is extending the file
+ * and the cache contents reflect the current
+ * data up to the current end of the file.
+ */
+
+ size_t data_used;
+
+ if(pos + n <= wcp->offset + wcp->alloc_size) {
+ data_used = n;
+ } else {
+ data_used = wcp->offset + wcp->alloc_size - pos;
+ }
+
+ /*
+ * Fill in the non-continuous area with zeros.
+ */
+
+ memset(wcp->data + wcp->data_size, '\0',
+ pos - (wcp->offset + wcp->data_size) );
+
+ memcpy(wcp->data + (pos - wcp->offset), data, data_used);
+
+ /*
+ * Update the current buffer size with the new data.
+ */
+
+ if(pos + data_used > wcp->offset + wcp->data_size) {
+ wcp->data_size = pos + data_used - wcp->offset;
+ }
+
+ /*
+ * Update the file size if changed.
+ */
+
+ if (wcp->offset + wcp->data_size > wcp->file_size) {
+ if (wcp_file_size_change(fsp) == -1) {
+ return -1;
+ }
+ }
+
+ /*
+ * If we used all the data then
+ * return here.
+ */
+
+ if(n == data_used) {
+ return n;
+ } else {
+ cache_flush_needed = True;
+ }
+
+ /*
+ * Move the start of data forward by the amount used,
+ * cut down the amount left by the same amount.
+ */
+
+ data += data_used;
+ pos += data_used;
+ n -= data_used;
+
+ DO_PROFILE_INC(writecache_abutted_writes);
+ total_written = data_used;
+
+ write_path = 3;
+
+ } else if ( (pos >= wcp->file_size) &&
+ (n == 1) &&
+ (wcp->file_size == wcp->offset + wcp->data_size) &&
+ (pos < wcp->file_size + wcp->alloc_size)) {
+
+ /*
+
+ End of file ---->|
+
+ +---------------+---------------+
+ | Cached data | Cache buffer |
+ +---------------+---------------+
+
+ |<------- allocated size ---------------->|
+
+ +--------+
+ | 1 Byte |
+ +--------+
+
+ MS-Office seems to do this a lot to determine if there's enough
+ space on the filesystem to write a new file.
+
+ Change to :
+
+ End of file ---->|
+ +-----------------------+--------+
+ | Zeroed Cached data | 1 Byte |
+ +-----------------------+--------+
+ */
+
+ flush_write_cache(fsp, WRITE_FLUSH);
+ wcp->offset = wcp->file_size;
+ wcp->data_size = pos - wcp->file_size + 1;
+ memset(wcp->data, '\0', wcp->data_size);
+ memcpy(wcp->data + wcp->data_size-1, data, 1);
+
+ /*
+ * Update the file size if changed.
+ */
+
+ if (wcp->offset + wcp->data_size > wcp->file_size) {
+ if (wcp_file_size_change(fsp) == -1) {
+ return -1;
+ }
+ }
+
+ return n;
+
+ } else {
+
+ /* ASCII art..... JRA.
+
+ Case 1).
+
+ +---------------+---------------+
+ | Cached data | Cache buffer |
+ +---------------+---------------+
+
+ +-------------------+
+ | Data to write |
+ +-------------------+
+
+ Case 2).
+
+ +---------------+---------------+
+ | Cached data | Cache buffer |
+ +---------------+---------------+
+
+ +-------------------+
+ | Data to write |
+ +-------------------+
+
+ Case 3).
+
+ +---------------+---------------+
+ | Cached data | Cache buffer |
+ +---------------+---------------+
+
+ +-----------------------------------------------------+
+ | Data to write |
+ +-----------------------------------------------------+
+
+ */
+
+ /*
+ * Write is bigger than buffer, or there is no overlap on the
+ * low or high ends.
+ */
+
+ DEBUG(9,("write_file: non cacheable write : fd = %d, pos = %.0f, len = %u, current cache pos = %.0f \
+len = %u\n",fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (unsigned int)wcp->data_size ));
+
+ /*
+ * If write would fit in the cache, and is larger than
+ * the data already in the cache, flush the cache and
+ * preferentially copy the data new data into it. Otherwise
+ * just write the data directly.
+ */
+
+ if ( n <= wcp->alloc_size && n > wcp->data_size) {
+ cache_flush_needed = True;
+ } else {
+ ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
+
+ /*
+ * If the write overlaps the entire cache, then
+ * discard the current contents of the cache.
+ * Fix from Rasmus Borup Hansen rbh@math.ku.dk.
+ */
+
+ if ((pos <= wcp->offset) &&
+ (pos + n >= wcp->offset + wcp->data_size) ) {
+ DEBUG(9,("write_file: discarding overwritten write \
+cache: fd = %d, off=%.0f, size=%u\n", fsp->fh->fd, (double)wcp->offset, (unsigned int)wcp->data_size ));
+ wcp->data_size = 0;
+ }
+
+ DO_PROFILE_INC(writecache_direct_writes);
+ if (ret == -1) {
+ return ret;
+ }
+
+ if (pos + ret > wcp->file_size) {
+ wcp->file_size = pos + ret;
+ }
+
+ return ret;
+ }
+
+ write_path = 4;
+
+ }
+
+ if (cache_flush_needed) {
+ DEBUG(3,("WRITE_FLUSH:%d: due to noncontinuous write: fd = %d, size = %.0f, pos = %.0f, \
+n = %u, wcp->offset=%.0f, wcp->data_size=%u\n",
+ write_path, fsp->fh->fd, (double)wcp->file_size, (double)pos, (unsigned int)n,
+ (double)wcp->offset, (unsigned int)wcp->data_size ));
+
+ flush_write_cache(fsp, WRITE_FLUSH);
+ }
+ }
+
+ /*
+ * If the write request is bigger than the cache
+ * size, write it all out.
+ */
+
+ if (n > wcp->alloc_size ) {
+ ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (pos + ret > wcp->file_size) {
+ wcp->file_size = pos + n;
+ }
+
+ DO_PROFILE_INC(writecache_direct_writes);
+ return total_written + n;
+ }
+
+ /*
+ * If there's any data left, cache it.
+ */
+
+ if (n) {
+#ifdef WITH_PROFILE
+ if (wcp->data_size) {
+ DO_PROFILE_INC(writecache_abutted_writes);
+ } else {
+ DO_PROFILE_INC(writecache_init_writes);
+ }
+#endif
+ memcpy(wcp->data+wcp->data_size, data, n);
+ if (wcp->data_size == 0) {
+ wcp->offset = pos;
+ DO_PROFILE_INC(writecache_num_write_caches);
+ }
+ wcp->data_size += n;
+
+ /*
+ * Update the file size if changed.
+ */
+
+ if (wcp->offset + wcp->data_size > wcp->file_size) {
+ if (wcp_file_size_change(fsp) == -1) {
+ return -1;
+ }
+ }
+ DEBUG(9,("wcp->offset = %.0f wcp->data_size = %u cache return %u\n",
+ (double)wcp->offset, (unsigned int)wcp->data_size, (unsigned int)n));
+
+ total_written += n;
+ return total_written; /* .... that's a write :) */
+ }
+
+ return total_written;
+}
+
+/****************************************************************************
+ Delete the write cache structure.
+****************************************************************************/
+
+void delete_write_cache(files_struct *fsp)
+{
+ write_cache *wcp;
+
+ if(!fsp) {
+ return;
+ }
+
+ if(!(wcp = fsp->wcp)) {
+ return;
+ }
+
+ DO_PROFILE_DEC(writecache_allocated_write_caches);
+ allocated_write_caches--;
+
+ SMB_ASSERT(wcp->data_size == 0);
+
+ SAFE_FREE(wcp->data);
+ SAFE_FREE(fsp->wcp);
+
+ DEBUG(10,("delete_write_cache: File %s deleted write cache\n", fsp->fsp_name ));
+}
+
+/****************************************************************************
+ Setup the write cache structure.
+****************************************************************************/
+
+static bool setup_write_cache(files_struct *fsp, SMB_OFF_T file_size)
+{
+ ssize_t alloc_size = lp_write_cache_size(SNUM(fsp->conn));
+ write_cache *wcp;
+
+ if (allocated_write_caches >= MAX_WRITE_CACHES) {
+ return False;
+ }
+
+ if(alloc_size == 0 || fsp->wcp) {
+ return False;
+ }
+
+ if((wcp = SMB_MALLOC_P(write_cache)) == NULL) {
+ DEBUG(0,("setup_write_cache: malloc fail.\n"));
+ return False;
+ }
+
+ wcp->file_size = file_size;
+ wcp->offset = 0;
+ wcp->alloc_size = alloc_size;
+ wcp->data_size = 0;
+ if((wcp->data = (char *)SMB_MALLOC(wcp->alloc_size)) == NULL) {
+ DEBUG(0,("setup_write_cache: malloc fail for buffer size %u.\n",
+ (unsigned int)wcp->alloc_size ));
+ SAFE_FREE(wcp);
+ return False;
+ }
+
+ memset(wcp->data, '\0', wcp->alloc_size );
+
+ fsp->wcp = wcp;
+ DO_PROFILE_INC(writecache_allocated_write_caches);
+ allocated_write_caches++;
+
+ DEBUG(10,("setup_write_cache: File %s allocated write cache size %lu\n",
+ fsp->fsp_name, (unsigned long)wcp->alloc_size ));
+
+ return True;
+}
+
+/****************************************************************************
+ Cope with a size change.
+****************************************************************************/
+
+void set_filelen_write_cache(files_struct *fsp, SMB_OFF_T file_size)
+{
+ if(fsp->wcp) {
+ /* The cache *must* have been flushed before we do this. */
+ if (fsp->wcp->data_size != 0) {
+ char *msg;
+ asprintf(&msg, "set_filelen_write_cache: size change "
+ "on file %s with write cache size = %lu\n",
+ fsp->fsp_name,
+ (unsigned long)fsp->wcp->data_size);
+ smb_panic(msg);
+ }
+ fsp->wcp->file_size = file_size;
+ }
+}
+
+/*******************************************************************
+ Flush a write cache struct to disk.
+********************************************************************/
+
+ssize_t flush_write_cache(files_struct *fsp, enum flush_reason_enum reason)
+{
+ write_cache *wcp = fsp->wcp;
+ size_t data_size;
+ ssize_t ret;
+
+ if(!wcp || !wcp->data_size) {
+ return 0;
+ }
+
+ data_size = wcp->data_size;
+ wcp->data_size = 0;
+
+ DO_PROFILE_DEC_INC(writecache_num_write_caches,writecache_flushed_writes[reason]);
+
+ DEBUG(9,("flushing write cache: fd = %d, off=%.0f, size=%u\n",
+ fsp->fh->fd, (double)wcp->offset, (unsigned int)data_size));
+
+#ifdef WITH_PROFILE
+ if(data_size == wcp->alloc_size) {
+ DO_PROFILE_INC(writecache_num_perfect_writes);
+ }
+#endif
+
+ ret = real_write_file(NULL, fsp, wcp->data, wcp->offset, data_size);
+
+ /*
+ * Ensure file size if kept up to date if write extends file.
+ */
+
+ if ((ret != -1) && (wcp->offset + ret > wcp->file_size)) {
+ wcp->file_size = wcp->offset + ret;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+sync a file
+********************************************************************/
+
+NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through)
+{
+ if (fsp->fh->fd == -1)
+ return NT_STATUS_INVALID_HANDLE;
+
+ if (lp_strict_sync(SNUM(conn)) &&
+ (lp_syncalways(SNUM(conn)) || write_through)) {
+ int ret = flush_write_cache(fsp, SYNC_FLUSH);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ ret = SMB_VFS_FSYNC(fsp);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/************************************************************
+ Perform a stat whether a valid fd or not.
+************************************************************/
+
+int fsp_stat(files_struct *fsp, SMB_STRUCT_STAT *pst)
+{
+ if (fsp->fh->fd == -1) {
+ return SMB_VFS_STAT(fsp->conn, fsp->fsp_name, pst);
+ } else {
+ return SMB_VFS_FSTAT(fsp, pst);
+ }
+}
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
new file mode 100644
index 0000000000..41a0b9296a
--- /dev/null
+++ b/source3/smbd/filename.c
@@ -0,0 +1,956 @@
+/*
+ Unix SMB/CIFS implementation.
+ filename handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1999-2007
+ Copyright (C) Ying Chen 2000
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+
+/*
+ * New hash table stat cache code added by Ying Chen.
+ */
+
+#include "includes.h"
+
+static bool scan_directory(connection_struct *conn, const char *path,
+ char *name, char **found_name);
+static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *orig_path,
+ const char *basepath,
+ const char *streamname,
+ SMB_STRUCT_STAT *pst,
+ char **path);
+
+/****************************************************************************
+ Mangle the 2nd name and check if it is then equal to the first name.
+****************************************************************************/
+
+static bool mangled_equal(const char *name1,
+ const char *name2,
+ const struct share_params *p)
+{
+ char mname[13];
+
+ if (!name_to_8_3(name2, mname, False, p)) {
+ return False;
+ }
+ return strequal(name1, mname);
+}
+
+/****************************************************************************
+ Cope with the differing wildcard and non-wildcard error cases.
+****************************************************************************/
+
+static NTSTATUS determine_path_error(const char *name,
+ bool allow_wcard_last_component)
+{
+ const char *p;
+
+ if (!allow_wcard_last_component) {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ /* We're terminating here so we
+ * can be a little slower and get
+ * the error code right. Windows
+ * treats the last part of the pathname
+ * separately I think, so if the last
+ * component is a wildcard then we treat
+ * this ./ as "end of component" */
+
+ p = strchr(name, '/');
+
+ if (!p && (ms_has_wild(name) || ISDOT(name))) {
+ /* Error code at the end of a pathname. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+}
+
+/****************************************************************************
+This routine is called to convert names from the dos namespace to unix
+namespace. It needs to handle any case conversions, mangling, format
+changes etc.
+
+We assume that we have already done a chdir() to the right "root" directory
+for this service.
+
+The function will return an NTSTATUS error if some part of the name except for
+the last part cannot be resolved, else NT_STATUS_OK.
+
+Note NT_STATUS_OK doesn't mean the name exists or is valid, just that we didn't
+get any fatal errors that should immediately terminate the calling
+SMB processing whilst resolving.
+
+If the saved_last_component != 0, then the unmodified last component
+of the pathname is returned there. This is used in an exceptional
+case in reply_mv (so far). If saved_last_component == 0 then nothing
+is returned there.
+
+If last_component_wcard is true then a MS wildcard was detected and
+should be allowed in the last component of the path only.
+
+On exit from unix_convert, if *pst was not null, then the file stat
+struct will be returned if the file exists and was found, if not this
+stat struct will be filled with zeros (and this can be detected by checking
+for nlinks = 0, which can never be true for any file).
+****************************************************************************/
+
+NTSTATUS unix_convert(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *orig_path,
+ bool allow_wcard_last_component,
+ char **pp_conv_path,
+ char **pp_saved_last_component,
+ SMB_STRUCT_STAT *pst)
+{
+ SMB_STRUCT_STAT st;
+ char *start, *end;
+ char *dirpath = NULL;
+ char *name = NULL;
+ char *stream = NULL;
+ bool component_was_mangled = False;
+ bool name_has_wildcard = False;
+ NTSTATUS result;
+
+ SET_STAT_INVALID(*pst);
+ *pp_conv_path = NULL;
+ if(pp_saved_last_component) {
+ *pp_saved_last_component = NULL;
+ }
+
+ if (conn->printer) {
+ /* we don't ever use the filenames on a printer share as a
+ filename - so don't convert them */
+ if (!(*pp_conv_path = talloc_strdup(ctx,orig_path))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5, ("unix_convert called on file \"%s\"\n", orig_path));
+
+ /*
+ * Conversion to basic unix format is already done in
+ * check_path_syntax().
+ */
+
+ /*
+ * Names must be relative to the root of the service - any leading /.
+ * and trailing /'s should have been trimmed by check_path_syntax().
+ */
+
+#ifdef DEVELOPER
+ SMB_ASSERT(*orig_path != '/');
+#endif
+
+ /*
+ * If we trimmed down to a single '\0' character
+ * then we should use the "." directory to avoid
+ * searching the cache, but not if we are in a
+ * printing share.
+ * As we know this is valid we can return true here.
+ */
+
+ if (!*orig_path) {
+ if (!(name = talloc_strdup(ctx,"."))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (SMB_VFS_STAT(conn,name,&st) == 0) {
+ *pst = st;
+ } else {
+ return map_nt_error_from_unix(errno);
+ }
+ DEBUG(5,("conversion finished \"\" -> %s\n",name));
+ goto done;
+ }
+
+ if (orig_path[0] == '.' && (orig_path[1] == '/' ||
+ orig_path[1] == '\0')) {
+ /* Start of pathname can't be "." only. */
+ if (orig_path[1] == '\0' || orig_path[2] == '\0') {
+ result = NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ result =determine_path_error(
+ &orig_path[2], allow_wcard_last_component);
+ }
+ return result;
+ }
+
+ /*
+ * Ensure saved_last_component is valid even if file exists.
+ */
+
+ if(pp_saved_last_component) {
+ end = strrchr_m(orig_path, '/');
+ if (end) {
+ *pp_saved_last_component = talloc_strdup(ctx, end + 1);
+ } else {
+ *pp_saved_last_component = talloc_strdup(ctx,
+ orig_path);
+ }
+ }
+
+ if (!(name = talloc_strdup(ctx, orig_path))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!lp_posix_pathnames()) {
+ stream = strchr_m(name, ':');
+
+ if (stream != NULL) {
+ char *tmp = talloc_strdup(ctx, stream);
+ if (tmp == NULL) {
+ TALLOC_FREE(name);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *stream = '\0';
+ stream = tmp;
+ }
+ }
+
+ /*
+ * Large directory fix normalization. If we're case sensitive, and
+ * the case preserving parameters are set to "no", normalize the case of
+ * the incoming filename from the client WHETHER IT EXISTS OR NOT !
+ * This is in conflict with the current (3.0.20) man page, but is
+ * what people expect from the "large directory howto". I'll update
+ * the man page. Thanks to jht@samba.org for finding this. JRA.
+ */
+
+ if (conn->case_sensitive && !conn->case_preserve &&
+ !conn->short_case_preserve) {
+ strnorm(name, lp_defaultcase(SNUM(conn)));
+ }
+
+ start = name;
+
+ /* If we're providing case insentive semantics or
+ * the underlying filesystem is case insensitive,
+ * then a case-normalized hit in the stat-cache is
+ * authoratitive. JRA.
+ */
+
+ if((!conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
+ stat_cache_lookup(conn, &name, &dirpath, &start, &st)) {
+ *pst = st;
+ goto done;
+ }
+
+ /*
+ * Make sure "dirpath" is an allocated string, we use this for
+ * building the directories with asprintf and free it.
+ */
+
+ if ((dirpath == NULL) && (!(dirpath = talloc_strdup(ctx,"")))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ TALLOC_FREE(name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * stat the name - if it exists then we are all done!
+ */
+
+ if (SMB_VFS_STAT(conn,name,&st) == 0) {
+ /* Ensure we catch all names with in "/."
+ this is disallowed under Windows. */
+ const char *p = strstr(name, "/."); /* mb safe. */
+ if (p) {
+ if (p[2] == '/') {
+ /* Error code within a pathname. */
+ result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ } else if (p[2] == '\0') {
+ /* Error code at the end of a pathname. */
+ result = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ }
+ stat_cache_add(orig_path, name, conn->case_sensitive);
+ DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+ *pst = st;
+ goto done;
+ }
+
+ DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
+ name, dirpath, start));
+
+ /*
+ * A special case - if we don't have any mangling chars and are case
+ * sensitive or the underlying filesystem is case insentive then searching
+ * won't help.
+ */
+
+ if ((conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
+ !mangle_is_mangled(name, conn->params)) {
+ goto done;
+ }
+
+ /*
+ * is_mangled() was changed to look at an entire pathname, not
+ * just a component. JRA.
+ */
+
+ if (mangle_is_mangled(start, conn->params)) {
+ component_was_mangled = True;
+ }
+
+ /*
+ * Now we need to recursively match the name against the real
+ * directory structure.
+ */
+
+ /*
+ * Match each part of the path name separately, trying the names
+ * as is first, then trying to scan the directory for matching names.
+ */
+
+ for (; start ; start = (end?end+1:(char *)NULL)) {
+ /*
+ * Pinpoint the end of this section of the filename.
+ */
+ /* mb safe. '/' can't be in any encoded char. */
+ end = strchr(start, '/');
+
+ /*
+ * Chop the name at this point.
+ */
+ if (end) {
+ *end = 0;
+ }
+
+ if (pp_saved_last_component) {
+ TALLOC_FREE(*pp_saved_last_component);
+ *pp_saved_last_component = talloc_strdup(ctx,
+ end ? end + 1 : start);
+ if (!*pp_saved_last_component) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* The name cannot have a component of "." */
+
+ if (ISDOT(start)) {
+ if (!end) {
+ /* Error code at the end of a pathname. */
+ result = NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ result = determine_path_error(end+1,
+ allow_wcard_last_component);
+ }
+ goto fail;
+ }
+
+ /* The name cannot have a wildcard if it's not
+ the last component. */
+
+ name_has_wildcard = ms_has_wild(start);
+
+ /* Wildcard not valid anywhere. */
+ if (name_has_wildcard && !allow_wcard_last_component) {
+ result = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /* Wildcards never valid within a pathname. */
+ if (name_has_wildcard && end) {
+ result = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /*
+ * Check if the name exists up to this point.
+ */
+
+ if (SMB_VFS_STAT(conn,name, &st) == 0) {
+ /*
+ * It exists. it must either be a directory or this must
+ * be the last part of the path for it to be OK.
+ */
+ if (end && !(st.st_mode & S_IFDIR)) {
+ /*
+ * An intermediate part of the name isn't
+ * a directory.
+ */
+ DEBUG(5,("Not a dir %s\n",start));
+ *end = '/';
+ /*
+ * We need to return the fact that the
+ * intermediate name resolution failed. This
+ * is used to return an error of ERRbadpath
+ * rather than ERRbadfile. Some Windows
+ * applications depend on the difference between
+ * these two errors.
+ */
+ result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+
+ if (!end) {
+ /*
+ * We just scanned for, and found the end of
+ * the path. We must return the valid stat
+ * struct. JRA.
+ */
+
+ *pst = st;
+ }
+
+ } else {
+ char *found_name = NULL;
+
+ /* Stat failed - ensure we don't use it. */
+ SET_STAT_INVALID(st);
+
+ /*
+ * Reset errno so we can detect
+ * directory open errors.
+ */
+ errno = 0;
+
+ /*
+ * Try to find this part of the path in the directory.
+ */
+
+ if (name_has_wildcard ||
+ !scan_directory(conn, dirpath,
+ start, &found_name)) {
+ char *unmangled;
+
+ if (end) {
+ /*
+ * An intermediate part of the name
+ * can't be found.
+ */
+ DEBUG(5,("Intermediate not found %s\n",
+ start));
+ *end = '/';
+
+ /*
+ * We need to return the fact that the
+ * intermediate name resolution failed.
+ * This is used to return an error of
+ * ERRbadpath rather than ERRbadfile.
+ * Some Windows applications depend on
+ * the difference between these two
+ * errors.
+ */
+
+ /*
+ * ENOENT, ENOTDIR and ELOOP all map
+ * to NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * in the filename walk.
+ */
+
+ if (errno == ENOENT ||
+ errno == ENOTDIR ||
+ errno == ELOOP) {
+ result =
+ NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ else {
+ result =
+ map_nt_error_from_unix(errno);
+ }
+ goto fail;
+ }
+
+ /* ENOENT is the only valid error here. */
+ if ((errno != 0) && (errno != ENOENT)) {
+ /*
+ * ENOTDIR and ELOOP both map to
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * in the filename walk.
+ */
+ if (errno == ENOTDIR ||
+ errno == ELOOP) {
+ result =
+ NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ else {
+ result =
+ map_nt_error_from_unix(errno);
+ }
+ goto fail;
+ }
+
+ /*
+ * Just the last part of the name doesn't exist.
+ * We need to strupper() or strlower() it as
+ * this conversion may be used for file creation
+ * purposes. Fix inspired by
+ * Thomas Neumann <t.neumann@iku-ag.de>.
+ */
+ if (!conn->case_preserve ||
+ (mangle_is_8_3(start, False,
+ conn->params) &&
+ !conn->short_case_preserve)) {
+ strnorm(start,
+ lp_defaultcase(SNUM(conn)));
+ }
+
+ /*
+ * check on the mangled stack to see if we can
+ * recover the base of the filename.
+ */
+
+ if (mangle_is_mangled(start, conn->params)
+ && mangle_lookup_name_from_8_3(ctx,
+ start,
+ &unmangled,
+ conn->params)) {
+ char *tmp;
+ size_t start_ofs = start - name;
+
+ if (*dirpath != '\0') {
+ tmp = talloc_asprintf(ctx,
+ "%s/%s", dirpath,
+ unmangled);
+ TALLOC_FREE(unmangled);
+ }
+ else {
+ tmp = unmangled;
+ }
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(name);
+ name = tmp;
+ start = name + start_ofs;
+ end = start + strlen(start);
+ }
+
+ DEBUG(5,("New file %s\n",start));
+ goto done;
+ }
+
+
+ /*
+ * Restore the rest of the string. If the string was
+ * mangled the size may have changed.
+ */
+ if (end) {
+ char *tmp;
+ size_t start_ofs = start - name;
+
+ if (*dirpath != '\0') {
+ tmp = talloc_asprintf(ctx,
+ "%s/%s/%s", dirpath,
+ found_name, end+1);
+ }
+ else {
+ tmp = talloc_asprintf(ctx,
+ "%s/%s", found_name,
+ end+1);
+ }
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(name);
+ name = tmp;
+ start = name + start_ofs;
+ end = start + strlen(found_name);
+ *end = '\0';
+ } else {
+ char *tmp;
+ size_t start_ofs = start - name;
+
+ if (*dirpath != '\0') {
+ tmp = talloc_asprintf(ctx,
+ "%s/%s", dirpath,
+ found_name);
+ } else {
+ tmp = talloc_strdup(ctx,
+ found_name);
+ }
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(name);
+ name = tmp;
+ start = name + start_ofs;
+
+ /*
+ * We just scanned for, and found the end of
+ * the path. We must return a valid stat struct
+ * if it exists. JRA.
+ */
+
+ if (SMB_VFS_STAT(conn,name, &st) == 0) {
+ *pst = st;
+ } else {
+ SET_STAT_INVALID(st);
+ }
+ }
+
+ TALLOC_FREE(found_name);
+ } /* end else */
+
+#ifdef DEVELOPER
+ /*
+ * This sucks!
+ * We should never provide different behaviors
+ * depending on DEVELOPER!!!
+ */
+ if (VALID_STAT(st)) {
+ bool delete_pending;
+ get_file_infos(vfs_file_id_from_sbuf(conn, &st),
+ &delete_pending, NULL);
+ if (delete_pending) {
+ result = NT_STATUS_DELETE_PENDING;
+ goto fail;
+ }
+ }
+#endif
+
+ /*
+ * Add to the dirpath that we have resolved so far.
+ */
+
+ if (*dirpath != '\0') {
+ char *tmp = talloc_asprintf(ctx,
+ "%s/%s", dirpath, start);
+ if (!tmp) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(dirpath);
+ dirpath = tmp;
+ }
+ else {
+ TALLOC_FREE(dirpath);
+ if (!(dirpath = talloc_strdup(ctx,start))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /*
+ * Don't cache a name with mangled or wildcard components
+ * as this can change the size.
+ */
+
+ if(!component_was_mangled && !name_has_wildcard) {
+ stat_cache_add(orig_path, dirpath,
+ conn->case_sensitive);
+ }
+
+ /*
+ * Restore the / that we wiped out earlier.
+ */
+ if (end) {
+ *end = '/';
+ }
+ }
+
+ /*
+ * Don't cache a name with mangled or wildcard components
+ * as this can change the size.
+ */
+
+ if(!component_was_mangled && !name_has_wildcard) {
+ stat_cache_add(orig_path, name, conn->case_sensitive);
+ }
+
+ /*
+ * The name has been resolved.
+ */
+
+ DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+
+ done:
+ if (stream != NULL) {
+ char *tmp = NULL;
+
+ result = build_stream_path(ctx, conn, orig_path, name, stream,
+ pst, &tmp);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto fail;
+ }
+
+ DEBUG(10, ("build_stream_path returned %s\n", tmp));
+
+ TALLOC_FREE(name);
+ name = tmp;
+ }
+ *pp_conv_path = name;
+ TALLOC_FREE(dirpath);
+ return NT_STATUS_OK;
+ fail:
+ DEBUG(10, ("dirpath = [%s] start = [%s]\n", dirpath, start));
+ if (*dirpath != '\0') {
+ *pp_conv_path = talloc_asprintf(ctx,
+ "%s/%s", dirpath, start);
+ } else {
+ *pp_conv_path = talloc_strdup(ctx, start);
+ }
+ if (!*pp_conv_path) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(name);
+ TALLOC_FREE(dirpath);
+ return result;
+}
+
+/****************************************************************************
+ Check a filename - possibly calling check_reduced_name.
+ This is called by every routine before it allows an operation on a filename.
+ It does any final confirmation necessary to ensure that the filename is
+ a valid one for the user to access.
+****************************************************************************/
+
+NTSTATUS check_name(connection_struct *conn, const char *name)
+{
+ if (IS_VETO_PATH(conn, name)) {
+ /* Is it not dot or dot dot. */
+ if (!((name[0] == '.') && (!name[1] ||
+ (name[1] == '.' && !name[2])))) {
+ DEBUG(5,("check_name: file path name %s vetoed\n",
+ name));
+ return map_nt_error_from_unix(ENOENT);
+ }
+ }
+
+ if (!lp_widelinks(SNUM(conn)) || !lp_symlinks(SNUM(conn))) {
+ NTSTATUS status = check_reduced_name(conn,name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("check_name: name %s failed with %s\n",name,
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if two filenames are equal.
+ This needs to be careful about whether we are case sensitive.
+****************************************************************************/
+
+static bool fname_equal(const char *name1, const char *name2,
+ bool case_sensitive)
+{
+ /* Normal filename handling */
+ if (case_sensitive) {
+ return(strcmp(name1,name2) == 0);
+ }
+
+ return(strequal(name1,name2));
+}
+
+/****************************************************************************
+ Scan a directory to find a filename, matching without case sensitivity.
+ If the name looks like a mangled name then try via the mangling functions
+****************************************************************************/
+
+static bool scan_directory(connection_struct *conn, const char *path,
+ char *name, char **found_name)
+{
+ struct smb_Dir *cur_dir;
+ const char *dname;
+ bool mangled;
+ char *unmangled_name = NULL;
+ long curpos;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ mangled = mangle_is_mangled(name, conn->params);
+
+ /* handle null paths */
+ if ((path == NULL) || (*path == 0)) {
+ path = ".";
+ }
+
+ /* If we have a case-sensitive filesystem, it doesn't do us any
+ * good to search for a name. If a case variation of the name was
+ * there, then the original stat(2) would have found it.
+ */
+ if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
+ errno = ENOENT;
+ return False;
+ }
+
+ /*
+ * The incoming name can be mangled, and if we de-mangle it
+ * here it will not compare correctly against the filename (name2)
+ * read from the directory and then mangled by the name_to_8_3()
+ * call. We need to mangle both names or neither.
+ * (JRA).
+ *
+ * Fix for bug found by Dina Fine. If in case sensitive mode then
+ * the mangle cache is no good (3 letter extension could be wrong
+ * case - so don't demangle in this case - leave as mangled and
+ * allow the mangling of the directory entry read (which is done
+ * case insensitively) to match instead. This will lead to more
+ * false positive matches but we fail completely without it. JRA.
+ */
+
+ if (mangled && !conn->case_sensitive) {
+ mangled = !mangle_lookup_name_from_8_3(ctx,
+ name,
+ &unmangled_name,
+ conn->params);
+ if (!mangled) {
+ /* Name is now unmangled. */
+ name = unmangled_name;
+ }
+ }
+
+ /* open the directory */
+ if (!(cur_dir = OpenDir(talloc_tos(), conn, path, NULL, 0))) {
+ DEBUG(3,("scan dir didn't open dir [%s]\n",path));
+ TALLOC_FREE(unmangled_name);
+ return(False);
+ }
+
+ /* now scan for matching names */
+ curpos = 0;
+ while ((dname = ReadDirName(cur_dir, &curpos))) {
+
+ /* Is it dot or dot dot. */
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ /*
+ * At this point dname is the unmangled name.
+ * name is either mangled or not, depending on the state
+ * of the "mangled" variable. JRA.
+ */
+
+ /*
+ * Check mangled name against mangled name, or unmangled name
+ * against unmangled name.
+ */
+
+ if ((mangled && mangled_equal(name,dname,conn->params)) ||
+ fname_equal(name, dname, conn->case_sensitive)) {
+ /* we've found the file, change it's name and return */
+ *found_name = talloc_strdup(ctx,dname);
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ if (!*found_name) {
+ errno = ENOMEM;
+ return False;
+ }
+ return(True);
+ }
+ }
+
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ errno = ENOENT;
+ return False;
+}
+
+static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *orig_path,
+ const char *basepath,
+ const char *streamname,
+ SMB_STRUCT_STAT *pst,
+ char **path)
+{
+ SMB_STRUCT_STAT st;
+ char *result = NULL;
+ NTSTATUS status;
+ unsigned int i, num_streams;
+ struct stream_struct *streams = NULL;
+
+ result = talloc_asprintf(mem_ctx, "%s%s", basepath, streamname);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (SMB_VFS_STAT(conn, result, &st) == 0) {
+ *pst = st;
+ *path = result;
+ return NT_STATUS_OK;
+ }
+
+ if (errno != ENOENT) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10, ("vfs_stat failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ status = SMB_VFS_STREAMINFO(conn, NULL, basepath, mem_ctx,
+ &num_streams, &streams);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ SET_STAT_INVALID(*pst);
+ *path = result;
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("vfs_streaminfo failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ DEBUG(10, ("comparing [%s] and [%s]: ",
+ streamname, streams[i].name));
+ if (fname_equal(streamname, streams[i].name,
+ conn->case_sensitive)) {
+ DEBUGADD(10, ("equal\n"));
+ break;
+ }
+ DEBUGADD(10, ("not equal\n"));
+ }
+
+ if (i == num_streams) {
+ SET_STAT_INVALID(*pst);
+ *path = result;
+ TALLOC_FREE(streams);
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(result);
+
+ result = talloc_asprintf(mem_ctx, "%s%s", basepath, streams[i].name);
+ if (result == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ SET_STAT_INVALID(*pst);
+
+ if (SMB_VFS_STAT(conn, result, pst) == 0) {
+ stat_cache_add(orig_path, result, conn->case_sensitive);
+ }
+
+ *path = result;
+ TALLOC_FREE(streams);
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(result);
+ TALLOC_FREE(streams);
+ return status;
+}
diff --git a/source3/smbd/files.c b/source3/smbd/files.c
new file mode 100644
index 0000000000..17c473f028
--- /dev/null
+++ b/source3/smbd/files.c
@@ -0,0 +1,547 @@
+/*
+ Unix SMB/CIFS implementation.
+ Files[] structure handling
+ Copyright (C) Andrew Tridgell 1998
+
+ 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"
+
+static int real_max_open_files;
+
+#define VALID_FNUM(fnum) (((fnum) >= 0) && ((fnum) < real_max_open_files))
+
+#define FILE_HANDLE_OFFSET 0x1000
+
+static struct bitmap *file_bmap;
+
+static files_struct *Files;
+
+/* a fsp to use when chaining */
+static files_struct *chain_fsp = NULL;
+
+static int files_used;
+
+/* A singleton cache to speed up searching by dev/inode. */
+static struct fsp_singleton_cache {
+ files_struct *fsp;
+ struct file_id id;
+} fsp_fi_cache;
+
+/****************************************************************************
+ Return a unique number identifying this fsp over the life of this pid.
+****************************************************************************/
+
+static unsigned long get_gen_count(void)
+{
+ static unsigned long file_gen_counter;
+
+ if ((++file_gen_counter) == 0)
+ return ++file_gen_counter;
+ return file_gen_counter;
+}
+
+/****************************************************************************
+ Find first available file slot.
+****************************************************************************/
+
+NTSTATUS file_new(connection_struct *conn, files_struct **result)
+{
+ int i;
+ static int first_file;
+ files_struct *fsp;
+
+ /* we want to give out file handles differently on each new
+ connection because of a common bug in MS clients where they try to
+ reuse a file descriptor from an earlier smb connection. This code
+ increases the chance that the errant client will get an error rather
+ than causing corruption */
+ if (first_file == 0) {
+ first_file = (sys_getpid() ^ (int)time(NULL)) % real_max_open_files;
+ }
+
+ /* TODO: Port the id-tree implementation from Samba4 */
+
+ i = bitmap_find(file_bmap, first_file);
+ if (i == -1) {
+ DEBUG(0,("ERROR! Out of file structures\n"));
+ /* TODO: We have to unconditionally return a DOS error here,
+ * W2k3 even returns ERRDOS/ERRnofids for ntcreate&x with
+ * NTSTATUS negotiated */
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+
+ fsp = SMB_MALLOC_P(files_struct);
+ if (!fsp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(fsp);
+
+ fsp->fh = SMB_MALLOC_P(struct fd_handle);
+ if (!fsp->fh) {
+ SAFE_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(fsp->fh);
+
+ fsp->fh->ref_count = 1;
+ fsp->fh->fd = -1;
+
+ fsp->conn = conn;
+ fsp->fh->gen_id = get_gen_count();
+ GetTimeOfDay(&fsp->open_time);
+
+ first_file = (i+1) % real_max_open_files;
+
+ bitmap_set(file_bmap, i);
+ files_used++;
+
+ fsp->fnum = i + FILE_HANDLE_OFFSET;
+ SMB_ASSERT(fsp->fnum < 65536);
+
+ string_set(&fsp->fsp_name,"");
+
+ DLIST_ADD(Files, fsp);
+
+ DEBUG(5,("allocated file structure %d, fnum = %d (%d used)\n",
+ i, fsp->fnum, files_used));
+
+ chain_fsp = fsp;
+
+ /* A new fsp invalidates a negative fsp_fi_cache. */
+ if (fsp_fi_cache.fsp == NULL) {
+ ZERO_STRUCT(fsp_fi_cache);
+ }
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Close all open files for a connection.
+****************************************************************************/
+
+void file_close_conn(connection_struct *conn)
+{
+ files_struct *fsp, *next;
+
+ for (fsp=Files;fsp;fsp=next) {
+ next = fsp->next;
+ if (fsp->conn == conn) {
+ close_file(fsp,SHUTDOWN_CLOSE);
+ }
+ }
+}
+
+/****************************************************************************
+ Close all open files for a pid and a vuid.
+****************************************************************************/
+
+void file_close_pid(uint16 smbpid, int vuid)
+{
+ files_struct *fsp, *next;
+
+ for (fsp=Files;fsp;fsp=next) {
+ next = fsp->next;
+ if ((fsp->file_pid == smbpid) && (fsp->vuid == vuid)) {
+ close_file(fsp,SHUTDOWN_CLOSE);
+ }
+ }
+}
+
+/****************************************************************************
+ Initialise file structures.
+****************************************************************************/
+
+#define MAX_OPEN_FUDGEFACTOR 20
+
+void file_init(void)
+{
+ int request_max_open_files = lp_max_open_files();
+ int real_lim;
+
+ /*
+ * Set the max_open files to be the requested
+ * max plus a fudgefactor to allow for the extra
+ * fd's we need such as log files etc...
+ */
+ real_lim = set_maxfiles(request_max_open_files + MAX_OPEN_FUDGEFACTOR);
+
+ real_max_open_files = real_lim - MAX_OPEN_FUDGEFACTOR;
+
+ if (real_max_open_files + FILE_HANDLE_OFFSET + MAX_OPEN_PIPES > 65536)
+ real_max_open_files = 65536 - FILE_HANDLE_OFFSET - MAX_OPEN_PIPES;
+
+ if(real_max_open_files != request_max_open_files) {
+ DEBUG(1,("file_init: Information only: requested %d \
+open files, %d are available.\n", request_max_open_files, real_max_open_files));
+ }
+
+ SMB_ASSERT(real_max_open_files > 100);
+
+ file_bmap = bitmap_allocate(real_max_open_files);
+
+ if (!file_bmap) {
+ exit_server("out of memory in file_init");
+ }
+
+ /*
+ * Ensure that pipe_handle_oppset is set correctly.
+ */
+ set_pipe_handle_offset(real_max_open_files);
+}
+
+/****************************************************************************
+ Close files open by a specified vuid.
+****************************************************************************/
+
+void file_close_user(int vuid)
+{
+ files_struct *fsp, *next;
+
+ for (fsp=Files;fsp;fsp=next) {
+ next=fsp->next;
+ if (fsp->vuid == vuid) {
+ close_file(fsp,SHUTDOWN_CLOSE);
+ }
+ }
+}
+
+/****************************************************************************
+ Debug to enumerate all open files in the smbd.
+****************************************************************************/
+
+void file_dump_open_table(void)
+{
+ int count=0;
+ files_struct *fsp;
+
+ for (fsp=Files;fsp;fsp=fsp->next,count++) {
+ DEBUG(10,("Files[%d], fnum = %d, name %s, fd = %d, gen = %lu, fileid=%s\n",
+ count, fsp->fnum, fsp->fsp_name, fsp->fh->fd, (unsigned long)fsp->fh->gen_id,
+ file_id_string_tos(&fsp->file_id)));
+ }
+}
+
+/****************************************************************************
+ Find a fsp given a file descriptor.
+****************************************************************************/
+
+files_struct *file_find_fd(int fd)
+{
+ int count=0;
+ files_struct *fsp;
+
+ for (fsp=Files;fsp;fsp=fsp->next,count++) {
+ if (fsp->fh->fd == fd) {
+ if (count > 10) {
+ DLIST_PROMOTE(Files, fsp);
+ }
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp given a device, inode and file_id.
+****************************************************************************/
+
+files_struct *file_find_dif(struct file_id id, unsigned long gen_id)
+{
+ int count=0;
+ files_struct *fsp;
+
+ for (fsp=Files;fsp;fsp=fsp->next,count++) {
+ /* We can have a fsp->fh->fd == -1 here as it could be a stat open. */
+ if (file_id_equal(&fsp->file_id, &id) &&
+ fsp->fh->gen_id == gen_id ) {
+ if (count > 10) {
+ DLIST_PROMOTE(Files, fsp);
+ }
+ /* Paranoia check. */
+ if ((fsp->fh->fd == -1) &&
+ (fsp->oplock_type != NO_OPLOCK) &&
+ (fsp->oplock_type != FAKE_LEVEL_II_OPLOCK)) {
+ DEBUG(0,("file_find_dif: file %s file_id = %s, gen = %u \
+oplock_type = %u is a stat open with oplock type !\n", fsp->fsp_name,
+ file_id_string_tos(&fsp->file_id),
+ (unsigned int)fsp->fh->gen_id,
+ (unsigned int)fsp->oplock_type ));
+ smb_panic("file_find_dif");
+ }
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Check if an fsp still exists.
+****************************************************************************/
+
+files_struct *file_find_fsp(files_struct *orig_fsp)
+{
+ files_struct *fsp;
+
+ for (fsp=Files;fsp;fsp=fsp->next) {
+ if (fsp == orig_fsp)
+ return fsp;
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find the first fsp given a device and inode.
+ We use a singleton cache here to speed up searching from getfilepathinfo
+ calls.
+****************************************************************************/
+
+files_struct *file_find_di_first(struct file_id id)
+{
+ files_struct *fsp;
+
+ if (file_id_equal(&fsp_fi_cache.id, &id)) {
+ /* Positive or negative cache hit. */
+ return fsp_fi_cache.fsp;
+ }
+
+ fsp_fi_cache.id = id;
+
+ for (fsp=Files;fsp;fsp=fsp->next) {
+ if ( fsp->fh->fd != -1 &&
+ file_id_equal(&fsp->file_id, &id)) {
+ /* Setup positive cache. */
+ fsp_fi_cache.fsp = fsp;
+ return fsp;
+ }
+ }
+
+ /* Setup negative cache. */
+ fsp_fi_cache.fsp = NULL;
+ return NULL;
+}
+
+/****************************************************************************
+ Find the next fsp having the same device and inode.
+****************************************************************************/
+
+files_struct *file_find_di_next(files_struct *start_fsp)
+{
+ files_struct *fsp;
+
+ for (fsp = start_fsp->next;fsp;fsp=fsp->next) {
+ if ( fsp->fh->fd != -1 &&
+ file_id_equal(&fsp->file_id, &start_fsp->file_id)) {
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp that is open for printing.
+****************************************************************************/
+
+files_struct *file_find_print(void)
+{
+ files_struct *fsp;
+
+ for (fsp=Files;fsp;fsp=fsp->next) {
+ if (fsp->print_file) {
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Sync open files on a connection.
+****************************************************************************/
+
+void file_sync_all(connection_struct *conn)
+{
+ files_struct *fsp, *next;
+
+ for (fsp=Files;fsp;fsp=next) {
+ next=fsp->next;
+ if ((conn == fsp->conn) && (fsp->fh->fd != -1)) {
+ sync_file(conn, fsp, True /* write through */);
+ }
+ }
+}
+
+/****************************************************************************
+ Free up a fsp.
+****************************************************************************/
+
+void file_free(files_struct *fsp)
+{
+ DLIST_REMOVE(Files, fsp);
+
+ string_free(&fsp->fsp_name);
+
+ if (fsp->fake_file_handle) {
+ destroy_fake_file_handle(&fsp->fake_file_handle);
+ }
+
+ if (fsp->fh->ref_count == 1) {
+ SAFE_FREE(fsp->fh);
+ } else {
+ fsp->fh->ref_count--;
+ }
+
+ if (fsp->notify) {
+ notify_remove(fsp->conn->notify_ctx, fsp);
+ TALLOC_FREE(fsp->notify);
+ }
+
+ /* Ensure this event will never fire. */
+ TALLOC_FREE(fsp->oplock_timeout);
+
+ /* Ensure this event will never fire. */
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ bitmap_clear(file_bmap, fsp->fnum - FILE_HANDLE_OFFSET);
+ files_used--;
+
+ DEBUG(5,("freed files structure %d (%d used)\n",
+ fsp->fnum, files_used));
+
+ /* this is paranoia, just in case someone tries to reuse the
+ information */
+ ZERO_STRUCTP(fsp);
+
+ if (fsp == chain_fsp) {
+ chain_fsp = NULL;
+ }
+
+ /* Closing a file can invalidate the positive cache. */
+ if (fsp == fsp_fi_cache.fsp) {
+ ZERO_STRUCT(fsp_fi_cache);
+ }
+
+ /* Drop all remaining extensions. */
+ while (fsp->vfs_extension) {
+ vfs_remove_fsp_extension(fsp->vfs_extension->owner, fsp);
+ }
+
+ SAFE_FREE(fsp);
+}
+
+/****************************************************************************
+ Get an fsp from a 16 bit fnum.
+****************************************************************************/
+
+files_struct *file_fnum(uint16 fnum)
+{
+ files_struct *fsp;
+ int count=0;
+
+ for (fsp=Files;fsp;fsp=fsp->next, count++) {
+ if (fsp->fnum == fnum) {
+ if (count > 10) {
+ DLIST_PROMOTE(Files, fsp);
+ }
+ return fsp;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Get an fsp from a packet given the offset of a 16 bit fnum.
+****************************************************************************/
+
+files_struct *file_fsp(uint16 fid)
+{
+ files_struct *fsp;
+
+ if (chain_fsp) {
+ return chain_fsp;
+ }
+
+ fsp = file_fnum(fid);
+ if (fsp) {
+ chain_fsp = fsp;
+ }
+ return fsp;
+}
+
+/****************************************************************************
+ Reset the chained fsp - done at the start of a packet reply.
+****************************************************************************/
+
+void file_chain_reset(void)
+{
+ chain_fsp = NULL;
+}
+
+/****************************************************************************
+ Duplicate the file handle part for a DOS or FCB open.
+****************************************************************************/
+
+NTSTATUS dup_file_fsp(files_struct *fsp,
+ uint32 access_mask,
+ uint32 share_access,
+ uint32 create_options,
+ files_struct **result)
+{
+ NTSTATUS status;
+ files_struct *dup_fsp;
+
+ status = file_new(fsp->conn, &dup_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ SAFE_FREE(dup_fsp->fh);
+
+ dup_fsp->fh = fsp->fh;
+ dup_fsp->fh->ref_count++;
+
+ dup_fsp->file_id = fsp->file_id;
+ dup_fsp->initial_allocation_size = fsp->initial_allocation_size;
+ dup_fsp->mode = fsp->mode;
+ dup_fsp->file_pid = fsp->file_pid;
+ dup_fsp->vuid = fsp->vuid;
+ dup_fsp->open_time = fsp->open_time;
+ dup_fsp->access_mask = access_mask;
+ dup_fsp->share_access = share_access;
+ dup_fsp->oplock_type = fsp->oplock_type;
+ dup_fsp->can_lock = fsp->can_lock;
+ dup_fsp->can_read = (access_mask & (FILE_READ_DATA)) ? True : False;
+ if (!CAN_WRITE(fsp->conn)) {
+ dup_fsp->can_write = False;
+ } else {
+ dup_fsp->can_write = (access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) ? True : False;
+ }
+ dup_fsp->print_file = fsp->print_file;
+ dup_fsp->modified = fsp->modified;
+ dup_fsp->is_directory = fsp->is_directory;
+ dup_fsp->aio_write_behind = fsp->aio_write_behind;
+ string_set(&dup_fsp->fsp_name,fsp->fsp_name);
+
+ *result = dup_fsp;
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/ipc.c b/source3/smbd/ipc.c
new file mode 100644
index 0000000000..f4c45999ba
--- /dev/null
+++ b/source3/smbd/ipc.c
@@ -0,0 +1,813 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+
+extern int max_send;
+
+#define NERR_notsupported 50
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req);
+
+/*******************************************************************
+ copies parameters and data, as needed, into the smb buffer
+
+ *both* the data and params sections should be aligned. this
+ is fudged in the rpc pipes by
+ at present, only the data section is. this may be a possible
+ cause of some of the ipc problems being experienced. lkcl26dec97
+
+ ******************************************************************/
+
+static void copy_trans_params_and_data(char *outbuf, int align,
+ char *rparam, int param_offset, int param_len,
+ char *rdata, int data_offset, int data_len)
+{
+ char *copy_into = smb_buf(outbuf);
+
+ if(param_len < 0)
+ param_len = 0;
+
+ if(data_len < 0)
+ data_len = 0;
+
+ DEBUG(5,("copy_trans_params_and_data: params[%d..%d] data[%d..%d] (align %d)\n",
+ param_offset, param_offset + param_len,
+ data_offset , data_offset + data_len,
+ align));
+
+ *copy_into = '\0';
+
+ copy_into += 1;
+
+ if (param_len)
+ memcpy(copy_into, &rparam[param_offset], param_len);
+
+ copy_into += param_len;
+ if (align) {
+ memset(copy_into, '\0', align);
+ }
+
+ copy_into += align;
+
+ if (data_len )
+ memcpy(copy_into, &rdata[data_offset], data_len);
+}
+
+/****************************************************************************
+ Send a trans reply.
+ ****************************************************************************/
+
+void send_trans_reply(connection_struct *conn, const uint8_t *inbuf,
+ char *rparam, int rparam_len,
+ char *rdata, int rdata_len,
+ bool buffer_too_large)
+{
+ int this_ldata,this_lparam;
+ int tot_data_sent = 0;
+ int tot_param_sent = 0;
+ int align;
+ char *outbuf;
+
+ int ldata = rdata ? rdata_len : 0;
+ int lparam = rparam ? rparam_len : 0;
+
+ if (buffer_too_large)
+ DEBUG(5,("send_trans_reply: buffer %d too large\n", ldata ));
+
+ this_lparam = MIN(lparam,max_send - 500); /* hack */
+ this_ldata = MIN(ldata,max_send - (500+this_lparam));
+
+ align = ((this_lparam)%4);
+
+ if (!create_outbuf(talloc_tos(), (char *)inbuf, &outbuf,
+ 10, 1+align+this_ldata+this_lparam)) {
+ smb_panic("could not allocate outbuf");
+ }
+
+ copy_trans_params_and_data(outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(outbuf,smb_vwv0,lparam);
+ SSVAL(outbuf,smb_vwv1,ldata);
+ SSVAL(outbuf,smb_vwv3,this_lparam);
+ SSVAL(outbuf,smb_vwv4,smb_offset(smb_buf(outbuf)+1,outbuf));
+ SSVAL(outbuf,smb_vwv5,0);
+ SSVAL(outbuf,smb_vwv6,this_ldata);
+ SSVAL(outbuf,smb_vwv7,smb_offset(smb_buf(outbuf)+1+this_lparam+align,
+ outbuf));
+ SSVAL(outbuf,smb_vwv8,0);
+ SSVAL(outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set((char *)outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW, __LINE__, __FILE__);
+ }
+
+ show_msg(outbuf);
+ if (!srv_send_smb(smbd_server_fd(), (char *)outbuf,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans_reply: srv_send_smb failed.");
+ }
+
+ TALLOC_FREE(outbuf);
+
+ tot_data_sent = this_ldata;
+ tot_param_sent = this_lparam;
+
+ while (tot_data_sent < ldata || tot_param_sent < lparam)
+ {
+ this_lparam = MIN(lparam-tot_param_sent,
+ max_send - 500); /* hack */
+ this_ldata = MIN(ldata -tot_data_sent,
+ max_send - (500+this_lparam));
+
+ if(this_lparam < 0)
+ this_lparam = 0;
+
+ if(this_ldata < 0)
+ this_ldata = 0;
+
+ align = (this_lparam%4);
+
+ if (!create_outbuf(talloc_tos(), (char *)inbuf, &outbuf,
+ 10, 1+align+this_ldata+this_lparam)) {
+ smb_panic("could not allocate outbuf");
+ }
+
+ copy_trans_params_and_data(outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(outbuf,smb_vwv3,this_lparam);
+ SSVAL(outbuf,smb_vwv4,smb_offset(smb_buf(outbuf)+1,outbuf));
+ SSVAL(outbuf,smb_vwv5,tot_param_sent);
+ SSVAL(outbuf,smb_vwv6,this_ldata);
+ SSVAL(outbuf,smb_vwv7,
+ smb_offset(smb_buf(outbuf)+1+this_lparam+align, outbuf));
+ SSVAL(outbuf,smb_vwv8,tot_data_sent);
+ SSVAL(outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set(outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__, __FILE__);
+ }
+
+ show_msg(outbuf);
+ if (!srv_send_smb(smbd_server_fd(), outbuf,
+ IS_CONN_ENCRYPTED(conn)))
+ exit_server_cleanly("send_trans_reply: srv_send_smb "
+ "failed.");
+
+ tot_data_sent += this_ldata;
+ tot_param_sent += this_lparam;
+ TALLOC_FREE(outbuf);
+ }
+}
+
+/****************************************************************************
+ Start the first part of an RPC reply which began with an SMBtrans request.
+****************************************************************************/
+
+static void api_rpc_trans_reply(connection_struct *conn, struct smb_request *req, smb_np_struct *p)
+{
+ bool is_data_outstanding;
+ char *rdata = (char *)SMB_MALLOC(p->max_trans_reply);
+ int data_len;
+
+ if(rdata == NULL) {
+ DEBUG(0,("api_rpc_trans_reply: malloc fail.\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if((data_len = read_from_pipe( p, rdata, p->max_trans_reply,
+ &is_data_outstanding)) < 0) {
+ SAFE_FREE(rdata);
+ api_no_reply(conn,req);
+ return;
+ }
+
+ send_trans_reply(conn, req->inbuf, NULL, 0, rdata, data_len,
+ is_data_outstanding);
+ SAFE_FREE(rdata);
+ return;
+}
+
+/****************************************************************************
+ WaitNamedPipeHandleState
+****************************************************************************/
+
+static void api_WNPHS(connection_struct *conn, struct smb_request *req, smb_np_struct *p,
+ char *param, int param_len)
+{
+ uint16 priority;
+
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ priority = SVAL(param,0);
+ DEBUG(4,("WaitNamedPipeHandleState priority %x\n", priority));
+
+ if (wait_rpc_pipe_hnd_state(p, priority)) {
+ /* now send the reply */
+ send_trans_reply(conn, req->inbuf, NULL, 0, NULL, 0, False);
+ return;
+ }
+ api_no_reply(conn,req);
+}
+
+
+/****************************************************************************
+ SetNamedPipeHandleState
+****************************************************************************/
+
+static void api_SNPHS(connection_struct *conn, struct smb_request *req, smb_np_struct *p,
+ char *param, int param_len)
+{
+ uint16 id;
+
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ id = SVAL(param,0);
+ DEBUG(4,("SetNamedPipeHandleState to code %x\n", id));
+
+ if (set_rpc_pipe_hnd_state(p, id)) {
+ /* now send the reply */
+ send_trans_reply(conn, req->inbuf, NULL, 0, NULL, 0, False);
+ return;
+ }
+ api_no_reply(conn,req);
+}
+
+
+/****************************************************************************
+ When no reply is generated, indicate unsupported.
+ ****************************************************************************/
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req)
+{
+ char rparam[4];
+
+ /* unsupported */
+ SSVAL(rparam,0,NERR_notsupported);
+ SSVAL(rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API fd command\n"));
+
+ /* now send the reply */
+ send_trans_reply(conn, req->inbuf, rparam, 4, NULL, 0, False);
+
+ return;
+}
+
+/****************************************************************************
+ Handle remote api calls delivered to a named pipe already opened.
+ ****************************************************************************/
+
+static void api_fd_reply(connection_struct *conn, uint16 vuid,
+ struct smb_request *req,
+ uint16 *setup, char *data, char *params,
+ int suwcnt, int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ bool reply = False;
+ smb_np_struct *p = NULL;
+ int pnum;
+ int subcommand;
+
+ DEBUG(5,("api_fd_reply\n"));
+
+ /* First find out the name of this file. */
+ if (suwcnt != 2) {
+ DEBUG(0,("Unexpected named pipe transaction.\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* Get the file handle and hence the file name. */
+ /*
+ * NB. The setup array has already been transformed
+ * via SVAL and so is in gost byte order.
+ */
+ pnum = ((int)setup[1]) & 0xFFFF;
+ subcommand = ((int)setup[0]) & 0xFFFF;
+
+ if(!(p = get_rpc_pipe(pnum))) {
+ if (subcommand == TRANSACT_WAITNAMEDPIPEHANDLESTATE) {
+ /* Win9x does this call with a unicode pipe name, not a pnum. */
+ /* Just return success for now... */
+ DEBUG(3,("Got TRANSACT_WAITNAMEDPIPEHANDLESTATE on text pipe name\n"));
+ send_trans_reply(conn, req->inbuf, NULL, 0, NULL, 0,
+ False);
+ return;
+ }
+
+ DEBUG(1,("api_fd_reply: INVALID PIPE HANDLE: %x\n", pnum));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (vuid != p->vuid) {
+ DEBUG(1, ("Got pipe request (pnum %x) using invalid VUID %d, "
+ "expected %d\n", pnum, vuid, p->vuid));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ DEBUG(3,("Got API command 0x%x on pipe \"%s\" (pnum %x)\n", subcommand, p->name, pnum));
+
+ /* record maximum data length that can be transmitted in an SMBtrans */
+ p->max_trans_reply = mdrcnt;
+
+ DEBUG(10,("api_fd_reply: p:%p max_trans_reply: %d\n", p, p->max_trans_reply));
+
+ switch (subcommand) {
+ case TRANSACT_DCERPCCMD:
+ /* dce/rpc command */
+ reply = write_to_pipe(p, data, tdscnt);
+ if (!reply) {
+ api_no_reply(conn, req);
+ return;
+ }
+ api_rpc_trans_reply(conn, req, p);
+ break;
+ case TRANSACT_WAITNAMEDPIPEHANDLESTATE:
+ /* Wait Named Pipe Handle state */
+ api_WNPHS(conn, req, p, params, tpscnt);
+ break;
+ case TRANSACT_SETNAMEDPIPEHANDLESTATE:
+ /* Set Named Pipe Handle state */
+ api_SNPHS(conn, req, p, params, tpscnt);
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+}
+
+/****************************************************************************
+ Handle named pipe commands.
+****************************************************************************/
+
+static void named_pipe(connection_struct *conn, uint16 vuid,
+ struct smb_request *req,
+ const char *name, uint16 *setup,
+ char *data, char *params,
+ int suwcnt, int tdscnt,int tpscnt,
+ int msrcnt, int mdrcnt, int mprcnt)
+{
+ DEBUG(3,("named pipe command on <%s> name\n", name));
+
+ if (strequal(name,"LANMAN")) {
+ api_reply(conn, vuid, req,
+ data, params,
+ tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strequal(name,"WKSSVC") ||
+ strequal(name,"SRVSVC") ||
+ strequal(name,"WINREG") ||
+ strequal(name,"SAMR") ||
+ strequal(name,"LSARPC")) {
+
+ DEBUG(4,("named pipe command from Win95 (wow!)\n"));
+
+ api_fd_reply(conn, vuid, req,
+ setup, data, params,
+ suwcnt, tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strlen(name) < 1) {
+ api_fd_reply(conn, vuid, req,
+ setup, data,
+ params, suwcnt, tdscnt,
+ tpscnt, mdrcnt, mprcnt);
+ return;
+ }
+
+ if (setup)
+ DEBUG(3,("unknown named pipe: setup 0x%X setup1=%d\n",
+ (int)setup[0],(int)setup[1]));
+
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+}
+
+static void handle_trans(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ char *local_machine_name;
+ int name_offset = 0;
+
+ DEBUG(3,("trans <%s> data=%u params=%u setup=%u\n",
+ state->name,(unsigned int)state->total_data,(unsigned int)state->total_param,
+ (unsigned int)state->setup_count));
+
+ /*
+ * WinCE wierdness....
+ */
+
+ local_machine_name = talloc_asprintf(state, "\\%s\\",
+ get_local_machine_name());
+
+ if (local_machine_name == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if (strnequal(state->name, local_machine_name,
+ strlen(local_machine_name))) {
+ name_offset = strlen(local_machine_name)-1;
+ }
+
+ if (!strnequal(&state->name[name_offset], "\\PIPE",
+ strlen("\\PIPE"))) {
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ name_offset += strlen("\\PIPE");
+
+ /* Win9x weirdness. When talking to a unicode server Win9x
+ only sends \PIPE instead of \PIPE\ */
+
+ if (state->name[name_offset] == '\\')
+ name_offset++;
+
+ DEBUG(5,("calling named_pipe\n"));
+ named_pipe(conn, state->vuid, req,
+ state->name+name_offset,
+ state->setup,state->data,
+ state->param,
+ state->setup_count,state->total_data,
+ state->total_param,
+ state->max_setup_return,
+ state->max_data_return,
+ state->max_param_return);
+
+ if (state->close_on_completion) {
+ close_cnum(conn,state->vuid);
+ req->conn = NULL;
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBtrans.
+ ****************************************************************************/
+
+void reply_trans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ struct trans_state *state;
+ NTSTATUS result;
+ unsigned int size;
+ unsigned int av_size;
+
+ START_PROFILE(SMBtrans);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ size = smb_len(req->inbuf) + 4;
+ av_size = smb_len(req->inbuf);
+ dsoff = SVAL(req->inbuf, smb_dsoff);
+ dscnt = SVAL(req->inbuf, smb_dscnt);
+ psoff = SVAL(req->inbuf, smb_psoff);
+ pscnt = SVAL(req->inbuf, smb_pscnt);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ if ((state = TALLOC_P(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ state->cmd = SMBtrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = CVAL(req->inbuf, smb_suwcnt);
+ state->setup = NULL;
+ state->total_param = SVAL(req->inbuf, smb_tpscnt);
+ state->param = NULL;
+ state->total_data = SVAL(req->inbuf, smb_tdscnt);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->inbuf, smb_mprcnt);
+ state->max_data_return = SVAL(req->inbuf, smb_mdrcnt);
+ state->max_setup_return = CVAL(req->inbuf, smb_msrcnt);
+ state->close_on_completion = BITSETW(req->inbuf+smb_vwv5,0);
+ state->one_way = BITSETW(req->inbuf+smb_vwv5,1);
+
+ srvstr_pull_buf_talloc(state, req->inbuf, req->flags2, &state->name,
+ smb_buf(req->inbuf), STR_TERMINATE);
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param) ||
+ !state->name)
+ goto bad_param;
+
+ if (state->total_data) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many. */
+ state->data = (char *)SMB_MALLOC(state->total_data+100);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->data[state->total_data], 0, 100);
+
+ if (dscnt > state->total_data ||
+ dsoff+dscnt < dsoff) {
+ goto bad_param;
+ }
+
+ if (dsoff > av_size ||
+ dscnt > av_size ||
+ dsoff+dscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many */
+ state->param = (char *)SMB_MALLOC(state->total_param+100);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->param[state->total_param], 0, 100);
+
+ if (pscnt > state->total_param ||
+ psoff+pscnt < psoff) {
+ goto bad_param;
+ }
+
+ if (psoff > av_size ||
+ pscnt > av_size ||
+ psoff+pscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if (state->setup_count) {
+ unsigned int i;
+ if((state->setup = TALLOC_ARRAY(
+ state, uint16, state->setup_count)) == NULL) {
+ DEBUG(0,("reply_trans: setup malloc fail for %u "
+ "bytes !\n", (unsigned int)
+ (state->setup_count * sizeof(uint16))));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ if (req->inbuf+smb_vwv14+(state->setup_count*SIZEOFWORD) >
+ req->inbuf + size)
+ goto bad_param;
+ if ((smb_vwv14+(state->setup_count*SIZEOFWORD) < smb_vwv14) ||
+ (smb_vwv14+(state->setup_count*SIZEOFWORD) <
+ (state->setup_count*SIZEOFWORD)))
+ goto bad_param;
+
+ for (i=0;i<state->setup_count;i++)
+ state->setup[i] = SVAL(req->inbuf,
+ smb_vwv14+i*SIZEOFWORD);
+ }
+
+ state->received_param = pscnt;
+
+ if ((state->received_param != state->total_param) ||
+ (state->received_data != state->total_data)) {
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ handle_trans(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+}
+
+/****************************************************************************
+ Reply to a secondary SMBtrans.
+ ****************************************************************************/
+
+void reply_transs(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+ unsigned int av_size;
+
+ START_PROFILE(SMBtranss);
+
+ show_msg((char *)req->inbuf);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ /* Revise total_params and total_data in case they have changed
+ * downwards */
+
+ if (SVAL(req->inbuf, smb_vwv0) < state->total_param)
+ state->total_param = SVAL(req->inbuf,smb_vwv0);
+ if (SVAL(req->inbuf, smb_vwv1) < state->total_data)
+ state->total_data = SVAL(req->inbuf,smb_vwv1);
+
+ av_size = smb_len(req->inbuf);
+
+ pcnt = SVAL(req->inbuf, smb_spscnt);
+ poff = SVAL(req->inbuf, smb_spsoff);
+ pdisp = SVAL(req->inbuf, smb_spsdisp);
+
+ dcnt = SVAL(req->inbuf, smb_sdscnt);
+ doff = SVAL(req->inbuf, smb_sdsoff);
+ ddisp = SVAL(req->inbuf, smb_sdsdisp);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (pdisp > state->total_param ||
+ pcnt > state->total_param ||
+ pdisp+pcnt > state->total_param ||
+ pdisp+pcnt < pdisp) {
+ goto bad_param;
+ }
+
+ if (poff > av_size ||
+ pcnt > av_size ||
+ poff+pcnt > av_size ||
+ poff+pcnt < poff) {
+ goto bad_param;
+ }
+
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,
+ pcnt);
+ }
+
+ if (dcnt) {
+ if (ddisp > state->total_data ||
+ dcnt > state->total_data ||
+ ddisp+dcnt > state->total_data ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ if (ddisp > av_size ||
+ dcnt > av_size ||
+ ddisp+dcnt > av_size ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,
+ dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ /*
+ * construct_reply_common will copy smb_com from inbuf to
+ * outbuf. SMBtranss is wrong here.
+ */
+ SCVAL(req->inbuf,smb_com,SMBtrans);
+
+ handle_trans(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+}
diff --git a/source3/smbd/lanman.c b/source3/smbd/lanman.c
new file mode 100644
index 0000000000..fe1d766b9d
--- /dev/null
+++ b/source3/smbd/lanman.c
@@ -0,0 +1,4642 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ 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/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+
+#ifdef CHECK_TYPES
+#undef CHECK_TYPES
+#endif
+#define CHECK_TYPES 0
+
+#define NERR_Success 0
+#define NERR_badpass 86
+#define NERR_notsupported 50
+
+#define NERR_BASE (2100)
+#define NERR_BufTooSmall (NERR_BASE+23)
+#define NERR_JobNotFound (NERR_BASE+51)
+#define NERR_DestNotFound (NERR_BASE+52)
+
+#define ACCESS_READ 0x01
+#define ACCESS_WRITE 0x02
+#define ACCESS_CREATE 0x04
+
+#define SHPWLEN 8 /* share password length */
+
+/* Limit size of ipc replies */
+
+static char *smb_realloc_limit(void *ptr, size_t size)
+{
+ char *val;
+
+ size = MAX((size),4*1024);
+ val = (char *)SMB_REALLOC(ptr,size);
+ if (val) {
+ memset(val,'\0',size);
+ }
+ return val;
+}
+
+static bool api_Unsupported(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+static bool api_TooSmall(connection_struct *conn, uint16 vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+
+static int CopyExpanded(connection_struct *conn,
+ int snum, char **dst, char *src, int *p_space_remaining)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ int l;
+
+ if (!src || !dst || !p_space_remaining || !(*dst) ||
+ *p_space_remaining <= 0) {
+ return 0;
+ }
+
+ buf = talloc_strdup(ctx, src);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_string_sub(ctx, buf,"%S",lp_servicename(snum));
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_sub_advanced(ctx,
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ buf);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ l = push_ascii(*dst,buf,*p_space_remaining, STR_TERMINATE);
+ if (l == -1) {
+ return 0;
+ }
+ (*dst) += l;
+ (*p_space_remaining) -= l;
+ return l;
+}
+
+static int CopyAndAdvance(char **dst, char *src, int *n)
+{
+ int l;
+ if (!src || !dst || !n || !(*dst)) {
+ return 0;
+ }
+ l = push_ascii(*dst,src,*n, STR_TERMINATE);
+ if (l == -1) {
+ return 0;
+ }
+ (*dst) += l;
+ (*n) -= l;
+ return l;
+}
+
+static int StrlenExpanded(connection_struct *conn, int snum, char *s)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ if (!s) {
+ return 0;
+ }
+ buf = talloc_strdup(ctx,s);
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_string_sub(ctx,buf,"%S",lp_servicename(snum));
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_sub_advanced(ctx,
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ buf);
+ if (!buf) {
+ return 0;
+ }
+ return strlen(buf) + 1;
+}
+
+static char *Expand(connection_struct *conn, int snum, char *s)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+
+ if (!s) {
+ return NULL;
+ }
+ buf = talloc_strdup(ctx,s);
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_string_sub(ctx,buf,"%S",lp_servicename(snum));
+ if (!buf) {
+ return 0;
+ }
+ return talloc_sub_advanced(ctx,
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ buf);
+}
+
+/*******************************************************************
+ Check a API string for validity when we only need to check the prefix.
+******************************************************************/
+
+static bool prefix_ok(const char *str, const char *prefix)
+{
+ return(strncmp(str,prefix,strlen(prefix)) == 0);
+}
+
+struct pack_desc {
+ const char *format; /* formatstring for structure */
+ const char *subformat; /* subformat for structure */
+ char *base; /* baseaddress of buffer */
+ int buflen; /* remaining size for fixed part; on init: length of base */
+ int subcount; /* count of substructures */
+ char *structbuf; /* pointer into buffer for remaining fixed part */
+ int stringlen; /* remaining size for variable part */
+ char *stringbuf; /* pointer into buffer for remaining variable part */
+ int neededlen; /* total needed size */
+ int usedlen; /* total used size (usedlen <= neededlen and usedlen <= buflen) */
+ const char *curpos; /* current position; pointer into format or subformat */
+ int errcode;
+};
+
+static int get_counter(const char **p)
+{
+ int i, n;
+ if (!p || !(*p)) {
+ return 1;
+ }
+ if (!isdigit((int)**p)) {
+ return 1;
+ }
+ for (n = 0;;) {
+ i = **p;
+ if (isdigit(i)) {
+ n = 10 * n + (i - '0');
+ } else {
+ return n;
+ }
+ (*p)++;
+ }
+}
+
+static int getlen(const char *p)
+{
+ int n = 0;
+ if (!p) {
+ return 0;
+ }
+
+ while (*p) {
+ switch( *p++ ) {
+ case 'W': /* word (2 byte) */
+ n += 2;
+ break;
+ case 'K': /* status word? (2 byte) */
+ n += 2;
+ break;
+ case 'N': /* count of substructures (word) at end */
+ n += 2;
+ break;
+ case 'D': /* double word (4 byte) */
+ case 'z': /* offset to zero terminated string (4 byte) */
+ case 'l': /* offset to user data (4 byte) */
+ n += 4;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ n += 4;
+ get_counter(&p);
+ break;
+ case 'B': /* byte (with optional counter) */
+ n += get_counter(&p);
+ break;
+ }
+ }
+ return n;
+}
+
+static bool init_package(struct pack_desc *p, int count, int subcount)
+{
+ int n = p->buflen;
+ int i;
+
+ if (!p->format || !p->base) {
+ return False;
+ }
+
+ i = count * getlen(p->format);
+ if (p->subformat) {
+ i += subcount * getlen(p->subformat);
+ }
+ p->structbuf = p->base;
+ p->neededlen = 0;
+ p->usedlen = 0;
+ p->subcount = 0;
+ p->curpos = p->format;
+ if (i > n) {
+ p->neededlen = i;
+ i = n = 0;
+#if 0
+ /*
+ * This is the old error code we used. Aparently
+ * WinNT/2k systems return ERRbuftoosmall (2123) and
+ * OS/2 needs this. I'm leaving this here so we can revert
+ * if needed. JRA.
+ */
+ p->errcode = ERRmoredata;
+#else
+ p->errcode = ERRbuftoosmall;
+#endif
+ } else {
+ p->errcode = NERR_Success;
+ }
+ p->buflen = i;
+ n -= i;
+ p->stringbuf = p->base + i;
+ p->stringlen = n;
+ return (p->errcode == NERR_Success);
+}
+
+static int package(struct pack_desc *p, ...)
+{
+ va_list args;
+ int needed=0, stringneeded;
+ const char *str=NULL;
+ int is_string=0, stringused;
+ int32 temp;
+
+ va_start(args,p);
+
+ if (!*p->curpos) {
+ if (!p->subcount) {
+ p->curpos = p->format;
+ } else {
+ p->curpos = p->subformat;
+ p->subcount--;
+ }
+ }
+#if CHECK_TYPES
+ str = va_arg(args,char*);
+ SMB_ASSERT(strncmp(str,p->curpos,strlen(str)) == 0);
+#endif
+ stringneeded = -1;
+
+ if (!p->curpos) {
+ va_end(args);
+ return 0;
+ }
+
+ switch( *p->curpos++ ) {
+ case 'W': /* word (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'K': /* status word? (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'N': /* count of substructures (word) at end */
+ needed = 2;
+ p->subcount = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,p->subcount);
+ }
+ break;
+ case 'D': /* double word (4 byte) */
+ needed = 4;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SIVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'B': /* byte (with optional counter) */
+ needed = get_counter(&p->curpos);
+ {
+ char *s = va_arg(args,char*);
+ if (p->buflen >= needed) {
+ StrnCpy(p->structbuf,s?s:"",needed-1);
+ }
+ }
+ break;
+ case 'z': /* offset to zero terminated string (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = (str ? strlen(str)+1 : 0);
+ is_string = 1;
+ break;
+ case 'l': /* offset to user data (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = va_arg(args,int);
+ is_string = 0;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = get_counter(&p->curpos);
+ is_string = 0;
+ break;
+ }
+
+ va_end(args);
+ if (stringneeded >= 0) {
+ needed = 4;
+ if (p->buflen >= needed) {
+ stringused = stringneeded;
+ if (stringused > p->stringlen) {
+ stringused = (is_string ? p->stringlen : 0);
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ if (!stringused) {
+ SIVAL(p->structbuf,0,0);
+ } else {
+ SIVAL(p->structbuf,0,PTR_DIFF(p->stringbuf,p->base));
+ memcpy(p->stringbuf,str?str:"",stringused);
+ if (is_string) {
+ p->stringbuf[stringused-1] = '\0';
+ }
+ p->stringbuf += stringused;
+ p->stringlen -= stringused;
+ p->usedlen += stringused;
+ }
+ }
+ p->neededlen += stringneeded;
+ }
+
+ p->neededlen += needed;
+ if (p->buflen >= needed) {
+ p->structbuf += needed;
+ p->buflen -= needed;
+ p->usedlen += needed;
+ } else {
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ return 1;
+}
+
+#if CHECK_TYPES
+#define PACK(desc,t,v) package(desc,t,v,0,0,0,0)
+#define PACKl(desc,t,v,l) package(desc,t,v,l,0,0,0,0)
+#else
+#define PACK(desc,t,v) package(desc,v)
+#define PACKl(desc,t,v,l) package(desc,v,l)
+#endif
+
+static void PACKI(struct pack_desc* desc, const char *t,int v)
+{
+ PACK(desc,t,v);
+}
+
+static void PACKS(struct pack_desc* desc,const char *t,const char *v)
+{
+ PACK(desc,t,v);
+}
+
+/****************************************************************************
+ Get a print queue.
+****************************************************************************/
+
+static void PackDriverData(struct pack_desc* desc)
+{
+ char drivdata[4+4+32];
+ SIVAL(drivdata,0,sizeof drivdata); /* cb */
+ SIVAL(drivdata,4,1000); /* lVersion */
+ memset(drivdata+8,0,32); /* szDeviceName */
+ push_ascii(drivdata+8,"NULL",32, STR_TERMINATE);
+ PACKl(desc,"l",drivdata,sizeof drivdata); /* pDriverData */
+}
+
+static int check_printq_info(struct pack_desc* desc,
+ unsigned int uLevel, char *id1, char *id2)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B13";
+ break;
+ case 1:
+ desc->format = "B13BWWWzzzzzWW";
+ break;
+ case 2:
+ desc->format = "B13BWWWzzzzzWN";
+ desc->subformat = "WB21BB16B10zWWzDDz";
+ break;
+ case 3:
+ desc->format = "zWWWWzzzzWWzzl";
+ break;
+ case 4:
+ desc->format = "zWWWWzzzzWNzzl";
+ desc->subformat = "WWzWWDDzz";
+ break;
+ case 5:
+ desc->format = "z";
+ break;
+ case 51:
+ desc->format = "K";
+ break;
+ case 52:
+ desc->format = "WzzzzzzzzN";
+ desc->subformat = "z";
+ break;
+ default:
+ DEBUG(0,("check_printq_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id1 == NULL || strcmp(desc->format,id1) != 0) {
+ DEBUG(0,("check_printq_info: invalid format %s\n",
+ id1 ? id1 : "<NULL>" ));
+ return False;
+ }
+ if (desc->subformat && (id2 == NULL || strcmp(desc->subformat,id2) != 0)) {
+ DEBUG(0,("check_printq_info: invalid subformat %s\n",
+ id2 ? id2 : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+
+#define RAP_JOB_STATUS_QUEUED 0
+#define RAP_JOB_STATUS_PAUSED 1
+#define RAP_JOB_STATUS_SPOOLING 2
+#define RAP_JOB_STATUS_PRINTING 3
+#define RAP_JOB_STATUS_PRINTED 4
+
+#define RAP_QUEUE_STATUS_PAUSED 1
+#define RAP_QUEUE_STATUS_ERROR 2
+
+/* turn a print job status into a on the wire status
+*/
+static int printj_status(int v)
+{
+ switch (v) {
+ case LPQ_QUEUED:
+ return RAP_JOB_STATUS_QUEUED;
+ case LPQ_PAUSED:
+ return RAP_JOB_STATUS_PAUSED;
+ case LPQ_SPOOLING:
+ return RAP_JOB_STATUS_SPOOLING;
+ case LPQ_PRINTING:
+ return RAP_JOB_STATUS_PRINTING;
+ }
+ return 0;
+}
+
+/* turn a print queue status into a on the wire status
+*/
+static int printq_status(int v)
+{
+ switch (v) {
+ case LPQ_QUEUED:
+ return 0;
+ case LPQ_PAUSED:
+ return RAP_QUEUE_STATUS_PAUSED;
+ }
+ return RAP_QUEUE_STATUS_ERROR;
+}
+
+static void fill_printjob_info(connection_struct *conn, int snum, int uLevel,
+ struct pack_desc *desc,
+ print_queue_struct *queue, int n)
+{
+ time_t t = queue->time;
+
+ /* the client expects localtime */
+ t -= get_time_zone(t);
+
+ PACKI(desc,"W",pjobid_to_rap(lp_const_servicename(snum),queue->job)); /* uJobId */
+ if (uLevel == 1) {
+ PACKS(desc,"B21",queue->fs_user); /* szUserName */
+ PACKS(desc,"B",""); /* pad */
+ PACKS(desc,"B16",""); /* szNotifyName */
+ PACKS(desc,"B10","PM_Q_RAW"); /* szDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W",printj_status(queue->status)); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D",t); /* ulSubmitted */
+ PACKI(desc,"D",queue->size); /* ulSize */
+ PACKS(desc,"z",queue->fs_file); /* pszComment */
+ }
+ if (uLevel == 2 || uLevel == 3 || uLevel == 4) {
+ PACKI(desc,"W",queue->priority); /* uPriority */
+ PACKS(desc,"z",queue->fs_user); /* pszUserName */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W",printj_status(queue->status)); /* fsStatus */
+ PACKI(desc,"D",t); /* ulSubmitted */
+ PACKI(desc,"D",queue->size); /* ulSize */
+ PACKS(desc,"z","Samba"); /* pszComment */
+ PACKS(desc,"z",queue->fs_file); /* pszDocument */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszNotifyName */
+ PACKS(desc,"z","PM_Q_RAW"); /* pszDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z",SERVICE(snum)); /* pszQueue */
+ PACKS(desc,"z","lpd"); /* pszQProcName */
+ PACKS(desc,"z",""); /* pszQProcParms */
+ PACKS(desc,"z","NULL"); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ PACKS(desc,"z",""); /* pszPrinterName */
+ } else if (uLevel == 4) { /* OS2 */
+ PACKS(desc,"z",""); /* pszSpoolFileName */
+ PACKS(desc,"z",""); /* pszPortName */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D",0); /* ulPagesSpooled */
+ PACKI(desc,"D",0); /* ulPagesSent */
+ PACKI(desc,"D",0); /* ulPagesPrinted */
+ PACKI(desc,"D",0); /* ulTimePrinted */
+ PACKI(desc,"D",0); /* ulExtendJobStatus */
+ PACKI(desc,"D",0); /* ulStartPage */
+ PACKI(desc,"D",0); /* ulEndPage */
+ }
+ }
+}
+
+/********************************************************************
+ Return a driver name given an snum.
+ Returns True if from tdb, False otherwise.
+ ********************************************************************/
+
+static bool get_driver_name(int snum, char **pp_drivername)
+{
+ NT_PRINTER_INFO_LEVEL *info = NULL;
+ bool in_tdb = false;
+
+ get_a_printer (NULL, &info, 2, lp_servicename(snum));
+ if (info != NULL) {
+ *pp_drivername = talloc_strdup(talloc_tos(),
+ info->info_2->drivername);
+ in_tdb = true;
+ free_a_printer(&info, 2);
+ if (!*pp_drivername) {
+ return false;
+ }
+ }
+
+ return in_tdb;
+}
+
+/********************************************************************
+ Respond to the DosPrintQInfo command with a level of 52
+ This is used to get printer driver information for Win9x clients
+ ********************************************************************/
+static void fill_printq_info_52(connection_struct *conn, int snum,
+ struct pack_desc* desc, int count )
+{
+ int i;
+ fstring location;
+ NT_PRINTER_DRIVER_INFO_LEVEL driver;
+ NT_PRINTER_INFO_LEVEL *printer = NULL;
+
+ ZERO_STRUCT(driver);
+
+ if ( !W_ERROR_IS_OK(get_a_printer( NULL, &printer, 2, lp_servicename(snum))) ) {
+ DEBUG(3,("fill_printq_info_52: Failed to lookup printer [%s]\n",
+ lp_servicename(snum)));
+ goto err;
+ }
+
+ if ( !W_ERROR_IS_OK(get_a_printer_driver(&driver, 3, printer->info_2->drivername,
+ "Windows 4.0", 0)) )
+ {
+ DEBUG(3,("fill_printq_info_52: Failed to lookup driver [%s]\n",
+ printer->info_2->drivername));
+ goto err;
+ }
+
+ trim_string(driver.info_3->driverpath, "\\print$\\WIN40\\0\\", 0);
+ trim_string(driver.info_3->datafile, "\\print$\\WIN40\\0\\", 0);
+ trim_string(driver.info_3->helpfile, "\\print$\\WIN40\\0\\", 0);
+
+ PACKI(desc, "W", 0x0400); /* don't know */
+ PACKS(desc, "z", driver.info_3->name); /* long printer name */
+ PACKS(desc, "z", driver.info_3->driverpath); /* Driverfile Name */
+ PACKS(desc, "z", driver.info_3->datafile); /* Datafile name */
+ PACKS(desc, "z", driver.info_3->monitorname); /* language monitor */
+
+ fstrcpy(location, "\\\\%L\\print$\\WIN40\\0");
+ standard_sub_basic( "", "", location, sizeof(location)-1 );
+ PACKS(desc,"z", location); /* share to retrieve files */
+
+ PACKS(desc,"z", driver.info_3->defaultdatatype); /* default data type */
+ PACKS(desc,"z", driver.info_3->helpfile); /* helpfile name */
+ PACKS(desc,"z", driver.info_3->driverpath); /* driver name */
+
+ DEBUG(3,("Printer Driver Name: %s:\n",driver.info_3->name));
+ DEBUG(3,("Driver: %s:\n",driver.info_3->driverpath));
+ DEBUG(3,("Data File: %s:\n",driver.info_3->datafile));
+ DEBUG(3,("Language Monitor: %s:\n",driver.info_3->monitorname));
+ DEBUG(3,("Driver Location: %s:\n",location));
+ DEBUG(3,("Data Type: %s:\n",driver.info_3->defaultdatatype));
+ DEBUG(3,("Help File: %s:\n",driver.info_3->helpfile));
+ PACKI(desc,"N",count); /* number of files to copy */
+
+ for ( i=0; i<count && driver.info_3->dependentfiles && *driver.info_3->dependentfiles[i]; i++)
+ {
+ trim_string(driver.info_3->dependentfiles[i], "\\print$\\WIN40\\0\\", 0);
+ PACKS(desc,"z",driver.info_3->dependentfiles[i]); /* driver files to copy */
+ DEBUG(3,("Dependent File: %s:\n",driver.info_3->dependentfiles[i]));
+ }
+
+ /* sanity check */
+ if ( i != count )
+ DEBUG(3,("fill_printq_info_52: file count specified by client [%d] != number of dependent files [%i]\n",
+ count, i));
+
+ DEBUG(3,("fill_printq_info on <%s> gave %d entries\n", SERVICE(snum),i));
+
+ desc->errcode=NERR_Success;
+ goto done;
+
+err:
+ DEBUG(3,("fill_printq_info: Can't supply driver files\n"));
+ desc->errcode=NERR_notsupported;
+
+done:
+ if ( printer )
+ free_a_printer( &printer, 2 );
+
+ if ( driver.info_3 )
+ free_a_printer_driver( driver, 3 );
+}
+
+
+static void fill_printq_info(connection_struct *conn, int snum, int uLevel,
+ struct pack_desc* desc,
+ int count, print_queue_struct* queue,
+ print_status_struct* status)
+{
+ switch (uLevel) {
+ case 1:
+ case 2:
+ PACKS(desc,"B13",SERVICE(snum));
+ break;
+ case 3:
+ case 4:
+ case 5:
+ PACKS(desc,"z",Expand(conn,snum,SERVICE(snum)));
+ break;
+ case 51:
+ PACKI(desc,"K",printq_status(status->status));
+ break;
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ PACKS(desc,"B",""); /* alignment */
+ PACKI(desc,"W",5); /* priority */
+ PACKI(desc,"W",0); /* start time */
+ PACKI(desc,"W",0); /* until time */
+ PACKS(desc,"z",""); /* pSepFile */
+ PACKS(desc,"z","lpd"); /* pPrProc */
+ PACKS(desc,"z",SERVICE(snum)); /* pDestinations */
+ PACKS(desc,"z",""); /* pParms */
+ if (snum < 0) {
+ PACKS(desc,"z","UNKNOWN PRINTER");
+ PACKI(desc,"W",LPSTAT_ERROR);
+ }
+ else if (!status || !status->message[0]) {
+ PACKS(desc,"z",Expand(conn,snum,lp_comment(snum)));
+ PACKI(desc,"W",LPSTAT_OK); /* status */
+ } else {
+ PACKS(desc,"z",status->message);
+ PACKI(desc,"W",printq_status(status->status)); /* status */
+ }
+ PACKI(desc,(uLevel == 1 ? "W" : "N"),count);
+ }
+
+ if (uLevel == 3 || uLevel == 4) {
+ char *drivername = NULL;
+
+ PACKI(desc,"W",5); /* uPriority */
+ PACKI(desc,"W",0); /* uStarttime */
+ PACKI(desc,"W",0); /* uUntiltime */
+ PACKI(desc,"W",5); /* pad1 */
+ PACKS(desc,"z",""); /* pszSepFile */
+ PACKS(desc,"z","WinPrint"); /* pszPrProc */
+ PACKS(desc,"z",NULL); /* pszParms */
+ PACKS(desc,"z",NULL); /* pszComment - don't ask.... JRA */
+ /* "don't ask" that it's done this way to fix corrupted
+ Win9X/ME printer comments. */
+ if (!status) {
+ PACKI(desc,"W",LPSTAT_OK); /* fsStatus */
+ } else {
+ PACKI(desc,"W",printq_status(status->status)); /* fsStatus */
+ }
+ PACKI(desc,(uLevel == 3 ? "W" : "N"),count); /* cJobs */
+ PACKS(desc,"z",SERVICE(snum)); /* pszPrinters */
+ get_driver_name(snum,&drivername);
+ if (!drivername) {
+ return;
+ }
+ PACKS(desc,"z",drivername); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ }
+
+ if (uLevel == 2 || uLevel == 4) {
+ int i;
+ for (i=0;i<count;i++)
+ fill_printjob_info(conn,snum,uLevel == 2 ? 1 : 2,desc,&queue[i],i);
+ }
+
+ if (uLevel==52)
+ fill_printq_info_52( conn, snum, desc, count );
+}
+
+/* This function returns the number of files for a given driver */
+static int get_printerdrivernumber(int snum)
+{
+ int result = 0;
+ NT_PRINTER_DRIVER_INFO_LEVEL driver;
+ NT_PRINTER_INFO_LEVEL *printer = NULL;
+
+ ZERO_STRUCT(driver);
+
+ if ( !W_ERROR_IS_OK(get_a_printer( NULL, &printer, 2, lp_servicename(snum))) ) {
+ DEBUG(3,("get_printerdrivernumber: Failed to lookup printer [%s]\n",
+ lp_servicename(snum)));
+ goto done;
+ }
+
+ if ( !W_ERROR_IS_OK(get_a_printer_driver(&driver, 3, printer->info_2->drivername,
+ "Windows 4.0", 0)) )
+ {
+ DEBUG(3,("get_printerdrivernumber: Failed to lookup driver [%s]\n",
+ printer->info_2->drivername));
+ goto done;
+ }
+
+ /* count the number of files */
+ while ( driver.info_3->dependentfiles && *driver.info_3->dependentfiles[result] )
+ result++;
+ \
+ done:
+ if ( printer )
+ free_a_printer( &printer, 2 );
+
+ if ( driver.info_3 )
+ free_a_printer_driver( driver, 3 );
+
+ return result;
+}
+
+static bool api_DosPrintQGetInfo(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *QueueName = p;
+ unsigned int uLevel;
+ int count=0;
+ int snum;
+ char *str3;
+ struct pack_desc desc;
+ print_queue_struct *queue=NULL;
+ print_status_struct status;
+ char* tmpdata=NULL;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&status,'\0',sizeof(status));
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ str3 = get_safe_str_ptr(param,tpscnt,p,4);
+ /* str3 may be null here and is checked in check_printq_info(). */
+
+ /* remove any trailing username */
+ if ((p = strchr_m(QueueName,'%')))
+ *p = 0;
+
+ DEBUG(3,("api_DosPrintQGetInfo uLevel=%d name=%s\n",uLevel,QueueName));
+
+ /* check it's a supported varient */
+ if (!prefix_ok(str1,"zWrLh"))
+ return False;
+ if (!check_printq_info(&desc,uLevel,str2,str3)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ snum = find_service(QueueName);
+ if ( !(lp_snum_ok(snum) && lp_print_ok(snum)) )
+ return False;
+
+ if (uLevel==52) {
+ count = get_printerdrivernumber(snum);
+ DEBUG(3,("api_DosPrintQGetInfo: Driver files count: %d\n",count));
+ } else {
+ count = print_queue_status(snum, &queue,&status);
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ SAFE_FREE(queue);
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *) SMB_MALLOC (desc.buflen);
+ }
+
+ if (init_package(&desc,1,count)) {
+ desc.subcount = count;
+ fill_printq_info(conn,snum,uLevel,&desc,count,queue,&status);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ /*
+ * We must set the return code to ERRbuftoosmall
+ * in order to support lanman style printing with Win NT/2k
+ * clients --jerry
+ */
+ if (!mdrcnt && lp_disable_spoolss())
+ desc.errcode = ERRbuftoosmall;
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ SAFE_FREE(queue);
+ SAFE_FREE(tmpdata);
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("printqgetinfo: errorcode %d\n",desc.errcode));
+
+ SAFE_FREE(queue);
+ SAFE_FREE(tmpdata);
+
+ return(True);
+}
+
+/****************************************************************************
+ View list of all print jobs on all queues.
+****************************************************************************/
+
+static bool api_DosPrintQEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char** rparam,
+ int *rdata_len, int *rparam_len)
+{
+ char *param_format = get_safe_str_ptr(param,tpscnt,param,2);
+ char *output_format1 = skip_string(param,tpscnt,param_format);
+ char *p = skip_string(param,tpscnt,output_format1);
+ unsigned int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *output_format2 = get_safe_str_ptr(param,tpscnt,p,4);
+ int services = lp_numservices();
+ int i, n;
+ struct pack_desc desc;
+ print_queue_struct **queue = NULL;
+ print_status_struct *status = NULL;
+ int *subcntarr = NULL;
+ int queuecnt = 0, subcnt = 0, succnt = 0;
+
+ if (!param_format || !output_format1 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("DosPrintQEnum uLevel=%d\n",uLevel));
+
+ if (!prefix_ok(param_format,"WrLeh")) {
+ return False;
+ }
+ if (!check_printq_info(&desc,uLevel,output_format1,output_format2)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ for (i = 0; i < services; i++) {
+ if (lp_snum_ok(i) && lp_print_ok(i) && lp_browseable(i)) {
+ queuecnt++;
+ }
+ }
+
+ if((queue = SMB_MALLOC_ARRAY(print_queue_struct*, queuecnt)) == NULL) {
+ DEBUG(0,("api_DosPrintQEnum: malloc fail !\n"));
+ goto err;
+ }
+ memset(queue,0,queuecnt*sizeof(print_queue_struct*));
+ if((status = SMB_MALLOC_ARRAY(print_status_struct,queuecnt)) == NULL) {
+ DEBUG(0,("api_DosPrintQEnum: malloc fail !\n"));
+ goto err;
+ }
+ memset(status,0,queuecnt*sizeof(print_status_struct));
+ if((subcntarr = SMB_MALLOC_ARRAY(int,queuecnt)) == NULL) {
+ DEBUG(0,("api_DosPrintQEnum: malloc fail !\n"));
+ goto err;
+ }
+
+ subcnt = 0;
+ n = 0;
+ for (i = 0; i < services; i++) {
+ if (lp_snum_ok(i) && lp_print_ok(i) && lp_browseable(i)) {
+ subcntarr[n] = print_queue_status(i, &queue[n],&status[n]);
+ subcnt += subcntarr[n];
+ n++;
+ }
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ goto err;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ if (init_package(&desc,queuecnt,subcnt)) {
+ n = 0;
+ succnt = 0;
+ for (i = 0; i < services; i++) {
+ if (lp_snum_ok(i) && lp_print_ok(i) && lp_browseable(i)) {
+ fill_printq_info(conn,i,uLevel,&desc,subcntarr[n],queue[n],&status[n]);
+ n++;
+ if (desc.errcode == NERR_Success) {
+ succnt = n;
+ }
+ }
+ }
+ }
+
+ SAFE_FREE(subcntarr);
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ goto err;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ for (i = 0; i < queuecnt; i++) {
+ if (queue) {
+ SAFE_FREE(queue[i]);
+ }
+ }
+
+ SAFE_FREE(queue);
+ SAFE_FREE(status);
+
+ return True;
+
+ err:
+
+ SAFE_FREE(subcntarr);
+ for (i = 0; i < queuecnt; i++) {
+ if (queue) {
+ SAFE_FREE(queue[i]);
+ }
+ }
+ SAFE_FREE(queue);
+ SAFE_FREE(status);
+
+ return False;
+}
+
+/****************************************************************************
+ Get info level for a server list query.
+****************************************************************************/
+
+static bool check_server_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B16") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ if (strcmp(id,"B16BBDz") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+struct srv_info_struct {
+ fstring name;
+ uint32 type;
+ fstring comment;
+ fstring domain;
+ bool server_added;
+};
+
+/*******************************************************************
+ Get server info lists from the files saved by nmbd. Return the
+ number of entries.
+******************************************************************/
+
+static int get_server_info(uint32 servertype,
+ struct srv_info_struct **servers,
+ const char *domain)
+{
+ int count=0;
+ int alloced=0;
+ char **lines;
+ bool local_list_only;
+ int i;
+
+ lines = file_lines_load(lock_path(SERVER_LIST), NULL, 0);
+ if (!lines) {
+ DEBUG(4,("Can't open %s - %s\n",lock_path(SERVER_LIST),strerror(errno)));
+ return 0;
+ }
+
+ /* request for everything is code for request all servers */
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ local_list_only = (servertype & SV_TYPE_LOCAL_LIST_ONLY);
+
+ DEBUG(4,("Servertype search: %8x\n",servertype));
+
+ for (i=0;lines[i];i++) {
+ fstring stype;
+ struct srv_info_struct *s;
+ const char *ptr = lines[i];
+ bool ok = True;
+ TALLOC_CTX *frame = NULL;
+ char *p;
+
+ if (!*ptr) {
+ continue;
+ }
+
+ if (count == alloced) {
+ alloced += 10;
+ *servers = SMB_REALLOC_ARRAY(*servers,struct srv_info_struct, alloced);
+ if (!*servers) {
+ DEBUG(0,("get_server_info: failed to enlarge servers info struct!\n"));
+ file_lines_free(lines);
+ return 0;
+ }
+ memset((char *)((*servers)+count),'\0',sizeof(**servers)*(alloced-count));
+ }
+ s = &(*servers)[count];
+
+ frame = talloc_stackframe();
+ s->name[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->name, p);
+
+ stype[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(stype, p);
+
+ s->comment[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->comment, p);
+
+ s->domain[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ /* this allows us to cope with an old nmbd */
+ fstrcpy(s->domain,lp_workgroup());
+ } else {
+ fstrcpy(s->domain, p);
+ }
+ TALLOC_FREE(frame);
+
+ if (sscanf(stype,"%X",&s->type) != 1) {
+ DEBUG(4,("r:host file "));
+ ok = False;
+ }
+
+ /* Filter the servers/domains we return based on what was asked for. */
+
+ /* Check to see if we are being asked for a local list only. */
+ if(local_list_only && ((s->type & SV_TYPE_LOCAL_LIST_ONLY) == 0)) {
+ DEBUG(4,("r: local list only"));
+ ok = False;
+ }
+
+ /* doesn't match up: don't want it */
+ if (!(servertype & s->type)) {
+ DEBUG(4,("r:serv type "));
+ ok = False;
+ }
+
+ if ((servertype & SV_TYPE_DOMAIN_ENUM) !=
+ (s->type & SV_TYPE_DOMAIN_ENUM)) {
+ DEBUG(4,("s: dom mismatch "));
+ ok = False;
+ }
+
+ if (!strequal(domain, s->domain) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ ok = False;
+ }
+
+ /* We should never return a server type with a SV_TYPE_LOCAL_LIST_ONLY set. */
+ s->type &= ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ if (ok) {
+ DEBUG(4,("**SV** %20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ s->server_added = True;
+ count++;
+ } else {
+ DEBUG(4,("%20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ }
+ }
+
+ file_lines_free(lines);
+ return count;
+}
+
+/*******************************************************************
+ Fill in a server info structure.
+******************************************************************/
+
+static int fill_srv_info(struct srv_info_struct *service,
+ int uLevel, char **buf, int *buflen,
+ char **stringbuf, int *stringspace, char *baseaddr)
+{
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch (uLevel) {
+ case 0:
+ struct_len = 16;
+ break;
+ case 1:
+ struct_len = 26;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!buf) {
+ len = 0;
+ switch (uLevel) {
+ case 1:
+ len = strlen(service->comment)+1;
+ break;
+ }
+
+ *buflen = struct_len;
+ *stringspace = len;
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if (*buflen < struct_len) {
+ return -1;
+ }
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = *buflen - struct_len;
+ }
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ switch (uLevel) {
+ case 0:
+ push_ascii(p,service->name, MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ break;
+
+ case 1:
+ push_ascii(p,service->name,MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ SIVAL(p,18,service->type);
+ SIVAL(p,22,PTR_DIFF(p2,baseaddr));
+ len += CopyAndAdvance(&p2,service->comment,&l2);
+ break;
+ }
+
+ if (stringbuf) {
+ *buf = p + struct_len;
+ *buflen -= struct_len;
+ *stringbuf = p2;
+ *stringspace = l2;
+ } else {
+ *buf = p2;
+ *buflen -= len;
+ }
+ return len;
+}
+
+
+static bool srv_comp(struct srv_info_struct *s1,struct srv_info_struct *s2)
+{
+ return(strcmp(s1->name,s2->name));
+}
+
+/****************************************************************************
+ View list of servers available (or possibly domains). The info is
+ extracted from lists saved by nmbd on the local host.
+****************************************************************************/
+
+static bool api_RNetServerEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param, tpscnt, param, 2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param, tpscnt, p, 0, -1);
+ int buf_len = get_safe_SVAL(param,tpscnt, p, 2, 0);
+ uint32 servertype = get_safe_IVAL(param,tpscnt,p,4, 0);
+ char *p2;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+ struct srv_info_struct *servers=NULL;
+ int counted=0,total=0;
+ int i,missed;
+ fstring domain;
+ bool domain_request;
+ bool local_request;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* If someone sets all the bits they don't really mean to set
+ DOMAIN_ENUM and LOCAL_LIST_ONLY, they just want all the
+ known servers. */
+
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ /* If someone sets SV_TYPE_LOCAL_LIST_ONLY but hasn't set
+ any other bit (they may just set this bit on its own) they
+ want all the locally seen servers. However this bit can be
+ set on its own so set the requested servers to be
+ ALL - DOMAIN_ENUM. */
+
+ if ((servertype & SV_TYPE_LOCAL_LIST_ONLY) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ servertype = SV_TYPE_ALL & ~(SV_TYPE_DOMAIN_ENUM);
+ }
+
+ domain_request = ((servertype & SV_TYPE_DOMAIN_ENUM) != 0);
+ local_request = ((servertype & SV_TYPE_LOCAL_LIST_ONLY) != 0);
+
+ p += 8;
+
+ if (!prefix_ok(str1,"WrLehD")) {
+ return False;
+ }
+ if (!check_server_info(uLevel,str2)) {
+ return False;
+ }
+
+ DEBUG(4, ("server request level: %s %8x ", str2, servertype));
+ DEBUG(4, ("domains_req:%s ", BOOLSTR(domain_request)));
+ DEBUG(4, ("local_only:%s\n", BOOLSTR(local_request)));
+
+ if (strcmp(str1, "WrLehDz") == 0) {
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(domain, p);
+ } else {
+ fstrcpy(domain, lp_workgroup());
+ }
+
+ if (lp_browse_list()) {
+ total = get_server_info(servertype,&servers,domain);
+ }
+
+ data_len = fixed_len = string_len = 0;
+ missed = 0;
+
+ if (total > 0) {
+ qsort(servers,total,sizeof(servers[0]),QSORT_CAST srv_comp);
+ }
+
+ {
+ char *lastname=NULL;
+
+ for (i=0;i<total;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ data_len += fill_srv_info(s,uLevel,0,&f_len,0,&s_len,0);
+ DEBUG(4,("fill_srv_info %20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+
+ if (data_len <= buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed++;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxilliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ {
+ char *lastname=NULL;
+ int count2 = counted;
+
+ for (i = 0; i < total && count2;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ fill_srv_info(s,uLevel,&p,&f_len,&p2,&s_len,*rdata);
+ DEBUG(4,("fill_srv_info %20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ count2--;
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,(missed == 0 ? NERR_Success : ERRmoredata));
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ SAFE_FREE(servers);
+
+ DEBUG(3,("NetServerEnum domain = %s uLevel=%d counted=%d total=%d\n",
+ domain,uLevel,counted,counted+missed));
+
+ return True;
+}
+
+/****************************************************************************
+ command 0x34 - suspected of being a "Lookup Names" stub api
+ ****************************************************************************/
+
+static bool api_RNetGroupGetUsers(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ int counted=0;
+ int missed=0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(5,("RNetGroupGetUsers: %s %s %s %d %d\n",
+ str1, str2, p, uLevel, buf_len));
+
+ if (!prefix_ok(str1,"zWrLeh")) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,0x08AC); /* informational warning message */
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a share
+ ****************************************************************************/
+
+static bool check_share_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B13") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ if (strcmp(id,"B13BWz") != 0) {
+ return False;
+ }
+ break;
+ case 2:
+ if (strcmp(id,"B13BWzWWWzB9B") != 0) {
+ return False;
+ }
+ break;
+ case 91:
+ if (strcmp(id,"B13BWzWWWzB9BB9BWzWWzWW") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+static int fill_share_info(connection_struct *conn, int snum, int uLevel,
+ char** buf, int* buflen,
+ char** stringbuf, int* stringspace, char* baseaddr)
+{
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch( uLevel ) {
+ case 0:
+ struct_len = 13;
+ break;
+ case 1:
+ struct_len = 20;
+ break;
+ case 2:
+ struct_len = 40;
+ break;
+ case 91:
+ struct_len = 68;
+ break;
+ default:
+ return -1;
+ }
+
+
+ if (!buf) {
+ len = 0;
+
+ if (uLevel > 0) {
+ len += StrlenExpanded(conn,snum,lp_comment(snum));
+ }
+ if (uLevel > 1) {
+ len += strlen(lp_pathname(snum)) + 1;
+ }
+ if (buflen) {
+ *buflen = struct_len;
+ }
+ if (stringspace) {
+ *stringspace = len;
+ }
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if ((*buflen) < struct_len) {
+ return -1;
+ }
+
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = (*buflen) - struct_len;
+ }
+
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ push_ascii(p,lp_servicename(snum),13, STR_TERMINATE);
+
+ if (uLevel > 0) {
+ int type;
+
+ SCVAL(p,13,0);
+ type = STYPE_DISKTREE;
+ if (lp_print_ok(snum)) {
+ type = STYPE_PRINTQ;
+ }
+ if (strequal("IPC",lp_fstype(snum))) {
+ type = STYPE_IPC;
+ }
+ SSVAL(p,14,type); /* device type */
+ SIVAL(p,16,PTR_DIFF(p2,baseaddr));
+ len += CopyExpanded(conn,snum,&p2,lp_comment(snum),&l2);
+ }
+
+ if (uLevel > 1) {
+ SSVAL(p,20,ACCESS_READ|ACCESS_WRITE|ACCESS_CREATE); /* permissions */
+ SSVALS(p,22,-1); /* max uses */
+ SSVAL(p,24,1); /* current uses */
+ SIVAL(p,26,PTR_DIFF(p2,baseaddr)); /* local pathname */
+ len += CopyAndAdvance(&p2,lp_pathname(snum),&l2);
+ memset(p+30,0,SHPWLEN+2); /* passwd (reserved), pad field */
+ }
+
+ if (uLevel > 2) {
+ memset(p+40,0,SHPWLEN+2);
+ SSVAL(p,50,0);
+ SIVAL(p,52,0);
+ SSVAL(p,56,0);
+ SSVAL(p,58,0);
+ SIVAL(p,60,0);
+ SSVAL(p,64,0);
+ SSVAL(p,66,0);
+ }
+
+ if (stringbuf) {
+ (*buf) = p + struct_len;
+ (*buflen) -= struct_len;
+ (*stringbuf) = p2;
+ (*stringspace) = l2;
+ } else {
+ (*buf) = p2;
+ (*buflen) -= len;
+ }
+
+ return len;
+}
+
+static bool api_RNetShareGetInfo(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *netname = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,netname);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int snum;
+
+ if (!str1 || !str2 || !netname || !p) {
+ return False;
+ }
+
+ snum = find_service(netname);
+ if (snum < 0) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+ if (!prefix_ok(str1,"zWrLh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ p = *rdata;
+ *rdata_len = fill_share_info(conn,snum,uLevel,&p,&mdrcnt,0,0,0);
+ if (*rdata_len < 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ View the list of available shares.
+
+ This function is the server side of the NetShareEnum() RAP call.
+ It fills the return buffer with share names and share comments.
+ Note that the return buffer normally (in all known cases) allows only
+ twelve byte strings for share names (plus one for a nul terminator).
+ Share names longer than 12 bytes must be skipped.
+ ****************************************************************************/
+
+static bool api_RNetShareEnum( connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,
+ int mprcnt,
+ char **rdata,
+ char **rparam,
+ int *rdata_len,
+ int *rparam_len )
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ char *p2;
+ int count = 0;
+ int total=0,counted=0;
+ bool missed = False;
+ int i;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (!prefix_ok(str1,"WrLeh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ load_registry_shares();
+ count = load_usershare_shares();
+ unbecome_root();
+
+ data_len = fixed_len = string_len = 0;
+ for (i=0;i<count;i++) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+ push_ascii_fstring(servicename_dos, lp_servicename(i));
+ /* Maximum name length = 13. */
+ if( lp_browseable( i ) && lp_snum_ok( i ) && (strlen(servicename_dos) < 13)) {
+ total++;
+ data_len += fill_share_info(conn,i,uLevel,0,&f_len,0,&s_len,0);
+ if (data_len <= buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed = True;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ for( i = 0; i < count; i++ ) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+
+ push_ascii_fstring(servicename_dos, lp_servicename(i));
+ if (lp_browseable(i) && lp_snum_ok(i) && (strlen(servicename_dos) < 13)) {
+ if (fill_share_info( conn,i,uLevel,&p,&f_len,&p2,&s_len,*rdata ) < 0) {
+ break;
+ }
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,missed ? ERRmoredata : NERR_Success);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,total);
+
+ DEBUG(3,("RNetShareEnum gave %d entries of %d (%d %d %d %d)\n",
+ counted,total,uLevel,
+ buf_len,*rdata_len,mdrcnt));
+
+ return True;
+}
+
+/****************************************************************************
+ Add a share
+ ****************************************************************************/
+
+static bool api_RNetShareAdd(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ fstring sharename;
+ fstring comment;
+ char *pathname = NULL;
+ char *command, *cmdname;
+ unsigned int offset;
+ int snum;
+ int res = ERRunsup;
+ size_t converted_size;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+ if (!prefix_ok(str1,RAP_WShareAdd_REQ)) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+ if (uLevel != 2) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(sharename,data);
+ snum = find_service(sharename);
+ if (snum >= 0) { /* already exists */
+ res = ERRfilexists;
+ goto error_exit;
+ }
+
+ if (mdrcnt < 28) {
+ return False;
+ }
+
+ /* only support disk share adds */
+ if (SVAL(data,14)!=STYPE_DISKTREE) {
+ return False;
+ }
+
+ offset = IVAL(data, 16);
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto error_exit;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(comment, offset? (data+offset) : "");
+
+ offset = IVAL(data, 26);
+
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto error_exit;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+
+ if (!pull_ascii_talloc(talloc_tos(), &pathname,
+ offset ? (data+offset) : "", &converted_size))
+ {
+ DEBUG(0,("api_RNetShareAdd: pull_ascii_talloc failed: %s",
+ strerror(errno)));
+ }
+
+ if (!pathname) {
+ return false;
+ }
+
+ string_replace(sharename, '"', ' ');
+ string_replace(pathname, '"', ' ');
+ string_replace(comment, '"', ' ');
+
+ cmdname = lp_add_share_cmd();
+
+ if (!cmdname || *cmdname == '\0') {
+ return False;
+ }
+
+ if (asprintf(&command, "%s \"%s\" \"%s\" \"%s\" \"%s\"",
+ lp_add_share_cmd(), get_dyn_CONFIGFILE(), sharename,
+ pathname, comment) == -1) {
+ return false;
+ }
+
+ DEBUG(10,("api_RNetShareAdd: Running [%s]\n", command ));
+
+ if ((res = smbrun(command, NULL)) != 0) {
+ DEBUG(1,("api_RNetShareAdd: Running [%s] returned (%d)\n",
+ command, res ));
+ SAFE_FREE(command);
+ res = ERRnoaccess;
+ goto error_exit;
+ } else {
+ SAFE_FREE(command);
+ message_send_all(smbd_messaging_context(),
+ MSG_SMB_CONF_UPDATED, NULL, 0, NULL);
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+ *rdata_len = 0;
+
+ return True;
+
+ error_exit:
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+ SSVAL(*rparam,0,res);
+ SSVAL(*rparam,2,0);
+ return True;
+}
+
+/****************************************************************************
+ view list of groups available
+ ****************************************************************************/
+
+static bool api_RNetGroupEnum(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int i;
+ int errflags=0;
+ int resume_context, cli_buf_size;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+
+ struct pdb_search *search;
+ struct samr_displayentry *entries;
+
+ int num_entries;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ if (strcmp("B21",str2) != 0) {
+ return False;
+ }
+
+ /* get list of domain groups SID_DOMAIN_GRP=2 */
+ become_root();
+ search = pdb_search_groups();
+ unbecome_root();
+
+ if (search == NULL) {
+ DEBUG(3,("api_RNetGroupEnum:failed to get group list"));
+ return False;
+ }
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetGroupEnum:resume context: %d, client buffer size: "
+ "%d\n", resume_context, cli_buf_size));
+
+ become_root();
+ num_entries = pdb_search_entries(search, resume_context, 0xffffffff,
+ &entries);
+ unbecome_root();
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+
+ for(i=0; i<num_entries; i++) {
+ fstring name;
+ fstrcpy(name, entries[i].account_name);
+ if( ((PTR_DIFF(p,*rdata)+21) <= *rdata_len) ) {
+ /* truncate the name at 21 chars. */
+ memcpy(p, name, 21);
+ DEBUG(10,("adding entry %d group %s\n", i, p));
+ p += 21;
+ p += 5; /* Both NT4 and W2k3SP1 do padding here.
+ No idea why... */
+ } else {
+ /* set overflow error */
+ DEBUG(3,("overflow on entry %d group %s\n", i, name));
+ errflags=234;
+ break;
+ }
+ }
+
+ pdb_search_destroy(search);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam, 0, errflags);
+ SSVAL(*rparam, 2, 0); /* converter word */
+ SSVAL(*rparam, 4, i); /* is this right?? */
+ SSVAL(*rparam, 6, resume_context+num_entries); /* is this right?? */
+
+ return(True);
+}
+
+/*******************************************************************
+ Get groups that a user is a member of.
+******************************************************************/
+
+static bool api_NetUserGetGroups(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ const char *level_string;
+ int count=0;
+ struct samu *sampw = NULL;
+ bool ret = False;
+ DOM_SID *sids;
+ gid_t *gids;
+ size_t num_groups;
+ size_t i;
+ NTSTATUS result;
+ DOM_SID user_sid;
+ enum lsa_SidType type;
+ char *endp = NULL;
+ TALLOC_CTX *mem_ctx;
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+
+ if ( strcmp(str1,"zWrLeh") != 0 )
+ return False;
+
+ switch( uLevel ) {
+ case 0:
+ level_string = "B21";
+ break;
+ default:
+ return False;
+ }
+
+ if (strcmp(level_string,str2) != 0)
+ return False;
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return False;
+ }
+
+ if ( !(sampw = samu_new(mem_ctx)) ) {
+ DEBUG(0, ("samu_new() failed!\n"));
+ TALLOC_FREE(mem_ctx);
+ return False;
+ }
+
+ /* Lookup the user information; This should only be one of
+ our accounts (not remote domains) */
+
+ become_root(); /* ROOT BLOCK */
+
+ if (!lookup_name(mem_ctx, UserName, LOOKUP_NAME_ALL,
+ NULL, NULL, &user_sid, &type)) {
+ DEBUG(10, ("lookup_name(%s) failed\n", UserName));
+ goto done;
+ }
+
+ if (type != SID_NAME_USER) {
+ DEBUG(10, ("%s is a %s, not a user\n", UserName,
+ sid_type_lookup(type)));
+ goto done;
+ }
+
+ if ( !pdb_getsampwsid(sampw, &user_sid) ) {
+ DEBUG(10, ("pdb_getsampwsid(%s) failed for user %s\n",
+ sid_string_dbg(&user_sid), UserName));
+ goto done;
+ }
+
+ gids = NULL;
+ sids = NULL;
+ num_groups = 0;
+
+ result = pdb_enum_group_memberships(mem_ctx, sampw,
+ &sids, &gids, &num_groups);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("pdb_enum_group_memberships failed for %s\n",
+ UserName));
+ goto done;
+ }
+
+ for (i=0; i<num_groups; i++) {
+ const char *grp_name;
+
+ if ( lookup_sid(mem_ctx, &sids[i], NULL, &grp_name, NULL) ) {
+ strlcpy(p, grp_name, PTR_DIFF(endp,p));
+ p += 21;
+ count++;
+ }
+ }
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,4,count); /* is this right?? */
+ SSVAL(*rparam,6,count); /* is this right?? */
+
+ ret = True;
+
+done:
+ unbecome_root(); /* END ROOT BLOCK */
+
+ TALLOC_FREE(mem_ctx);
+
+ return ret;
+}
+
+/*******************************************************************
+ Get all users.
+******************************************************************/
+
+static bool api_RNetUserEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int count_sent=0;
+ int num_users=0;
+ int errflags=0;
+ int i, resume_context, cli_buf_size;
+ struct pdb_search *search;
+ struct samr_displayentry *users;
+
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *endp = NULL;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0)
+ return False;
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetUserEnum:resume context: %d, client buffer size: %d\n",
+ resume_context, cli_buf_size));
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+ if (strcmp("B21",str2) != 0)
+ return False;
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ become_root();
+ search = pdb_search_users(ACB_NORMAL);
+ unbecome_root();
+ if (search == NULL) {
+ DEBUG(0, ("api_RNetUserEnum:unable to open sam database.\n"));
+ return False;
+ }
+
+ become_root();
+ num_users = pdb_search_entries(search, resume_context, 0xffffffff,
+ &users);
+ unbecome_root();
+
+ errflags=NERR_Success;
+
+ for (i=0; i<num_users; i++) {
+ const char *name = users[i].account_name;
+
+ if(((PTR_DIFF(p,*rdata)+21)<=*rdata_len)&&(strlen(name)<=21)) {
+ strlcpy(p,name,PTR_DIFF(endp,p));
+ DEBUG(10,("api_RNetUserEnum:adding entry %d username "
+ "%s\n",count_sent,p));
+ p += 21;
+ count_sent++;
+ } else {
+ /* set overflow error */
+ DEBUG(10,("api_RNetUserEnum:overflow on entry %d "
+ "username %s\n",count_sent,name));
+ errflags=234;
+ break;
+ }
+ }
+
+ pdb_search_destroy(search);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,0,errflags);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,count_sent); /* is this right?? */
+ SSVAL(*rparam,6,num_users); /* is this right?? */
+
+ return True;
+}
+
+/****************************************************************************
+ Get the time of day info.
+****************************************************************************/
+
+static bool api_NetRemoteTOD(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct tm *t;
+ time_t unixdate = time(NULL);
+ char *p;
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 21;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+
+ srv_put_dos_date3(p,0,unixdate); /* this is the time that is looked at
+ by NT in a "net time" operation,
+ it seems to ignore the one below */
+
+ /* the client expects to get localtime, not GMT, in this bit
+ (I think, this needs testing) */
+ t = localtime(&unixdate);
+ if (!t) {
+ return False;
+ }
+
+ SIVAL(p,4,0); /* msecs ? */
+ SCVAL(p,8,t->tm_hour);
+ SCVAL(p,9,t->tm_min);
+ SCVAL(p,10,t->tm_sec);
+ SCVAL(p,11,0); /* hundredths of seconds */
+ SSVALS(p,12,get_time_zone(unixdate)/60); /* timezone in minutes from GMT */
+ SSVAL(p,14,10000); /* timer interval in 0.0001 of sec */
+ SCVAL(p,16,t->tm_mday);
+ SCVAL(p,17,t->tm_mon + 1);
+ SSVAL(p,18,1900+t->tm_year);
+ SCVAL(p,20,t->tm_wday);
+
+ return True;
+}
+
+/****************************************************************************
+ Set the user password.
+*****************************************************************************/
+
+static bool api_SetUserPassword(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *np = get_safe_str_ptr(param,tpscnt,param,2);
+ char *p = NULL;
+ fstring user;
+ fstring pass1,pass2;
+
+ /* Skip 2 strings. */
+ p = skip_string(param,tpscnt,np);
+ p = skip_string(param,tpscnt,p);
+
+ if (!np || !p) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(user,p);
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+
+ memset(pass1,'\0',sizeof(pass1));
+ memset(pass2,'\0',sizeof(pass2));
+ /*
+ * We use 31 here not 32 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,31)) {
+ return False;
+ }
+ memcpy(pass1,p,16);
+ memcpy(pass2,p+16,16);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_badpass);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ DEBUG(3,("Set password for <%s>\n",user));
+
+ /*
+ * Attempt to verify the old password against smbpasswd entries
+ * Win98 clients send old and new password in plaintext for this call.
+ */
+
+ {
+ auth_serversupplied_info *server_info = NULL;
+ DATA_BLOB password = data_blob(pass1, strlen(pass1)+1);
+
+ if (NT_STATUS_IS_OK(check_plaintext_password(user,password,&server_info))) {
+
+ become_root();
+ if (NT_STATUS_IS_OK(change_oem_password(server_info->sam_account, pass1, pass2, False, NULL))) {
+ SSVAL(*rparam,0,NERR_Success);
+ }
+ unbecome_root();
+
+ TALLOC_FREE(server_info);
+ }
+ data_blob_clear_free(&password);
+ }
+
+ /*
+ * If the plaintext change failed, attempt
+ * the old encrypted method. NT will generate this
+ * after trying the samr method. Note that this
+ * method is done as a last resort as this
+ * password change method loses the NT password hash
+ * and cannot change the UNIX password as no plaintext
+ * is received.
+ */
+
+ if(SVAL(*rparam,0) != NERR_Success) {
+ struct samu *hnd = NULL;
+
+ if (check_lanman_password(user,(unsigned char *)pass1,(unsigned char *)pass2, &hnd)) {
+ become_root();
+ if (change_lanman_password(hnd,(uchar *)pass2)) {
+ SSVAL(*rparam,0,NERR_Success);
+ }
+ unbecome_root();
+ TALLOC_FREE(hnd);
+ }
+ }
+
+ memset((char *)pass1,'\0',sizeof(fstring));
+ memset((char *)pass2,'\0',sizeof(fstring));
+
+ return(True);
+}
+
+/****************************************************************************
+ Set the user password (SamOEM version - gets plaintext).
+****************************************************************************/
+
+static bool api_SamOEMChangePassword(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ fstring user;
+ char *p = get_safe_str_ptr(param,tpscnt,param,2);
+ *rparam_len = 2;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ if (!p) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_badpass);
+
+ /*
+ * Check the parameter definition is correct.
+ */
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "zsT")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param, tpscnt, p);
+ if (!p) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "B516B16")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid data parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ p += pull_ascii_fstring(user,p);
+
+ DEBUG(3,("api_SamOEMChangePassword: Change password for <%s>\n",user));
+
+ /*
+ * Pass the user through the NT -> unix user mapping
+ * function.
+ */
+
+ (void)map_username(user);
+
+ if (NT_STATUS_IS_OK(pass_oem_change(user, (uchar*) data, (uchar *)&data[516], NULL, NULL, NULL))) {
+ SSVAL(*rparam,0,NERR_Success);
+ }
+
+ return(True);
+}
+
+/****************************************************************************
+ delete a print job
+ Form: <W> <>
+ ****************************************************************************/
+
+static bool api_RDosPrintJobDel(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32 jobid;
+ int snum;
+ fstring sharename;
+ int errcode;
+ WERROR werr = WERR_OK;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+
+ /* check it's a supported varient */
+ if (!(strcsequal(str1,"W") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ if (!print_job_exists(sharename, jobid)) {
+ errcode = NERR_JobNotFound;
+ goto out;
+ }
+
+ snum = lp_servicenumber( sharename);
+ if (snum == -1) {
+ errcode = NERR_DestNotFound;
+ goto out;
+ }
+
+ errcode = NERR_notsupported;
+
+ switch (function) {
+ case 81: /* delete */
+ if (print_job_delete(conn->server_info, snum, jobid, &werr))
+ errcode = NERR_Success;
+ break;
+ case 82: /* pause */
+ if (print_job_pause(conn->server_info, snum, jobid, &werr))
+ errcode = NERR_Success;
+ break;
+ case 83: /* resume */
+ if (print_job_resume(conn->server_info, snum, jobid, &werr))
+ errcode = NERR_Success;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(werr))
+ errcode = W_ERROR_V(werr);
+
+ out:
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ Purge a print queue - or pause or resume it.
+ ****************************************************************************/
+
+static bool api_WPrintQueueCtrl(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *QueueName = skip_string(param,tpscnt,str2);
+ int errcode = NERR_notsupported;
+ int snum;
+ WERROR werr = WERR_OK;
+
+ if (!str1 || !str2 || !QueueName) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+ if (!(strcsequal(str1,"z") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ if (skip_string(param,tpscnt,QueueName) == NULL) {
+ return False;
+ }
+ snum = print_queue_snum(QueueName);
+
+ if (snum == -1) {
+ errcode = NERR_JobNotFound;
+ goto out;
+ }
+
+ switch (function) {
+ case 74: /* Pause queue */
+ if (print_queue_pause(conn->server_info, snum, &werr)) {
+ errcode = NERR_Success;
+ }
+ break;
+ case 75: /* Resume queue */
+ if (print_queue_resume(conn->server_info, snum, &werr)) {
+ errcode = NERR_Success;
+ }
+ break;
+ case 103: /* Purge */
+ if (print_queue_purge(conn->server_info, snum, &werr)) {
+ errcode = NERR_Success;
+ }
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) errcode = W_ERROR_V(werr);
+
+ out:
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ set the property of a print job (undocumented?)
+ ? function = 0xb -> set name of print job
+ ? function = 0x6 -> move print job up/down
+ Form: <WWsTP> <WWzWWDDzzzzzzzzzzlz>
+ or <WWsTP> <WB21BB16B10zWWzDDz>
+****************************************************************************/
+
+static int check_printjob_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0: desc->format = "W"; break;
+ case 1: desc->format = "WB21BB16B10zWWzDDz"; break;
+ case 2: desc->format = "WWzWWDDzz"; break;
+ case 3: desc->format = "WWzWWDDzzzzzzzzzzlz"; break;
+ case 4: desc->format = "WWzWWDDzzzzzDDDDDDD"; break;
+ default:
+ DEBUG(0,("check_printjob_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printjob_info: invalid format %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static bool api_PrintJobInfo(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct pack_desc desc;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32 jobid;
+ fstring sharename;
+ int uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+ int function = get_safe_SVAL(param,tpscnt,p,4,-1);
+ int place, errcode;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ if (!share_defined(sharename)) {
+ DEBUG(0,("api_PrintJobInfo: sharen [%s] not defined\n",
+ sharename));
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ /* check it's a supported varient */
+ if ((strcmp(str1,"WWsTP")) ||
+ (!check_printjob_info(&desc,uLevel,str2)))
+ return(False);
+
+ if (!print_job_exists(sharename, jobid)) {
+ errcode=NERR_JobNotFound;
+ goto out;
+ }
+
+ errcode = NERR_notsupported;
+
+ switch (function) {
+ case 0x6:
+ /* change job place in the queue,
+ data gives the new place */
+ place = SVAL(data,0);
+ if (print_job_set_place(sharename, jobid, place)) {
+ errcode=NERR_Success;
+ }
+ break;
+
+ case 0xb:
+ /* change print job name, data gives the name */
+ if (print_job_set_name(sharename, jobid, data)) {
+ errcode=NERR_Success;
+ }
+ break;
+
+ default:
+ return False;
+ }
+
+ out:
+ SSVALS(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_RNetServerGetInfo(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ int struct_len;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetServerGetInfo level %d\n",uLevel));
+
+ /* check it's a supported varient */
+ if (!prefix_ok(str1,"WrLh")) {
+ return False;
+ }
+
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(str2,"B16") != 0) {
+ return False;
+ }
+ struct_len = 16;
+ break;
+ case 1:
+ if (strcmp(str2,"B16BBDz") != 0) {
+ return False;
+ }
+ struct_len = 26;
+ break;
+ case 2:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWz")!= 0) {
+ return False;
+ }
+ struct_len = 134;
+ break;
+ case 3:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWzDWz") != 0) {
+ return False;
+ }
+ struct_len = 144;
+ break;
+ case 20:
+ if (strcmp(str2,"DN") != 0) {
+ return False;
+ }
+ struct_len = 6;
+ break;
+ case 50:
+ if (strcmp(str2,"B16BBDzWWzzz") != 0) {
+ return False;
+ }
+ struct_len = 42;
+ break;
+ default:
+ return False;
+ }
+
+ *rdata_len = mdrcnt;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ p2 = p + struct_len;
+ if (uLevel != 20) {
+ srvstr_push(NULL, 0, p,global_myname(),16,
+ STR_ASCII|STR_UPPER|STR_TERMINATE);
+ }
+ p += 16;
+ if (uLevel > 0) {
+ struct srv_info_struct *servers=NULL;
+ int i,count;
+ char *comment = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ uint32 servertype= lp_default_server_announce();
+
+ comment = talloc_strdup(ctx,lp_serverstring());
+ if (!comment) {
+ return false;
+ }
+
+ if ((count=get_server_info(SV_TYPE_ALL,&servers,lp_workgroup()))>0) {
+ for (i=0;i<count;i++) {
+ if (strequal(servers[i].name,global_myname())) {
+ servertype = servers[i].type;
+ TALLOC_FREE(comment);
+ comment = talloc_strdup(ctx,
+ servers[i].comment);
+ if (comment) {
+ return false;
+ }
+ }
+ }
+ }
+
+ SAFE_FREE(servers);
+
+ SCVAL(p,0,lp_major_announce_version());
+ SCVAL(p,1,lp_minor_announce_version());
+ SIVAL(p,2,servertype);
+
+ if (mdrcnt == struct_len) {
+ SIVAL(p,6,0);
+ } else {
+ SIVAL(p,6,PTR_DIFF(p2,*rdata));
+ comment = talloc_sub_advanced(
+ ctx,
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ comment);
+ if (comment) {
+ return false;
+ }
+ if (mdrcnt - struct_len <= 0) {
+ return false;
+ }
+ push_ascii(p2,
+ comment,
+ MIN(mdrcnt - struct_len,
+ MAX_SERVER_STRING_LENGTH),
+ STR_TERMINATE);
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+ }
+
+ if (uLevel > 1) {
+ return False; /* not yet implemented */
+ }
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_NetWkstaGetInfo(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *p2;
+ char *endp;
+ int level = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetWkstaGetInfo level %d\n",level));
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported varient */
+ if (!(level==10 && strcsequal(str1,"WrLh") && strcsequal(str2,"zzzBBzz"))) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,22);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* host name */
+ strlcpy(p2,get_local_machine_name(),PTR_DIFF(endp,p2));
+ strupper_m(p2);
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,conn->server_info->sanitized_username,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* login domain */
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2));
+ strupper_m(p2);
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SCVAL(p,0,lp_major_announce_version()); /* system version - e.g 4 in 4.1 */
+ SCVAL(p,1,lp_minor_announce_version()); /* system version - e.g .1 in 4.1 */
+ p += 2;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2)); /* don't know. login domain?? */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* don't know */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a user
+
+ struct user_info_11 {
+ char usri11_name[21]; 0-20
+ char usri11_pad; 21
+ char *usri11_comment; 22-25
+ char *usri11_usr_comment; 26-29
+ unsigned short usri11_priv; 30-31
+ unsigned long usri11_auth_flags; 32-35
+ long usri11_password_age; 36-39
+ char *usri11_homedir; 40-43
+ char *usri11_parms; 44-47
+ long usri11_last_logon; 48-51
+ long usri11_last_logoff; 52-55
+ unsigned short usri11_bad_pw_count; 56-57
+ unsigned short usri11_num_logons; 58-59
+ char *usri11_logon_server; 60-63
+ unsigned short usri11_country_code; 64-65
+ char *usri11_workstations; 66-69
+ unsigned long usri11_max_storage; 70-73
+ unsigned short usri11_units_per_week; 74-75
+ unsigned char *usri11_logon_hours; 76-79
+ unsigned short usri11_code_page; 80-81
+ };
+
+where:
+
+ usri11_name specifies the user name for which information is retrieved
+
+ usri11_pad aligns the next data structure element to a word boundary
+
+ usri11_comment is a null terminated ASCII comment
+
+ usri11_user_comment is a null terminated ASCII comment about the user
+
+ usri11_priv specifies the level of the privilege assigned to the user.
+ The possible values are:
+
+Name Value Description
+USER_PRIV_GUEST 0 Guest privilege
+USER_PRIV_USER 1 User privilege
+USER_PRV_ADMIN 2 Administrator privilege
+
+ usri11_auth_flags specifies the account operator privileges. The
+ possible values are:
+
+Name Value Description
+AF_OP_PRINT 0 Print operator
+
+
+Leach, Naik [Page 28]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+AF_OP_COMM 1 Communications operator
+AF_OP_SERVER 2 Server operator
+AF_OP_ACCOUNTS 3 Accounts operator
+
+
+ usri11_password_age specifies how many seconds have elapsed since the
+ password was last changed.
+
+ usri11_home_dir points to a null terminated ASCII string that contains
+ the path name of the user's home directory.
+
+ usri11_parms points to a null terminated ASCII string that is set
+ aside for use by applications.
+
+ usri11_last_logon specifies the time when the user last logged on.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970.
+
+ usri11_last_logoff specifies the time when the user last logged off.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970. A value of 0 means the last logoff
+ time is unknown.
+
+ usri11_bad_pw_count specifies the number of incorrect passwords
+ entered since the last successful logon.
+
+ usri11_log1_num_logons specifies the number of times this user has
+ logged on. A value of -1 means the number of logons is unknown.
+
+ usri11_logon_server points to a null terminated ASCII string that
+ contains the name of the server to which logon requests are sent.
+ A null string indicates logon requests should be sent to the
+ domain controller.
+
+ usri11_country_code specifies the country code for the user's language
+ of choice.
+
+ usri11_workstations points to a null terminated ASCII string that
+ contains the names of workstations the user may log on from.
+ There may be up to 8 workstations, with the names separated by
+ commas. A null strings indicates there are no restrictions.
+
+ usri11_max_storage specifies the maximum amount of disk space the user
+ can occupy. A value of 0xffffffff indicates there are no
+ restrictions.
+
+ usri11_units_per_week specifies the equal number of time units into
+ which a week is divided. This value must be equal to 168.
+
+ usri11_logon_hours points to a 21 byte (168 bits) string that
+ specifies the time during which the user can log on. Each bit
+ represents one unique hour in a week. The first bit (bit 0, word
+ 0) is Sunday, 0:00 to 0:59, the second bit (bit 1, word 0) is
+
+
+
+Leach, Naik [Page 29]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+ Sunday, 1:00 to 1:59 and so on. A null pointer indicates there
+ are no restrictions.
+
+ usri11_code_page specifies the code page for the user's language of
+ choice
+
+All of the pointers in this data structure need to be treated
+specially. The pointer is a 32 bit pointer. The higher 16 bits need
+to be ignored. The converter word returned in the parameters section
+needs to be subtracted from the lower 16 bits to calculate an offset
+into the return buffer where this ASCII string resides.
+
+There is no auxiliary data in the response.
+
+ ****************************************************************************/
+
+#define usri11_name 0
+#define usri11_pad 21
+#define usri11_comment 22
+#define usri11_usr_comment 26
+#define usri11_full_name 30
+#define usri11_priv 34
+#define usri11_auth_flags 36
+#define usri11_password_age 40
+#define usri11_homedir 44
+#define usri11_parms 48
+#define usri11_last_logon 52
+#define usri11_last_logoff 56
+#define usri11_bad_pw_count 60
+#define usri11_num_logons 62
+#define usri11_logon_server 64
+#define usri11_country_code 68
+#define usri11_workstations 70
+#define usri11_max_storage 74
+#define usri11_units_per_week 78
+#define usri11_logon_hours 80
+#define usri11_code_page 84
+#define usri11_end 86
+
+#define USER_PRIV_GUEST 0
+#define USER_PRIV_USER 1
+#define USER_PRIV_ADMIN 2
+
+#define AF_OP_PRINT 0
+#define AF_OP_COMM 1
+#define AF_OP_SERVER 2
+#define AF_OP_ACCOUNTS 3
+
+
+static bool api_RNetUserGetInfo(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ char *endp;
+ const char *level_string;
+
+ /* get NIS home of a previously validated user - simeon */
+ /* With share level security vuid will always be zero.
+ Don't depend on vuser being non-null !!. JRA */
+ user_struct *vuser = get_valid_user_struct(vuid);
+ if(vuser != NULL) {
+ DEBUG(3,(" Username of UID %d is %s\n",
+ (int)vuser->server_info->utok.uid,
+ vuser->server_info->unix_name));
+ }
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ DEBUG(4,("RNetUserGetInfo level=%d\n", uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ switch( uLevel ) {
+ case 0: level_string = "B21"; break;
+ case 1: level_string = "B21BB16DWzzWz"; break;
+ case 2: level_string = "B21BB16DWzzWzDzzzzDDDDWb21WWzWW"; break;
+ case 10: level_string = "B21Bzzz"; break;
+ case 11: level_string = "B21BzzzWDDzzDDWWzWzDWb21W"; break;
+ default: return False;
+ }
+
+ if (strcmp(level_string,str2) != 0) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,usri11_end);
+ if (!p2) {
+ return False;
+ }
+
+ memset(p,0,21);
+ fstrcpy(p+usri11_name,UserName); /* 21 bytes - user name */
+
+ if (uLevel > 0) {
+ SCVAL(p,usri11_pad,0); /* padding - 1 byte */
+ *p2 = 0;
+ }
+
+ if (uLevel >= 10) {
+ SIVAL(p,usri11_comment,PTR_DIFF(p2,p)); /* comment */
+ strlcpy(p2,"Comment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,usri11_usr_comment,PTR_DIFF(p2,p)); /* user_comment */
+ strlcpy(p2,"UserComment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ /* EEK! the cifsrap.txt doesn't have this in!!!! */
+ SIVAL(p,usri11_full_name,PTR_DIFF(p2,p)); /* full name */
+ strlcpy(p2,((vuser != NULL)
+ ? pdb_get_fullname(vuser->server_info->sam_account)
+ : UserName),PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+
+ if (uLevel == 11) {
+ const char *homedir = "";
+ if (vuser != NULL) {
+ homedir = pdb_get_homedir(
+ vuser->server_info->sam_account);
+ }
+ /* modelled after NTAS 3.51 reply */
+ SSVAL(p,usri11_priv,conn->admin_user?USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,usri11_auth_flags,AF_OP_PRINT); /* auth flags */
+ SIVALS(p,usri11_password_age,-1); /* password age */
+ SIVAL(p,usri11_homedir,PTR_DIFF(p2,p)); /* home dir */
+ strlcpy(p2, homedir, PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_parms,PTR_DIFF(p2,p)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_last_logon,0); /* last logon */
+ SIVAL(p,usri11_last_logoff,0); /* last logoff */
+ SSVALS(p,usri11_bad_pw_count,-1); /* bad pw counts */
+ SSVALS(p,usri11_num_logons,-1); /* num logons */
+ SIVAL(p,usri11_logon_server,PTR_DIFF(p2,p)); /* logon server */
+ strlcpy(p2,"\\\\*",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,usri11_country_code,0); /* country code */
+
+ SIVAL(p,usri11_workstations,PTR_DIFF(p2,p)); /* workstations */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVALS(p,usri11_max_storage,-1); /* max storage */
+ SSVAL(p,usri11_units_per_week,168); /* units per week */
+ SIVAL(p,usri11_logon_hours,PTR_DIFF(p2,p)); /* logon hours */
+
+ /* a simple way to get logon hours at all times. */
+ memset(p2,0xff,21);
+ SCVAL(p2,21,0); /* fix zero termination */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SSVAL(p,usri11_code_page,0); /* code page */
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ memset(p+22,' ',16); /* password */
+ SIVALS(p,38,-1); /* password age */
+ SSVAL(p,42,
+ conn->admin_user?USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,44,PTR_DIFF(p2,*rdata)); /* home dir */
+ strlcpy(p2, vuser ? pdb_get_homedir(
+ vuser->server_info->sam_account) : "",
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,48,PTR_DIFF(p2,*rdata)); /* comment */
+ *p2++ = 0;
+ SSVAL(p,52,0); /* flags */
+ SIVAL(p,54,PTR_DIFF(p2,*rdata)); /* script_path */
+ strlcpy(p2, vuser ? pdb_get_logon_script(
+ vuser->server_info->sam_account) : "",
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ if (uLevel == 2) {
+ SIVAL(p,60,0); /* auth_flags */
+ SIVAL(p,64,PTR_DIFF(p2,*rdata)); /* full_name */
+ strlcpy(p2,((vuser != NULL)
+ ? pdb_get_fullname(vuser->server_info->sam_account)
+ : UserName),PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,68,0); /* urs_comment */
+ SIVAL(p,72,PTR_DIFF(p2,*rdata)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,76,0); /* workstations */
+ SIVAL(p,80,0); /* last_logon */
+ SIVAL(p,84,0); /* last_logoff */
+ SIVALS(p,88,-1); /* acct_expires */
+ SIVALS(p,92,-1); /* max_storage */
+ SSVAL(p,96,168); /* units_per_week */
+ SIVAL(p,98,PTR_DIFF(p2,*rdata)); /* logon_hours */
+ memset(p2,-1,21);
+ p2 += 21;
+ SSVALS(p,102,-1); /* bad_pw_count */
+ SSVALS(p,104,-1); /* num_logons */
+ SIVAL(p,106,PTR_DIFF(p2,*rdata)); /* logon_server */
+ {
+ TALLOC_CTX *ctx = talloc_tos();
+ int space_rem = *rdata_len - (p2 - *rdata);
+ char *tmp;
+
+ if (space_rem <= 0) {
+ return false;
+ }
+ tmp = talloc_strdup(ctx, "\\\\%L");
+ if (!tmp) {
+ return false;
+ }
+ tmp = talloc_sub_basic(ctx,
+ "",
+ "",
+ tmp);
+ if (!tmp) {
+ return false;
+ }
+
+ push_ascii(p2,
+ tmp,
+ space_rem,
+ STR_TERMINATE);
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,110,49); /* country_code */
+ SSVAL(p,112,860); /* code page */
+ }
+ }
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ SSVAL(*rparam,4,*rdata_len); /* is this right?? */
+
+ return(True);
+}
+
+static bool api_WWkstaUserLogon(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ char* name;
+ /* With share level security vuid will always be zero.
+ Don't depend on vuser being non-null !!. JRA */
+ user_struct *vuser = get_valid_user_struct(vuid);
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if(vuser != NULL) {
+ DEBUG(3,(" Username of UID %d is %s\n",
+ (int)vuser->server_info->utok.uid,
+ vuser->server_info->unix_name));
+ }
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ name = get_safe_str_ptr(param,tpscnt,p,2);
+ if (!name) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("WWkstaUserLogon uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"OOWb54WrLh") != 0) {
+ return False;
+ }
+ if (uLevel != 1 || strcmp(str2,"WB21BWDWWDDDDDDDzzzD") != 0) {
+ return False;
+ }
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.subformat = NULL;
+ desc.format = str2;
+
+ if (init_package(&desc,1,0)) {
+ PACKI(&desc,"W",0); /* code */
+ PACKS(&desc,"B21",name); /* eff. name */
+ PACKS(&desc,"B",""); /* pad */
+ PACKI(&desc,"W", conn->admin_user?USER_PRIV_ADMIN:USER_PRIV_USER);
+ PACKI(&desc,"D",0); /* auth flags XXX */
+ PACKI(&desc,"W",0); /* num logons */
+ PACKI(&desc,"W",0); /* bad pw count */
+ PACKI(&desc,"D",0); /* last logon */
+ PACKI(&desc,"D",-1); /* last logoff */
+ PACKI(&desc,"D",-1); /* logoff time */
+ PACKI(&desc,"D",-1); /* kickoff time */
+ PACKI(&desc,"D",0); /* password age */
+ PACKI(&desc,"D",0); /* password can change */
+ PACKI(&desc,"D",-1); /* password must change */
+
+ {
+ fstring mypath;
+ fstrcpy(mypath,"\\\\");
+ fstrcat(mypath,get_local_machine_name());
+ strupper_m(mypath);
+ PACKS(&desc,"z",mypath); /* computer */
+ }
+
+ PACKS(&desc,"z",lp_workgroup());/* domain */
+ PACKS(&desc,"z", vuser ? pdb_get_logon_script(
+ vuser->server_info->sam_account) : ""); /* script path */
+ PACKI(&desc,"D",0x00000000); /* reserved */
+ }
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WWkstaUserLogon: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ api_WAccessGetUserPerms
+****************************************************************************/
+
+static bool api_WAccessGetUserPerms(connection_struct *conn,uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *user = skip_string(param,tpscnt,str2);
+ char *resource = skip_string(param,tpscnt,user);
+
+ if (!str1 || !str2 || !user || !resource) {
+ return False;
+ }
+
+ if (skip_string(param,tpscnt,resource) == NULL) {
+ return False;
+ }
+ DEBUG(3,("WAccessGetUserPerms user=%s resource=%s\n",user,resource));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"zzh") != 0) {
+ return False;
+ }
+ if (strcmp(str2,"") != 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,0); /* errorcode */
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,0x7f); /* permission flags */
+
+ return True;
+}
+
+/****************************************************************************
+ api_WPrintJobEnumerate
+ ****************************************************************************/
+
+static bool api_WPrintJobGetInfo(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int count;
+ int i;
+ int snum;
+ fstring sharename;
+ uint32 jobid;
+ struct pack_desc desc;
+ print_queue_struct *queue=NULL;
+ print_status_struct status;
+ char *tmpdata=NULL;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+
+ memset((char *)&desc,'\0',sizeof(desc));
+ memset((char *)&status,'\0',sizeof(status));
+
+ DEBUG(3,("WPrintJobGetInfo uLevel=%d uJobId=0x%X\n",uLevel,SVAL(p,0)));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"WWrLh") != 0) {
+ return False;
+ }
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid)) {
+ return False;
+ }
+
+ snum = lp_servicenumber( sharename);
+ if (snum < 0 || !VALID_SNUM(snum)) {
+ return(False);
+ }
+
+ count = print_queue_status(snum,&queue,&status);
+ for (i = 0; i < count; i++) {
+ if (queue[i].job == jobid) {
+ break;
+ }
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+
+ if (init_package(&desc,1,0)) {
+ if (i < count) {
+ fill_printjob_info(conn,snum,uLevel,&desc,&queue[i],i);
+ *rdata_len = desc.usedlen;
+ } else {
+ desc.errcode = NERR_JobNotFound;
+ *rdata_len = 0;
+ }
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ SAFE_FREE(queue);
+ SAFE_FREE(tmpdata);
+
+ DEBUG(4,("WPrintJobGetInfo: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintJobEnumerate(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *name = p;
+ int uLevel;
+ int count;
+ int i, succnt=0;
+ int snum;
+ struct pack_desc desc;
+ print_queue_struct *queue=NULL;
+ print_status_struct status;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+ memset((char *)&status,'\0',sizeof(status));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintJobEnumerate uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLeh") != 0) {
+ return False;
+ }
+
+ if (uLevel > 2) {
+ return False; /* defined only for uLevel 0,1,2 */
+ }
+
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ snum = find_service(name);
+ if ( !(lp_snum_ok(snum) && lp_print_ok(snum)) ) {
+ return False;
+ }
+
+ count = print_queue_status(snum,&queue,&status);
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ if (init_package(&desc,count,0)) {
+ succnt = 0;
+ for (i = 0; i < count; i++) {
+ fill_printjob_info(conn,snum,uLevel,&desc,&queue[i],i);
+ if (desc.errcode == NERR_Success) {
+ succnt = i+1;
+ }
+ }
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,count);
+
+ SAFE_FREE(queue);
+
+ DEBUG(4,("WPrintJobEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static int check_printdest_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B9";
+ break;
+ case 1:
+ desc->format = "B9B21WWzW";
+ break;
+ case 2:
+ desc->format = "z";
+ break;
+ case 3:
+ desc->format = "zzzWWzzzWW";
+ break;
+ default:
+ DEBUG(0,("check_printdest_info: invalid level %d\n",
+ uLevel));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printdest_info: invalid string %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static void fill_printdest_info(connection_struct *conn, int snum, int uLevel,
+ struct pack_desc* desc)
+{
+ char buf[100];
+
+ strncpy(buf,SERVICE(snum),sizeof(buf)-1);
+ buf[sizeof(buf)-1] = 0;
+ strupper_m(buf);
+
+ if (uLevel <= 1) {
+ PACKS(desc,"B9",buf); /* szName */
+ if (uLevel == 1) {
+ PACKS(desc,"B21",""); /* szUserName */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"W",0); /* time */
+ }
+ }
+
+ if (uLevel == 2 || uLevel == 3) {
+ PACKS(desc,"z",buf); /* pszPrinterName */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszUserName */
+ PACKS(desc,"z",""); /* pszLogAddr */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z",""); /* pszComment */
+ PACKS(desc,"z","NULL"); /* pszDrivers */
+ PACKI(desc,"W",0); /* time */
+ PACKI(desc,"W",0); /* pad1 */
+ }
+ }
+}
+
+static bool api_WPrintDestGetInfo(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char* PrinterName = p;
+ int uLevel;
+ struct pack_desc desc;
+ int snum;
+ char *tmpdata=NULL;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestGetInfo uLevel=%d PrinterName=%s\n",uLevel,PrinterName));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ snum = find_service(PrinterName);
+ if ( !(lp_snum_ok(snum) && lp_print_ok(snum)) ) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ } else {
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+ if (init_package(&desc,1,0)) {
+ fill_printdest_info(conn,snum,uLevel,&desc);
+ }
+ *rdata_len = desc.usedlen;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WPrintDestGetInfo: errorcode %d\n",desc.errcode));
+ SAFE_FREE(tmpdata);
+
+ return True;
+}
+
+static bool api_WPrintDestEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int queuecnt;
+ int i, n, succnt=0;
+ struct pack_desc desc;
+ int services = lp_numservices();
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ queuecnt = 0;
+ for (i = 0; i < services; i++) {
+ if (lp_snum_ok(i) && lp_print_ok(i) && lp_browseable(i)) {
+ queuecnt++;
+ }
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,queuecnt,0)) {
+ succnt = 0;
+ n = 0;
+ for (i = 0; i < services; i++) {
+ if (lp_snum_ok(i) && lp_print_ok(i) && lp_browseable(i)) {
+ fill_printdest_info(conn,i,uLevel,&desc);
+ n++;
+ if (desc.errcode == NERR_Success) {
+ succnt = n;
+ }
+ }
+ }
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ DEBUG(4,("WPrintDestEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintDriverEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDriverEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B41") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B41","NULL");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintDriverEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintQProcEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintQProcEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B13") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lpd");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintQProcEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintPortEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintPortEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B9") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lp0");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintPortEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ List open sessions
+ ****************************************************************************/
+
+static bool api_RNetSessionEnum(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ struct sessionid *session_list;
+ int i, num_sessions;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("RNetSessionEnum uLevel=%d\n",uLevel));
+ DEBUG(7,("RNetSessionEnum req string=%s\n",str1));
+ DEBUG(7,("RNetSessionEnum ret string=%s\n",str2));
+
+ /* check it's a supported varient */
+ if (strcmp(str1,RAP_NetSessionEnum_REQ) != 0) {
+ return False;
+ }
+ if (uLevel != 2 || strcmp(str2,RAP_SESSION_INFO_L2) != 0) {
+ return False;
+ }
+
+ num_sessions = list_sessions(talloc_tos(), &session_list);
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (!init_package(&desc,num_sessions,0)) {
+ return False;
+ }
+
+ for(i=0; i<num_sessions; i++) {
+ PACKS(&desc, "z", session_list[i].remote_machine);
+ PACKS(&desc, "z", session_list[i].username);
+ PACKI(&desc, "W", 1); /* num conns */
+ PACKI(&desc, "W", 0); /* num opens */
+ PACKI(&desc, "W", 1); /* num users */
+ PACKI(&desc, "D", 0); /* session time */
+ PACKI(&desc, "D", 0); /* idle time */
+ PACKI(&desc, "D", 0); /* flags */
+ PACKS(&desc, "z", "Unknown Client"); /* client type string */
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0); /* converter */
+ SSVAL(*rparam,4,num_sessions); /* count */
+
+ DEBUG(4,("RNetSessionEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+
+/****************************************************************************
+ The buffer was too small.
+ ****************************************************************************/
+
+static bool api_TooSmall(connection_struct *conn,uint16 vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = MIN(*rparam_len,mprcnt);
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_BufTooSmall);
+
+ DEBUG(3,("Supplied buffer too small in API command\n"));
+
+ return True;
+}
+
+/****************************************************************************
+ The request is not supported.
+ ****************************************************************************/
+
+static bool api_Unsupported(connection_struct *conn, uint16 vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_notsupported);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API command\n"));
+
+ return True;
+}
+
+static const struct {
+ const char *name;
+ int id;
+ bool (*fn)(connection_struct *, uint16,
+ char *, int,
+ char *, int,
+ int,int,char **,char **,int *,int *);
+ bool auth_user; /* Deny anonymous access? */
+} api_commands[] = {
+ {"RNetShareEnum", RAP_WshareEnum, api_RNetShareEnum, True},
+ {"RNetShareGetInfo", RAP_WshareGetInfo, api_RNetShareGetInfo},
+ {"RNetShareAdd", RAP_WshareAdd, api_RNetShareAdd},
+ {"RNetSessionEnum", RAP_WsessionEnum, api_RNetSessionEnum, True},
+ {"RNetServerGetInfo", RAP_WserverGetInfo, api_RNetServerGetInfo},
+ {"RNetGroupEnum", RAP_WGroupEnum, api_RNetGroupEnum, True},
+ {"RNetGroupGetUsers", RAP_WGroupGetUsers, api_RNetGroupGetUsers, True},
+ {"RNetUserEnum", RAP_WUserEnum, api_RNetUserEnum, True},
+ {"RNetUserGetInfo", RAP_WUserGetInfo, api_RNetUserGetInfo},
+ {"NetUserGetGroups", RAP_WUserGetGroups, api_NetUserGetGroups},
+ {"NetWkstaGetInfo", RAP_WWkstaGetInfo, api_NetWkstaGetInfo},
+ {"DosPrintQEnum", RAP_WPrintQEnum, api_DosPrintQEnum, True},
+ {"DosPrintQGetInfo", RAP_WPrintQGetInfo, api_DosPrintQGetInfo},
+ {"WPrintQueuePause", RAP_WPrintQPause, api_WPrintQueueCtrl},
+ {"WPrintQueueResume", RAP_WPrintQContinue, api_WPrintQueueCtrl},
+ {"WPrintJobEnumerate",RAP_WPrintJobEnum, api_WPrintJobEnumerate},
+ {"WPrintJobGetInfo", RAP_WPrintJobGetInfo, api_WPrintJobGetInfo},
+ {"RDosPrintJobDel", RAP_WPrintJobDel, api_RDosPrintJobDel},
+ {"RDosPrintJobPause", RAP_WPrintJobPause, api_RDosPrintJobDel},
+ {"RDosPrintJobResume",RAP_WPrintJobContinue, api_RDosPrintJobDel},
+ {"WPrintDestEnum", RAP_WPrintDestEnum, api_WPrintDestEnum},
+ {"WPrintDestGetInfo", RAP_WPrintDestGetInfo, api_WPrintDestGetInfo},
+ {"NetRemoteTOD", RAP_NetRemoteTOD, api_NetRemoteTOD},
+ {"WPrintQueuePurge", RAP_WPrintQPurge, api_WPrintQueueCtrl},
+ {"NetServerEnum", RAP_NetServerEnum2, api_RNetServerEnum}, /* anon OK */
+ {"WAccessGetUserPerms",RAP_WAccessGetUserPerms,api_WAccessGetUserPerms},
+ {"SetUserPassword", RAP_WUserPasswordSet2, api_SetUserPassword},
+ {"WWkstaUserLogon", RAP_WWkstaUserLogon, api_WWkstaUserLogon},
+ {"PrintJobInfo", RAP_WPrintJobSetInfo, api_PrintJobInfo},
+ {"WPrintDriverEnum", RAP_WPrintDriverEnum, api_WPrintDriverEnum},
+ {"WPrintQProcEnum", RAP_WPrintQProcessorEnum,api_WPrintQProcEnum},
+ {"WPrintPortEnum", RAP_WPrintPortEnum, api_WPrintPortEnum},
+ {"SamOEMChangePassword",RAP_SamOEMChgPasswordUser2_P,api_SamOEMChangePassword}, /* anon OK */
+ {NULL, -1, api_Unsupported}
+ /* The following RAP calls are not implemented by Samba:
+
+ RAP_WFileEnum2 - anon not OK
+ */
+};
+
+
+/****************************************************************************
+ Handle remote api calls.
+****************************************************************************/
+
+void api_reply(connection_struct *conn, uint16 vuid,
+ struct smb_request *req,
+ char *data, char *params,
+ int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ int api_command;
+ char *rdata = NULL;
+ char *rparam = NULL;
+ const char *name1 = NULL;
+ const char *name2 = NULL;
+ int rdata_len = 0;
+ int rparam_len = 0;
+ bool reply=False;
+ int i;
+
+ if (!params) {
+ DEBUG(0,("ERROR: NULL params in api_reply()\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (tpscnt < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ api_command = SVAL(params,0);
+ /* Is there a string at position params+2 ? */
+ if (skip_string(params,tpscnt,params+2)) {
+ name1 = params + 2;
+ } else {
+ name1 = "";
+ }
+ name2 = skip_string(params,tpscnt,params+2);
+ if (!name2) {
+ name2 = "";
+ }
+
+ DEBUG(3,("Got API command %d of form <%s> <%s> (tdscnt=%d,tpscnt=%d,mdrcnt=%d,mprcnt=%d)\n",
+ api_command,
+ name1,
+ name2,
+ tdscnt,tpscnt,mdrcnt,mprcnt));
+
+ for (i=0;api_commands[i].name;i++) {
+ if (api_commands[i].id == api_command && api_commands[i].fn) {
+ DEBUG(3,("Doing %s\n",api_commands[i].name));
+ break;
+ }
+ }
+
+ /* Check whether this api call can be done anonymously */
+
+ if (api_commands[i].auth_user && lp_restrict_anonymous()) {
+ user_struct *user = get_valid_user_struct(vuid);
+
+ if (!user || user->server_info->guest) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ rdata = (char *)SMB_MALLOC(1024);
+ if (rdata) {
+ memset(rdata,'\0',1024);
+ }
+
+ rparam = (char *)SMB_MALLOC(1024);
+ if (rparam) {
+ memset(rparam,'\0',1024);
+ }
+
+ if(!rdata || !rparam) {
+ DEBUG(0,("api_reply: malloc fail !\n"));
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ reply = api_commands[i].fn(conn,
+ vuid,
+ params,tpscnt, /* params + length */
+ data,tdscnt, /* data + length */
+ mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+
+
+ if (rdata_len > mdrcnt || rparam_len > mprcnt) {
+ reply = api_TooSmall(conn,vuid,params,data,mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* if we get False back then it's actually unsupported */
+ if (!reply) {
+ reply = api_Unsupported(conn,vuid,params,tpscnt,data,tdscnt,mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* If api_Unsupported returns false we can't return anything. */
+ if (reply) {
+ send_trans_reply(conn, req->inbuf, rparam, rparam_len,
+ rdata, rdata_len, False);
+ }
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return;
+}
diff --git a/source3/smbd/mangle.c b/source3/smbd/mangle.c
new file mode 100644
index 0000000000..360692c546
--- /dev/null
+++ b/source3/smbd/mangle.c
@@ -0,0 +1,152 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling interface
+ Copyright (C) Andrew Tridgell 2002
+
+ 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"
+
+static struct mangle_fns *mangle_fns;
+
+/* this allows us to add more mangling backends */
+static const struct {
+ const char *name;
+ struct mangle_fns *(*init_fn)(void);
+} mangle_backends[] = {
+ { "hash", mangle_hash_init },
+ { "hash2", mangle_hash2_init },
+ { "posix", posix_mangle_init },
+ /*{ "tdb", mangle_tdb_init }, */
+ { NULL, NULL }
+};
+
+/*
+ initialise the mangling subsystem
+*/
+static void mangle_init(void)
+{
+ int i;
+ const char *method;
+
+ if (mangle_fns)
+ return;
+
+ method = lp_mangling_method();
+
+ /* find the first mangling method that manages to initialise and
+ matches the "mangling method" parameter */
+ for (i=0; mangle_backends[i].name && !mangle_fns; i++) {
+ if (!method || !*method || strcmp(method, mangle_backends[i].name) == 0) {
+ mangle_fns = mangle_backends[i].init_fn();
+ }
+ }
+
+ if (!mangle_fns) {
+ DEBUG(0,("Failed to initialise mangling system '%s'\n", method));
+ exit_server("mangling init failed");
+ }
+}
+
+
+/*
+ reset the cache. This is called when smb.conf has been reloaded
+*/
+void mangle_reset_cache(void)
+{
+ mangle_init();
+ mangle_fns->reset();
+}
+
+void mangle_change_to_posix(void)
+{
+ mangle_fns = NULL;
+ lp_set_mangling_method("posix");
+ mangle_reset_cache();
+}
+
+/*
+ see if a filename has come out of our mangling code
+*/
+bool mangle_is_mangled(const char *s, const struct share_params *p)
+{
+ return mangle_fns->is_mangled(s, p);
+}
+
+/*
+ see if a filename matches the rules of a 8.3 filename
+*/
+bool mangle_is_8_3(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, False, p);
+}
+
+bool mangle_is_8_3_wildcards(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, True, p);
+}
+
+bool mangle_must_mangle(const char *fname,
+ const struct share_params *p)
+{
+ if (!lp_manglednames(p)) {
+ return False;
+ }
+ return mangle_fns->must_mangle(fname, p);
+}
+
+/*
+ try to reverse map a 8.3 name to the original filename. This doesn't have to
+ always succeed, as the directory handling code in smbd will scan the directory
+ looking for a matching name if it doesn't. It should succeed most of the time
+ or there will be a huge performance penalty
+*/
+bool mangle_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return mangle_fns->lookup_name_from_8_3(ctx, in, out, p);
+}
+
+/*
+ mangle a long filename to a 8.3 name.
+ Return True if we did mangle the name (ie. out is filled in).
+ False on error.
+ JRA.
+ */
+
+bool name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ const struct share_params *p)
+{
+ memset(out,'\0',13);
+
+ /* name mangling can be disabled for speed, in which case
+ we just truncate the string */
+ if (!lp_manglednames(p)) {
+ safe_strcpy(out,in,12);
+ return True;
+ }
+
+ return mangle_fns->name_to_8_3(in,
+ out,
+ cache83,
+ lp_defaultcase(p->service),
+ p);
+}
diff --git a/source3/smbd/mangle_hash.c b/source3/smbd/mangle_hash.c
new file mode 100644
index 0000000000..69ecf77834
--- /dev/null
+++ b/source3/smbd/mangle_hash.c
@@ -0,0 +1,695 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling
+ Copyright (C) Andrew Tridgell 1992-2002
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Jeremy Allison 2007
+
+ 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"
+
+/* -------------------------------------------------------------------------- **
+ * Other stuff...
+ *
+ * magic_char - This is the magic char used for mangling. It's
+ * global. There is a call to lp_magicchar() in server.c
+ * that is used to override the initial value.
+ *
+ * MANGLE_BASE - This is the number of characters we use for name mangling.
+ *
+ * basechars - The set characters used for name mangling. This
+ * is static (scope is this file only).
+ *
+ * mangle() - Macro used to select a character from basechars (i.e.,
+ * mangle(n) will return the nth digit, modulo MANGLE_BASE).
+ *
+ * chartest - array 0..255. The index range is the set of all possible
+ * values of a byte. For each byte value, the content is a
+ * two nibble pair. See BASECHAR_MASK below.
+ *
+ * ct_initialized - False until the chartest array has been initialized via
+ * a call to init_chartest().
+ *
+ * BASECHAR_MASK - Masks the upper nibble of a one-byte value.
+ *
+ * isbasecahr() - Given a character, check the chartest array to see
+ * if that character is in the basechars set. This is
+ * faster than using strchr_m().
+ *
+ */
+
+static char magic_char = '~';
+
+static const char basechars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%";
+#define MANGLE_BASE (sizeof(basechars)/sizeof(char)-1)
+
+static unsigned char *chartest;
+
+#define mangle(V) ((char)(basechars[(V) % MANGLE_BASE]))
+#define BASECHAR_MASK 0xf0
+#define isbasechar(C) ( (chartest[ ((C) & 0xff) ]) & BASECHAR_MASK )
+
+static TDB_CONTEXT *tdb_mangled_cache;
+
+/* -------------------------------------------------------------------- */
+
+static NTSTATUS has_valid_83_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!*s) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if(!isvalid83_w(*s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS has_illegal_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if (*s <= 0x1f) {
+ /* Control characters. */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ switch(*s) {
+ case UCS2_CHAR('\\'):
+ case UCS2_CHAR('/'):
+ case UCS2_CHAR('|'):
+ case UCS2_CHAR(':'):
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* return False if something fail and
+ * return 2 alloced unicode strings that contain prefix and extension
+ */
+
+static NTSTATUS mangle_get_prefix(const smb_ucs2_t *ucs2_string, smb_ucs2_t **prefix,
+ smb_ucs2_t **extension, bool allow_wildcards)
+{
+ size_t ext_len;
+ smb_ucs2_t *p;
+
+ *extension = 0;
+ *prefix = strdup_w(ucs2_string);
+ if (!*prefix) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((p = strrchr_w(*prefix, UCS2_CHAR('.')))) {
+ ext_len = strlen_w(p+1);
+ if ((ext_len > 0) && (ext_len < 4) && (p != *prefix) &&
+ (NT_STATUS_IS_OK(has_valid_83_chars(p+1,allow_wildcards)))) /* check extension */ {
+ *p = 0;
+ *extension = strdup_w(p+1);
+ if (!*extension) {
+ SAFE_FREE(*prefix);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/* ************************************************************************** **
+ * Return NT_STATUS_UNSUCCESSFUL if a name is a special msdos reserved name.
+ * or contains illegal characters.
+ *
+ * Input: fname - String containing the name to be tested.
+ *
+ * Output: NT_STATUS_UNSUCCESSFUL, if the condition above is true.
+ *
+ * Notes: This is a static function called by is_8_3(), below.
+ *
+ * ************************************************************************** **
+ */
+
+static NTSTATUS is_valid_name(const smb_ucs2_t *fname, bool allow_wildcards, bool only_8_3)
+{
+ smb_ucs2_t *str, *p;
+ size_t num_ucs2_chars;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ /* . and .. are valid names. */
+ if (strcmp_wa(fname, ".")==0 || strcmp_wa(fname, "..")==0)
+ return NT_STATUS_OK;
+
+ if (only_8_3) {
+ ret = has_valid_83_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+ }
+
+ ret = has_illegal_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ /* Name can't end in '.' or ' ' */
+ num_ucs2_chars = strlen_w(fname);
+ if (fname[num_ucs2_chars-1] == UCS2_CHAR('.') || fname[num_ucs2_chars-1] == UCS2_CHAR(' ')) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ str = strdup_w(fname);
+
+ /* Truncate copy after the first dot. */
+ p = strchr_w(str, UCS2_CHAR('.'));
+ if (p) {
+ *p = 0;
+ }
+
+ strupper_w(str);
+ p = &str[1];
+
+ switch(str[0])
+ {
+ case UCS2_CHAR('A'):
+ if(strcmp_wa(p, "UX") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('C'):
+ if((strcmp_wa(p, "LOCK$") == 0)
+ || (strcmp_wa(p, "ON") == 0)
+ || (strcmp_wa(p, "OM1") == 0)
+ || (strcmp_wa(p, "OM2") == 0)
+ || (strcmp_wa(p, "OM3") == 0)
+ || (strcmp_wa(p, "OM4") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('L'):
+ if((strcmp_wa(p, "PT1") == 0)
+ || (strcmp_wa(p, "PT2") == 0)
+ || (strcmp_wa(p, "PT3") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('N'):
+ if(strcmp_wa(p, "UL") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('P'):
+ if(strcmp_wa(p, "RN") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ default:
+ break;
+ }
+
+ SAFE_FREE(str);
+ return ret;
+}
+
+static NTSTATUS is_8_3_w(const smb_ucs2_t *fname, bool allow_wildcards)
+{
+ smb_ucs2_t *pref = 0, *ext = 0;
+ size_t plen;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ if (strlen_w(fname) > 12)
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (strcmp_wa(fname, ".") == 0 || strcmp_wa(fname, "..") == 0)
+ return NT_STATUS_OK;
+
+ /* Name cannot start with '.' */
+ if (*fname == UCS2_CHAR('.'))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (!NT_STATUS_IS_OK(is_valid_name(fname, allow_wildcards, True)))
+ goto done;
+
+ if (!NT_STATUS_IS_OK(mangle_get_prefix(fname, &pref, &ext, allow_wildcards)))
+ goto done;
+ plen = strlen_w(pref);
+
+ if (strchr_wa(pref, '.'))
+ goto done;
+ if (plen < 1 || plen > 8)
+ goto done;
+ if (ext && (strlen_w(ext) > 3))
+ goto done;
+
+ ret = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(pref);
+ SAFE_FREE(ext);
+ return ret;
+}
+
+static bool is_8_3(const char *fname, bool check_case, bool allow_wildcards,
+ const struct share_params *p)
+{
+ const char *f;
+ smb_ucs2_t *ucs2name;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ size_t size;
+
+ magic_char = lp_magicchar(p);
+
+ if (!fname || !*fname)
+ return False;
+ if ((f = strrchr(fname, '/')) == NULL)
+ f = fname;
+ else
+ f++;
+
+ if (strlen(f) > 12)
+ return False;
+
+ if (!push_ucs2_allocate(&ucs2name, f, &size)) {
+ DEBUG(0,("is_8_3: internal error push_ucs2_allocate() failed!\n"));
+ goto done;
+ }
+
+ ret = is_8_3_w(ucs2name, allow_wildcards);
+
+done:
+ SAFE_FREE(ucs2name);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ return False;
+ }
+
+ return True;
+}
+
+/* -------------------------------------------------------------------------- **
+ * Functions...
+ */
+
+/* ************************************************************************** **
+ * Initialize the static character test array.
+ *
+ * Input: none
+ *
+ * Output: none
+ *
+ * Notes: This function changes (loads) the contents of the <chartest>
+ * array. The scope of <chartest> is this file.
+ *
+ * ************************************************************************** **
+ */
+
+static void init_chartest( void )
+{
+ const unsigned char *s;
+
+ chartest = SMB_MALLOC_ARRAY(unsigned char, 256);
+
+ SMB_ASSERT(chartest != NULL);
+
+ for( s = (const unsigned char *)basechars; *s; s++ ) {
+ chartest[*s] |= BASECHAR_MASK;
+ }
+}
+
+/* ************************************************************************** **
+ * Return True if the name *could be* a mangled name.
+ *
+ * Input: s - A path name - in UNIX pathname format.
+ *
+ * Output: True if the name matches the pattern described below in the
+ * notes, else False.
+ *
+ * Notes: The input name is *not* tested for 8.3 compliance. This must be
+ * done separately. This function returns true if the name contains
+ * a magic character followed by excactly two characters from the
+ * basechars list (above), which in turn are followed either by the
+ * nul (end of string) byte or a dot (extension) or by a '/' (end of
+ * a directory name).
+ *
+ * ************************************************************************** **
+ */
+
+static bool is_mangled(const char *s, const struct share_params *p)
+{
+ char *magic;
+
+ magic_char = lp_magicchar(p);
+
+ if (chartest == NULL) {
+ init_chartest();
+ }
+
+ magic = strchr_m( s, magic_char );
+ while( magic && magic[1] && magic[2] ) { /* 3 chars, 1st is magic. */
+ if( ('.' == magic[3] || '/' == magic[3] || !(magic[3])) /* Ends with '.' or nul or '/' ? */
+ && isbasechar( toupper_ascii(magic[1]) ) /* is 2nd char basechar? */
+ && isbasechar( toupper_ascii(magic[2]) ) ) /* is 3rd char basechar? */
+ return( True ); /* If all above, then true, */
+ magic = strchr_m( magic+1, magic_char ); /* else seek next magic. */
+ }
+ return( False );
+}
+
+/***************************************************************************
+ Initializes or clears the mangled cache.
+***************************************************************************/
+
+static void mangle_reset( void )
+{
+ /* We could close and re-open the tdb here... should we ? The old code did
+ the equivalent... JRA. */
+}
+
+/***************************************************************************
+ Add a mangled name into the cache.
+ If the extension of the raw name maps directly to the
+ extension of the mangled name, then we'll store both names
+ *without* extensions. That way, we can provide consistent
+ reverse mangling for all names that match. The test here is
+ a bit more careful than the one done in earlier versions of
+ mangle.c:
+
+ - the extension must exist on the raw name,
+ - it must be all lower case
+ - it must match the mangled extension (to prove that no
+ mangling occurred).
+ crh 07-Apr-1998
+**************************************************************************/
+
+static void cache_mangled_name( const char mangled_name[13],
+ const char *raw_name )
+{
+ TDB_DATA data_val;
+ char mangled_name_key[13];
+ char *s1;
+ char *s2;
+
+ /* If the cache isn't initialized, give up. */
+ if( !tdb_mangled_cache )
+ return;
+
+ /* Init the string lengths. */
+ safe_strcpy(mangled_name_key, mangled_name, sizeof(mangled_name_key)-1);
+
+ /* See if the extensions are unmangled. If so, store the entry
+ * without the extension, thus creating a "group" reverse map.
+ */
+ s1 = strrchr( mangled_name_key, '.' );
+ if( s1 && (s2 = strrchr( raw_name, '.' )) ) {
+ size_t i = 1;
+ while( s1[i] && (tolower_ascii( s1[i] ) == s2[i]) )
+ i++;
+ if( !s1[i] && !s2[i] ) {
+ /* Truncate at the '.' */
+ *s1 = '\0';
+ *s2 = '\0';
+ }
+ }
+
+ /* Allocate a new cache entry. If the allocation fails, just return. */
+ data_val = string_term_tdb_data(raw_name);
+ if (tdb_store_bystring(tdb_mangled_cache, mangled_name_key, data_val, TDB_REPLACE) != 0) {
+ DEBUG(0,("cache_mangled_name: Error storing entry %s -> %s\n", mangled_name_key, raw_name));
+ } else {
+ DEBUG(5,("cache_mangled_name: Stored entry %s -> %s\n", mangled_name_key, raw_name));
+ }
+}
+
+/* ************************************************************************** **
+ * Check for a name on the mangled name stack
+ *
+ * Input: s - Input *and* output string buffer.
+ * maxlen - space in i/o string buffer.
+ * Output: True if the name was found in the cache, else False.
+ *
+ * Notes: If a reverse map is found, the function will overwrite the string
+ * space indicated by the input pointer <s>. This is frightening.
+ * It should be rewritten to return NULL if the long name was not
+ * found, and a pointer to the long name if it was found.
+ *
+ * ************************************************************************** **
+ */
+
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ TDB_DATA data_val;
+ char *saved_ext = NULL;
+ char *s = talloc_strdup(ctx, in);
+
+ magic_char = lp_magicchar(p);
+
+ /* If the cache isn't initialized, give up. */
+ if(!s || !tdb_mangled_cache ) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+
+ /* If we didn't find the name *with* the extension, try without. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ char *ext_start = strrchr( s, '.' );
+ if( ext_start ) {
+ if((saved_ext = talloc_strdup(ctx,ext_start)) == NULL) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ *ext_start = '\0';
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+ /*
+ * At this point s is the name without the
+ * extension. We re-add the extension if saved_ext
+ * is not null, before freeing saved_ext.
+ */
+ }
+ }
+
+ /* Okay, if we haven't found it we're done. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ TALLOC_FREE(saved_ext);
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ /* If we *did* find it, we need to talloc it on the given ctx. */
+ if (saved_ext) {
+ *out = talloc_asprintf(ctx, "%s%s",
+ (char *)data_val.dptr,
+ saved_ext);
+ } else {
+ *out = talloc_strdup(ctx, (char *)data_val.dptr);
+ }
+
+ TALLOC_FREE(s);
+ TALLOC_FREE(saved_ext);
+ SAFE_FREE(data_val.dptr);
+
+ return *out ? True : False;
+}
+
+/*****************************************************************************
+ Do the actual mangling to 8.3 format.
+*****************************************************************************/
+
+static bool to_8_3(const char *in, char out[13], int default_case)
+{
+ int csum;
+ char *p;
+ char extension[4];
+ char base[9];
+ int baselen = 0;
+ int extlen = 0;
+ char *s = SMB_STRDUP(in);
+
+ extension[0] = 0;
+ base[0] = 0;
+
+ if (!s) {
+ return False;
+ }
+
+ p = strrchr(s,'.');
+ if( p && (strlen(p+1) < (size_t)4) ) {
+ bool all_normal = ( strisnormal(p+1, default_case) ); /* XXXXXXXXX */
+
+ if( all_normal && p[1] != 0 ) {
+ *p = 0;
+ csum = str_checksum( s );
+ *p = '.';
+ } else
+ csum = str_checksum(s);
+ } else
+ csum = str_checksum(s);
+
+ strupper_m( s );
+
+ if( p ) {
+ if( p == s )
+ safe_strcpy( extension, "___", 3 );
+ else {
+ *p++ = 0;
+ while( *p && extlen < 3 ) {
+ if ( *p != '.') {
+ extension[extlen++] = p[0];
+ }
+ p++;
+ }
+ extension[extlen] = 0;
+ }
+ }
+
+ p = s;
+
+ while( *p && baselen < 5 ) {
+ if (isbasechar(*p)) {
+ base[baselen++] = p[0];
+ }
+ p++;
+ }
+ base[baselen] = 0;
+
+ csum = csum % (MANGLE_BASE*MANGLE_BASE);
+
+ memcpy(out, base, baselen);
+ out[baselen] = magic_char;
+ out[baselen+1] = mangle( csum/MANGLE_BASE );
+ out[baselen+2] = mangle( csum );
+
+ if( *extension ) {
+ out[baselen+3] = '.';
+ safe_strcpy(&out[baselen+4], extension, 3);
+ }
+
+ SAFE_FREE(s);
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ smb_ucs2_t *name_ucs2 = NULL;
+ NTSTATUS status;
+ size_t converted_size;
+
+ magic_char = lp_magicchar(p);
+
+ if (!push_ucs2_allocate(&name_ucs2, name, &converted_size)) {
+ DEBUG(0, ("push_ucs2_allocate failed!\n"));
+ return False;
+ }
+ status = is_valid_name(name_ucs2, False, False);
+ SAFE_FREE(name_ucs2);
+ return NT_STATUS_IS_OK(status);
+}
+
+/*****************************************************************************
+ * Convert a filename to DOS format. Return True if successful.
+ * Input: in Incoming name.
+ *
+ * out 8.3 DOS name.
+ *
+ * cache83 - If False, the mangled name cache will not be updated.
+ * This is usually used to prevent that we overwrite
+ * a conflicting cache entry prematurely, i.e. before
+ * we know whether the client is really interested in the
+ * current name. (See PR#13758). UKD.
+ *
+ * ****************************************************************************
+ */
+
+static bool hash_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ smb_ucs2_t *in_ucs2 = NULL;
+ size_t converted_size;
+
+ magic_char = lp_magicchar(p);
+
+ DEBUG(5,("hash_name_to_8_3( %s, cache83 = %s)\n", in,
+ cache83 ? "True" : "False"));
+
+ if (!push_ucs2_allocate(&in_ucs2, in, &converted_size)) {
+ DEBUG(0, ("push_ucs2_allocate failed!\n"));
+ return False;
+ }
+
+ /* If it's already 8.3, just copy. */
+ if (NT_STATUS_IS_OK(is_valid_name(in_ucs2, False, False)) &&
+ NT_STATUS_IS_OK(is_8_3_w(in_ucs2, False))) {
+ SAFE_FREE(in_ucs2);
+ safe_strcpy(out, in, 12);
+ return True;
+ }
+
+ SAFE_FREE(in_ucs2);
+ if (!to_8_3(in, out, default_case)) {
+ return False;
+ }
+
+ cache_mangled_name(out, in);
+
+ DEBUG(5,("hash_name_to_8_3(%s) ==> [%s]\n", in, out));
+ return True;
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation
+*/
+static struct mangle_fns mangle_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash_name_to_8_3
+};
+
+/* return the methods for this mangling implementation */
+struct mangle_fns *mangle_hash_init(void)
+{
+ mangle_reset();
+
+ /* Create the in-memory tdb using our custom hash function. */
+ tdb_mangled_cache = tdb_open_ex("mangled_cache", 1031, TDB_INTERNAL,
+ (O_RDWR|O_CREAT), 0644, NULL, fast_string_hash);
+
+ return &mangle_fns;
+}
diff --git a/source3/smbd/mangle_hash2.c b/source3/smbd/mangle_hash2.c
new file mode 100644
index 0000000000..a9b94aabc3
--- /dev/null
+++ b/source3/smbd/mangle_hash2.c
@@ -0,0 +1,761 @@
+/*
+ Unix SMB/CIFS implementation.
+ new hash based name mangling implementation
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Simo Sorce 2002
+
+ 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/>.
+*/
+
+/*
+ this mangling scheme uses the following format
+
+ Annnn~n.AAA
+
+ where nnnnn is a base 36 hash, and A represents characters from the original string
+
+ The hash is taken of the leading part of the long filename, in uppercase
+
+ for simplicity, we only allow ascii characters in 8.3 names
+ */
+
+ /* hash alghorithm changed to FNV1 by idra@samba.org (Simo Sorce).
+ * see http://www.isthe.com/chongo/tech/comp/fnv/index.html for a
+ * discussion on Fowler / Noll / Vo (FNV) Hash by one of it's authors
+ */
+
+/*
+ ===============================================================================
+ NOTE NOTE NOTE!!!
+
+ This file deliberately uses non-multibyte string functions in many places. This
+ is *not* a mistake. This code is multi-byte safe, but it gets this property
+ through some very subtle knowledge of the way multi-byte strings are encoded
+ and the fact that this mangling algorithm only supports ascii characters in
+ 8.3 names.
+
+ please don't convert this file to use the *_m() functions!!
+ ===============================================================================
+*/
+
+
+#include "includes.h"
+
+#if 1
+#define M_DEBUG(level, x) DEBUG(level, x)
+#else
+#define M_DEBUG(level, x)
+#endif
+
+/* these flags are used to mark characters in as having particular
+ properties */
+#define FLAG_BASECHAR 1
+#define FLAG_ASCII 2
+#define FLAG_ILLEGAL 4
+#define FLAG_WILDCARD 8
+
+/* the "possible" flags are used as a fast way to find possible DOS
+ reserved filenames */
+#define FLAG_POSSIBLE1 16
+#define FLAG_POSSIBLE2 32
+#define FLAG_POSSIBLE3 64
+#define FLAG_POSSIBLE4 128
+
+/* by default have a max of 4096 entries in the cache. */
+#ifndef MANGLE_CACHE_SIZE
+#define MANGLE_CACHE_SIZE 4096
+#endif
+
+#define FNV1_PRIME 0x01000193
+/*the following number is a fnv1 of the string: idra@samba.org 2002 */
+#define FNV1_INIT 0xa6b93095
+
+/* these tables are used to provide fast tests for characters */
+static unsigned char char_flags[256];
+
+#define FLAG_CHECK(c, flag) (char_flags[(unsigned char)(c)] & (flag))
+
+/*
+ this determines how many characters are used from the original filename
+ in the 8.3 mangled name. A larger value leads to a weaker hash and more collisions.
+ The largest possible value is 6.
+*/
+static unsigned mangle_prefix;
+
+/* these are the characters we use in the 8.3 hash. Must be 36 chars long */
+static const char *basechars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+static unsigned char base_reverse[256];
+#define base_forward(v) basechars[v]
+
+/* the list of reserved dos names - all of these are illegal */
+static const char *reserved_names[] =
+{ "AUX", "LOCK$", "CON", "COM1", "COM2", "COM3", "COM4",
+ "LPT1", "LPT2", "LPT3", "NUL", "PRN", NULL };
+
+/*
+ hash a string of the specified length. The string does not need to be
+ null terminated
+
+ this hash needs to be fast with a low collision rate (what hash doesn't?)
+*/
+static unsigned int mangle_hash(const char *key, unsigned int length)
+{
+ unsigned int value;
+ unsigned int i;
+ fstring str;
+
+ /* we have to uppercase here to ensure that the mangled name
+ doesn't depend on the case of the long name. Note that this
+ is the only place where we need to use a multi-byte string
+ function */
+ length = MIN(length,sizeof(fstring)-1);
+ strncpy(str, key, length);
+ str[length] = 0;
+ strupper_m(str);
+
+ /* the length of a multi-byte string can change after a strupper_m */
+ length = strlen(str);
+
+ /* Set the initial value from the key size. */
+ for (value = FNV1_INIT, i=0; i < length; i++) {
+ value *= (unsigned int)FNV1_PRIME;
+ value ^= (unsigned int)(str[i]);
+ }
+
+ /* note that we force it to a 31 bit hash, to keep within the limits
+ of the 36^6 mangle space */
+ return value & ~0x80000000;
+}
+
+/*
+ insert an entry into the prefix cache. The string might not be null
+ terminated */
+static void cache_insert(const char *prefix, int length, unsigned int hash)
+{
+ char *str = SMB_STRNDUP(prefix, length);
+
+ if (str == NULL) {
+ return;
+ }
+
+ memcache_add(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)),
+ data_blob_const(str, length+1));
+ SAFE_FREE(str);
+}
+
+/*
+ lookup an entry in the prefix cache. Return NULL if not found.
+*/
+static char *cache_lookup(TALLOC_CTX *mem_ctx, unsigned int hash)
+{
+ DATA_BLOB value;
+
+ if (!memcache_lookup(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)), &value)) {
+ return NULL;
+ }
+
+ SMB_ASSERT((value.length > 0)
+ && (value.data[value.length-1] == '\0'));
+
+ return talloc_strdup(mem_ctx, (char *)value.data);
+}
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+ */
+static bool is_mangled_component(const char *name, size_t len)
+{
+ unsigned int i;
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) ?\n", name, (unsigned long)len));
+
+ /* check the length */
+ if (len > 12 || len < 8)
+ return False;
+
+ /* the best distinguishing characteristic is the ~ */
+ if (name[6] != '~')
+ return False;
+
+ /* check extension */
+ if (len > 8) {
+ if (name[8] != '.')
+ return False;
+ for (i=9; name[i] && i < len; i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+ }
+
+ /* check lead characters */
+ for (i=0;i<mangle_prefix;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+
+ /* check rest of hash */
+ if (! FLAG_CHECK(name[7], FLAG_BASECHAR)) {
+ return False;
+ }
+ for (i=mangle_prefix;i<6;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_BASECHAR)) {
+ return False;
+ }
+ }
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) -> yes\n", name, (unsigned long)len));
+
+ return True;
+}
+
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+
+ NOTE! This interface must be able to handle a path with unix
+ directory separators. It should return true if any component is
+ mangled
+ */
+static bool is_mangled(const char *name, const struct share_params *parm)
+{
+ const char *p;
+ const char *s;
+
+ M_DEBUG(10,("is_mangled %s ?\n", name));
+
+ for (s=name; (p=strchr(s, '/')); s=p+1) {
+ if (is_mangled_component(s, PTR_DIFF(p, s))) {
+ return True;
+ }
+ }
+
+ /* and the last part ... */
+ return is_mangled_component(s,strlen(s));
+}
+
+
+/*
+ see if a filename is an allowable 8.3 name.
+
+ we are only going to allow ascii characters in 8.3 names, as this
+ simplifies things greatly (it means that we know the string won't
+ get larger when converted from UNIX to DOS formats)
+*/
+static bool is_8_3(const char *name, bool check_case, bool allow_wildcards, const struct share_params *p)
+{
+ int len, i;
+ char *dot_p;
+
+ /* as a special case, the names '.' and '..' are allowable 8.3 names */
+ if (name[0] == '.') {
+ if (!name[1] || (name[1] == '.' && !name[2])) {
+ return True;
+ }
+ }
+
+ /* the simplest test is on the overall length of the
+ filename. Note that we deliberately use the ascii string
+ length (not the multi-byte one) as it is faster, and gives us
+ the result we need in this case. Using strlen_m would not
+ only be slower, it would be incorrect */
+ len = strlen(name);
+ if (len > 12)
+ return False;
+
+ /* find the '.'. Note that once again we use the non-multibyte
+ function */
+ dot_p = strchr(name, '.');
+
+ if (!dot_p) {
+ /* if the name doesn't contain a '.' then its length
+ must be less than 8 */
+ if (len > 8) {
+ return False;
+ }
+ } else {
+ int prefix_len, suffix_len;
+
+ /* if it does contain a dot then the prefix must be <=
+ 8 and the suffix <= 3 in length */
+ prefix_len = PTR_DIFF(dot_p, name);
+ suffix_len = len - (prefix_len+1);
+
+ if (prefix_len > 8 || suffix_len > 3 || suffix_len == 0) {
+ return False;
+ }
+
+ /* a 8.3 name cannot contain more than 1 '.' */
+ if (strchr(dot_p+1, '.')) {
+ return False;
+ }
+ }
+
+ /* the length are all OK. Now check to see if the characters themselves are OK */
+ for (i=0; name[i]; i++) {
+ /* note that we may allow wildcard petterns! */
+ if (!FLAG_CHECK(name[i], FLAG_ASCII|(allow_wildcards ? FLAG_WILDCARD : 0)) && name[i] != '.') {
+ return False;
+ }
+ }
+
+ /* it is a good 8.3 name */
+ return True;
+}
+
+
+/*
+ reset the mangling cache on a smb.conf reload. This only really makes sense for
+ mangling backends that have parameters in smb.conf, and as this backend doesn't
+ this is a NULL operation
+*/
+static void mangle_reset(void)
+{
+ /* noop */
+}
+
+
+/*
+ try to find a 8.3 name in the cache, and if found then
+ replace the string with the original long name.
+*/
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *name,
+ char **pp_out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ unsigned int hash, multiplier;
+ unsigned int i;
+ char *prefix;
+ char extension[4];
+
+ *pp_out = NULL;
+
+ /* make sure that this is a mangled name from this cache */
+ if (!is_mangled(name, p)) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> not mangled\n", name));
+ return False;
+ }
+
+ /* we need to extract the hash from the 8.3 name */
+ hash = base_reverse[(unsigned char)name[7]];
+ for (multiplier=36, i=5;i>=mangle_prefix;i--) {
+ unsigned int v = base_reverse[(unsigned char)name[i]];
+ hash += multiplier * v;
+ multiplier *= 36;
+ }
+
+ /* now look in the prefix cache for that hash */
+ prefix = cache_lookup(ctx, hash);
+ if (!prefix) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %08X -> not found\n",
+ name, hash));
+ return False;
+ }
+
+ /* we found it - construct the full name */
+ if (name[8] == '.') {
+ strncpy(extension, name+9, 3);
+ extension[3] = 0;
+ } else {
+ extension[0] = 0;
+ }
+
+ if (extension[0]) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s.%s\n",
+ name, prefix, extension));
+ *pp_out = talloc_asprintf(ctx, "%s.%s", prefix, extension);
+ } else {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s\n", name, prefix));
+ *pp_out = talloc_strdup(ctx, prefix);
+ }
+
+ TALLOC_FREE(prefix);
+
+ if (!*pp_out) {
+ M_DEBUG(0,("talloc_fail"));
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ look for a DOS reserved name
+*/
+static bool is_reserved_name(const char *name)
+{
+ if (FLAG_CHECK(name[0], FLAG_POSSIBLE1) &&
+ FLAG_CHECK(name[1], FLAG_POSSIBLE2) &&
+ FLAG_CHECK(name[2], FLAG_POSSIBLE3) &&
+ FLAG_CHECK(name[3], FLAG_POSSIBLE4)) {
+ /* a likely match, scan the lot */
+ int i;
+ for (i=0; reserved_names[i]; i++) {
+ int len = strlen(reserved_names[i]);
+ /* note that we match on COM1 as well as COM1.foo */
+ if (strnequal(name, reserved_names[i], len) &&
+ (name[len] == '.' || name[len] == 0)) {
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
+
+/*
+ See if a filename is a legal long filename.
+ A filename ending in a '.' is not legal unless it's "." or "..". JRA.
+ A filename ending in ' ' is not legal either. See bug id #2769.
+*/
+
+static bool is_legal_name(const char *name)
+{
+ const char *dot_pos = NULL;
+ bool alldots = True;
+ size_t numdots = 0;
+
+ while (*name) {
+ if (((unsigned int)name[0]) > 128 && (name[1] != 0)) {
+ /* Possible start of mb character. */
+ char mbc[2];
+ /*
+ * Note that if CH_UNIX is utf8 a string may be 3
+ * bytes, but this is ok as mb utf8 characters don't
+ * contain embedded ascii bytes. We are really checking
+ * for mb UNIX asian characters like Japanese (SJIS) here.
+ * JRA.
+ */
+ if (convert_string(CH_UNIX, CH_UTF16LE, name, 2, mbc, 2, False) == 2) {
+ /* Was a good mb string. */
+ name += 2;
+ continue;
+ }
+ }
+
+ if (FLAG_CHECK(name[0], FLAG_ILLEGAL)) {
+ return False;
+ }
+ if (name[0] == '.') {
+ dot_pos = name;
+ numdots++;
+ } else {
+ alldots = False;
+ }
+ if ((name[0] == ' ') && (name[1] == '\0')) {
+ /* Can't end in ' ' */
+ return False;
+ }
+ name++;
+ }
+
+ if (dot_pos) {
+ if (alldots && (numdots == 1 || numdots == 2))
+ return True; /* . or .. is a valid name */
+
+ /* A valid long name cannot end in '.' */
+ if (dot_pos[1] == '\0')
+ return False;
+ }
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ if (is_reserved_name(name)) {
+ return True;
+ }
+ return !is_legal_name(name);
+}
+
+/*
+ the main forward mapping function, which converts a long filename to
+ a 8.3 name
+
+ if cache83 is not set then we don't cache the result
+
+*/
+static bool hash2_name_to_8_3(const char *name,
+ char new_name[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ char *dot_p;
+ char lead_chars[7];
+ char extension[4];
+ unsigned int extension_length, i;
+ unsigned int prefix_len;
+ unsigned int hash, v;
+
+ /* reserved names are handled specially */
+ if (!is_reserved_name(name)) {
+ /* if the name is already a valid 8.3 name then we don't need to
+ * change anything */
+ if (is_legal_name(name) && is_8_3(name, False, False, p)) {
+ safe_strcpy(new_name, name, 12);
+ return True;
+ }
+ }
+
+ /* find the '.' if any */
+ dot_p = strrchr(name, '.');
+
+ if (dot_p) {
+ /* if the extension contains any illegal characters or
+ is too long or zero length then we treat it as part
+ of the prefix */
+ for (i=0; i<4 && dot_p[i+1]; i++) {
+ if (! FLAG_CHECK(dot_p[i+1], FLAG_ASCII)) {
+ dot_p = NULL;
+ break;
+ }
+ }
+ if (i == 0 || i == 4) {
+ dot_p = NULL;
+ }
+ }
+
+ /* the leading characters in the mangled name is taken from
+ the first characters of the name, if they are ascii otherwise
+ '_' is used
+ */
+ for (i=0;i<mangle_prefix && name[i];i++) {
+ lead_chars[i] = name[i];
+ if (! FLAG_CHECK(lead_chars[i], FLAG_ASCII)) {
+ lead_chars[i] = '_';
+ }
+ lead_chars[i] = toupper_ascii(lead_chars[i]);
+ }
+ for (;i<mangle_prefix;i++) {
+ lead_chars[i] = '_';
+ }
+
+ /* the prefix is anything up to the first dot */
+ if (dot_p) {
+ prefix_len = PTR_DIFF(dot_p, name);
+ } else {
+ prefix_len = strlen(name);
+ }
+
+ /* the extension of the mangled name is taken from the first 3
+ ascii chars after the dot */
+ extension_length = 0;
+ if (dot_p) {
+ for (i=1; extension_length < 3 && dot_p[i]; i++) {
+ char c = dot_p[i];
+ if (FLAG_CHECK(c, FLAG_ASCII)) {
+ extension[extension_length++] =
+ toupper_ascii(c);
+ }
+ }
+ }
+
+ /* find the hash for this prefix */
+ v = hash = mangle_hash(name, prefix_len);
+
+ /* now form the mangled name. */
+ for (i=0;i<mangle_prefix;i++) {
+ new_name[i] = lead_chars[i];
+ }
+ new_name[7] = base_forward(v % 36);
+ new_name[6] = '~';
+ for (i=5; i>=mangle_prefix; i--) {
+ v = v / 36;
+ new_name[i] = base_forward(v % 36);
+ }
+
+ /* add the extension */
+ if (extension_length) {
+ new_name[8] = '.';
+ memcpy(&new_name[9], extension, extension_length);
+ new_name[9+extension_length] = 0;
+ } else {
+ new_name[8] = 0;
+ }
+
+ if (cache83) {
+ /* put it in the cache */
+ cache_insert(name, prefix_len, hash);
+ }
+
+ M_DEBUG(10,("hash2_name_to_8_3: %s -> %08X -> %s (cache=%d)\n",
+ name, hash, new_name, cache83));
+
+ return True;
+}
+
+/* initialise the flags table
+
+ we allow only a very restricted set of characters as 'ascii' in this
+ mangling backend. This isn't a significant problem as modern clients
+ use the 'long' filenames anyway, and those don't have these
+ restrictions.
+*/
+static void init_tables(void)
+{
+ int i;
+
+ memset(char_flags, 0, sizeof(char_flags));
+
+ for (i=1;i<128;i++) {
+ if (i <= 0x1f) {
+ /* Control characters. */
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if ((i >= '0' && i <= '9') ||
+ (i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z')) {
+ char_flags[i] |= (FLAG_ASCII | FLAG_BASECHAR);
+ }
+ if (strchr("_-$~", i)) {
+ char_flags[i] |= FLAG_ASCII;
+ }
+
+ if (strchr("*\\/?<>|\":", i)) {
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if (strchr("*?\"<>", i)) {
+ char_flags[i] |= FLAG_WILDCARD;
+ }
+ }
+
+ memset(base_reverse, 0, sizeof(base_reverse));
+ for (i=0;i<36;i++) {
+ base_reverse[(unsigned char)base_forward(i)] = i;
+ }
+
+ /* fill in the reserved names flags. These are used as a very
+ fast filter for finding possible DOS reserved filenames */
+ for (i=0; reserved_names[i]; i++) {
+ unsigned char c1, c2, c3, c4;
+
+ c1 = (unsigned char)reserved_names[i][0];
+ c2 = (unsigned char)reserved_names[i][1];
+ c3 = (unsigned char)reserved_names[i][2];
+ c4 = (unsigned char)reserved_names[i][3];
+
+ char_flags[c1] |= FLAG_POSSIBLE1;
+ char_flags[c2] |= FLAG_POSSIBLE2;
+ char_flags[c3] |= FLAG_POSSIBLE3;
+ char_flags[c4] |= FLAG_POSSIBLE4;
+ char_flags[tolower_ascii(c1)] |= FLAG_POSSIBLE1;
+ char_flags[tolower_ascii(c2)] |= FLAG_POSSIBLE2;
+ char_flags[tolower_ascii(c3)] |= FLAG_POSSIBLE3;
+ char_flags[tolower_ascii(c4)] |= FLAG_POSSIBLE4;
+
+ char_flags[(unsigned char)'.'] |= FLAG_POSSIBLE4;
+ }
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation */
+static struct mangle_fns mangle_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash2_name_to_8_3
+};
+
+/* return the methods for this mangling implementation */
+struct mangle_fns *mangle_hash2_init(void)
+{
+ /* the mangle prefix can only be in the mange 1 to 6 */
+ mangle_prefix = lp_mangle_prefix();
+ if (mangle_prefix > 6) {
+ mangle_prefix = 6;
+ }
+ if (mangle_prefix < 1) {
+ mangle_prefix = 1;
+ }
+
+ init_tables();
+ mangle_reset();
+
+ return &mangle_fns;
+}
+
+static void posix_mangle_reset(void)
+{;}
+
+static bool posix_is_mangled(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_must_mangle(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_is_8_3(const char *fname,
+ bool check_case,
+ bool allow_wildcards,
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ memset(out, '\0', 13);
+ return True;
+}
+
+/* POSIX paths backend - no mangle. */
+static struct mangle_fns posix_mangle_fns = {
+ posix_mangle_reset,
+ posix_is_mangled,
+ posix_must_mangle,
+ posix_is_8_3,
+ posix_lookup_name_from_8_3,
+ posix_name_to_8_3
+};
+
+struct mangle_fns *posix_mangle_init(void)
+{
+ return &posix_mangle_fns;
+}
diff --git a/source3/smbd/map_username.c b/source3/smbd/map_username.c
new file mode 100644
index 0000000000..7536758bcb
--- /dev/null
+++ b/source3/smbd/map_username.c
@@ -0,0 +1,215 @@
+/*
+ Unix SMB/CIFS implementation.
+ Username handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1997-2001.
+ Copyright (C) Volker Lendecke 2006
+
+ 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"
+
+/*******************************************************************
+ Map a username from a dos name to a unix name by looking in the username
+ map. Note that this modifies the name in place.
+ This is the main function that should be called *once* on
+ any incoming or new username - in order to canonicalize the name.
+ This is being done to de-couple the case conversions from the user mapping
+ function. Previously, the map_username was being called
+ every time Get_Pwnam_alloc was called.
+ Returns True if username was changed, false otherwise.
+********************************************************************/
+
+static char *last_from, *last_to;
+
+static const char *get_last_from(void)
+{
+ if (!last_from) {
+ return "";
+ }
+ return last_from;
+}
+
+static const char *get_last_to(void)
+{
+ if (!last_to) {
+ return "";
+ }
+ return last_to;
+}
+
+static bool set_last_from_to(const char *from, const char *to)
+{
+ char *orig_from = last_from;
+ char *orig_to = last_to;
+
+ last_from = SMB_STRDUP(from);
+ last_to = SMB_STRDUP(to);
+
+ SAFE_FREE(orig_from);
+ SAFE_FREE(orig_to);
+
+ if (!last_from || !last_to) {
+ SAFE_FREE(last_from);
+ SAFE_FREE(last_to);
+ return false;
+ }
+ return true;
+}
+
+bool map_username(fstring user)
+{
+ XFILE *f;
+ char *mapfile = lp_username_map();
+ char *s;
+ char buf[512];
+ bool mapped_user = False;
+ char *cmd = lp_username_map_script();
+
+ if (!*user)
+ return false;
+
+ if (strequal(user,get_last_to()))
+ return false;
+
+ if (strequal(user,get_last_from())) {
+ DEBUG(3,("Mapped user %s to %s\n",user,get_last_to()));
+ fstrcpy(user,get_last_to());
+ return true;
+ }
+
+ /* first try the username map script */
+
+ if ( *cmd ) {
+ char **qlines;
+ char *command = NULL;
+ int numlines, ret, fd;
+
+ command = talloc_asprintf(talloc_tos(),
+ "%s \"%s\"",
+ cmd,
+ user);
+ if (!command) {
+ return false;
+ }
+
+ DEBUG(10,("Running [%s]\n", command));
+ ret = smbrun(command, &fd);
+ DEBUGADD(10,("returned [%d]\n", ret));
+
+ if ( ret != 0 ) {
+ if (fd != -1)
+ close(fd);
+ return False;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines,0);
+ DEBUGADD(10,("Lines returned = [%d]\n", numlines));
+ close(fd);
+
+ /* should be either no lines or a single line with the mapped username */
+
+ if (numlines && qlines) {
+ DEBUG(3,("Mapped user %s to %s\n", user, qlines[0] ));
+ fstrcpy( user, qlines[0] );
+ }
+
+ file_lines_free(qlines);
+
+ return numlines != 0;
+ }
+
+ /* ok. let's try the mapfile */
+ if (!*mapfile)
+ return False;
+
+ f = x_fopen(mapfile,O_RDONLY, 0);
+ if (!f) {
+ DEBUG(0,("can't open username map %s. Error %s\n",mapfile, strerror(errno) ));
+ return False;
+ }
+
+ DEBUG(4,("Scanning username map %s\n",mapfile));
+
+ while((s=fgets_slash(buf,sizeof(buf),f))!=NULL) {
+ char *unixname = s;
+ char *dosname = strchr_m(unixname,'=');
+ char **dosuserlist;
+ bool return_if_mapped = False;
+
+ if (!dosname)
+ continue;
+
+ *dosname++ = 0;
+
+ while (isspace((int)*unixname))
+ unixname++;
+
+ if ('!' == *unixname) {
+ return_if_mapped = True;
+ unixname++;
+ while (*unixname && isspace((int)*unixname))
+ unixname++;
+ }
+
+ if (!*unixname || strchr_m("#;",*unixname))
+ continue;
+
+ {
+ int l = strlen(unixname);
+ while (l && isspace((int)unixname[l-1])) {
+ unixname[l-1] = 0;
+ l--;
+ }
+ }
+
+ /* skip lines like 'user = ' */
+
+ dosuserlist = str_list_make(talloc_tos(), dosname, NULL);
+ if (!dosuserlist) {
+ DEBUG(0,("Bad username map entry. Unable to build user list. Ignoring.\n"));
+ continue;
+ }
+
+ if (strchr_m(dosname,'*') ||
+ user_in_list(user, (const char **)dosuserlist)) {
+ DEBUG(3,("Mapped user %s to %s\n",user,unixname));
+ mapped_user = True;
+
+ set_last_from_to(user, unixname);
+ fstrcpy( user, unixname );
+
+ if ( return_if_mapped ) {
+ TALLOC_FREE(dosuserlist);
+ x_fclose(f);
+ return True;
+ }
+ }
+
+ TALLOC_FREE(dosuserlist);
+ }
+
+ x_fclose(f);
+
+ /*
+ * Setup the last_from and last_to as an optimization so
+ * that we don't scan the file again for the same user.
+ */
+
+ set_last_from_to(user, user);
+
+ return mapped_user;
+}
diff --git a/source3/smbd/message.c b/source3/smbd/message.c
new file mode 100644
index 0000000000..62df5c37eb
--- /dev/null
+++ b/source3/smbd/message.c
@@ -0,0 +1,309 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB messaging
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+/*
+ This file handles the messaging system calls for winpopup style
+ messages
+*/
+
+
+#include "includes.h"
+
+extern userdom_struct current_user_info;
+
+struct msg_state {
+ char *from;
+ char *to;
+ char *msg;
+};
+
+static struct msg_state *smbd_msg_state;
+
+/****************************************************************************
+ Deliver the message.
+****************************************************************************/
+
+static void msg_deliver(struct msg_state *state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *name = NULL;
+ int i;
+ int fd;
+ char *msg;
+ size_t len;
+ ssize_t sz;
+ fstring alpha_buf;
+ char *s;
+
+ if (! (*lp_msg_command())) {
+ DEBUG(1,("no messaging command specified\n"));
+ goto done;
+ }
+
+ /* put it in a temporary file */
+ name = talloc_asprintf(talloc_tos(), "%s/msg.XXXXXX", tmpdir());
+ if (!name) {
+ goto done;
+ }
+ fd = smb_mkstemp(name);
+
+ if (fd == -1) {
+ DEBUG(1, ("can't open message file %s: %s\n", name,
+ strerror(errno)));
+ goto done;
+ }
+
+ /*
+ * Incoming message is in DOS codepage format. Convert to UNIX.
+ */
+
+ if (!convert_string_talloc(talloc_tos(), CH_DOS, CH_UNIX, state->msg,
+ talloc_get_size(state->msg), (void *)&msg,
+ &len, true)) {
+ DEBUG(3, ("Conversion failed, delivering message in DOS "
+ "codepage format\n"));
+ msg = state->msg;
+ }
+
+ for (i = 0; i < len; i++) {
+ if ((msg[i] == '\r') &&
+ (i < (len-1)) && (msg[i+1] == '\n')) {
+ continue;
+ }
+ sz = write(fd, &msg[i], 1);
+ if ( sz != 1 ) {
+ DEBUG(0, ("Write error to fd %d: %ld(%s)\n", fd,
+ (long)sz, strerror(errno)));
+ }
+ }
+
+ close(fd);
+
+ /* run the command */
+ s = talloc_strdup(talloc_tos(), lp_msg_command());
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->from, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%f", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->to, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%t", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_sub_basic(talloc_tos(), current_user_info.smb_name,
+ current_user_info.domain, s);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_string_sub(talloc_tos(), s, "%s", name);
+ if (s == NULL) {
+ goto done;
+ }
+ smbrun(s,NULL);
+
+ done:
+ TALLOC_FREE(frame);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sends.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sends(struct smb_request *req)
+{
+ struct msg_state *state;
+ int len;
+ char *msg;
+ char *p;
+
+ START_PROFILE(SMBsends);
+
+ if (!(*lp_msg_command())) {
+ reply_doserror(req, ERRSRV, ERRmsgoff);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ state = talloc(talloc_tos(), struct msg_state);
+
+ p = smb_buf(req->inbuf)+1;
+ p += srvstr_pull_buf_talloc(
+ state, (char *)req->inbuf, req->flags2, &state->from, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_buf_talloc(
+ state, (char *)req->inbuf, req->flags2, &state->to, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+
+ msg = p;
+
+ len = SVAL(msg,0);
+ len = MIN(len, smb_bufrem(req->inbuf, msg+2));
+
+ state->msg = talloc_array(state, char, len);
+
+ if (state->msg == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ memcpy(state->msg, msg+2, len);
+
+ msg_deliver(state);
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsends);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendstrt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendstrt(struct smb_request *req)
+{
+ char *p;
+
+ START_PROFILE(SMBsendstrt);
+
+ if (!(*lp_msg_command())) {
+ reply_doserror(req, ERRSRV, ERRmsgoff);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ TALLOC_FREE(smbd_msg_state);
+
+ smbd_msg_state = TALLOC_ZERO_P(NULL, struct msg_state);
+
+ if (smbd_msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ p = smb_buf(req->inbuf)+1;
+ p += srvstr_pull_buf_talloc(
+ smbd_msg_state, (char *)req->inbuf, req->flags2,
+ &smbd_msg_state->from, p, STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_buf_talloc(
+ smbd_msg_state, (char *)req->inbuf, req->flags2,
+ &smbd_msg_state->to, p, STR_ASCII|STR_TERMINATE) + 1;
+
+ DEBUG( 3, ( "SMBsendstrt (from %s to %s)\n", smbd_msg_state->from,
+ smbd_msg_state->to ) );
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendstrt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendtxt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendtxt(struct smb_request *req)
+{
+ int len;
+ char *msg;
+ char *tmp;
+ size_t old_len;
+
+ START_PROFILE(SMBsendtxt);
+
+ if (! (*lp_msg_command())) {
+ reply_doserror(req, ERRSRV, ERRmsgoff);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ if (smbd_msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ msg = smb_buf(req->inbuf) + 1;
+
+ old_len = talloc_get_size(smbd_msg_state->msg);
+
+ len = MIN(SVAL(msg, 0), smb_bufrem(req->inbuf, msg+2));
+
+ tmp = TALLOC_REALLOC_ARRAY(smbd_msg_state, smbd_msg_state->msg,
+ char, old_len + len);
+
+ if (tmp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ smbd_msg_state->msg = tmp;
+
+ memcpy(&smbd_msg_state->msg[old_len], msg+2, len);
+
+ DEBUG( 3, ( "SMBsendtxt\n" ) );
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendtxt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendend.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendend(struct smb_request *req)
+{
+ START_PROFILE(SMBsendend);
+
+ if (! (*lp_msg_command())) {
+ reply_doserror(req, ERRSRV, ERRmsgoff);
+ END_PROFILE(SMBsendend);
+ return;
+ }
+
+ DEBUG(3,("SMBsendend\n"));
+
+ msg_deliver(smbd_msg_state);
+
+ TALLOC_FREE(smbd_msg_state);
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendend);
+ return;
+}
diff --git a/source3/smbd/msdfs.c b/source3/smbd/msdfs.c
new file mode 100644
index 0000000000..32240ff0d5
--- /dev/null
+++ b/source3/smbd/msdfs.c
@@ -0,0 +1,1724 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ MSDFS services for Samba
+ Copyright (C) Shirish Kalele 2000
+ Copyright (C) Jeremy Allison 2007
+
+ 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/>.
+
+*/
+
+#define DBGC_CLASS DBGC_MSDFS
+#include "includes.h"
+
+extern uint32 global_client_caps;
+
+/**********************************************************************
+ Parse a DFS pathname of the form \hostname\service\reqpath
+ into the dfs_path structure.
+ If POSIX pathnames is true, the pathname may also be of the
+ form /hostname/service/reqpath.
+ We cope with either here.
+
+ Unfortunately, due to broken clients who might set the
+ SVAL(inbuf,smb_flg2) & FLAGS2_DFS_PATHNAMES bit and then
+ send a local path, we have to cope with that too....
+
+ If conn != NULL then ensure the provided service is
+ the one pointed to by the connection.
+
+ This version does everything using pointers within one copy of the
+ pathname string, talloced on the struct dfs_path pointer (which
+ must be talloced). This may be too clever to live....
+ JRA.
+**********************************************************************/
+
+static NTSTATUS parse_dfs_path(connection_struct *conn,
+ const char *pathname,
+ bool allow_wcards,
+ struct dfs_path *pdp, /* MUST BE TALLOCED */
+ bool *ppath_contains_wcard)
+{
+ char *pathname_local;
+ char *p,*temp;
+ char *servicename;
+ char *eos_ptr;
+ NTSTATUS status = NT_STATUS_OK;
+ char sepchar;
+
+ ZERO_STRUCTP(pdp);
+
+ /*
+ * This is the only talloc we should need to do
+ * on the struct dfs_path. All the pointers inside
+ * it should point to offsets within this string.
+ */
+
+ pathname_local = talloc_strdup(pdp, pathname);
+ if (!pathname_local) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /* Get a pointer to the terminating '\0' */
+ eos_ptr = &pathname_local[strlen(pathname_local)];
+ p = temp = pathname_local;
+
+ pdp->posix_path = (lp_posix_pathnames() && *pathname == '/');
+
+ sepchar = pdp->posix_path ? '/' : '\\';
+
+ if (*pathname != sepchar) {
+ DEBUG(10,("parse_dfs_path: path %s doesn't start with %c\n",
+ pathname, sepchar ));
+ /*
+ * Possibly client sent a local path by mistake.
+ * Try and convert to a local path.
+ */
+
+ pdp->hostname = eos_ptr; /* "" */
+ pdp->servicename = eos_ptr; /* "" */
+
+ /* We've got no info about separators. */
+ pdp->posix_path = lp_posix_pathnames();
+ p = temp;
+ DEBUG(10,("parse_dfs_path: trying to convert %s to a "
+ "local path\n",
+ temp));
+ goto local_path;
+ }
+
+ /*
+ * Safe to use on talloc'ed string as it only shrinks.
+ * It also doesn't affect the eos_ptr.
+ */
+ trim_char(temp,sepchar,sepchar);
+
+ DEBUG(10,("parse_dfs_path: temp = |%s| after trimming %c's\n",
+ temp, sepchar));
+
+ /* Now tokenize. */
+ /* Parse out hostname. */
+ p = strchr_m(temp,sepchar);
+ if(p == NULL) {
+ DEBUG(10,("parse_dfs_path: can't parse hostname from path %s\n",
+ temp));
+ /*
+ * Possibly client sent a local path by mistake.
+ * Try and convert to a local path.
+ */
+
+ pdp->hostname = eos_ptr; /* "" */
+ pdp->servicename = eos_ptr; /* "" */
+
+ p = temp;
+ DEBUG(10,("parse_dfs_path: trying to convert %s "
+ "to a local path\n",
+ temp));
+ goto local_path;
+ }
+ *p = '\0';
+ pdp->hostname = temp;
+
+ DEBUG(10,("parse_dfs_path: hostname: %s\n",pdp->hostname));
+
+ /* Parse out servicename. */
+ servicename = p+1;
+ p = strchr_m(servicename,sepchar);
+ if (p) {
+ *p = '\0';
+ }
+
+ /* Is this really our servicename ? */
+ if (conn && !( strequal(servicename, lp_servicename(SNUM(conn)))
+ || (strequal(servicename, HOMES_NAME)
+ && strequal(lp_servicename(SNUM(conn)),
+ get_current_username()) )) ) {
+ DEBUG(10,("parse_dfs_path: %s is not our servicename\n",
+ servicename));
+
+ /*
+ * Possibly client sent a local path by mistake.
+ * Try and convert to a local path.
+ */
+
+ pdp->hostname = eos_ptr; /* "" */
+ pdp->servicename = eos_ptr; /* "" */
+
+ /* Repair the path - replace the sepchar's
+ we nulled out */
+ servicename--;
+ *servicename = sepchar;
+ if (p) {
+ *p = sepchar;
+ }
+
+ p = temp;
+ DEBUG(10,("parse_dfs_path: trying to convert %s "
+ "to a local path\n",
+ temp));
+ goto local_path;
+ }
+
+ pdp->servicename = servicename;
+
+ DEBUG(10,("parse_dfs_path: servicename: %s\n",pdp->servicename));
+
+ if(p == NULL) {
+ /* Client sent self referral \server\share. */
+ pdp->reqpath = eos_ptr; /* "" */
+ return NT_STATUS_OK;
+ }
+
+ p++;
+
+ local_path:
+
+ *ppath_contains_wcard = False;
+
+ pdp->reqpath = p;
+
+ /* Rest is reqpath. */
+ if (pdp->posix_path) {
+ status = check_path_syntax_posix(pdp->reqpath);
+ } else {
+ if (allow_wcards) {
+ status = check_path_syntax_wcard(pdp->reqpath,
+ ppath_contains_wcard);
+ } else {
+ status = check_path_syntax(pdp->reqpath);
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("parse_dfs_path: '%s' failed with %s\n",
+ p, nt_errstr(status) ));
+ return status;
+ }
+
+ DEBUG(10,("parse_dfs_path: rest of the path: %s\n",pdp->reqpath));
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer.
+ Note this CHANGES CWD !!!! JRA.
+*********************************************************/
+
+NTSTATUS create_conn_struct(TALLOC_CTX *ctx,
+ connection_struct **pconn,
+ int snum,
+ const char *path,
+ char **poldcwd)
+{
+ connection_struct *conn;
+ char *connpath;
+ char *oldcwd;
+
+ conn = TALLOC_ZERO_P(ctx, connection_struct);
+ if (conn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ connpath = talloc_strdup(conn, path);
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+ connpath = talloc_string_sub(conn,
+ connpath,
+ "%S",
+ lp_servicename(snum));
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* needed for smbd_vfs_init() */
+
+ if (!(conn->params = TALLOC_ZERO_P(conn, struct share_params))) {
+ DEBUG(0, ("TALLOC failed\n"));
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ conn->params->service = snum;
+
+ set_conn_connectpath(conn, connpath);
+
+ if (!smbd_vfs_init(conn)) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DEBUG(0,("create_conn_struct: smbd_vfs_init failed.\n"));
+ conn_free_internal(conn);
+ return status;
+ }
+
+ /*
+ * Windows seems to insist on doing trans2getdfsreferral() calls on
+ * the IPC$ share as the anonymous user. If we try to chdir as that
+ * user we will fail.... WTF ? JRA.
+ */
+
+ oldcwd = vfs_GetWd(ctx, conn);
+ if (oldcwd == NULL) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("vfs_GetWd failed: %s\n", strerror(errno)));
+ conn_free_internal(conn);
+ return status;
+ }
+
+ if (vfs_ChDir(conn,conn->connectpath) != 0) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DEBUG(3,("create_conn_struct: Can't ChDir to new conn path %s. "
+ "Error was %s\n",
+ conn->connectpath, strerror(errno) ));
+ conn_free_internal(conn);
+ return status;
+ }
+
+ *pconn = conn;
+ *poldcwd = oldcwd;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Parse the contents of a symlink to verify if it is an msdfs referral
+ A valid referral is of the form:
+
+ msdfs:server1\share1,server2\share2
+ msdfs:server1\share1\pathname,server2\share2\pathname
+ msdfs:server1/share1,server2/share2
+ msdfs:server1/share1/pathname,server2/share2/pathname.
+
+ Note that the alternate paths returned here must be of the canonicalized
+ form:
+
+ \server\share or
+ \server\share\path\to\file,
+
+ even in posix path mode. This is because we have no knowledge if the
+ server we're referring to understands posix paths.
+ **********************************************************************/
+
+static bool parse_msdfs_symlink(TALLOC_CTX *ctx,
+ const char *target,
+ struct referral **preflist,
+ int *refcount)
+{
+ char *temp = NULL;
+ char *prot;
+ char **alt_path = NULL;
+ int count = 0, i;
+ struct referral *reflist;
+ char *saveptr;
+
+ temp = talloc_strdup(ctx, target);
+ if (!temp) {
+ return False;
+ }
+ prot = strtok_r(temp, ":", &saveptr);
+ if (!prot) {
+ DEBUG(0,("parse_msdfs_symlink: invalid path !\n"));
+ return False;
+ }
+
+ alt_path = TALLOC_ARRAY(ctx, char *, MAX_REFERRAL_COUNT);
+ if (!alt_path) {
+ return False;
+ }
+
+ /* parse out the alternate paths */
+ while((count<MAX_REFERRAL_COUNT) &&
+ ((alt_path[count] = strtok_r(NULL, ",", &saveptr)) != NULL)) {
+ count++;
+ }
+
+ DEBUG(10,("parse_msdfs_symlink: count=%d\n", count));
+
+ if (count) {
+ reflist = *preflist = TALLOC_ZERO_ARRAY(ctx,
+ struct referral, count);
+ if(reflist == NULL) {
+ TALLOC_FREE(alt_path);
+ return False;
+ }
+ } else {
+ reflist = *preflist = NULL;
+ }
+
+ for(i=0;i<count;i++) {
+ char *p;
+
+ /* Canonicalize link target.
+ * Replace all /'s in the path by a \ */
+ string_replace(alt_path[i], '/', '\\');
+
+ /* Remove leading '\\'s */
+ p = alt_path[i];
+ while (*p && (*p == '\\')) {
+ p++;
+ }
+
+ reflist[i].alternate_path = talloc_asprintf(ctx,
+ "\\%s",
+ p);
+ if (!reflist[i].alternate_path) {
+ return False;
+ }
+
+ reflist[i].proximity = 0;
+ reflist[i].ttl = REFERRAL_TTL;
+ DEBUG(10, ("parse_msdfs_symlink: Created alt path: %s\n",
+ reflist[i].alternate_path));
+ }
+
+ *refcount = count;
+
+ TALLOC_FREE(alt_path);
+ return True;
+}
+
+/**********************************************************************
+ Returns true if the unix path is a valid msdfs symlink and also
+ returns the target string from inside the link.
+**********************************************************************/
+
+static bool is_msdfs_link_internal(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *path,
+ char **pp_link_target,
+ SMB_STRUCT_STAT *sbufp)
+{
+ SMB_STRUCT_STAT st;
+ int referral_len = 0;
+ char link_target_buf[7];
+ size_t bufsize = 0;
+ char *link_target = NULL;
+
+ if (pp_link_target) {
+ bufsize = 1024;
+ link_target = TALLOC_ARRAY(ctx, char, bufsize);
+ if (!link_target) {
+ return False;
+ }
+ *pp_link_target = link_target;
+ } else {
+ bufsize = sizeof(link_target_buf);
+ link_target = link_target_buf;
+ }
+
+ if (sbufp == NULL) {
+ sbufp = &st;
+ }
+
+ if (SMB_VFS_LSTAT(conn, path, sbufp) != 0) {
+ DEBUG(5,("is_msdfs_link_read_target: %s does not exist.\n",
+ path));
+ goto err;
+ }
+
+ if (!S_ISLNK(sbufp->st_mode)) {
+ DEBUG(5,("is_msdfs_link_read_target: %s is not a link.\n",
+ path));
+ goto err;
+ }
+
+ referral_len = SMB_VFS_READLINK(conn, path, link_target, bufsize - 1);
+ if (referral_len == -1) {
+ DEBUG(0,("is_msdfs_link_read_target: Error reading "
+ "msdfs link %s: %s\n",
+ path, strerror(errno)));
+ goto err;
+ }
+ link_target[referral_len] = '\0';
+
+ DEBUG(5,("is_msdfs_link_internal: %s -> %s\n",path,
+ link_target));
+
+ if (!strnequal(link_target, "msdfs:", 6)) {
+ goto err;
+ }
+ return True;
+
+ err:
+
+ if (link_target != link_target_buf) {
+ TALLOC_FREE(link_target);
+ }
+ return False;
+}
+
+/**********************************************************************
+ Returns true if the unix path is a valid msdfs symlink.
+**********************************************************************/
+
+bool is_msdfs_link(connection_struct *conn,
+ const char *path,
+ SMB_STRUCT_STAT *sbufp)
+{
+ return is_msdfs_link_internal(talloc_tos(),
+ conn,
+ path,
+ NULL,
+ sbufp);
+}
+
+/*****************************************************************
+ Used by other functions to decide if a dfs path is remote,
+ and to get the list of referred locations for that remote path.
+
+ search_flag: For findfirsts, dfs links themselves are not
+ redirected, but paths beyond the links are. For normal smb calls,
+ even dfs links need to be redirected.
+
+ consumedcntp: how much of the dfs path is being redirected. the client
+ should try the remaining path on the redirected server.
+
+ If this returns NT_STATUS_PATH_NOT_COVERED the contents of the msdfs
+ link redirect are in targetpath.
+*****************************************************************/
+
+static NTSTATUS dfs_path_lookup(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *dfspath, /* Incoming complete dfs path */
+ const struct dfs_path *pdp, /* Parsed out
+ server+share+extrapath. */
+ bool search_flag, /* Called from a findfirst ? */
+ int *consumedcntp,
+ char **pp_targetpath)
+{
+ char *p = NULL;
+ char *q = NULL;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+ char *localpath = NULL;
+ char *canon_dfspath = NULL; /* Canonicalized dfs path. (only '/'
+ components). */
+
+ DEBUG(10,("dfs_path_lookup: Conn path = %s reqpath = %s\n",
+ conn->connectpath, pdp->reqpath));
+
+ /*
+ * Note the unix path conversion here we're doing we can
+ * throw away. We're looking for a symlink for a dfs
+ * resolution, if we don't find it we'll do another
+ * unix_convert later in the codepath.
+ * If we needed to remember what we'd resolved in
+ * dp->reqpath (as the original code did) we'd
+ * copy (localhost, dp->reqpath) on any code
+ * path below that returns True - but I don't
+ * think this is needed. JRA.
+ */
+
+ status = unix_convert(ctx, conn, pdp->reqpath, search_flag, &localpath,
+ NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ return status;
+ }
+
+ /* Optimization - check if we can redirect the whole path. */
+
+ if (is_msdfs_link_internal(ctx, conn, localpath, pp_targetpath, NULL)) {
+ if (search_flag) {
+ DEBUG(6,("dfs_path_lookup (FindFirst) No redirection "
+ "for dfs link %s.\n", dfspath));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(6,("dfs_path_lookup: %s resolves to a "
+ "valid dfs link %s.\n", dfspath,
+ pp_targetpath ? *pp_targetpath : ""));
+
+ if (consumedcntp) {
+ *consumedcntp = strlen(dfspath);
+ }
+ return NT_STATUS_PATH_NOT_COVERED;
+ }
+
+ /* Prepare to test only for '/' components in the given path,
+ * so if a Windows path replace all '\\' characters with '/'.
+ * For a POSIX DFS path we know all separators are already '/'. */
+
+ canon_dfspath = talloc_strdup(ctx, dfspath);
+ if (!canon_dfspath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!pdp->posix_path) {
+ string_replace(canon_dfspath, '\\', '/');
+ }
+
+ /*
+ * localpath comes out of unix_convert, so it has
+ * no trailing backslash. Make sure that canon_dfspath hasn't either.
+ * Fix for bug #4860 from Jan Martin <Jan.Martin@rwedea.com>.
+ */
+
+ trim_char(canon_dfspath,0,'/');
+
+ /*
+ * Redirect if any component in the path is a link.
+ * We do this by walking backwards through the
+ * local path, chopping off the last component
+ * in both the local path and the canonicalized
+ * DFS path. If we hit a DFS link then we're done.
+ */
+
+ p = strrchr_m(localpath, '/');
+ if (consumedcntp) {
+ q = strrchr_m(canon_dfspath, '/');
+ }
+
+ while (p) {
+ *p = '\0';
+ if (q) {
+ *q = '\0';
+ }
+
+ if (is_msdfs_link_internal(ctx, conn,
+ localpath, pp_targetpath, NULL)) {
+ DEBUG(4, ("dfs_path_lookup: Redirecting %s because "
+ "parent %s is dfs link\n", dfspath, localpath));
+
+ if (consumedcntp) {
+ *consumedcntp = strlen(canon_dfspath);
+ DEBUG(10, ("dfs_path_lookup: Path consumed: %s "
+ "(%d)\n",
+ canon_dfspath,
+ *consumedcntp));
+ }
+
+ return NT_STATUS_PATH_NOT_COVERED;
+ }
+
+ /* Step back on the filesystem. */
+ p = strrchr_m(localpath, '/');
+
+ if (consumedcntp) {
+ /* And in the canonicalized dfs path. */
+ q = strrchr_m(canon_dfspath, '/');
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*****************************************************************
+ Decides if a dfs pathname should be redirected or not.
+ If not, the pathname is converted to a tcon-relative local unix path
+
+ search_wcard_flag: this flag performs 2 functions both related
+ to searches. See resolve_dfs_path() and parse_dfs_path_XX()
+ for details.
+
+ This function can return NT_STATUS_OK, meaning use the returned path as-is
+ (mapped into a local path).
+ or NT_STATUS_NOT_COVERED meaning return a DFS redirect, or
+ any other NT_STATUS error which is a genuine error to be
+ returned to the client.
+*****************************************************************/
+
+static NTSTATUS dfs_redirect(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *path_in,
+ bool search_wcard_flag,
+ char **pp_path_out,
+ bool *ppath_contains_wcard)
+{
+ NTSTATUS status;
+ struct dfs_path *pdp = TALLOC_P(ctx, struct dfs_path);
+
+ if (!pdp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = parse_dfs_path(conn, path_in, search_wcard_flag, pdp,
+ ppath_contains_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(pdp);
+ return status;
+ }
+
+ if (pdp->reqpath[0] == '\0') {
+ TALLOC_FREE(pdp);
+ *pp_path_out = talloc_strdup(ctx, "");
+ if (!*pp_path_out) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ DEBUG(5,("dfs_redirect: self-referral.\n"));
+ return NT_STATUS_OK;
+ }
+
+ /* If dfs pathname for a non-dfs share, convert to tcon-relative
+ path and return OK */
+
+ if (!lp_msdfs_root(SNUM(conn))) {
+ *pp_path_out = talloc_strdup(ctx, pdp->reqpath);
+ TALLOC_FREE(pdp);
+ if (!*pp_path_out) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ /* If it looked like a local path (zero hostname/servicename)
+ * just treat as a tcon-relative path. */
+
+ if (pdp->hostname[0] == '\0' && pdp->servicename[0] == '\0') {
+ *pp_path_out = talloc_strdup(ctx, pdp->reqpath);
+ TALLOC_FREE(pdp);
+ if (!*pp_path_out) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (!( strequal(pdp->servicename, lp_servicename(SNUM(conn)))
+ || (strequal(pdp->servicename, HOMES_NAME)
+ && strequal(lp_servicename(SNUM(conn)),
+ conn->server_info->sanitized_username) )) ) {
+
+ /* The given sharename doesn't match this connection. */
+ TALLOC_FREE(pdp);
+
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ status = dfs_path_lookup(ctx, conn, path_in, pdp,
+ search_wcard_flag, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
+ DEBUG(3,("dfs_redirect: Redirecting %s\n", path_in));
+ } else {
+ DEBUG(10,("dfs_redirect: dfs_path_lookup "
+ "failed for %s with %s\n",
+ path_in, nt_errstr(status) ));
+ }
+ return status;
+ }
+
+ DEBUG(3,("dfs_redirect: Not redirecting %s.\n", path_in));
+
+ /* Form non-dfs tcon-relative path */
+ *pp_path_out = talloc_strdup(ctx, pdp->reqpath);
+ TALLOC_FREE(pdp);
+ if (!*pp_path_out) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3,("dfs_redirect: Path %s converted to non-dfs path %s\n",
+ path_in,
+ *pp_path_out));
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Return a self referral.
+**********************************************************************/
+
+static NTSTATUS self_ref(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn,
+ int *consumedcntp,
+ bool *self_referralp)
+{
+ struct referral *ref;
+
+ *self_referralp = True;
+
+ jucn->referral_count = 1;
+ if((ref = TALLOC_ZERO_P(ctx, struct referral)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ref->alternate_path = talloc_strdup(ctx, dfs_path);
+ if (!ref->alternate_path) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Gets valid referrals for a dfs path and fills up the
+ junction_map structure.
+**********************************************************************/
+
+NTSTATUS get_referred_path(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn,
+ int *consumedcntp,
+ bool *self_referralp)
+{
+ struct connection_struct *conn;
+ char *targetpath = NULL;
+ int snum;
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+ bool dummy;
+ struct dfs_path *pdp = TALLOC_P(ctx, struct dfs_path);
+ char *oldpath;
+
+ if (!pdp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *self_referralp = False;
+
+ status = parse_dfs_path(NULL, dfs_path, False, pdp, &dummy);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, pdp->servicename);
+ jucn->volume_name = talloc_strdup(ctx, pdp->reqpath);
+ if (!jucn->service_name || !jucn->volume_name) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Verify the share is a dfs root */
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ fstring service_name;
+ fstrcpy(service_name, jucn->service_name);
+ if ((snum = find_service(service_name)) < 0) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ TALLOC_FREE(jucn->service_name);
+ jucn->service_name = talloc_strdup(ctx, service_name);
+ if (!jucn->service_name) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!lp_msdfs_root(snum) && (*lp_msdfs_proxy(snum) == '\0')) {
+ DEBUG(3,("get_referred_path: |%s| in dfs path %s is not "
+ "a dfs root.\n",
+ pdp->servicename, dfs_path));
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /*
+ * Self referrals are tested with a anonymous IPC connection and
+ * a GET_DFS_REFERRAL call to \\server\share. (which means
+ * dp.reqpath[0] points to an empty string). create_conn_struct cd's
+ * into the directory and will fail if it cannot (as the anonymous
+ * user). Cope with this.
+ */
+
+ if (pdp->reqpath[0] == '\0') {
+ char *tmp;
+ struct referral *ref;
+
+ if (*lp_msdfs_proxy(snum) == '\0') {
+ TALLOC_FREE(pdp);
+ return self_ref(ctx,
+ dfs_path,
+ jucn,
+ consumedcntp,
+ self_referralp);
+ }
+
+ /*
+ * It's an msdfs proxy share. Redirect to
+ * the configured target share.
+ */
+
+ jucn->referral_count = 1;
+ if ((ref = TALLOC_ZERO_P(ctx, struct referral)) == NULL) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!(tmp = talloc_strdup(ctx, lp_msdfs_proxy(snum)))) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ trim_string(tmp, "\\", 0);
+
+ ref->alternate_path = talloc_asprintf(ctx, "\\%s", tmp);
+ TALLOC_FREE(tmp);
+
+ if (!ref->alternate_path) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (pdp->reqpath[0] != '\0') {
+ ref->alternate_path = talloc_asprintf_append(
+ ref->alternate_path,
+ "%s",
+ pdp->reqpath);
+ if (!ref->alternate_path) {
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ TALLOC_FREE(pdp);
+ return NT_STATUS_OK;
+ }
+
+ status = create_conn_struct(ctx, &conn, snum, lp_pathname(snum),
+ &oldpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(pdp);
+ return status;
+ }
+
+ /* If this is a DFS path dfs_lookup should return
+ * NT_STATUS_PATH_NOT_COVERED. */
+
+ status = dfs_path_lookup(ctx, conn, dfs_path, pdp,
+ False, consumedcntp, &targetpath);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
+ DEBUG(3,("get_referred_path: No valid referrals for path %s\n",
+ dfs_path));
+ vfs_ChDir(conn, oldpath);
+ conn_free_internal(conn);
+ TALLOC_FREE(pdp);
+ return status;
+ }
+
+ /* We know this is a valid dfs link. Parse the targetpath. */
+ if (!parse_msdfs_symlink(ctx, targetpath,
+ &jucn->referral_list,
+ &jucn->referral_count)) {
+ DEBUG(3,("get_referred_path: failed to parse symlink "
+ "target %s\n", targetpath ));
+ vfs_ChDir(conn, oldpath);
+ conn_free_internal(conn);
+ TALLOC_FREE(pdp);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ vfs_ChDir(conn, oldpath);
+ conn_free_internal(conn);
+ TALLOC_FREE(pdp);
+ return NT_STATUS_OK;
+}
+
+static int setup_ver2_dfs_referral(const char *pathname,
+ char **ppdata,
+ struct junction_map *junction,
+ int consumedcnt,
+ bool self_referral)
+{
+ char* pdata = *ppdata;
+
+ smb_ucs2_t *uni_requestedpath = NULL;
+ int uni_reqpathoffset1,uni_reqpathoffset2;
+ int uni_curroffset;
+ int requestedpathlen=0;
+ int offset;
+ int reply_size = 0;
+ int i=0;
+
+ DEBUG(10,("Setting up version2 referral\nRequested path:\n"));
+
+ requestedpathlen = rpcstr_push_talloc(talloc_tos(),
+ &uni_requestedpath, pathname);
+ if (uni_requestedpath == NULL || requestedpathlen == 0) {
+ return -1;
+ }
+
+ if (DEBUGLVL(10)) {
+ dump_data(0, (unsigned char *)uni_requestedpath,
+ requestedpathlen);
+ }
+
+ DEBUG(10,("ref count = %u\n",junction->referral_count));
+
+ uni_reqpathoffset1 = REFERRAL_HEADER_SIZE +
+ VERSION2_REFERRAL_SIZE * junction->referral_count;
+
+ uni_reqpathoffset2 = uni_reqpathoffset1 + requestedpathlen;
+
+ uni_curroffset = uni_reqpathoffset2 + requestedpathlen;
+
+ reply_size = REFERRAL_HEADER_SIZE +
+ VERSION2_REFERRAL_SIZE*junction->referral_count +
+ 2 * requestedpathlen;
+ DEBUG(10,("reply_size: %u\n",reply_size));
+
+ /* add up the unicode lengths of all the referral paths */
+ for(i=0;i<junction->referral_count;i++) {
+ DEBUG(10,("referral %u : %s\n",
+ i,
+ junction->referral_list[i].alternate_path));
+ reply_size +=
+ (strlen(junction->referral_list[i].alternate_path)+1)*2;
+ }
+
+ DEBUG(10,("reply_size = %u\n",reply_size));
+ /* add the unexplained 0x16 bytes */
+ reply_size += 0x16;
+
+ pdata = (char *)SMB_REALLOC(pdata,reply_size);
+ if(pdata == NULL) {
+ DEBUG(0,("Realloc failed!\n"));
+ return -1;
+ }
+ *ppdata = pdata;
+
+ /* copy in the dfs requested paths.. required for offset calculations */
+ memcpy(pdata+uni_reqpathoffset1,uni_requestedpath,requestedpathlen);
+ memcpy(pdata+uni_reqpathoffset2,uni_requestedpath,requestedpathlen);
+
+ /* create the header */
+ SSVAL(pdata,0,consumedcnt * 2); /* path consumed */
+ /* number of referral in this pkt */
+ SSVAL(pdata,2,junction->referral_count);
+ if(self_referral) {
+ SIVAL(pdata,4,DFSREF_REFERRAL_SERVER | DFSREF_STORAGE_SERVER);
+ } else {
+ SIVAL(pdata,4,DFSREF_STORAGE_SERVER);
+ }
+
+ offset = 8;
+ /* add the referral elements */
+ for(i=0;i<junction->referral_count;i++) {
+ struct referral* ref = &junction->referral_list[i];
+ int unilen;
+
+ SSVAL(pdata,offset,2); /* version 2 */
+ SSVAL(pdata,offset+2,VERSION2_REFERRAL_SIZE);
+ if(self_referral) {
+ SSVAL(pdata,offset+4,1);
+ } else {
+ SSVAL(pdata,offset+4,0);
+ }
+
+ /* ref_flags :use path_consumed bytes? */
+ SSVAL(pdata,offset+6,0);
+ SIVAL(pdata,offset+8,ref->proximity);
+ SIVAL(pdata,offset+12,ref->ttl);
+
+ SSVAL(pdata,offset+16,uni_reqpathoffset1-offset);
+ SSVAL(pdata,offset+18,uni_reqpathoffset2-offset);
+ /* copy referred path into current offset */
+ unilen = rpcstr_push(pdata+uni_curroffset,
+ ref->alternate_path,
+ reply_size - uni_curroffset,
+ STR_UNICODE);
+
+ SSVAL(pdata,offset+20,uni_curroffset-offset);
+
+ uni_curroffset += unilen;
+ offset += VERSION2_REFERRAL_SIZE;
+ }
+ /* add in the unexplained 22 (0x16) bytes at the end */
+ memset(pdata+uni_curroffset,'\0',0x16);
+ return reply_size;
+}
+
+static int setup_ver3_dfs_referral(const char *pathname,
+ char **ppdata,
+ struct junction_map *junction,
+ int consumedcnt,
+ bool self_referral)
+{
+ char *pdata = *ppdata;
+
+ smb_ucs2_t *uni_reqpath = NULL;
+ int uni_reqpathoffset1, uni_reqpathoffset2;
+ int uni_curroffset;
+ int reply_size = 0;
+
+ int reqpathlen = 0;
+ int offset,i=0;
+
+ DEBUG(10,("setting up version3 referral\n"));
+
+ reqpathlen = rpcstr_push_talloc(talloc_tos(), &uni_reqpath, pathname);
+ if (uni_reqpath == NULL || reqpathlen == 0) {
+ return -1;
+ }
+
+ if (DEBUGLVL(10)) {
+ dump_data(0, (unsigned char *)uni_reqpath,
+ reqpathlen);
+ }
+
+ uni_reqpathoffset1 = REFERRAL_HEADER_SIZE +
+ VERSION3_REFERRAL_SIZE * junction->referral_count;
+ uni_reqpathoffset2 = uni_reqpathoffset1 + reqpathlen;
+ reply_size = uni_curroffset = uni_reqpathoffset2 + reqpathlen;
+
+ for(i=0;i<junction->referral_count;i++) {
+ DEBUG(10,("referral %u : %s\n",
+ i,
+ junction->referral_list[i].alternate_path));
+ reply_size +=
+ (strlen(junction->referral_list[i].alternate_path)+1)*2;
+ }
+
+ pdata = (char *)SMB_REALLOC(pdata,reply_size);
+ if(pdata == NULL) {
+ DEBUG(0,("version3 referral setup:"
+ "malloc failed for Realloc!\n"));
+ return -1;
+ }
+ *ppdata = pdata;
+
+ /* create the header */
+ SSVAL(pdata,0,consumedcnt * 2); /* path consumed */
+ SSVAL(pdata,2,junction->referral_count); /* number of referral */
+ if(self_referral) {
+ SIVAL(pdata,4,DFSREF_REFERRAL_SERVER | DFSREF_STORAGE_SERVER);
+ } else {
+ SIVAL(pdata,4,DFSREF_STORAGE_SERVER);
+ }
+
+ /* copy in the reqpaths */
+ memcpy(pdata+uni_reqpathoffset1,uni_reqpath,reqpathlen);
+ memcpy(pdata+uni_reqpathoffset2,uni_reqpath,reqpathlen);
+
+ offset = 8;
+ for(i=0;i<junction->referral_count;i++) {
+ struct referral* ref = &(junction->referral_list[i]);
+ int unilen;
+
+ SSVAL(pdata,offset,3); /* version 3 */
+ SSVAL(pdata,offset+2,VERSION3_REFERRAL_SIZE);
+ if(self_referral) {
+ SSVAL(pdata,offset+4,1);
+ } else {
+ SSVAL(pdata,offset+4,0);
+ }
+
+ /* ref_flags :use path_consumed bytes? */
+ SSVAL(pdata,offset+6,0);
+ SIVAL(pdata,offset+8,ref->ttl);
+
+ SSVAL(pdata,offset+12,uni_reqpathoffset1-offset);
+ SSVAL(pdata,offset+14,uni_reqpathoffset2-offset);
+ /* copy referred path into current offset */
+ unilen = rpcstr_push(pdata+uni_curroffset,ref->alternate_path,
+ reply_size - uni_curroffset,
+ STR_UNICODE | STR_TERMINATE);
+ SSVAL(pdata,offset+16,uni_curroffset-offset);
+ /* copy 0x10 bytes of 00's in the ServiceSite GUID */
+ memset(pdata+offset+18,'\0',16);
+
+ uni_curroffset += unilen;
+ offset += VERSION3_REFERRAL_SIZE;
+ }
+ return reply_size;
+}
+
+/******************************************************************
+ Set up the DFS referral for the dfs pathname. This call returns
+ the amount of the path covered by this server, and where the
+ client should be redirected to. This is the meat of the
+ TRANS2_GET_DFS_REFERRAL call.
+******************************************************************/
+
+int setup_dfs_referral(connection_struct *orig_conn,
+ const char *dfs_path,
+ int max_referral_level,
+ char **ppdata, NTSTATUS *pstatus)
+{
+ struct junction_map *junction = NULL;
+ int consumedcnt = 0;
+ bool self_referral = False;
+ int reply_size = 0;
+ char *pathnamep = NULL;
+ char *local_dfs_path = NULL;
+ TALLOC_CTX *ctx;
+
+ if (!(ctx=talloc_init("setup_dfs_referral"))) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ /* get the junction entry */
+ if (!dfs_path) {
+ talloc_destroy(ctx);
+ *pstatus = NT_STATUS_NOT_FOUND;
+ return -1;
+ }
+
+ /*
+ * Trim pathname sent by client so it begins with only one backslash.
+ * Two backslashes confuse some dfs clients
+ */
+
+ local_dfs_path = talloc_strdup(ctx,dfs_path);
+ if (!local_dfs_path) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ talloc_destroy(ctx);
+ return -1;
+ }
+ pathnamep = local_dfs_path;
+ while (IS_DIRECTORY_SEP(pathnamep[0]) &&
+ IS_DIRECTORY_SEP(pathnamep[1])) {
+ pathnamep++;
+ }
+
+ junction = TALLOC_ZERO_P(ctx, struct junction_map);
+ if (!junction) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ talloc_destroy(ctx);
+ return -1;
+ }
+
+ /* The following call can change cwd. */
+ *pstatus = get_referred_path(ctx, pathnamep, junction,
+ &consumedcnt, &self_referral);
+ if (!NT_STATUS_IS_OK(*pstatus)) {
+ vfs_ChDir(orig_conn,orig_conn->connectpath);
+ talloc_destroy(ctx);
+ return -1;
+ }
+ vfs_ChDir(orig_conn,orig_conn->connectpath);
+
+ if (!self_referral) {
+ pathnamep[consumedcnt] = '\0';
+
+ if( DEBUGLVL( 3 ) ) {
+ int i=0;
+ dbgtext("setup_dfs_referral: Path %s to "
+ "alternate path(s):",
+ pathnamep);
+ for(i=0;i<junction->referral_count;i++)
+ dbgtext(" %s",
+ junction->referral_list[i].alternate_path);
+ dbgtext(".\n");
+ }
+ }
+
+ /* create the referral depeding on version */
+ DEBUG(10,("max_referral_level :%d\n",max_referral_level));
+
+ if (max_referral_level < 2) {
+ max_referral_level = 2;
+ }
+ if (max_referral_level > 3) {
+ max_referral_level = 3;
+ }
+
+ switch(max_referral_level) {
+ case 2:
+ reply_size = setup_ver2_dfs_referral(pathnamep,
+ ppdata, junction,
+ consumedcnt, self_referral);
+ break;
+ case 3:
+ reply_size = setup_ver3_dfs_referral(pathnamep, ppdata,
+ junction, consumedcnt, self_referral);
+ break;
+ default:
+ DEBUG(0,("setup_dfs_referral: Invalid dfs referral "
+ "version: %d\n",
+ max_referral_level));
+ talloc_destroy(ctx);
+ *pstatus = NT_STATUS_INVALID_LEVEL;
+ return -1;
+ }
+
+ if (DEBUGLVL(10)) {
+ DEBUGADD(0,("DFS Referral pdata:\n"));
+ dump_data(0,(uint8 *)*ppdata,reply_size);
+ }
+
+ talloc_destroy(ctx);
+ *pstatus = NT_STATUS_OK;
+ return reply_size;
+}
+
+/**********************************************************************
+ The following functions are called by the NETDFS RPC pipe functions
+ **********************************************************************/
+
+/*********************************************************************
+ Creates a junction structure from a DFS pathname
+**********************************************************************/
+
+bool create_junction(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn)
+{
+ int snum;
+ bool dummy;
+ struct dfs_path *pdp = TALLOC_P(ctx,struct dfs_path);
+ NTSTATUS status;
+
+ if (!pdp) {
+ return False;
+ }
+ status = parse_dfs_path(NULL, dfs_path, False, pdp, &dummy);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ /* check if path is dfs : validate first token */
+ if (!is_myname_or_ipaddr(pdp->hostname)) {
+ DEBUG(4,("create_junction: Invalid hostname %s "
+ "in dfs path %s\n",
+ pdp->hostname, dfs_path));
+ TALLOC_FREE(pdp);
+ return False;
+ }
+
+ /* Check for a non-DFS share */
+ snum = lp_servicenumber(pdp->servicename);
+
+ if(snum < 0 || !lp_msdfs_root(snum)) {
+ DEBUG(4,("create_junction: %s is not an msdfs root.\n",
+ pdp->servicename));
+ TALLOC_FREE(pdp);
+ return False;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, pdp->servicename);
+ jucn->volume_name = talloc_strdup(ctx, pdp->reqpath);
+ jucn->comment = talloc_strdup(ctx, lp_comment(snum));
+
+ TALLOC_FREE(pdp);
+ if (!jucn->service_name || !jucn->volume_name || ! jucn->comment) {
+ return False;
+ }
+ return True;
+}
+
+/**********************************************************************
+ Forms a valid Unix pathname from the junction
+ **********************************************************************/
+
+static bool junction_to_local_path(const struct junction_map *jucn,
+ char **pp_path_out,
+ connection_struct **conn_out,
+ char **oldpath)
+{
+ int snum;
+ NTSTATUS status;
+
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ return False;
+ }
+ status = create_conn_struct(talloc_tos(), conn_out, snum,
+ lp_pathname(snum), oldpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ *pp_path_out = talloc_asprintf(*conn_out,
+ "%s/%s",
+ lp_pathname(snum),
+ jucn->volume_name);
+ if (!*pp_path_out) {
+ vfs_ChDir(*conn_out, *oldpath);
+ conn_free_internal(*conn_out);
+ return False;
+ }
+ return True;
+}
+
+bool create_msdfs_link(const struct junction_map *jucn)
+{
+ char *path = NULL;
+ char *cwd;
+ char *msdfs_link = NULL;
+ connection_struct *conn;
+ int i=0;
+ bool insert_comma = False;
+ bool ret = False;
+
+ if(!junction_to_local_path(jucn, &path, &conn, &cwd)) {
+ return False;
+ }
+
+ /* Form the msdfs_link contents */
+ msdfs_link = talloc_strdup(conn, "msdfs:");
+ if (!msdfs_link) {
+ goto out;
+ }
+ for(i=0; i<jucn->referral_count; i++) {
+ char *refpath = jucn->referral_list[i].alternate_path;
+
+ /* Alternate paths always use Windows separators. */
+ trim_char(refpath, '\\', '\\');
+ if(*refpath == '\0') {
+ if (i == 0) {
+ insert_comma = False;
+ }
+ continue;
+ }
+ if (i > 0 && insert_comma) {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ ",%s",
+ refpath);
+ } else {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ "%s",
+ refpath);
+ }
+
+ if (!msdfs_link) {
+ goto out;
+ }
+ if (!insert_comma) {
+ insert_comma = True;
+ }
+ }
+
+ DEBUG(5,("create_msdfs_link: Creating new msdfs link: %s -> %s\n",
+ path, msdfs_link));
+
+ if(SMB_VFS_SYMLINK(conn, msdfs_link, path) < 0) {
+ if (errno == EEXIST) {
+ if(SMB_VFS_UNLINK(conn,path)!=0) {
+ goto out;
+ }
+ }
+ if (SMB_VFS_SYMLINK(conn, msdfs_link, path) < 0) {
+ DEBUG(1,("create_msdfs_link: symlink failed "
+ "%s -> %s\nError: %s\n",
+ path, msdfs_link, strerror(errno)));
+ goto out;
+ }
+ }
+
+ ret = True;
+
+out:
+ vfs_ChDir(conn, cwd);
+ conn_free_internal(conn);
+ return ret;
+}
+
+bool remove_msdfs_link(const struct junction_map *jucn)
+{
+ char *path = NULL;
+ char *cwd;
+ connection_struct *conn;
+ bool ret = False;
+
+ if (!junction_to_local_path(jucn, &path, &conn, &cwd)) {
+ return false;
+ }
+
+ if( SMB_VFS_UNLINK(conn, path) == 0 ) {
+ ret = True;
+ }
+
+ vfs_ChDir(conn, cwd);
+ conn_free_internal(conn);
+ return ret;
+}
+
+/*********************************************************************
+ Return the number of DFS links at the root of this share.
+*********************************************************************/
+
+static int count_dfs_links(TALLOC_CTX *ctx, int snum)
+{
+ size_t cnt = 0;
+ SMB_STRUCT_DIR *dirp = NULL;
+ char *dname = NULL;
+ const char *connect_path = lp_pathname(snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(snum);
+ connection_struct *conn;
+ NTSTATUS status;
+ char *cwd;
+
+ if(*connect_path == '\0') {
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct(talloc_tos(), &conn, snum, connect_path,
+ &cwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ return 0;
+ }
+
+ /* Count a link for the msdfs root - convention */
+ cnt = 1;
+
+ /* No more links if this is an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ dirp = SMB_VFS_OPENDIR(conn, ".", NULL, 0);
+ if(!dirp) {
+ goto out;
+ }
+
+ while ((dname = vfs_readdirname(conn, dirp)) != NULL) {
+ if (is_msdfs_link(conn,
+ dname,
+ NULL)) {
+ cnt++;
+ }
+ }
+
+ SMB_VFS_CLOSEDIR(conn,dirp);
+
+out:
+ vfs_ChDir(conn, cwd);
+ conn_free_internal(conn);
+ return cnt;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static int form_junctions(TALLOC_CTX *ctx,
+ int snum,
+ struct junction_map *jucn,
+ size_t jn_remain)
+{
+ size_t cnt = 0;
+ SMB_STRUCT_DIR *dirp = NULL;
+ char *dname = NULL;
+ const char *connect_path = lp_pathname(snum);
+ char *service_name = lp_servicename(snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(snum);
+ connection_struct *conn;
+ struct referral *ref = NULL;
+ char *cwd;
+ NTSTATUS status;
+
+ if (jn_remain == 0) {
+ return 0;
+ }
+
+ if(*connect_path == '\0') {
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct(ctx, &conn, snum, connect_path, &cwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ return 0;
+ }
+
+ /* form a junction for the msdfs root - convention
+ DO NOT REMOVE THIS: NT clients will not work with us
+ if this is not present
+ */
+ jucn[cnt].service_name = talloc_strdup(ctx,service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx, "");
+ if (!jucn[cnt].service_name || !jucn[cnt].volume_name) {
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ jucn[cnt].referral_count = 1;
+
+ ref = jucn[cnt].referral_list = TALLOC_ZERO_P(ctx, struct referral);
+ if (jucn[cnt].referral_list == NULL) {
+ goto out;
+ }
+
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ if (*msdfs_proxy != '\0') {
+ ref->alternate_path = talloc_strdup(ctx,
+ msdfs_proxy);
+ } else {
+ ref->alternate_path = talloc_asprintf(ctx,
+ "\\\\%s\\%s",
+ get_local_machine_name(),
+ service_name);
+ }
+
+ if (!ref->alternate_path) {
+ goto out;
+ }
+ cnt++;
+
+ /* Don't enumerate if we're an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ dirp = SMB_VFS_OPENDIR(conn, ".", NULL, 0);
+ if(!dirp) {
+ goto out;
+ }
+
+ while ((dname = vfs_readdirname(conn, dirp)) != NULL) {
+ char *link_target = NULL;
+ if (cnt >= jn_remain) {
+ DEBUG(2, ("form_junctions: ran out of MSDFS "
+ "junction slots"));
+ goto out;
+ }
+ if (is_msdfs_link_internal(ctx,
+ conn,
+ dname, &link_target,
+ NULL)) {
+ if (parse_msdfs_symlink(ctx,
+ link_target,
+ &jucn[cnt].referral_list,
+ &jucn[cnt].referral_count)) {
+
+ jucn[cnt].service_name = talloc_strdup(ctx,
+ service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx,
+ dname);
+ if (!jucn[cnt].service_name ||
+ !jucn[cnt].volume_name) {
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ cnt++;
+ }
+ TALLOC_FREE(link_target);
+ }
+ }
+
+out:
+
+ if (dirp) {
+ SMB_VFS_CLOSEDIR(conn,dirp);
+ }
+
+ vfs_ChDir(conn, cwd);
+ conn_free_internal(conn);
+ return cnt;
+}
+
+struct junction_map *enum_msdfs_links(TALLOC_CTX *ctx, size_t *p_num_jn)
+{
+ struct junction_map *jn = NULL;
+ int i=0;
+ size_t jn_count = 0;
+ int sharecount = 0;
+
+ *p_num_jn = 0;
+ if(!lp_host_msdfs()) {
+ return NULL;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ load_registry_shares();
+ sharecount = load_usershare_shares();
+ unbecome_root();
+
+ for(i=0;i < sharecount;i++) {
+ if(lp_msdfs_root(i)) {
+ jn_count += count_dfs_links(ctx, i);
+ }
+ }
+ if (jn_count == 0) {
+ return NULL;
+ }
+ jn = TALLOC_ARRAY(ctx, struct junction_map, jn_count);
+ if (!jn) {
+ return NULL;
+ }
+ for(i=0; i < sharecount; i++) {
+ if (*p_num_jn >= jn_count) {
+ break;
+ }
+ if(lp_msdfs_root(i)) {
+ *p_num_jn += form_junctions(ctx, i,
+ &jn[*p_num_jn],
+ jn_count - *p_num_jn);
+ }
+ }
+ return jn;
+}
+
+/******************************************************************************
+ Core function to resolve a dfs pathname.
+******************************************************************************/
+
+NTSTATUS resolve_dfspath(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ bool dfs_pathnames,
+ const char *name_in,
+ char **pp_name_out)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ bool dummy;
+ if (dfs_pathnames) {
+ status = dfs_redirect(ctx,
+ conn,
+ name_in,
+ False,
+ pp_name_out,
+ &dummy);
+ } else {
+ /*
+ * Cheat and just return a copy of the in ptr.
+ * Once srvstr_get_path() uses talloc it'll
+ * be a talloced ptr anyway.
+ */
+ *pp_name_out = CONST_DISCARD(char *,name_in);
+ }
+ return status;
+}
+
+/******************************************************************************
+ Core function to resolve a dfs pathname possibly containing a wildcard.
+ This function is identical to the above except for the bool param to
+ dfs_redirect but I need this to be separate so it's really clear when
+ we're allowing wildcards and when we're not. JRA.
+******************************************************************************/
+
+NTSTATUS resolve_dfspath_wcard(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ bool dfs_pathnames,
+ const char *name_in,
+ char **pp_name_out,
+ bool *ppath_contains_wcard)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ if (dfs_pathnames) {
+ status = dfs_redirect(ctx,
+ conn,
+ name_in,
+ True,
+ pp_name_out,
+ ppath_contains_wcard);
+ } else {
+ /*
+ * Cheat and just return a copy of the in ptr.
+ * Once srvstr_get_path() uses talloc it'll
+ * be a talloced ptr anyway.
+ */
+ *pp_name_out = CONST_DISCARD(char *,name_in);
+ }
+ return status;
+}
diff --git a/source3/smbd/negprot.c b/source3/smbd/negprot.c
new file mode 100644
index 0000000000..84f111fb02
--- /dev/null
+++ b/source3/smbd/negprot.c
@@ -0,0 +1,691 @@
+/*
+ Unix SMB/CIFS implementation.
+ negprot reply code
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2007
+
+ 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"
+
+extern fstring remote_proto;
+extern enum protocol_types Protocol;
+extern int max_recv;
+
+bool global_encrypted_passwords_negotiated = False;
+bool global_spnego_negotiated = False;
+struct auth_context *negprot_global_auth_context = NULL;
+
+static void get_challenge(uint8 buff[8])
+{
+ NTSTATUS nt_status;
+ const uint8 *cryptkey;
+
+ /* We might be called more than once, multiple negprots are
+ * permitted */
+ if (negprot_global_auth_context) {
+ DEBUG(3, ("get challenge: is this a secondary negprot? negprot_global_auth_context is non-NULL!\n"));
+ (negprot_global_auth_context->free)(&negprot_global_auth_context);
+ }
+
+ DEBUG(10, ("get challenge: creating negprot_global_auth_context\n"));
+ if (!NT_STATUS_IS_OK(nt_status = make_auth_context_subsystem(&negprot_global_auth_context))) {
+ DEBUG(0, ("make_auth_context_subsystem returned %s", nt_errstr(nt_status)));
+ smb_panic("cannot make_negprot_global_auth_context!");
+ }
+ DEBUG(10, ("get challenge: getting challenge\n"));
+ cryptkey = negprot_global_auth_context->get_ntlm_challenge(negprot_global_auth_context);
+ memcpy(buff, cryptkey, 8);
+}
+
+/****************************************************************************
+ Reply for the core protocol.
+****************************************************************************/
+
+static void reply_corep(struct smb_request *req, uint16 choice)
+{
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, choice);
+
+ Protocol = PROTOCOL_CORE;
+}
+
+/****************************************************************************
+ Reply for the coreplus protocol.
+****************************************************************************/
+
+static void reply_coreplus(struct smb_request *req, uint16 choice)
+{
+ int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
+
+ reply_outbuf(req, 13, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SSVAL(req->outbuf,smb_vwv5,raw); /* tell redirector we support
+ readbraw and writebraw (possibly) */
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg,FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv1,0x1); /* user level security, don't
+ * encrypt */
+ Protocol = PROTOCOL_COREPLUS;
+}
+
+/****************************************************************************
+ Reply for the lanman 1.0 protocol.
+****************************************************************************/
+
+static void reply_lanman1(struct smb_request *req, uint16 choice)
+{
+ int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
+ int secword=0;
+ time_t t = time(NULL);
+
+ global_encrypted_passwords_negotiated = lp_encrypted_passwords();
+
+ if (lp_security()>=SEC_USER)
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (global_encrypted_passwords_negotiated)
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+
+ reply_outbuf(req, 13, global_encrypted_passwords_negotiated?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SSVAL(req->outbuf,smb_vwv1,secword);
+ /* Create a token value and add it to the outgoing packet. */
+ if (global_encrypted_passwords_negotiated) {
+ get_challenge((uint8 *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ Protocol = PROTOCOL_LANMAN1;
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg,FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2,max_recv);
+ SSVAL(req->outbuf,smb_vwv3,lp_maxmux()); /* maxmux */
+ SSVAL(req->outbuf,smb_vwv4,1);
+ SSVAL(req->outbuf,smb_vwv5,raw); /* tell redirector we support
+ readbraw writebraw (possibly) */
+ SIVAL(req->outbuf,smb_vwv6,sys_getpid());
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+
+ return;
+}
+
+/****************************************************************************
+ Reply for the lanman 2.0 protocol.
+****************************************************************************/
+
+static void reply_lanman2(struct smb_request *req, uint16 choice)
+{
+ int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
+ int secword=0;
+ time_t t = time(NULL);
+
+ global_encrypted_passwords_negotiated = lp_encrypted_passwords();
+
+ if (lp_security()>=SEC_USER)
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (global_encrypted_passwords_negotiated)
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+
+ reply_outbuf(req, 13, global_encrypted_passwords_negotiated?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SSVAL(req->outbuf,smb_vwv1,secword);
+ SIVAL(req->outbuf,smb_vwv6,sys_getpid());
+
+ /* Create a token value and add it to the outgoing packet. */
+ if (global_encrypted_passwords_negotiated) {
+ get_challenge((uint8 *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ Protocol = PROTOCOL_LANMAN2;
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg,FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2,max_recv);
+ SSVAL(req->outbuf,smb_vwv3,lp_maxmux());
+ SSVAL(req->outbuf,smb_vwv4,1);
+ SSVAL(req->outbuf,smb_vwv5,raw); /* readbraw and/or writebraw */
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+}
+
+/****************************************************************************
+ Generate the spnego negprot reply blob. Return the number of bytes used.
+****************************************************************************/
+
+static DATA_BLOB negprot_spnego(void)
+{
+ DATA_BLOB blob;
+ nstring dos_name;
+ fstring unix_name;
+#ifdef DEVELOPER
+ size_t slen;
+#endif
+ char guid[17];
+ const char *OIDs_krb5[] = {OID_KERBEROS5,
+ OID_KERBEROS5_OLD,
+ OID_NTLMSSP,
+ NULL};
+ const char *OIDs_plain[] = {OID_NTLMSSP, NULL};
+
+ global_spnego_negotiated = True;
+
+ memset(guid, '\0', sizeof(guid));
+
+ safe_strcpy(unix_name, global_myname(), sizeof(unix_name)-1);
+ strlower_m(unix_name);
+ push_ascii_nstring(dos_name, unix_name);
+ safe_strcpy(guid, dos_name, sizeof(guid)-1);
+
+#ifdef DEVELOPER
+ /* Fix valgrind 'uninitialized bytes' issue. */
+ slen = strlen(dos_name);
+ if (slen < sizeof(guid)) {
+ memset(guid+slen, '\0', sizeof(guid) - slen);
+ }
+#endif
+
+ /* strangely enough, NT does not sent the single OID NTLMSSP when
+ not a ADS member, it sends no OIDs at all
+
+ OLD COMMENT : "we can't do this until we teach our sesssion setup parser to know
+ about raw NTLMSSP (clients send no ASN.1 wrapping if we do this)"
+
+ Our sessionsetup code now handles raw NTLMSSP connects, so we can go
+ back to doing what W2K3 does here. This is needed to make PocketPC 2003
+ CIFS connections work with SPNEGO. See bugzilla bugs #1828 and #3133
+ for details. JRA.
+
+ */
+
+ if (lp_security() != SEC_ADS && !lp_use_kerberos_keytab()) {
+#if 0
+ /* Code for PocketPC client */
+ blob = data_blob(guid, 16);
+#else
+ /* Code for standalone WXP client */
+ blob = spnego_gen_negTokenInit(guid, OIDs_plain, "NONE");
+#endif
+ } else {
+ fstring myname;
+ char *host_princ_s = NULL;
+ name_to_fqdn(myname, global_myname());
+ strlower_m(myname);
+ if (asprintf(&host_princ_s, "cifs/%s@%s", myname, lp_realm())
+ == -1) {
+ return data_blob_null;
+ }
+ blob = spnego_gen_negTokenInit(guid, OIDs_krb5, host_princ_s);
+ SAFE_FREE(host_princ_s);
+ }
+
+ return blob;
+}
+
+/****************************************************************************
+ Reply for the nt protocol.
+****************************************************************************/
+
+static void reply_nt1(struct smb_request *req, uint16 choice)
+{
+ /* dual names + lock_and_read + nt SMBs + remote API calls */
+ int capabilities = CAP_NT_FIND|CAP_LOCK_AND_READ|
+ CAP_LEVEL_II_OPLOCKS;
+
+ int secword=0;
+ char *p, *q;
+ bool negotiate_spnego = False;
+ time_t t = time(NULL);
+ ssize_t ret;
+
+ global_encrypted_passwords_negotiated = lp_encrypted_passwords();
+
+ /* Check the flags field to see if this is Vista.
+ WinXP sets it and Vista does not. But we have to
+ distinguish from NT which doesn't set it either. */
+
+ if ( (req->flags2 & FLAGS2_EXTENDED_SECURITY) &&
+ ((req->flags2 & FLAGS2_UNKNOWN_BIT4) == 0) )
+ {
+ if (get_remote_arch() != RA_SAMBA) {
+ set_remote_arch( RA_VISTA );
+ }
+ }
+
+ reply_outbuf(req,17,0);
+
+ /* do spnego in user level security if the client
+ supports it and we can do encrypted passwords */
+
+ if (global_encrypted_passwords_negotiated &&
+ (lp_security() != SEC_SHARE) &&
+ lp_use_spnego() &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+ negotiate_spnego = True;
+ capabilities |= CAP_EXTENDED_SECURITY;
+ add_to_common_flags2(FLAGS2_EXTENDED_SECURITY);
+ /* Ensure FLAGS2_EXTENDED_SECURITY gets set in this reply
+ (already partially constructed. */
+ SSVAL(req->outbuf, smb_flg2,
+ req->flags2 | FLAGS2_EXTENDED_SECURITY);
+ }
+
+ capabilities |= CAP_NT_SMBS|CAP_RPC_REMOTE_APIS|CAP_UNICODE;
+
+ if (lp_unix_extensions()) {
+ capabilities |= CAP_UNIX;
+ }
+
+ if (lp_large_readwrite() && (SMB_OFF_T_BITS == 64))
+ capabilities |= CAP_LARGE_READX|CAP_LARGE_WRITEX|CAP_W2K_SMBS;
+
+ if (SMB_OFF_T_BITS == 64)
+ capabilities |= CAP_LARGE_FILES;
+
+ if (lp_readraw() && lp_writeraw())
+ capabilities |= CAP_RAW_MODE;
+
+ if (lp_nt_status_support())
+ capabilities |= CAP_STATUS32;
+
+ if (lp_host_msdfs())
+ capabilities |= CAP_DFS;
+
+ if (lp_security() >= SEC_USER)
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (global_encrypted_passwords_negotiated)
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+
+ if (lp_server_signing()) {
+ if (lp_security() >= SEC_USER) {
+ secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED;
+ /* No raw mode with smb signing. */
+ capabilities &= ~CAP_RAW_MODE;
+ if (lp_server_signing() == Required)
+ secword |=NEGOTIATE_SECURITY_SIGNATURES_REQUIRED;
+ srv_set_signing_negotiated();
+ } else {
+ DEBUG(0,("reply_nt1: smb signing is incompatible with share level security !\n"));
+ if (lp_server_signing() == Required) {
+ exit_server_cleanly("reply_nt1: smb signing required and share level security selected.");
+ }
+ }
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SCVAL(req->outbuf,smb_vwv1,secword);
+
+ Protocol = PROTOCOL_NT1;
+
+ SSVAL(req->outbuf,smb_vwv1+1,lp_maxmux()); /* maxmpx */
+ SSVAL(req->outbuf,smb_vwv2+1,1); /* num vcs */
+ SIVAL(req->outbuf,smb_vwv3+1,max_recv); /* max buffer. LOTS! */
+ SIVAL(req->outbuf,smb_vwv5+1,0x10000); /* raw size. full 64k */
+ SIVAL(req->outbuf,smb_vwv7+1,sys_getpid()); /* session key */
+ SIVAL(req->outbuf,smb_vwv9+1,capabilities); /* capabilities */
+ put_long_date((char *)req->outbuf+smb_vwv11+1,t);
+ SSVALS(req->outbuf,smb_vwv15+1,set_server_zone_offset(t)/60);
+
+ p = q = smb_buf(req->outbuf);
+ if (!negotiate_spnego) {
+ /* Create a token value and add it to the outgoing packet. */
+ if (global_encrypted_passwords_negotiated) {
+ uint8 chal[8];
+ /* note that we do not send a challenge at all if
+ we are using plaintext */
+ get_challenge(chal);
+ ret = message_push_blob(
+ &req->outbuf, data_blob_const(chal, sizeof(chal)));
+ if (ret == -1) {
+ DEBUG(0, ("Could not push challenge\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ SCVAL(req->outbuf, smb_vwv16+1, ret);
+ p += ret;
+ }
+ ret = message_push_string(&req->outbuf, lp_workgroup(),
+ STR_UNICODE|STR_TERMINATE
+ |STR_NOALIGN);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push challenge\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ DEBUG(3,("not using SPNEGO\n"));
+ } else {
+ DATA_BLOB spnego_blob = negprot_spnego();
+
+ if (spnego_blob.data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ ret = message_push_blob(&req->outbuf, spnego_blob);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push spnego blob\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ p += ret;
+ data_blob_free(&spnego_blob);
+
+ SCVAL(req->outbuf,smb_vwv16+1, 0);
+ DEBUG(3,("using SPNEGO\n"));
+ }
+
+ SSVAL(req->outbuf,smb_vwv17, p - q); /* length of challenge+domain
+ * strings */
+
+ return;
+}
+
+/* these are the protocol lists used for auto architecture detection:
+
+WinNT 3.51:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win95:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win2K:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Vista:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+protocol [SMB 2.001]
+
+OS/2:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [LANMAN1.0]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+*/
+
+/*
+ * Modified to recognize the architecture of the remote machine better.
+ *
+ * This appears to be the matrix of which protocol is used by which
+ * MS product.
+ Protocol WfWg Win95 WinNT Win2K OS/2 Vista
+ PC NETWORK PROGRAM 1.0 1 1 1 1 1 1
+ XENIX CORE 2 2
+ MICROSOFT NETWORKS 3.0 2 2
+ DOS LM1.2X002 3 3
+ MICROSOFT NETWORKS 1.03 3
+ DOS LANMAN2.1 4 4
+ LANMAN1.0 4 2 3 2
+ Windows for Workgroups 3.1a 5 5 5 3 3
+ LM1.2X002 6 4 4 4
+ LANMAN2.1 7 5 5 5
+ NT LM 0.12 6 8 6 6
+ SMB 2.001 7
+ *
+ * tim@fsg.com 09/29/95
+ * Win2K added by matty 17/7/99
+ */
+
+#define ARCH_WFWG 0x3 /* This is a fudge because WfWg is like Win95 */
+#define ARCH_WIN95 0x2
+#define ARCH_WINNT 0x4
+#define ARCH_WIN2K 0xC /* Win2K is like NT */
+#define ARCH_OS2 0x14 /* Again OS/2 is like NT */
+#define ARCH_SAMBA 0x20
+#define ARCH_CIFSFS 0x40
+#define ARCH_VISTA 0x8C /* Vista is like XP/2K */
+
+#define ARCH_ALL 0x7F
+
+/* List of supported protocols, most desired first */
+static const struct {
+ const char *proto_name;
+ const char *short_name;
+ void (*proto_reply_fn)(struct smb_request *req, uint16 choice);
+ int protocol_level;
+} supported_protocols[] = {
+ {"NT LANMAN 1.0", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"NT LM 0.12", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"POSIX 2", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"LANMAN2.1", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"Samba", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"DOS LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LANMAN1.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {"MICROSOFT NETWORKS 3.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {"MICROSOFT NETWORKS 1.03", "COREPLUS", reply_coreplus, PROTOCOL_COREPLUS},
+ {"PC NETWORK PROGRAM 1.0", "CORE", reply_corep, PROTOCOL_CORE},
+ {NULL,NULL,NULL,0},
+};
+
+/****************************************************************************
+ Reply to a negprot.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_negprot(struct smb_request *req)
+{
+ size_t size = smb_len(req->inbuf) + 4;
+ int choice= -1;
+ int protocol;
+ char *p;
+ int bcc = SVAL(smb_buf(req->inbuf),-2);
+ int arch = ARCH_ALL;
+ int num_cliprotos;
+ char **cliprotos;
+ int i;
+ size_t converted_size;
+
+ static bool done_negprot = False;
+
+ START_PROFILE(SMBnegprot);
+
+ if (done_negprot) {
+ END_PROFILE(SMBnegprot);
+ exit_server_cleanly("multiple negprot's are not permitted");
+ }
+ done_negprot = True;
+
+ if (req->inbuf[size-1] != '\0') {
+ DEBUG(0, ("negprot protocols not 0-terminated\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ p = smb_buf(req->inbuf) + 1;
+
+ num_cliprotos = 0;
+ cliprotos = NULL;
+
+ while (p < (smb_buf(req->inbuf) + bcc)) {
+
+ char **tmp;
+
+ tmp = TALLOC_REALLOC_ARRAY(talloc_tos(), cliprotos, char *,
+ num_cliprotos+1);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ cliprotos = tmp;
+
+ if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
+ &converted_size)) {
+ DEBUG(0, ("pull_ascii_talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ DEBUG(3, ("Requested protocol [%s]\n",
+ cliprotos[num_cliprotos]));
+
+ num_cliprotos += 1;
+ p += strlen(p) + 2;
+ }
+
+ for (i=0; i<num_cliprotos; i++) {
+ if (strcsequal(cliprotos[i], "Windows for Workgroups 3.1a"))
+ arch &= ( ARCH_WFWG | ARCH_WIN95 | ARCH_WINNT
+ | ARCH_WIN2K );
+ else if (strcsequal(cliprotos[i], "DOS LM1.2X002"))
+ arch &= ( ARCH_WFWG | ARCH_WIN95 );
+ else if (strcsequal(cliprotos[i], "DOS LANMAN2.1"))
+ arch &= ( ARCH_WFWG | ARCH_WIN95 );
+ else if (strcsequal(cliprotos[i], "NT LM 0.12"))
+ arch &= ( ARCH_WIN95 | ARCH_WINNT | ARCH_WIN2K
+ | ARCH_CIFSFS);
+ else if (strcsequal(cliprotos[i], "SMB 2.001"))
+ arch = ARCH_VISTA;
+ else if (strcsequal(cliprotos[i], "LANMAN2.1"))
+ arch &= ( ARCH_WINNT | ARCH_WIN2K | ARCH_OS2 );
+ else if (strcsequal(cliprotos[i], "LM1.2X002"))
+ arch &= ( ARCH_WINNT | ARCH_WIN2K | ARCH_OS2 );
+ else if (strcsequal(cliprotos[i], "MICROSOFT NETWORKS 1.03"))
+ arch &= ARCH_WINNT;
+ else if (strcsequal(cliprotos[i], "XENIX CORE"))
+ arch &= ( ARCH_WINNT | ARCH_OS2 );
+ else if (strcsequal(cliprotos[i], "Samba")) {
+ arch = ARCH_SAMBA;
+ break;
+ } else if (strcsequal(cliprotos[i], "POSIX 2")) {
+ arch = ARCH_CIFSFS;
+ break;
+ }
+ }
+
+ /* CIFSFS can send one arch only, NT LM 0.12. */
+ if (i == 1 && (arch & ARCH_CIFSFS)) {
+ arch = ARCH_CIFSFS;
+ }
+
+ switch ( arch ) {
+ case ARCH_CIFSFS:
+ set_remote_arch(RA_CIFSFS);
+ break;
+ case ARCH_SAMBA:
+ set_remote_arch(RA_SAMBA);
+ break;
+ case ARCH_WFWG:
+ set_remote_arch(RA_WFWG);
+ break;
+ case ARCH_WIN95:
+ set_remote_arch(RA_WIN95);
+ break;
+ case ARCH_WINNT:
+ if(req->flags2 == FLAGS2_WIN2K_SIGNATURE)
+ set_remote_arch(RA_WIN2K);
+ else
+ set_remote_arch(RA_WINNT);
+ break;
+ case ARCH_WIN2K:
+ /* Vista may have been set in the negprot so don't
+ override it here */
+ if ( get_remote_arch() != RA_VISTA )
+ set_remote_arch(RA_WIN2K);
+ break;
+ case ARCH_VISTA:
+ set_remote_arch(RA_VISTA);
+ break;
+ case ARCH_OS2:
+ set_remote_arch(RA_OS2);
+ break;
+ default:
+ set_remote_arch(RA_UNKNOWN);
+ break;
+ }
+
+ /* possibly reload - change of architecture */
+ reload_services(True);
+
+ /* moved from the netbios session setup code since we don't have that
+ when the client connects to port 445. Of course there is a small
+ window where we are listening to messages -- jerry */
+
+ claim_connection(
+ NULL,"",FLAG_MSG_GENERAL|FLAG_MSG_SMBD|FLAG_MSG_PRINT_GENERAL);
+
+ /* Check for protocols, most desirable first */
+ for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
+ i = 0;
+ if ((supported_protocols[protocol].protocol_level <= lp_maxprotocol()) &&
+ (supported_protocols[protocol].protocol_level >= lp_minprotocol()))
+ while (i < num_cliprotos) {
+ if (strequal(cliprotos[i],supported_protocols[protocol].proto_name))
+ choice = i;
+ i++;
+ }
+ if(choice != -1)
+ break;
+ }
+
+ if(choice != -1) {
+ fstrcpy(remote_proto,supported_protocols[protocol].short_name);
+ reload_services(True);
+ supported_protocols[protocol].proto_reply_fn(req, choice);
+ DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
+ } else {
+ DEBUG(0,("No protocol supported !\n"));
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, choice);
+ }
+
+ DEBUG( 5, ( "negprot index=%d\n", choice ) );
+
+ if ((lp_server_signing() == Required) && (Protocol < PROTOCOL_NT1)) {
+ exit_server_cleanly("SMB signing is required and "
+ "client negotiated a downlevel protocol");
+ }
+
+ TALLOC_FREE(cliprotos);
+ END_PROFILE(SMBnegprot);
+ return;
+}
diff --git a/source3/smbd/noquotas.c b/source3/smbd/noquotas.c
new file mode 100644
index 0000000000..c8ff8edf62
--- /dev/null
+++ b/source3/smbd/noquotas.c
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ No support for quotas :-).
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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"
+
+/*
+ * Needed for auto generation of proto.h.
+ */
+
+bool disk_quotas(const char *path,SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+{
+ (*bsize) = 512; /* This value should be ignored */
+
+ /* And just to be sure we set some values that hopefully */
+ /* will be larger that any possible real-world value */
+ (*dfree) = (SMB_BIG_UINT)-1;
+ (*dsize) = (SMB_BIG_UINT)-1;
+
+ /* As we have select not to use quotas, allways fail */
+ return False;
+}
diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c
new file mode 100644
index 0000000000..139dfe7d5b
--- /dev/null
+++ b/source3/smbd/notify.c
@@ -0,0 +1,524 @@
+/*
+ Unix SMB/CIFS implementation.
+ change notify handling
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Jeremy Allison 1994-1998
+ Copyright (C) Volker Lendecke 2007
+
+ 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"
+
+struct notify_change_request {
+ struct notify_change_request *prev, *next;
+ struct files_struct *fsp; /* backpointer for cancel by mid */
+ uint8 request_buf[smb_size];
+ uint32 filter;
+ uint32 max_param;
+ struct notify_mid_map *mid_map;
+ void *backend_data;
+};
+
+static void notify_fsp(files_struct *fsp, uint32 action, const char *name);
+
+static struct notify_mid_map *notify_changes_by_mid;
+
+/*
+ * For NTCancel, we need to find the notify_change_request indexed by
+ * mid. Separate list here.
+ */
+
+struct notify_mid_map {
+ struct notify_mid_map *prev, *next;
+ struct notify_change_request *req;
+ uint16 mid;
+};
+
+static bool notify_change_record_identical(struct notify_change *c1,
+ struct notify_change *c2)
+{
+ /* Note this is deliberately case sensitive. */
+ if (c1->action == c2->action &&
+ strcmp(c1->name, c2->name) == 0) {
+ return True;
+ }
+ return False;
+}
+
+static bool notify_marshall_changes(int num_changes,
+ uint32 max_offset,
+ struct notify_change *changes,
+ prs_struct *ps)
+{
+ int i;
+ UNISTR uni_name;
+
+ uni_name.buffer = NULL;
+
+ for (i=0; i<num_changes; i++) {
+ struct notify_change *c;
+ size_t namelen;
+ uint32 u32_tmp; /* Temp arg to prs_uint32 to avoid
+ * signed/unsigned issues */
+
+ /* Coalesce any identical records. */
+ while (i+1 < num_changes &&
+ notify_change_record_identical(&changes[i],
+ &changes[i+1])) {
+ i++;
+ }
+
+ c = &changes[i];
+
+ if (!convert_string_allocate(NULL, CH_UNIX, CH_UTF16LE,
+ c->name, strlen(c->name)+1, &uni_name.buffer,
+ &namelen, True) || (uni_name.buffer == NULL)) {
+ goto fail;
+ }
+
+ namelen -= 2; /* Dump NULL termination */
+
+ /*
+ * Offset to next entry, only if there is one
+ */
+
+ u32_tmp = (i == num_changes-1) ? 0 : namelen + 12;
+ if (!prs_uint32("offset", ps, 1, &u32_tmp)) goto fail;
+
+ u32_tmp = c->action;
+ if (!prs_uint32("action", ps, 1, &u32_tmp)) goto fail;
+
+ u32_tmp = namelen;
+ if (!prs_uint32("namelen", ps, 1, &u32_tmp)) goto fail;
+
+ if (!prs_unistr("name", ps, 1, &uni_name)) goto fail;
+
+ /*
+ * Not NULL terminated, decrease by the 2 UCS2 \0 chars
+ */
+ prs_set_offset(ps, prs_offset(ps)-2);
+
+ SAFE_FREE(uni_name.buffer);
+
+ if (prs_offset(ps) > max_offset) {
+ /* Too much data for client. */
+ DEBUG(10, ("Client only wanted %d bytes, trying to "
+ "marshall %d bytes\n", (int)max_offset,
+ (int)prs_offset(ps)));
+ return False;
+ }
+ }
+
+ return True;
+
+ fail:
+ SAFE_FREE(uni_name.buffer);
+ return False;
+}
+
+/****************************************************************************
+ Setup the common parts of the return packet and send it.
+*****************************************************************************/
+
+static void change_notify_reply_packet(connection_struct *conn,
+ const uint8 *request_buf,
+ NTSTATUS error_code)
+{
+ char outbuf[smb_size+38];
+
+ memset(outbuf, '\0', sizeof(outbuf));
+ construct_reply_common((char *)request_buf, outbuf);
+
+ ERROR_NT(error_code);
+
+ /*
+ * Seems NT needs a transact command with an error code
+ * in it. This is a longer packet than a simple error.
+ */
+ srv_set_message(outbuf,18,0,False);
+
+ show_msg(outbuf);
+ if (!srv_send_smb(smbd_server_fd(),
+ outbuf,
+ IS_CONN_ENCRYPTED(conn)))
+ exit_server_cleanly("change_notify_reply_packet: srv_send_smb "
+ "failed.");
+}
+
+void change_notify_reply(connection_struct *conn,
+ const uint8 *request_buf, uint32 max_param,
+ struct notify_change_buf *notify_buf)
+{
+ prs_struct ps;
+ struct smb_request *req = NULL;
+ uint8 tmp_request[smb_size];
+
+ if (notify_buf->num_changes == -1) {
+ change_notify_reply_packet(conn, request_buf, NT_STATUS_OK);
+ notify_buf->num_changes = 0;
+ return;
+ }
+
+ prs_init_empty(&ps, NULL, MARSHALL);
+
+ if (!notify_marshall_changes(notify_buf->num_changes, max_param,
+ notify_buf->changes, &ps)) {
+ /*
+ * We exceed what the client is willing to accept. Send
+ * nothing.
+ */
+ change_notify_reply_packet(conn, request_buf, NT_STATUS_OK);
+ goto done;
+ }
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ change_notify_reply_packet(conn, request_buf, NT_STATUS_NO_MEMORY);
+ goto done;
+ }
+
+ memcpy(tmp_request, request_buf, smb_size);
+
+ /*
+ * We're only interested in the header fields here
+ */
+
+ smb_setlen((char *)tmp_request, smb_size);
+ SCVAL(tmp_request, smb_wct, 0);
+
+ init_smb_request(req, tmp_request,0, conn->encrypted_tid);
+
+ send_nt_replies(conn, req, NT_STATUS_OK, prs_data_p(&ps),
+ prs_offset(&ps), NULL, 0);
+
+ done:
+ TALLOC_FREE(req);
+ prs_mem_free(&ps);
+
+ TALLOC_FREE(notify_buf->changes);
+ notify_buf->num_changes = 0;
+}
+
+static void notify_callback(void *private_data, const struct notify_event *e)
+{
+ files_struct *fsp = (files_struct *)private_data;
+ DEBUG(10, ("notify_callback called for %s\n", fsp->fsp_name));
+ notify_fsp(fsp, e->action, e->path);
+}
+
+NTSTATUS change_notify_create(struct files_struct *fsp, uint32 filter,
+ bool recursive)
+{
+ char *fullpath;
+ struct notify_entry e;
+ NTSTATUS status;
+
+ SMB_ASSERT(fsp->notify == NULL);
+
+ if (!(fsp->notify = TALLOC_ZERO_P(NULL, struct notify_change_buf))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&fullpath, "%s/%s", fsp->conn->connectpath,
+ fsp->fsp_name) == -1) {
+ DEBUG(0, ("asprintf failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(e);
+ e.path = fullpath;
+ e.filter = filter;
+ e.subdir_filter = 0;
+ if (recursive) {
+ e.subdir_filter = filter;
+ }
+
+ status = notify_add(fsp->conn->notify_ctx, &e, notify_callback, fsp);
+ SAFE_FREE(fullpath);
+
+ return status;
+}
+
+NTSTATUS change_notify_add_request(const struct smb_request *req,
+ uint32 max_param,
+ uint32 filter, bool recursive,
+ struct files_struct *fsp)
+{
+ struct notify_change_request *request = NULL;
+ struct notify_mid_map *map = NULL;
+
+ DEBUG(10, ("change_notify_add_request: Adding request for %s: "
+ "max_param = %d\n", fsp->fsp_name, (int)max_param));
+
+ if (!(request = SMB_MALLOC_P(struct notify_change_request))
+ || !(map = SMB_MALLOC_P(struct notify_mid_map))) {
+ SAFE_FREE(request);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ request->mid_map = map;
+ map->req = request;
+
+ memcpy(request->request_buf, req->inbuf, sizeof(request->request_buf));
+ request->max_param = max_param;
+ request->filter = filter;
+ request->fsp = fsp;
+ request->backend_data = NULL;
+
+ DLIST_ADD_END(fsp->notify->requests, request,
+ struct notify_change_request *);
+
+ map->mid = SVAL(req->inbuf, smb_mid);
+ DLIST_ADD(notify_changes_by_mid, map);
+
+ /* Push the MID of this packet on the signing queue. */
+ srv_defer_sign_response(SVAL(req->inbuf,smb_mid));
+
+ return NT_STATUS_OK;
+}
+
+static void change_notify_remove_request(struct notify_change_request *remove_req)
+{
+ files_struct *fsp;
+ struct notify_change_request *req;
+
+ /*
+ * Paranoia checks, the fsp referenced must must have the request in
+ * its list of pending requests
+ */
+
+ fsp = remove_req->fsp;
+ SMB_ASSERT(fsp->notify != NULL);
+
+ for (req = fsp->notify->requests; req; req = req->next) {
+ if (req == remove_req) {
+ break;
+ }
+ }
+
+ if (req == NULL) {
+ smb_panic("notify_req not found in fsp's requests");
+ }
+
+ DLIST_REMOVE(fsp->notify->requests, req);
+ DLIST_REMOVE(notify_changes_by_mid, req->mid_map);
+ SAFE_FREE(req->mid_map);
+ TALLOC_FREE(req->backend_data);
+ SAFE_FREE(req);
+}
+
+/****************************************************************************
+ Delete entries by mid from the change notify pending queue. Always send reply.
+*****************************************************************************/
+
+void remove_pending_change_notify_requests_by_mid(uint16 mid)
+{
+ struct notify_mid_map *map;
+
+ for (map = notify_changes_by_mid; map; map = map->next) {
+ if (map->mid == mid) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ return;
+ }
+
+ change_notify_reply_packet(map->req->fsp->conn,
+ map->req->request_buf, NT_STATUS_CANCELLED);
+ change_notify_remove_request(map->req);
+}
+
+/****************************************************************************
+ Delete entries by fnum from the change notify pending queue.
+*****************************************************************************/
+
+void remove_pending_change_notify_requests_by_fid(files_struct *fsp,
+ NTSTATUS status)
+{
+ if (fsp->notify == NULL) {
+ return;
+ }
+
+ while (fsp->notify->requests != NULL) {
+ change_notify_reply_packet(fsp->conn,
+ fsp->notify->requests->request_buf, status);
+ change_notify_remove_request(fsp->notify->requests);
+ }
+}
+
+void notify_fname(connection_struct *conn, uint32 action, uint32 filter,
+ const char *path)
+{
+ char *fullpath;
+
+ if (asprintf(&fullpath, "%s/%s", conn->connectpath, path) == -1) {
+ DEBUG(0, ("asprintf failed\n"));
+ return;
+ }
+
+ notify_trigger(conn->notify_ctx, action, filter, fullpath);
+ SAFE_FREE(fullpath);
+}
+
+static void notify_fsp(files_struct *fsp, uint32 action, const char *name)
+{
+ struct notify_change *change, *changes;
+ char *tmp;
+
+ if (fsp->notify == NULL) {
+ /*
+ * Nobody is waiting, don't queue
+ */
+ return;
+ }
+
+ /*
+ * Someone has triggered a notify previously, queue the change for
+ * later.
+ */
+
+ if ((fsp->notify->num_changes > 1000) || (name == NULL)) {
+ /*
+ * The real number depends on the client buf, just provide a
+ * guard against a DoS here.
+ */
+ TALLOC_FREE(fsp->notify->changes);
+ fsp->notify->num_changes = -1;
+ return;
+ }
+
+ if (fsp->notify->num_changes == -1) {
+ return;
+ }
+
+ if (!(changes = TALLOC_REALLOC_ARRAY(
+ fsp->notify, fsp->notify->changes,
+ struct notify_change, fsp->notify->num_changes+1))) {
+ DEBUG(0, ("talloc_realloc failed\n"));
+ return;
+ }
+
+ fsp->notify->changes = changes;
+
+ change = &(fsp->notify->changes[fsp->notify->num_changes]);
+
+ if (!(tmp = talloc_strdup(changes, name))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return;
+ }
+
+ string_replace(tmp, '/', '\\');
+ change->name = tmp;
+
+ change->action = action;
+ fsp->notify->num_changes += 1;
+
+ if (fsp->notify->requests == NULL) {
+ /*
+ * Nobody is waiting, so don't send anything. The ot
+ */
+ return;
+ }
+
+ if (action == NOTIFY_ACTION_OLD_NAME) {
+ /*
+ * We have to send the two rename events in one reply. So hold
+ * the first part back.
+ */
+ return;
+ }
+
+ /*
+ * Someone is waiting for the change, trigger the reply immediately.
+ *
+ * TODO: do we have to walk the lists of requests pending?
+ */
+
+ change_notify_reply(fsp->conn,
+ fsp->notify->requests->request_buf,
+ fsp->notify->requests->max_param,
+ fsp->notify);
+
+ change_notify_remove_request(fsp->notify->requests);
+}
+
+char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32 filter)
+{
+ char *result = NULL;
+
+ result = talloc_strdup(mem_ctx, "");
+
+ if (filter & FILE_NOTIFY_CHANGE_FILE_NAME)
+ result = talloc_asprintf_append(result, "FILE_NAME|");
+ if (filter & FILE_NOTIFY_CHANGE_DIR_NAME)
+ result = talloc_asprintf_append(result, "DIR_NAME|");
+ if (filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)
+ result = talloc_asprintf_append(result, "ATTRIBUTES|");
+ if (filter & FILE_NOTIFY_CHANGE_SIZE)
+ result = talloc_asprintf_append(result, "SIZE|");
+ if (filter & FILE_NOTIFY_CHANGE_LAST_WRITE)
+ result = talloc_asprintf_append(result, "LAST_WRITE|");
+ if (filter & FILE_NOTIFY_CHANGE_LAST_ACCESS)
+ result = talloc_asprintf_append(result, "LAST_ACCESS|");
+ if (filter & FILE_NOTIFY_CHANGE_CREATION)
+ result = talloc_asprintf_append(result, "CREATION|");
+ if (filter & FILE_NOTIFY_CHANGE_EA)
+ result = talloc_asprintf_append(result, "EA|");
+ if (filter & FILE_NOTIFY_CHANGE_SECURITY)
+ result = talloc_asprintf_append(result, "SECURITY|");
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_NAME)
+ result = talloc_asprintf_append(result, "STREAM_NAME|");
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_SIZE)
+ result = talloc_asprintf_append(result, "STREAM_SIZE|");
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_WRITE)
+ result = talloc_asprintf_append(result, "STREAM_WRITE|");
+
+ if (result == NULL) return NULL;
+ if (*result == '\0') return result;
+
+ result[strlen(result)-1] = '\0';
+ return result;
+}
+
+struct sys_notify_context *sys_notify_context_create(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ struct event_context *ev)
+{
+ struct sys_notify_context *ctx;
+
+ if (!(ctx = TALLOC_P(mem_ctx, struct sys_notify_context))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ ctx->ev = ev;
+ ctx->conn = conn;
+ ctx->private_data = NULL;
+ return ctx;
+}
+
+NTSTATUS sys_notify_watch(struct sys_notify_context *ctx,
+ struct notify_entry *e,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev),
+ void *private_data, void *handle)
+{
+ return SMB_VFS_NOTIFY_WATCH(ctx->conn, ctx, e, callback, private_data,
+ handle);
+}
+
diff --git a/source3/smbd/notify_inotify.c b/source3/smbd/notify_inotify.c
new file mode 100644
index 0000000000..fa0f0ed51d
--- /dev/null
+++ b/source3/smbd/notify_inotify.c
@@ -0,0 +1,434 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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/>.
+*/
+
+/*
+ notify implementation using inotify
+*/
+
+#include "includes.h"
+
+#ifdef HAVE_INOTIFY
+
+#ifdef HAVE_ASM_TYPES_H
+#include <asm/types.h>
+#endif
+
+#ifndef HAVE_INOTIFY_INIT
+
+#include <linux/inotify.h>
+#include <asm/unistd.h>
+
+
+/*
+ glibc doesn't define these functions yet (as of March 2006)
+*/
+static int inotify_init(void)
+{
+ return syscall(__NR_inotify_init);
+}
+
+static int inotify_add_watch(int fd, const char *path, __u32 mask)
+{
+ return syscall(__NR_inotify_add_watch, fd, path, mask);
+}
+
+static int inotify_rm_watch(int fd, int wd)
+{
+ return syscall(__NR_inotify_rm_watch, fd, wd);
+}
+#else
+
+#include <sys/inotify.h>
+
+#endif
+
+
+/* older glibc headers don't have these defines either */
+#ifndef IN_ONLYDIR
+#define IN_ONLYDIR 0x01000000
+#endif
+#ifndef IN_MASK_ADD
+#define IN_MASK_ADD 0x20000000
+#endif
+
+struct inotify_private {
+ struct sys_notify_context *ctx;
+ int fd;
+ struct inotify_watch_context *watches;
+};
+
+struct inotify_watch_context {
+ struct inotify_watch_context *next, *prev;
+ struct inotify_private *in;
+ int wd;
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev);
+ void *private_data;
+ uint32_t mask; /* the inotify mask */
+ uint32_t filter; /* the windows completion filter */
+ const char *path;
+};
+
+
+/*
+ destroy the inotify private context
+*/
+static int inotify_destructor(struct inotify_private *in)
+{
+ close(in->fd);
+ return 0;
+}
+
+
+/*
+ see if a particular event from inotify really does match a requested
+ notify event in SMB
+*/
+static bool filter_match(struct inotify_watch_context *w,
+ struct inotify_event *e)
+{
+ DEBUG(10, ("filter_match: e->mask=%x, w->mask=%x, w->filter=%x\n",
+ e->mask, w->mask, w->filter));
+
+ if ((e->mask & w->mask) == 0) {
+ /* this happens because inotify_add_watch() coalesces watches on the same
+ path, oring their masks together */
+ return False;
+ }
+
+ /* SMB separates the filters for files and directories */
+ if (e->mask & IN_ISDIR) {
+ if ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) == 0) {
+ return False;
+ }
+ } else {
+ if ((e->mask & IN_ATTRIB) &&
+ (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
+ FILE_NOTIFY_CHANGE_LAST_WRITE|
+ FILE_NOTIFY_CHANGE_LAST_ACCESS|
+ FILE_NOTIFY_CHANGE_EA|
+ FILE_NOTIFY_CHANGE_SECURITY))) {
+ return True;
+ }
+ if ((e->mask & IN_MODIFY) &&
+ (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
+ return True;
+ }
+ if ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) == 0) {
+ return False;
+ }
+ }
+
+ return True;
+}
+
+
+
+/*
+ dispatch one inotify event
+
+ the cookies are used to correctly handle renames
+*/
+static void inotify_dispatch(struct inotify_private *in,
+ struct inotify_event *e,
+ uint32_t prev_cookie,
+ struct inotify_event *e2)
+{
+ struct inotify_watch_context *w, *next;
+ struct notify_event ne;
+
+ DEBUG(10, ("inotify_dispatch called with mask=%x, name=[%s]\n",
+ e->mask, e->len ? e->name : ""));
+
+ /* ignore extraneous events, such as unmount and IN_IGNORED events */
+ if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
+ IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
+ return;
+ }
+
+ /* map the inotify mask to a action. This gets complicated for
+ renames */
+ if (e->mask & IN_CREATE) {
+ ne.action = NOTIFY_ACTION_ADDED;
+ } else if (e->mask & IN_DELETE) {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ } else if (e->mask & IN_MOVED_FROM) {
+ if (e2 != NULL && e2->cookie == e->cookie) {
+ ne.action = NOTIFY_ACTION_OLD_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ }
+ } else if (e->mask & IN_MOVED_TO) {
+ if (e->cookie == prev_cookie) {
+ ne.action = NOTIFY_ACTION_NEW_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_ADDED;
+ }
+ } else {
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ }
+ ne.path = e->name;
+
+ DEBUG(10, ("inotify_dispatch: ne.action = %d, ne.path = %s\n",
+ ne.action, ne.path));
+
+ /* find any watches that have this watch descriptor */
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e)) {
+ w->callback(in->ctx, w->private_data, &ne);
+ }
+ }
+
+ /* SMB expects a file rename to generate three events, two for
+ the rename and the other for a modify of the
+ destination. Strange! */
+ if (ne.action != NOTIFY_ACTION_NEW_NAME ||
+ (e->mask & IN_ISDIR) != 0) {
+ return;
+ }
+
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ e->mask = IN_ATTRIB;
+
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e) &&
+ !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) {
+ w->callback(in->ctx, w->private_data, &ne);
+ }
+ }
+}
+
+/*
+ called when the kernel has some events for us
+*/
+static void inotify_handler(struct event_context *ev, struct fd_event *fde,
+ uint16_t flags, void *private_data)
+{
+ struct inotify_private *in = talloc_get_type(private_data,
+ struct inotify_private);
+ int bufsize = 0;
+ struct inotify_event *e0, *e;
+ uint32_t prev_cookie=0;
+
+ /*
+ we must use FIONREAD as we cannot predict the length of the
+ filenames, and thus can't know how much to allocate
+ otherwise
+ */
+ if (ioctl(in->fd, FIONREAD, &bufsize) != 0 ||
+ bufsize == 0) {
+ DEBUG(0,("No data on inotify fd?!\n"));
+ return;
+ }
+
+ e0 = e = (struct inotify_event *)TALLOC_SIZE(in, bufsize);
+ if (e == NULL) return;
+
+ if (read(in->fd, e0, bufsize) != bufsize) {
+ DEBUG(0,("Failed to read all inotify data\n"));
+ talloc_free(e0);
+ return;
+ }
+
+ /* we can get more than one event in the buffer */
+ while (bufsize >= sizeof(*e)) {
+ struct inotify_event *e2 = NULL;
+ bufsize -= e->len + sizeof(*e);
+ if (bufsize >= sizeof(*e)) {
+ e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e);
+ }
+ inotify_dispatch(in, e, prev_cookie, e2);
+ prev_cookie = e->cookie;
+ e = e2;
+ }
+
+ talloc_free(e0);
+}
+
+/*
+ setup the inotify handle - called the first time a watch is added on
+ this context
+*/
+static NTSTATUS inotify_setup(struct sys_notify_context *ctx)
+{
+ struct inotify_private *in;
+
+ if (!lp_parm_bool(-1, "notify", "inotify", True)) {
+ return NT_STATUS_INVALID_SYSTEM_SERVICE;
+ }
+
+ in = talloc(ctx, struct inotify_private);
+ NT_STATUS_HAVE_NO_MEMORY(in);
+ in->fd = inotify_init();
+ if (in->fd == -1) {
+ DEBUG(0,("Failed to init inotify - %s\n", strerror(errno)));
+ talloc_free(in);
+ return map_nt_error_from_unix(errno);
+ }
+ in->ctx = ctx;
+ in->watches = NULL;
+
+ ctx->private_data = in;
+ talloc_set_destructor(in, inotify_destructor);
+
+ /* add a event waiting for the inotify fd to be readable */
+ event_add_fd(ctx->ev, in, in->fd, EVENT_FD_READ, inotify_handler, in);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ map from a change notify mask to a inotify mask. Remove any bits
+ which we can handle
+*/
+static const struct {
+ uint32_t notify_mask;
+ uint32_t inotify_mask;
+} inotify_mapping[] = {
+ {FILE_NOTIFY_CHANGE_FILE_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_DIR_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_ATTRIBUTES, IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
+ {FILE_NOTIFY_CHANGE_LAST_WRITE, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_EA, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_SECURITY, IN_ATTRIB}
+};
+
+static uint32_t inotify_map(struct notify_entry *e)
+{
+ int i;
+ uint32_t out=0;
+ for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
+ if (inotify_mapping[i].notify_mask & e->filter) {
+ out |= inotify_mapping[i].inotify_mask;
+ e->filter &= ~inotify_mapping[i].notify_mask;
+ }
+ }
+ return out;
+}
+
+/*
+ destroy a watch
+*/
+static int watch_destructor(struct inotify_watch_context *w)
+{
+ struct inotify_private *in = w->in;
+ int wd = w->wd;
+ DLIST_REMOVE(w->in->watches, w);
+
+ /* only rm the watch if its the last one with this wd */
+ for (w=in->watches;w;w=w->next) {
+ if (w->wd == wd) break;
+ }
+ if (w == NULL) {
+ DEBUG(10, ("Deleting inotify watch %d\n", wd));
+ if (inotify_rm_watch(in->fd, wd) == -1) {
+ DEBUG(1, ("inotify_rm_watch returned %s\n",
+ strerror(errno)));
+ }
+
+ }
+ return 0;
+}
+
+
+/*
+ add a watch. The watch is removed when the caller calls
+ talloc_free() on *handle
+*/
+NTSTATUS inotify_watch(struct sys_notify_context *ctx,
+ struct notify_entry *e,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev),
+ void *private_data,
+ void *handle_p)
+{
+ struct inotify_private *in;
+ int wd;
+ uint32_t mask;
+ struct inotify_watch_context *w;
+ uint32_t filter = e->filter;
+ void **handle = (void **)handle_p;
+
+ /* maybe setup the inotify fd */
+ if (ctx->private_data == NULL) {
+ NTSTATUS status;
+ status = inotify_setup(ctx);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ in = talloc_get_type(ctx->private_data, struct inotify_private);
+
+ mask = inotify_map(e);
+ if (mask == 0) {
+ /* this filter can't be handled by inotify */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* using IN_MASK_ADD allows us to cope with inotify() returning the same
+ watch descriptor for muliple watches on the same path */
+ mask |= (IN_MASK_ADD | IN_ONLYDIR);
+
+ /* get a new watch descriptor for this path */
+ wd = inotify_add_watch(in->fd, e->path, mask);
+ if (wd == -1) {
+ e->filter = filter;
+ DEBUG(1, ("inotify_add_watch returned %s\n", strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10, ("inotify_add_watch for %s mask %x returned wd %d\n",
+ e->path, mask, wd));
+
+ w = talloc(in, struct inotify_watch_context);
+ if (w == NULL) {
+ inotify_rm_watch(in->fd, wd);
+ e->filter = filter;
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ w->in = in;
+ w->wd = wd;
+ w->callback = callback;
+ w->private_data = private_data;
+ w->mask = mask;
+ w->filter = filter;
+ w->path = talloc_strdup(w, e->path);
+ if (w->path == NULL) {
+ inotify_rm_watch(in->fd, wd);
+ e->filter = filter;
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*handle) = w;
+
+ DLIST_ADD(in->watches, w);
+
+ /* the caller frees the handle to stop watching */
+ talloc_set_destructor(w, watch_destructor);
+
+ return NT_STATUS_OK;
+}
+
+#endif
diff --git a/source3/smbd/notify_internal.c b/source3/smbd/notify_internal.c
new file mode 100644
index 0000000000..84b8e1098e
--- /dev/null
+++ b/source3/smbd/notify_internal.c
@@ -0,0 +1,690 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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/>.
+*/
+
+/*
+ this is the change notify database. It implements mechanisms for
+ storing current change notify waiters in a tdb, and checking if a
+ given event matches any of the stored notify waiiters.
+*/
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_notify.h"
+
+struct notify_context {
+ struct db_context *db;
+ struct server_id server;
+ struct messaging_context *messaging_ctx;
+ struct notify_list *list;
+ struct notify_array *array;
+ int seqnum;
+ struct sys_notify_context *sys_notify_ctx;
+ TDB_DATA key;
+};
+
+
+struct notify_list {
+ struct notify_list *next, *prev;
+ void *private_data;
+ void (*callback)(void *, const struct notify_event *);
+ void *sys_notify_handle;
+ int depth;
+};
+
+#define NOTIFY_KEY "notify array"
+
+#define NOTIFY_ENABLE "notify:enable"
+#define NOTIFY_ENABLE_DEFAULT True
+
+static NTSTATUS notify_remove_all(struct notify_context *notify,
+ const struct server_id *server);
+static void notify_handler(struct messaging_context *msg_ctx, void *private_data,
+ uint32_t msg_type, struct server_id server_id, DATA_BLOB *data);
+
+/*
+ destroy the notify context
+*/
+static int notify_destructor(struct notify_context *notify)
+{
+ messaging_deregister(notify->messaging_ctx, MSG_PVFS_NOTIFY, notify);
+
+ if (notify->list != NULL) {
+ notify_remove_all(notify, &notify->server);
+ }
+
+ return 0;
+}
+
+/*
+ Open up the notify.tdb database. You should close it down using
+ talloc_free(). We need the messaging_ctx to allow for notifications
+ via internal messages
+*/
+struct notify_context *notify_init(TALLOC_CTX *mem_ctx, struct server_id server,
+ struct messaging_context *messaging_ctx,
+ struct event_context *ev,
+ connection_struct *conn)
+{
+ struct notify_context *notify;
+
+ if (!lp_change_notify(conn->params)) {
+ return NULL;
+ }
+
+ notify = talloc(mem_ctx, struct notify_context);
+ if (notify == NULL) {
+ return NULL;
+ }
+
+ notify->db = db_open(notify, lock_path("notify.tdb"),
+ 0, TDB_SEQNUM|TDB_CLEAR_IF_FIRST,
+ O_RDWR|O_CREAT, 0644);
+ if (notify->db == NULL) {
+ talloc_free(notify);
+ return NULL;
+ }
+
+ notify->server = server;
+ notify->messaging_ctx = messaging_ctx;
+ notify->list = NULL;
+ notify->array = NULL;
+ notify->seqnum = notify->db->get_seqnum(notify->db);
+ notify->key = string_term_tdb_data(NOTIFY_KEY);
+
+ talloc_set_destructor(notify, notify_destructor);
+
+ /* register with the messaging subsystem for the notify
+ message type */
+ messaging_register(notify->messaging_ctx, notify,
+ MSG_PVFS_NOTIFY, notify_handler);
+
+ notify->sys_notify_ctx = sys_notify_context_create(conn, notify, ev);
+
+ return notify;
+}
+
+/*
+ lock and fetch the record
+*/
+static NTSTATUS notify_fetch_locked(struct notify_context *notify, struct db_record **rec)
+{
+ *rec = notify->db->fetch_locked(notify->db, notify, notify->key);
+ if (*rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ load the notify array
+*/
+static NTSTATUS notify_load(struct notify_context *notify, struct db_record *rec)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ int seqnum;
+
+ seqnum = notify->db->get_seqnum(notify->db);
+
+ if (seqnum == notify->seqnum && notify->array != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ notify->seqnum = seqnum;
+
+ talloc_free(notify->array);
+ notify->array = TALLOC_ZERO_P(notify, struct notify_array);
+ NT_STATUS_HAVE_NO_MEMORY(notify->array);
+
+ if (!rec) {
+ if (notify->db->fetch(notify->db, notify, notify->key, &dbuf) != 0) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else {
+ dbuf = rec->value;
+ }
+
+ blob.data = (uint8 *)dbuf.dptr;
+ blob.length = dbuf.dsize;
+
+ status = NT_STATUS_OK;
+ if (blob.length > 0) {
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(&blob, notify->array, notify->array,
+ (ndr_pull_flags_fn_t)ndr_pull_notify_array);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ }
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("notify_load:\n"));
+ NDR_PRINT_DEBUG(notify_array, notify->array);
+ }
+
+ if (!rec) {
+ talloc_free(dbuf.dptr);
+ }
+
+ return status;
+}
+
+/*
+ compare notify entries for sorting
+*/
+static int notify_compare(const void *p1, const void *p2)
+{
+ const struct notify_entry *e1 = (const struct notify_entry *)p1;
+ const struct notify_entry *e2 = (const struct notify_entry *)p2;
+ return strcmp(e1->path, e2->path);
+}
+
+/*
+ save the notify array
+*/
+static NTSTATUS notify_save(struct notify_context *notify, struct db_record *rec)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx;
+
+ /* if possible, remove some depth arrays */
+ while (notify->array->num_depths > 0 &&
+ notify->array->depth[notify->array->num_depths-1].num_entries == 0) {
+ notify->array->num_depths--;
+ }
+
+ /* we might just be able to delete the record */
+ if (notify->array->num_depths == 0) {
+ return rec->delete_rec(rec);
+ }
+
+ tmp_ctx = talloc_new(notify);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ ndr_err = ndr_push_struct_blob(&blob, tmp_ctx, notify->array,
+ (ndr_push_flags_fn_t)ndr_push_notify_array);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("notify_save:\n"));
+ NDR_PRINT_DEBUG(notify_array, notify->array);
+ }
+
+ dbuf.dptr = blob.data;
+ dbuf.dsize = blob.length;
+
+ status = rec->store(rec, dbuf, TDB_REPLACE);
+ talloc_free(tmp_ctx);
+
+ return status;
+}
+
+
+/*
+ handle incoming notify messages
+*/
+static void notify_handler(struct messaging_context *msg_ctx, void *private_data,
+ uint32_t msg_type, struct server_id server_id, DATA_BLOB *data)
+{
+ struct notify_context *notify = talloc_get_type(private_data, struct notify_context);
+ enum ndr_err_code ndr_err;
+ struct notify_event ev;
+ TALLOC_CTX *tmp_ctx = talloc_new(notify);
+ struct notify_list *listel;
+
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(data, tmp_ctx, &ev,
+ (ndr_pull_flags_fn_t)ndr_pull_notify_event);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ for (listel=notify->list;listel;listel=listel->next) {
+ if (listel->private_data == ev.private_data) {
+ listel->callback(listel->private_data, &ev);
+ break;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ callback from sys_notify telling us about changes from the OS
+*/
+static void sys_notify_callback(struct sys_notify_context *ctx,
+ void *ptr, struct notify_event *ev)
+{
+ struct notify_list *listel = talloc_get_type(ptr, struct notify_list);
+ ev->private_data = listel;
+ DEBUG(10, ("sys_notify_callback called with action=%d, for %s\n",
+ ev->action, ev->path));
+ listel->callback(listel->private_data, ev);
+}
+
+/*
+ add an entry to the notify array
+*/
+static NTSTATUS notify_add_array(struct notify_context *notify, struct db_record *rec,
+ struct notify_entry *e,
+ void *private_data, int depth)
+{
+ int i;
+ struct notify_depth *d;
+ struct notify_entry *ee;
+
+ /* possibly expand the depths array */
+ if (depth >= notify->array->num_depths) {
+ d = talloc_realloc(notify->array, notify->array->depth,
+ struct notify_depth, depth+1);
+ NT_STATUS_HAVE_NO_MEMORY(d);
+ for (i=notify->array->num_depths;i<=depth;i++) {
+ ZERO_STRUCT(d[i]);
+ }
+ notify->array->depth = d;
+ notify->array->num_depths = depth+1;
+ }
+ d = &notify->array->depth[depth];
+
+ /* expand the entries array */
+ ee = talloc_realloc(notify->array->depth, d->entries, struct notify_entry,
+ d->num_entries+1);
+ NT_STATUS_HAVE_NO_MEMORY(ee);
+ d->entries = ee;
+
+ d->entries[d->num_entries] = *e;
+ d->entries[d->num_entries].private_data = private_data;
+ d->entries[d->num_entries].server = notify->server;
+ d->entries[d->num_entries].path_len = strlen(e->path);
+ d->num_entries++;
+
+ d->max_mask |= e->filter;
+ d->max_mask_subdir |= e->subdir_filter;
+
+ if (d->num_entries > 1) {
+ qsort(d->entries, d->num_entries, sizeof(d->entries[0]), notify_compare);
+ }
+
+ /* recalculate the maximum masks */
+ d->max_mask = 0;
+ d->max_mask_subdir = 0;
+
+ for (i=0;i<d->num_entries;i++) {
+ d->max_mask |= d->entries[i].filter;
+ d->max_mask_subdir |= d->entries[i].subdir_filter;
+ }
+
+ return notify_save(notify, rec);
+}
+
+/*
+ add a notify watch. This is called when a notify is first setup on a open
+ directory handle.
+*/
+NTSTATUS notify_add(struct notify_context *notify, struct notify_entry *e0,
+ void (*callback)(void *, const struct notify_event *),
+ void *private_data)
+{
+ struct notify_entry e = *e0;
+ NTSTATUS status;
+ char *tmp_path = NULL;
+ struct notify_list *listel;
+ size_t len;
+ int depth;
+ struct db_record *rec;
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ status = notify_fetch_locked(notify, &rec);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = notify_load(notify, rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(rec);
+ return status;
+ }
+
+ /* cope with /. on the end of the path */
+ len = strlen(e.path);
+ if (len > 1 && e.path[len-1] == '.' && e.path[len-2] == '/') {
+ tmp_path = talloc_strndup(notify, e.path, len-2);
+ if (tmp_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ e.path = tmp_path;
+ }
+
+ depth = count_chars(e.path, '/');
+
+ listel = TALLOC_ZERO_P(notify, struct notify_list);
+ if (listel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ listel->private_data = private_data;
+ listel->callback = callback;
+ listel->depth = depth;
+ DLIST_ADD(notify->list, listel);
+
+ /* ignore failures from sys_notify */
+ if (notify->sys_notify_ctx != NULL) {
+ /*
+ this call will modify e.filter and e.subdir_filter
+ to remove bits handled by the backend
+ */
+ status = sys_notify_watch(notify->sys_notify_ctx, &e,
+ sys_notify_callback, listel,
+ &listel->sys_notify_handle);
+ if (NT_STATUS_IS_OK(status)) {
+ talloc_steal(listel, listel->sys_notify_handle);
+ }
+ }
+
+ /* if the system notify handler couldn't handle some of the
+ filter bits, or couldn't handle a request for recursion
+ then we need to install it in the array used for the
+ intra-samba notify handling */
+ if (e.filter != 0 || e.subdir_filter != 0) {
+ status = notify_add_array(notify, rec, &e, private_data, depth);
+ }
+
+done:
+ talloc_free(rec);
+ talloc_free(tmp_path);
+
+ return status;
+}
+
+/*
+ remove a notify watch. Called when the directory handle is closed
+*/
+NTSTATUS notify_remove(struct notify_context *notify, void *private_data)
+{
+ NTSTATUS status;
+ struct notify_list *listel;
+ int i, depth;
+ struct notify_depth *d;
+ struct db_record *rec;
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ for (listel=notify->list;listel;listel=listel->next) {
+ if (listel->private_data == private_data) {
+ DLIST_REMOVE(notify->list, listel);
+ break;
+ }
+ }
+ if (listel == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ depth = listel->depth;
+
+ talloc_free(listel);
+
+ status = notify_fetch_locked(notify, &rec);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = notify_load(notify, rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(rec);
+ return status;
+ }
+
+ if (depth >= notify->array->num_depths) {
+ talloc_free(rec);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* we only have to search at the depth of this element */
+ d = &notify->array->depth[depth];
+
+ for (i=0;i<d->num_entries;i++) {
+ if (private_data == d->entries[i].private_data &&
+ cluster_id_equal(&notify->server, &d->entries[i].server)) {
+ break;
+ }
+ }
+ if (i == d->num_entries) {
+ talloc_free(rec);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (i < d->num_entries-1) {
+ memmove(&d->entries[i], &d->entries[i+1],
+ sizeof(d->entries[i])*(d->num_entries-(i+1)));
+ }
+ d->num_entries--;
+
+ status = notify_save(notify, rec);
+
+ talloc_free(rec);
+
+ return status;
+}
+
+/*
+ remove all notify watches for a messaging server
+*/
+static NTSTATUS notify_remove_all(struct notify_context *notify,
+ const struct server_id *server)
+{
+ NTSTATUS status;
+ int i, depth, del_count=0;
+ struct db_record *rec;
+
+ status = notify_fetch_locked(notify, &rec);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = notify_load(notify, rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(rec);
+ return status;
+ }
+
+ /* we have to search for all entries across all depths, looking for matches
+ for the server id */
+ for (depth=0;depth<notify->array->num_depths;depth++) {
+ struct notify_depth *d = &notify->array->depth[depth];
+ for (i=0;i<d->num_entries;i++) {
+ if (cluster_id_equal(server, &d->entries[i].server)) {
+ if (i < d->num_entries-1) {
+ memmove(&d->entries[i], &d->entries[i+1],
+ sizeof(d->entries[i])*(d->num_entries-(i+1)));
+ }
+ i--;
+ d->num_entries--;
+ del_count++;
+ }
+ }
+ }
+
+ if (del_count > 0) {
+ status = notify_save(notify, rec);
+ }
+
+ talloc_free(rec);
+
+ return status;
+}
+
+
+/*
+ send a notify message to another messaging server
+*/
+static NTSTATUS notify_send(struct notify_context *notify, struct notify_entry *e,
+ const char *path, uint32_t action)
+{
+ struct notify_event ev;
+ DATA_BLOB data;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx;
+
+ ev.action = action;
+ ev.path = path;
+ ev.private_data = e->private_data;
+
+ tmp_ctx = talloc_new(notify);
+
+ ndr_err = ndr_push_struct_blob(&data, tmp_ctx, &ev,
+ (ndr_push_flags_fn_t)ndr_push_notify_event);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ status = messaging_send(notify->messaging_ctx, e->server,
+ MSG_PVFS_NOTIFY, &data);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+
+/*
+ trigger a notify message for anyone waiting on a matching event
+
+ This function is called a lot, and needs to be very fast. The unusual data structure
+ and traversal is designed to be fast in the average case, even for large numbers of
+ notifies
+*/
+void notify_trigger(struct notify_context *notify,
+ uint32_t action, uint32_t filter, const char *path)
+{
+ NTSTATUS status;
+ int depth;
+ const char *p, *next_p;
+
+ DEBUG(10, ("notify_trigger called action=0x%x, filter=0x%x, "
+ "path=%s\n", (unsigned)action, (unsigned)filter, path));
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return;
+ }
+
+ again:
+ status = notify_load(notify, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ /* loop along the given path, working with each directory depth separately */
+ for (depth=0,p=path;
+ p && depth < notify->array->num_depths;
+ p=next_p,depth++) {
+ int p_len = p - path;
+ int min_i, max_i, i;
+ struct notify_depth *d = &notify->array->depth[depth];
+ next_p = strchr(p+1, '/');
+
+ /* see if there are any entries at this depth */
+ if (d->num_entries == 0) continue;
+
+ /* try to skip based on the maximum mask. If next_p is
+ NULL then we know it will be a 'this directory'
+ match, otherwise it must be a subdir match */
+ if (next_p != NULL) {
+ if (0 == (filter & d->max_mask_subdir)) {
+ continue;
+ }
+ } else {
+ if (0 == (filter & d->max_mask)) {
+ continue;
+ }
+ }
+
+ /* we know there is an entry here worth looking
+ for. Use a bisection search to find the first entry
+ with a matching path */
+ min_i = 0;
+ max_i = d->num_entries-1;
+
+ while (min_i < max_i) {
+ struct notify_entry *e;
+ int cmp;
+ i = (min_i+max_i)/2;
+ e = &d->entries[i];
+ cmp = strncmp(path, e->path, p_len);
+ if (cmp == 0) {
+ if (p_len == e->path_len) {
+ max_i = i;
+ } else {
+ max_i = i-1;
+ }
+ } else if (cmp < 0) {
+ max_i = i-1;
+ } else {
+ min_i = i+1;
+ }
+ }
+
+ if (min_i != max_i) {
+ /* none match */
+ continue;
+ }
+
+ /* we now know that the entries start at min_i */
+ for (i=min_i;i<d->num_entries;i++) {
+ struct notify_entry *e = &d->entries[i];
+ if (p_len != e->path_len ||
+ strncmp(path, e->path, p_len) != 0) break;
+ if (next_p != NULL) {
+ if (0 == (filter & e->subdir_filter)) {
+ continue;
+ }
+ } else {
+ if (0 == (filter & e->filter)) {
+ continue;
+ }
+ }
+ status = notify_send(notify, e, path + e->path_len + 1,
+ action);
+
+ if (NT_STATUS_EQUAL(
+ status, NT_STATUS_INVALID_HANDLE)) {
+ struct server_id server = e->server;
+
+ DEBUG(10, ("Deleting notify entries for "
+ "process %s because it's gone\n",
+ procid_str_static(&e->server)));
+ notify_remove_all(notify, &server);
+ goto again;
+ }
+ }
+ }
+}
diff --git a/source3/smbd/ntquotas.c b/source3/smbd/ntquotas.c
new file mode 100644
index 0000000000..c616c494dc
--- /dev/null
+++ b/source3/smbd/ntquotas.c
@@ -0,0 +1,247 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT QUOTA suppport
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+static SMB_BIG_UINT limit_nt2unix(SMB_BIG_UINT in, SMB_BIG_UINT bsize)
+{
+ SMB_BIG_UINT ret = (SMB_BIG_UINT)0;
+
+ ret = (SMB_BIG_UINT)(in/bsize);
+ if (in>0 && ret==0) {
+ /* we have to make sure that a overflow didn't set NO_LIMIT */
+ ret = (SMB_BIG_UINT)1;
+ }
+
+ if (in == SMB_NTQUOTAS_NO_LIMIT)
+ ret = SMB_QUOTAS_NO_LIMIT;
+ else if (in == SMB_NTQUOTAS_NO_SPACE)
+ ret = SMB_QUOTAS_NO_SPACE;
+ else if (in == SMB_NTQUOTAS_NO_ENTRY)
+ ret = SMB_QUOTAS_NO_LIMIT;
+
+ return ret;
+}
+
+static SMB_BIG_UINT limit_unix2nt(SMB_BIG_UINT in, SMB_BIG_UINT bsize)
+{
+ SMB_BIG_UINT ret = (SMB_BIG_UINT)0;
+
+ ret = (SMB_BIG_UINT)(in*bsize);
+
+ if (ret < in) {
+ /* we overflow */
+ ret = SMB_NTQUOTAS_NO_LIMIT;
+ }
+
+ if (in == SMB_QUOTAS_NO_LIMIT)
+ ret = SMB_NTQUOTAS_NO_LIMIT;
+
+ return ret;
+}
+
+static SMB_BIG_UINT limit_blk2inodes(SMB_BIG_UINT in)
+{
+ SMB_BIG_UINT ret = (SMB_BIG_UINT)0;
+
+ ret = (SMB_BIG_UINT)(in/2);
+
+ if (ret == 0 && in != 0)
+ ret = (SMB_BIG_UINT)1;
+
+ return ret;
+}
+
+int vfs_get_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, DOM_SID *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+
+ ZERO_STRUCT(D);
+
+ if (!fsp||!fsp->conn||!qt)
+ return (-1);
+
+ ZERO_STRUCT(*qt);
+
+ id.uid = -1;
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ sid_string_dbg(psid)));
+ }
+
+ ret = SMB_VFS_GET_QUOTA(fsp->conn, qtype, id, &D);
+
+ if (psid)
+ qt->sid = *psid;
+
+ if (ret!=0) {
+ return ret;
+ }
+
+ qt->usedspace = (SMB_BIG_UINT)D.curblocks*D.bsize;
+ qt->softlim = limit_unix2nt(D.softlimit, D.bsize);
+ qt->hardlim = limit_unix2nt(D.hardlimit, D.bsize);
+ qt->qflags = D.qflags;
+
+
+ return 0;
+}
+
+int vfs_set_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, DOM_SID *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+ ZERO_STRUCT(D);
+
+ if (!fsp||!fsp->conn||!qt)
+ return (-1);
+
+ id.uid = -1;
+
+ D.bsize = (SMB_BIG_UINT)QUOTABLOCK_SIZE;
+
+ D.softlimit = limit_nt2unix(qt->softlim,D.bsize);
+ D.hardlimit = limit_nt2unix(qt->hardlim,D.bsize);
+ D.qflags = qt->qflags;
+
+ D.isoftlimit = limit_blk2inodes(D.softlimit);
+ D.ihardlimit = limit_blk2inodes(D.hardlimit);
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ sid_string_dbg(psid)));
+ }
+
+ ret = SMB_VFS_SET_QUOTA(fsp->conn, qtype, id, &D);
+
+ return ret;
+}
+
+static bool allready_in_quota_list(SMB_NTQUOTA_LIST *qt_list, uid_t uid)
+{
+ SMB_NTQUOTA_LIST *tmp_list = NULL;
+
+ if (!qt_list)
+ return False;
+
+ for (tmp_list=qt_list;tmp_list!=NULL;tmp_list=tmp_list->next) {
+ if (tmp_list->uid == uid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+int vfs_get_user_ntquota_list(files_struct *fsp, SMB_NTQUOTA_LIST **qt_list)
+{
+ struct passwd *usr;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ if (!fsp||!fsp->conn||!qt_list)
+ return (-1);
+
+ *qt_list = NULL;
+
+ if ((mem_ctx=talloc_init("SMB_USER_QUOTA_LIST"))==NULL) {
+ DEBUG(0,("talloc_init() failed\n"));
+ return (-1);
+ }
+
+ sys_setpwent();
+ while ((usr = sys_getpwent()) != NULL) {
+ SMB_NTQUOTA_STRUCT tmp_qt;
+ SMB_NTQUOTA_LIST *tmp_list_ent;
+ DOM_SID sid;
+
+ ZERO_STRUCT(tmp_qt);
+
+ if (allready_in_quota_list((*qt_list),usr->pw_uid)) {
+ DEBUG(5,("record for uid[%ld] allready in the list\n",(long)usr->pw_uid));
+ continue;
+ }
+
+ uid_to_sid(&sid, usr->pw_uid);
+
+ if (vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &tmp_qt)!=0) {
+ DEBUG(5,("no quota entry for sid[%s] path[%s]\n",
+ sid_string_dbg(&sid),
+ fsp->conn->connectpath));
+ continue;
+ }
+
+ DEBUG(15,("quota entry for id[%s] path[%s]\n",
+ sid_string_dbg(&sid), fsp->conn->connectpath));
+
+ if ((tmp_list_ent=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_LIST))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ if ((tmp_list_ent->quotas=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_STRUCT))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ tmp_list_ent->uid = usr->pw_uid;
+ memcpy(tmp_list_ent->quotas,&tmp_qt,sizeof(tmp_qt));
+ tmp_list_ent->mem_ctx = mem_ctx;
+
+ DLIST_ADD((*qt_list),tmp_list_ent);
+
+ }
+ sys_endpwent();
+
+ return 0;
+}
+
+static int quota_handle_destructor(SMB_NTQUOTA_HANDLE *handle)
+{
+ if (handle->quota_list)
+ free_ntquota_list(&handle->quota_list);
+ return 0;
+}
+
+void *init_quota_handle(TALLOC_CTX *mem_ctx)
+{
+ SMB_NTQUOTA_HANDLE *qt_handle;
+
+ if (!mem_ctx)
+ return False;
+
+ qt_handle = TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_HANDLE);
+ if (qt_handle==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ return NULL;
+ }
+
+ talloc_set_destructor(qt_handle, quota_handle_destructor);
+ return (void *)qt_handle;
+}
diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
new file mode 100644
index 0000000000..584399c86c
--- /dev/null
+++ b/source3/smbd/nttrans.c
@@ -0,0 +1,2864 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ 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"
+
+extern int max_send;
+extern enum protocol_types Protocol;
+
+static char *nttrans_realloc(char **ptr, size_t size)
+{
+ if (ptr==NULL) {
+ smb_panic("nttrans_realloc() called with NULL ptr");
+ }
+
+ *ptr = (char *)SMB_REALLOC(*ptr, size);
+ if(*ptr == NULL) {
+ return NULL;
+ }
+ memset(*ptr,'\0',size);
+ return *ptr;
+}
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+void send_nt_replies(connection_struct *conn,
+ struct smb_request *req, NTSTATUS nt_error,
+ char *params, int paramsize,
+ char *pdata, int datasize)
+{
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ char *pp = params;
+ char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 3;
+ int data_alignment_offset = 0;
+
+ /*
+ * If there genuinely are no parameters or data to send just send
+ * the empty packet.
+ */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_outbuf(req, 18, 0);
+ show_msg((char *)req->outbuf);
+ return;
+ }
+
+ /*
+ * When sending params and data ensure that both are nicely aligned.
+ * Only do this alignment when there is also data to send - else
+ * can cause NT redirector problems.
+ */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0)) {
+ data_alignment_offset = 4 - (params_to_send % 4);
+ }
+
+ /*
+ * Space is bufsize minus Netbios over TCP header minus SMB header.
+ * The alignment_offset is to align the param bytes on a four byte
+ * boundary (2 bytes for data len, one byte pad).
+ * NT needs this to work correctly.
+ */
+
+ useable_space = max_send - (smb_size
+ + 2 * 18 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ DEBUG(0, ("send_nt_replies failed sanity useable_space "
+ "= %d!!!", useable_space));
+ exit_server_cleanly("send_nt_replies: srv_send_smb failed.");
+ }
+
+ while (params_to_send || data_to_send) {
+
+ /*
+ * Calculate whether we will totally or partially fill this packet.
+ */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /*
+ * We can never send more than useable_space.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_outbuf(req, 18,
+ total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /*
+ * Set total params and data to be sent.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_TotalParameterCount,paramsize);
+ SIVAL(req->outbuf,smb_ntr_TotalDataCount,datasize);
+
+ /*
+ * Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence.
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SIVAL(req->outbuf, smb_ntr_ParameterCount,
+ params_sent_thistime);
+
+ if(params_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,0);
+ SIVAL(req->outbuf,smb_ntr_ParameterDisplacement,0);
+ } else {
+ /*
+ * smb_ntr_ParameterOffset is the offset from the start of the SMB header to the
+ * parameter bytes, however the first 4 bytes of outbuf are
+ * the Netbios over TCP header. Thus use smb_base() to subtract
+ * them from the calculation.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+ /*
+ * Absolute displacement of param bytes sent in this packet.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_ParameterDisplacement,
+ pp - params);
+ }
+
+ /*
+ * Deal with the data portion.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataCount, data_sent_thistime);
+
+ if(data_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_DataOffset,0);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, 0);
+ } else {
+ /*
+ * The offset of the data bytes is the offset of the
+ * parameter bytes plus the number of parameters being sent this time.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataOffset,
+ ((smb_buf(req->outbuf)+alignment_offset) -
+ smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, pd - pdata);
+ }
+
+ /*
+ * Copy the param bytes into the packet.
+ */
+
+ if(params_sent_thistime) {
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0,
+ alignment_offset);
+ }
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /*
+ * Copy in the data bytes
+ */
+
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("nt_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("nt_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (NT_STATUS_V(nt_error)) {
+ error_packet_set((char *)req->outbuf,
+ 0, 0, nt_error,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!srv_send_smb(smbd_server_fd(),
+ (char *)req->outbuf,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_nt_replies: srv_send_smb failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /*
+ * Sanity check
+ */
+
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_nt_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ exit_server_cleanly("send_nt_replies: internal error");
+ }
+ }
+}
+
+/****************************************************************************
+ Is it an NTFS stream name ?
+ An NTFS file name is <path>.<extention>:<stream name>:<stream type>
+ $DATA can be used as both a stream name and a stream type. A missing stream
+ name or type implies $DATA.
+****************************************************************************/
+
+bool is_ntfs_stream_name(const char *fname)
+{
+ if (lp_posix_pathnames()) {
+ return False;
+ }
+ return (strchr_m(fname, ':') != NULL) ? True : False;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call on a pipe
+****************************************************************************/
+
+static void nt_open_pipe(char *fname, connection_struct *conn,
+ struct smb_request *req, int *ppnum)
+{
+ smb_np_struct *p = NULL;
+
+ DEBUG(4,("nt_open_pipe: Opening pipe %s.\n", fname));
+
+ /* See if it is one we want to handle. */
+
+ if (!is_known_pipename(fname)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+
+ /* Strip \\ off the name. */
+ fname++;
+
+ DEBUG(3,("nt_open_pipe: Known pipe %s opening.\n", fname));
+
+ p = open_rpc_pipe_p(fname, conn, req->vuid);
+ if (!p) {
+ reply_doserror(req, ERRSRV, ERRnofids);
+ return;
+ }
+
+ /* TODO: Add pipe to db */
+
+ if ( !store_pipe_opendb( p ) ) {
+ DEBUG(3,("nt_open_pipe: failed to store %s pipe open.\n", fname));
+ }
+
+ *ppnum = p->pnum;
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call for pipes.
+****************************************************************************/
+
+static void do_ntcreate_pipe_open(connection_struct *conn,
+ struct smb_request *req)
+{
+ char *fname = NULL;
+ int pnum = -1;
+ char *p = NULL;
+ uint32 flags = IVAL(req->inbuf,smb_ntcreate_Flags);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ srvstr_pull_buf_talloc(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf), STR_TERMINATE);
+
+ if (!fname) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* error reply */
+ return;
+ }
+
+ /*
+ * Deal with pipe return.
+ */
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definately
+ * what happens on the wire....
+ */
+ reply_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_outbuf(req, 34, 0);
+ }
+
+ p = (char *)req->outbuf + smb_vwv2;
+ p++;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 4;
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_ntcreate_pipe_open: open pipe = %s\n", fname));
+
+ chain_reply(req);
+}
+
+/****************************************************************************
+ Reply to an NT create and X call.
+****************************************************************************/
+
+void reply_ntcreate_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ uint32 flags;
+ uint32 access_mask;
+ uint32 file_attributes;
+ uint32 share_access;
+ uint32 create_disposition;
+ uint32 create_options;
+ uint16 root_dir_fid;
+ SMB_BIG_UINT allocation_size;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ uint32 fattr=0;
+ SMB_OFF_T file_len = 0;
+ SMB_STRUCT_STAT sbuf;
+ int info = 0;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ NTSTATUS status;
+ int oplock_request;
+ uint8_t oplock_granted = NO_OPLOCK_RETURN;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBntcreateX);
+
+ if (req->wct < 24) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ flags = IVAL(req->inbuf,smb_ntcreate_Flags);
+ access_mask = IVAL(req->inbuf,smb_ntcreate_DesiredAccess);
+ file_attributes = IVAL(req->inbuf,smb_ntcreate_FileAttributes);
+ share_access = IVAL(req->inbuf,smb_ntcreate_ShareAccess);
+ create_disposition = IVAL(req->inbuf,smb_ntcreate_CreateDisposition);
+ create_options = IVAL(req->inbuf,smb_ntcreate_CreateOptions);
+ root_dir_fid = (uint16)IVAL(req->inbuf,smb_ntcreate_RootDirectoryFid);
+
+ allocation_size = (SMB_BIG_UINT)IVAL(req->inbuf,
+ smb_ntcreate_AllocationSize);
+#ifdef LARGE_SMB_OFF_T
+ allocation_size |= (((SMB_BIG_UINT)IVAL(
+ req->inbuf,
+ smb_ntcreate_AllocationSize + 4)) << 32);
+#endif
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf), 0, STR_TERMINATE, &status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBntcreateX);
+ return;
+ }
+
+ DEBUG(10,("reply_ntcreate_and_X: flags = 0x%x, access_mask = 0x%x "
+ "file_attributes = 0x%x, share_access = 0x%x, "
+ "create_disposition = 0x%x create_options = 0x%x "
+ "root_dir_fid = 0x%x, fname = %s\n",
+ (unsigned int)flags,
+ (unsigned int)access_mask,
+ (unsigned int)file_attributes,
+ (unsigned int)share_access,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)root_dir_fid,
+ fname));
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_ntcreate_pipe_open(conn, req);
+ END_PROFILE(SMBntcreateX);
+ return;
+ }
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBntcreateX);
+ return;
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ status = create_file(conn, req, root_dir_fid, fname,
+ access_mask, share_access, create_disposition,
+ create_options, file_attributes, oplock_request,
+ allocation_size, NULL, NULL, &fsp, &info, &sbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ END_PROFILE(SMBntcreateX);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ reply_botherror(req, status, ERRDOS, ERRfilexists);
+ }
+ else {
+ reply_nterror(req, status);
+ }
+ END_PROFILE(SMBntcreateX);
+ return;
+ }
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = sbuf.st_size;
+ fattr = dos_mode(conn,fsp->fsp_name,&sbuf);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definately
+ * what happens on the wire....
+ */
+ reply_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_outbuf(req, 34, 0);
+ }
+
+ p = (char *)req->outbuf + smb_vwv2;
+
+ SCVAL(p, 0, oplock_granted);
+
+ p++;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 4;
+
+ /* Create time. */
+ c_timespec = get_create_timespec(
+ &sbuf,lp_fake_dir_create_times(SNUM(conn)));
+ a_timespec = get_atimespec(&sbuf);
+ m_timespec = get_mtimespec(&sbuf);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&c_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ }
+
+ put_long_date_timespec(p, c_timespec); /* create time. */
+ p += 8;
+ put_long_date_timespec(p, a_timespec); /* access time */
+ p += 8;
+ put_long_date_timespec(p, m_timespec); /* write time */
+ p += 8;
+ put_long_date_timespec(p, m_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, get_allocation_size(conn,fsp,&sbuf));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ SSVAL(p,2,0x7);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32 perms = 0;
+ p += 25;
+ if (fsp->is_directory
+ || can_write_to_file(conn, fsp->fsp_name, &sbuf)) {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("reply_ntcreate_and_X: fnum = %d, open name = %s\n",
+ fsp->fnum, fsp->fsp_name));
+
+ chain_reply(req);
+ END_PROFILE(SMBntcreateX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call to open a pipe.
+****************************************************************************/
+
+static void do_nt_transact_create_pipe(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup, uint32 setup_count,
+ char **ppparams, uint32 parameter_count,
+ char **ppdata, uint32 data_count)
+{
+ char *fname = NULL;
+ char *params = *ppparams;
+ int pnum = -1;
+ char *p = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ uint32 flags;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("do_nt_transact_create_pipe - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ flags = IVAL(params,0);
+
+ srvstr_get_path(ctx, params, req->flags2, &fname, params+53,
+ parameter_count-53, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* Error return */
+ return;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ p = params;
+ SCVAL(p,0,NO_OPLOCK_RETURN);
+
+ p += 2;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 8;
+
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_nt_transact_create_pipe: open name = %s\n", fname));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+
+ return;
+}
+
+/****************************************************************************
+ Internal fn to set security descriptors.
+****************************************************************************/
+
+static NTSTATUS set_sd(files_struct *fsp, uint8 *data, uint32 sd_len,
+ uint32 security_info_sent)
+{
+ SEC_DESC *psd = NULL;
+ NTSTATUS status;
+
+ if (sd_len == 0 || !lp_nt_acl_support(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ status = unmarshall_sec_desc(talloc_tos(), data, sd_len, &psd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (psd->owner_sid==0) {
+ security_info_sent &= ~OWNER_SECURITY_INFORMATION;
+ }
+ if (psd->group_sid==0) {
+ security_info_sent &= ~GROUP_SECURITY_INFORMATION;
+ }
+ if (psd->sacl==0) {
+ security_info_sent &= ~SACL_SECURITY_INFORMATION;
+ }
+ if (psd->dacl==0) {
+ security_info_sent &= ~DACL_SECURITY_INFORMATION;
+ }
+
+ status = SMB_VFS_FSET_NT_ACL(fsp, security_info_sent, psd);
+
+ TALLOC_FREE(psd);
+
+ return status;
+}
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+static struct ea_list *read_nttrans_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t offset = 0;
+
+ if (data_size < 4) {
+ return NULL;
+ }
+
+ while (offset + 4 <= data_size) {
+ size_t next_offset = IVAL(pdata,offset);
+ struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset + 4, data_size - offset - 4, NULL);
+
+ if (!eal) {
+ return NULL;
+ }
+
+ DLIST_ADD_END(ea_list_head, eal, struct ea_list *);
+ if (next_offset == 0) {
+ break;
+ }
+ offset += next_offset;
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call (needs to process SD's).
+****************************************************************************/
+
+static void call_nt_transact_create(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup, uint32 setup_count,
+ char **ppparams, uint32 parameter_count,
+ char **ppdata, uint32 data_count,
+ uint32 max_data_count)
+{
+ char *fname = NULL;
+ char *params = *ppparams;
+ char *data = *ppdata;
+ /* Breakout the oplock request bits so we can set the reply bits separately. */
+ uint32 fattr=0;
+ SMB_OFF_T file_len = 0;
+ SMB_STRUCT_STAT sbuf;
+ int info = 0;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ uint32 flags;
+ uint32 access_mask;
+ uint32 file_attributes;
+ uint32 share_access;
+ uint32 create_disposition;
+ uint32 create_options;
+ uint32 sd_len;
+ struct security_descriptor *sd = NULL;
+ uint32 ea_len;
+ uint16 root_dir_fid;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ SMB_BIG_UINT allocation_size;
+ int oplock_request;
+ uint8_t oplock_granted;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(5,("call_nt_transact_create\n"));
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_nt_transact_create_pipe(
+ conn, req,
+ ppsetup, setup_count,
+ ppparams, parameter_count,
+ ppdata, data_count);
+ return;
+ }
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("call_nt_transact_create - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ flags = IVAL(params,0);
+ access_mask = IVAL(params,8);
+ file_attributes = IVAL(params,20);
+ share_access = IVAL(params,24);
+ create_disposition = IVAL(params,28);
+ create_options = IVAL(params,32);
+ sd_len = IVAL(params,36);
+ ea_len = IVAL(params,40);
+ root_dir_fid = (uint16)IVAL(params,4);
+ allocation_size = (SMB_BIG_UINT)IVAL(params,12);
+#ifdef LARGE_SMB_OFF_T
+ allocation_size |= (((SMB_BIG_UINT)IVAL(params,16)) << 32);
+#endif
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ /* Ensure the data_len is correct for the sd and ea values given. */
+ if ((ea_len + sd_len > data_count)
+ || (ea_len > data_count) || (sd_len > data_count)
+ || (ea_len + sd_len < ea_len) || (ea_len + sd_len < sd_len)) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u, sd_len = "
+ "%u, data_count = %u\n", (unsigned int)ea_len,
+ (unsigned int)sd_len, (unsigned int)data_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (sd_len) {
+ DEBUG(10, ("call_nt_transact_create - sd_len = %d\n",
+ sd_len));
+
+ status = unmarshall_sec_desc(ctx, (uint8_t *)data, sd_len,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("call_nt_transact_create: "
+ "unmarshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ if (ea_len) {
+ if (!lp_ea_support(SNUM(conn))) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u but "
+ "EA's not supported.\n",
+ (unsigned int)ea_len));
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ if (ea_len < 10) {
+ DEBUG(10,("call_nt_transact_create - ea_len = %u - "
+ "too small (should be more than 10)\n",
+ (unsigned int)ea_len ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* We have already checked that ea_len <= data_count here. */
+ ea_list = read_nttrans_ea_list(talloc_tos(), data + sd_len,
+ ea_len);
+ if (ea_list == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ srvstr_get_path(ctx, params, req->flags2, &fname,
+ params+53, parameter_count-53,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ status = create_file(conn, req, root_dir_fid, fname,
+ access_mask, share_access, create_disposition,
+ create_options, file_attributes, oplock_request,
+ allocation_size, sd, ea_list, &fsp, &info, &sbuf);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ return;
+ }
+ reply_openerror(req, status);
+ return;
+ }
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = sbuf.st_size;
+ fattr = dos_mode(conn,fsp->fsp_name,&sbuf);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ p = params;
+ SCVAL(p, 0, oplock_granted);
+
+ p += 2;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 8;
+
+ /* Create time. */
+ c_timespec = get_create_timespec(
+ &sbuf,lp_fake_dir_create_times(SNUM(conn)));
+ a_timespec = get_atimespec(&sbuf);
+ m_timespec = get_mtimespec(&sbuf);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&c_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ }
+
+ put_long_date_timespec(p, c_timespec); /* create time. */
+ p += 8;
+ put_long_date_timespec(p, a_timespec); /* access time */
+ p += 8;
+ put_long_date_timespec(p, m_timespec); /* write time */
+ p += 8;
+ put_long_date_timespec(p, m_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, get_allocation_size(conn,fsp,&sbuf));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ SSVAL(p,2,0x7);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32 perms = 0;
+ p += 25;
+ if (fsp->is_directory
+ || can_write_to_file(conn, fsp->fsp_name, &sbuf)) {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("call_nt_transact_create: open name = %s\n", fsp->fsp_name));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT CANCEL request.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ntcancel(struct smb_request *req)
+{
+ /*
+ * Go through and cancel any pending change notifies.
+ */
+
+ START_PROFILE(SMBntcancel);
+ remove_pending_change_notify_requests_by_mid(req->mid);
+ remove_pending_lock_requests_by_mid(req->mid);
+ srv_cancel_sign_response(req->mid);
+
+ DEBUG(3,("reply_ntcancel: cancel called on mid = %d.\n", req->mid));
+
+ END_PROFILE(SMBntcancel);
+ return;
+}
+
+/****************************************************************************
+ Copy a file.
+****************************************************************************/
+
+static NTSTATUS copy_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ const char *oldname_in,
+ const char *newname_in,
+ uint32 attrs)
+{
+ SMB_STRUCT_STAT sbuf1, sbuf2;
+ char *oldname = NULL;
+ char *newname = NULL;
+ char *last_component_oldname = NULL;
+ char *last_component_newname = NULL;
+ files_struct *fsp1,*fsp2;
+ uint32 fattr;
+ int info;
+ SMB_OFF_T ret=-1;
+ NTSTATUS status = NT_STATUS_OK;
+
+ ZERO_STRUCT(sbuf1);
+ ZERO_STRUCT(sbuf2);
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ status = unix_convert(ctx, conn, oldname_in, False, &oldname,
+ &last_component_oldname, &sbuf1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_name(conn, oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Source must already exist. */
+ if (!VALID_STAT(sbuf1)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /* Ensure attributes match. */
+ fattr = dos_mode(conn,oldname,&sbuf1);
+ if ((fattr & ~attrs) & (aHIDDEN | aSYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ status = unix_convert(ctx, conn, newname_in, False, &newname,
+ &last_component_newname, &sbuf2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_name(conn, newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Disallow if newname already exists. */
+ if (VALID_STAT(sbuf2)) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(sbuf1.st_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ /* Ensure this is within the share. */
+ status = check_reduced_name(conn, oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("copy_internals: doing file copy %s to %s\n",
+ oldname, newname));
+
+ status = open_file_ntcreate(conn, req, oldname, &sbuf1,
+ FILE_READ_DATA, /* Read-only. */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0, /* No create options. */
+ FILE_ATTRIBUTE_NORMAL,
+ NO_OPLOCK,
+ &info, &fsp1);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = open_file_ntcreate(conn, req, newname, &sbuf2,
+ FILE_WRITE_DATA, /* Read-only. */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0, /* No create options. */
+ fattr,
+ NO_OPLOCK,
+ &info, &fsp2);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file(fsp1,ERROR_CLOSE);
+ return status;
+ }
+
+ if (sbuf1.st_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, sbuf1.st_size);
+ }
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ close_file(fsp1,NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, get_mtimespec(&sbuf1));
+
+ status = close_file(fsp2,NORMAL_CLOSE);
+
+ /* Grrr. We have to do this as open_file_ntcreate adds aARCH when it
+ creates the file. This isn't the correct thing to do in the copy
+ case. JRA */
+ file_set_dosmode(conn, newname, fattr, &sbuf2,
+ parent_dirname(newname),false);
+
+ if (ret < (SMB_OFF_T)sbuf1.st_size) {
+ return NT_STATUS_DISK_FULL;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("copy_internals: Error %s copy file %s to %s\n",
+ nt_errstr(status), oldname, newname));
+ }
+ return status;
+}
+
+/****************************************************************************
+ Reply to a NT rename request.
+****************************************************************************/
+
+void reply_ntrename(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *oldname = NULL;
+ char *newname = NULL;
+ char *p;
+ NTSTATUS status;
+ bool src_has_wcard = False;
+ bool dest_has_wcard = False;
+ uint32 attrs;
+ uint16 rename_type;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBntrename);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ attrs = SVAL(req->inbuf,smb_vwv0);
+ rename_type = SVAL(req->inbuf,smb_vwv1);
+
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &oldname, p,
+ 0, STR_TERMINATE, &status,
+ &src_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ if( is_ntfs_stream_name(oldname)) {
+ /* Can't rename a stream. */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ if (ms_has_wild(oldname)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ p++;
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &newname, p,
+ 0, STR_TERMINATE, &status,
+ &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ oldname,
+ &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ newname,
+ &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ DEBUG(3,("reply_ntrename : %s -> %s\n",oldname,newname));
+
+ switch(rename_type) {
+ case RENAME_FLAG_RENAME:
+ status = rename_internals(ctx, conn, req, oldname,
+ newname, attrs, False, src_has_wcard,
+ dest_has_wcard, DELETE_ACCESS);
+ break;
+ case RENAME_FLAG_HARD_LINK:
+ if (src_has_wcard || dest_has_wcard) {
+ /* No wildcards. */
+ status = NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ } else {
+ status = hardlink_internals(ctx,
+ conn,
+ oldname,
+ newname);
+ }
+ break;
+ case RENAME_FLAG_COPY:
+ if (src_has_wcard || dest_has_wcard) {
+ /* No wildcards. */
+ status = NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ } else {
+ status = copy_internals(ctx, conn, req, oldname,
+ newname, attrs);
+ }
+ break;
+ case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ default:
+ status = NT_STATUS_ACCESS_DENIED; /* Default error. */
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ reply_nterror(req, status);
+ END_PROFILE(SMBntrename);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBntrename);
+ return;
+}
+
+/****************************************************************************
+ Reply to a notify change - queue the request and
+ don't allow a directory to be opened.
+****************************************************************************/
+
+static void call_nt_transact_notify_change(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup,
+ uint32 setup_count,
+ char **ppparams,
+ uint32 parameter_count,
+ char **ppdata, uint32 data_count,
+ uint32 max_data_count,
+ uint32 max_param_count)
+{
+ uint16 *setup = *ppsetup;
+ files_struct *fsp;
+ uint32 filter;
+ NTSTATUS status;
+ bool recursive;
+
+ if(setup_count < 6) {
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(setup,4));
+ filter = IVAL(setup, 0);
+ recursive = (SVAL(setup, 6) != 0) ? True : False;
+
+ DEBUG(3,("call_nt_transact_notify_change\n"));
+
+ if(!fsp) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ {
+ char *filter_string;
+
+ if (!(filter_string = notify_filter_string(NULL, filter))) {
+ reply_nterror(req,NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ DEBUG(3,("call_nt_transact_notify_change: notify change "
+ "called on %s, filter = %s, recursive = %d\n",
+ fsp->fsp_name, filter_string, recursive));
+
+ TALLOC_FREE(filter_string);
+ }
+
+ if((!fsp->is_directory) || (conn != fsp->conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (fsp->notify == NULL) {
+
+ status = change_notify_create(fsp, filter, recursive);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("change_notify_create returned %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ if (fsp->notify->num_changes != 0) {
+
+ /*
+ * We've got changes pending, respond immediately
+ */
+
+ /*
+ * TODO: write a torture test to check the filtering behaviour
+ * here.
+ */
+
+ change_notify_reply(fsp->conn, req->inbuf, max_param_count, fsp->notify);
+
+ /*
+ * change_notify_reply() above has independently sent its
+ * results
+ */
+ return;
+ }
+
+ /*
+ * No changes pending, queue the request
+ */
+
+ status = change_notify_add_request(req,
+ max_param_count,
+ filter,
+ recursive, fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT transact rename command.
+****************************************************************************/
+
+static void call_nt_transact_rename(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup, uint32 setup_count,
+ char **ppparams, uint32 parameter_count,
+ char **ppdata, uint32 data_count,
+ uint32 max_data_count)
+{
+ char *params = *ppparams;
+ char *new_name = NULL;
+ files_struct *fsp = NULL;
+ bool dest_has_wcard = False;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if(parameter_count < 5) {
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(params, 0));
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+ srvstr_get_path_wcard(ctx, params, req->flags2, &new_name, params+4,
+ parameter_count - 4,
+ STR_TERMINATE, &status, &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * W2K3 ignores this request as the RAW-RENAME test
+ * demonstrates, so we do.
+ */
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+
+ DEBUG(3,("nt transact rename from = %s, to = %s ignored!\n",
+ fsp->fsp_name, new_name));
+
+ return;
+}
+
+/******************************************************************************
+ Fake up a completely empty SD.
+*******************************************************************************/
+
+static NTSTATUS get_null_nt_acl(TALLOC_CTX *mem_ctx, SEC_DESC **ppsd)
+{
+ size_t sd_size;
+
+ *ppsd = make_standard_sec_desc( mem_ctx, &global_sid_World, &global_sid_World, NULL, &sd_size);
+ if(!*ppsd) {
+ DEBUG(0,("get_null_nt_acl: Unable to malloc space for security descriptor.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to query a security descriptor.
+****************************************************************************/
+
+static void call_nt_transact_query_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup,
+ uint32 setup_count,
+ char **ppparams,
+ uint32 parameter_count,
+ char **ppdata,
+ uint32 data_count,
+ uint32 max_data_count)
+{
+ char *params = *ppparams;
+ char *data = *ppdata;
+ SEC_DESC *psd = NULL;
+ size_t sd_size;
+ uint32 security_info_wanted;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ if(parameter_count < 8) {
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(params,0));
+ if(!fsp) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ security_info_wanted = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_query_security_desc: file = %s, info_wanted = 0x%x\n", fsp->fsp_name,
+ (unsigned int)security_info_wanted ));
+
+ params = nttrans_realloc(ppparams, 4);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ /*
+ * Get the permissions to return.
+ */
+
+ if (!lp_nt_acl_support(SNUM(conn))) {
+ status = get_null_nt_acl(talloc_tos(), &psd);
+ } else {
+ status = SMB_VFS_FGET_NT_ACL(
+ fsp, security_info_wanted, &psd);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ sd_size = ndr_size_security_descriptor(psd, 0);
+
+ DEBUG(3,("call_nt_transact_query_security_desc: sd_size = %lu.\n",(unsigned long)sd_size));
+
+ SIVAL(params,0,(uint32)sd_size);
+
+ if (max_data_count < sd_size) {
+ send_nt_replies(conn, req, NT_STATUS_BUFFER_TOO_SMALL,
+ params, 4, *ppdata, 0);
+ return;
+ }
+
+ /*
+ * Allocate the data we will point this at.
+ */
+
+ data = nttrans_realloc(ppdata, sd_size);
+ if(data == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ status = marshall_sec_desc(talloc_tos(), psd,
+ &blob.data, &blob.length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SMB_ASSERT(sd_size == blob.length);
+ memcpy(data, blob.data, sd_size);
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, 4, data, (int)sd_size);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor. Map to UNIX perms or POSIX ACLs.
+****************************************************************************/
+
+static void call_nt_transact_set_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup,
+ uint32 setup_count,
+ char **ppparams,
+ uint32 parameter_count,
+ char **ppdata,
+ uint32 data_count,
+ uint32 max_data_count)
+{
+ char *params= *ppparams;
+ char *data = *ppdata;
+ files_struct *fsp = NULL;
+ uint32 security_info_sent = 0;
+ NTSTATUS status;
+
+ if(parameter_count < 8) {
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+
+ if((fsp = file_fsp(SVAL(params,0))) == NULL) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ if(!lp_nt_acl_support(SNUM(conn))) {
+ goto done;
+ }
+
+ security_info_sent = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_set_security_desc: file = %s, sent 0x%x\n", fsp->fsp_name,
+ (unsigned int)security_info_sent ));
+
+ if (data_count == 0) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ status = set_sd(fsp, (uint8 *)data, data_count, security_info_sent);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ done:
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+ return;
+}
+
+/****************************************************************************
+ Reply to NT IOCTL
+****************************************************************************/
+
+static void call_nt_transact_ioctl(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup, uint32 setup_count,
+ char **ppparams, uint32 parameter_count,
+ char **ppdata, uint32 data_count,
+ uint32 max_data_count)
+{
+ uint32 function;
+ uint16 fidnum;
+ files_struct *fsp;
+ uint8 isFSctl;
+ uint8 compfilter;
+ static bool logged_message;
+ char *pdata = *ppdata;
+
+ if (setup_count != 8) {
+ DEBUG(3,("call_nt_transact_ioctl: invalid setup count %d\n", setup_count));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ function = IVAL(*ppsetup, 0);
+ fidnum = SVAL(*ppsetup, 4);
+ isFSctl = CVAL(*ppsetup, 6);
+ compfilter = CVAL(*ppsetup, 7);
+
+ DEBUG(10,("call_nt_transact_ioctl: function[0x%08X] FID[0x%04X] isFSctl[0x%02X] compfilter[0x%02X]\n",
+ function, fidnum, isFSctl, compfilter));
+
+ fsp=file_fsp(fidnum);
+ /* this check is done in each implemented function case for now
+ because I don't want to break anything... --metze
+ FSP_BELONGS_CONN(fsp,conn);*/
+
+ switch (function) {
+ case FSCTL_SET_SPARSE:
+ /* pretend this succeeded - tho strictly we should
+ mark the file sparse (if the local fs supports it)
+ so we can know if we need to pre-allocate or not */
+
+ DEBUG(10,("FSCTL_SET_SPARSE: called on FID[0x%04X](but not implemented)\n", fidnum));
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+ return;
+
+ case FSCTL_CREATE_OR_GET_OBJECT_ID:
+ {
+ unsigned char objid[16];
+
+ /* This should return the object-id on this file.
+ * I think I'll make this be the inode+dev. JRA.
+ */
+
+ DEBUG(10,("FSCTL_CREATE_OR_GET_OBJECT_ID: called on FID[0x%04X]\n",fidnum));
+
+ if (!fsp_belongs_conn(conn, req, fsp)) {
+ return;
+ }
+
+ data_count = 64;
+ pdata = nttrans_realloc(ppdata, data_count);
+ if (pdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ push_file_id_16(pdata, &fsp->file_id);
+ memcpy(pdata+16,create_volume_objectid(conn,objid),16);
+ push_file_id_16(pdata+32, &fsp->file_id);
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0,
+ pdata, data_count);
+ return;
+ }
+
+ case FSCTL_GET_REPARSE_POINT:
+ /* pretend this fail - my winXP does it like this
+ * --metze
+ */
+
+ DEBUG(10,("FSCTL_GET_REPARSE_POINT: called on FID[0x%04X](but not implemented)\n",fidnum));
+ reply_nterror(req, NT_STATUS_NOT_A_REPARSE_POINT);
+ return;
+
+ case FSCTL_SET_REPARSE_POINT:
+ /* pretend this fail - I'm assuming this because of the FSCTL_GET_REPARSE_POINT case.
+ * --metze
+ */
+
+ DEBUG(10,("FSCTL_SET_REPARSE_POINT: called on FID[0x%04X](but not implemented)\n",fidnum));
+ reply_nterror(req, NT_STATUS_NOT_A_REPARSE_POINT);
+ return;
+
+ case FSCTL_GET_SHADOW_COPY_DATA: /* don't know if this name is right...*/
+ {
+ /*
+ * This is called to retrieve the number of Shadow Copies (a.k.a. snapshots)
+ * and return their volume names. If max_data_count is 16, then it is just
+ * asking for the number of volumes and length of the combined names.
+ *
+ * pdata is the data allocated by our caller, but that uses
+ * total_data_count (which is 0 in our case) rather than max_data_count.
+ * Allocate the correct amount and return the pointer to let
+ * it be deallocated when we return.
+ */
+ SHADOW_COPY_DATA *shadow_data = NULL;
+ TALLOC_CTX *shadow_mem_ctx = NULL;
+ bool labels = False;
+ uint32 labels_data_count = 0;
+ uint32 i;
+ char *cur_pdata;
+
+ if (!fsp_belongs_conn(conn, req, fsp)) {
+ return;
+ }
+
+ if (max_data_count < 16) {
+ DEBUG(0,("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) < 16 is invalid!\n",
+ max_data_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (max_data_count > 16) {
+ labels = True;
+ }
+
+ shadow_mem_ctx = talloc_init("SHADOW_COPY_DATA");
+ if (shadow_mem_ctx == NULL) {
+ DEBUG(0,("talloc_init(SHADOW_COPY_DATA) failed!\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ shadow_data = TALLOC_ZERO_P(shadow_mem_ctx,SHADOW_COPY_DATA);
+ if (shadow_data == NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed!\n"));
+ talloc_destroy(shadow_mem_ctx);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ shadow_data->mem_ctx = shadow_mem_ctx;
+
+ /*
+ * Call the VFS routine to actually do the work.
+ */
+ if (SMB_VFS_GET_SHADOW_COPY_DATA(fsp, shadow_data, labels)!=0) {
+ talloc_destroy(shadow_data->mem_ctx);
+ if (errno == ENOSYS) {
+ DEBUG(5,("FSCTL_GET_SHADOW_COPY_DATA: connectpath %s, not supported.\n",
+ conn->connectpath));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ } else {
+ DEBUG(0,("FSCTL_GET_SHADOW_COPY_DATA: connectpath %s, failed.\n",
+ conn->connectpath));
+ reply_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+ }
+
+ labels_data_count = (shadow_data->num_volumes*2*sizeof(SHADOW_COPY_LABEL))+2;
+
+ if (!labels) {
+ data_count = 16;
+ } else {
+ data_count = 12+labels_data_count+4;
+ }
+
+ if (max_data_count<data_count) {
+ DEBUG(0,("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) too small (%u) bytes needed!\n",
+ max_data_count,data_count));
+ talloc_destroy(shadow_data->mem_ctx);
+ reply_nterror(req, NT_STATUS_BUFFER_TOO_SMALL);
+ return;
+ }
+
+ pdata = nttrans_realloc(ppdata, data_count);
+ if (pdata == NULL) {
+ talloc_destroy(shadow_data->mem_ctx);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ cur_pdata = pdata;
+
+ /* num_volumes 4 bytes */
+ SIVAL(pdata,0,shadow_data->num_volumes);
+
+ if (labels) {
+ /* num_labels 4 bytes */
+ SIVAL(pdata,4,shadow_data->num_volumes);
+ }
+
+ /* needed_data_count 4 bytes */
+ SIVAL(pdata,8,labels_data_count);
+
+ cur_pdata+=12;
+
+ DEBUG(10,("FSCTL_GET_SHADOW_COPY_DATA: %u volumes for path[%s].\n",
+ shadow_data->num_volumes,fsp->fsp_name));
+ if (labels && shadow_data->labels) {
+ for (i=0;i<shadow_data->num_volumes;i++) {
+ srvstr_push(pdata, req->flags2,
+ cur_pdata, shadow_data->labels[i],
+ 2*sizeof(SHADOW_COPY_LABEL),
+ STR_UNICODE|STR_TERMINATE);
+ cur_pdata+=2*sizeof(SHADOW_COPY_LABEL);
+ DEBUGADD(10,("Label[%u]: '%s'\n",i,shadow_data->labels[i]));
+ }
+ }
+
+ talloc_destroy(shadow_data->mem_ctx);
+
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0,
+ pdata, data_count);
+
+ return;
+ }
+
+ case FSCTL_FIND_FILES_BY_SID: /* I hope this name is right */
+ {
+ /* pretend this succeeded -
+ *
+ * we have to send back a list with all files owned by this SID
+ *
+ * but I have to check that --metze
+ */
+ DOM_SID sid;
+ uid_t uid;
+ size_t sid_len = MIN(data_count-4,SID_MAX_SIZE);
+
+ DEBUG(10,("FSCTL_FIND_FILES_BY_SID: called on FID[0x%04X]\n",fidnum));
+
+ if (!fsp_belongs_conn(conn, req, fsp)) {
+ return;
+ }
+
+ /* unknown 4 bytes: this is not the length of the sid :-( */
+ /*unknown = IVAL(pdata,0);*/
+
+ sid_parse(pdata+4,sid_len,&sid);
+ DEBUGADD(10, ("for SID: %s\n", sid_string_dbg(&sid)));
+
+ if (!sid_to_uid(&sid, &uid)) {
+ DEBUG(0,("sid_to_uid: failed, sid[%s] sid_len[%lu]\n",
+ sid_string_dbg(&sid),
+ (unsigned long)sid_len));
+ uid = (-1);
+ }
+
+ /* we can take a look at the find source :-)
+ *
+ * find ./ -uid $uid -name '*' is what we need here
+ *
+ *
+ * and send 4bytes len and then NULL terminated unicode strings
+ * for each file
+ *
+ * but I don't know how to deal with the paged results
+ * (maybe we can hang the result anywhere in the fsp struct)
+ *
+ * we don't send all files at once
+ * and at the next we should *not* start from the beginning,
+ * so we have to cache the result
+ *
+ * --metze
+ */
+
+ /* this works for now... */
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+ return;
+ }
+ default:
+ if (!logged_message) {
+ logged_message = True; /* Only print this once... */
+ DEBUG(0,("call_nt_transact_ioctl(0x%x): Currently not implemented.\n",
+ function));
+ }
+ }
+
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+}
+
+
+#ifdef HAVE_SYS_QUOTAS
+/****************************************************************************
+ Reply to get user quota
+****************************************************************************/
+
+static void call_nt_transact_get_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup,
+ uint32 setup_count,
+ char **ppparams,
+ uint32 parameter_count,
+ char **ppdata,
+ uint32 data_count,
+ uint32 max_data_count)
+{
+ NTSTATUS nt_status = NT_STATUS_OK;
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ char *entry;
+ int data_len=0,param_len=0;
+ int qt_len=0;
+ int entry_len = 0;
+ files_struct *fsp = NULL;
+ uint16 level = 0;
+ size_t sid_len;
+ DOM_SID sid;
+ bool start_enum = True;
+ SMB_NTQUOTA_STRUCT qt;
+ SMB_NTQUOTA_LIST *tmp_list;
+ SMB_NTQUOTA_HANDLE *qt_handle = NULL;
+
+ ZERO_STRUCT(qt);
+
+ /* access check */
+ if (conn->server_info->utok.uid != 0) {
+ DEBUG(1,("get_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name));
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if (parameter_count < 4) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA: requires %d >= 4 bytes parameters\n",parameter_count));
+ reply_doserror(req, ERRDOS, ERRinvalidparam);
+ return;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(SVAL(params,0));
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ /* the NULL pointer checking for fsp->fake_file_handle->pd
+ * is done by CHECK_NTQUOTA_HANDLE_OK()
+ */
+ qt_handle = (SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
+
+ level = SVAL(params,2);
+
+ /* unknown 12 bytes leading in params */
+
+ switch (level) {
+ case TRANSACT_GET_USER_QUOTA_LIST_CONTINUE:
+ /* seems that we should continue with the enum here --metze */
+
+ if (qt_handle->quota_list!=NULL &&
+ qt_handle->tmp_list==NULL) {
+
+ /* free the list */
+ free_ntquota_list(&(qt_handle->quota_list));
+
+ /* Realloc the size of parameters and data we will return */
+ param_len = 4;
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ data_len = 0;
+ SIVAL(params,0,data_len);
+
+ break;
+ }
+
+ start_enum = False;
+
+ case TRANSACT_GET_USER_QUOTA_LIST_START:
+
+ if (qt_handle->quota_list==NULL &&
+ qt_handle->tmp_list==NULL) {
+ start_enum = True;
+ }
+
+ if (start_enum && vfs_get_user_ntquota_list(fsp,&(qt_handle->quota_list))!=0) {
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ param_len = 4;
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ /* we should not trust the value in max_data_count*/
+ max_data_count = MIN(max_data_count,2048);
+
+ pdata = nttrans_realloc(ppdata, max_data_count);/* should be max data count from client*/
+ if(pdata == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ entry = pdata;
+
+ /* set params Size of returned Quota Data 4 bytes*/
+ /* but set it later when we know it */
+
+ /* for each entry push the data */
+
+ if (start_enum) {
+ qt_handle->tmp_list = qt_handle->quota_list;
+ }
+
+ tmp_list = qt_handle->tmp_list;
+
+ for (;((tmp_list!=NULL)&&((qt_len +40+SID_MAX_SIZE)<max_data_count));
+ tmp_list=tmp_list->next,entry+=entry_len,qt_len+=entry_len) {
+
+ sid_len = ndr_size_dom_sid(
+ &tmp_list->quotas->sid, 0);
+ entry_len = 40 + sid_len;
+
+ /* nextoffset entry 4 bytes */
+ SIVAL(entry,0,entry_len);
+
+ /* then the len of the SID 4 bytes */
+ SIVAL(entry,4,sid_len);
+
+ /* unknown data 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,8,(SMB_BIG_UINT)0); /* this is not 0 in windows...-metze*/
+
+ /* the used disk space 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,16,tmp_list->quotas->usedspace);
+
+ /* the soft quotas 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,24,tmp_list->quotas->softlim);
+
+ /* the hard quotas 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,32,tmp_list->quotas->hardlim);
+
+ /* and now the SID */
+ sid_linearize(entry+40, sid_len, &tmp_list->quotas->sid);
+ }
+
+ qt_handle->tmp_list = tmp_list;
+
+ /* overwrite the offset of the last entry */
+ SIVAL(entry-entry_len,0,0);
+
+ data_len = 4+qt_len;
+ /* overwrite the params quota_data_len */
+ SIVAL(params,0,data_len);
+
+ break;
+
+ case TRANSACT_GET_USER_QUOTA_FOR_SID:
+
+ /* unknown 4 bytes IVAL(pdata,0) */
+
+ if (data_count < 8) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: requires %d >= %d bytes data\n",data_count,8));
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+
+ sid_len = IVAL(pdata,4);
+ /* Ensure this is less than 1mb. */
+ if (sid_len > (1024*1024)) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ if (data_count < 8+sid_len) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: requires %d >= %lu bytes data\n",data_count,(unsigned long)(8+sid_len)));
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+
+ data_len = 4+40+sid_len;
+
+ if (max_data_count < data_len) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: max_data_count(%d) < data_len(%d)\n",
+ max_data_count, data_len));
+ param_len = 4;
+ SIVAL(params,0,data_len);
+ data_len = 0;
+ nt_status = NT_STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+
+ sid_parse(pdata+8,sid_len,&sid);
+
+ if (vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &qt)!=0) {
+ ZERO_STRUCT(qt);
+ /*
+ * we have to return zero's in all fields
+ * instead of returning an error here
+ * --metze
+ */
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ param_len = 4;
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ pdata = nttrans_realloc(ppdata, data_len);
+ if(pdata == NULL) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ return;
+ }
+
+ entry = pdata;
+
+ /* set params Size of returned Quota Data 4 bytes*/
+ SIVAL(params,0,data_len);
+
+ /* nextoffset entry 4 bytes */
+ SIVAL(entry,0,0);
+
+ /* then the len of the SID 4 bytes */
+ SIVAL(entry,4,sid_len);
+
+ /* unknown data 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,8,(SMB_BIG_UINT)0); /* this is not 0 in windows...-mezte*/
+
+ /* the used disk space 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,16,qt.usedspace);
+
+ /* the soft quotas 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,24,qt.softlim);
+
+ /* the hard quotas 8 bytes SMB_BIG_UINT */
+ SBIG_UINT(entry,32,qt.hardlim);
+
+ /* and now the SID */
+ sid_linearize(entry+40, sid_len, &sid);
+
+ break;
+
+ default:
+ DEBUG(0,("do_nt_transact_get_user_quota: fnum %d unknown level 0x%04hX\n",fsp->fnum,level));
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ break;
+ }
+
+ send_nt_replies(conn, req, nt_status, params, param_len,
+ pdata, data_len);
+}
+
+/****************************************************************************
+ Reply to set user quota
+****************************************************************************/
+
+static void call_nt_transact_set_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16 **ppsetup,
+ uint32 setup_count,
+ char **ppparams,
+ uint32 parameter_count,
+ char **ppdata,
+ uint32 data_count,
+ uint32 max_data_count)
+{
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ int data_len=0,param_len=0;
+ SMB_NTQUOTA_STRUCT qt;
+ size_t sid_len;
+ DOM_SID sid;
+ files_struct *fsp = NULL;
+
+ ZERO_STRUCT(qt);
+
+ /* access check */
+ if (conn->server_info->utok.uid != 0) {
+ DEBUG(1,("set_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name));
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if (parameter_count < 2) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= 2 bytes parameters\n",parameter_count));
+ reply_doserror(req, ERRDOS, ERRinvalidparam);
+ return;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(SVAL(params,0));
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (data_count < 40) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= %d bytes data\n",data_count,40));
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+
+ /* offset to next quota record.
+ * 4 bytes IVAL(pdata,0)
+ * unused here...
+ */
+
+ /* sid len */
+ sid_len = IVAL(pdata,4);
+
+ if (data_count < 40+sid_len) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= %lu bytes data\n",data_count,(unsigned long)40+sid_len));
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+
+ /* unknown 8 bytes in pdata
+ * maybe its the change time in NTTIME
+ */
+
+ /* the used space 8 bytes (SMB_BIG_UINT)*/
+ qt.usedspace = (SMB_BIG_UINT)IVAL(pdata,16);
+#ifdef LARGE_SMB_OFF_T
+ qt.usedspace |= (((SMB_BIG_UINT)IVAL(pdata,20)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if ((IVAL(pdata,20) != 0)&&
+ ((qt.usedspace != 0xFFFFFFFF)||
+ (IVAL(pdata,20)!=0xFFFFFFFF))) {
+ /* more than 32 bits? */
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ /* the soft quotas 8 bytes (SMB_BIG_UINT)*/
+ qt.softlim = (SMB_BIG_UINT)IVAL(pdata,24);
+#ifdef LARGE_SMB_OFF_T
+ qt.softlim |= (((SMB_BIG_UINT)IVAL(pdata,28)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if ((IVAL(pdata,28) != 0)&&
+ ((qt.softlim != 0xFFFFFFFF)||
+ (IVAL(pdata,28)!=0xFFFFFFFF))) {
+ /* more than 32 bits? */
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ /* the hard quotas 8 bytes (SMB_BIG_UINT)*/
+ qt.hardlim = (SMB_BIG_UINT)IVAL(pdata,32);
+#ifdef LARGE_SMB_OFF_T
+ qt.hardlim |= (((SMB_BIG_UINT)IVAL(pdata,36)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if ((IVAL(pdata,36) != 0)&&
+ ((qt.hardlim != 0xFFFFFFFF)||
+ (IVAL(pdata,36)!=0xFFFFFFFF))) {
+ /* more than 32 bits? */
+ reply_doserror(req, ERRDOS, ERRunknownlevel);
+ return;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ sid_parse(pdata+40,sid_len,&sid);
+ DEBUGADD(8,("SID: %s\n", sid_string_dbg(&sid)));
+
+ /* 44 unknown bytes left... */
+
+ if (vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &qt)!=0) {
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len,
+ pdata, data_len);
+}
+#endif /* HAVE_SYS_QUOTAS */
+
+static void handle_nttrans(connection_struct *conn,
+ struct trans_state *state,
+ struct smb_request *req)
+{
+ if (Protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL(req->inbuf,smb_flg2,req->flags2);
+ }
+
+ /* Now we must call the relevant NT_TRANS function */
+ switch(state->call) {
+ case NT_TRANSACT_CREATE:
+ {
+ START_PROFILE(NT_transact_create);
+ call_nt_transact_create(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_create);
+ break;
+ }
+
+ case NT_TRANSACT_IOCTL:
+ {
+ START_PROFILE(NT_transact_ioctl);
+ call_nt_transact_ioctl(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_ioctl);
+ break;
+ }
+
+ case NT_TRANSACT_SET_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_set_security_desc);
+ call_nt_transact_set_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_security_desc);
+ break;
+ }
+
+ case NT_TRANSACT_NOTIFY_CHANGE:
+ {
+ START_PROFILE(NT_transact_notify_change);
+ call_nt_transact_notify_change(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return,
+ state->max_param_return);
+ END_PROFILE(NT_transact_notify_change);
+ break;
+ }
+
+ case NT_TRANSACT_RENAME:
+ {
+ START_PROFILE(NT_transact_rename);
+ call_nt_transact_rename(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_rename);
+ break;
+ }
+
+ case NT_TRANSACT_QUERY_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_query_security_desc);
+ call_nt_transact_query_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_query_security_desc);
+ break;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ case NT_TRANSACT_GET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_get_user_quota);
+ call_nt_transact_get_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_get_user_quota);
+ break;
+ }
+
+ case NT_TRANSACT_SET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_set_user_quota);
+ call_nt_transact_set_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_user_quota);
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+
+ default:
+ /* Error in request */
+ DEBUG(0,("handle_nttrans: Unknown request %d in "
+ "nttrans call\n", state->call));
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBNTtrans.
+****************************************************************************/
+
+void reply_nttrans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pscnt;
+ uint32_t psoff;
+ uint32_t dscnt;
+ uint32_t dsoff;
+ uint16 function_code;
+ NTSTATUS result;
+ struct trans_state *state;
+ uint32_t size;
+ uint32_t av_size;
+
+ START_PROFILE(SMBnttrans);
+
+ if (req->wct < 19) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ size = smb_len(req->inbuf) + 4;
+ av_size = smb_len(req->inbuf);
+ pscnt = IVAL(req->inbuf,smb_nt_ParameterCount);
+ psoff = IVAL(req->inbuf,smb_nt_ParameterOffset);
+ dscnt = IVAL(req->inbuf,smb_nt_DataCount);
+ dsoff = IVAL(req->inbuf,smb_nt_DataOffset);
+ function_code = SVAL(req->inbuf, smb_nt_Function);
+
+ if (IS_IPC(conn) && (function_code != NT_TRANSACT_CREATE)) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid nttrans request: %s\n", nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((state = TALLOC_P(conn, struct trans_state)) == NULL) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ state->cmd = SMBnttrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->total_data = IVAL(req->inbuf, smb_nt_TotalDataCount);
+ state->data = NULL;
+ state->total_param = IVAL(req->inbuf, smb_nt_TotalParameterCount);
+ state->param = NULL;
+ state->max_data_return = IVAL(req->inbuf,smb_nt_MaxDataCount);
+ state->max_param_return = IVAL(req->inbuf,smb_nt_MaxParameterCount);
+
+ /* setup count is in *words* */
+ state->setup_count = 2*CVAL(req->inbuf,smb_nt_SetupCount);
+ state->setup = NULL;
+ state->call = function_code;
+
+ DEBUG(10, ("num_setup=%u, "
+ "param_total=%u, this_param=%u, max_param=%u, "
+ "data_total=%u, this_data=%u, max_data=%u, "
+ "param_offset=%u, data_offset=%u\n",
+ (unsigned)state->setup_count,
+ (unsigned)state->total_param, (unsigned)pscnt,
+ (unsigned)state->max_param_return,
+ (unsigned)state->total_data, (unsigned)dscnt,
+ (unsigned)state->max_data_return,
+ (unsigned)psoff, (unsigned)dsoff));
+
+ /*
+ * All nttrans messages we handle have smb_wct == 19 +
+ * state->setup_count. Ensure this is so as a sanity check.
+ */
+
+ if(req->wct != 19 + (state->setup_count/2)) {
+ DEBUG(2,("Invalid smb_wct %d in nttrans call (should be %d)\n",
+ req->wct, 19 + (state->setup_count/2)));
+ goto bad_param;
+ }
+
+ /* Don't allow more than 128mb for each value. */
+ if ((state->total_data > (1024*1024*128)) ||
+ (state->total_param > (1024*1024*128))) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->data = (char *)SMB_MALLOC(state->total_data)) == NULL) {
+ DEBUG(0,("reply_nttrans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_doserror(req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if (dscnt > state->total_data ||
+ dsoff+dscnt < dsoff) {
+ goto bad_param;
+ }
+
+ if (dsoff > av_size ||
+ dscnt > av_size ||
+ dsoff+dscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->param = (char *)SMB_MALLOC(state->total_param)) == NULL) {
+ DEBUG(0,("reply_nttrans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_doserror(req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if (pscnt > state->total_param ||
+ psoff+pscnt < psoff) {
+ goto bad_param;
+ }
+
+ if (psoff > av_size ||
+ pscnt > av_size ||
+ psoff+pscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if(state->setup_count > 0) {
+ DEBUG(10,("reply_nttrans: state->setup_count = %d\n",
+ state->setup_count));
+ state->setup = (uint16 *)TALLOC(state, state->setup_count);
+ if (state->setup == NULL) {
+ DEBUG(0,("reply_nttrans : Out of memory\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_doserror(req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((smb_nt_SetupStart + state->setup_count < smb_nt_SetupStart) ||
+ (smb_nt_SetupStart + state->setup_count < state->setup_count)) {
+ goto bad_param;
+ }
+ if (smb_nt_SetupStart + state->setup_count > size) {
+ goto bad_param;
+ }
+
+ memcpy( state->setup, &req->inbuf[smb_nt_SetupStart],
+ state->setup_count);
+ dump_data(10, (uint8 *)state->setup, state->setup_count);
+ }
+
+ if ((state->received_data == state->total_data) &&
+ (state->received_param == state->total_param)) {
+ handle_nttrans(conn, state, req);
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBnttrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttrans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBnttranss
+ ****************************************************************************/
+
+void reply_nttranss(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+ uint32_t av_size;
+ uint32_t size;
+
+ START_PROFILE(SMBnttranss);
+
+ show_msg((char *)req->inbuf);
+
+ if (req->wct < 18) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBnttrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+ if (IVAL(req->inbuf, smb_nts_TotalParameterCount)
+ < state->total_param) {
+ state->total_param = IVAL(req->inbuf,
+ smb_nts_TotalParameterCount);
+ }
+ if (IVAL(req->inbuf, smb_nts_TotalDataCount) < state->total_data) {
+ state->total_data = IVAL(req->inbuf, smb_nts_TotalDataCount);
+ }
+
+ size = smb_len(req->inbuf) + 4;
+ av_size = smb_len(req->inbuf);
+
+ pcnt = IVAL(req->inbuf,smb_nts_ParameterCount);
+ poff = IVAL(req->inbuf, smb_nts_ParameterOffset);
+ pdisp = IVAL(req->inbuf, smb_nts_ParameterDisplacement);
+
+ dcnt = IVAL(req->inbuf, smb_nts_DataCount);
+ ddisp = IVAL(req->inbuf, smb_nts_DataDisplacement);
+ doff = IVAL(req->inbuf, smb_nts_DataOffset);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (pdisp > state->total_param ||
+ pcnt > state->total_param ||
+ pdisp+pcnt > state->total_param ||
+ pdisp+pcnt < pdisp) {
+ goto bad_param;
+ }
+
+ if (poff > av_size ||
+ pcnt > av_size ||
+ poff+pcnt > av_size ||
+ poff+pcnt < poff) {
+ goto bad_param;
+ }
+
+ memcpy(state->param+pdisp, smb_base(req->inbuf)+poff,
+ pcnt);
+ }
+
+ if (dcnt) {
+ if (ddisp > state->total_data ||
+ dcnt > state->total_data ||
+ ddisp+dcnt > state->total_data ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ if (ddisp > av_size ||
+ dcnt > av_size ||
+ ddisp+dcnt > av_size ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,
+ dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ /*
+ * construct_reply_common will copy smb_com from inbuf to
+ * outbuf. SMBnttranss is wrong here.
+ */
+ SCVAL(req->inbuf,smb_com,SMBnttrans);
+
+ handle_nttrans(conn, state, req);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttranss: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+}
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
new file mode 100644
index 0000000000..8b32907a4b
--- /dev/null
+++ b/source3/smbd/open.c
@@ -0,0 +1,3093 @@
+/*
+ Unix SMB/CIFS implementation.
+ file opening and share modes
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2004
+ Copyright (C) Volker Lendecke 2005
+
+ 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"
+
+extern const struct generic_mapping file_generic_mapping;
+extern bool global_client_failed_oplock_break;
+
+struct deferred_open_record {
+ bool delayed_for_oplocks;
+ struct file_id id;
+};
+
+/****************************************************************************
+ fd support routines - attempt to do a dos_open.
+****************************************************************************/
+
+static NTSTATUS fd_open(struct connection_struct *conn,
+ const char *fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ NTSTATUS status = NT_STATUS_OK;
+
+#ifdef O_NOFOLLOW
+ /*
+ * Never follow symlinks on a POSIX client. The
+ * client should be doing this.
+ */
+
+ if (fsp->posix_open || !lp_symlinks(SNUM(conn))) {
+ flags |= O_NOFOLLOW;
+ }
+#endif
+
+ fsp->fh->fd = SMB_VFS_OPEN(conn,fname,fsp,flags,mode);
+ if (fsp->fh->fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10,("fd_open: name %s, flags = 0%o mode = 0%o, fd = %d. %s\n",
+ fname, flags, (int)mode, fsp->fh->fd,
+ (fsp->fh->fd == -1) ? strerror(errno) : "" ));
+
+ return status;
+}
+
+/****************************************************************************
+ Close the file associated with a fsp.
+****************************************************************************/
+
+NTSTATUS fd_close(files_struct *fsp)
+{
+ int ret;
+
+ if (fsp->fh->fd == -1) {
+ return NT_STATUS_OK; /* What we used to call a stat open. */
+ }
+ if (fsp->fh->ref_count > 1) {
+ return NT_STATUS_OK; /* Shared handle. Only close last reference. */
+ }
+
+ ret = SMB_VFS_CLOSE(fsp);
+ fsp->fh->fd = -1;
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Change the ownership of a file to that of the parent directory.
+ Do this by fd if possible.
+****************************************************************************/
+
+static void change_file_owner_to_parent(connection_struct *conn,
+ const char *inherit_from_dir,
+ files_struct *fsp)
+{
+ SMB_STRUCT_STAT parent_st;
+ int ret;
+
+ ret = SMB_VFS_STAT(conn, inherit_from_dir, &parent_st);
+ if (ret == -1) {
+ DEBUG(0,("change_file_owner_to_parent: failed to stat parent "
+ "directory %s. Error was %s\n",
+ inherit_from_dir, strerror(errno) ));
+ return;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp, parent_st.st_uid, (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ DEBUG(0,("change_file_owner_to_parent: failed to fchown "
+ "file %s to parent directory uid %u. Error "
+ "was %s\n", fsp->fsp_name,
+ (unsigned int)parent_st.st_uid,
+ strerror(errno) ));
+ }
+
+ DEBUG(10,("change_file_owner_to_parent: changed new file %s to "
+ "parent directory uid %u.\n", fsp->fsp_name,
+ (unsigned int)parent_st.st_uid ));
+}
+
+static NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
+ const char *inherit_from_dir,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ char *saved_dir = NULL;
+ SMB_STRUCT_STAT sbuf;
+ SMB_STRUCT_STAT parent_st;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ ret = SMB_VFS_STAT(conn, inherit_from_dir, &parent_st);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(0,("change_dir_owner_to_parent: failed to stat parent "
+ "directory %s. Error was %s\n",
+ inherit_from_dir, strerror(errno) ));
+ return status;
+ }
+
+ /* We've already done an lstat into psbuf, and we know it's a
+ directory. If we can cd into the directory and the dev/ino
+ are the same then we can safely chown without races as
+ we're locking the directory in place by being in it. This
+ should work on any UNIX (thanks tridge :-). JRA.
+ */
+
+ saved_dir = vfs_GetWd(ctx,conn);
+ if (!saved_dir) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(0,("change_dir_owner_to_parent: failed to get "
+ "current working directory. Error was %s\n",
+ strerror(errno)));
+ return status;
+ }
+
+ /* Chdir into the new path. */
+ if (vfs_ChDir(conn, fname) == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(0,("change_dir_owner_to_parent: failed to change "
+ "current working directory to %s. Error "
+ "was %s\n", fname, strerror(errno) ));
+ goto out;
+ }
+
+ if (SMB_VFS_STAT(conn,".",&sbuf) == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(0,("change_dir_owner_to_parent: failed to stat "
+ "directory '.' (%s) Error was %s\n",
+ fname, strerror(errno)));
+ goto out;
+ }
+
+ /* Ensure we're pointing at the same place. */
+ if (sbuf.st_dev != psbuf->st_dev ||
+ sbuf.st_ino != psbuf->st_ino ||
+ sbuf.st_mode != psbuf->st_mode ) {
+ DEBUG(0,("change_dir_owner_to_parent: "
+ "device/inode/mode on directory %s changed. "
+ "Refusing to chown !\n", fname ));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ become_root();
+ ret = SMB_VFS_CHOWN(conn, ".", parent_st.st_uid, (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10,("change_dir_owner_to_parent: failed to chown "
+ "directory %s to parent directory uid %u. "
+ "Error was %s\n", fname,
+ (unsigned int)parent_st.st_uid, strerror(errno) ));
+ goto out;
+ }
+
+ DEBUG(10,("change_dir_owner_to_parent: changed ownership of new "
+ "directory %s to parent directory uid %u.\n",
+ fname, (unsigned int)parent_st.st_uid ));
+
+ out:
+
+ vfs_ChDir(conn,saved_dir);
+ return status;
+}
+
+/****************************************************************************
+ Open a file.
+****************************************************************************/
+
+static NTSTATUS open_file(files_struct *fsp,
+ connection_struct *conn,
+ struct smb_request *req,
+ const char *parent_dir,
+ const char *name,
+ const char *path,
+ SMB_STRUCT_STAT *psbuf,
+ int flags,
+ mode_t unx_mode,
+ uint32 access_mask, /* client requested access mask. */
+ uint32 open_access_mask) /* what we're actually using in the open. */
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int accmode = (flags & O_ACCMODE);
+ int local_flags = flags;
+ bool file_existed = VALID_STAT(*psbuf);
+
+ fsp->fh->fd = -1;
+ errno = EPERM;
+
+ /* Check permissions */
+
+ /*
+ * This code was changed after seeing a client open request
+ * containing the open mode of (DENY_WRITE/read-only) with
+ * the 'create if not exist' bit set. The previous code
+ * would fail to open the file read only on a read-only share
+ * as it was checking the flags parameter directly against O_RDONLY,
+ * this was failing as the flags parameter was set to O_RDONLY|O_CREAT.
+ * JRA.
+ */
+
+ if (!CAN_WRITE(conn)) {
+ /* It's a read-only share - fail if we wanted to write. */
+ if(accmode != O_RDONLY) {
+ DEBUG(3,("Permission denied opening %s\n", path));
+ return NT_STATUS_ACCESS_DENIED;
+ } else if(flags & O_CREAT) {
+ /* We don't want to write - but we must make sure that
+ O_CREAT doesn't create the file if we have write
+ access into the directory.
+ */
+ flags &= ~O_CREAT;
+ local_flags &= ~O_CREAT;
+ }
+ }
+
+ /*
+ * This little piece of insanity is inspired by the
+ * fact that an NT client can open a file for O_RDONLY,
+ * but set the create disposition to FILE_EXISTS_TRUNCATE.
+ * If the client *can* write to the file, then it expects to
+ * truncate the file, even though it is opening for readonly.
+ * Quicken uses this stupid trick in backup file creation...
+ * Thanks *greatly* to "David W. Chapman Jr." <dwcjr@inethouston.net>
+ * for helping track this one down. It didn't bite us in 2.0.x
+ * as we always opened files read-write in that release. JRA.
+ */
+
+ if ((accmode == O_RDONLY) && ((flags & O_TRUNC) == O_TRUNC)) {
+ DEBUG(10,("open_file: truncate requested on read-only open "
+ "for file %s\n", path));
+ local_flags = (flags & ~O_ACCMODE)|O_RDWR;
+ }
+
+ if ((open_access_mask & (FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_EXECUTE)) ||
+ (!file_existed && (local_flags & O_CREAT)) ||
+ ((local_flags & O_TRUNC) == O_TRUNC) ) {
+
+ /*
+ * We can't actually truncate here as the file may be locked.
+ * open_file_ntcreate will take care of the truncate later. JRA.
+ */
+
+ local_flags &= ~O_TRUNC;
+
+#if defined(O_NONBLOCK) && defined(S_ISFIFO)
+ /*
+ * We would block on opening a FIFO with no one else on the
+ * other end. Do what we used to do and add O_NONBLOCK to the
+ * open flags. JRA.
+ */
+
+ if (file_existed && S_ISFIFO(psbuf->st_mode)) {
+ local_flags |= O_NONBLOCK;
+ }
+#endif
+
+ /* Don't create files with Microsoft wildcard characters. */
+ if ((local_flags & O_CREAT) && !file_existed &&
+ ms_has_wild(path)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ /* Actually do the open */
+ status = fd_open(conn, path, fsp, local_flags, unx_mode);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("Error opening file %s (%s) (local_flags=%d) "
+ "(flags=%d)\n",
+ path,nt_errstr(status),local_flags,flags));
+ return status;
+ }
+
+ if ((local_flags & O_CREAT) && !file_existed) {
+
+ /* Inherit the ACL if required */
+ if (lp_inherit_perms(SNUM(conn))) {
+ inherit_access_posix_acl(conn, parent_dir, path,
+ unx_mode);
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn))) {
+ change_file_owner_to_parent(conn, parent_dir,
+ fsp);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_FILE_NAME, path);
+ }
+
+ } else {
+ fsp->fh->fd = -1; /* What we used to call a stat open. */
+ }
+
+ if (!file_existed) {
+ int ret;
+
+ if (fsp->fh->fd == -1) {
+ ret = SMB_VFS_STAT(conn, path, psbuf);
+ } else {
+ ret = SMB_VFS_FSTAT(fsp, psbuf);
+ /* If we have an fd, this stat should succeed. */
+ if (ret == -1) {
+ DEBUG(0,("Error doing fstat on open file %s "
+ "(%s)\n", path,strerror(errno) ));
+ }
+ }
+
+ /* For a non-io open, this stat failing means file not found. JRA */
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ /*
+ * POSIX allows read-only opens of directories. We don't
+ * want to do this (we use a different code path for this)
+ * so catch a directory open and return an EISDIR. JRA.
+ */
+
+ if(S_ISDIR(psbuf->st_mode)) {
+ fd_close(fsp);
+ errno = EISDIR;
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ fsp->mode = psbuf->st_mode;
+ fsp->file_id = vfs_file_id_from_sbuf(conn, psbuf);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->can_lock = True;
+ fsp->can_read = (access_mask & (FILE_READ_DATA)) ? True : False;
+ if (!CAN_WRITE(conn)) {
+ fsp->can_write = False;
+ } else {
+ fsp->can_write = (access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) ?
+ True : False;
+ }
+ fsp->print_file = False;
+ fsp->modified = False;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->is_directory = False;
+ if (conn->aio_write_behind_list &&
+ is_in_path(path, conn->aio_write_behind_list, conn->case_sensitive)) {
+ fsp->aio_write_behind = True;
+ }
+
+ string_set(&fsp->fsp_name, path);
+ fsp->wcp = NULL; /* Write cache pointer. */
+
+ DEBUG(2,("%s opened file %s read=%s write=%s (numopen=%d)\n",
+ conn->server_info->unix_name,
+ fsp->fsp_name,
+ BOOLSTR(fsp->can_read), BOOLSTR(fsp->can_write),
+ conn->num_files_open + 1));
+
+ errno = 0;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Return True if the filename is one of the special executable types.
+********************************************************************/
+
+static bool is_executable(const char *fname)
+{
+ if ((fname = strrchr_m(fname,'.'))) {
+ if (strequal(fname,".com") ||
+ strequal(fname,".dll") ||
+ strequal(fname,".exe") ||
+ strequal(fname,".sym")) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Check if we can open a file with a share mode.
+ Returns True if conflict, False if not.
+****************************************************************************/
+
+static bool share_conflict(struct share_mode_entry *entry,
+ uint32 access_mask,
+ uint32 share_access)
+{
+ DEBUG(10,("share_conflict: entry->access_mask = 0x%x, "
+ "entry->share_access = 0x%x, "
+ "entry->private_options = 0x%x\n",
+ (unsigned int)entry->access_mask,
+ (unsigned int)entry->share_access,
+ (unsigned int)entry->private_options));
+
+ DEBUG(10,("share_conflict: access_mask = 0x%x, share_access = 0x%x\n",
+ (unsigned int)access_mask, (unsigned int)share_access));
+
+ if ((entry->access_mask & (FILE_WRITE_DATA|
+ FILE_APPEND_DATA|
+ FILE_READ_DATA|
+ FILE_EXECUTE|
+ DELETE_ACCESS)) == 0) {
+ DEBUG(10,("share_conflict: No conflict due to "
+ "entry->access_mask = 0x%x\n",
+ (unsigned int)entry->access_mask ));
+ return False;
+ }
+
+ if ((access_mask & (FILE_WRITE_DATA|
+ FILE_APPEND_DATA|
+ FILE_READ_DATA|
+ FILE_EXECUTE|
+ DELETE_ACCESS)) == 0) {
+ DEBUG(10,("share_conflict: No conflict due to "
+ "access_mask = 0x%x\n",
+ (unsigned int)access_mask ));
+ return False;
+ }
+
+#if 1 /* JRA TEST - Superdebug. */
+#define CHECK_MASK(num, am, right, sa, share) \
+ DEBUG(10,("share_conflict: [%d] am (0x%x) & right (0x%x) = 0x%x\n", \
+ (unsigned int)(num), (unsigned int)(am), \
+ (unsigned int)(right), (unsigned int)(am)&(right) )); \
+ DEBUG(10,("share_conflict: [%d] sa (0x%x) & share (0x%x) = 0x%x\n", \
+ (unsigned int)(num), (unsigned int)(sa), \
+ (unsigned int)(share), (unsigned int)(sa)&(share) )); \
+ if (((am) & (right)) && !((sa) & (share))) { \
+ DEBUG(10,("share_conflict: check %d conflict am = 0x%x, right = 0x%x, \
+sa = 0x%x, share = 0x%x\n", (num), (unsigned int)(am), (unsigned int)(right), (unsigned int)(sa), \
+ (unsigned int)(share) )); \
+ return True; \
+ }
+#else
+#define CHECK_MASK(num, am, right, sa, share) \
+ if (((am) & (right)) && !((sa) & (share))) { \
+ DEBUG(10,("share_conflict: check %d conflict am = 0x%x, right = 0x%x, \
+sa = 0x%x, share = 0x%x\n", (num), (unsigned int)(am), (unsigned int)(right), (unsigned int)(sa), \
+ (unsigned int)(share) )); \
+ return True; \
+ }
+#endif
+
+ CHECK_MASK(1, entry->access_mask, FILE_WRITE_DATA | FILE_APPEND_DATA,
+ share_access, FILE_SHARE_WRITE);
+ CHECK_MASK(2, access_mask, FILE_WRITE_DATA | FILE_APPEND_DATA,
+ entry->share_access, FILE_SHARE_WRITE);
+
+ CHECK_MASK(3, entry->access_mask, FILE_READ_DATA | FILE_EXECUTE,
+ share_access, FILE_SHARE_READ);
+ CHECK_MASK(4, access_mask, FILE_READ_DATA | FILE_EXECUTE,
+ entry->share_access, FILE_SHARE_READ);
+
+ CHECK_MASK(5, entry->access_mask, DELETE_ACCESS,
+ share_access, FILE_SHARE_DELETE);
+ CHECK_MASK(6, access_mask, DELETE_ACCESS,
+ entry->share_access, FILE_SHARE_DELETE);
+
+ DEBUG(10,("share_conflict: No conflict.\n"));
+ return False;
+}
+
+#if defined(DEVELOPER)
+static void validate_my_share_entries(int num,
+ struct share_mode_entry *share_entry)
+{
+ files_struct *fsp;
+
+ if (!procid_is_me(&share_entry->pid)) {
+ return;
+ }
+
+ if (is_deferred_open_entry(share_entry) &&
+ !open_was_deferred(share_entry->op_mid)) {
+ char *str = talloc_asprintf(talloc_tos(),
+ "Got a deferred entry without a request: "
+ "PANIC: %s\n",
+ share_mode_str(talloc_tos(), num, share_entry));
+ smb_panic(str);
+ }
+
+ if (!is_valid_share_mode_entry(share_entry)) {
+ return;
+ }
+
+ fsp = file_find_dif(share_entry->id,
+ share_entry->share_file_id);
+ if (!fsp) {
+ DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, share_entry) ));
+ smb_panic("validate_my_share_entries: Cannot match a "
+ "share entry with an open file\n");
+ }
+
+ if (is_deferred_open_entry(share_entry) ||
+ is_unused_share_mode_entry(share_entry)) {
+ goto panic;
+ }
+
+ if ((share_entry->op_type == NO_OPLOCK) &&
+ (fsp->oplock_type == FAKE_LEVEL_II_OPLOCK)) {
+ /* Someone has already written to it, but I haven't yet
+ * noticed */
+ return;
+ }
+
+ if (((uint16)fsp->oplock_type) != share_entry->op_type) {
+ goto panic;
+ }
+
+ return;
+
+ panic:
+ {
+ char *str;
+ DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, share_entry) ));
+ str = talloc_asprintf(talloc_tos(),
+ "validate_my_share_entries: "
+ "file %s, oplock_type = 0x%x, op_type = 0x%x\n",
+ fsp->fsp_name, (unsigned int)fsp->oplock_type,
+ (unsigned int)share_entry->op_type );
+ smb_panic(str);
+ }
+}
+#endif
+
+static bool is_stat_open(uint32 access_mask)
+{
+ return (access_mask &&
+ ((access_mask & ~(SYNCHRONIZE_ACCESS| FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES))==0) &&
+ ((access_mask & (SYNCHRONIZE_ACCESS|FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES)) != 0));
+}
+
+/****************************************************************************
+ Deal with share modes
+ Invarient: Share mode must be locked on entry and exit.
+ Returns -1 on error, or number of share modes on success (may be zero).
+****************************************************************************/
+
+static NTSTATUS open_mode_check(connection_struct *conn,
+ const char *fname,
+ struct share_mode_lock *lck,
+ uint32 access_mask,
+ uint32 share_access,
+ uint32 create_options,
+ bool *file_existed)
+{
+ int i;
+
+ if(lck->num_share_modes == 0) {
+ return NT_STATUS_OK;
+ }
+
+ *file_existed = True;
+
+ /* A delete on close prohibits everything */
+
+ if (lck->delete_on_close) {
+ return NT_STATUS_DELETE_PENDING;
+ }
+
+ if (is_stat_open(access_mask)) {
+ /* Stat open that doesn't trigger oplock breaks or share mode
+ * checks... ! JRA. */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Check if the share modes will give us access.
+ */
+
+#if defined(DEVELOPER)
+ for(i = 0; i < lck->num_share_modes; i++) {
+ validate_my_share_entries(i, &lck->share_modes[i]);
+ }
+#endif
+
+ if (!lp_share_modes(SNUM(conn))) {
+ return NT_STATUS_OK;
+ }
+
+ /* Now we check the share modes, after any oplock breaks. */
+ for(i = 0; i < lck->num_share_modes; i++) {
+
+ if (!is_valid_share_mode_entry(&lck->share_modes[i])) {
+ continue;
+ }
+
+ /* someone else has a share lock on it, check to see if we can
+ * too */
+ if (share_conflict(&lck->share_modes[i],
+ access_mask, share_access)) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool is_delete_request(files_struct *fsp) {
+ return ((fsp->access_mask == DELETE_ACCESS) &&
+ (fsp->oplock_type == NO_OPLOCK));
+}
+
+/*
+ * 1) No files open at all or internal open: Grant whatever the client wants.
+ *
+ * 2) Exclusive (or batch) oplock around: If the requested access is a delete
+ * request, break if the oplock around is a batch oplock. If it's another
+ * requested access type, break.
+ *
+ * 3) Only level2 around: Grant level2 and do nothing else.
+ */
+
+static bool delay_for_oplocks(struct share_mode_lock *lck,
+ files_struct *fsp,
+ uint16 mid,
+ int pass_number,
+ int oplock_request)
+{
+ int i;
+ struct share_mode_entry *exclusive = NULL;
+ bool valid_entry = False;
+ bool delay_it = False;
+ bool have_level2 = False;
+ NTSTATUS status;
+ char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+ if (oplock_request & INTERNAL_OPEN_ONLY) {
+ fsp->oplock_type = NO_OPLOCK;
+ }
+
+ if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) {
+ return False;
+ }
+
+ for (i=0; i<lck->num_share_modes; i++) {
+
+ if (!is_valid_share_mode_entry(&lck->share_modes[i])) {
+ continue;
+ }
+
+ /* At least one entry is not an invalid or deferred entry. */
+ valid_entry = True;
+
+ if (pass_number == 1) {
+ if (BATCH_OPLOCK_TYPE(lck->share_modes[i].op_type)) {
+ SMB_ASSERT(exclusive == NULL);
+ exclusive = &lck->share_modes[i];
+ }
+ } else {
+ if (EXCLUSIVE_OPLOCK_TYPE(lck->share_modes[i].op_type)) {
+ SMB_ASSERT(exclusive == NULL);
+ exclusive = &lck->share_modes[i];
+ }
+ }
+
+ if (lck->share_modes[i].op_type == LEVEL_II_OPLOCK) {
+ SMB_ASSERT(exclusive == NULL);
+ have_level2 = True;
+ }
+ }
+
+ if (!valid_entry) {
+ /* All entries are placeholders or deferred.
+ * Directly grant whatever the client wants. */
+ if (fsp->oplock_type == NO_OPLOCK) {
+ /* Store a level2 oplock, but don't tell the client */
+ fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+ }
+ return False;
+ }
+
+ if (exclusive != NULL) { /* Found an exclusive oplock */
+ SMB_ASSERT(!have_level2);
+ delay_it = is_delete_request(fsp) ?
+ BATCH_OPLOCK_TYPE(exclusive->op_type) : True;
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ /* We can at most grant level2 as there are other
+ * level2 or NO_OPLOCK entries. */
+ fsp->oplock_type = LEVEL_II_OPLOCK;
+ }
+
+ if ((fsp->oplock_type == NO_OPLOCK) && have_level2) {
+ /* Store a level2 oplock, but don't tell the client */
+ fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+ }
+
+ if (!delay_it) {
+ return False;
+ }
+
+ /*
+ * Send a break message to the oplock holder and delay the open for
+ * our client.
+ */
+
+ DEBUG(10, ("Sending break request to PID %s\n",
+ procid_str_static(&exclusive->pid)));
+ exclusive->op_mid = mid;
+
+ /* Create the message. */
+ share_mode_entry_to_message(msg, exclusive);
+
+ /* Add in the FORCE_OPLOCK_BREAK_TO_NONE bit in the message if set. We
+ don't want this set in the share mode struct pointed to by lck. */
+
+ if (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE) {
+ SSVAL(msg,6,exclusive->op_type | FORCE_OPLOCK_BREAK_TO_NONE);
+ }
+
+ status = messaging_send_buf(smbd_messaging_context(), exclusive->pid,
+ MSG_SMB_BREAK_REQUEST,
+ (uint8 *)msg,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not send oplock break message: %s\n",
+ nt_errstr(status)));
+ }
+
+ return True;
+}
+
+static bool request_timed_out(struct timeval request_time,
+ struct timeval timeout)
+{
+ struct timeval now, end_time;
+ GetTimeOfDay(&now);
+ end_time = timeval_sum(&request_time, &timeout);
+ return (timeval_compare(&end_time, &now) < 0);
+}
+
+/****************************************************************************
+ Handle the 1 second delay in returning a SHARING_VIOLATION error.
+****************************************************************************/
+
+static void defer_open(struct share_mode_lock *lck,
+ struct timeval request_time,
+ struct timeval timeout,
+ struct smb_request *req,
+ struct deferred_open_record *state)
+{
+ int i;
+
+ /* Paranoia check */
+
+ for (i=0; i<lck->num_share_modes; i++) {
+ struct share_mode_entry *e = &lck->share_modes[i];
+
+ if (!is_deferred_open_entry(e)) {
+ continue;
+ }
+
+ if (procid_is_me(&e->pid) && (e->op_mid == req->mid)) {
+ DEBUG(0, ("Trying to defer an already deferred "
+ "request: mid=%d, exiting\n", req->mid));
+ exit_server("attempt to defer a deferred request");
+ }
+ }
+
+ /* End paranoia check */
+
+ DEBUG(10,("defer_open_sharing_error: time [%u.%06u] adding deferred "
+ "open entry for mid %u\n",
+ (unsigned int)request_time.tv_sec,
+ (unsigned int)request_time.tv_usec,
+ (unsigned int)req->mid));
+
+ if (!push_deferred_smb_message(req, request_time, timeout,
+ (char *)state, sizeof(*state))) {
+ exit_server("push_deferred_smb_message failed");
+ }
+ add_deferred_open(lck, req->mid, request_time, state->id);
+
+ /*
+ * Push the MID of this packet on the signing queue.
+ * We only do this once, the first time we push the packet
+ * onto the deferred open queue, as this has a side effect
+ * of incrementing the response sequence number.
+ */
+
+ srv_defer_sign_response(req->mid);
+}
+
+
+/****************************************************************************
+ On overwrite open ensure that the attributes match.
+****************************************************************************/
+
+static bool open_match_attributes(connection_struct *conn,
+ const char *path,
+ uint32 old_dos_attr,
+ uint32 new_dos_attr,
+ mode_t existing_unx_mode,
+ mode_t new_unx_mode,
+ mode_t *returned_unx_mode)
+{
+ uint32 noarch_old_dos_attr, noarch_new_dos_attr;
+
+ noarch_old_dos_attr = (old_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+ noarch_new_dos_attr = (new_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+
+ if((noarch_old_dos_attr == 0 && noarch_new_dos_attr != 0) ||
+ (noarch_old_dos_attr != 0 && ((noarch_old_dos_attr & noarch_new_dos_attr) == noarch_old_dos_attr))) {
+ *returned_unx_mode = new_unx_mode;
+ } else {
+ *returned_unx_mode = (mode_t)0;
+ }
+
+ DEBUG(10,("open_match_attributes: file %s old_dos_attr = 0x%x, "
+ "existing_unx_mode = 0%o, new_dos_attr = 0x%x "
+ "returned_unx_mode = 0%o\n",
+ path,
+ (unsigned int)old_dos_attr,
+ (unsigned int)existing_unx_mode,
+ (unsigned int)new_dos_attr,
+ (unsigned int)*returned_unx_mode ));
+
+ /* If we're mapping SYSTEM and HIDDEN ensure they match. */
+ if (lp_map_system(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_SYSTEM) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_SYSTEM)) {
+ return False;
+ }
+ }
+ if (lp_map_hidden(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_HIDDEN) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_HIDDEN)) {
+ return False;
+ }
+ }
+ return True;
+}
+
+/****************************************************************************
+ Special FCB or DOS processing in the case of a sharing violation.
+ Try and find a duplicated file handle.
+****************************************************************************/
+
+static files_struct *fcb_or_dos_open(connection_struct *conn,
+ const char *fname,
+ struct file_id id,
+ uint16 file_pid,
+ uint16 vuid,
+ uint32 access_mask,
+ uint32 share_access,
+ uint32 create_options)
+{
+ files_struct *fsp;
+ files_struct *dup_fsp;
+
+ DEBUG(5,("fcb_or_dos_open: attempting old open semantics for "
+ "file %s.\n", fname ));
+
+ for(fsp = file_find_di_first(id); fsp;
+ fsp = file_find_di_next(fsp)) {
+
+ DEBUG(10,("fcb_or_dos_open: checking file %s, fd = %d, "
+ "vuid = %u, file_pid = %u, private_options = 0x%x "
+ "access_mask = 0x%x\n", fsp->fsp_name,
+ fsp->fh->fd, (unsigned int)fsp->vuid,
+ (unsigned int)fsp->file_pid,
+ (unsigned int)fsp->fh->private_options,
+ (unsigned int)fsp->access_mask ));
+
+ if (fsp->fh->fd != -1 &&
+ fsp->vuid == vuid &&
+ fsp->file_pid == file_pid &&
+ (fsp->fh->private_options & (NTCREATEX_OPTIONS_PRIVATE_DENY_DOS |
+ NTCREATEX_OPTIONS_PRIVATE_DENY_FCB)) &&
+ (fsp->access_mask & FILE_WRITE_DATA) &&
+ strequal(fsp->fsp_name, fname)) {
+ DEBUG(10,("fcb_or_dos_open: file match\n"));
+ break;
+ }
+ }
+
+ if (!fsp) {
+ return NULL;
+ }
+
+ /* quite an insane set of semantics ... */
+ if (is_executable(fname) &&
+ (fsp->fh->private_options & NTCREATEX_OPTIONS_PRIVATE_DENY_DOS)) {
+ DEBUG(10,("fcb_or_dos_open: file fail due to is_executable.\n"));
+ return NULL;
+ }
+
+ /* We need to duplicate this fsp. */
+ if (!NT_STATUS_IS_OK(dup_file_fsp(fsp, access_mask, share_access,
+ create_options, &dup_fsp))) {
+ return NULL;
+ }
+
+ return dup_fsp;
+}
+
+/****************************************************************************
+ Open a file with a share mode - old openX method - map into NTCreate.
+****************************************************************************/
+
+bool map_open_params_to_ntcreate(const char *fname, int deny_mode, int open_func,
+ uint32 *paccess_mask,
+ uint32 *pshare_mode,
+ uint32 *pcreate_disposition,
+ uint32 *pcreate_options)
+{
+ uint32 access_mask;
+ uint32 share_mode;
+ uint32 create_disposition;
+ uint32 create_options = 0;
+
+ DEBUG(10,("map_open_params_to_ntcreate: fname = %s, deny_mode = 0x%x, "
+ "open_func = 0x%x\n",
+ fname, (unsigned int)deny_mode, (unsigned int)open_func ));
+
+ /* Create the NT compatible access_mask. */
+ switch (GET_OPENX_MODE(deny_mode)) {
+ case DOS_OPEN_EXEC: /* Implies read-only - used to be FILE_READ_DATA */
+ case DOS_OPEN_RDONLY:
+ access_mask = FILE_GENERIC_READ;
+ break;
+ case DOS_OPEN_WRONLY:
+ access_mask = FILE_GENERIC_WRITE;
+ break;
+ case DOS_OPEN_RDWR:
+ case DOS_OPEN_FCB:
+ access_mask = FILE_GENERIC_READ|FILE_GENERIC_WRITE;
+ break;
+ default:
+ DEBUG(10,("map_open_params_to_ntcreate: bad open mode = 0x%x\n",
+ (unsigned int)GET_OPENX_MODE(deny_mode)));
+ return False;
+ }
+
+ /* Create the NT compatible create_disposition. */
+ switch (open_func) {
+ case OPENX_FILE_EXISTS_FAIL|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_CREATE;
+ break;
+
+ case OPENX_FILE_EXISTS_OPEN:
+ create_disposition = FILE_OPEN;
+ break;
+
+ case OPENX_FILE_EXISTS_OPEN|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_OPEN_IF;
+ break;
+
+ case OPENX_FILE_EXISTS_TRUNCATE:
+ create_disposition = FILE_OVERWRITE;
+ break;
+
+ case OPENX_FILE_EXISTS_TRUNCATE|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_OVERWRITE_IF;
+ break;
+
+ default:
+ /* From samba4 - to be confirmed. */
+ if (GET_OPENX_MODE(deny_mode) == DOS_OPEN_EXEC) {
+ create_disposition = FILE_CREATE;
+ break;
+ }
+ DEBUG(10,("map_open_params_to_ntcreate: bad "
+ "open_func 0x%x\n", (unsigned int)open_func));
+ return False;
+ }
+
+ /* Create the NT compatible share modes. */
+ switch (GET_DENY_MODE(deny_mode)) {
+ case DENY_ALL:
+ share_mode = FILE_SHARE_NONE;
+ break;
+
+ case DENY_WRITE:
+ share_mode = FILE_SHARE_READ;
+ break;
+
+ case DENY_READ:
+ share_mode = FILE_SHARE_WRITE;
+ break;
+
+ case DENY_NONE:
+ share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ break;
+
+ case DENY_DOS:
+ create_options |= NTCREATEX_OPTIONS_PRIVATE_DENY_DOS;
+ if (is_executable(fname)) {
+ share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ } else {
+ if (GET_OPENX_MODE(deny_mode) == DOS_OPEN_RDONLY) {
+ share_mode = FILE_SHARE_READ;
+ } else {
+ share_mode = FILE_SHARE_NONE;
+ }
+ }
+ break;
+
+ case DENY_FCB:
+ create_options |= NTCREATEX_OPTIONS_PRIVATE_DENY_FCB;
+ share_mode = FILE_SHARE_NONE;
+ break;
+
+ default:
+ DEBUG(10,("map_open_params_to_ntcreate: bad deny_mode 0x%x\n",
+ (unsigned int)GET_DENY_MODE(deny_mode) ));
+ return False;
+ }
+
+ DEBUG(10,("map_open_params_to_ntcreate: file %s, access_mask = 0x%x, "
+ "share_mode = 0x%x, create_disposition = 0x%x, "
+ "create_options = 0x%x\n",
+ fname,
+ (unsigned int)access_mask,
+ (unsigned int)share_mode,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options ));
+
+ if (paccess_mask) {
+ *paccess_mask = access_mask;
+ }
+ if (pshare_mode) {
+ *pshare_mode = share_mode;
+ }
+ if (pcreate_disposition) {
+ *pcreate_disposition = create_disposition;
+ }
+ if (pcreate_options) {
+ *pcreate_options = create_options;
+ }
+
+ return True;
+
+}
+
+static void schedule_defer_open(struct share_mode_lock *lck,
+ struct timeval request_time,
+ struct smb_request *req)
+{
+ struct deferred_open_record state;
+
+ /* This is a relative time, added to the absolute
+ request_time value to get the absolute timeout time.
+ Note that if this is the second or greater time we enter
+ this codepath for this particular request mid then
+ request_time is left as the absolute time of the *first*
+ time this request mid was processed. This is what allows
+ the request to eventually time out. */
+
+ struct timeval timeout;
+
+ /* Normally the smbd we asked should respond within
+ * OPLOCK_BREAK_TIMEOUT seconds regardless of whether
+ * the client did, give twice the timeout as a safety
+ * measure here in case the other smbd is stuck
+ * somewhere else. */
+
+ timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+
+ /* Nothing actually uses state.delayed_for_oplocks
+ but it's handy to differentiate in debug messages
+ between a 30 second delay due to oplock break, and
+ a 1 second delay for share mode conflicts. */
+
+ state.delayed_for_oplocks = True;
+ state.id = lck->id;
+
+ if (!request_timed_out(request_time, timeout)) {
+ defer_open(lck, request_time, timeout, req, &state);
+ }
+}
+
+/****************************************************************************
+ Open a file with a share mode.
+****************************************************************************/
+
+NTSTATUS open_file_ntcreate(connection_struct *conn,
+ struct smb_request *req,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ uint32 access_mask, /* access bits (FILE_READ_DATA etc.) */
+ uint32 share_access, /* share constants (FILE_SHARE_READ etc) */
+ uint32 create_disposition, /* FILE_OPEN_IF etc. */
+ uint32 create_options, /* options such as delete on close. */
+ uint32 new_dos_attributes, /* attributes used for new file. */
+ int oplock_request, /* internal Samba oplock codes. */
+ /* Information (FILE_EXISTS etc.) */
+ int *pinfo,
+ files_struct **result)
+{
+ int flags=0;
+ int flags2=0;
+ bool file_existed = VALID_STAT(*psbuf);
+ bool def_acl = False;
+ bool posix_open = False;
+ bool new_file_created = False;
+ struct file_id id;
+ NTSTATUS fsp_open = NT_STATUS_ACCESS_DENIED;
+ files_struct *fsp = NULL;
+ mode_t new_unx_mode = (mode_t)0;
+ mode_t unx_mode = (mode_t)0;
+ int info;
+ uint32 existing_dos_attributes = 0;
+ struct pending_message_list *pml = NULL;
+ struct timeval request_time = timeval_zero();
+ struct share_mode_lock *lck = NULL;
+ uint32 open_access_mask = access_mask;
+ NTSTATUS status;
+ int ret_flock;
+ char *parent_dir;
+ const char *newname;
+
+ ZERO_STRUCT(id);
+
+ if (conn->printer) {
+ /*
+ * Printers are handled completely differently.
+ * Most of the passed parameters are ignored.
+ */
+
+ if (pinfo) {
+ *pinfo = FILE_WAS_CREATED;
+ }
+
+ DEBUG(10, ("open_file_ntcreate: printer open fname=%s\n", fname));
+
+ return print_fsp_open(conn, fname, req->vuid, result);
+ }
+
+ if (!parent_dirname_talloc(talloc_tos(), fname, &parent_dir,
+ &newname)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (new_dos_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = True;
+ unx_mode = (mode_t)(new_dos_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ new_dos_attributes = 0;
+ } else {
+ /* We add aARCH to this as this mode is only used if the file is
+ * created new. */
+ unx_mode = unix_mode(conn, new_dos_attributes | aARCH, fname,
+ parent_dir);
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, dos_attrs=0x%x "
+ "access_mask=0x%x share_access=0x%x "
+ "create_disposition = 0x%x create_options=0x%x "
+ "unix mode=0%o oplock_request=%d\n",
+ fname, new_dos_attributes, access_mask, share_access,
+ create_disposition, create_options, unx_mode,
+ oplock_request));
+
+ if ((req == NULL) && ((oplock_request & INTERNAL_OPEN_ONLY) == 0)) {
+ DEBUG(0, ("No smb request but not an internal only open!\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Only non-internal opens can be deferred at all
+ */
+
+ if ((req != NULL)
+ && ((pml = get_open_deferred_message(req->mid)) != NULL)) {
+ struct deferred_open_record *state =
+ (struct deferred_open_record *)pml->private_data.data;
+
+ /* Remember the absolute time of the original
+ request with this mid. We'll use it later to
+ see if this has timed out. */
+
+ request_time = pml->request_time;
+
+ /* Remove the deferred open entry under lock. */
+ lck = get_share_mode_lock(talloc_tos(), state->id, NULL, NULL,
+ NULL);
+ if (lck == NULL) {
+ DEBUG(0, ("could not get share mode lock\n"));
+ } else {
+ del_deferred_open_entry(lck, req->mid);
+ TALLOC_FREE(lck);
+ }
+
+ /* Ensure we don't reprocess this message. */
+ remove_deferred_open_smb_message(req->mid);
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!posix_open) {
+ new_dos_attributes &= SAMBA_ATTRIBUTES_MASK;
+ if (file_existed) {
+ existing_dos_attributes = dos_mode(conn, fname, psbuf);
+ }
+ }
+
+ /* ignore any oplock requests if oplocks are disabled */
+ if (!lp_oplocks(SNUM(conn)) || global_client_failed_oplock_break ||
+ IS_VETO_OPLOCK_PATH(conn, fname)) {
+ /* Mask off everything except the private Samba bits. */
+ oplock_request &= SAMBA_PRIVATE_OPLOCK_MASK;
+ }
+
+ /* this is for OS/2 long file names - say we don't support them */
+ if (!lp_posix_pathnames() && strstr(fname,".+,;=[].")) {
+ /* OS/2 Workplace shell fix may be main code stream in a later
+ * release. */
+ DEBUG(5,("open_file_ntcreate: OS/2 long filenames are not "
+ "supported.\n"));
+ if (use_nt_status()) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_DOS(ERRDOS, ERRcannotopen);
+ }
+
+ switch( create_disposition ) {
+ /*
+ * Currently we're using FILE_SUPERSEDE as the same as
+ * FILE_OVERWRITE_IF but they really are
+ * different. FILE_SUPERSEDE deletes an existing file
+ * (requiring delete access) then recreates it.
+ */
+ case FILE_SUPERSEDE:
+ /* If file exists replace/overwrite. If file doesn't
+ * exist create. */
+ flags2 |= (O_CREAT | O_TRUNC);
+ break;
+
+ case FILE_OVERWRITE_IF:
+ /* If file exists replace/overwrite. If file doesn't
+ * exist create. */
+ flags2 |= (O_CREAT | O_TRUNC);
+ break;
+
+ case FILE_OPEN:
+ /* If file exists open. If file doesn't exist error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OPEN "
+ "requested for file %s and file "
+ "doesn't exist.\n", fname ));
+ errno = ENOENT;
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ break;
+
+ case FILE_OVERWRITE:
+ /* If file exists overwrite. If file doesn't exist
+ * error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OVERWRITE "
+ "requested for file %s and file "
+ "doesn't exist.\n", fname ));
+ errno = ENOENT;
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ flags2 |= O_TRUNC;
+ break;
+
+ case FILE_CREATE:
+ /* If file exists error. If file doesn't exist
+ * create. */
+ if (file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_CREATE "
+ "requested for file %s and file "
+ "already exists.\n", fname ));
+ if (S_ISDIR(psbuf->st_mode)) {
+ errno = EISDIR;
+ } else {
+ errno = EEXIST;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+ flags2 |= (O_CREAT|O_EXCL);
+ break;
+
+ case FILE_OPEN_IF:
+ /* If file exists open. If file doesn't exist
+ * create. */
+ flags2 |= O_CREAT;
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* We only care about matching attributes on file exists and
+ * overwrite. */
+
+ if (!posix_open && file_existed && ((create_disposition == FILE_OVERWRITE) ||
+ (create_disposition == FILE_OVERWRITE_IF))) {
+ if (!open_match_attributes(conn, fname,
+ existing_dos_attributes,
+ new_dos_attributes, psbuf->st_mode,
+ unx_mode, &new_unx_mode)) {
+ DEBUG(5,("open_file_ntcreate: attributes missmatch "
+ "for file %s (%x %x) (0%o, 0%o)\n",
+ fname, existing_dos_attributes,
+ new_dos_attributes,
+ (unsigned int)psbuf->st_mode,
+ (unsigned int)unx_mode ));
+ errno = EACCES;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ /* This is a nasty hack - must fix... JRA. */
+ if (access_mask == MAXIMUM_ALLOWED_ACCESS) {
+ open_access_mask = access_mask = FILE_GENERIC_ALL;
+ }
+
+ /*
+ * Convert GENERIC bits to specific bits.
+ */
+
+ se_map_generic(&access_mask, &file_generic_mapping);
+ open_access_mask = access_mask;
+
+ if ((flags2 & O_TRUNC) || (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE)) {
+ open_access_mask |= FILE_WRITE_DATA; /* This will cause oplock breaks. */
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, after mapping "
+ "access_mask=0x%x\n", fname, access_mask ));
+
+ /*
+ * Note that we ignore the append flag as append does not
+ * mean the same thing under DOS and Unix.
+ */
+
+ if ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) ||
+ (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE)) {
+ /* DENY_DOS opens are always underlying read-write on the
+ file handle, no matter what the requested access mask
+ says. */
+ if ((create_options & NTCREATEX_OPTIONS_PRIVATE_DENY_DOS) ||
+ access_mask & (FILE_READ_ATTRIBUTES|FILE_READ_DATA|FILE_READ_EA|FILE_EXECUTE)) {
+ flags = O_RDWR;
+ } else {
+ flags = O_WRONLY;
+ }
+ } else {
+ flags = O_RDONLY;
+ }
+
+ /*
+ * Currently we only look at FILE_WRITE_THROUGH for create options.
+ */
+
+#if defined(O_SYNC)
+ if ((create_options & FILE_WRITE_THROUGH) && lp_strict_sync(SNUM(conn))) {
+ flags2 |= O_SYNC;
+ }
+#endif /* O_SYNC */
+
+ if (posix_open && (access_mask & FILE_APPEND_DATA)) {
+ flags2 |= O_APPEND;
+ }
+
+ if (!posix_open && !CAN_WRITE(conn)) {
+ /*
+ * We should really return a permission denied error if either
+ * O_CREAT or O_TRUNC are set, but for compatibility with
+ * older versions of Samba we just AND them out.
+ */
+ flags2 &= ~(O_CREAT|O_TRUNC);
+ }
+
+ /*
+ * Ensure we can't write on a read-only share or file.
+ */
+
+ if (flags != O_RDONLY && file_existed &&
+ (!CAN_WRITE(conn) || IS_DOS_READONLY(existing_dos_attributes))) {
+ DEBUG(5,("open_file_ntcreate: write access requested for "
+ "file %s on read only %s\n",
+ fname, !CAN_WRITE(conn) ? "share" : "file" ));
+ errno = EACCES;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = file_new(conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, psbuf);
+ fsp->share_access = share_access;
+ fsp->fh->private_options = create_options;
+ fsp->access_mask = open_access_mask; /* We change this to the
+ * requested access_mask after
+ * the open is done. */
+ fsp->posix_open = posix_open;
+
+ /* Ensure no SAMBA_PRIVATE bits can be set. */
+ fsp->oplock_type = (oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
+
+ if (timeval_is_zero(&request_time)) {
+ request_time = fsp->open_time;
+ }
+
+ if (file_existed) {
+ struct timespec old_write_time = get_mtimespec(psbuf);
+ id = vfs_file_id_from_sbuf(conn, psbuf);
+
+ lck = get_share_mode_lock(talloc_tos(), id,
+ conn->connectpath,
+ fname, &old_write_time);
+
+ if (lck == NULL) {
+ file_free(fsp);
+ DEBUG(0, ("Could not get share mode lock\n"));
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /* First pass - send break only on batch oplocks. */
+ if ((req != NULL)
+ && delay_for_oplocks(lck, fsp, req->mid, 1,
+ oplock_request)) {
+ schedule_defer_open(lck, request_time, req);
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /* Use the client requested access mask here, not the one we
+ * open with. */
+ status = open_mode_check(conn, fname, lck,
+ access_mask, share_access,
+ create_options, &file_existed);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* We might be going to allow this open. Check oplock
+ * status again. */
+ /* Second pass - send break for both batch or
+ * exclusive oplocks. */
+ if ((req != NULL)
+ && delay_for_oplocks(lck, fsp, req->mid, 2,
+ oplock_request)) {
+ schedule_defer_open(lck, request_time, req);
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) {
+ /* DELETE_PENDING is not deferred for a second */
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ uint32 can_access_mask;
+ bool can_access = True;
+
+ SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION));
+
+ /* Check if this can be done with the deny_dos and fcb
+ * calls. */
+ if (create_options &
+ (NTCREATEX_OPTIONS_PRIVATE_DENY_DOS|
+ NTCREATEX_OPTIONS_PRIVATE_DENY_FCB)) {
+ files_struct *fsp_dup;
+
+ if (req == NULL) {
+ DEBUG(0, ("DOS open without an SMB "
+ "request!\n"));
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Use the client requested access mask here,
+ * not the one we open with. */
+ fsp_dup = fcb_or_dos_open(conn, fname, id,
+ req->smbpid,
+ req->vuid,
+ access_mask,
+ share_access,
+ create_options);
+
+ if (fsp_dup) {
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ if (pinfo) {
+ *pinfo = FILE_WAS_OPENED;
+ }
+ conn->num_files_open++;
+ *result = fsp_dup;
+ return NT_STATUS_OK;
+ }
+ }
+
+ /*
+ * This next line is a subtlety we need for
+ * MS-Access. If a file open will fail due to share
+ * permissions and also for security (access) reasons,
+ * we need to return the access failed error, not the
+ * share error. We can't open the file due to kernel
+ * oplock deadlock (it's possible we failed above on
+ * the open_mode_check()) so use a userspace check.
+ */
+
+ if (flags & O_RDWR) {
+ can_access_mask = FILE_READ_DATA|FILE_WRITE_DATA;
+ } else if (flags & O_WRONLY) {
+ can_access_mask = FILE_WRITE_DATA;
+ } else {
+ can_access_mask = FILE_READ_DATA;
+ }
+
+ if (((can_access_mask & FILE_WRITE_DATA) && !CAN_WRITE(conn)) ||
+ !can_access_file_data(conn,fname,psbuf,can_access_mask)) {
+ can_access = False;
+ }
+
+ /*
+ * If we're returning a share violation, ensure we
+ * cope with the braindead 1 second delay.
+ */
+
+ if (!(oplock_request & INTERNAL_OPEN_ONLY) &&
+ lp_defer_sharing_violations()) {
+ struct timeval timeout;
+ struct deferred_open_record state;
+ int timeout_usecs;
+
+ /* this is a hack to speed up torture tests
+ in 'make test' */
+ timeout_usecs = lp_parm_int(SNUM(conn),
+ "smbd","sharedelay",
+ SHARING_VIOLATION_USEC_WAIT);
+
+ /* This is a relative time, added to the absolute
+ request_time value to get the absolute timeout time.
+ Note that if this is the second or greater time we enter
+ this codepath for this particular request mid then
+ request_time is left as the absolute time of the *first*
+ time this request mid was processed. This is what allows
+ the request to eventually time out. */
+
+ timeout = timeval_set(0, timeout_usecs);
+
+ /* Nothing actually uses state.delayed_for_oplocks
+ but it's handy to differentiate in debug messages
+ between a 30 second delay due to oplock break, and
+ a 1 second delay for share mode conflicts. */
+
+ state.delayed_for_oplocks = False;
+ state.id = id;
+
+ if ((req != NULL)
+ && !request_timed_out(request_time,
+ timeout)) {
+ defer_open(lck, request_time, timeout,
+ req, &state);
+ }
+ }
+
+ TALLOC_FREE(lck);
+ if (can_access) {
+ /*
+ * We have detected a sharing violation here
+ * so return the correct error code
+ */
+ status = NT_STATUS_SHARING_VIOLATION;
+ } else {
+ status = NT_STATUS_ACCESS_DENIED;
+ }
+ file_free(fsp);
+ return status;
+ }
+
+ /*
+ * We exit this block with the share entry *locked*.....
+ */
+ }
+
+ SMB_ASSERT(!file_existed || (lck != NULL));
+
+ /*
+ * Ensure we pay attention to default ACLs on directories if required.
+ */
+
+ if ((flags2 & O_CREAT) && lp_inherit_acls(SNUM(conn)) &&
+ (def_acl = directory_has_default_acl(conn, parent_dir))) {
+ unx_mode = 0777;
+ }
+
+ DEBUG(4,("calling open_file with flags=0x%X flags2=0x%X mode=0%o, "
+ "access_mask = 0x%x, open_access_mask = 0x%x\n",
+ (unsigned int)flags, (unsigned int)flags2,
+ (unsigned int)unx_mode, (unsigned int)access_mask,
+ (unsigned int)open_access_mask));
+
+ /*
+ * open_file strips any O_TRUNC flags itself.
+ */
+
+ fsp_open = open_file(fsp, conn, req, parent_dir, newname, fname, psbuf,
+ flags|flags2, unx_mode, access_mask,
+ open_access_mask);
+
+ if (!NT_STATUS_IS_OK(fsp_open)) {
+ if (lck != NULL) {
+ TALLOC_FREE(lck);
+ }
+ file_free(fsp);
+ return fsp_open;
+ }
+
+ if (!file_existed) {
+ struct timespec old_write_time = get_mtimespec(psbuf);
+ /*
+ * Deal with the race condition where two smbd's detect the
+ * file doesn't exist and do the create at the same time. One
+ * of them will win and set a share mode, the other (ie. this
+ * one) should check if the requested share mode for this
+ * create is allowed.
+ */
+
+ /*
+ * Now the file exists and fsp is successfully opened,
+ * fsp->dev and fsp->inode are valid and should replace the
+ * dev=0,inode=0 from a non existent file. Spotted by
+ * Nadav Danieli <nadavd@exanet.com>. JRA.
+ */
+
+ id = fsp->file_id;
+
+ lck = get_share_mode_lock(talloc_tos(), id,
+ conn->connectpath,
+ fname, &old_write_time);
+
+ if (lck == NULL) {
+ DEBUG(0, ("open_file_ntcreate: Could not get share "
+ "mode lock for %s\n", fname));
+ fd_close(fsp);
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /* First pass - send break only on batch oplocks. */
+ if ((req != NULL)
+ && delay_for_oplocks(lck, fsp, req->mid, 1,
+ oplock_request)) {
+ schedule_defer_open(lck, request_time, req);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ status = open_mode_check(conn, fname, lck,
+ access_mask, share_access,
+ create_options, &file_existed);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* We might be going to allow this open. Check oplock
+ * status again. */
+ /* Second pass - send break for both batch or
+ * exclusive oplocks. */
+ if ((req != NULL)
+ && delay_for_oplocks(lck, fsp, req->mid, 2,
+ oplock_request)) {
+ schedule_defer_open(lck, request_time, req);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct deferred_open_record state;
+
+ fd_close(fsp);
+ file_free(fsp);
+
+ state.delayed_for_oplocks = False;
+ state.id = id;
+
+ /* Do it all over again immediately. In the second
+ * round we will find that the file existed and handle
+ * the DELETE_PENDING and FCB cases correctly. No need
+ * to duplicate the code here. Essentially this is a
+ * "goto top of this function", but don't tell
+ * anybody... */
+
+ if (req != NULL) {
+ defer_open(lck, request_time, timeval_zero(),
+ req, &state);
+ }
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ /*
+ * We exit this block with the share entry *locked*.....
+ */
+
+ }
+
+ SMB_ASSERT(lck != NULL);
+
+ /* note that we ignore failure for the following. It is
+ basically a hack for NFS, and NFS will never set one of
+ these only read them. Nobody but Samba can ever set a deny
+ mode and we have already checked our more authoritative
+ locking database for permission to set this deny mode. If
+ the kernel refuses the operations then the kernel is wrong.
+ note that GPFS supports it as well - jmcd */
+
+ if (fsp->fh->fd != -1) {
+ ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, share_access);
+ if(ret_flock == -1 ){
+
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ file_free(fsp);
+
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ /*
+ * At this point onwards, we can guarentee that the share entry
+ * is locked, whether we created the file or not, and that the
+ * deny mode is compatible with all current opens.
+ */
+
+ /*
+ * If requested, truncate the file.
+ */
+
+ if (flags2&O_TRUNC) {
+ /*
+ * We are modifing the file after open - update the stat
+ * struct..
+ */
+ if ((SMB_VFS_FTRUNCATE(fsp, 0) == -1) ||
+ (SMB_VFS_FSTAT(fsp, psbuf)==-1)) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ file_free(fsp);
+ return status;
+ }
+ }
+
+ /* Record the options we were opened with. */
+ fsp->share_access = share_access;
+ fsp->fh->private_options = create_options;
+ fsp->access_mask = access_mask;
+
+ if (file_existed) {
+ /* stat opens on existing files don't get oplocks. */
+ if (is_stat_open(open_access_mask)) {
+ fsp->oplock_type = NO_OPLOCK;
+ }
+
+ if (!(flags2 & O_TRUNC)) {
+ info = FILE_WAS_OPENED;
+ } else {
+ info = FILE_WAS_OVERWRITTEN;
+ }
+ } else {
+ info = FILE_WAS_CREATED;
+ }
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ /*
+ * Setup the oplock info in both the shared memory and
+ * file structs.
+ */
+
+ if ((fsp->oplock_type != NO_OPLOCK) &&
+ (fsp->oplock_type != FAKE_LEVEL_II_OPLOCK)) {
+ if (!set_file_oplock(fsp, fsp->oplock_type)) {
+ /* Could not get the kernel oplock */
+ fsp->oplock_type = NO_OPLOCK;
+ }
+ }
+
+ if (info == FILE_WAS_OVERWRITTEN || info == FILE_WAS_CREATED || info == FILE_WAS_SUPERSEDED) {
+ new_file_created = True;
+ }
+
+ set_share_mode(lck, fsp, conn->server_info->utok.uid, 0,
+ fsp->oplock_type, new_file_created);
+
+ /* Handle strange delete on close create semantics. */
+ if ((create_options & FILE_DELETE_ON_CLOSE)
+ && (is_ntfs_stream_name(fname)
+ || can_set_initial_delete_on_close(lck))) {
+ status = can_set_delete_on_close(fsp, True, new_dos_attributes);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Remember to delete the mode we just added. */
+ del_share_mode(lck, fsp);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ file_free(fsp);
+ return status;
+ }
+ /* Note that here we set the *inital* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->initial_delete_on_close = True;
+ }
+
+ if (new_file_created) {
+ /* Files should be initially set as archive */
+ if (lp_map_archive(SNUM(conn)) ||
+ lp_store_dos_attributes(SNUM(conn))) {
+ if (!posix_open) {
+ SMB_STRUCT_STAT tmp_sbuf;
+ SET_STAT_INVALID(tmp_sbuf);
+ if (file_set_dosmode(
+ conn, fname,
+ new_dos_attributes | aARCH,
+ &tmp_sbuf, parent_dir,
+ true) == 0) {
+ unx_mode = tmp_sbuf.st_mode;
+ }
+ }
+ }
+ }
+
+ /*
+ * Take care of inherited ACLs on created files - if default ACL not
+ * selected.
+ */
+
+ if (!posix_open && !file_existed && !def_acl) {
+
+ int saved_errno = errno; /* We might get ENOSYS in the next
+ * call.. */
+
+ if (SMB_VFS_FCHMOD_ACL(fsp, unx_mode) == -1 &&
+ errno == ENOSYS) {
+ errno = saved_errno; /* Ignore ENOSYS */
+ }
+
+ } else if (new_unx_mode) {
+
+ int ret = -1;
+
+ /* Attributes need changing. File already existed. */
+
+ {
+ int saved_errno = errno; /* We might get ENOSYS in the
+ * next call.. */
+ ret = SMB_VFS_FCHMOD_ACL(fsp, new_unx_mode);
+
+ if (ret == -1 && errno == ENOSYS) {
+ errno = saved_errno; /* Ignore ENOSYS */
+ } else {
+ DEBUG(5, ("open_file_ntcreate: reset "
+ "attributes of file %s to 0%o\n",
+ fname, (unsigned int)new_unx_mode));
+ ret = 0; /* Don't do the fchmod below. */
+ }
+ }
+
+ if ((ret == -1) &&
+ (SMB_VFS_FCHMOD(fsp, new_unx_mode) == -1))
+ DEBUG(5, ("open_file_ntcreate: failed to reset "
+ "attributes of file %s to 0%o\n",
+ fname, (unsigned int)new_unx_mode));
+ }
+
+ /* If this is a successful open, we must remove any deferred open
+ * records. */
+ if (req != NULL) {
+ del_deferred_open_entry(lck, req->mid);
+ }
+ TALLOC_FREE(lck);
+
+ conn->num_files_open++;
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a file for for write to ensure that we can fchmod it.
+****************************************************************************/
+
+NTSTATUS open_file_fchmod(connection_struct *conn, const char *fname,
+ SMB_STRUCT_STAT *psbuf, files_struct **result)
+{
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = file_new(conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* note! we must use a non-zero desired access or we don't get
+ a real file descriptor. Oh what a twisted web we weave. */
+ status = open_file(fsp, conn, NULL, NULL, NULL, fname, psbuf, O_WRONLY,
+ 0, FILE_WRITE_DATA, FILE_WRITE_DATA);
+
+ /*
+ * This is not a user visible file open.
+ * Don't set a share mode and don't increment
+ * the conn->num_files_open.
+ */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(fsp);
+ return status;
+ }
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Close the fchmod file fd - ensure no locks are lost.
+****************************************************************************/
+
+NTSTATUS close_file_fchmod(files_struct *fsp)
+{
+ NTSTATUS status = fd_close(fsp);
+ file_free(fsp);
+ return status;
+}
+
+static NTSTATUS mkdir_internal(connection_struct *conn,
+ const char *name,
+ uint32 file_attributes,
+ SMB_STRUCT_STAT *psbuf)
+{
+ mode_t mode;
+ char *parent_dir;
+ const char *dirname;
+ NTSTATUS status;
+ bool posix_open = false;
+
+ if(!CAN_WRITE(conn)) {
+ DEBUG(5,("mkdir_internal: failing create on read-only share "
+ "%s\n", lp_servicename(SNUM(conn))));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = check_name(conn, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!parent_dirname_talloc(talloc_tos(), name, &parent_dir,
+ &dirname)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = true;
+ mode = (mode_t)(file_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ } else {
+ mode = unix_mode(conn, aDIR, name, parent_dir);
+ }
+
+ if (SMB_VFS_MKDIR(conn, name, mode) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Ensure we're checking for a symlink here.... */
+ /* We don't want to get caught by a symlink racer. */
+
+ if (SMB_VFS_LSTAT(conn, name, psbuf) == -1) {
+ DEBUG(2, ("Could not stat directory '%s' just created: %s\n",
+ name, strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (!S_ISDIR(psbuf->st_mode)) {
+ DEBUG(0, ("Directory just '%s' created is not a directory\n",
+ name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (lp_store_dos_attributes(SNUM(conn))) {
+ if (!posix_open) {
+ file_set_dosmode(conn, name,
+ file_attributes | aDIR, NULL,
+ parent_dir,
+ true);
+ }
+ }
+
+ if (lp_inherit_perms(SNUM(conn))) {
+ inherit_access_posix_acl(conn, parent_dir, name, mode);
+ }
+
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS)) {
+ /*
+ * Check if high bits should have been set,
+ * then (if bits are missing): add them.
+ * Consider bits automagically set by UNIX, i.e. SGID bit from parent
+ * dir.
+ */
+ if (mode & ~(S_IRWXU|S_IRWXG|S_IRWXO) && (mode & ~psbuf->st_mode)) {
+ SMB_VFS_CHMOD(conn, name,
+ psbuf->st_mode | (mode & ~psbuf->st_mode));
+ }
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn))) {
+ change_dir_owner_to_parent(conn, parent_dir, name, psbuf);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED, FILE_NOTIFY_CHANGE_DIR_NAME,
+ name);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a directory from an NT SMB call.
+****************************************************************************/
+
+NTSTATUS open_directory(connection_struct *conn,
+ struct smb_request *req,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ uint32 access_mask,
+ uint32 share_access,
+ uint32 create_disposition,
+ uint32 create_options,
+ uint32 file_attributes,
+ int *pinfo,
+ files_struct **result)
+{
+ files_struct *fsp = NULL;
+ bool dir_existed = VALID_STAT(*psbuf) ? True : False;
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+ struct timespec mtimespec;
+ int info = 0;
+
+ DEBUG(5,("open_directory: opening directory %s, access_mask = 0x%x, "
+ "share_access = 0x%x create_options = 0x%x, "
+ "create_disposition = 0x%x, file_attributes = 0x%x\n",
+ fname,
+ (unsigned int)access_mask,
+ (unsigned int)share_access,
+ (unsigned int)create_options,
+ (unsigned int)create_disposition,
+ (unsigned int)file_attributes));
+
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS) && is_ntfs_stream_name(fname)) {
+ DEBUG(2, ("open_directory: %s is a stream name!\n", fname));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ switch( create_disposition ) {
+ case FILE_OPEN:
+
+ info = FILE_WAS_OPENED;
+
+ /*
+ * We want to follow symlinks here.
+ */
+
+ if (SMB_VFS_STAT(conn, fname, psbuf) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ break;
+
+ case FILE_CREATE:
+
+ /* If directory exists error. If directory doesn't
+ * exist create. */
+
+ status = mkdir_internal(conn,
+ fname,
+ file_attributes,
+ psbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n", fname,
+ nt_errstr(status)));
+ return status;
+ }
+
+ info = FILE_WAS_CREATED;
+ break;
+
+ case FILE_OPEN_IF:
+ /*
+ * If directory exists open. If directory doesn't
+ * exist create.
+ */
+
+ status = mkdir_internal(conn,
+ fname,
+ file_attributes,
+ psbuf);
+
+ if (NT_STATUS_IS_OK(status)) {
+ info = FILE_WAS_CREATED;
+ }
+
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ info = FILE_WAS_OPENED;
+ status = NT_STATUS_OK;
+ }
+
+ break;
+
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ default:
+ DEBUG(5,("open_directory: invalid create_disposition "
+ "0x%x for directory %s\n",
+ (unsigned int)create_disposition, fname));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(!S_ISDIR(psbuf->st_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ fname ));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ status = file_new(conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Setup the files_struct for it.
+ */
+
+ fsp->mode = psbuf->st_mode;
+ fsp->file_id = vfs_file_id_from_sbuf(conn, psbuf);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->can_lock = False;
+ fsp->can_read = False;
+ fsp->can_write = False;
+
+ fsp->share_access = share_access;
+ fsp->fh->private_options = create_options;
+ fsp->access_mask = access_mask;
+
+ fsp->print_file = False;
+ fsp->modified = False;
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->is_directory = True;
+ fsp->posix_open = (file_attributes & FILE_FLAG_POSIX_SEMANTICS) ? True : False;
+
+ string_set(&fsp->fsp_name,fname);
+
+ mtimespec = get_mtimespec(psbuf);
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id,
+ conn->connectpath,
+ fname, &mtimespec);
+
+ if (lck == NULL) {
+ DEBUG(0, ("open_directory: Could not get share mode lock for %s\n", fname));
+ file_free(fsp);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ status = open_mode_check(conn, fname, lck,
+ access_mask, share_access,
+ create_options, &dir_existed);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return status;
+ }
+
+ set_share_mode(lck, fsp, conn->server_info->utok.uid, 0, NO_OPLOCK,
+ True);
+
+ /* For directories the delete on close bit at open time seems
+ always to be honored on close... See test 19 in Samba4 BASE-DELETE. */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, True, 0);
+ if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, NT_STATUS_DIRECTORY_NOT_EMPTY)) {
+ TALLOC_FREE(lck);
+ file_free(fsp);
+ return status;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *inital* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->initial_delete_on_close = True;
+ }
+ }
+
+ TALLOC_FREE(lck);
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ conn->num_files_open++;
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS create_directory(connection_struct *conn, struct smb_request *req, const char *directory)
+{
+ NTSTATUS status;
+ SMB_STRUCT_STAT sbuf;
+ files_struct *fsp;
+
+ SET_STAT_INVALID(sbuf);
+
+ status = open_directory(conn, req, directory, &sbuf,
+ FILE_READ_ATTRIBUTES, /* Just a stat open */
+ FILE_SHARE_NONE, /* Ignored for stat opens */
+ FILE_CREATE,
+ 0,
+ FILE_ATTRIBUTE_DIRECTORY,
+ NULL,
+ &fsp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file(fsp, NORMAL_CLOSE);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Receive notification that one of our open files has been renamed by another
+ smbd process.
+****************************************************************************/
+
+void msg_file_was_renamed(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ files_struct *fsp;
+ char *frm = (char *)data->data;
+ struct file_id id;
+ const char *sharepath;
+ const char *newname;
+ size_t sp_len;
+
+ if (data->data == NULL
+ || data->length < MSG_FILE_RENAMED_MIN_SIZE + 2) {
+ DEBUG(0, ("msg_file_was_renamed: Got invalid msg len %d\n",
+ (int)data->length));
+ return;
+ }
+
+ /* Unpack the message. */
+ pull_file_id_16(frm, &id);
+ sharepath = &frm[16];
+ newname = sharepath + strlen(sharepath) + 1;
+ sp_len = strlen(sharepath);
+
+ DEBUG(10,("msg_file_was_renamed: Got rename message for sharepath %s, new name %s, "
+ "file_id %s\n",
+ sharepath, newname, file_id_string_tos(&id)));
+
+ for(fsp = file_find_di_first(id); fsp; fsp = file_find_di_next(fsp)) {
+ if (memcmp(fsp->conn->connectpath, sharepath, sp_len) == 0) {
+ DEBUG(10,("msg_file_was_renamed: renaming file fnum %d from %s -> %s\n",
+ fsp->fnum, fsp->fsp_name, newname ));
+ string_set(&fsp->fsp_name, newname);
+ } else {
+ /* TODO. JRA. */
+ /* Now we have the complete path we can work out if this is
+ actually within this share and adjust newname accordingly. */
+ DEBUG(10,("msg_file_was_renamed: share mismatch (sharepath %s "
+ "not sharepath %s) "
+ "fnum %d from %s -> %s\n",
+ fsp->conn->connectpath,
+ sharepath,
+ fsp->fnum,
+ fsp->fsp_name,
+ newname ));
+ }
+ }
+}
+
+struct case_semantics_state {
+ connection_struct *conn;
+ bool case_sensitive;
+ bool case_preserve;
+ bool short_case_preserve;
+};
+
+/****************************************************************************
+ Restore case semantics.
+****************************************************************************/
+static int restore_case_semantics(struct case_semantics_state *state)
+{
+ state->conn->case_sensitive = state->case_sensitive;
+ state->conn->case_preserve = state->case_preserve;
+ state->conn->short_case_preserve = state->short_case_preserve;
+ return 0;
+}
+
+/****************************************************************************
+ Save case semantics.
+****************************************************************************/
+static struct case_semantics_state *set_posix_case_semantics(TALLOC_CTX *mem_ctx,
+ connection_struct *conn)
+{
+ struct case_semantics_state *result;
+
+ if (!(result = talloc(mem_ctx, struct case_semantics_state))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->conn = conn;
+ result->case_sensitive = conn->case_sensitive;
+ result->case_preserve = conn->case_preserve;
+ result->short_case_preserve = conn->short_case_preserve;
+
+ /* Set to POSIX. */
+ conn->case_sensitive = True;
+ conn->case_preserve = True;
+ conn->short_case_preserve = True;
+
+ talloc_set_destructor(result, restore_case_semantics);
+
+ return result;
+}
+
+/*
+ * If a main file is opened for delete, all streams need to be checked for
+ * !FILE_SHARE_DELETE. Do this by opening with DELETE_ACCESS.
+ * If that works, delete them all by setting the delete on close and close.
+ */
+
+static NTSTATUS open_streams_for_delete(connection_struct *conn,
+ const char *fname)
+{
+ struct stream_struct *stream_info;
+ files_struct **streams;
+ int i;
+ unsigned int num_streams;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = SMB_VFS_STREAMINFO(conn, NULL, fname, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
+ || NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("SMB_VFS_STREAMINFO failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("open_streams_for_delete found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ streams = TALLOC_ARRAY(talloc_tos(), files_struct *, num_streams);
+ if (streams == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ char *streamname;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ streams[i] = NULL;
+ continue;
+ }
+
+ streamname = talloc_asprintf(talloc_tos(), "%s%s", fname,
+ stream_info[i].name);
+
+ if (streamname == NULL) {
+ DEBUG(0, ("talloc_aprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = create_file_unixpath
+ (conn, /* conn */
+ NULL, /* req */
+ streamname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE
+ | FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &streams[i], /* result */
+ NULL, /* pinfo */
+ NULL); /* psbuf */
+
+ TALLOC_FREE(streamname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not open stream %s: %s\n",
+ streamname, nt_errstr(status)));
+ break;
+ }
+ }
+
+ /*
+ * don't touch the variable "status" beyond this point :-)
+ */
+
+ for (i -= 1 ; i >= 0; i--) {
+ if (streams[i] == NULL) {
+ continue;
+ }
+
+ DEBUG(10, ("Closing stream # %d, %s\n", i,
+ streams[i]->fsp_name));
+ close_file(streams[i], NORMAL_CLOSE);
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Wrapper around open_file_ntcreate and open_directory
+ */
+
+NTSTATUS create_file_unixpath(connection_struct *conn,
+ struct smb_request *req,
+ const char *fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ SMB_BIG_UINT allocation_size,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+
+ files_struct **result,
+ int *pinfo,
+ SMB_STRUCT_STAT *psbuf)
+{
+ SMB_STRUCT_STAT sbuf;
+ int info = FILE_WAS_OPENED;
+ files_struct *base_fsp = NULL;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ DEBUG(10,("create_file_unixpath: access_mask = 0x%x "
+ "file_attributes = 0x%x, share_access = 0x%x, "
+ "create_disposition = 0x%x create_options = 0x%x "
+ "oplock_request = 0x%x ea_list = 0x%p, sd = 0x%p, "
+ "fname = %s\n",
+ (unsigned int)access_mask,
+ (unsigned int)file_attributes,
+ (unsigned int)share_access,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)oplock_request,
+ ea_list, sd, fname));
+
+ if (create_options & FILE_OPEN_BY_FILE_ID) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_INVALID_PARAM_MASK) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (req == NULL) {
+ oplock_request |= INTERNAL_OPEN_ONLY;
+ }
+
+ if (psbuf != NULL) {
+ sbuf = *psbuf;
+ }
+ else {
+ if (SMB_VFS_STAT(conn, fname, &sbuf) == -1) {
+ SET_STAT_INVALID(sbuf);
+ }
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && (access_mask & DELETE_ACCESS)
+ && !is_ntfs_stream_name(fname)) {
+ /*
+ * We can't open a file with DELETE access if any of the
+ * streams is open without FILE_SHARE_DELETE
+ */
+ status = open_streams_for_delete(conn, fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ /* This is the correct thing to do (check every time) but can_delete
+ * is expensive (it may have to read the parent directory
+ * permissions). So for now we're not doing it unless we have a strong
+ * hint the client is really going to delete this file. If the client
+ * is forcing FILE_CREATE let the filesystem take care of the
+ * permissions. */
+
+ /* Setting FILE_SHARE_DELETE is the hint. */
+
+ if (lp_acl_check_permissions(SNUM(conn))
+ && (create_disposition != FILE_CREATE)
+ && (share_access & FILE_SHARE_DELETE)
+ && (access_mask & DELETE_ACCESS)
+ && (((dos_mode(conn, fname, &sbuf) & FILE_ATTRIBUTE_READONLY)
+ && !lp_delete_readonly(SNUM(conn)))
+ || !can_delete_file_in_directory(conn, fname))) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+
+#if 0
+ /* We need to support SeSecurityPrivilege for this. */
+ if ((access_mask & SEC_RIGHT_SYSTEM_SECURITY) &&
+ !user_has_privileges(current_user.nt_user_token,
+ &se_security)) {
+ status = NT_STATUS_PRIVILEGE_NOT_HELD;
+ goto fail;
+ }
+#endif
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && is_ntfs_stream_name(fname)
+ && (!(create_options & NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE))) {
+ char *base;
+ uint32 base_create_disposition;
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+
+ status = split_ntfs_stream_name(talloc_tos(), fname,
+ &base, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_file_unixpath: "
+ "split_ntfs_stream_name failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ SMB_ASSERT(!is_ntfs_stream_name(base)); /* paranoia.. */
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ base_create_disposition = FILE_OPEN;
+ break;
+ default:
+ base_create_disposition = FILE_OPEN_IF;
+ break;
+ }
+
+ status = create_file_unixpath(conn, NULL, base, 0,
+ FILE_SHARE_READ
+ | FILE_SHARE_WRITE
+ | FILE_SHARE_DELETE,
+ base_create_disposition,
+ 0, 0, 0, 0, NULL, NULL,
+ &base_fsp, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_file_unixpath for base %s failed: "
+ "%s\n", base, nt_errstr(status)));
+ goto fail;
+ }
+ }
+
+ /*
+ * If it's a request for a directory open, deal with it separately.
+ */
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* Can't open a temp directory. IFS kit test. */
+ if (file_attributes & FILE_ATTRIBUTE_TEMPORARY) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /*
+ * We will get a create directory here if the Win32
+ * app specified a security descriptor in the
+ * CreateDirectory() call.
+ */
+
+ oplock_request = 0;
+ status = open_directory(
+ conn, req, fname, &sbuf, access_mask, share_access,
+ create_disposition, create_options, file_attributes,
+ &info, &fsp);
+ } else {
+
+ /*
+ * Ordinary file case.
+ */
+
+ status = open_file_ntcreate(
+ conn, req, fname, &sbuf, access_mask, share_access,
+ create_disposition, create_options, file_attributes,
+ oplock_request, &info, &fsp);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+
+ /*
+ * Fail the open if it was explicitly a non-directory
+ * file.
+ */
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+
+ oplock_request = 0;
+ status = open_directory(
+ conn, req, fname, &sbuf, access_mask,
+ share_access, create_disposition,
+ create_options, file_attributes,
+ &info, &fsp);
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * According to the MS documentation, the only time the security
+ * descriptor is applied to the opened file is iff we *created* the
+ * file; an existing file stays the same.
+ *
+ * Also, it seems (from observation) that you can open the file with
+ * any access mask but you can still write the sd. We need to override
+ * the granted access before we call set_sd
+ * Patch for bug #2242 from Tom Lackemann <cessnatomny@yahoo.com>.
+ */
+
+ if ((sd != NULL) && (info == FILE_WAS_CREATED)
+ && lp_nt_acl_support(SNUM(conn))) {
+
+ uint32_t sec_info_sent = ALL_SECURITY_INFORMATION;
+ uint32_t saved_access_mask = fsp->access_mask;
+
+ if (sd->owner_sid == NULL) {
+ sec_info_sent &= ~OWNER_SECURITY_INFORMATION;
+ }
+ if (sd->group_sid == NULL) {
+ sec_info_sent &= ~GROUP_SECURITY_INFORMATION;
+ }
+ if (sd->sacl == NULL) {
+ sec_info_sent &= ~SACL_SECURITY_INFORMATION;
+ }
+ if (sd->dacl == NULL) {
+ sec_info_sent &= ~DACL_SECURITY_INFORMATION;
+ }
+
+ fsp->access_mask = FILE_GENERIC_ALL;
+
+ status = SMB_VFS_FSET_NT_ACL(fsp, sec_info_sent, sd);
+
+ fsp->access_mask = saved_access_mask;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if ((ea_list != NULL) && (info == FILE_WAS_CREATED)) {
+ status = set_ea(conn, fsp, fname, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (!fsp->is_directory && S_ISDIR(sbuf.st_mode)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+
+ /* Save the requested allocation size. */
+ if ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN)) {
+ if (allocation_size
+ && (allocation_size > sbuf.st_size)) {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, allocation_size);
+ if (fsp->is_directory) {
+ /* Can't set allocation size on a directory. */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+ if (vfs_allocate_file_space(
+ fsp, fsp->initial_allocation_size) == -1) {
+ status = NT_STATUS_DISK_FULL;
+ goto fail;
+ }
+ } else {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, (SMB_BIG_UINT)sbuf.st_size);
+ }
+ }
+
+ DEBUG(10, ("create_file_unixpath: info=%d\n", info));
+
+ /*
+ * Set fsp->base_fsp late enough that we can't "goto fail" anymore. In
+ * the fail: branch we call close_file(fsp, ERROR_CLOSE) which would
+ * also close fsp->base_fsp which we have to also do explicitly in
+ * this routine here, as not in all "goto fail:" we have the fsp set
+ * up already to be initialized with the base_fsp.
+ */
+
+ fsp->base_fsp = base_fsp;
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+ if (psbuf != NULL) {
+ if ((fsp->fh == NULL) || (fsp->fh->fd == -1)) {
+ *psbuf = sbuf;
+ }
+ else {
+ SMB_VFS_FSTAT(fsp, psbuf);
+ }
+ }
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file_unixpath: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ close_file(fsp, ERROR_CLOSE);
+ fsp = NULL;
+ }
+ if (base_fsp != NULL) {
+ close_file(base_fsp, ERROR_CLOSE);
+ base_fsp = NULL;
+ }
+ return status;
+}
+
+NTSTATUS create_file(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t root_dir_fid,
+ const char *fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ SMB_BIG_UINT allocation_size,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+
+ files_struct **result,
+ int *pinfo,
+ SMB_STRUCT_STAT *psbuf)
+{
+ struct case_semantics_state *case_state = NULL;
+ SMB_STRUCT_STAT sbuf;
+ int info = FILE_WAS_OPENED;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ DEBUG(10,("create_file: access_mask = 0x%x "
+ "file_attributes = 0x%x, share_access = 0x%x, "
+ "create_disposition = 0x%x create_options = 0x%x "
+ "oplock_request = 0x%x "
+ "root_dir_fid = 0x%x, ea_list = 0x%p, sd = 0x%p, "
+ "fname = %s\n",
+ (unsigned int)access_mask,
+ (unsigned int)file_attributes,
+ (unsigned int)share_access,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)oplock_request,
+ (unsigned int)root_dir_fid,
+ ea_list, sd, fname));
+
+ /*
+ * Get the file name.
+ */
+
+ if (root_dir_fid != 0) {
+ /*
+ * This filename is relative to a directory fid.
+ */
+ char *parent_fname = NULL;
+ files_struct *dir_fsp = file_fsp(root_dir_fid);
+
+ if (dir_fsp == NULL) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ if (!dir_fsp->is_directory) {
+
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+
+ if (is_ntfs_stream_name(fname)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+
+ /*
+ we need to handle the case when we get a
+ relative open relative to a file and the
+ pathname is blank - this is a reopen!
+ (hint from demyn plantenberg)
+ */
+
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ if (ISDOT(dir_fsp->fsp_name)) {
+ /*
+ * We're at the toplevel dir, the final file name
+ * must not contain ./, as this is filtered out
+ * normally by srvstr_get_path and unix_convert
+ * explicitly rejects paths containing ./.
+ */
+ parent_fname = talloc_strdup(talloc_tos(), "");
+ if (parent_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ size_t dir_name_len = strlen(dir_fsp->fsp_name);
+
+ /*
+ * Copy in the base directory name.
+ */
+
+ parent_fname = TALLOC_ARRAY(talloc_tos(), char,
+ dir_name_len+2);
+ if (parent_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ memcpy(parent_fname, dir_fsp->fsp_name,
+ dir_name_len+1);
+
+ /*
+ * Ensure it ends in a '/'.
+ * We used TALLOC_SIZE +2 to add space for the '/'.
+ */
+
+ if(dir_name_len
+ && (parent_fname[dir_name_len-1] != '\\')
+ && (parent_fname[dir_name_len-1] != '/')) {
+ parent_fname[dir_name_len] = '/';
+ parent_fname[dir_name_len+1] = '\0';
+ }
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s%s", parent_fname,
+ fname);
+ if (fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+
+ if (is_ntfs_stream_name(fname)) {
+ enum FAKE_FILE_TYPE fake_file_type;
+
+ fake_file_type = is_fake_file(fname);
+
+ if (fake_file_type != FAKE_FILE_TYPE_NONE) {
+
+ /*
+ * Here we go! support for changing the disk quotas
+ * --metze
+ *
+ * We need to fake up to open this MAGIC QUOTA file
+ * and return a valid FID.
+ *
+ * w2k close this file directly after openening xp
+ * also tries a QUERY_FILE_INFO on the file and then
+ * close it
+ */
+ status = open_fake_file(conn, req->vuid,
+ fake_file_type, fname,
+ access_mask, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ ZERO_STRUCT(sbuf);
+ goto done;
+ }
+
+ if (!(conn->fs_capabilities & FILE_NAMED_STREAMS)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+ }
+
+ if ((req != NULL) && (req->flags2 & FLAGS2_DFS_PATHNAMES)) {
+ char *resolved_fname;
+
+ status = resolve_dfspath(talloc_tos(), conn, true, fname,
+ &resolved_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * For PATH_NOT_COVERED we had
+ * reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ * ERRSRV, ERRbadpath);
+ * Need to fix in callers
+ */
+ goto fail;
+ }
+ fname = resolved_fname;
+ }
+
+ /*
+ * Check if POSIX semantics are wanted.
+ */
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ case_state = set_posix_case_semantics(talloc_tos(), conn);
+ file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+ }
+
+ {
+ char *converted_fname;
+
+ SET_STAT_INVALID(sbuf);
+
+ status = unix_convert(talloc_tos(), conn, fname, False,
+ &converted_fname, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ fname = converted_fname;
+ }
+
+ TALLOC_FREE(case_state);
+
+ /* All file access must go through check_name() */
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = create_file_unixpath(
+ conn, req, fname, access_mask, share_access,
+ create_disposition, create_options, file_attributes,
+ oplock_request, allocation_size, sd, ea_list,
+ &fsp, &info, &sbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ done:
+ DEBUG(10, ("create_file: info=%d\n", info));
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+ if (psbuf != NULL) {
+ *psbuf = sbuf;
+ }
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ close_file(fsp, ERROR_CLOSE);
+ fsp = NULL;
+ }
+ return status;
+}
diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c
new file mode 100644
index 0000000000..23411294df
--- /dev/null
+++ b/source3/smbd/oplock.c
@@ -0,0 +1,897 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ 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/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+
+/* Current number of oplocks we have outstanding. */
+static int32 exclusive_oplocks_open = 0;
+static int32 level_II_oplocks_open = 0;
+bool global_client_failed_oplock_break = False;
+
+extern uint32 global_client_caps;
+
+static struct kernel_oplocks *koplocks;
+
+/****************************************************************************
+ Get the number of current exclusive oplocks.
+****************************************************************************/
+
+int32 get_number_of_exclusive_open_oplocks(void)
+{
+ return exclusive_oplocks_open;
+}
+
+/****************************************************************************
+ Return True if an oplock message is pending.
+****************************************************************************/
+
+bool oplock_message_waiting(fd_set *fds)
+{
+ if (koplocks && koplocks->msg_waiting(fds)) {
+ return True;
+ }
+
+ return False;
+}
+
+/****************************************************************************
+ Find out if there are any kernel oplock messages waiting and process them
+ if so. pfds is the fd_set from the main select loop (which contains any
+ kernel oplock fd if that's what the system uses (IRIX). If may be NULL if
+ we're calling this in a shutting down state.
+****************************************************************************/
+
+void process_kernel_oplocks(struct messaging_context *msg_ctx, fd_set *pfds)
+{
+ /*
+ * We need to check for kernel oplocks before going into the select
+ * here, as the EINTR generated by the linux kernel oplock may have
+ * already been eaten. JRA.
+ */
+
+ if (!koplocks) {
+ return;
+ }
+
+ while (koplocks->msg_waiting(pfds)) {
+ files_struct *fsp;
+ char msg[MSG_SMB_KERNEL_BREAK_SIZE];
+
+ fsp = koplocks->receive_message(pfds);
+
+ if (fsp == NULL) {
+ DEBUG(3, ("Kernel oplock message announced, but none "
+ "received\n"));
+ return;
+ }
+
+ /* Put the kernel break info into the message. */
+ push_file_id_16(msg, &fsp->file_id);
+ SIVAL(msg,16,fsp->fh->gen_id);
+
+ /* Don't need to be root here as we're only ever
+ sending to ourselves. */
+
+ messaging_send_buf(msg_ctx, procid_self(),
+ MSG_SMB_KERNEL_BREAK,
+ (uint8 *)&msg, MSG_SMB_KERNEL_BREAK_SIZE);
+ }
+}
+
+/****************************************************************************
+ Attempt to set an oplock on a file. Always succeeds if kernel oplocks are
+ disabled (just sets flags). Returns True if oplock set.
+****************************************************************************/
+
+bool set_file_oplock(files_struct *fsp, int oplock_type)
+{
+ if (koplocks && !koplocks->set_oplock(fsp, oplock_type)) {
+ return False;
+ }
+
+ fsp->oplock_type = oplock_type;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ if (oplock_type == LEVEL_II_OPLOCK) {
+ level_II_oplocks_open++;
+ } else {
+ exclusive_oplocks_open++;
+ }
+
+ DEBUG(5,("set_file_oplock: granted oplock on file %s, %s/%lu, "
+ "tv_sec = %x, tv_usec = %x\n",
+ fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id, (int)fsp->open_time.tv_sec,
+ (int)fsp->open_time.tv_usec ));
+
+ return True;
+}
+
+/****************************************************************************
+ Attempt to release an oplock on a file. Decrements oplock count.
+****************************************************************************/
+
+void release_file_oplock(files_struct *fsp)
+{
+ if ((fsp->oplock_type != NO_OPLOCK) &&
+ (fsp->oplock_type != FAKE_LEVEL_II_OPLOCK) &&
+ koplocks) {
+ koplocks->release_oplock(fsp);
+ }
+
+ if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ level_II_oplocks_open--;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ exclusive_oplocks_open--;
+ }
+
+ SMB_ASSERT(exclusive_oplocks_open>=0);
+ SMB_ASSERT(level_II_oplocks_open>=0);
+
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+
+ flush_write_cache(fsp, OPLOCK_RELEASE_FLUSH);
+
+ TALLOC_FREE(fsp->oplock_timeout);
+}
+
+/****************************************************************************
+ Attempt to downgrade an oplock on a file. Doesn't decrement oplock count.
+****************************************************************************/
+
+static void downgrade_file_oplock(files_struct *fsp)
+{
+ if (koplocks) {
+ koplocks->release_oplock(fsp);
+ }
+ fsp->oplock_type = LEVEL_II_OPLOCK;
+ exclusive_oplocks_open--;
+ level_II_oplocks_open++;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+}
+
+/****************************************************************************
+ Remove a file oplock. Copes with level II and exclusive.
+ Locks then unlocks the share mode lock. Client can decide to go directly
+ to none even if a "break-to-level II" was sent.
+****************************************************************************/
+
+bool remove_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ /* Remove the oplock flag from the sharemode. */
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+ if (lck == NULL) {
+ DEBUG(0,("remove_oplock: failed to lock share entry for "
+ "file %s\n", fsp->fsp_name ));
+ return False;
+ }
+ ret = remove_share_oplock(lck, fsp);
+ if (!ret) {
+ DEBUG(0,("remove_oplock: failed to remove share oplock for "
+ "file %s fnum %d, %s\n",
+ fsp->fsp_name, fsp->fnum, file_id_string_tos(&fsp->file_id)));
+ }
+ release_file_oplock(fsp);
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+/*
+ * Deal with a reply when a break-to-level II was sent.
+ */
+bool downgrade_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+ if (lck == NULL) {
+ DEBUG(0,("downgrade_oplock: failed to lock share entry for "
+ "file %s\n", fsp->fsp_name ));
+ return False;
+ }
+ ret = downgrade_share_oplock(lck, fsp);
+ if (!ret) {
+ DEBUG(0,("downgrade_oplock: failed to downgrade share oplock "
+ "for file %s fnum %d, file_id %s\n",
+ fsp->fsp_name, fsp->fnum, file_id_string_tos(&fsp->file_id)));
+ }
+
+ downgrade_file_oplock(fsp);
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+/****************************************************************************
+ Return the fd (if any) used for receiving oplock notifications.
+****************************************************************************/
+
+int oplock_notify_fd(void)
+{
+ if (koplocks) {
+ return koplocks->notification_fd;
+ }
+
+ return -1;
+}
+
+/****************************************************************************
+ Set up an oplock break message.
+****************************************************************************/
+
+static char *new_break_smb_message(TALLOC_CTX *mem_ctx,
+ files_struct *fsp, uint8 cmd)
+{
+ char *result = TALLOC_ARRAY(mem_ctx, char, smb_size + 8*2 + 0);
+
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ memset(result,'\0',smb_size);
+ srv_set_message(result,8,0,true);
+ SCVAL(result,smb_com,SMBlockingX);
+ SSVAL(result,smb_tid,fsp->conn->cnum);
+ SSVAL(result,smb_pid,0xFFFF);
+ SSVAL(result,smb_uid,0);
+ SSVAL(result,smb_mid,0xFFFF);
+ SCVAL(result,smb_vwv0,0xFF);
+ SSVAL(result,smb_vwv2,fsp->fnum);
+ SCVAL(result,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE);
+ SCVAL(result,smb_vwv3+1,cmd);
+ return result;
+}
+
+/****************************************************************************
+ Function to do the waiting before sending a local break.
+****************************************************************************/
+
+static void wait_before_sending_break(void)
+{
+ long wait_time = (long)lp_oplock_break_wait_time();
+
+ if (wait_time) {
+ smb_msleep(wait_time);
+ }
+}
+
+/****************************************************************************
+ Ensure that we have a valid oplock.
+****************************************************************************/
+
+static files_struct *initial_break_processing(struct file_id id, unsigned long file_id)
+{
+ files_struct *fsp = NULL;
+
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "initial_break_processing: called for %s/%u\n",
+ file_id_string_tos(&id), (int)file_id);
+ dbgtext( "Current oplocks_open (exclusive = %d, levelII = %d)\n",
+ exclusive_oplocks_open, level_II_oplocks_open );
+ }
+
+ /*
+ * We need to search the file open table for the
+ * entry containing this dev and inode, and ensure
+ * we have an oplock on it.
+ */
+
+ fsp = file_find_dif(id, file_id);
+
+ if(fsp == NULL) {
+ /* The file could have been closed in the meantime - return success. */
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "initial_break_processing: cannot find open file with " );
+ dbgtext( "file_id %s gen_id = %lu", file_id_string_tos(&id), file_id);
+ dbgtext( "allowing break to succeed.\n" );
+ }
+ return NULL;
+ }
+
+ /* Ensure we have an oplock on the file */
+
+ /*
+ * There is a potential race condition in that an oplock could
+ * have been broken due to another udp request, and yet there are
+ * still oplock break messages being sent in the udp message
+ * queue for this file. So return true if we don't have an oplock,
+ * as we may have just freed it.
+ */
+
+ if(fsp->oplock_type == NO_OPLOCK) {
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "initial_break_processing: file %s ", fsp->fsp_name );
+ dbgtext( "(file_id = %s gen_id = %lu) has no oplock.\n",
+ file_id_string_tos(&id), fsp->fh->gen_id );
+ dbgtext( "Allowing break to succeed regardless.\n" );
+ }
+ return NULL;
+ }
+
+ return fsp;
+}
+
+static void oplock_timeout_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+
+ /* Remove the timed event handler. */
+ TALLOC_FREE(fsp->oplock_timeout);
+ DEBUG(0, ("Oplock break failed for file %s -- replying anyway\n", fsp->fsp_name));
+ global_client_failed_oplock_break = True;
+ remove_oplock(fsp);
+ reply_to_oplock_break_requests(fsp);
+}
+
+/*******************************************************************
+ Add a timeout handler waiting for the client reply.
+*******************************************************************/
+
+static void add_oplock_timeout_handler(files_struct *fsp)
+{
+ if (fsp->oplock_timeout != NULL) {
+ DEBUG(0, ("Logic problem -- have an oplock event hanging "
+ "around\n"));
+ }
+
+ fsp->oplock_timeout =
+ event_add_timed(smbd_event_context(), NULL,
+ timeval_current_ofs(OPLOCK_BREAK_TIMEOUT, 0),
+ "oplock_timeout_handler",
+ oplock_timeout_handler, fsp);
+
+ if (fsp->oplock_timeout == NULL) {
+ DEBUG(0, ("Could not add oplock timeout handler\n"));
+ }
+}
+
+/*******************************************************************
+ This handles the case of a write triggering a break to none
+ message on a level2 oplock.
+ When we get this message we may be in any of three states :
+ NO_OPLOCK, LEVEL_II, FAKE_LEVEL2. We only send a message to
+ the client for LEVEL2.
+*******************************************************************/
+
+static void process_oplock_async_level2_break_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct share_mode_entry msg;
+ files_struct *fsp;
+ char *break_msg;
+ bool sign_state;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_SHARE_MODE_ENTRY_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* De-linearize incoming message. */
+ message_to_share_mode_entry(&msg, (char *)data->data);
+
+ DEBUG(10, ("Got oplock async level 2 break message from pid %d: %s/%lu\n",
+ (int)procid_to_pid(&src), file_id_string_tos(&msg.id), msg.share_file_id));
+
+ fsp = initial_break_processing(msg.id, msg.share_file_id);
+
+ if (fsp == NULL) {
+ /* We hit a race here. Break messages are sent, and before we
+ * get to process this message, we have closed the file.
+ * No need to reply as this is an async message. */
+ DEBUG(3, ("process_oplock_async_level2_break_message: Did not find fsp, ignoring\n"));
+ return;
+ }
+
+ if (fsp->oplock_type == NO_OPLOCK) {
+ /* We already got a "break to none" message and we've handled it.
+ * just ignore. */
+ DEBUG(3, ("process_oplock_async_level2_break_message: already broken to none, ignoring.\n"));
+ return;
+ }
+
+ if (fsp->oplock_type == FAKE_LEVEL_II_OPLOCK) {
+ /* Don't tell the client, just downgrade. */
+ DEBUG(3, ("process_oplock_async_level2_break_message: downgrading fake level 2 oplock.\n"));
+ remove_oplock(fsp);
+ return;
+ }
+
+ /* Ensure we're really at level2 state. */
+ SMB_ASSERT(fsp->oplock_type == LEVEL_II_OPLOCK);
+
+ /* Now send a break to none message to our client. */
+
+ break_msg = new_break_smb_message(NULL, fsp, OPLOCKLEVEL_NONE);
+ if (break_msg == NULL) {
+ exit_server("Could not talloc break_msg\n");
+ }
+
+ /* Need to wait before sending a break message if we sent ourselves this message. */
+ if (procid_to_pid(&src) == sys_getpid()) {
+ wait_before_sending_break();
+ }
+
+ /* Save the server smb signing state. */
+ sign_state = srv_oplock_set_signing(False);
+
+ show_msg(break_msg);
+ if (!srv_send_smb(smbd_server_fd(),
+ break_msg,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("oplock_break: srv_send_smb failed.");
+ }
+
+ /* Restore the sign state to what it was. */
+ srv_oplock_set_signing(sign_state);
+
+ TALLOC_FREE(break_msg);
+
+ /* Async level2 request, don't send a reply, just remove the oplock. */
+ remove_oplock(fsp);
+}
+
+/*******************************************************************
+ This handles the generic oplock break message from another smbd.
+*******************************************************************/
+
+static void process_oplock_break_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct share_mode_entry msg;
+ files_struct *fsp;
+ char *break_msg;
+ bool break_to_level2 = False;
+ bool sign_state;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_SHARE_MODE_ENTRY_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* De-linearize incoming message. */
+ message_to_share_mode_entry(&msg, (char *)data->data);
+
+ DEBUG(10, ("Got oplock break message from pid %d: %s/%lu\n",
+ (int)procid_to_pid(&src), file_id_string_tos(&msg.id), msg.share_file_id));
+
+ fsp = initial_break_processing(msg.id, msg.share_file_id);
+
+ if (fsp == NULL) {
+ /* a We hit race here. Break messages are sent, and before we
+ * get to process this message, we have closed the file. Reply
+ * with 'ok, oplock broken' */
+ DEBUG(3, ("Did not find fsp\n"));
+
+ /* We just send the same message back. */
+ messaging_send_buf(msg_ctx, src, MSG_SMB_BREAK_RESPONSE,
+ (uint8 *)data->data,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ return;
+ }
+
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /* Remember we have to inform the requesting PID when the
+ * client replies */
+ msg.pid = src;
+ ADD_TO_ARRAY(NULL, struct share_mode_entry, msg,
+ &fsp->pending_break_messages,
+ &fsp->num_pending_break_messages);
+ return;
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(msg.op_type) &&
+ !EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ DEBUG(3, ("Already downgraded oplock on %s: %s\n",
+ file_id_string_tos(&fsp->file_id),
+ fsp->fsp_name));
+ /* We just send the same message back. */
+ messaging_send_buf(msg_ctx, src, MSG_SMB_BREAK_RESPONSE,
+ (uint8 *)data->data,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ return;
+ }
+
+ if ((global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+ !(msg.op_type & FORCE_OPLOCK_BREAK_TO_NONE) &&
+ !koplocks && /* NOTE: we force levelII off for kernel oplocks -
+ * this will change when it is supported */
+ lp_level2_oplocks(SNUM(fsp->conn))) {
+ break_to_level2 = True;
+ }
+
+ break_msg = new_break_smb_message(NULL, fsp, break_to_level2 ?
+ OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+ if (break_msg == NULL) {
+ exit_server("Could not talloc break_msg\n");
+ }
+
+ /* Need to wait before sending a break message if we sent ourselves this message. */
+ if (procid_to_pid(&src) == sys_getpid()) {
+ wait_before_sending_break();
+ }
+
+ /* Save the server smb signing state. */
+ sign_state = srv_oplock_set_signing(False);
+
+ show_msg(break_msg);
+ if (!srv_send_smb(smbd_server_fd(),
+ break_msg,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("oplock_break: srv_send_smb failed.");
+ }
+
+ /* Restore the sign state to what it was. */
+ srv_oplock_set_signing(sign_state);
+
+ TALLOC_FREE(break_msg);
+
+ fsp->sent_oplock_break = break_to_level2 ? LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
+ msg.pid = src;
+ ADD_TO_ARRAY(NULL, struct share_mode_entry, msg,
+ &fsp->pending_break_messages,
+ &fsp->num_pending_break_messages);
+
+ add_oplock_timeout_handler(fsp);
+}
+
+/*******************************************************************
+ This handles the kernel oplock break message.
+*******************************************************************/
+
+static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct file_id id;
+ unsigned long file_id;
+ files_struct *fsp;
+ char *break_msg;
+ bool sign_state;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_KERNEL_BREAK_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* Pull the data from the message. */
+ pull_file_id_16((char *)data->data, &id);
+ file_id = (unsigned long)IVAL(data->data, 16);
+
+ DEBUG(10, ("Got kernel oplock break message from pid %d: %s/%u\n",
+ (int)procid_to_pid(&src), file_id_string_tos(&id),
+ (unsigned int)file_id));
+
+ fsp = initial_break_processing(id, file_id);
+
+ if (fsp == NULL) {
+ DEBUG(3, ("Got a kernel oplock break message for a file "
+ "I don't know about\n"));
+ return;
+ }
+
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /* This is ok, kernel oplocks come in completely async */
+ DEBUG(3, ("Got a kernel oplock request while waiting for a "
+ "break reply\n"));
+ return;
+ }
+
+ break_msg = new_break_smb_message(NULL, fsp, OPLOCKLEVEL_NONE);
+ if (break_msg == NULL) {
+ exit_server("Could not talloc break_msg\n");
+ }
+
+ /* Save the server smb signing state. */
+ sign_state = srv_oplock_set_signing(False);
+
+ show_msg(break_msg);
+ if (!srv_send_smb(smbd_server_fd(),
+ break_msg,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("oplock_break: srv_send_smb failed.");
+ }
+
+ /* Restore the sign state to what it was. */
+ srv_oplock_set_signing(sign_state);
+
+ TALLOC_FREE(break_msg);
+
+ fsp->sent_oplock_break = BREAK_TO_NONE_SENT;
+
+ add_oplock_timeout_handler(fsp);
+}
+
+void reply_to_oplock_break_requests(files_struct *fsp)
+{
+ int i;
+
+ for (i=0; i<fsp->num_pending_break_messages; i++) {
+ struct share_mode_entry *e = &fsp->pending_break_messages[i];
+ char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+ share_mode_entry_to_message(msg, e);
+
+ messaging_send_buf(smbd_messaging_context(), e->pid,
+ MSG_SMB_BREAK_RESPONSE,
+ (uint8 *)msg,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ }
+
+ SAFE_FREE(fsp->pending_break_messages);
+ fsp->num_pending_break_messages = 0;
+ if (fsp->oplock_timeout != NULL) {
+ /* Remove the timed event handler. */
+ TALLOC_FREE(fsp->oplock_timeout);
+ fsp->oplock_timeout = NULL;
+ }
+ return;
+}
+
+static void process_oplock_break_response(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct share_mode_entry msg;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_SHARE_MODE_ENTRY_SIZE) {
+ DEBUG(0, ("Got invalid msg len %u\n",
+ (unsigned int)data->length));
+ return;
+ }
+
+ /* De-linearize incoming message. */
+ message_to_share_mode_entry(&msg, (char *)data->data);
+
+ DEBUG(10, ("Got oplock break response from pid %d: %s/%lu mid %u\n",
+ (int)procid_to_pid(&src), file_id_string_tos(&msg.id), msg.share_file_id,
+ (unsigned int)msg.op_mid));
+
+ /* Here's the hack from open.c, store the mid in the 'port' field */
+ schedule_deferred_open_smb_message(msg.op_mid);
+}
+
+static void process_open_retry_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct share_mode_entry msg;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_SHARE_MODE_ENTRY_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* De-linearize incoming message. */
+ message_to_share_mode_entry(&msg, (char *)data->data);
+
+ DEBUG(10, ("Got open retry msg from pid %d: %s mid %u\n",
+ (int)procid_to_pid(&src), file_id_string_tos(&msg.id),
+ (unsigned int)msg.op_mid));
+
+ schedule_deferred_open_smb_message(msg.op_mid);
+}
+
+/****************************************************************************
+ This function is called on any file modification or lock request. If a file
+ is level 2 oplocked then it must tell all other level 2 holders to break to
+ none.
+****************************************************************************/
+
+void release_level_2_oplocks_on_change(files_struct *fsp)
+{
+ int i;
+ struct share_mode_lock *lck;
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ if (!LEVEL_II_OPLOCK_TYPE(fsp->oplock_type))
+ return;
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+ if (lck == NULL) {
+ DEBUG(0,("release_level_2_oplocks_on_change: failed to lock "
+ "share mode entry for file %s.\n", fsp->fsp_name ));
+ return;
+ }
+
+ DEBUG(10,("release_level_2_oplocks_on_change: num_share_modes = %d\n",
+ lck->num_share_modes ));
+
+ for(i = 0; i < lck->num_share_modes; i++) {
+ struct share_mode_entry *share_entry = &lck->share_modes[i];
+ char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+ if (!is_valid_share_mode_entry(share_entry)) {
+ continue;
+ }
+
+ /*
+ * As there could have been multiple writes waiting at the
+ * lock_share_entry gate we may not be the first to
+ * enter. Hence the state of the op_types in the share mode
+ * entries may be partly NO_OPLOCK and partly LEVEL_II or FAKE_LEVEL_II
+ * oplock. It will do no harm to re-send break messages to
+ * those smbd's that are still waiting their turn to remove
+ * their LEVEL_II state, and also no harm to ignore existing
+ * NO_OPLOCK states. JRA.
+ */
+
+ DEBUG(10,("release_level_2_oplocks_on_change: "
+ "share_entry[%i]->op_type == %d\n",
+ i, share_entry->op_type ));
+
+ if (share_entry->op_type == NO_OPLOCK) {
+ continue;
+ }
+
+ /* Paranoia .... */
+ if (EXCLUSIVE_OPLOCK_TYPE(share_entry->op_type)) {
+ DEBUG(0,("release_level_2_oplocks_on_change: PANIC. "
+ "share mode entry %d is an exlusive "
+ "oplock !\n", i ));
+ TALLOC_FREE(lck);
+ abort();
+ }
+
+ share_mode_entry_to_message(msg, share_entry);
+
+ messaging_send_buf(smbd_messaging_context(), share_entry->pid,
+ MSG_SMB_ASYNC_LEVEL2_BREAK,
+ (uint8 *)msg,
+ MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+ }
+
+ /* We let the message receivers handle removing the oplock state
+ in the share mode lock db. */
+
+ TALLOC_FREE(lck);
+}
+
+/****************************************************************************
+ Linearize a share mode entry struct to an internal oplock break message.
+****************************************************************************/
+
+void share_mode_entry_to_message(char *msg, const struct share_mode_entry *e)
+{
+ SIVAL(msg,0,(uint32)e->pid.pid);
+ SSVAL(msg,4,e->op_mid);
+ SSVAL(msg,6,e->op_type);
+ SIVAL(msg,8,e->access_mask);
+ SIVAL(msg,12,e->share_access);
+ SIVAL(msg,16,e->private_options);
+ SIVAL(msg,20,(uint32)e->time.tv_sec);
+ SIVAL(msg,24,(uint32)e->time.tv_usec);
+ push_file_id_16(msg+28, &e->id);
+ SIVAL(msg,44,e->share_file_id);
+ SIVAL(msg,48,e->uid);
+ SSVAL(msg,52,e->flags);
+#ifdef CLUSTER_SUPPORT
+ SIVAL(msg,54,e->pid.vnn);
+#endif
+}
+
+/****************************************************************************
+ De-linearize an internal oplock break message to a share mode entry struct.
+****************************************************************************/
+
+void message_to_share_mode_entry(struct share_mode_entry *e, char *msg)
+{
+ e->pid.pid = (pid_t)IVAL(msg,0);
+ e->op_mid = SVAL(msg,4);
+ e->op_type = SVAL(msg,6);
+ e->access_mask = IVAL(msg,8);
+ e->share_access = IVAL(msg,12);
+ e->private_options = IVAL(msg,16);
+ e->time.tv_sec = (time_t)IVAL(msg,20);
+ e->time.tv_usec = (int)IVAL(msg,24);
+ pull_file_id_16(msg+28, &e->id);
+ e->share_file_id = (unsigned long)IVAL(msg,44);
+ e->uid = (uint32)IVAL(msg,48);
+ e->flags = (uint16)SVAL(msg,52);
+#ifdef CLUSTER_SUPPORT
+ e->pid.vnn = IVAL(msg,54);
+#endif
+}
+
+/****************************************************************************
+ Setup oplocks for this process.
+****************************************************************************/
+
+bool init_oplocks(struct messaging_context *msg_ctx)
+{
+ DEBUG(3,("init_oplocks: initializing messages.\n"));
+
+ messaging_register(msg_ctx, NULL, MSG_SMB_BREAK_REQUEST,
+ process_oplock_break_message);
+ messaging_register(msg_ctx, NULL, MSG_SMB_ASYNC_LEVEL2_BREAK,
+ process_oplock_async_level2_break_message);
+ messaging_register(msg_ctx, NULL, MSG_SMB_BREAK_RESPONSE,
+ process_oplock_break_response);
+ messaging_register(msg_ctx, NULL, MSG_SMB_KERNEL_BREAK,
+ process_kernel_oplock_break);
+ messaging_register(msg_ctx, NULL, MSG_SMB_OPEN_RETRY,
+ process_open_retry_message);
+
+ if (lp_kernel_oplocks()) {
+#if HAVE_KERNEL_OPLOCKS_IRIX
+ koplocks = irix_init_kernel_oplocks();
+#elif HAVE_KERNEL_OPLOCKS_LINUX
+ koplocks = linux_init_kernel_oplocks();
+#endif
+ }
+
+ return True;
+}
diff --git a/source3/smbd/oplock_irix.c b/source3/smbd/oplock_irix.c
new file mode 100644
index 0000000000..8c287c9836
--- /dev/null
+++ b/source3/smbd/oplock_irix.c
@@ -0,0 +1,301 @@
+/*
+ Unix SMB/CIFS implementation.
+ IRIX kernel oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+
+#if HAVE_KERNEL_OPLOCKS_IRIX
+
+static int oplock_pipe_write = -1;
+static int oplock_pipe_read = -1;
+
+/****************************************************************************
+ Test to see if IRIX kernel oplocks work.
+****************************************************************************/
+
+static bool irix_oplocks_available(void)
+{
+ int fd;
+ int pfd[2];
+ TALLOC_CTX *ctx = talloc_stackframe();
+ char *tmpname = NULL;
+
+ set_effective_capability(KERNEL_OPLOCK_CAPABILITY);
+
+ tmpname = talloc_asprintf(ctx,
+ "%s/koplock.%d",
+ lp_lockdir(),
+ (int)sys_getpid());
+ if (!tmpname) {
+ TALLOC_FREE(ctx);
+ return False;
+ }
+
+ if(pipe(pfd) != 0) {
+ DEBUG(0,("check_kernel_oplocks: Unable to create pipe. Error "
+ "was %s\n",
+ strerror(errno) ));
+ TALLOC_FREE(ctx);
+ return False;
+ }
+
+ if((fd = sys_open(tmpname, O_RDWR|O_CREAT|O_EXCL|O_TRUNC, 0600)) < 0) {
+ DEBUG(0,("check_kernel_oplocks: Unable to open temp test file "
+ "%s. Error was %s\n",
+ tmpname, strerror(errno) ));
+ unlink( tmpname );
+ close(pfd[0]);
+ close(pfd[1]);
+ TALLOC_FREE(ctx);
+ return False;
+ }
+
+ unlink(tmpname);
+
+ TALLOC_FREE(ctx);
+
+ if(sys_fcntl_long(fd, F_OPLKREG, pfd[1]) == -1) {
+ DEBUG(0,("check_kernel_oplocks: Kernel oplocks are not "
+ "available on this machine. Disabling kernel oplock "
+ "support.\n" ));
+ close(pfd[0]);
+ close(pfd[1]);
+ close(fd);
+ return False;
+ }
+
+ if(sys_fcntl_long(fd, F_OPLKACK, OP_REVOKE) < 0 ) {
+ DEBUG(0,("check_kernel_oplocks: Error when removing kernel "
+ "oplock. Error was %s. Disabling kernel oplock "
+ "support.\n", strerror(errno) ));
+ close(pfd[0]);
+ close(pfd[1]);
+ close(fd);
+ return False;
+ }
+
+ close(pfd[0]);
+ close(pfd[1]);
+ close(fd);
+
+ return True;
+}
+
+/****************************************************************************
+ * Deal with the IRIX kernel <--> smbd
+ * oplock break protocol.
+****************************************************************************/
+
+static files_struct *irix_oplock_receive_message(fd_set *fds)
+{
+ oplock_stat_t os;
+ char dummy;
+ struct file_id fileid;
+ files_struct *fsp;
+
+ /* Ensure we only get one call per select fd set. */
+ FD_CLR(oplock_pipe_read, fds);
+
+ /*
+ * Read one byte of zero to clear the
+ * kernel break notify message.
+ */
+
+ if(read(oplock_pipe_read, &dummy, 1) != 1) {
+ DEBUG(0,("irix_oplock_receive_message: read of kernel "
+ "notification failed. Error was %s.\n",
+ strerror(errno) ));
+ return NULL;
+ }
+
+ /*
+ * Do a query to get the
+ * device and inode of the file that has the break
+ * request outstanding.
+ */
+
+ if(sys_fcntl_ptr(oplock_pipe_read, F_OPLKSTAT, &os) < 0) {
+ DEBUG(0,("irix_oplock_receive_message: fcntl of kernel "
+ "notification failed. Error was %s.\n",
+ strerror(errno) ));
+ if(errno == EAGAIN) {
+ /*
+ * Duplicate kernel break message - ignore.
+ */
+ return NULL;
+ }
+ return NULL;
+ }
+
+ /*
+ * We only have device and inode info here - we have to guess that this
+ * is the first fsp open with this dev,ino pair.
+ *
+ * NOTE: this doesn't work if any VFS modules overloads
+ * the file_id_create() hook!
+ */
+
+ fileid = file_id_create_dev((SMB_DEV_T)os.os_dev,
+ (SMB_INO_T)os.os_ino);
+ if ((fsp = file_find_di_first(fileid)) == NULL) {
+ DEBUG(0,("irix_oplock_receive_message: unable to find open "
+ "file with dev = %x, inode = %.0f\n",
+ (unsigned int)os.os_dev, (double)os.os_ino ));
+ return NULL;
+ }
+
+ DEBUG(5,("irix_oplock_receive_message: kernel oplock break request "
+ "received for file_id %s gen_id = %ul",
+ file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id ));
+
+ return fsp;
+}
+
+/****************************************************************************
+ Attempt to set an kernel oplock on a file.
+****************************************************************************/
+
+static bool irix_set_kernel_oplock(files_struct *fsp, int oplock_type)
+{
+ if (sys_fcntl_long(fsp->fh->fd, F_OPLKREG, oplock_pipe_write) == -1) {
+ if(errno != EAGAIN) {
+ DEBUG(0,("irix_set_kernel_oplock: Unable to get "
+ "kernel oplock on file %s, file_id %s "
+ "gen_id = %ul. Error was %s\n",
+ fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id,
+ strerror(errno) ));
+ } else {
+ DEBUG(5,("irix_set_kernel_oplock: Refused oplock on "
+ "file %s, fd = %d, file_id = %s, "
+ "gen_id = %ul. Another process had the file "
+ "open.\n",
+ fsp->fsp_name, fsp->fh->fd,
+ file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id ));
+ }
+ return False;
+ }
+
+ DEBUG(10,("irix_set_kernel_oplock: got kernel oplock on file %s, file_id = %s "
+ "gen_id = %ul\n",
+ fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id));
+
+ return True;
+}
+
+/****************************************************************************
+ Release a kernel oplock on a file.
+****************************************************************************/
+
+static void irix_release_kernel_oplock(files_struct *fsp)
+{
+ if (DEBUGLVL(10)) {
+ /*
+ * Check and print out the current kernel
+ * oplock state of this file.
+ */
+ int state = sys_fcntl_long(fsp->fh->fd, F_OPLKACK, -1);
+ dbgtext("irix_release_kernel_oplock: file %s, file_id = %s"
+ "gen_id = %ul, has kernel oplock state "
+ "of %x.\n", fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id, state );
+ }
+
+ /*
+ * Remove the kernel oplock on this file.
+ */
+ if(sys_fcntl_long(fsp->fh->fd, F_OPLKACK, OP_REVOKE) < 0) {
+ if( DEBUGLVL( 0 )) {
+ dbgtext("irix_release_kernel_oplock: Error when "
+ "removing kernel oplock on file " );
+ dbgtext("%s, file_id = %s gen_id = %ul. "
+ "Error was %s\n",
+ fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id,
+ strerror(errno) );
+ }
+ }
+}
+
+/****************************************************************************
+ See if there is a message waiting in this fd set.
+ Note that fds MAY BE NULL ! If so we must do our own select.
+****************************************************************************/
+
+static bool irix_oplock_msg_waiting(fd_set *fds)
+{
+ int selrtn;
+ fd_set myfds;
+ struct timeval to;
+
+ if (oplock_pipe_read == -1)
+ return False;
+
+ if (fds) {
+ return FD_ISSET(oplock_pipe_read, fds);
+ }
+
+ /* Do a zero-time select. We just need to find out if there
+ * are any outstanding messages. We use sys_select_intr as
+ * we need to ignore any signals. */
+
+ FD_ZERO(&myfds);
+ FD_SET(oplock_pipe_read, &myfds);
+
+ to = timeval_set(0, 0);
+ selrtn = sys_select_intr(oplock_pipe_read+1,&myfds,NULL,NULL,&to);
+ return (selrtn == 1) ? True : False;
+}
+
+/****************************************************************************
+ Setup kernel oplocks.
+****************************************************************************/
+
+struct kernel_oplocks *irix_init_kernel_oplocks(void)
+{
+ int pfd[2];
+ static struct kernel_oplocks koplocks;
+
+ if (!irix_oplocks_available())
+ return NULL;
+
+ if(pipe(pfd) != 0) {
+ DEBUG(0,("setup_kernel_oplock_pipe: Unable to create pipe. "
+ "Error was %s\n", strerror(errno) ));
+ return False;
+ }
+
+ oplock_pipe_read = pfd[0];
+ oplock_pipe_write = pfd[1];
+
+ koplocks.receive_message = irix_oplock_receive_message;
+ koplocks.set_oplock = irix_set_kernel_oplock;
+ koplocks.release_oplock = irix_release_kernel_oplock;
+ koplocks.msg_waiting = irix_oplock_msg_waiting;
+ koplocks.notification_fd = oplock_pipe_read;
+
+ return &koplocks;
+}
+#else
+ void oplock_irix_dummy(void);
+ void oplock_irix_dummy(void) {}
+#endif /* HAVE_KERNEL_OPLOCKS_IRIX */
diff --git a/source3/smbd/oplock_linux.c b/source3/smbd/oplock_linux.c
new file mode 100644
index 0000000000..08df228f8f
--- /dev/null
+++ b/source3/smbd/oplock_linux.c
@@ -0,0 +1,249 @@
+/*
+ Unix SMB/CIFS implementation.
+ kernel oplock processing for Linux
+ Copyright (C) Andrew Tridgell 2000
+
+ 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/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+
+#if HAVE_KERNEL_OPLOCKS_LINUX
+
+static SIG_ATOMIC_T signals_received;
+#define FD_PENDING_SIZE 100
+static SIG_ATOMIC_T fd_pending_array[FD_PENDING_SIZE];
+
+#ifndef F_SETLEASE
+#define F_SETLEASE 1024
+#endif
+
+#ifndef F_GETLEASE
+#define F_GETLEASE 1025
+#endif
+
+#ifndef CAP_LEASE
+#define CAP_LEASE 28
+#endif
+
+#ifndef RT_SIGNAL_LEASE
+#define RT_SIGNAL_LEASE (SIGRTMIN+1)
+#endif
+
+#ifndef F_SETSIG
+#define F_SETSIG 10
+#endif
+
+/****************************************************************************
+ Handle a LEASE signal, incrementing the signals_received and blocking the signal.
+****************************************************************************/
+
+static void signal_handler(int sig, siginfo_t *info, void *unused)
+{
+ if (signals_received < FD_PENDING_SIZE - 1) {
+ fd_pending_array[signals_received] = (SIG_ATOMIC_T)info->si_fd;
+ signals_received++;
+ } /* Else signal is lost. */
+ sys_select_signal(RT_SIGNAL_LEASE);
+}
+
+/*
+ * public function to get linux lease capability. Needed by some VFS modules (eg. gpfs.c)
+ */
+void linux_set_lease_capability(void)
+{
+ set_effective_capability(LEASE_CAPABILITY);
+}
+
+/*
+ * Call to set the kernel lease signal handler
+ */
+int linux_set_lease_sighandler(int fd)
+{
+ if (fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
+ DEBUG(3,("Failed to set signal handler for kernel lease\n"));
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Call SETLEASE. If we get EACCES then we try setting up the right capability and
+ try again.
+ Use the SMB_VFS_LINUX_SETLEASE instead of this call directly.
+****************************************************************************/
+
+int linux_setlease(int fd, int leasetype)
+{
+ int ret;
+
+ ret = fcntl(fd, F_SETLEASE, leasetype);
+ if (ret == -1 && errno == EACCES) {
+ set_effective_capability(LEASE_CAPABILITY);
+ ret = fcntl(fd, F_SETLEASE, leasetype);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Deal with the Linux kernel <--> smbd
+ * oplock break protocol.
+****************************************************************************/
+
+static files_struct *linux_oplock_receive_message(fd_set *fds)
+{
+ int fd;
+ files_struct *fsp;
+
+ BlockSignals(True, RT_SIGNAL_LEASE);
+ fd = fd_pending_array[0];
+ fsp = file_find_fd(fd);
+ fd_pending_array[0] = (SIG_ATOMIC_T)-1;
+ if (signals_received > 1)
+ memmove(CONST_DISCARD(void *, &fd_pending_array[0]),
+ CONST_DISCARD(void *, &fd_pending_array[1]),
+ sizeof(SIG_ATOMIC_T)*(signals_received-1));
+ signals_received--;
+ /* now we can receive more signals */
+ BlockSignals(False, RT_SIGNAL_LEASE);
+
+ return fsp;
+}
+
+/****************************************************************************
+ Attempt to set an kernel oplock on a file.
+****************************************************************************/
+
+static bool linux_set_kernel_oplock(files_struct *fsp, int oplock_type)
+{
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_WRLCK) == -1) {
+ DEBUG(3,("linux_set_kernel_oplock: Refused oplock on file %s, "
+ "fd = %d, file_id = %s. (%s)\n",
+ fsp->fsp_name, fsp->fh->fd,
+ file_id_string_tos(&fsp->file_id),
+ strerror(errno)));
+ return False;
+ }
+
+ DEBUG(3,("linux_set_kernel_oplock: got kernel oplock on file %s, "
+ "file_id = %s gen_id = %lu\n",
+ fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id));
+
+ return True;
+}
+
+/****************************************************************************
+ Release a kernel oplock on a file.
+****************************************************************************/
+
+static void linux_release_kernel_oplock(files_struct *fsp)
+{
+ if (DEBUGLVL(10)) {
+ /*
+ * Check and print out the current kernel
+ * oplock state of this file.
+ */
+ int state = fcntl(fsp->fh->fd, F_GETLEASE, 0);
+ dbgtext("linux_release_kernel_oplock: file %s, file_id = %s "
+ "gen_id = %lu has kernel oplock state "
+ "of %x.\n", fsp->fsp_name, file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id, state );
+ }
+
+ /*
+ * Remove the kernel oplock on this file.
+ */
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_UNLCK) == -1) {
+ if (DEBUGLVL(0)) {
+ dbgtext("linux_release_kernel_oplock: Error when "
+ "removing kernel oplock on file " );
+ dbgtext("%s, file_id = %s, gen_id = %lu. "
+ "Error was %s\n", fsp->fsp_name,
+ file_id_string_tos(&fsp->file_id),
+ fsp->fh->gen_id, strerror(errno) );
+ }
+ }
+}
+
+/****************************************************************************
+ See if a oplock message is waiting.
+****************************************************************************/
+
+static bool linux_oplock_msg_waiting(fd_set *fds)
+{
+ return signals_received != 0;
+}
+
+/****************************************************************************
+ See if the kernel supports oplocks.
+****************************************************************************/
+
+static bool linux_oplocks_available(void)
+{
+ int fd, ret;
+ fd = open("/dev/null", O_RDONLY);
+ if (fd == -1)
+ return False; /* uggh! */
+ ret = fcntl(fd, F_GETLEASE, 0);
+ close(fd);
+ return ret == F_UNLCK;
+}
+
+/****************************************************************************
+ Setup kernel oplocks.
+****************************************************************************/
+
+struct kernel_oplocks *linux_init_kernel_oplocks(void)
+{
+ static struct kernel_oplocks koplocks;
+ struct sigaction act;
+
+ if (!linux_oplocks_available()) {
+ DEBUG(3,("Linux kernel oplocks not available\n"));
+ return NULL;
+ }
+
+ ZERO_STRUCT(act);
+
+ act.sa_handler = NULL;
+ act.sa_sigaction = signal_handler;
+ act.sa_flags = SA_SIGINFO;
+ sigemptyset( &act.sa_mask );
+ if (sigaction(RT_SIGNAL_LEASE, &act, NULL) != 0) {
+ DEBUG(0,("Failed to setup RT_SIGNAL_LEASE handler\n"));
+ return NULL;
+ }
+
+ koplocks.receive_message = linux_oplock_receive_message;
+ koplocks.set_oplock = linux_set_kernel_oplock;
+ koplocks.release_oplock = linux_release_kernel_oplock;
+ koplocks.msg_waiting = linux_oplock_msg_waiting;
+ koplocks.notification_fd = -1;
+
+ /* the signal can start off blocked due to a bug in bash */
+ BlockSignals(False, RT_SIGNAL_LEASE);
+
+ DEBUG(3,("Linux kernel oplocks enabled\n"));
+
+ return &koplocks;
+}
+#else
+ void oplock_linux_dummy(void);
+
+ void oplock_linux_dummy(void) {}
+#endif /* HAVE_KERNEL_OPLOCKS_LINUX */
diff --git a/source3/smbd/password.c b/source3/smbd/password.c
new file mode 100644
index 0000000000..1d3514429f
--- /dev/null
+++ b/source3/smbd/password.c
@@ -0,0 +1,829 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ 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"
+
+/* users from session setup */
+static char *session_userlist = NULL;
+/* workgroup from session setup. */
+static char *session_workgroup = NULL;
+
+/* this holds info on user ids that are already validated for this VC */
+static user_struct *validated_users;
+static int next_vuid = VUID_OFFSET;
+static int num_validated_vuids;
+
+enum server_allocated_state { SERVER_ALLOCATED_REQUIRED_YES,
+ SERVER_ALLOCATED_REQUIRED_NO,
+ SERVER_ALLOCATED_REQUIRED_ANY};
+
+static user_struct *get_valid_user_struct_internal(uint16 vuid,
+ enum server_allocated_state server_allocated)
+{
+ user_struct *usp;
+ int count=0;
+
+ if (vuid == UID_FIELD_INVALID)
+ return NULL;
+
+ for (usp=validated_users;usp;usp=usp->next,count++) {
+ if (vuid == usp->vuid) {
+ switch (server_allocated) {
+ case SERVER_ALLOCATED_REQUIRED_YES:
+ if (usp->server_info == NULL) {
+ continue;
+ }
+ break;
+ case SERVER_ALLOCATED_REQUIRED_NO:
+ if (usp->server_info != NULL) {
+ continue;
+ }
+ case SERVER_ALLOCATED_REQUIRED_ANY:
+ break;
+ }
+ if (count > 10) {
+ DLIST_PROMOTE(validated_users, usp);
+ }
+ return usp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Check if a uid has been validated, and return an pointer to the user_struct
+ if it has. NULL if not. vuid is biased by an offset. This allows us to
+ tell random client vuid's (normally zero) from valid vuids.
+****************************************************************************/
+
+user_struct *get_valid_user_struct(uint16 vuid)
+{
+ return get_valid_user_struct_internal(vuid,
+ SERVER_ALLOCATED_REQUIRED_YES);
+}
+
+bool is_partial_auth_vuid(uint16 vuid)
+{
+ if (vuid == UID_FIELD_INVALID) {
+ return False;
+ }
+ return get_valid_user_struct_internal(vuid,
+ SERVER_ALLOCATED_REQUIRED_NO) ? True : False;
+}
+
+/****************************************************************************
+ Get the user struct of a partial NTLMSSP login
+****************************************************************************/
+
+user_struct *get_partial_auth_user_struct(uint16 vuid)
+{
+ return get_valid_user_struct_internal(vuid,
+ SERVER_ALLOCATED_REQUIRED_NO);
+}
+
+/****************************************************************************
+ Invalidate a uid.
+****************************************************************************/
+
+void invalidate_vuid(uint16 vuid)
+{
+ user_struct *vuser = NULL;
+
+ if (vuid == UID_FIELD_INVALID) {
+ return;
+ }
+
+ vuser = get_valid_user_struct_internal(vuid,
+ SERVER_ALLOCATED_REQUIRED_ANY);
+ if (vuser == NULL) {
+ return;
+ }
+
+ session_yield(vuser);
+
+ if (vuser->auth_ntlmssp_state) {
+ auth_ntlmssp_end(&vuser->auth_ntlmssp_state);
+ }
+
+ DLIST_REMOVE(validated_users, vuser);
+
+ /* clear the vuid from the 'cache' on each connection, and
+ from the vuid 'owner' of connections */
+ conn_clear_vuid_caches(vuid);
+
+ TALLOC_FREE(vuser);
+ num_validated_vuids--;
+}
+
+/****************************************************************************
+ Invalidate all vuid entries for this process.
+****************************************************************************/
+
+void invalidate_all_vuids(void)
+{
+ user_struct *usp, *next=NULL;
+
+ for (usp=validated_users;usp;usp=next) {
+ next = usp->next;
+ invalidate_vuid(usp->vuid);
+ }
+}
+
+/****************************************************
+ Create a new partial auth user struct.
+*****************************************************/
+
+int register_initial_vuid(void)
+{
+ user_struct *vuser;
+
+ /* Paranoia check. */
+ if(lp_security() == SEC_SHARE) {
+ smb_panic("register_initial_vuid: "
+ "Tried to register uid in security=share");
+ }
+
+ /* Limit allowed vuids to 16bits - VUID_OFFSET. */
+ if (num_validated_vuids >= 0xFFFF-VUID_OFFSET) {
+ return UID_FIELD_INVALID;
+ }
+
+ if((vuser = talloc_zero(NULL, user_struct)) == NULL) {
+ DEBUG(0,("register_initial_vuid: "
+ "Failed to talloc users struct!\n"));
+ return UID_FIELD_INVALID;
+ }
+
+ /* Allocate a free vuid. Yes this is a linear search... */
+ while( get_valid_user_struct_internal(next_vuid,
+ SERVER_ALLOCATED_REQUIRED_ANY) != NULL ) {
+ next_vuid++;
+ /* Check for vuid wrap. */
+ if (next_vuid == UID_FIELD_INVALID) {
+ next_vuid = VUID_OFFSET;
+ }
+ }
+
+ DEBUG(10,("register_initial_vuid: allocated vuid = %u\n",
+ (unsigned int)next_vuid ));
+
+ vuser->vuid = next_vuid;
+
+ /*
+ * This happens in an unfinished NTLMSSP session setup. We
+ * need to allocate a vuid between the first and second calls
+ * to NTLMSSP.
+ */
+ next_vuid++;
+ num_validated_vuids++;
+
+ DLIST_ADD(validated_users, vuser);
+ return vuser->vuid;
+}
+
+static int register_homes_share(const char *username)
+{
+ int result;
+ struct passwd *pwd;
+
+ result = lp_servicenumber(username);
+ if (result != -1) {
+ DEBUG(3, ("Using static (or previously created) service for "
+ "user '%s'; path = '%s'\n", username,
+ lp_pathname(result)));
+ return result;
+ }
+
+ pwd = getpwnam_alloc(talloc_tos(), username);
+
+ if ((pwd == NULL) || (pwd->pw_dir[0] == '\0')) {
+ DEBUG(3, ("No home directory defined for user '%s'\n",
+ username));
+ TALLOC_FREE(pwd);
+ return -1;
+ }
+
+ DEBUG(3, ("Adding homes service for user '%s' using home directory: "
+ "'%s'\n", username, pwd->pw_dir));
+
+ result = add_home_service(username, username, pwd->pw_dir);
+
+ TALLOC_FREE(pwd);
+ return result;
+}
+
+/**
+ * register that a valid login has been performed, establish 'session'.
+ * @param server_info The token returned from the authentication process.
+ * (now 'owned' by register_existing_vuid)
+ *
+ * @param session_key The User session key for the login session (now also
+ * 'owned' by register_existing_vuid)
+ *
+ * @param respose_blob The NT challenge-response, if available. (May be
+ * freed after this call)
+ *
+ * @param smb_name The untranslated name of the user
+ *
+ * @return Newly allocated vuid, biased by an offset. (This allows us to
+ * tell random client vuid's (normally zero) from valid vuids.)
+ *
+ */
+
+int register_existing_vuid(uint16 vuid,
+ auth_serversupplied_info *server_info,
+ DATA_BLOB response_blob,
+ const char *smb_name)
+{
+ fstring tmp;
+ user_struct *vuser;
+
+ vuser = get_partial_auth_user_struct(vuid);
+ if (!vuser) {
+ goto fail;
+ }
+
+ /* Use this to keep tabs on all our info from the authentication */
+ vuser->server_info = talloc_move(vuser, &server_info);
+
+ /* This is a potentially untrusted username */
+ alpha_strcpy(tmp, smb_name, ". _-$", sizeof(tmp));
+
+ vuser->server_info->sanitized_username = talloc_strdup(
+ vuser->server_info, tmp);
+
+ DEBUG(10,("register_existing_vuid: (%u,%u) %s %s %s guest=%d\n",
+ (unsigned int)vuser->server_info->utok.uid,
+ (unsigned int)vuser->server_info->utok.gid,
+ vuser->server_info->unix_name,
+ vuser->server_info->sanitized_username,
+ pdb_get_domain(vuser->server_info->sam_account),
+ vuser->server_info->guest ));
+
+ DEBUG(3, ("register_existing_vuid: User name: %s\t"
+ "Real name: %s\n", vuser->server_info->unix_name,
+ pdb_get_fullname(vuser->server_info->sam_account)));
+
+ if (!vuser->server_info->ptok) {
+ DEBUG(1, ("register_existing_vuid: server_info does not "
+ "contain a user_token - cannot continue\n"));
+ goto fail;
+ }
+
+ DEBUG(3,("register_existing_vuid: UNIX uid %d is UNIX user %s, "
+ "and will be vuid %u\n", (int)vuser->server_info->utok.uid,
+ vuser->server_info->unix_name, vuser->vuid));
+
+ next_vuid++;
+ num_validated_vuids++;
+
+ if (!session_claim(vuser)) {
+ DEBUG(1, ("register_existing_vuid: Failed to claim session "
+ "for vuid=%d\n",
+ vuser->vuid));
+ goto fail;
+ }
+
+ /* Register a home dir service for this user if
+ (a) This is not a guest connection,
+ (b) we have a home directory defined
+ (c) there s not an existing static share by that name
+ If a share exists by this name (autoloaded or not) reuse it . */
+
+ vuser->homes_snum = -1;
+
+ if (!vuser->server_info->guest) {
+ vuser->homes_snum = register_homes_share(
+ vuser->server_info->unix_name);
+ }
+
+ if (srv_is_signing_negotiated() && !vuser->server_info->guest &&
+ !srv_signing_started()) {
+ /* Try and turn on server signing on the first non-guest
+ * sessionsetup. */
+ srv_set_signing(vuser->server_info->user_session_key, response_blob);
+ }
+
+ /* fill in the current_user_info struct */
+ set_current_user_info(
+ vuser->server_info->sanitized_username,
+ vuser->server_info->unix_name,
+ pdb_get_fullname(vuser->server_info->sam_account),
+ pdb_get_domain(vuser->server_info->sam_account));
+
+ return vuser->vuid;
+
+ fail:
+
+ if (vuser) {
+ invalidate_vuid(vuid);
+ }
+ return UID_FIELD_INVALID;
+}
+
+/****************************************************************************
+ Add a name to the session users list.
+****************************************************************************/
+
+void add_session_user(const char *user)
+{
+ struct passwd *pw;
+ char *tmp;
+
+ pw = Get_Pwnam_alloc(talloc_tos(), user);
+
+ if (pw == NULL) {
+ return;
+ }
+
+ if (session_userlist == NULL) {
+ session_userlist = SMB_STRDUP(pw->pw_name);
+ goto done;
+ }
+
+ if (in_list(pw->pw_name,session_userlist,False) ) {
+ goto done;
+ }
+
+ if (strlen(session_userlist) > 128 * 1024) {
+ DEBUG(3,("add_session_user: session userlist already "
+ "too large.\n"));
+ goto done;
+ }
+
+ if (asprintf(&tmp, "%s %s", session_userlist, pw->pw_name) == -1) {
+ DEBUG(3, ("asprintf failed\n"));
+ goto done;
+ }
+
+ SAFE_FREE(session_userlist);
+ session_userlist = tmp;
+ done:
+ TALLOC_FREE(pw);
+}
+
+/****************************************************************************
+ In security=share mode we need to store the client workgroup, as that's
+ what Vista uses for the NTLMv2 calculation.
+****************************************************************************/
+
+void add_session_workgroup(const char *workgroup)
+{
+ if (session_workgroup) {
+ SAFE_FREE(session_workgroup);
+ }
+ session_workgroup = smb_xstrdup(workgroup);
+}
+
+/****************************************************************************
+ In security=share mode we need to return the client workgroup, as that's
+ what Vista uses for the NTLMv2 calculation.
+****************************************************************************/
+
+const char *get_session_workgroup(void)
+{
+ return session_workgroup;
+}
+
+/****************************************************************************
+ Check if a user is in a netgroup user list. If at first we don't succeed,
+ try lower case.
+****************************************************************************/
+
+bool user_in_netgroup(const char *user, const char *ngname)
+{
+#ifdef HAVE_NETGROUP
+ static char *mydomain = NULL;
+ fstring lowercase_user;
+
+ if (mydomain == NULL)
+ yp_get_default_domain(&mydomain);
+
+ if(mydomain == NULL) {
+ DEBUG(5,("Unable to get default yp domain, "
+ "let's try without specifying it\n"));
+ }
+
+ DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
+ user, mydomain?mydomain:"(ANY)", ngname));
+
+ if (innetgr(ngname, NULL, user, mydomain)) {
+ DEBUG(5,("user_in_netgroup: Found\n"));
+ return (True);
+ } else {
+
+ /*
+ * Ok, innetgr is case sensitive. Try once more with lowercase
+ * just in case. Attempt to fix #703. JRA.
+ */
+
+ fstrcpy(lowercase_user, user);
+ strlower_m(lowercase_user);
+
+ DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
+ lowercase_user, mydomain?mydomain:"(ANY)", ngname));
+
+ if (innetgr(ngname, NULL, lowercase_user, mydomain)) {
+ DEBUG(5,("user_in_netgroup: Found\n"));
+ return (True);
+ }
+ }
+#endif /* HAVE_NETGROUP */
+ return False;
+}
+
+/****************************************************************************
+ Check if a user is in a user list - can check combinations of UNIX
+ and netgroup lists.
+****************************************************************************/
+
+bool user_in_list(const char *user,const char **list)
+{
+ if (!list || !*list)
+ return False;
+
+ DEBUG(10,("user_in_list: checking user %s in list\n", user));
+
+ while (*list) {
+
+ DEBUG(10,("user_in_list: checking user |%s| against |%s|\n",
+ user, *list));
+
+ /*
+ * Check raw username.
+ */
+ if (strequal(user, *list))
+ return(True);
+
+ /*
+ * Now check to see if any combination
+ * of UNIX and netgroups has been specified.
+ */
+
+ if(**list == '@') {
+ /*
+ * Old behaviour. Check netgroup list
+ * followed by UNIX list.
+ */
+ if(user_in_netgroup(user, *list +1))
+ return True;
+ if(user_in_group(user, *list +1))
+ return True;
+ } else if (**list == '+') {
+
+ if((*(*list +1)) == '&') {
+ /*
+ * Search UNIX list followed by netgroup.
+ */
+ if(user_in_group(user, *list +2))
+ return True;
+ if(user_in_netgroup(user, *list +2))
+ return True;
+
+ } else {
+
+ /*
+ * Just search UNIX list.
+ */
+
+ if(user_in_group(user, *list +1))
+ return True;
+ }
+
+ } else if (**list == '&') {
+
+ if(*(*list +1) == '+') {
+ /*
+ * Search netgroup list followed by UNIX list.
+ */
+ if(user_in_netgroup(user, *list +2))
+ return True;
+ if(user_in_group(user, *list +2))
+ return True;
+ } else {
+ /*
+ * Just search netgroup list.
+ */
+ if(user_in_netgroup(user, *list +1))
+ return True;
+ }
+ }
+
+ list++;
+ }
+ return(False);
+}
+
+/****************************************************************************
+ Check if a username is valid.
+****************************************************************************/
+
+static bool user_ok(const char *user, int snum)
+{
+ char **valid, **invalid;
+ bool ret;
+
+ valid = invalid = NULL;
+ ret = True;
+
+ if (lp_invalid_users(snum)) {
+ str_list_copy(talloc_tos(), &invalid, lp_invalid_users(snum));
+ if (invalid &&
+ str_list_substitute(invalid, "%S", lp_servicename(snum))) {
+
+ /* This is used in sec=share only, so no current user
+ * around to pass to str_list_sub_basic() */
+
+ if ( invalid && str_list_sub_basic(invalid, "", "") ) {
+ ret = !user_in_list(user,
+ (const char **)invalid);
+ }
+ }
+ }
+ TALLOC_FREE(invalid);
+
+ if (ret && lp_valid_users(snum)) {
+ str_list_copy(talloc_tos(), &valid, lp_valid_users(snum));
+ if ( valid &&
+ str_list_substitute(valid, "%S", lp_servicename(snum)) ) {
+
+ /* This is used in sec=share only, so no current user
+ * around to pass to str_list_sub_basic() */
+
+ if ( valid && str_list_sub_basic(valid, "", "") ) {
+ ret = user_in_list(user, (const char **)valid);
+ }
+ }
+ }
+ TALLOC_FREE(valid);
+
+ if (ret && lp_onlyuser(snum)) {
+ char **user_list = str_list_make(
+ talloc_tos(), lp_username(snum), NULL);
+ if (user_list &&
+ str_list_substitute(user_list, "%S",
+ lp_servicename(snum))) {
+ ret = user_in_list(user, (const char **)user_list);
+ }
+ TALLOC_FREE(user_list);
+ }
+
+ return(ret);
+}
+
+/****************************************************************************
+ Validate a group username entry. Return the username or NULL.
+****************************************************************************/
+
+static char *validate_group(char *group, DATA_BLOB password,int snum)
+{
+#ifdef HAVE_NETGROUP
+ {
+ char *host, *user, *domain;
+ setnetgrent(group);
+ while (getnetgrent(&host, &user, &domain)) {
+ if (user) {
+ if (user_ok(user, snum) &&
+ password_ok(user,password)) {
+ endnetgrent();
+ return(user);
+ }
+ }
+ }
+ endnetgrent();
+ }
+#endif
+
+#ifdef HAVE_GETGRENT
+ {
+ struct group *gptr;
+ setgrent();
+ while ((gptr = (struct group *)getgrent())) {
+ if (strequal(gptr->gr_name,group))
+ break;
+ }
+
+ /*
+ * As user_ok can recurse doing a getgrent(), we must
+ * copy the member list onto the heap before
+ * use. Bug pointed out by leon@eatworms.swmed.edu.
+ */
+
+ if (gptr) {
+ char *member_list = NULL;
+ size_t list_len = 0;
+ char *member;
+ int i;
+
+ for(i = 0; gptr->gr_mem && gptr->gr_mem[i]; i++) {
+ list_len += strlen(gptr->gr_mem[i])+1;
+ }
+ list_len++;
+
+ member_list = (char *)SMB_MALLOC(list_len);
+ if (!member_list) {
+ endgrent();
+ return NULL;
+ }
+
+ *member_list = '\0';
+ member = member_list;
+
+ for(i = 0; gptr->gr_mem && gptr->gr_mem[i]; i++) {
+ size_t member_len = strlen(gptr->gr_mem[i])+1;
+
+ DEBUG(10,("validate_group: = gr_mem = "
+ "%s\n", gptr->gr_mem[i]));
+
+ safe_strcpy(member, gptr->gr_mem[i],
+ list_len - (member-member_list));
+ member += member_len;
+ }
+
+ endgrent();
+
+ member = member_list;
+ while (*member) {
+ if (user_ok(member,snum) &&
+ password_ok(member,password)) {
+ char *name = talloc_strdup(talloc_tos(),
+ member);
+ SAFE_FREE(member_list);
+ return name;
+ }
+
+ DEBUG(10,("validate_group = member = %s\n",
+ member));
+
+ member += strlen(member) + 1;
+ }
+
+ SAFE_FREE(member_list);
+ } else {
+ endgrent();
+ return NULL;
+ }
+ }
+#endif
+ return(NULL);
+}
+
+/****************************************************************************
+ Check for authority to login to a service with a given username/password.
+ Note this is *NOT* used when logging on using sessionsetup_and_X.
+****************************************************************************/
+
+bool authorise_login(int snum, fstring user, DATA_BLOB password,
+ bool *guest)
+{
+ bool ok = False;
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("authorise_login: checking authorisation on "
+ "user=%s pass=%s\n", user,password.data));
+#endif
+
+ *guest = False;
+
+ /* there are several possibilities:
+ 1) login as the given user with given password
+ 2) login as a previously registered username with the given
+ password
+ 3) login as a session list username with the given password
+ 4) login as a previously validated user/password pair
+ 5) login as the "user =" user with given password
+ 6) login as the "user =" user with no password
+ (guest connection)
+ 7) login as guest user with no password
+
+ if the service is guest_only then steps 1 to 5 are skipped
+ */
+
+ /* now check the list of session users */
+ if (!ok) {
+ char *auser;
+ char *user_list = NULL;
+ char *saveptr;
+
+ if ( session_userlist )
+ user_list = SMB_STRDUP(session_userlist);
+ else
+ user_list = SMB_STRDUP("");
+
+ if (!user_list)
+ return(False);
+
+ for (auser = strtok_r(user_list, LIST_SEP, &saveptr);
+ !ok && auser;
+ auser = strtok_r(NULL, LIST_SEP, &saveptr)) {
+ fstring user2;
+ fstrcpy(user2,auser);
+ if (!user_ok(user2,snum))
+ continue;
+
+ if (password_ok(user2,password)) {
+ ok = True;
+ fstrcpy(user,user2);
+ DEBUG(3,("authorise_login: ACCEPTED: session "
+ "list username (%s) and given "
+ "password ok\n", user));
+ }
+ }
+
+ SAFE_FREE(user_list);
+ }
+
+ /* check the user= fields and the given password */
+ if (!ok && lp_username(snum)) {
+ TALLOC_CTX *ctx = talloc_tos();
+ char *auser;
+ char *user_list = talloc_strdup(ctx, lp_username(snum));
+ char *saveptr;
+
+ if (!user_list) {
+ goto check_guest;
+ }
+
+ user_list = talloc_string_sub(ctx,
+ user_list,
+ "%S",
+ lp_servicename(snum));
+
+ if (!user_list) {
+ goto check_guest;
+ }
+
+ for (auser = strtok_r(user_list, LIST_SEP, &saveptr);
+ auser && !ok;
+ auser = strtok_r(NULL, LIST_SEP, &saveptr)) {
+ if (*auser == '@') {
+ auser = validate_group(auser+1,password,snum);
+ if (auser) {
+ ok = True;
+ fstrcpy(user,auser);
+ DEBUG(3,("authorise_login: ACCEPTED: "
+ "group username and given "
+ "password ok (%s)\n", user));
+ }
+ } else {
+ fstring user2;
+ fstrcpy(user2,auser);
+ if (user_ok(user2,snum) &&
+ password_ok(user2,password)) {
+ ok = True;
+ fstrcpy(user,user2);
+ DEBUG(3,("authorise_login: ACCEPTED: "
+ "user list username and "
+ "given password ok (%s)\n",
+ user));
+ }
+ }
+ }
+ }
+
+ check_guest:
+
+ /* check for a normal guest connection */
+ if (!ok && GUEST_OK(snum)) {
+ struct passwd *guest_pw;
+ fstring guestname;
+ fstrcpy(guestname,lp_guestaccount());
+ guest_pw = Get_Pwnam_alloc(talloc_tos(), guestname);
+ if (guest_pw != NULL) {
+ fstrcpy(user,guestname);
+ ok = True;
+ DEBUG(3,("authorise_login: ACCEPTED: guest account "
+ "and guest ok (%s)\n", user));
+ } else {
+ DEBUG(0,("authorise_login: Invalid guest account "
+ "%s??\n",guestname));
+ }
+ TALLOC_FREE(guest_pw);
+ *guest = True;
+ }
+
+ if (ok && !user_ok(user, snum)) {
+ DEBUG(0,("authorise_login: rejected invalid user %s\n",user));
+ ok = False;
+ }
+
+ return(ok);
+}
diff --git a/source3/smbd/pipes.c b/source3/smbd/pipes.c
new file mode 100644
index 0000000000..4fdcdcc557
--- /dev/null
+++ b/source3/smbd/pipes.c
@@ -0,0 +1,320 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ 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/>.
+*/
+/*
+ This file handles reply_ calls on named pipes that the server
+ makes to handle specific protocols
+*/
+
+
+#include "includes.h"
+
+#define PIPE "\\PIPE\\"
+#define PIPELEN strlen(PIPE)
+
+#define MAX_PIPE_NAME_LEN 24
+
+/* PIPE/<name>/<pid>/<pnum> */
+#define PIPEDB_KEY_FORMAT "PIPE/%s/%u/%d"
+
+struct pipe_dbrec {
+ struct server_id pid;
+ int pnum;
+ uid_t uid;
+
+ char name[MAX_PIPE_NAME_LEN];
+ fstring user;
+};
+
+/****************************************************************************
+ Reply to an open and X on a named pipe.
+ This code is basically stolen from reply_open_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+void reply_open_pipe_and_X(connection_struct *conn, struct smb_request *req)
+{
+ const char *fname = NULL;
+ char *pipe_name = NULL;
+ smb_np_struct *p;
+ int size=0,fmode=0,mtime=0,rmode=0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_pull_buf_talloc(ctx, req->inbuf, req->flags2, &pipe_name,
+ smb_buf(req->inbuf), STR_TERMINATE);
+ if (!pipe_name) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+
+ /* If the name doesn't start \PIPE\ then this is directed */
+ /* at a mailslot or something we really, really don't understand, */
+ /* not just something we really don't understand. */
+ if ( strncmp(pipe_name,PIPE,PIPELEN) != 0 ) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+
+ DEBUG(4,("Opening pipe %s.\n", pipe_name));
+
+ /* See if it is one we want to handle. */
+ if (!is_known_pipename(pipe_name)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+
+ /* Strip \PIPE\ off the name. */
+ fname = pipe_name + PIPELEN;
+
+#if 0
+ /*
+ * Hack for NT printers... JRA.
+ */
+ if(should_fail_next_srvsvc_open(fname)) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+#endif
+
+ /* Known pipes arrive with DIR attribs. Remove it so a regular file */
+ /* can be opened and add it in after the open. */
+ DEBUG(3,("Known pipe %s opening.\n",fname));
+
+ p = open_rpc_pipe_p(fname, conn, req->vuid);
+ if (!p) {
+ reply_doserror(req, ERRSRV, ERRnofids);
+ return;
+ }
+
+ /* Prepare the reply */
+ reply_outbuf(req, 15, 0);
+
+ /* Mark the opened file as an existing named pipe in message mode. */
+ SSVAL(req->outbuf,smb_vwv9,2);
+ SSVAL(req->outbuf,smb_vwv10,0xc700);
+
+ if (rmode == 2) {
+ DEBUG(4,("Resetting open result to open from create.\n"));
+ rmode = 1;
+ }
+
+ SSVAL(req->outbuf,smb_vwv2, p->pnum);
+ SSVAL(req->outbuf,smb_vwv3,fmode);
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime);
+ SIVAL(req->outbuf,smb_vwv6,size);
+ SSVAL(req->outbuf,smb_vwv8,rmode);
+ SSVAL(req->outbuf,smb_vwv11,0x0001);
+
+ chain_reply(req);
+ return;
+}
+
+/****************************************************************************
+ Reply to a write on a pipe.
+****************************************************************************/
+
+void reply_pipe_write(struct smb_request *req)
+{
+ smb_np_struct *p = get_rpc_pipe_p(SVAL(req->inbuf,smb_vwv0));
+ size_t numtowrite = SVAL(req->inbuf,smb_vwv1);
+ int nwritten;
+ char *data;
+
+ if (!p) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ if (p->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ data = smb_buf(req->inbuf) + 3;
+
+ if (numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ nwritten = write_to_pipe(p, data, numtowrite);
+ }
+
+ if ((nwritten == 0 && numtowrite != 0) || (nwritten < 0)) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3,("write-IPC pnum=%04x nwritten=%d\n", p->pnum, nwritten));
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a write and X.
+
+ This code is basically stolen from reply_write_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+void reply_pipe_write_and_X(struct smb_request *req)
+{
+ smb_np_struct *p = get_rpc_pipe_p(SVAL(req->inbuf,smb_vwv2));
+ size_t numtowrite = SVAL(req->inbuf,smb_vwv10);
+ int nwritten = -1;
+ int smb_doff = SVAL(req->inbuf, smb_vwv11);
+ bool pipe_start_message_raw =
+ ((SVAL(req->inbuf, smb_vwv7)
+ & (PIPE_START_MESSAGE|PIPE_RAW_MODE))
+ == (PIPE_START_MESSAGE|PIPE_RAW_MODE));
+ char *data;
+
+ if (!p) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ if (p->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ data = smb_base(req->inbuf) + smb_doff;
+
+ if (numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ if(pipe_start_message_raw) {
+ /*
+ * For the start of a message in named pipe byte mode,
+ * the first two bytes are a length-of-pdu field. Ignore
+ * them (we don't trust the client). JRA.
+ */
+ if(numtowrite < 2) {
+ DEBUG(0,("reply_pipe_write_and_X: start of "
+ "message set and not enough data "
+ "sent.(%u)\n",
+ (unsigned int)numtowrite ));
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ data += 2;
+ numtowrite -= 2;
+ }
+ nwritten = write_to_pipe(p, data, numtowrite);
+ }
+
+ if ((nwritten == 0 && numtowrite != 0) || (nwritten < 0)) {
+ reply_unixerror(req, ERRDOS,ERRnoaccess);
+ return;
+ }
+
+ reply_outbuf(req, 6, 0);
+
+ nwritten = (pipe_start_message_raw ? nwritten + 2 : nwritten);
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+
+ DEBUG(3,("writeX-IPC pnum=%04x nwritten=%d\n", p->pnum, nwritten));
+
+ chain_reply(req);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+ This code is basically stolen from reply_read_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+void reply_pipe_read_and_X(struct smb_request *req)
+{
+ smb_np_struct *p = get_rpc_pipe_p(SVAL(req->inbuf,smb_vwv2));
+ int smb_maxcnt = SVAL(req->inbuf,smb_vwv5);
+ int smb_mincnt = SVAL(req->inbuf,smb_vwv6);
+ int nread = -1;
+ char *data;
+ bool unused;
+
+ /* we don't use the offset given to use for pipe reads. This
+ is deliberate, instead we always return the next lump of
+ data on the pipe */
+#if 0
+ uint32 smb_offs = IVAL(req->inbuf,smb_vwv3);
+#endif
+
+ if (!p) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ reply_outbuf(req, 12, smb_maxcnt);
+
+ data = smb_buf(req->outbuf);
+
+ nread = read_from_pipe(p, data, smb_maxcnt, &unused);
+
+ if (nread < 0) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ srv_set_message((char *)req->outbuf, 12, nread, False);
+
+ SSVAL(req->outbuf,smb_vwv5,nread);
+ SSVAL(req->outbuf,smb_vwv6,smb_offset(data,req->outbuf));
+ SSVAL(smb_buf(req->outbuf),-2,nread);
+
+ DEBUG(3,("readX-IPC pnum=%04x min=%d max=%d nread=%d\n",
+ p->pnum, smb_mincnt, smb_maxcnt, nread));
+
+ chain_reply(req);
+}
+
+/****************************************************************************
+ Reply to a close.
+****************************************************************************/
+
+void reply_pipe_close(connection_struct *conn, struct smb_request *req)
+{
+ smb_np_struct *p = get_rpc_pipe_p(SVAL(req->inbuf,smb_vwv0));
+
+ if (!p) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ DEBUG(5,("reply_pipe_close: pnum:%x\n", p->pnum));
+
+ if (!close_rpc_pipe_hnd(p)) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ /* TODO: REMOVE PIPE FROM DB */
+
+ reply_outbuf(req, 0, 0);
+ return;
+}
diff --git a/source3/smbd/posix_acls.c b/source3/smbd/posix_acls.c
new file mode 100644
index 0000000000..7479aea076
--- /dev/null
+++ b/source3/smbd/posix_acls.c
@@ -0,0 +1,4322 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT Security Descriptor / Unix permission conversion.
+ Copyright (C) Jeremy Allison 1994-2000.
+ Copyright (C) Andreas Gruenbacher 2002.
+
+ 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"
+
+extern struct current_user current_user;
+extern const struct generic_mapping file_generic_mapping;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/****************************************************************************
+ Data structures representing the internal ACE format.
+****************************************************************************/
+
+enum ace_owner {UID_ACE, GID_ACE, WORLD_ACE};
+enum ace_attribute {ALLOW_ACE, DENY_ACE}; /* Used for incoming NT ACLS. */
+
+typedef union posix_id {
+ uid_t uid;
+ gid_t gid;
+ int world;
+} posix_id;
+
+typedef struct canon_ace {
+ struct canon_ace *next, *prev;
+ SMB_ACL_TAG_T type;
+ mode_t perms; /* Only use S_I(R|W|X)USR mode bits here. */
+ DOM_SID trustee;
+ enum ace_owner owner_type;
+ enum ace_attribute attr;
+ posix_id unix_ug;
+ bool inherited;
+} canon_ace;
+
+#define ALL_ACE_PERMS (S_IRUSR|S_IWUSR|S_IXUSR)
+
+/*
+ * EA format of user.SAMBA_PAI (Samba_Posix_Acl_Interitance)
+ * attribute on disk.
+ *
+ * | 1 | 1 | 2 | 2 | ....
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ * | vers | flag | num_entries | num_default_entries | ..entries.. | default_entries... |
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ */
+
+#define PAI_VERSION_OFFSET 0
+#define PAI_FLAG_OFFSET 1
+#define PAI_NUM_ENTRIES_OFFSET 2
+#define PAI_NUM_DEFAULT_ENTRIES_OFFSET 4
+#define PAI_ENTRIES_BASE 6
+
+#define PAI_VERSION 1
+#define PAI_ACL_FLAG_PROTECTED 0x1
+#define PAI_ENTRY_LENGTH 5
+
+/*
+ * In memory format of user.SAMBA_PAI attribute.
+ */
+
+struct pai_entry {
+ struct pai_entry *next, *prev;
+ enum ace_owner owner_type;
+ posix_id unix_ug;
+};
+
+struct pai_val {
+ bool pai_protected;
+ unsigned int num_entries;
+ struct pai_entry *entry_list;
+ unsigned int num_def_entries;
+ struct pai_entry *def_entry_list;
+};
+
+/************************************************************************
+ Return a uint32 of the pai_entry principal.
+************************************************************************/
+
+static uint32 get_pai_entry_val(struct pai_entry *paie)
+{
+ switch (paie->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_pai_entry_val: uid = %u\n", (unsigned int)paie->unix_ug.uid ));
+ return (uint32)paie->unix_ug.uid;
+ case GID_ACE:
+ DEBUG(10,("get_pai_entry_val: gid = %u\n", (unsigned int)paie->unix_ug.gid ));
+ return (uint32)paie->unix_ug.gid;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_pai_entry_val: world ace\n"));
+ return (uint32)-1;
+ }
+}
+
+/************************************************************************
+ Return a uint32 of the entry principal.
+************************************************************************/
+
+static uint32 get_entry_val(canon_ace *ace_entry)
+{
+ switch (ace_entry->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_entry_val: uid = %u\n", (unsigned int)ace_entry->unix_ug.uid ));
+ return (uint32)ace_entry->unix_ug.uid;
+ case GID_ACE:
+ DEBUG(10,("get_entry_val: gid = %u\n", (unsigned int)ace_entry->unix_ug.gid ));
+ return (uint32)ace_entry->unix_ug.gid;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_entry_val: world ace\n"));
+ return (uint32)-1;
+ }
+}
+
+/************************************************************************
+ Count the inherited entries.
+************************************************************************/
+
+static unsigned int num_inherited_entries(canon_ace *ace_list)
+{
+ unsigned int num_entries = 0;
+
+ for (; ace_list; ace_list = ace_list->next)
+ if (ace_list->inherited)
+ num_entries++;
+ return num_entries;
+}
+
+/************************************************************************
+ Create the on-disk format. Caller must free.
+************************************************************************/
+
+static char *create_pai_buf(canon_ace *file_ace_list, canon_ace *dir_ace_list, bool pai_protected, size_t *store_size)
+{
+ char *pai_buf = NULL;
+ canon_ace *ace_list = NULL;
+ char *entry_offset = NULL;
+ unsigned int num_entries = 0;
+ unsigned int num_def_entries = 0;
+
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next)
+ if (ace_list->inherited)
+ num_entries++;
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next)
+ if (ace_list->inherited)
+ num_def_entries++;
+
+ DEBUG(10,("create_pai_buf: num_entries = %u, num_def_entries = %u\n", num_entries, num_def_entries ));
+
+ *store_size = PAI_ENTRIES_BASE + ((num_entries + num_def_entries)*PAI_ENTRY_LENGTH);
+
+ pai_buf = (char *)SMB_MALLOC(*store_size);
+ if (!pai_buf) {
+ return NULL;
+ }
+
+ /* Set up the header. */
+ memset(pai_buf, '\0', PAI_ENTRIES_BASE);
+ SCVAL(pai_buf,PAI_VERSION_OFFSET,PAI_VERSION);
+ SCVAL(pai_buf,PAI_FLAG_OFFSET,(pai_protected ? PAI_ACL_FLAG_PROTECTED : 0));
+ SSVAL(pai_buf,PAI_NUM_ENTRIES_OFFSET,num_entries);
+ SSVAL(pai_buf,PAI_NUM_DEFAULT_ENTRIES_OFFSET,num_def_entries);
+
+ entry_offset = pai_buf + PAI_ENTRIES_BASE;
+
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next) {
+ if (ace_list->inherited) {
+ uint8 type_val = (unsigned char)ace_list->owner_type;
+ uint32 entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,type_val);
+ SIVAL(entry_offset,1,entry_val);
+ entry_offset += PAI_ENTRY_LENGTH;
+ }
+ }
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next) {
+ if (ace_list->inherited) {
+ uint8 type_val = (unsigned char)ace_list->owner_type;
+ uint32 entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,type_val);
+ SIVAL(entry_offset,1,entry_val);
+ entry_offset += PAI_ENTRY_LENGTH;
+ }
+ }
+
+ return pai_buf;
+}
+
+/************************************************************************
+ Store the user.SAMBA_PAI attribute on disk.
+************************************************************************/
+
+static void store_inheritance_attributes(files_struct *fsp, canon_ace *file_ace_list,
+ canon_ace *dir_ace_list, bool pai_protected)
+{
+ int ret;
+ size_t store_size;
+ char *pai_buf;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn)))
+ return;
+
+ /*
+ * Don't store if this ACL isn't protected and
+ * none of the entries in it are marked as inherited.
+ */
+
+ if (!pai_protected && num_inherited_entries(file_ace_list) == 0 && num_inherited_entries(dir_ace_list) == 0) {
+ /* Instead just remove the attribute if it exists. */
+ if (fsp->fh->fd != -1)
+ SMB_VFS_FREMOVEXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME);
+ else
+ SMB_VFS_REMOVEXATTR(fsp->conn, fsp->fsp_name, SAMBA_POSIX_INHERITANCE_EA_NAME);
+ return;
+ }
+
+ pai_buf = create_pai_buf(file_ace_list, dir_ace_list, pai_protected, &store_size);
+
+ if (fsp->fh->fd != -1)
+ ret = SMB_VFS_FSETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, store_size, 0);
+ else
+ ret = SMB_VFS_SETXATTR(fsp->conn,fsp->fsp_name, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, store_size, 0);
+
+ SAFE_FREE(pai_buf);
+
+ DEBUG(10,("store_inheritance_attribute:%s for file %s\n", pai_protected ? " (protected)" : "", fsp->fsp_name));
+ if (ret == -1 && !no_acl_syscall_error(errno))
+ DEBUG(1,("store_inheritance_attribute: Error %s\n", strerror(errno) ));
+}
+
+/************************************************************************
+ Delete the in memory inheritance info.
+************************************************************************/
+
+static void free_inherited_info(struct pai_val *pal)
+{
+ if (pal) {
+ struct pai_entry *paie, *paie_next;
+ for (paie = pal->entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ SAFE_FREE(paie);
+ }
+ for (paie = pal->def_entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ SAFE_FREE(paie);
+ }
+ SAFE_FREE(pal);
+ }
+}
+
+/************************************************************************
+ Was this ACL protected ?
+************************************************************************/
+
+static bool get_protected_flag(struct pai_val *pal)
+{
+ if (!pal)
+ return False;
+ return pal->pai_protected;
+}
+
+/************************************************************************
+ Was this ACE inherited ?
+************************************************************************/
+
+static bool get_inherited_flag(struct pai_val *pal, canon_ace *ace_entry, bool default_ace)
+{
+ struct pai_entry *paie;
+
+ if (!pal)
+ return False;
+
+ /* If the entry exists it is inherited. */
+ for (paie = (default_ace ? pal->def_entry_list : pal->entry_list); paie; paie = paie->next) {
+ if (ace_entry->owner_type == paie->owner_type &&
+ get_entry_val(ace_entry) == get_pai_entry_val(paie))
+ return True;
+ }
+ return False;
+}
+
+/************************************************************************
+ Ensure an attribute just read is valid.
+************************************************************************/
+
+static bool check_pai_ok(char *pai_buf, size_t pai_buf_data_size)
+{
+ uint16 num_entries;
+ uint16 num_def_entries;
+
+ if (pai_buf_data_size < PAI_ENTRIES_BASE) {
+ /* Corrupted - too small. */
+ return False;
+ }
+
+ if (CVAL(pai_buf,PAI_VERSION_OFFSET) != PAI_VERSION)
+ return False;
+
+ num_entries = SVAL(pai_buf,PAI_NUM_ENTRIES_OFFSET);
+ num_def_entries = SVAL(pai_buf,PAI_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ /* Check the entry lists match. */
+ /* Each entry is 5 bytes (type plus 4 bytes of uid or gid). */
+
+ if (((num_entries + num_def_entries)*PAI_ENTRY_LENGTH) + PAI_ENTRIES_BASE != pai_buf_data_size)
+ return False;
+
+ return True;
+}
+
+
+/************************************************************************
+ Convert to in-memory format.
+************************************************************************/
+
+static struct pai_val *create_pai_val(char *buf, size_t size)
+{
+ char *entry_offset;
+ struct pai_val *paiv = NULL;
+ int i;
+
+ if (!check_pai_ok(buf, size))
+ return NULL;
+
+ paiv = SMB_MALLOC_P(struct pai_val);
+ if (!paiv)
+ return NULL;
+
+ memset(paiv, '\0', sizeof(struct pai_val));
+
+ paiv->pai_protected = (CVAL(buf,PAI_FLAG_OFFSET) == PAI_ACL_FLAG_PROTECTED);
+
+ paiv->num_entries = SVAL(buf,PAI_NUM_ENTRIES_OFFSET);
+ paiv->num_def_entries = SVAL(buf,PAI_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ entry_offset = buf + PAI_ENTRIES_BASE;
+
+ DEBUG(10,("create_pai_val:%s num_entries = %u, num_def_entries = %u\n",
+ paiv->pai_protected ? " (pai_protected)" : "", paiv->num_entries, paiv->num_def_entries ));
+
+ for (i = 0; i < paiv->num_entries; i++) {
+ struct pai_entry *paie;
+
+ paie = SMB_MALLOC_P(struct pai_entry);
+ if (!paie) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ paie->owner_type = (enum ace_owner)CVAL(entry_offset,0);
+ switch( paie->owner_type) {
+ case UID_ACE:
+ paie->unix_ug.uid = (uid_t)IVAL(entry_offset,1);
+ DEBUG(10,("create_pai_val: uid = %u\n", (unsigned int)paie->unix_ug.uid ));
+ break;
+ case GID_ACE:
+ paie->unix_ug.gid = (gid_t)IVAL(entry_offset,1);
+ DEBUG(10,("create_pai_val: gid = %u\n", (unsigned int)paie->unix_ug.gid ));
+ break;
+ case WORLD_ACE:
+ paie->unix_ug.world = -1;
+ DEBUG(10,("create_pai_val: world ace\n"));
+ break;
+ default:
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset += PAI_ENTRY_LENGTH;
+ DLIST_ADD(paiv->entry_list, paie);
+ }
+
+ for (i = 0; i < paiv->num_def_entries; i++) {
+ struct pai_entry *paie;
+
+ paie = SMB_MALLOC_P(struct pai_entry);
+ if (!paie) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ paie->owner_type = (enum ace_owner)CVAL(entry_offset,0);
+ switch( paie->owner_type) {
+ case UID_ACE:
+ paie->unix_ug.uid = (uid_t)IVAL(entry_offset,1);
+ DEBUG(10,("create_pai_val: (def) uid = %u\n", (unsigned int)paie->unix_ug.uid ));
+ break;
+ case GID_ACE:
+ paie->unix_ug.gid = (gid_t)IVAL(entry_offset,1);
+ DEBUG(10,("create_pai_val: (def) gid = %u\n", (unsigned int)paie->unix_ug.gid ));
+ break;
+ case WORLD_ACE:
+ paie->unix_ug.world = -1;
+ DEBUG(10,("create_pai_val: (def) world ace\n"));
+ break;
+ default:
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset += PAI_ENTRY_LENGTH;
+ DLIST_ADD(paiv->def_entry_list, paie);
+ }
+
+ return paiv;
+}
+
+/************************************************************************
+ Load the user.SAMBA_PAI attribute.
+************************************************************************/
+
+static struct pai_val *fload_inherited_info(files_struct *fsp)
+{
+ char *pai_buf;
+ size_t pai_buf_size = 1024;
+ struct pai_val *paiv = NULL;
+ ssize_t ret;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn)))
+ return NULL;
+
+ if ((pai_buf = (char *)SMB_MALLOC(pai_buf_size)) == NULL)
+ return NULL;
+
+ do {
+ if (fsp->fh->fd != -1)
+ ret = SMB_VFS_FGETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, pai_buf_size);
+ else
+ ret = SMB_VFS_GETXATTR(fsp->conn,fsp->fsp_name,SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, pai_buf_size);
+
+ if (ret == -1) {
+ if (errno != ERANGE) {
+ break;
+ }
+ /* Buffer too small - enlarge it. */
+ pai_buf_size *= 2;
+ SAFE_FREE(pai_buf);
+ if (pai_buf_size > 1024*1024) {
+ return NULL; /* Limit malloc to 1mb. */
+ }
+ if ((pai_buf = (char *)SMB_MALLOC(pai_buf_size)) == NULL)
+ return NULL;
+ }
+ } while (ret == -1);
+
+ DEBUG(10,("load_inherited_info: ret = %lu for file %s\n", (unsigned long)ret, fsp->fsp_name));
+
+ if (ret == -1) {
+ /* No attribute or not supported. */
+#if defined(ENOATTR)
+ if (errno != ENOATTR)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#else
+ if (errno != ENOSYS)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#endif
+ SAFE_FREE(pai_buf);
+ return NULL;
+ }
+
+ paiv = create_pai_val(pai_buf, ret);
+
+ if (paiv && paiv->pai_protected)
+ DEBUG(10,("load_inherited_info: ACL is protected for file %s\n", fsp->fsp_name));
+
+ SAFE_FREE(pai_buf);
+ return paiv;
+}
+
+/************************************************************************
+ Load the user.SAMBA_PAI attribute.
+************************************************************************/
+
+static struct pai_val *load_inherited_info(const struct connection_struct *conn,
+ const char *fname)
+{
+ char *pai_buf;
+ size_t pai_buf_size = 1024;
+ struct pai_val *paiv = NULL;
+ ssize_t ret;
+
+ if (!lp_map_acl_inherit(SNUM(conn))) {
+ return NULL;
+ }
+
+ if ((pai_buf = (char *)SMB_MALLOC(pai_buf_size)) == NULL) {
+ return NULL;
+ }
+
+ do {
+ ret = SMB_VFS_GETXATTR(conn, fname,
+ SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, pai_buf_size);
+
+ if (ret == -1) {
+ if (errno != ERANGE) {
+ break;
+ }
+ /* Buffer too small - enlarge it. */
+ pai_buf_size *= 2;
+ SAFE_FREE(pai_buf);
+ if (pai_buf_size > 1024*1024) {
+ return NULL; /* Limit malloc to 1mb. */
+ }
+ if ((pai_buf = (char *)SMB_MALLOC(pai_buf_size)) == NULL)
+ return NULL;
+ }
+ } while (ret == -1);
+
+ DEBUG(10,("load_inherited_info: ret = %lu for file %s\n", (unsigned long)ret, fname));
+
+ if (ret == -1) {
+ /* No attribute or not supported. */
+#if defined(ENOATTR)
+ if (errno != ENOATTR)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#else
+ if (errno != ENOSYS)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#endif
+ SAFE_FREE(pai_buf);
+ return NULL;
+ }
+
+ paiv = create_pai_val(pai_buf, ret);
+
+ if (paiv && paiv->pai_protected) {
+ DEBUG(10,("load_inherited_info: ACL is protected for file %s\n", fname));
+ }
+
+ SAFE_FREE(pai_buf);
+ return paiv;
+}
+
+/****************************************************************************
+ Functions to manipulate the internal ACE format.
+****************************************************************************/
+
+/****************************************************************************
+ Count a linked list of canonical ACE entries.
+****************************************************************************/
+
+static size_t count_canon_ace_list( canon_ace *list_head )
+{
+ size_t count = 0;
+ canon_ace *ace;
+
+ for (ace = list_head; ace; ace = ace->next)
+ count++;
+
+ return count;
+}
+
+/****************************************************************************
+ Free a linked list of canonical ACE entries.
+****************************************************************************/
+
+static void free_canon_ace_list( canon_ace *list_head )
+{
+ canon_ace *list, *next;
+
+ for (list = list_head; list; list = next) {
+ next = list->next;
+ DLIST_REMOVE(list_head, list);
+ SAFE_FREE(list);
+ }
+}
+
+/****************************************************************************
+ Function to duplicate a canon_ace entry.
+****************************************************************************/
+
+static canon_ace *dup_canon_ace( canon_ace *src_ace)
+{
+ canon_ace *dst_ace = SMB_MALLOC_P(canon_ace);
+
+ if (dst_ace == NULL)
+ return NULL;
+
+ *dst_ace = *src_ace;
+ dst_ace->prev = dst_ace->next = NULL;
+ return dst_ace;
+}
+
+/****************************************************************************
+ Print out a canon ace.
+****************************************************************************/
+
+static void print_canon_ace(canon_ace *pace, int num)
+{
+ dbgtext( "canon_ace index %d. Type = %s ", num, pace->attr == ALLOW_ACE ? "allow" : "deny" );
+ dbgtext( "SID = %s ", sid_string_dbg(&pace->trustee));
+ if (pace->owner_type == UID_ACE) {
+ const char *u_name = uidtoname(pace->unix_ug.uid);
+ dbgtext( "uid %u (%s) ", (unsigned int)pace->unix_ug.uid, u_name );
+ } else if (pace->owner_type == GID_ACE) {
+ char *g_name = gidtoname(pace->unix_ug.gid);
+ dbgtext( "gid %u (%s) ", (unsigned int)pace->unix_ug.gid, g_name );
+ } else
+ dbgtext( "other ");
+ switch (pace->type) {
+ case SMB_ACL_USER:
+ dbgtext( "SMB_ACL_USER ");
+ break;
+ case SMB_ACL_USER_OBJ:
+ dbgtext( "SMB_ACL_USER_OBJ ");
+ break;
+ case SMB_ACL_GROUP:
+ dbgtext( "SMB_ACL_GROUP ");
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ dbgtext( "SMB_ACL_GROUP_OBJ ");
+ break;
+ case SMB_ACL_OTHER:
+ dbgtext( "SMB_ACL_OTHER ");
+ break;
+ default:
+ dbgtext( "MASK " );
+ break;
+ }
+ if (pace->inherited)
+ dbgtext( "(inherited) ");
+ dbgtext( "perms ");
+ dbgtext( "%c", pace->perms & S_IRUSR ? 'r' : '-');
+ dbgtext( "%c", pace->perms & S_IWUSR ? 'w' : '-');
+ dbgtext( "%c\n", pace->perms & S_IXUSR ? 'x' : '-');
+}
+
+/****************************************************************************
+ Print out a canon ace list.
+****************************************************************************/
+
+static void print_canon_ace_list(const char *name, canon_ace *ace_list)
+{
+ int count = 0;
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext( "print_canon_ace_list: %s\n", name );
+ for (;ace_list; ace_list = ace_list->next, count++)
+ print_canon_ace(ace_list, count );
+ }
+}
+
+/****************************************************************************
+ Map POSIX ACL perms to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+static mode_t convert_permset_to_mode_t(connection_struct *conn, SMB_ACL_PERMSET_T permset)
+{
+ mode_t ret = 0;
+
+ ret |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_READ) ? S_IRUSR : 0);
+ ret |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_WRITE) ? S_IWUSR : 0);
+ ret |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_EXECUTE) ? S_IXUSR : 0);
+
+ return ret;
+}
+
+/****************************************************************************
+ Map generic UNIX permissions to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+static mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask)
+{
+ mode_t ret = 0;
+
+ if (mode & r_mask)
+ ret |= S_IRUSR;
+ if (mode & w_mask)
+ ret |= S_IWUSR;
+ if (mode & x_mask)
+ ret |= S_IXUSR;
+
+ return ret;
+}
+
+/****************************************************************************
+ Map canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits) to
+ an SMB_ACL_PERMSET_T.
+****************************************************************************/
+
+static int map_acl_perms_to_permset(connection_struct *conn, mode_t mode, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (SMB_VFS_SYS_ACL_CLEAR_PERMS(conn, *p_permset) == -1)
+ return -1;
+ if (mode & S_IRUSR) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_READ) == -1)
+ return -1;
+ }
+ if (mode & S_IWUSR) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_WRITE) == -1)
+ return -1;
+ }
+ if (mode & S_IXUSR) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_EXECUTE) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Function to create owner and group SIDs from a SMB_STRUCT_STAT.
+****************************************************************************/
+
+static void create_file_sids(const SMB_STRUCT_STAT *psbuf, DOM_SID *powner_sid, DOM_SID *pgroup_sid)
+{
+ uid_to_sid( powner_sid, psbuf->st_uid );
+ gid_to_sid( pgroup_sid, psbuf->st_gid );
+}
+
+/****************************************************************************
+ Is the identity in two ACEs equal ? Check both SID and uid/gid.
+****************************************************************************/
+
+static bool identity_in_ace_equal(canon_ace *ace1, canon_ace *ace2)
+{
+ if (sid_equal(&ace1->trustee, &ace2->trustee)) {
+ return True;
+ }
+ if (ace1->owner_type == ace2->owner_type) {
+ if (ace1->owner_type == UID_ACE &&
+ ace1->unix_ug.uid == ace2->unix_ug.uid) {
+ return True;
+ } else if (ace1->owner_type == GID_ACE &&
+ ace1->unix_ug.gid == ace2->unix_ug.gid) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Merge aces with a common sid - if both are allow or deny, OR the permissions together and
+ delete the second one. If the first is deny, mask the permissions off and delete the allow
+ if the permissions become zero, delete the deny if the permissions are non zero.
+****************************************************************************/
+
+static void merge_aces( canon_ace **pp_list_head )
+{
+ canon_ace *list_head = *pp_list_head;
+ canon_ace *curr_ace_outer;
+ canon_ace *curr_ace_outer_next;
+
+ /*
+ * First, merge allow entries with identical SIDs, and deny entries
+ * with identical SIDs.
+ */
+
+ for (curr_ace_outer = list_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ if (identity_in_ace_equal(curr_ace, curr_ace_outer) &&
+ (curr_ace->attr == curr_ace_outer->attr)) {
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Merging ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ /* Merge two allow or two deny ACE's. */
+
+ curr_ace_outer->perms |= curr_ace->perms;
+ DLIST_REMOVE(list_head, curr_ace);
+ SAFE_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+ }
+ }
+ }
+
+ /*
+ * Now go through and mask off allow permissions with deny permissions.
+ * We can delete either the allow or deny here as we know that each SID
+ * appears only once in the list.
+ */
+
+ for (curr_ace_outer = list_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ /*
+ * Subtract ACE's with different entries. Due to the ordering constraints
+ * we've put on the ACL, we know the deny must be the first one.
+ */
+
+ if (identity_in_ace_equal(curr_ace, curr_ace_outer) &&
+ (curr_ace_outer->attr == DENY_ACE) && (curr_ace->attr == ALLOW_ACE)) {
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Masking ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ curr_ace->perms &= ~curr_ace_outer->perms;
+
+ if (curr_ace->perms == 0) {
+
+ /*
+ * The deny overrides the allow. Remove the allow.
+ */
+
+ DLIST_REMOVE(list_head, curr_ace);
+ SAFE_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+
+ } else {
+
+ /*
+ * Even after removing permissions, there
+ * are still allow permissions - delete the deny.
+ * It is safe to delete the deny here,
+ * as we are guarenteed by the deny first
+ * ordering that all the deny entries for
+ * this SID have already been merged into one
+ * before we can get to an allow ace.
+ */
+
+ DLIST_REMOVE(list_head, curr_ace_outer);
+ SAFE_FREE(curr_ace_outer);
+ break;
+ }
+ }
+
+ } /* end for curr_ace */
+ } /* end for curr_ace_outer */
+
+ /* We may have modified the list. */
+
+ *pp_list_head = list_head;
+}
+
+/****************************************************************************
+ Check if we need to return NT4.x compatible ACL entries.
+****************************************************************************/
+
+static bool nt4_compatible_acls(void)
+{
+ int compat = lp_acl_compatibility();
+
+ if (compat == ACL_COMPAT_AUTO) {
+ enum remote_arch_types ra_type = get_remote_arch();
+
+ /* Automatically adapt to client */
+ return (ra_type <= RA_WINNT);
+ } else
+ return (compat == ACL_COMPAT_WINNT);
+}
+
+
+/****************************************************************************
+ Map canon_ace perms to permission bits NT.
+ The attr element is not used here - we only process deny entries on set,
+ not get. Deny entries are implicit on get with ace->perms = 0.
+****************************************************************************/
+
+static SEC_ACCESS map_canon_ace_perms(int snum,
+ enum security_ace_type *pacl_type,
+ mode_t perms,
+ bool directory_ace)
+{
+ SEC_ACCESS sa;
+ uint32 nt_mask = 0;
+
+ *pacl_type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+
+ if (lp_acl_map_full_control(snum) && ((perms & ALL_ACE_PERMS) == ALL_ACE_PERMS)) {
+ if (directory_ace) {
+ nt_mask = UNIX_DIRECTORY_ACCESS_RWX;
+ } else {
+ nt_mask = (UNIX_ACCESS_RWX & ~DELETE_ACCESS);
+ }
+ } else if ((perms & ALL_ACE_PERMS) == (mode_t)0) {
+ /*
+ * Windows NT refuses to display ACEs with no permissions in them (but
+ * they are perfectly legal with Windows 2000). If the ACE has empty
+ * permissions we cannot use 0, so we use the otherwise unused
+ * WRITE_OWNER permission, which we ignore when we set an ACL.
+ * We abstract this into a #define of UNIX_ACCESS_NONE to allow this
+ * to be changed in the future.
+ */
+
+ if (nt4_compatible_acls())
+ nt_mask = UNIX_ACCESS_NONE;
+ else
+ nt_mask = 0;
+ } else {
+ if (directory_ace) {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_DIRECTORY_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_DIRECTORY_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_DIRECTORY_ACCESS_X : 0 );
+ } else {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_ACCESS_X : 0 );
+ }
+ }
+
+ DEBUG(10,("map_canon_ace_perms: Mapped (UNIX) %x to (NT) %x\n",
+ (unsigned int)perms, (unsigned int)nt_mask ));
+
+ init_sec_access(&sa,nt_mask);
+ return sa;
+}
+
+/****************************************************************************
+ Map NT perms to a UNIX mode_t.
+****************************************************************************/
+
+#define FILE_SPECIFIC_READ_BITS (FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES)
+#define FILE_SPECIFIC_WRITE_BITS (FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA|FILE_WRITE_ATTRIBUTES)
+#define FILE_SPECIFIC_EXECUTE_BITS (FILE_EXECUTE)
+
+static mode_t map_nt_perms( uint32 *mask, int type)
+{
+ mode_t mode = 0;
+
+ switch(type) {
+ case S_IRUSR:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRUSR|S_IWUSR|S_IXUSR;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRUSR : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWUSR : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXUSR : 0;
+ }
+ break;
+ case S_IRGRP:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRGRP|S_IWGRP|S_IXGRP;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRGRP : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWGRP : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXGRP : 0;
+ }
+ break;
+ case S_IROTH:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IROTH|S_IWOTH|S_IXOTH;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IROTH : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWOTH : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXOTH : 0;
+ }
+ break;
+ }
+
+ return mode;
+}
+
+/****************************************************************************
+ Unpack a SEC_DESC into a UNIX owner and group.
+****************************************************************************/
+
+NTSTATUS unpack_nt_owners(int snum, uid_t *puser, gid_t *pgrp, uint32 security_info_sent, SEC_DESC *psd)
+{
+ DOM_SID owner_sid;
+ DOM_SID grp_sid;
+
+ *puser = (uid_t)-1;
+ *pgrp = (gid_t)-1;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_nt_owners: no security info sent !\n"));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Validate the owner and group SID's.
+ */
+
+ memset(&owner_sid, '\0', sizeof(owner_sid));
+ memset(&grp_sid, '\0', sizeof(grp_sid));
+
+ DEBUG(5,("unpack_nt_owners: validating owner_sids.\n"));
+
+ /*
+ * Don't immediately fail if the owner sid cannot be validated.
+ * This may be a group chown only set.
+ */
+
+ if (security_info_sent & OWNER_SECURITY_INFORMATION) {
+ sid_copy(&owner_sid, psd->owner_sid);
+ if (!sid_to_uid(&owner_sid, puser)) {
+ if (lp_force_unknown_acl_user(snum)) {
+ /* this allows take ownership to work
+ * reasonably */
+ *puser = current_user.ut.uid;
+ } else {
+ DEBUG(3,("unpack_nt_owners: unable to validate"
+ " owner sid for %s\n",
+ sid_string_dbg(&owner_sid)));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: owner sid mapped to uid %u\n",
+ (unsigned int)*puser ));
+ }
+
+ /*
+ * Don't immediately fail if the group sid cannot be validated.
+ * This may be an owner chown only set.
+ */
+
+ if (security_info_sent & GROUP_SECURITY_INFORMATION) {
+ sid_copy(&grp_sid, psd->group_sid);
+ if (!sid_to_gid( &grp_sid, pgrp)) {
+ if (lp_force_unknown_acl_user(snum)) {
+ /* this allows take group ownership to work
+ * reasonably */
+ *pgrp = current_user.ut.gid;
+ } else {
+ DEBUG(3,("unpack_nt_owners: unable to validate"
+ " group sid.\n"));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: group sid mapped to gid %u\n",
+ (unsigned int)*pgrp));
+ }
+
+ DEBUG(5,("unpack_nt_owners: owner_sids validated.\n"));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Ensure the enforced permissions for this share apply.
+****************************************************************************/
+
+static void apply_default_perms(const struct share_params *params,
+ const bool is_directory, canon_ace *pace,
+ mode_t type)
+{
+ mode_t and_bits = (mode_t)0;
+ mode_t or_bits = (mode_t)0;
+
+ /* Get the initial bits to apply. */
+
+ if (is_directory) {
+ and_bits = lp_dir_security_mask(params->service);
+ or_bits = lp_force_dir_security_mode(params->service);
+ } else {
+ and_bits = lp_security_mask(params->service);
+ or_bits = lp_force_security_mode(params->service);
+ }
+
+ /* Now bounce them into the S_USR space. */
+ switch(type) {
+ case S_IRUSR:
+ /* Ensure owner has read access. */
+ pace->perms |= S_IRUSR;
+ if (is_directory)
+ pace->perms |= (S_IWUSR|S_IXUSR);
+ and_bits = unix_perms_to_acl_perms(and_bits, S_IRUSR, S_IWUSR, S_IXUSR);
+ or_bits = unix_perms_to_acl_perms(or_bits, S_IRUSR, S_IWUSR, S_IXUSR);
+ break;
+ case S_IRGRP:
+ and_bits = unix_perms_to_acl_perms(and_bits, S_IRGRP, S_IWGRP, S_IXGRP);
+ or_bits = unix_perms_to_acl_perms(or_bits, S_IRGRP, S_IWGRP, S_IXGRP);
+ break;
+ case S_IROTH:
+ and_bits = unix_perms_to_acl_perms(and_bits, S_IROTH, S_IWOTH, S_IXOTH);
+ or_bits = unix_perms_to_acl_perms(or_bits, S_IROTH, S_IWOTH, S_IXOTH);
+ break;
+ }
+
+ pace->perms = ((pace->perms & and_bits)|or_bits);
+}
+
+/****************************************************************************
+ Check if a given uid/SID is in a group gid/SID. This is probably very
+ expensive and will need optimisation. A *lot* of optimisation :-). JRA.
+****************************************************************************/
+
+static bool uid_entry_in_group( canon_ace *uid_ace, canon_ace *group_ace )
+{
+ const char *u_name = NULL;
+
+ /* "Everyone" always matches every uid. */
+
+ if (sid_equal(&group_ace->trustee, &global_sid_World))
+ return True;
+
+ /* Assume that the current user is in the current group (force group) */
+
+ if (uid_ace->unix_ug.uid == current_user.ut.uid && group_ace->unix_ug.gid == current_user.ut.gid)
+ return True;
+
+ /* u_name talloc'ed off tos. */
+ u_name = uidtoname(uid_ace->unix_ug.uid);
+ if (!u_name) {
+ return False;
+ }
+ return user_in_group_sid(u_name, &group_ace->trustee);
+}
+
+/****************************************************************************
+ A well formed POSIX file or default ACL has at least 3 entries, a
+ SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ.
+ In addition, the owner must always have at least read access.
+ When using this call on get_acl, the pst struct is valid and contains
+ the mode of the file. When using this call on set_acl, the pst struct has
+ been modified to have a mode containing the default for this file or directory
+ type.
+****************************************************************************/
+
+static bool ensure_canon_entry_valid(canon_ace **pp_ace,
+ const struct share_params *params,
+ const bool is_directory,
+ const DOM_SID *pfile_owner_sid,
+ const DOM_SID *pfile_grp_sid,
+ const SMB_STRUCT_STAT *pst,
+ bool setting_acl)
+{
+ canon_ace *pace;
+ bool got_user = False;
+ bool got_grp = False;
+ bool got_other = False;
+ canon_ace *pace_other = NULL;
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ if (pace->type == SMB_ACL_USER_OBJ) {
+
+ if (setting_acl)
+ apply_default_perms(params, is_directory, pace, S_IRUSR);
+ got_user = True;
+
+ } else if (pace->type == SMB_ACL_GROUP_OBJ) {
+
+ /*
+ * Ensure create mask/force create mode is respected on set.
+ */
+
+ if (setting_acl)
+ apply_default_perms(params, is_directory, pace, S_IRGRP);
+ got_grp = True;
+
+ } else if (pace->type == SMB_ACL_OTHER) {
+
+ /*
+ * Ensure create mask/force create mode is respected on set.
+ */
+
+ if (setting_acl)
+ apply_default_perms(params, is_directory, pace, S_IROTH);
+ got_other = True;
+ pace_other = pace;
+ }
+ }
+
+ if (!got_user) {
+ if ((pace = SMB_MALLOC_P(canon_ace)) == NULL) {
+ DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER_OBJ;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.uid = pst->st_uid;
+ pace->trustee = *pfile_owner_sid;
+ pace->attr = ALLOW_ACE;
+
+ if (setting_acl) {
+ /* See if the owning user is in any of the other groups in
+ the ACE. If so, OR in the permissions from that group. */
+
+ bool group_matched = False;
+ canon_ace *pace_iter;
+
+ for (pace_iter = *pp_ace; pace_iter; pace_iter = pace_iter->next) {
+ if (pace_iter->type == SMB_ACL_GROUP_OBJ || pace_iter->type == SMB_ACL_GROUP) {
+ if (uid_entry_in_group(pace, pace_iter)) {
+ pace->perms |= pace_iter->perms;
+ group_matched = True;
+ }
+ }
+ }
+
+ /* If we only got an "everyone" perm, just use that. */
+ if (!group_matched) {
+ if (got_other)
+ pace->perms = pace_other->perms;
+ else
+ pace->perms = 0;
+ }
+
+ apply_default_perms(params, is_directory, pace, S_IRUSR);
+ } else {
+ pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ }
+
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_grp) {
+ if ((pace = SMB_MALLOC_P(canon_ace)) == NULL) {
+ DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP_OBJ;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.uid = pst->st_gid;
+ pace->trustee = *pfile_grp_sid;
+ pace->attr = ALLOW_ACE;
+ if (setting_acl) {
+ /* If we only got an "everyone" perm, just use that. */
+ if (got_other)
+ pace->perms = pace_other->perms;
+ else
+ pace->perms = 0;
+ apply_default_perms(params, is_directory, pace, S_IRGRP);
+ } else {
+ pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ }
+
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_other) {
+ if ((pace = SMB_MALLOC_P(canon_ace)) == NULL) {
+ DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_OTHER;
+ pace->owner_type = WORLD_ACE;
+ pace->unix_ug.world = -1;
+ pace->trustee = global_sid_World;
+ pace->attr = ALLOW_ACE;
+ if (setting_acl) {
+ pace->perms = 0;
+ apply_default_perms(params, is_directory, pace, S_IROTH);
+ } else
+ pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IROTH, S_IWOTH, S_IXOTH);
+
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Check if a POSIX ACL has the required SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries.
+ If it does not have them, check if there are any entries where the trustee is the
+ file owner or the owning group, and map these to SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ.
+****************************************************************************/
+
+static void check_owning_objs(canon_ace *ace, DOM_SID *pfile_owner_sid, DOM_SID *pfile_grp_sid)
+{
+ bool got_user_obj, got_group_obj;
+ canon_ace *current_ace;
+ int i, entries;
+
+ entries = count_canon_ace_list(ace);
+ got_user_obj = False;
+ got_group_obj = False;
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (current_ace->type == SMB_ACL_USER_OBJ)
+ got_user_obj = True;
+ else if (current_ace->type == SMB_ACL_GROUP_OBJ)
+ got_group_obj = True;
+ }
+ if (got_user_obj && got_group_obj) {
+ DEBUG(10,("check_owning_objs: ACL had owning user/group entries.\n"));
+ return;
+ }
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (!got_user_obj && current_ace->owner_type == UID_ACE &&
+ sid_equal(&current_ace->trustee, pfile_owner_sid)) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ got_user_obj = True;
+ }
+ if (!got_group_obj && current_ace->owner_type == GID_ACE &&
+ sid_equal(&current_ace->trustee, pfile_grp_sid)) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ got_group_obj = True;
+ }
+ }
+ if (!got_user_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owner entry.\n"));
+ if (!got_group_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owning group entry.\n"));
+}
+
+/****************************************************************************
+ Unpack a SEC_DESC into two canonical ace lists.
+****************************************************************************/
+
+static bool create_canon_ace_lists(files_struct *fsp, SMB_STRUCT_STAT *pst,
+ DOM_SID *pfile_owner_sid,
+ DOM_SID *pfile_grp_sid,
+ canon_ace **ppfile_ace, canon_ace **ppdir_ace,
+ SEC_ACL *dacl)
+{
+ bool all_aces_are_inherit_only = (fsp->is_directory ? True : False);
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ canon_ace *current_ace = NULL;
+ bool got_dir_allow = False;
+ bool got_file_allow = False;
+ int i, j;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ /*
+ * Convert the incoming ACL into a more regular form.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ SEC_ACE *psa = &dacl->aces[i];
+
+ if((psa->type != SEC_ACE_TYPE_ACCESS_ALLOWED) && (psa->type != SEC_ACE_TYPE_ACCESS_DENIED)) {
+ DEBUG(3,("create_canon_ace_lists: unable to set anything but an ALLOW or DENY ACE.\n"));
+ return False;
+ }
+
+ if (nt4_compatible_acls()) {
+ /*
+ * The security mask may be UNIX_ACCESS_NONE which should map into
+ * no permissions (we overload the WRITE_OWNER bit for this) or it
+ * should be one of the ALL/EXECUTE/READ/WRITE bits. Arrange for this
+ * to be so. Any other bits override the UNIX_ACCESS_NONE bit.
+ */
+
+ /*
+ * Convert GENERIC bits to specific bits.
+ */
+
+ se_map_generic(&psa->access_mask, &file_generic_mapping);
+
+ psa->access_mask &= (UNIX_ACCESS_NONE|FILE_ALL_ACCESS);
+
+ if(psa->access_mask != UNIX_ACCESS_NONE)
+ psa->access_mask &= ~UNIX_ACCESS_NONE;
+ }
+ }
+
+ /*
+ * Deal with the fact that NT 4.x re-writes the canonical format
+ * that we return for default ACLs. If a directory ACE is identical
+ * to a inherited directory ACE then NT changes the bits so that the
+ * first ACE is set to OI|IO and the second ACE for this SID is set
+ * to CI. We need to repair this. JRA.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ SEC_ACE *psa1 = &dacl->aces[i];
+
+ for (j = i + 1; j < dacl->num_aces; j++) {
+ SEC_ACE *psa2 = &dacl->aces[j];
+
+ if (psa1->access_mask != psa2->access_mask)
+ continue;
+
+ if (!sid_equal(&psa1->trustee, &psa2->trustee))
+ continue;
+
+ /*
+ * Ok - permission bits and SIDs are equal.
+ * Check if flags were re-written.
+ */
+
+ if (psa1->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa1->flags |= (psa2->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa2->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ } else if (psa2->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa2->flags |= (psa1->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa1->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ }
+ }
+ }
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ SEC_ACE *psa = &dacl->aces[i];
+
+ /*
+ * Create a cannon_ace entry representing this NT DACL ACE.
+ */
+
+ if ((current_ace = SMB_MALLOC_P(canon_ace)) == NULL) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0,("create_canon_ace_lists: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(current_ace);
+
+ sid_copy(&current_ace->trustee, &psa->trustee);
+
+ /*
+ * Try and work out if the SID is a user or group
+ * as we need to flag these differently for POSIX.
+ * Note what kind of a POSIX ACL this should map to.
+ */
+
+ if( sid_equal(&current_ace->trustee, &global_sid_World)) {
+ current_ace->owner_type = WORLD_ACE;
+ current_ace->unix_ug.world = -1;
+ current_ace->type = SMB_ACL_OTHER;
+ } else if (sid_equal(&current_ace->trustee, &global_sid_Creator_Owner)) {
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.uid = pst->st_uid;
+ current_ace->type = SMB_ACL_USER_OBJ;
+
+ /*
+ * The Creator Owner entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ *INHERIT_ONLY, though.
+ */
+
+ if (nt4_compatible_acls())
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+ } else if (sid_equal(&current_ace->trustee, &global_sid_Creator_Group)) {
+ current_ace->owner_type = GID_ACE;
+ current_ace->unix_ug.gid = pst->st_gid;
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+
+ /*
+ * The Creator Group entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ *INHERIT_ONLY, though.
+ */
+ if (nt4_compatible_acls())
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+
+ } else if (sid_to_uid( &current_ace->trustee, &current_ace->unix_ug.uid)) {
+ current_ace->owner_type = UID_ACE;
+ /* If it's the owning user, this is a user_obj, not
+ * a user. */
+ if (current_ace->unix_ug.uid == pst->st_uid) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_USER;
+ }
+ } else if (sid_to_gid( &current_ace->trustee, &current_ace->unix_ug.gid)) {
+ current_ace->owner_type = GID_ACE;
+ /* If it's the primary group, this is a group_obj, not
+ * a group. */
+ if (current_ace->unix_ug.gid == pst->st_gid) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_GROUP;
+ }
+ } else {
+ /*
+ * Silently ignore map failures in non-mappable SIDs (NT Authority, BUILTIN etc).
+ */
+
+ if (non_mappable_sid(&psa->trustee)) {
+ DEBUG(10, ("create_canon_ace_lists: ignoring "
+ "non-mappable SID %s\n",
+ sid_string_dbg(&psa->trustee)));
+ SAFE_FREE(current_ace);
+ continue;
+ }
+
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0, ("create_canon_ace_lists: unable to map SID "
+ "%s to uid or gid.\n",
+ sid_string_dbg(&current_ace->trustee)));
+ SAFE_FREE(current_ace);
+ return False;
+ }
+
+ /*
+ * Map the given NT permissions into a UNIX mode_t containing only
+ * S_I(R|W|X)USR bits.
+ */
+
+ current_ace->perms |= map_nt_perms( &psa->access_mask, S_IRUSR);
+ current_ace->attr = (psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) ? ALLOW_ACE : DENY_ACE;
+ current_ace->inherited = ((psa->flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False);
+
+ /*
+ * Now add the created ace to either the file list, the directory
+ * list, or both. We *MUST* preserve the order here (hence we use
+ * DLIST_ADD_END) as NT ACLs are order dependent.
+ */
+
+ if (fsp->is_directory) {
+
+ /*
+ * We can only add to the default POSIX ACE list if the ACE is
+ * designed to be inherited by both files and directories.
+ */
+
+ if ((psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) ==
+ (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+
+ DLIST_ADD_END(dir_ace, current_ace, canon_ace *);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ got_dir_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && got_dir_allow) {
+ DEBUG(0,("create_canon_ace_lists: malformed ACL in inheritable ACL ! \
+Deny entry after Allow entry. Failing to set on file %s.\n", fsp->fsp_name ));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("create_canon_ace_lists: adding dir ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+
+ /*
+ * If this is not an inherit only ACE we need to add a duplicate
+ * to the file acl.
+ */
+
+ if (!(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ canon_ace *dup_ace = dup_canon_ace(current_ace);
+
+ if (!dup_ace) {
+ DEBUG(0,("create_canon_ace_lists: malloc fail !\n"));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = dup_ace;
+ } else {
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = NULL;
+ }
+ }
+ }
+
+ /*
+ * Only add to the file ACL if not inherit only.
+ */
+
+ if (current_ace && !(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ DLIST_ADD_END(file_ace, current_ace, canon_ace *);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ got_file_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && got_file_allow) {
+ DEBUG(0,("create_canon_ace_lists: malformed ACL in file ACL ! \
+Deny entry after Allow entry. Failing to set on file %s.\n", fsp->fsp_name ));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("create_canon_ace_lists: adding file ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+ all_aces_are_inherit_only = False;
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the file_ace list.
+ */
+ current_ace = NULL;
+ }
+
+ /*
+ * Free if ACE was not added.
+ */
+
+ SAFE_FREE(current_ace);
+ }
+
+ if (fsp->is_directory && all_aces_are_inherit_only) {
+ /*
+ * Windows 2000 is doing one of these weird 'inherit acl'
+ * traverses to conserve NTFS ACL resources. Just pretend
+ * there was no DACL sent. JRA.
+ */
+
+ DEBUG(10,("create_canon_ace_lists: Win2k inherit acl traverse. Ignoring DACL.\n"));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ file_ace = NULL;
+ dir_ace = NULL;
+ } else {
+ /*
+ * Check if we have SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries in each
+ * ACL. If we don't have them, check if any SMB_ACL_USER/SMB_ACL_GROUP
+ * entries can be converted to *_OBJ. Usually we will already have these
+ * entries in the Default ACL, and the Access ACL will not have them.
+ */
+ if (file_ace) {
+ check_owning_objs(file_ace, pfile_owner_sid, pfile_grp_sid);
+ }
+ if (dir_ace) {
+ check_owning_objs(dir_ace, pfile_owner_sid, pfile_grp_sid);
+ }
+ }
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+
+ return True;
+}
+
+/****************************************************************************
+ ASCII art time again... JRA :-).
+
+ We have 4 cases to process when moving from an NT ACL to a POSIX ACL. Firstly,
+ we insist the ACL is in canonical form (ie. all DENY entries preceede ALLOW
+ entries). Secondly, the merge code has ensured that all duplicate SID entries for
+ allow or deny have been merged, so the same SID can only appear once in the deny
+ list or once in the allow list.
+
+ We then process as follows :
+
+ ---------------------------------------------------------------------------
+ First pass - look for a Everyone DENY entry.
+
+ If it is deny all (rwx) trunate the list at this point.
+ Else, walk the list from this point and use the deny permissions of this
+ entry as a mask on all following allow entries. Finally, delete
+ the Everyone DENY entry (we have applied it to everything possible).
+
+ In addition, in this pass we remove any DENY entries that have
+ no permissions (ie. they are a DENY nothing).
+ ---------------------------------------------------------------------------
+ Second pass - only deal with deny user entries.
+
+ DENY user1 (perms XXX)
+
+ new_perms = 0
+ for all following allow group entries where user1 is in group
+ new_perms |= group_perms;
+
+ user1 entry perms = new_perms & ~ XXX;
+
+ Convert the deny entry to an allow entry with the new perms and
+ push to the end of the list. Note if the user was in no groups
+ this maps to a specific allow nothing entry for this user.
+
+ The common case from the NT ACL choser (userX deny all) is
+ optimised so we don't do the group lookup - we just map to
+ an allow nothing entry.
+
+ What we're doing here is inferring the allow permissions the
+ person setting the ACE on user1 wanted by looking at the allow
+ permissions on the groups the user is currently in. This will
+ be a snapshot, depending on group membership but is the best
+ we can do and has the advantage of failing closed rather than
+ open.
+ ---------------------------------------------------------------------------
+ Third pass - only deal with deny group entries.
+
+ DENY group1 (perms XXX)
+
+ for all following allow user entries where user is in group1
+ user entry perms = user entry perms & ~ XXX;
+
+ If there is a group Everyone allow entry with permissions YYY,
+ convert the group1 entry to an allow entry and modify its
+ permissions to be :
+
+ new_perms = YYY & ~ XXX
+
+ and push to the end of the list.
+
+ If there is no group Everyone allow entry then convert the
+ group1 entry to a allow nothing entry and push to the end of the list.
+
+ Note that the common case from the NT ACL choser (groupX deny all)
+ cannot be optimised here as we need to modify user entries who are
+ in the group to change them to a deny all also.
+
+ What we're doing here is modifying the allow permissions of
+ user entries (which are more specific in POSIX ACLs) to mask
+ out the explicit deny set on the group they are in. This will
+ be a snapshot depending on current group membership but is the
+ best we can do and has the advantage of failing closed rather
+ than open.
+ ---------------------------------------------------------------------------
+ Fourth pass - cope with cumulative permissions.
+
+ for all allow user entries, if there exists an allow group entry with
+ more permissive permissions, and the user is in that group, rewrite the
+ allow user permissions to contain both sets of permissions.
+
+ Currently the code for this is #ifdef'ed out as these semantics make
+ no sense to me. JRA.
+ ---------------------------------------------------------------------------
+
+ Note we *MUST* do the deny user pass first as this will convert deny user
+ entries into allow user entries which can then be processed by the deny
+ group pass.
+
+ The above algorithm took a *lot* of thinking about - hence this
+ explaination :-). JRA.
+****************************************************************************/
+
+/****************************************************************************
+ Process a canon_ace list entries. This is very complex code. We need
+ to go through and remove the "deny" permissions from any allow entry that matches
+ the id of this entry. We have already refused any NT ACL that wasn't in correct
+ order (DENY followed by ALLOW). If any allow entry ends up with zero permissions,
+ we just remove it (to fail safe). We have already removed any duplicate ace
+ entries. Treat an "Everyone" DENY_ACE as a special case - use it to mask all
+ allow entries.
+****************************************************************************/
+
+static void process_deny_list( canon_ace **pp_ace_list )
+{
+ canon_ace *ace_list = *pp_ace_list;
+ canon_ace *curr_ace = NULL;
+ canon_ace *curr_ace_next = NULL;
+
+ /* Pass 1 above - look for an Everyone, deny entry. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->perms == (mode_t)0) {
+
+ /* Deny nothing entry - delete. */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ continue;
+ }
+
+ if (!sid_equal(&curr_ace->trustee, &global_sid_World))
+ continue;
+
+ /* JRATEST - assert. */
+ SMB_ASSERT(curr_ace->owner_type == WORLD_ACE);
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation. This is a DENY_ALL to Everyone. Truncate the
+ * list at this point including this entry.
+ */
+
+ canon_ace *prev_entry = curr_ace->prev;
+
+ free_canon_ace_list( curr_ace );
+ if (prev_entry)
+ prev_entry->next = NULL;
+ else {
+ /* We deleted the entire list. */
+ ace_list = NULL;
+ }
+ break;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ /*
+ * Only mask off allow entries.
+ */
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Now it's been applied, remove it.
+ */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ }
+
+ /* Pass 2 above - deal with deny user entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ mode_t new_perms = (mode_t)0;
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation - this is a deny everything to this user.
+ * Convert to an allow nothing and push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace, canon_ace *);
+ continue;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE and WORLD_ACE entries only. */
+
+ if (allow_ace_p->owner_type == UID_ACE)
+ continue;
+
+ if (uid_entry_in_group( curr_ace, allow_ace_p))
+ new_perms |= allow_ace_p->perms;
+ }
+
+ /*
+ * Convert to a allow entry, modify the perms and push to the end
+ * of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (new_perms & ~curr_ace->perms);
+ DLIST_DEMOTE(ace_list, curr_ace, canon_ace *);
+ }
+
+ /* Pass 3 above - deal with deny group entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+ canon_ace *allow_everyone_p = NULL;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != GID_ACE)
+ continue;
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* Store a pointer to the Everyone allow, if it exists. */
+ if (allow_ace_p->owner_type == WORLD_ACE)
+ allow_everyone_p = allow_ace_p;
+
+ /* We process UID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != UID_ACE)
+ continue;
+
+ /* Mask off the deny group perms. */
+
+ if (uid_entry_in_group( allow_ace_p, curr_ace))
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Convert the deny to an allow with the correct perms and
+ * push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ if (allow_everyone_p)
+ curr_ace->perms = allow_everyone_p->perms & ~curr_ace->perms;
+ else
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace, canon_ace *);
+ }
+
+ /* Doing this fourth pass allows Windows semantics to be layered
+ * on top of POSIX semantics. I'm not sure if this is desirable.
+ * For example, in W2K ACLs there is no way to say, "Group X no
+ * access, user Y full access" if user Y is a member of group X.
+ * This seems completely broken semantics to me.... JRA.
+ */
+
+#if 0
+ /* Pass 4 above - deal with allow entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != ALLOW_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ for (allow_ace_p = ace_list; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != GID_ACE)
+ continue;
+
+ /* OR in the group perms. */
+
+ if (uid_entry_in_group( curr_ace, allow_ace_p))
+ curr_ace->perms |= allow_ace_p->perms;
+ }
+ }
+#endif
+
+ *pp_ace_list = ace_list;
+}
+
+/****************************************************************************
+ Create a default mode that will be used if a security descriptor entry has
+ no user/group/world entries.
+****************************************************************************/
+
+static mode_t create_default_mode(files_struct *fsp, bool interitable_mode)
+{
+ int snum = SNUM(fsp->conn);
+ mode_t and_bits = (mode_t)0;
+ mode_t or_bits = (mode_t)0;
+ mode_t mode = interitable_mode
+ ? unix_mode( fsp->conn, FILE_ATTRIBUTE_ARCHIVE, fsp->fsp_name,
+ NULL )
+ : S_IRUSR;
+
+ if (fsp->is_directory)
+ mode |= (S_IWUSR|S_IXUSR);
+
+ /*
+ * Now AND with the create mode/directory mode bits then OR with the
+ * force create mode/force directory mode bits.
+ */
+
+ if (fsp->is_directory) {
+ and_bits = lp_dir_security_mask(snum);
+ or_bits = lp_force_dir_security_mode(snum);
+ } else {
+ and_bits = lp_security_mask(snum);
+ or_bits = lp_force_security_mode(snum);
+ }
+
+ return ((mode & and_bits)|or_bits);
+}
+
+/****************************************************************************
+ Unpack a SEC_DESC into two canonical ace lists. We don't depend on this
+ succeeding.
+****************************************************************************/
+
+static bool unpack_canon_ace(files_struct *fsp,
+ SMB_STRUCT_STAT *pst,
+ DOM_SID *pfile_owner_sid,
+ DOM_SID *pfile_grp_sid,
+ canon_ace **ppfile_ace, canon_ace **ppdir_ace,
+ uint32 security_info_sent, SEC_DESC *psd)
+{
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_canon_ace: no security info sent !\n"));
+ return False;
+ }
+
+ /*
+ * If no DACL then this is a chown only security descriptor.
+ */
+
+ if(!(security_info_sent & DACL_SECURITY_INFORMATION) || !psd->dacl)
+ return True;
+
+ /*
+ * Now go through the DACL and create the canon_ace lists.
+ */
+
+ if (!create_canon_ace_lists( fsp, pst, pfile_owner_sid, pfile_grp_sid,
+ &file_ace, &dir_ace, psd->dacl))
+ return False;
+
+ if ((file_ace == NULL) && (dir_ace == NULL)) {
+ /* W2K traverse DACL set - ignore. */
+ return True;
+ }
+
+ /*
+ * Go through the canon_ace list and merge entries
+ * belonging to identical users of identical allow or deny type.
+ * We can do this as all deny entries come first, followed by
+ * all allow entries (we have mandated this before accepting this acl).
+ */
+
+ print_canon_ace_list( "file ace - before merge", file_ace);
+ merge_aces( &file_ace );
+
+ print_canon_ace_list( "dir ace - before merge", dir_ace);
+ merge_aces( &dir_ace );
+
+ /*
+ * NT ACLs are order dependent. Go through the acl lists and
+ * process DENY entries by masking the allow entries.
+ */
+
+ print_canon_ace_list( "file ace - before deny", file_ace);
+ process_deny_list( &file_ace);
+
+ print_canon_ace_list( "dir ace - before deny", dir_ace);
+ process_deny_list( &dir_ace);
+
+ /*
+ * A well formed POSIX file or default ACL has at least 3 entries, a
+ * SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ
+ * and optionally a mask entry. Ensure this is the case.
+ */
+
+ print_canon_ace_list( "file ace - before valid", file_ace);
+
+ /*
+ * A default 3 element mode entry for a file should be r-- --- ---.
+ * A default 3 element mode entry for a directory should be rwx --- ---.
+ */
+
+ pst->st_mode = create_default_mode(fsp, False);
+
+ if (!ensure_canon_entry_valid(&file_ace, fsp->conn->params, fsp->is_directory, pfile_owner_sid, pfile_grp_sid, pst, True)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ print_canon_ace_list( "dir ace - before valid", dir_ace);
+
+ /*
+ * A default inheritable 3 element mode entry for a directory should be the
+ * mode Samba will use to create a file within. Ensure user rwx bits are set if
+ * it's a directory.
+ */
+
+ pst->st_mode = create_default_mode(fsp, True);
+
+ if (dir_ace && !ensure_canon_entry_valid(&dir_ace, fsp->conn->params, fsp->is_directory, pfile_owner_sid, pfile_grp_sid, pst, True)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ print_canon_ace_list( "file ace - return", file_ace);
+ print_canon_ace_list( "dir ace - return", dir_ace);
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+ return True;
+
+}
+
+/******************************************************************************
+ When returning permissions, try and fit NT display
+ semantics if possible. Note the the canon_entries here must have been malloced.
+ The list format should be - first entry = owner, followed by group and other user
+ entries, last entry = other.
+
+ Note that this doesn't exactly match the NT semantics for an ACL. As POSIX entries
+ are not ordered, and match on the most specific entry rather than walking a list,
+ then a simple POSIX permission of rw-r--r-- should really map to 5 entries,
+
+ Entry 0: owner : deny all except read and write.
+ Entry 1: owner : allow read and write.
+ Entry 2: group : deny all except read.
+ Entry 3: group : allow read.
+ Entry 4: Everyone : allow read.
+
+ But NT cannot display this in their ACL editor !
+********************************************************************************/
+
+static void arrange_posix_perms(const char *filename, canon_ace **pp_list_head)
+{
+ canon_ace *list_head = *pp_list_head;
+ canon_ace *owner_ace = NULL;
+ canon_ace *other_ace = NULL;
+ canon_ace *ace = NULL;
+
+ for (ace = list_head; ace; ace = ace->next) {
+ if (ace->type == SMB_ACL_USER_OBJ)
+ owner_ace = ace;
+ else if (ace->type == SMB_ACL_OTHER) {
+ /* Last ace - this is "other" */
+ other_ace = ace;
+ }
+ }
+
+ if (!owner_ace || !other_ace) {
+ DEBUG(0,("arrange_posix_perms: Invalid POSIX permissions for file %s, missing owner or other.\n",
+ filename ));
+ return;
+ }
+
+ /*
+ * The POSIX algorithm applies to owner first, and other last,
+ * so ensure they are arranged in this order.
+ */
+
+ if (owner_ace) {
+ DLIST_PROMOTE(list_head, owner_ace);
+ }
+
+ if (other_ace) {
+ DLIST_DEMOTE(list_head, other_ace, canon_ace *);
+ }
+
+ /* We have probably changed the head of the list. */
+
+ *pp_list_head = list_head;
+}
+
+/****************************************************************************
+ Create a linked list of canonical ACE entries.
+****************************************************************************/
+
+static canon_ace *canonicalise_acl(struct connection_struct *conn,
+ const char *fname, SMB_ACL_T posix_acl,
+ const SMB_STRUCT_STAT *psbuf,
+ const DOM_SID *powner, const DOM_SID *pgroup, struct pai_val *pal, SMB_ACL_TYPE_T the_acl_type)
+{
+ mode_t acl_mask = (S_IRUSR|S_IWUSR|S_IXUSR);
+ canon_ace *list_head = NULL;
+ canon_ace *ace = NULL;
+ canon_ace *next_ace = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ size_t ace_count;
+
+ while ( posix_acl && (SMB_VFS_SYS_ACL_GET_ENTRY(conn, posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ DOM_SID sid;
+ posix_id unix_ug;
+ enum ace_owner owner_type;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ /* Is this a MASK entry ? */
+ if (SMB_VFS_SYS_ACL_GET_TAG_TYPE(conn, entry, &tagtype) == -1)
+ continue;
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, entry, &permset) == -1)
+ continue;
+
+ /* Decide which SID to use based on the ACL type. */
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ /* Get the SID from the owner. */
+ sid_copy(&sid, powner);
+ unix_ug.uid = psbuf->st_uid;
+ owner_type = UID_ACE;
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)SMB_VFS_SYS_ACL_GET_QUALIFIER(conn, entry);
+ if (puid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get uid.\n"));
+ continue;
+ }
+ /*
+ * A SMB_ACL_USER entry for the owner is shadowed by the
+ * SMB_ACL_USER_OBJ entry and Windows also cannot represent
+ * that entry, so we ignore it. We also don't create such
+ * entries out of the blue when setting ACLs, so a get/set
+ * cycle will drop them.
+ */
+ if (the_acl_type == SMB_ACL_TYPE_ACCESS && *puid == psbuf->st_uid) {
+ SMB_VFS_SYS_ACL_FREE_QUALIFIER(conn, (void *)puid,tagtype);
+ continue;
+ }
+ uid_to_sid( &sid, *puid);
+ unix_ug.uid = *puid;
+ owner_type = UID_ACE;
+ SMB_VFS_SYS_ACL_FREE_QUALIFIER(conn, (void *)puid,tagtype);
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ /* Get the SID from the owning group. */
+ sid_copy(&sid, pgroup);
+ unix_ug.gid = psbuf->st_gid;
+ owner_type = GID_ACE;
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid = (gid_t *)SMB_VFS_SYS_ACL_GET_QUALIFIER(conn, entry);
+ if (pgid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get gid.\n"));
+ continue;
+ }
+ gid_to_sid( &sid, *pgid);
+ unix_ug.gid = *pgid;
+ owner_type = GID_ACE;
+ SMB_VFS_SYS_ACL_FREE_QUALIFIER(conn, (void *)pgid,tagtype);
+ break;
+ }
+ case SMB_ACL_MASK:
+ acl_mask = convert_permset_to_mode_t(conn, permset);
+ continue; /* Don't count the mask as an entry. */
+ case SMB_ACL_OTHER:
+ /* Use the Everyone SID */
+ sid = global_sid_World;
+ unix_ug.world = -1;
+ owner_type = WORLD_ACE;
+ break;
+ default:
+ DEBUG(0,("canonicalise_acl: Unknown tagtype %u\n", (unsigned int)tagtype));
+ continue;
+ }
+
+ /*
+ * Add this entry to the list.
+ */
+
+ if ((ace = SMB_MALLOC_P(canon_ace)) == NULL)
+ goto fail;
+
+ ZERO_STRUCTP(ace);
+ ace->type = tagtype;
+ ace->perms = convert_permset_to_mode_t(conn, permset);
+ ace->attr = ALLOW_ACE;
+ ace->trustee = sid;
+ ace->unix_ug = unix_ug;
+ ace->owner_type = owner_type;
+ ace->inherited = get_inherited_flag(pal, ace, (the_acl_type == SMB_ACL_TYPE_DEFAULT));
+
+ DLIST_ADD(list_head, ace);
+ }
+
+ /*
+ * This next call will ensure we have at least a user/group/world set.
+ */
+
+ if (!ensure_canon_entry_valid(&list_head, conn->params,
+ S_ISDIR(psbuf->st_mode), powner, pgroup,
+ psbuf, False))
+ goto fail;
+
+ /*
+ * Now go through the list, masking the permissions with the
+ * acl_mask. Ensure all DENY Entries are at the start of the list.
+ */
+
+ DEBUG(10,("canonicalise_acl: %s ace entries before arrange :\n", the_acl_type == SMB_ACL_TYPE_ACCESS ? "Access" : "Default" ));
+
+ for ( ace_count = 0, ace = list_head; ace; ace = next_ace, ace_count++) {
+ next_ace = ace->next;
+
+ /* Masks are only applied to entries other than USER_OBJ and OTHER. */
+ if (ace->type != SMB_ACL_OTHER && ace->type != SMB_ACL_USER_OBJ)
+ ace->perms &= acl_mask;
+
+ if (ace->perms == 0) {
+ DLIST_PROMOTE(list_head, ace);
+ }
+
+ if( DEBUGLVL( 10 ) ) {
+ print_canon_ace(ace, ace_count);
+ }
+ }
+
+ arrange_posix_perms(fname,&list_head );
+
+ print_canon_ace_list( "canonicalise_acl: ace entries after arrange", list_head );
+
+ return list_head;
+
+ fail:
+
+ free_canon_ace_list(list_head);
+ return NULL;
+}
+
+/****************************************************************************
+ Check if the current user group list contains a given group.
+****************************************************************************/
+
+static bool current_user_in_group(gid_t gid)
+{
+ int i;
+
+ for (i = 0; i < current_user.ut.ngroups; i++) {
+ if (current_user.ut.groups[i] == gid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+/****************************************************************************
+ Should we override a deny ? Check 'acl group control' and 'dos filemode'.
+****************************************************************************/
+
+static bool acl_group_override(connection_struct *conn,
+ gid_t prim_gid,
+ const char *fname)
+{
+ SMB_STRUCT_STAT sbuf;
+
+ if ((errno != EPERM) && (errno != EACCES)) {
+ return false;
+ }
+
+ /* file primary group == user primary or supplementary group */
+ if (lp_acl_group_control(SNUM(conn)) &&
+ current_user_in_group(prim_gid)) {
+ return true;
+ }
+
+ /* user has writeable permission */
+ if (lp_dos_filemode(SNUM(conn)) &&
+ can_write_to_file(conn, fname, &sbuf)) {
+ return true;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Attempt to apply an ACL to a file or directory.
+****************************************************************************/
+
+static bool set_canon_ace_list(files_struct *fsp, canon_ace *the_ace, bool default_ace, gid_t prim_gid, bool *pacl_set_support)
+{
+ connection_struct *conn = fsp->conn;
+ bool ret = False;
+ SMB_ACL_T the_acl = SMB_VFS_SYS_ACL_INIT(conn, (int)count_canon_ace_list(the_ace) + 1);
+ canon_ace *p_ace;
+ int i;
+ SMB_ACL_ENTRY_T mask_entry;
+ bool got_mask_entry = False;
+ SMB_ACL_PERMSET_T mask_permset;
+ SMB_ACL_TYPE_T the_acl_type = (default_ace ? SMB_ACL_TYPE_DEFAULT : SMB_ACL_TYPE_ACCESS);
+ bool needs_mask = False;
+ mode_t mask_perms = 0;
+
+#if defined(POSIX_ACL_NEEDS_MASK)
+ /* HP-UX always wants to have a mask (called "class" there). */
+ needs_mask = True;
+#endif
+
+ if (the_acl == NULL) {
+
+ if (!no_acl_syscall_error(errno)) {
+ /*
+ * Only print this error message if we have some kind of ACL
+ * support that's not working. Otherwise we would always get this.
+ */
+ DEBUG(0,("set_canon_ace_list: Unable to init %s ACL. (%s)\n",
+ default_ace ? "default" : "file", strerror(errno) ));
+ }
+ *pacl_set_support = False;
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("set_canon_ace_list: setting ACL:\n");
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ print_canon_ace( p_ace, i);
+ }
+ }
+
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+
+ /*
+ * ACLs only "need" an ACL_MASK entry if there are any named user or
+ * named group entries. But if there is an ACL_MASK entry, it applies
+ * to ACL_USER, ACL_GROUP, and ACL_GROUP_OBJ entries. Set the mask
+ * so that it doesn't deny (i.e., mask off) any permissions.
+ */
+
+ if (p_ace->type == SMB_ACL_USER || p_ace->type == SMB_ACL_GROUP) {
+ needs_mask = True;
+ mask_perms |= p_ace->perms;
+ } else if (p_ace->type == SMB_ACL_GROUP_OBJ) {
+ mask_perms |= p_ace->perms;
+ }
+
+ /*
+ * Get the entry for this ACE.
+ */
+
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &the_acl, &the_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (p_ace->type == SMB_ACL_MASK) {
+ mask_entry = the_entry;
+ got_mask_entry = True;
+ }
+
+ /*
+ * Ok - we now know the ACL calls should be working, don't
+ * allow fallback to chmod.
+ */
+
+ *pacl_set_support = True;
+
+ /*
+ * Initialise the entry from the canon_ace.
+ */
+
+ /*
+ * First tell the entry what type of ACE this is.
+ */
+
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, the_entry, p_ace->type) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * Only set the qualifier (user or group id) if the entry is a user
+ * or group id ACE.
+ */
+
+ if ((p_ace->type == SMB_ACL_USER) || (p_ace->type == SMB_ACL_GROUP)) {
+ if (SMB_VFS_SYS_ACL_SET_QUALIFIER(conn, the_entry,(void *)&p_ace->unix_ug.uid) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set qualifier on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Convert the mode_t perms in the canon_ace to a POSIX permset.
+ */
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, the_entry, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(conn, p_ace->perms, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create permset for mode (%u) on entry %d. (%s)\n",
+ (unsigned int)p_ace->perms, i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * ..and apply them to the entry.
+ */
+
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, the_entry, the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if( DEBUGLVL( 10 ))
+ print_canon_ace( p_ace, i);
+
+ }
+
+ if (needs_mask && !got_mask_entry) {
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &the_acl, &mask_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask entry. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, mask_entry, SMB_ACL_MASK) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on mask entry. (%s)\n",strerror(errno) ));
+ goto fail;
+ }
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, mask_entry, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(conn, S_IRUSR|S_IWUSR|S_IXUSR, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, mask_entry, mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Finally apply it to the file or directory.
+ */
+
+ if(default_ace || fsp->is_directory || fsp->fh->fd == -1) {
+ if (SMB_VFS_SYS_ACL_SET_FILE(conn, fsp->fsp_name, the_acl_type, the_acl) == -1) {
+ /*
+ * Some systems allow all the above calls and only fail with no ACL support
+ * when attempting to apply the acl. HPUX with HFS is an example of this. JRA.
+ */
+ if (no_acl_syscall_error(errno)) {
+ *pacl_set_support = False;
+ }
+
+ if (acl_group_override(conn, prim_gid, fsp->fsp_name)) {
+ int sret;
+
+ DEBUG(5,("set_canon_ace_list: acl group control on and current user in file %s primary group.\n",
+ fsp->fsp_name ));
+
+ become_root();
+ sret = SMB_VFS_SYS_ACL_SET_FILE(conn, fsp->fsp_name, the_acl_type, the_acl);
+ unbecome_root();
+ if (sret == 0) {
+ ret = True;
+ }
+ }
+
+ if (ret == False) {
+ DEBUG(2,("set_canon_ace_list: sys_acl_set_file type %s failed for file %s (%s).\n",
+ the_acl_type == SMB_ACL_TYPE_DEFAULT ? "directory default" : "file",
+ fsp->fsp_name, strerror(errno) ));
+ goto fail;
+ }
+ }
+ } else {
+ if (SMB_VFS_SYS_ACL_SET_FD(fsp, the_acl) == -1) {
+ /*
+ * Some systems allow all the above calls and only fail with no ACL support
+ * when attempting to apply the acl. HPUX with HFS is an example of this. JRA.
+ */
+ if (no_acl_syscall_error(errno)) {
+ *pacl_set_support = False;
+ }
+
+ if (acl_group_override(conn, prim_gid, fsp->fsp_name)) {
+ int sret;
+
+ DEBUG(5,("set_canon_ace_list: acl group control on and current user in file %s primary group.\n",
+ fsp->fsp_name ));
+
+ become_root();
+ sret = SMB_VFS_SYS_ACL_SET_FD(fsp, the_acl);
+ unbecome_root();
+ if (sret == 0) {
+ ret = True;
+ }
+ }
+
+ if (ret == False) {
+ DEBUG(2,("set_canon_ace_list: sys_acl_set_file failed for file %s (%s).\n",
+ fsp->fsp_name, strerror(errno) ));
+ goto fail;
+ }
+ }
+ }
+
+ ret = True;
+
+ fail:
+
+ if (the_acl != NULL) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, the_acl);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Find a particular canon_ace entry.
+****************************************************************************/
+
+static struct canon_ace *canon_ace_entry_for(struct canon_ace *list, SMB_ACL_TAG_T type, posix_id *id)
+{
+ while (list) {
+ if (list->type == type && ((type != SMB_ACL_USER && type != SMB_ACL_GROUP) ||
+ (type == SMB_ACL_USER && id && id->uid == list->unix_ug.uid) ||
+ (type == SMB_ACL_GROUP && id && id->gid == list->unix_ug.gid)))
+ break;
+ list = list->next;
+ }
+ return list;
+}
+
+/****************************************************************************
+
+****************************************************************************/
+
+SMB_ACL_T free_empty_sys_acl(connection_struct *conn, SMB_ACL_T the_acl)
+{
+ SMB_ACL_ENTRY_T entry;
+
+ if (!the_acl)
+ return NULL;
+ if (SMB_VFS_SYS_ACL_GET_ENTRY(conn, the_acl, SMB_ACL_FIRST_ENTRY, &entry) != 1) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, the_acl);
+ return NULL;
+ }
+ return the_acl;
+}
+
+/****************************************************************************
+ Convert a canon_ace to a generic 3 element permission - if possible.
+****************************************************************************/
+
+#define MAP_PERM(p,mask,result) (((p) & (mask)) ? (result) : 0 )
+
+static bool convert_canon_ace_to_posix_perms( files_struct *fsp, canon_ace *file_ace_list, mode_t *posix_perms)
+{
+ int snum = SNUM(fsp->conn);
+ size_t ace_count = count_canon_ace_list(file_ace_list);
+ canon_ace *ace_p;
+ canon_ace *owner_ace = NULL;
+ canon_ace *group_ace = NULL;
+ canon_ace *other_ace = NULL;
+ mode_t and_bits;
+ mode_t or_bits;
+
+ if (ace_count != 3) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Too many ACE entries for file %s to convert to \
+posix perms.\n", fsp->fsp_name ));
+ return False;
+ }
+
+ for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) {
+ if (ace_p->owner_type == UID_ACE)
+ owner_ace = ace_p;
+ else if (ace_p->owner_type == GID_ACE)
+ group_ace = ace_p;
+ else if (ace_p->owner_type == WORLD_ACE)
+ other_ace = ace_p;
+ }
+
+ if (!owner_ace || !group_ace || !other_ace) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Can't get standard entries for file %s.\n",
+ fsp->fsp_name ));
+ return False;
+ }
+
+ *posix_perms = (mode_t)0;
+
+ *posix_perms |= owner_ace->perms;
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IRUSR, S_IRGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IWUSR, S_IWGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IXUSR, S_IXGRP);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IRUSR, S_IROTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IWUSR, S_IWOTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IXUSR, S_IXOTH);
+
+ /* The owner must have at least read access. */
+
+ *posix_perms |= S_IRUSR;
+ if (fsp->is_directory)
+ *posix_perms |= (S_IWUSR|S_IXUSR);
+
+ /* If requested apply the masks. */
+
+ /* Get the initial bits to apply. */
+
+ if (fsp->is_directory) {
+ and_bits = lp_dir_security_mask(snum);
+ or_bits = lp_force_dir_security_mode(snum);
+ } else {
+ and_bits = lp_security_mask(snum);
+ or_bits = lp_force_security_mode(snum);
+ }
+
+ *posix_perms = (((*posix_perms) & and_bits)|or_bits);
+
+ DEBUG(10,("convert_canon_ace_to_posix_perms: converted u=%o,g=%o,w=%o to perm=0%o for file %s.\n",
+ (int)owner_ace->perms, (int)group_ace->perms, (int)other_ace->perms, (int)*posix_perms,
+ fsp->fsp_name ));
+
+ return True;
+}
+
+/****************************************************************************
+ Incoming NT ACLs on a directory can be split into a default POSIX acl (CI|OI|IO) and
+ a normal POSIX acl. Win2k needs these split acls re-merging into one ACL
+ with CI|OI set so it is inherited and also applies to the directory.
+ Based on code from "Jim McDonough" <jmcd@us.ibm.com>.
+****************************************************************************/
+
+static size_t merge_default_aces( SEC_ACE *nt_ace_list, size_t num_aces)
+{
+ size_t i, j;
+
+ for (i = 0; i < num_aces; i++) {
+ for (j = i+1; j < num_aces; j++) {
+ uint32 i_flags_ni = (nt_ace_list[i].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ uint32 j_flags_ni = (nt_ace_list[j].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ bool i_inh = (nt_ace_list[i].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+ bool j_inh = (nt_ace_list[j].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+
+ /* We know the lower number ACE's are file entries. */
+ if ((nt_ace_list[i].type == nt_ace_list[j].type) &&
+ (nt_ace_list[i].size == nt_ace_list[j].size) &&
+ (nt_ace_list[i].access_mask == nt_ace_list[j].access_mask) &&
+ sid_equal(&nt_ace_list[i].trustee, &nt_ace_list[j].trustee) &&
+ (i_inh == j_inh) &&
+ (i_flags_ni == 0) &&
+ (j_flags_ni == (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY))) {
+ /*
+ * W2K wants to have access allowed zero access ACE's
+ * at the end of the list. If the mask is zero, merge
+ * the non-inherited ACE onto the inherited ACE.
+ */
+
+ if (nt_ace_list[i].access_mask == 0) {
+ nt_ace_list[j].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ if (num_aces - i - 1 > 0)
+ memmove(&nt_ace_list[i], &nt_ace_list[i+1], (num_aces-i-1) *
+ sizeof(SEC_ACE));
+
+ DEBUG(10,("merge_default_aces: Merging zero access ACE %u onto ACE %u.\n",
+ (unsigned int)i, (unsigned int)j ));
+ } else {
+ /*
+ * These are identical except for the flags.
+ * Merge the inherited ACE onto the non-inherited ACE.
+ */
+
+ nt_ace_list[i].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ if (num_aces - j - 1 > 0)
+ memmove(&nt_ace_list[j], &nt_ace_list[j+1], (num_aces-j-1) *
+ sizeof(SEC_ACE));
+
+ DEBUG(10,("merge_default_aces: Merging ACE %u onto ACE %u.\n",
+ (unsigned int)j, (unsigned int)i ));
+ }
+ num_aces--;
+ break;
+ }
+ }
+ }
+
+ return num_aces;
+}
+
+/****************************************************************************
+ Reply to query a security descriptor from an fsp. If it succeeds it allocates
+ the space for the return elements and returns the size needed to return the
+ security descriptor. This should be the only external function needed for
+ the UNIX style get ACL.
+****************************************************************************/
+
+static NTSTATUS posix_get_nt_acl_common(struct connection_struct *conn,
+ const char *name,
+ const SMB_STRUCT_STAT *sbuf,
+ struct pai_val *pal,
+ SMB_ACL_T posix_acl,
+ SMB_ACL_T def_acl,
+ uint32_t security_info,
+ SEC_DESC **ppdesc)
+{
+ DOM_SID owner_sid;
+ DOM_SID group_sid;
+ size_t sd_size = 0;
+ SEC_ACL *psa = NULL;
+ size_t num_acls = 0;
+ size_t num_def_acls = 0;
+ size_t num_aces = 0;
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ SEC_ACE *nt_ace_list = NULL;
+ size_t num_profile_acls = 0;
+ SEC_DESC *psd = NULL;
+
+ /*
+ * Get the owner, group and world SIDs.
+ */
+
+ if (lp_profile_acls(SNUM(conn))) {
+ /* For WXP SP1 the owner must be administrators. */
+ sid_copy(&owner_sid, &global_sid_Builtin_Administrators);
+ sid_copy(&group_sid, &global_sid_Builtin_Users);
+ num_profile_acls = 2;
+ } else {
+ create_file_sids(sbuf, &owner_sid, &group_sid);
+ }
+
+ if ((security_info & DACL_SECURITY_INFORMATION) && !(security_info & PROTECTED_DACL_SECURITY_INFORMATION)) {
+
+ /*
+ * In the optimum case Creator Owner and Creator Group would be used for
+ * the ACL_USER_OBJ and ACL_GROUP_OBJ entries, respectively, but this
+ * would lead to usability problems under Windows: The Creator entries
+ * are only available in browse lists of directories and not for files;
+ * additionally the identity of the owning group couldn't be determined.
+ * We therefore use those identities only for Default ACLs.
+ */
+
+ /* Create the canon_ace lists. */
+ file_ace = canonicalise_acl(conn, name, posix_acl, sbuf,
+ &owner_sid, &group_sid, pal,
+ SMB_ACL_TYPE_ACCESS);
+
+ /* We must have *some* ACLS. */
+
+ if (count_canon_ace_list(file_ace) == 0) {
+ DEBUG(0,("get_nt_acl : No ACLs on file (%s) !\n", name));
+ goto done;
+ }
+
+ if (S_ISDIR(sbuf->st_mode) && def_acl) {
+ dir_ace = canonicalise_acl(conn, name, def_acl,
+ sbuf,
+ &global_sid_Creator_Owner,
+ &global_sid_Creator_Group,
+ pal, SMB_ACL_TYPE_DEFAULT);
+ }
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ {
+ canon_ace *ace;
+ enum security_ace_type nt_acl_type;
+
+ if (nt4_compatible_acls() && dir_ace) {
+ /*
+ * NT 4 chokes if an ACL contains an INHERIT_ONLY entry
+ * but no non-INHERIT_ONLY entry for one SID. So we only
+ * remove entries from the Access ACL if the
+ * corresponding Default ACL entries have also been
+ * removed. ACEs for CREATOR-OWNER and CREATOR-GROUP
+ * are exceptions. We can do nothing
+ * intelligent if the Default ACL contains entries that
+ * are not also contained in the Access ACL, so this
+ * case will still fail under NT 4.
+ */
+
+ ace = canon_ace_entry_for(dir_ace, SMB_ACL_OTHER, NULL);
+ if (ace && !ace->perms) {
+ DLIST_REMOVE(dir_ace, ace);
+ SAFE_FREE(ace);
+
+ ace = canon_ace_entry_for(file_ace, SMB_ACL_OTHER, NULL);
+ if (ace && !ace->perms) {
+ DLIST_REMOVE(file_ace, ace);
+ SAFE_FREE(ace);
+ }
+ }
+
+ /*
+ * WinNT doesn't usually have Creator Group
+ * in browse lists, so we send this entry to
+ * WinNT even if it contains no relevant
+ * permissions. Once we can add
+ * Creator Group to browse lists we can
+ * re-enable this.
+ */
+
+#if 0
+ ace = canon_ace_entry_for(dir_ace, SMB_ACL_GROUP_OBJ, NULL);
+ if (ace && !ace->perms) {
+ DLIST_REMOVE(dir_ace, ace);
+ SAFE_FREE(ace);
+ }
+#endif
+
+ ace = canon_ace_entry_for(file_ace, SMB_ACL_GROUP_OBJ, NULL);
+ if (ace && !ace->perms) {
+ DLIST_REMOVE(file_ace, ace);
+ SAFE_FREE(ace);
+ }
+ }
+
+ num_acls = count_canon_ace_list(file_ace);
+ num_def_acls = count_canon_ace_list(dir_ace);
+
+ /* Allocate the ace list. */
+ if ((nt_ace_list = SMB_MALLOC_ARRAY(SEC_ACE,num_acls + num_profile_acls + num_def_acls)) == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for nt_ace_list.\n"));
+ goto done;
+ }
+
+ memset(nt_ace_list, '\0', (num_acls + num_def_acls) * sizeof(SEC_ACE) );
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ for (ace = file_ace; ace != NULL; ace = ace->next) {
+ SEC_ACCESS acc;
+
+ acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ ace->inherited ?
+ SEC_ACE_FLAG_INHERITED_ACE : 0);
+ }
+
+ /* The User must have access to a profile share - even
+ * if we can't map the SID. */
+ if (lp_profile_acls(SNUM(conn))) {
+ SEC_ACCESS acc;
+
+ init_sec_access(&acc,FILE_GENERIC_ALL);
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &global_sid_Builtin_Users,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ acc, 0);
+ }
+
+ for (ace = dir_ace; ace != NULL; ace = ace->next) {
+ SEC_ACCESS acc;
+
+ acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY|
+ (ace->inherited ?
+ SEC_ACE_FLAG_INHERITED_ACE : 0));
+ }
+
+ /* The User must have access to a profile share - even
+ * if we can't map the SID. */
+ if (lp_profile_acls(SNUM(conn))) {
+ SEC_ACCESS acc;
+
+ init_sec_access(&acc,FILE_GENERIC_ALL);
+ init_sec_ace(&nt_ace_list[num_aces++], &global_sid_Builtin_Users, SEC_ACE_TYPE_ACCESS_ALLOWED, acc,
+ SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY|0);
+ }
+
+ /*
+ * Merge POSIX default ACLs and normal ACLs into one NT ACE.
+ * Win2K needs this to get the inheritance correct when replacing ACLs
+ * on a directory tree. Based on work by Jim @ IBM.
+ */
+
+ num_aces = merge_default_aces(nt_ace_list, num_aces);
+
+ }
+
+ if (num_aces) {
+ if((psa = make_sec_acl( talloc_tos(), NT4_ACL_REVISION, num_aces, nt_ace_list)) == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for acl.\n"));
+ goto done;
+ }
+ }
+ } /* security_info & DACL_SECURITY_INFORMATION */
+
+ psd = make_standard_sec_desc( talloc_tos(),
+ (security_info & OWNER_SECURITY_INFORMATION) ? &owner_sid : NULL,
+ (security_info & GROUP_SECURITY_INFORMATION) ? &group_sid : NULL,
+ psa,
+ &sd_size);
+
+ if(!psd) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for security descriptor.\n"));
+ sd_size = 0;
+ goto done;
+ }
+
+ /*
+ * Windows 2000: The DACL_PROTECTED flag in the security
+ * descriptor marks the ACL as non-inheriting, i.e., no
+ * ACEs from higher level directories propagate to this
+ * ACL. In the POSIX ACL model permissions are only
+ * inherited at file create time, so ACLs never contain
+ * any ACEs that are inherited dynamically. The DACL_PROTECTED
+ * flag doesn't seem to bother Windows NT.
+ * Always set this if map acl inherit is turned off.
+ */
+ if (get_protected_flag(pal) || !lp_map_acl_inherit(SNUM(conn))) {
+ psd->type |= SE_DESC_DACL_PROTECTED;
+ }
+
+ if (psd->dacl) {
+ dacl_sort_into_canonical_order(psd->dacl->aces, (unsigned int)psd->dacl->num_aces);
+ }
+
+ *ppdesc = psd;
+
+ done:
+
+ if (posix_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, posix_acl);
+ }
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ free_inherited_info(pal);
+ SAFE_FREE(nt_ace_list);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS posix_fget_nt_acl(struct files_struct *fsp, uint32_t security_info,
+ SEC_DESC **ppdesc)
+{
+ SMB_STRUCT_STAT sbuf;
+ SMB_ACL_T posix_acl = NULL;
+ struct pai_val *pal;
+
+ *ppdesc = NULL;
+
+ DEBUG(10,("posix_fget_nt_acl: called for file %s\n", fsp->fsp_name ));
+
+ /* can it happen that fsp_name == NULL ? */
+ if (fsp->is_directory || fsp->fh->fd == -1) {
+ return posix_get_nt_acl(fsp->conn, fsp->fsp_name,
+ security_info, ppdesc);
+ }
+
+ /* Get the stat struct for the owner info. */
+ if(SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Get the ACL from the fd. */
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(fsp);
+
+ pal = fload_inherited_info(fsp);
+
+ return posix_get_nt_acl_common(fsp->conn, fsp->fsp_name, &sbuf, pal,
+ posix_acl, NULL, security_info, ppdesc);
+}
+
+NTSTATUS posix_get_nt_acl(struct connection_struct *conn, const char *name,
+ uint32_t security_info, SEC_DESC **ppdesc)
+{
+ SMB_STRUCT_STAT sbuf;
+ SMB_ACL_T posix_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ struct pai_val *pal;
+
+ *ppdesc = NULL;
+
+ DEBUG(10,("posix_get_nt_acl: called for file %s\n", name ));
+
+ /* Get the stat struct for the owner info. */
+ if(SMB_VFS_STAT(conn, name, &sbuf) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Get the ACL from the path. */
+ posix_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, name, SMB_ACL_TYPE_ACCESS);
+
+ /* If it's a directory get the default POSIX ACL. */
+ if(S_ISDIR(sbuf.st_mode)) {
+ def_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, name, SMB_ACL_TYPE_DEFAULT);
+ def_acl = free_empty_sys_acl(conn, def_acl);
+ }
+
+ pal = load_inherited_info(conn, name);
+
+ return posix_get_nt_acl_common(conn, name, &sbuf, pal, posix_acl,
+ def_acl, security_info, ppdesc);
+}
+
+/****************************************************************************
+ Try to chown a file. We will be able to chown it under the following conditions.
+
+ 1) If we have root privileges, then it will just work.
+ 2) If we have SeTakeOwnershipPrivilege we can change the user to the current user.
+ 3) If we have SeRestorePrivilege we can change the user to any other user.
+ 4) If we have write permission to the file and dos_filemodes is set
+ then allow chown to the currently authenticated user.
+****************************************************************************/
+
+int try_chown(connection_struct *conn, const char *fname, uid_t uid, gid_t gid)
+{
+ int ret;
+ files_struct *fsp;
+ SMB_STRUCT_STAT st;
+
+ if(!CAN_WRITE(conn)) {
+ return -1;
+ }
+
+ /* Case (1). */
+ /* try the direct way first */
+ ret = SMB_VFS_CHOWN(conn, fname, uid, gid);
+ if (ret == 0)
+ return 0;
+
+ /* Case (2) / (3) */
+ if (lp_enable_privileges()) {
+
+ bool has_take_ownership_priv = user_has_privileges(current_user.nt_user_token,
+ &se_take_ownership);
+ bool has_restore_priv = user_has_privileges(current_user.nt_user_token,
+ &se_restore);
+
+ /* Case (2) */
+ if ( ( has_take_ownership_priv && ( uid == current_user.ut.uid ) ) ||
+ /* Case (3) */
+ ( has_restore_priv ) ) {
+
+ become_root();
+ /* Keep the current file gid the same - take ownership doesn't imply group change. */
+ ret = SMB_VFS_CHOWN(conn, fname, uid, (gid_t)-1);
+ unbecome_root();
+ return ret;
+ }
+ }
+
+ /* Case (4). */
+ if (!lp_dos_filemode(SNUM(conn))) {
+ errno = EPERM;
+ return -1;
+ }
+
+ if (SMB_VFS_STAT(conn,fname,&st)) {
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(open_file_fchmod(conn,fname,&st,&fsp))) {
+ return -1;
+ }
+
+ /* only allow chown to the current user. This is more secure,
+ and also copes with the case where the SID in a take ownership ACL is
+ a local SID on the users workstation
+ */
+ uid = current_user.ut.uid;
+
+ become_root();
+ /* Keep the current file gid the same. */
+ ret = SMB_VFS_FCHOWN(fsp, uid, (gid_t)-1);
+ unbecome_root();
+
+ close_file_fchmod(fsp);
+
+ return ret;
+}
+
+/****************************************************************************
+ Take care of parent ACL inheritance.
+****************************************************************************/
+
+static NTSTATUS append_parent_acl(files_struct *fsp,
+ SMB_STRUCT_STAT *psbuf,
+ SEC_DESC *psd,
+ SEC_DESC **pp_new_sd)
+{
+ SEC_DESC *parent_sd = NULL;
+ files_struct *parent_fsp = NULL;
+ TALLOC_CTX *mem_ctx = talloc_parent(psd);
+ char *parent_name = NULL;
+ SEC_ACE *new_ace = NULL;
+ unsigned int num_aces = psd->dacl->num_aces;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+ int info;
+ unsigned int i, j;
+ bool is_dacl_protected = (psd->type & SE_DESC_DACL_PROTECTED);
+
+ ZERO_STRUCT(sbuf);
+
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!parent_dirname_talloc(mem_ctx,
+ fsp->fsp_name,
+ &parent_name,
+ NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = open_directory(fsp->conn,
+ NULL,
+ parent_name,
+ &sbuf,
+ FILE_READ_ATTRIBUTES, /* Just a stat open */
+ FILE_SHARE_NONE, /* Ignored for stat opens */
+ FILE_OPEN,
+ 0,
+ INTERNAL_OPEN_ONLY,
+ &info,
+ &parent_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = SMB_VFS_GET_NT_ACL(parent_fsp->conn, parent_fsp->fsp_name,
+ DACL_SECURITY_INFORMATION, &parent_sd );
+
+ close_file(parent_fsp, NORMAL_CLOSE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Make room for potentially all the ACLs from
+ * the parent. We used to add the ugw triple here,
+ * as we knew we were dealing with POSIX ACLs.
+ * We no longer need to do so as we can guarentee
+ * that a default ACL from the parent directory will
+ * be well formed for POSIX ACLs if it came from a
+ * POSIX ACL source, and if we're not writing to a
+ * POSIX ACL sink then we don't care if it's not well
+ * formed. JRA.
+ */
+
+ num_aces += parent_sd->dacl->num_aces;
+
+ if((new_ace = TALLOC_ZERO_ARRAY(mem_ctx, SEC_ACE,
+ num_aces)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Start by copying in all the given ACE entries. */
+ for (i = 0; i < psd->dacl->num_aces; i++) {
+ sec_ace_copy(&new_ace[i], &psd->dacl->aces[i]);
+ }
+
+ /*
+ * Note that we're ignoring "inherit permissions" here
+ * as that really only applies to newly created files. JRA.
+ */
+
+ /* Finally append any inherited ACEs. */
+ for (j = 0; j < parent_sd->dacl->num_aces; j++) {
+ SEC_ACE *se = &parent_sd->dacl->aces[j];
+
+ if (fsp->is_directory) {
+ if (!(se->flags & SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ /* Doesn't apply to a directory - ignore. */
+ DEBUG(10,("append_parent_acl: directory %s "
+ "ignoring non container "
+ "inherit flags %u on ACE with sid %s "
+ "from parent %s\n",
+ fsp->fsp_name,
+ (unsigned int)se->flags,
+ sid_string_dbg(&se->trustee),
+ parent_name));
+ continue;
+ }
+ } else {
+ if (!(se->flags & SEC_ACE_FLAG_OBJECT_INHERIT)) {
+ /* Doesn't apply to a file - ignore. */
+ DEBUG(10,("append_parent_acl: file %s "
+ "ignoring non object "
+ "inherit flags %u on ACE with sid %s "
+ "from parent %s\n",
+ fsp->fsp_name,
+ (unsigned int)se->flags,
+ sid_string_dbg(&se->trustee),
+ parent_name));
+ continue;
+ }
+ }
+
+ if (is_dacl_protected) {
+ /* If the DACL is protected it means we must
+ * not overwrite an existing ACE entry with the
+ * same SID. This is order N^2. Ouch :-(. JRA. */
+ unsigned int k;
+ for (k = 0; k < psd->dacl->num_aces; k++) {
+ if (sid_equal(&psd->dacl->aces[k].trustee,
+ &se->trustee)) {
+ break;
+ }
+ }
+ if (k < psd->dacl->num_aces) {
+ /* SID matched. Ignore. */
+ DEBUG(10,("append_parent_acl: path %s "
+ "ignoring ACE with protected sid %s "
+ "from parent %s\n",
+ fsp->fsp_name,
+ sid_string_dbg(&se->trustee),
+ parent_name));
+ continue;
+ }
+ }
+
+ sec_ace_copy(&new_ace[i], se);
+ if (se->flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) {
+ new_ace[i].flags &= ~(SEC_ACE_FLAG_VALID_INHERIT);
+ }
+ new_ace[i].flags |= SEC_ACE_FLAG_INHERITED_ACE;
+
+ if (fsp->is_directory) {
+ /*
+ * Strip off any inherit only. It's applied.
+ */
+ new_ace[i].flags &= ~(SEC_ACE_FLAG_INHERIT_ONLY);
+ if (se->flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) {
+ /* No further inheritance. */
+ new_ace[i].flags &=
+ ~(SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ }
+ } else {
+ /*
+ * Strip off any container or inherit
+ * flags, they can't apply to objects.
+ */
+ new_ace[i].flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY|
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT);
+ }
+ i++;
+
+ DEBUG(10,("append_parent_acl: path %s "
+ "inheriting ACE with sid %s "
+ "from parent %s\n",
+ fsp->fsp_name,
+ sid_string_dbg(&se->trustee),
+ parent_name));
+ }
+
+ /* This sucks. psd should be const and we should
+ * be doing a deep-copy here. We're getting away
+ * with is as we know parent_sd is talloced off
+ * talloc_tos() as well as psd. JRA. */
+
+ psd->dacl->aces = new_ace;
+ psd->dacl->num_aces = i;
+ psd->type &= ~(SE_DESC_DACL_AUTO_INHERITED|
+ SE_DESC_DACL_AUTO_INHERIT_REQ);
+
+ *pp_new_sd = psd;
+ return status;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor on an fsp. security_info_sent is the
+ description of the following NT ACL.
+ This should be the only external function needed for the UNIX style set ACL.
+****************************************************************************/
+
+NTSTATUS set_nt_acl(files_struct *fsp, uint32 security_info_sent, SEC_DESC *psd)
+{
+ connection_struct *conn = fsp->conn;
+ uid_t user = (uid_t)-1;
+ gid_t grp = (gid_t)-1;
+ SMB_STRUCT_STAT sbuf;
+ DOM_SID file_owner_sid;
+ DOM_SID file_grp_sid;
+ canon_ace *file_ace_list = NULL;
+ canon_ace *dir_ace_list = NULL;
+ bool acl_perms = False;
+ mode_t orig_mode = (mode_t)0;
+ NTSTATUS status;
+ uid_t orig_uid;
+ gid_t orig_gid;
+ bool need_chown = False;
+
+ DEBUG(10,("set_nt_acl: called for file %s\n", fsp->fsp_name ));
+
+ if (!CAN_WRITE(conn)) {
+ DEBUG(10,("set acl rejected on read-only share\n"));
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ /*
+ * Get the current state of the file.
+ */
+
+ if(fsp->is_directory || fsp->fh->fd == -1) {
+ if(SMB_VFS_STAT(fsp->conn,fsp->fsp_name, &sbuf) != 0)
+ return map_nt_error_from_unix(errno);
+ } else {
+ if(SMB_VFS_FSTAT(fsp, &sbuf) != 0)
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Save the original elements we check against. */
+ orig_mode = sbuf.st_mode;
+ orig_uid = sbuf.st_uid;
+ orig_gid = sbuf.st_gid;
+
+ /*
+ * Unpack the user/group/world id's.
+ */
+
+ status = unpack_nt_owners( SNUM(conn), &user, &grp, security_info_sent, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Do we need to chown ?
+ */
+
+ if (((user != (uid_t)-1) && (orig_uid != user)) || (( grp != (gid_t)-1) && (orig_gid != grp))) {
+ need_chown = True;
+ }
+
+ if (need_chown && (user == (uid_t)-1 || user == current_user.ut.uid)) {
+
+ DEBUG(3,("set_nt_acl: chown %s. uid = %u, gid = %u.\n",
+ fsp->fsp_name, (unsigned int)user, (unsigned int)grp ));
+
+ if(try_chown( fsp->conn, fsp->fsp_name, user, grp) == -1) {
+ DEBUG(3,("set_nt_acl: chown %s, %u, %u failed. Error = %s.\n",
+ fsp->fsp_name, (unsigned int)user, (unsigned int)grp, strerror(errno) ));
+ if (errno == EPERM) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Recheck the current state of the file, which may have changed.
+ * (suid/sgid bits, for instance)
+ */
+
+ if(fsp->is_directory) {
+ if(SMB_VFS_STAT(fsp->conn, fsp->fsp_name, &sbuf) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+
+ int ret;
+
+ if(fsp->fh->fd == -1)
+ ret = SMB_VFS_STAT(fsp->conn, fsp->fsp_name, &sbuf);
+ else
+ ret = SMB_VFS_FSTAT(fsp, &sbuf);
+
+ if(ret != 0)
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Save the original elements we check against. */
+ orig_mode = sbuf.st_mode;
+ orig_uid = sbuf.st_uid;
+ orig_gid = sbuf.st_gid;
+
+ /* We did chown already, drop the flag */
+ need_chown = False;
+ }
+
+ create_file_sids(&sbuf, &file_owner_sid, &file_grp_sid);
+
+ if ((security_info_sent & DACL_SECURITY_INFORMATION) &&
+ psd->dacl != NULL &&
+ (psd->type & (SE_DESC_DACL_AUTO_INHERITED|
+ SE_DESC_DACL_AUTO_INHERIT_REQ))==
+ (SE_DESC_DACL_AUTO_INHERITED|
+ SE_DESC_DACL_AUTO_INHERIT_REQ) ) {
+ status = append_parent_acl(fsp, &sbuf, psd, &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ acl_perms = unpack_canon_ace( fsp, &sbuf, &file_owner_sid, &file_grp_sid,
+ &file_ace_list, &dir_ace_list, security_info_sent, psd);
+
+ /* Ignore W2K traverse DACL set. */
+ if (file_ace_list || dir_ace_list) {
+
+ if (!acl_perms) {
+ DEBUG(3,("set_nt_acl: cannot set permissions\n"));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Only change security if we got a DACL.
+ */
+
+ if((security_info_sent & DACL_SECURITY_INFORMATION) && (psd->dacl != NULL)) {
+
+ bool acl_set_support = False;
+ bool ret = False;
+
+ /*
+ * Try using the POSIX ACL set first. Fall back to chmod if
+ * we have no ACL support on this filesystem.
+ */
+
+ if (acl_perms && file_ace_list) {
+ ret = set_canon_ace_list(fsp, file_ace_list, False, sbuf.st_gid, &acl_set_support);
+ if (acl_set_support && ret == False) {
+ DEBUG(3,("set_nt_acl: failed to set file acl on file %s (%s).\n", fsp->fsp_name, strerror(errno) ));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ if (acl_perms && acl_set_support && fsp->is_directory) {
+ if (dir_ace_list) {
+ if (!set_canon_ace_list(fsp, dir_ace_list, True, sbuf.st_gid, &acl_set_support)) {
+ DEBUG(3,("set_nt_acl: failed to set default acl on directory %s (%s).\n", fsp->fsp_name, strerror(errno) ));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+
+ /*
+ * No default ACL - delete one if it exists.
+ */
+
+ if (SMB_VFS_SYS_ACL_DELETE_DEF_FILE(conn, fsp->fsp_name) == -1) {
+ int sret = -1;
+
+ if (acl_group_override(conn, sbuf.st_gid, fsp->fsp_name)) {
+ DEBUG(5,("set_nt_acl: acl group control on and "
+ "current user in file %s primary group. Override delete_def_acl\n",
+ fsp->fsp_name ));
+
+ become_root();
+ sret = SMB_VFS_SYS_ACL_DELETE_DEF_FILE(conn, fsp->fsp_name);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DEBUG(3,("set_nt_acl: sys_acl_delete_def_file failed (%s)\n", strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+
+ if (acl_set_support) {
+ store_inheritance_attributes(fsp, file_ace_list, dir_ace_list,
+ (psd->type & SE_DESC_DACL_PROTECTED) ? True : False);
+ }
+
+ /*
+ * If we cannot set using POSIX ACLs we fall back to checking if we need to chmod.
+ */
+
+ if(!acl_set_support && acl_perms) {
+ mode_t posix_perms;
+
+ if (!convert_canon_ace_to_posix_perms( fsp, file_ace_list, &posix_perms)) {
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ DEBUG(3,("set_nt_acl: failed to convert file acl to posix permissions for file %s.\n",
+ fsp->fsp_name ));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (orig_mode != posix_perms) {
+
+ DEBUG(3,("set_nt_acl: chmod %s. perms = 0%o.\n",
+ fsp->fsp_name, (unsigned int)posix_perms ));
+
+ if(SMB_VFS_CHMOD(conn,fsp->fsp_name, posix_perms) == -1) {
+ int sret = -1;
+ if (acl_group_override(conn, sbuf.st_gid, fsp->fsp_name)) {
+ DEBUG(5,("set_nt_acl: acl group control on and "
+ "current user in file %s primary group. Override chmod\n",
+ fsp->fsp_name ));
+
+ become_root();
+ sret = SMB_VFS_CHMOD(conn,fsp->fsp_name, posix_perms);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DEBUG(3,("set_nt_acl: chmod %s, 0%o failed. Error = %s.\n",
+ fsp->fsp_name, (unsigned int)posix_perms, strerror(errno) ));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+ }
+
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ }
+
+ /* Any chown pending? */
+ if (need_chown) {
+ DEBUG(3,("set_nt_acl: chown %s. uid = %u, gid = %u.\n",
+ fsp->fsp_name, (unsigned int)user, (unsigned int)grp ));
+
+ if(try_chown( fsp->conn, fsp->fsp_name, user, grp) == -1) {
+ DEBUG(3,("set_nt_acl: chown %s, %u, %u failed. Error = %s.\n",
+ fsp->fsp_name, (unsigned int)user, (unsigned int)grp, strerror(errno) ));
+ if (errno == EPERM) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get the actual group bits stored on a file with an ACL. Has no effect if
+ the file has no ACL. Needed in dosmode code where the stat() will return
+ the mask bits, not the real group bits, for a file with an ACL.
+****************************************************************************/
+
+int get_acl_group_bits( connection_struct *conn, const char *fname, mode_t *mode )
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_T posix_acl;
+ int result = -1;
+
+ posix_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, fname, SMB_ACL_TYPE_ACCESS);
+ if (posix_acl == (SMB_ACL_T)NULL)
+ return -1;
+
+ while (SMB_VFS_SYS_ACL_GET_ENTRY(conn, posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (SMB_VFS_SYS_ACL_GET_TAG_TYPE(conn, entry, &tagtype) ==-1)
+ break;
+
+ if (tagtype == SMB_ACL_GROUP_OBJ) {
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, entry, &permset) == -1) {
+ break;
+ } else {
+ *mode &= ~(S_IRGRP|S_IWGRP|S_IXGRP);
+ *mode |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_READ) ? S_IRGRP : 0);
+ *mode |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_WRITE) ? S_IWGRP : 0);
+ *mode |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_EXECUTE) ? S_IXGRP : 0);
+ result = 0;
+ break;
+ }
+ }
+ }
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, posix_acl);
+ return result;
+}
+
+/****************************************************************************
+ Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL
+ and set the mask to rwx. Needed to preserve complex ACLs set by NT.
+****************************************************************************/
+
+static int chmod_acl_internals( connection_struct *conn, SMB_ACL_T posix_acl, mode_t mode)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ int num_entries = 0;
+
+ while ( SMB_VFS_SYS_ACL_GET_ENTRY(conn, posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ mode_t perms;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (SMB_VFS_SYS_ACL_GET_TAG_TYPE(conn, entry, &tagtype) == -1)
+ return -1;
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, entry, &permset) == -1)
+ return -1;
+
+ num_entries++;
+
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ break;
+ case SMB_ACL_MASK:
+ /*
+ * FIXME: The ACL_MASK entry permissions should really be set to
+ * the union of the permissions of all ACL_USER,
+ * ACL_GROUP_OBJ, and ACL_GROUP entries. That's what
+ * acl_calc_mask() does, but Samba ACLs doesn't provide it.
+ */
+ perms = S_IRUSR|S_IWUSR|S_IXUSR;
+ break;
+ case SMB_ACL_OTHER:
+ perms = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH);
+ break;
+ default:
+ continue;
+ }
+
+ if (map_acl_perms_to_permset(conn, perms, &permset) == -1)
+ return -1;
+
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, entry, permset) == -1)
+ return -1;
+ }
+
+ /*
+ * If this is a simple 3 element ACL or no elements then it's a standard
+ * UNIX permission set. Just use chmod...
+ */
+
+ if ((num_entries == 3) || (num_entries == 0))
+ return -1;
+
+ return 0;
+}
+
+/****************************************************************************
+ Get the access ACL of FROM, do a chmod by setting the ACL USER_OBJ,
+ GROUP_OBJ and OTHER bits in an ACL and set the mask to rwx. Set the
+ resulting ACL on TO. Note that name is in UNIX character set.
+****************************************************************************/
+
+static int copy_access_posix_acl(connection_struct *conn, const char *from, const char *to, mode_t mode)
+{
+ SMB_ACL_T posix_acl = NULL;
+ int ret = -1;
+
+ if ((posix_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, from, SMB_ACL_TYPE_ACCESS)) == NULL)
+ return -1;
+
+ if ((ret = chmod_acl_internals(conn, posix_acl, mode)) == -1)
+ goto done;
+
+ ret = SMB_VFS_SYS_ACL_SET_FILE(conn, to, SMB_ACL_TYPE_ACCESS, posix_acl);
+
+ done:
+
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, posix_acl);
+ return ret;
+}
+
+/****************************************************************************
+ Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL
+ and set the mask to rwx. Needed to preserve complex ACLs set by NT.
+ Note that name is in UNIX character set.
+****************************************************************************/
+
+int chmod_acl(connection_struct *conn, const char *name, mode_t mode)
+{
+ return copy_access_posix_acl(conn, name, name, mode);
+}
+
+/****************************************************************************
+ Check for an existing default POSIX ACL on a directory.
+****************************************************************************/
+
+static bool directory_has_default_posix_acl(connection_struct *conn, const char *fname)
+{
+ SMB_ACL_T def_acl = SMB_VFS_SYS_ACL_GET_FILE( conn, fname, SMB_ACL_TYPE_DEFAULT);
+ bool has_acl = False;
+ SMB_ACL_ENTRY_T entry;
+
+ if (def_acl != NULL && (SMB_VFS_SYS_ACL_GET_ENTRY(conn, def_acl, SMB_ACL_FIRST_ENTRY, &entry) == 1)) {
+ has_acl = True;
+ }
+
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ return has_acl;
+}
+
+/****************************************************************************
+ If the parent directory has no default ACL but it does have an Access ACL,
+ inherit this Access ACL to file name.
+****************************************************************************/
+
+int inherit_access_posix_acl(connection_struct *conn, const char *inherit_from_dir,
+ const char *name, mode_t mode)
+{
+ if (directory_has_default_posix_acl(conn, inherit_from_dir))
+ return 0;
+
+ return copy_access_posix_acl(conn, inherit_from_dir, name, mode);
+}
+
+/****************************************************************************
+ Do an fchmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL
+ and set the mask to rwx. Needed to preserve complex ACLs set by NT.
+****************************************************************************/
+
+int fchmod_acl(files_struct *fsp, mode_t mode)
+{
+ connection_struct *conn = fsp->conn;
+ SMB_ACL_T posix_acl = NULL;
+ int ret = -1;
+
+ if ((posix_acl = SMB_VFS_SYS_ACL_GET_FD(fsp)) == NULL)
+ return -1;
+
+ if ((ret = chmod_acl_internals(conn, posix_acl, mode)) == -1)
+ goto done;
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp, posix_acl);
+
+ done:
+
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, posix_acl);
+ return ret;
+}
+
+/****************************************************************************
+ Map from wire type to permset.
+****************************************************************************/
+
+static bool unix_ex_wire_to_permset(connection_struct *conn, unsigned char wire_perm, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (wire_perm & ~(SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE)) {
+ return False;
+ }
+
+ if (SMB_VFS_SYS_ACL_CLEAR_PERMS(conn, *p_permset) == -1) {
+ return False;
+ }
+
+ if (wire_perm & SMB_POSIX_ACL_READ) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_READ) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_WRITE) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_WRITE) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_EXECUTE) {
+ if (SMB_VFS_SYS_ACL_ADD_PERM(conn, *p_permset, SMB_ACL_EXECUTE) == -1) {
+ return False;
+ }
+ }
+ return True;
+}
+
+/****************************************************************************
+ Map from wire type to tagtype.
+****************************************************************************/
+
+static bool unix_ex_wire_to_tagtype(unsigned char wire_tt, SMB_ACL_TAG_T *p_tt)
+{
+ switch (wire_tt) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ *p_tt = SMB_ACL_USER_OBJ;
+ break;
+ case SMB_POSIX_ACL_USER:
+ *p_tt = SMB_ACL_USER;
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ *p_tt = SMB_ACL_GROUP_OBJ;
+ break;
+ case SMB_POSIX_ACL_GROUP:
+ *p_tt = SMB_ACL_GROUP;
+ break;
+ case SMB_POSIX_ACL_MASK:
+ *p_tt = SMB_ACL_MASK;
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ *p_tt = SMB_ACL_OTHER;
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Create a new POSIX acl from wire permissions.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static SMB_ACL_T create_posix_acl_from_wire(connection_struct *conn, uint16 num_acls, const char *pdata)
+{
+ unsigned int i;
+ SMB_ACL_T the_acl = SMB_VFS_SYS_ACL_INIT(conn, num_acls);
+
+ if (the_acl == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < num_acls; i++) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+ SMB_ACL_TAG_T tag_type;
+
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &the_acl, &the_entry) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to create entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (!unix_ex_wire_to_tagtype(CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), &tag_type)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid wire tagtype %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), i ));
+ goto fail;
+ }
+
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, the_entry, tag_type) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set tagtype on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Get the permset pointer from the new ACL entry. */
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, the_entry, &the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to get permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Map from wire to permissions. */
+ if (!unix_ex_wire_to_permset(conn, CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+1), &the_permset)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid permset %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE) + 1), i ));
+ goto fail;
+ }
+
+ /* Now apply to the new ACL entry. */
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, the_entry, the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to add permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (tag_type == SMB_ACL_USER) {
+ uint32 uidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ uid_t uid = (uid_t)uidval;
+ if (SMB_VFS_SYS_ACL_SET_QUALIFIER(conn, the_entry,(void *)&uid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set uid %u on entry %u. (%s)\n",
+ (unsigned int)uid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ if (tag_type == SMB_ACL_GROUP) {
+ uint32 gidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ gid_t gid = (uid_t)gidval;
+ if (SMB_VFS_SYS_ACL_SET_QUALIFIER(conn, the_entry,(void *)&gid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set gid %u on entry %u. (%s)\n",
+ (unsigned int)gid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+ }
+
+ return the_acl;
+
+ fail:
+
+ if (the_acl != NULL) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, the_acl);
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - Default POSIX ACL set.
+ If num_def_acls == 0 and not a directory just return. If it is a directory
+ and num_def_acls == 0 then remove the default acl. Else set the default acl
+ on the directory.
+****************************************************************************/
+
+bool set_unix_posix_default_acl(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf,
+ uint16 num_def_acls, const char *pdata)
+{
+ SMB_ACL_T def_acl = NULL;
+
+ if (num_def_acls && !S_ISDIR(psbuf->st_mode)) {
+ DEBUG(5,("set_unix_posix_default_acl: Can't set default ACL on non-directory file %s\n", fname ));
+ errno = EISDIR;
+ return False;
+ }
+
+ if (!num_def_acls) {
+ /* Remove the default ACL. */
+ if (SMB_VFS_SYS_ACL_DELETE_DEF_FILE(conn, fname) == -1) {
+ DEBUG(5,("set_unix_posix_default_acl: acl_delete_def_file failed on directory %s (%s)\n",
+ fname, strerror(errno) ));
+ return False;
+ }
+ return True;
+ }
+
+ if ((def_acl = create_posix_acl_from_wire(conn, num_def_acls, pdata)) == NULL) {
+ return False;
+ }
+
+ if (SMB_VFS_SYS_ACL_SET_FILE(conn, fname, SMB_ACL_TYPE_DEFAULT, def_acl) == -1) {
+ DEBUG(5,("set_unix_posix_default_acl: acl_set_file failed on directory %s (%s)\n",
+ fname, strerror(errno) ));
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ return False;
+ }
+
+ DEBUG(10,("set_unix_posix_default_acl: set default acl for file %s\n", fname ));
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ return True;
+}
+
+/****************************************************************************
+ Remove an ACL from a file. As we don't have acl_delete_entry() available
+ we must read the current acl and copy all entries except MASK, USER and GROUP
+ to a new acl, then set that. This (at least on Linux) causes any ACL to be
+ removed.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static bool remove_posix_acl(connection_struct *conn, files_struct *fsp, const char *fname)
+{
+ SMB_ACL_T file_acl = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ bool ret = False;
+ /* Create a new ACL with only 3 entries, u/g/w. */
+ SMB_ACL_T new_file_acl = SMB_VFS_SYS_ACL_INIT(conn, 3);
+ SMB_ACL_ENTRY_T user_ent = NULL;
+ SMB_ACL_ENTRY_T group_ent = NULL;
+ SMB_ACL_ENTRY_T other_ent = NULL;
+
+ if (new_file_acl == NULL) {
+ DEBUG(5,("remove_posix_acl: failed to init new ACL with 3 entries for file %s.\n", fname));
+ return False;
+ }
+
+ /* Now create the u/g/w entries. */
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &new_file_acl, &user_ent) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to create user entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, user_ent, SMB_ACL_USER_OBJ) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to set user entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &new_file_acl, &group_ent) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to create group entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, group_ent, SMB_ACL_GROUP_OBJ) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to set group entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ if (SMB_VFS_SYS_ACL_CREATE_ENTRY(conn, &new_file_acl, &other_ent) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to create other entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+ if (SMB_VFS_SYS_ACL_SET_TAG_TYPE(conn, other_ent, SMB_ACL_OTHER) == -1) {
+ DEBUG(5,("remove_posix_acl: Failed to set other entry for file %s. (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ /* Get the current file ACL. */
+ if (fsp && fsp->fh->fd != -1) {
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp);
+ } else {
+ file_acl = SMB_VFS_SYS_ACL_GET_FILE( conn, fname, SMB_ACL_TYPE_ACCESS);
+ }
+
+ if (file_acl == NULL) {
+ /* This is only returned if an error occurred. Even for a file with
+ no acl a u/g/w acl should be returned. */
+ DEBUG(5,("remove_posix_acl: failed to get ACL from file %s (%s).\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ while ( SMB_VFS_SYS_ACL_GET_ENTRY(conn, file_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (SMB_VFS_SYS_ACL_GET_TAG_TYPE(conn, entry, &tagtype) == -1) {
+ DEBUG(5,("remove_posix_acl: failed to get tagtype from ACL on file %s (%s).\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, entry, &permset) == -1) {
+ DEBUG(5,("remove_posix_acl: failed to get permset from ACL on file %s (%s).\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+
+ if (tagtype == SMB_ACL_USER_OBJ) {
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, user_ent, permset) == -1) {
+ DEBUG(5,("remove_posix_acl: failed to set permset from ACL on file %s (%s).\n",
+ fname, strerror(errno) ));
+ }
+ } else if (tagtype == SMB_ACL_GROUP_OBJ) {
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, group_ent, permset) == -1) {
+ DEBUG(5,("remove_posix_acl: failed to set permset from ACL on file %s (%s).\n",
+ fname, strerror(errno) ));
+ }
+ } else if (tagtype == SMB_ACL_OTHER) {
+ if (SMB_VFS_SYS_ACL_SET_PERMSET(conn, other_ent, permset) == -1) {
+ DEBUG(5,("remove_posix_acl: failed to set permset from ACL on file %s (%s).\n",
+ fname, strerror(errno) ));
+ }
+ }
+ }
+
+ /* Set the new empty file ACL. */
+ if (fsp && fsp->fh->fd != -1) {
+ if (SMB_VFS_SYS_ACL_SET_FD(fsp, new_file_acl) == -1) {
+ DEBUG(5,("remove_posix_acl: acl_set_file failed on %s (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+ } else {
+ if (SMB_VFS_SYS_ACL_SET_FILE(conn, fname, SMB_ACL_TYPE_ACCESS, new_file_acl) == -1) {
+ DEBUG(5,("remove_posix_acl: acl_set_file failed on %s (%s)\n",
+ fname, strerror(errno) ));
+ goto done;
+ }
+ }
+
+ ret = True;
+
+ done:
+
+ if (file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ }
+ if (new_file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, new_file_acl);
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - POSIX ACL set.
+ If num_def_acls == 0 then read/modify/write acl after removing all entries
+ except SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER.
+****************************************************************************/
+
+bool set_unix_posix_acl(connection_struct *conn, files_struct *fsp, const char *fname, uint16 num_acls, const char *pdata)
+{
+ SMB_ACL_T file_acl = NULL;
+
+ if (!num_acls) {
+ /* Remove the ACL from the file. */
+ return remove_posix_acl(conn, fsp, fname);
+ }
+
+ if ((file_acl = create_posix_acl_from_wire(conn, num_acls, pdata)) == NULL) {
+ return False;
+ }
+
+ if (fsp && fsp->fh->fd != -1) {
+ /* The preferred way - use an open fd. */
+ if (SMB_VFS_SYS_ACL_SET_FD(fsp, file_acl) == -1) {
+ DEBUG(5,("set_unix_posix_acl: acl_set_file failed on %s (%s)\n",
+ fname, strerror(errno) ));
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ return False;
+ }
+ } else {
+ if (SMB_VFS_SYS_ACL_SET_FILE(conn, fname, SMB_ACL_TYPE_ACCESS, file_acl) == -1) {
+ DEBUG(5,("set_unix_posix_acl: acl_set_file failed on %s (%s)\n",
+ fname, strerror(errno) ));
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ return False;
+ }
+ }
+
+ DEBUG(10,("set_unix_posix_acl: set acl for file %s\n", fname ));
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ return True;
+}
+
+/********************************************************************
+ Pull the NT ACL from a file on disk or the OpenEventlog() access
+ check. Caller is responsible for freeing the returned security
+ descriptor via TALLOC_FREE(). This is designed for dealing with
+ user space access checks in smbd outside of the VFS. For example,
+ checking access rights in OpenEventlog().
+
+ Assume we are dealing with files (for now)
+********************************************************************/
+
+SEC_DESC *get_nt_acl_no_snum( TALLOC_CTX *ctx, const char *fname)
+{
+ SEC_DESC *psd, *ret_sd;
+ connection_struct *conn;
+ files_struct finfo;
+ struct fd_handle fh;
+
+ conn = TALLOC_ZERO_P(ctx, connection_struct);
+ if (conn == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ if (!(conn->params = TALLOC_P(conn, struct share_params))) {
+ DEBUG(0,("get_nt_acl_no_snum: talloc() failed!\n"));
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+
+ conn->params->service = -1;
+
+ set_conn_connectpath(conn, "/");
+
+ if (!smbd_vfs_init(conn)) {
+ DEBUG(0,("get_nt_acl_no_snum: Unable to create a fake connection struct!\n"));
+ conn_free_internal( conn );
+ return NULL;
+ }
+
+ ZERO_STRUCT( finfo );
+ ZERO_STRUCT( fh );
+
+ finfo.fnum = -1;
+ finfo.conn = conn;
+ finfo.fh = &fh;
+ finfo.fh->fd = -1;
+ finfo.fsp_name = CONST_DISCARD(char *,fname);
+
+ if (!NT_STATUS_IS_OK(posix_fget_nt_acl( &finfo, DACL_SECURITY_INFORMATION, &psd))) {
+ DEBUG(0,("get_nt_acl_no_snum: get_nt_acl returned zero.\n"));
+ conn_free_internal( conn );
+ return NULL;
+ }
+
+ ret_sd = dup_sec_desc( ctx, psd );
+
+ conn_free_internal( conn );
+
+ return ret_sd;
+}
diff --git a/source3/smbd/process.c b/source3/smbd/process.c
new file mode 100644
index 0000000000..b2d19e11e3
--- /dev/null
+++ b/source3/smbd/process.c
@@ -0,0 +1,2113 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ 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"
+
+extern int smb_echo_count;
+
+/*
+ * Size of data we can send to client. Set
+ * by the client for all protocols above CORE.
+ * Set by us for CORE protocol.
+ */
+int max_send = BUFFER_SIZE;
+/*
+ * Size of the data we can receive. Set by us.
+ * Can be modified by the max xmit parameter.
+ */
+int max_recv = BUFFER_SIZE;
+
+SIG_ATOMIC_T reload_after_sighup = 0;
+SIG_ATOMIC_T got_sig_term = 0;
+extern bool global_machine_password_needs_changing;
+extern int max_send;
+
+/* Accessor function for smb_read_error for smbd functions. */
+
+/****************************************************************************
+ Send an smb to a fd.
+****************************************************************************/
+
+bool srv_send_smb(int fd, char *buffer, bool do_encrypt)
+{
+ size_t len;
+ size_t nwritten=0;
+ ssize_t ret;
+ char *buf_out = buffer;
+
+ /* Sign the outgoing packet if required. */
+ srv_calculate_sign_mac(buf_out);
+
+ if (do_encrypt) {
+ NTSTATUS status = srv_encrypt_buffer(buffer, &buf_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("send_smb: SMB encryption failed "
+ "on outgoing packet! Error %s\n",
+ nt_errstr(status) ));
+ return false;
+ }
+ }
+
+ len = smb_len(buf_out) + 4;
+
+ while (nwritten < len) {
+ ret = write_data(fd,buf_out+nwritten,len - nwritten);
+ if (ret <= 0) {
+ DEBUG(0,("Error writing %d bytes to client. %d. (%s)\n",
+ (int)len,(int)ret, strerror(errno) ));
+ srv_free_enc_buffer(buf_out);
+ return false;
+ }
+ nwritten += ret;
+ }
+
+ srv_free_enc_buffer(buf_out);
+ return true;
+}
+
+/*******************************************************************
+ Setup the word count and byte count for a smb message.
+********************************************************************/
+
+int srv_set_message(char *buf,
+ int num_words,
+ int num_bytes,
+ bool zero)
+{
+ if (zero && (num_words || num_bytes)) {
+ memset(buf + smb_size,'\0',num_words*2 + num_bytes);
+ }
+ SCVAL(buf,smb_wct,num_words);
+ SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
+ smb_setlen(buf,(smb_size + num_words*2 + num_bytes - 4));
+ return (smb_size + num_words*2 + num_bytes);
+}
+
+static bool valid_smb_header(const uint8_t *inbuf)
+{
+ if (is_encrypted_packet(inbuf)) {
+ return true;
+ }
+ return (strncmp(smb_base(inbuf),"\377SMB",4) == 0);
+}
+
+/* Socket functions for smbd packet processing. */
+
+static bool valid_packet_size(size_t len)
+{
+ /*
+ * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
+ * of header. Don't print the error if this fits.... JRA.
+ */
+
+ if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
+ DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+ (unsigned long)len));
+ return false;
+ }
+ return true;
+}
+
+static NTSTATUS read_packet_remainder(int fd, char *buffer,
+ unsigned int timeout, ssize_t len)
+{
+ if (len <= 0) {
+ return NT_STATUS_OK;
+ }
+
+ return read_socket_with_timeout(fd, buffer, len, len, timeout, NULL);
+}
+
+/****************************************************************************
+ Attempt a zerocopy writeX read. We know here that len > smb_size-4
+****************************************************************************/
+
+/*
+ * Unfortunately, earlier versions of smbclient/libsmbclient
+ * don't send this "standard" writeX header. I've fixed this
+ * for 3.2 but we'll use the old method with earlier versions.
+ * Windows and CIFSFS at least use this standard size. Not
+ * sure about MacOSX.
+ */
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
+ const char lenbuf[4],
+ int fd, char **buffer,
+ unsigned int timeout,
+ size_t *p_unread,
+ size_t *len_ret)
+{
+ /* Size of a WRITEX call (+4 byte len). */
+ char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
+ ssize_t len = smb_len_large(lenbuf); /* Could be a UNIX large writeX. */
+ ssize_t toread;
+ NTSTATUS status;
+
+ memcpy(writeX_header, lenbuf, 4);
+
+ status = read_socket_with_timeout(
+ fd, writeX_header + 4,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Ok - now try and see if this is a possible
+ * valid writeX call.
+ */
+
+ if (is_valid_writeX_buffer((uint8_t *)writeX_header)) {
+ /*
+ * If the data offset is beyond what
+ * we've read, drain the extra bytes.
+ */
+ uint16_t doff = SVAL(writeX_header,smb_vwv11);
+ ssize_t newlen;
+
+ if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ if (drain_socket(smbd_server_fd(), drain) != drain) {
+ smb_panic("receive_smb_raw_talloc_partial_read:"
+ " failed to drain pending bytes");
+ }
+ } else {
+ doff = STANDARD_WRITE_AND_X_HEADER_SIZE;
+ }
+
+ /* Spoof down the length and null out the bcc. */
+ set_message_bcc(writeX_header, 0);
+ newlen = smb_len(writeX_header);
+
+ /* Copy the header we've written. */
+
+ *buffer = (char *)TALLOC_MEMDUP(mem_ctx,
+ writeX_header,
+ sizeof(writeX_header));
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)sizeof(writeX_header)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Work out the remaining bytes. */
+ *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ *len_ret = newlen + 4;
+ return NT_STATUS_OK;
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Not a valid writeX call. Just do the standard
+ * talloc and return.
+ */
+
+ *buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Copy in what we already read. */
+ memcpy(*buffer,
+ writeX_header,
+ 4 + STANDARD_WRITE_AND_X_HEADER_SIZE);
+ toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+ if(toread > 0) {
+ status = read_packet_remainder(
+ fd, (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, toread);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("receive_smb_raw_talloc_partial_read: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ *len_ret = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, size_t *plen)
+{
+ char lenbuf[4];
+ size_t len;
+ int min_recv_size = lp_min_receive_file_size();
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ status = read_smb_length_return_keepalive(fd, lenbuf, timeout, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("receive_smb_raw: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ if (CVAL(lenbuf,0) == 0 &&
+ min_recv_size &&
+ smb_len_large(lenbuf) > min_recv_size && /* Could be a UNIX large writeX. */
+ !srv_is_signing_active()) {
+
+ return receive_smb_raw_talloc_partial_read(
+ mem_ctx, lenbuf, fd, buffer, timeout, p_unread, plen);
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+
+ *buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(fd, (*buffer)+4, timeout, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *plen = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len)
+{
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_encrypted = false;
+
+ status = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout,
+ p_unread, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (is_encrypted_packet((uint8_t *)*buffer)) {
+ status = srv_decrypt_buffer(*buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("receive_smb_talloc: SMB decryption failed on "
+ "incoming packet! Error %s\n",
+ nt_errstr(status) ));
+ return status;
+ }
+ *p_encrypted = true;
+ }
+
+ /* Check the incoming SMB signature. */
+ if (!srv_check_sign_mac(*buffer, true)) {
+ DEBUG(0, ("receive_smb: SMB Signature verification failed on "
+ "incoming packet!\n"));
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Initialize a struct smb_request from an inbuf
+ */
+
+void init_smb_request(struct smb_request *req,
+ const uint8 *inbuf,
+ size_t unread_bytes,
+ bool encrypted)
+{
+ size_t req_size = smb_len(inbuf) + 4;
+ /* Ensure we have at least smb_size bytes. */
+ if (req_size < smb_size) {
+ DEBUG(0,("init_smb_request: invalid request size %u\n",
+ (unsigned int)req_size ));
+ exit_server_cleanly("Invalid SMB request");
+ }
+ req->flags2 = SVAL(inbuf, smb_flg2);
+ req->smbpid = SVAL(inbuf, smb_pid);
+ req->mid = SVAL(inbuf, smb_mid);
+ req->vuid = SVAL(inbuf, smb_uid);
+ req->tid = SVAL(inbuf, smb_tid);
+ req->wct = CVAL(inbuf, smb_wct);
+ req->unread_bytes = unread_bytes;
+ req->encrypted = encrypted;
+ req->conn = conn_find(req->tid);
+
+ /* Ensure we have at least wct words and 2 bytes of bcc. */
+ if (smb_size + req->wct*2 > req_size) {
+ DEBUG(0,("init_smb_request: invalid wct number %u (size %u)\n",
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ exit_server_cleanly("Invalid SMB request");
+ }
+ /* Ensure bcc is correct. */
+ if (((uint8 *)smb_buf(inbuf)) + smb_buflen(inbuf) > inbuf + req_size) {
+ DEBUG(0,("init_smb_request: invalid bcc number %u "
+ "(wct = %u, size %u)\n",
+ (unsigned int)smb_buflen(inbuf),
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ exit_server_cleanly("Invalid SMB request");
+ }
+ req->inbuf = inbuf;
+ req->outbuf = NULL;
+}
+
+/****************************************************************************
+ structure to hold a linked list of queued messages.
+ for processing.
+****************************************************************************/
+
+static struct pending_message_list *deferred_open_queue;
+
+/****************************************************************************
+ Function to push a message onto the tail of a linked list of smb messages ready
+ for processing.
+****************************************************************************/
+
+static bool push_queued_message(struct smb_request *req,
+ struct timeval request_time,
+ struct timeval end_time,
+ char *private_data, size_t private_len)
+{
+ int msg_len = smb_len(req->inbuf) + 4;
+ struct pending_message_list *msg;
+
+ msg = TALLOC_ZERO_P(NULL, struct pending_message_list);
+
+ if(msg == NULL) {
+ DEBUG(0,("push_message: malloc fail (1)\n"));
+ return False;
+ }
+
+ msg->buf = data_blob_talloc(msg, req->inbuf, msg_len);
+ if(msg->buf.data == NULL) {
+ DEBUG(0,("push_message: malloc fail (2)\n"));
+ TALLOC_FREE(msg);
+ return False;
+ }
+
+ msg->request_time = request_time;
+ msg->end_time = end_time;
+ msg->encrypted = req->encrypted;
+
+ if (private_data) {
+ msg->private_data = data_blob_talloc(msg, private_data,
+ private_len);
+ if (msg->private_data.data == NULL) {
+ DEBUG(0,("push_message: malloc fail (3)\n"));
+ TALLOC_FREE(msg);
+ return False;
+ }
+ }
+
+ DLIST_ADD_END(deferred_open_queue, msg, struct pending_message_list *);
+
+ DEBUG(10,("push_message: pushed message length %u on "
+ "deferred_open_queue\n", (unsigned int)msg_len));
+
+ return True;
+}
+
+/****************************************************************************
+ Function to delete a sharing violation open message by mid.
+****************************************************************************/
+
+void remove_deferred_open_smb_message(uint16 mid)
+{
+ struct pending_message_list *pml;
+
+ for (pml = deferred_open_queue; pml; pml = pml->next) {
+ if (mid == SVAL(pml->buf.data,smb_mid)) {
+ DEBUG(10,("remove_sharing_violation_open_smb_message: "
+ "deleting mid %u len %u\n",
+ (unsigned int)mid,
+ (unsigned int)pml->buf.length ));
+ DLIST_REMOVE(deferred_open_queue, pml);
+ TALLOC_FREE(pml);
+ return;
+ }
+ }
+}
+
+/****************************************************************************
+ Move a sharing violation open retry message to the front of the list and
+ schedule it for immediate processing.
+****************************************************************************/
+
+void schedule_deferred_open_smb_message(uint16 mid)
+{
+ struct pending_message_list *pml;
+ int i = 0;
+
+ for (pml = deferred_open_queue; pml; pml = pml->next) {
+ uint16 msg_mid = SVAL(pml->buf.data,smb_mid);
+ DEBUG(10,("schedule_deferred_open_smb_message: [%d] msg_mid = %u\n", i++,
+ (unsigned int)msg_mid ));
+ if (mid == msg_mid) {
+ DEBUG(10,("schedule_deferred_open_smb_message: scheduling mid %u\n",
+ mid ));
+ pml->end_time.tv_sec = 0;
+ pml->end_time.tv_usec = 0;
+ DLIST_PROMOTE(deferred_open_queue, pml);
+ return;
+ }
+ }
+
+ DEBUG(10,("schedule_deferred_open_smb_message: failed to find message mid %u\n",
+ mid ));
+}
+
+/****************************************************************************
+ Return true if this mid is on the deferred queue.
+****************************************************************************/
+
+bool open_was_deferred(uint16 mid)
+{
+ struct pending_message_list *pml;
+
+ for (pml = deferred_open_queue; pml; pml = pml->next) {
+ if (SVAL(pml->buf.data,smb_mid) == mid) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Return the message queued by this mid.
+****************************************************************************/
+
+struct pending_message_list *get_open_deferred_message(uint16 mid)
+{
+ struct pending_message_list *pml;
+
+ for (pml = deferred_open_queue; pml; pml = pml->next) {
+ if (SVAL(pml->buf.data,smb_mid) == mid) {
+ return pml;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Function to push a deferred open smb message onto a linked list of local smb
+ messages ready for processing.
+****************************************************************************/
+
+bool push_deferred_smb_message(struct smb_request *req,
+ struct timeval request_time,
+ struct timeval timeout,
+ char *private_data, size_t priv_len)
+{
+ struct timeval end_time;
+
+ if (req->unread_bytes) {
+ DEBUG(0,("push_deferred_smb_message: logic error ! "
+ "unread_bytes = %u\n",
+ (unsigned int)req->unread_bytes ));
+ smb_panic("push_deferred_smb_message: "
+ "logic error unread_bytes != 0" );
+ }
+
+ end_time = timeval_sum(&request_time, &timeout);
+
+ DEBUG(10,("push_deferred_open_smb_message: pushing message len %u mid %u "
+ "timeout time [%u.%06u]\n",
+ (unsigned int) smb_len(req->inbuf)+4, (unsigned int)req->mid,
+ (unsigned int)end_time.tv_sec,
+ (unsigned int)end_time.tv_usec));
+
+ return push_queued_message(req, request_time, end_time,
+ private_data, priv_len);
+}
+
+struct idle_event {
+ struct timed_event *te;
+ struct timeval interval;
+ char *name;
+ bool (*handler)(const struct timeval *now, void *private_data);
+ void *private_data;
+};
+
+static void idle_event_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct idle_event *event =
+ talloc_get_type_abort(private_data, struct idle_event);
+
+ TALLOC_FREE(event->te);
+
+ if (!event->handler(now, event->private_data)) {
+ /* Don't repeat, delete ourselves */
+ TALLOC_FREE(event);
+ return;
+ }
+
+ event->te = event_add_timed(ctx, event,
+ timeval_sum(now, &event->interval),
+ event->name,
+ idle_event_handler, event);
+
+ /* We can't do much but fail here. */
+ SMB_ASSERT(event->te != NULL);
+}
+
+struct idle_event *event_add_idle(struct event_context *event_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct timeval interval,
+ const char *name,
+ bool (*handler)(const struct timeval *now,
+ void *private_data),
+ void *private_data)
+{
+ struct idle_event *result;
+ struct timeval now = timeval_current();
+
+ result = TALLOC_P(mem_ctx, struct idle_event);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->interval = interval;
+ result->handler = handler;
+ result->private_data = private_data;
+
+ if (!(result->name = talloc_asprintf(result, "idle_evt(%s)", name))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->te = event_add_timed(event_ctx, result,
+ timeval_sum(&now, &interval),
+ result->name,
+ idle_event_handler, result);
+ if (result->te == NULL) {
+ DEBUG(0, ("event_add_timed failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+/****************************************************************************
+ Do all async processing in here. This includes kernel oplock messages, change
+ notify events etc.
+****************************************************************************/
+
+static void async_processing(fd_set *pfds)
+{
+ DEBUG(10,("async_processing: Doing async processing.\n"));
+
+ process_aio_queue();
+
+ process_kernel_oplocks(smbd_messaging_context(), pfds);
+
+ /* Do the aio check again after receive_local_message as it does a
+ select and may have eaten our signal. */
+ /* Is this till true? -- vl */
+ process_aio_queue();
+
+ if (got_sig_term) {
+ exit_server_cleanly("termination signal");
+ }
+
+ /* check for sighup processing */
+ if (reload_after_sighup) {
+ change_to_root_user();
+ DEBUG(1,("Reloading services after SIGHUP\n"));
+ reload_services(False);
+ reload_after_sighup = 0;
+ }
+}
+
+/****************************************************************************
+ Add a fd to the set we will be select(2)ing on.
+****************************************************************************/
+
+static int select_on_fd(int fd, int maxfd, fd_set *fds)
+{
+ if (fd != -1) {
+ FD_SET(fd, fds);
+ maxfd = MAX(maxfd, fd);
+ }
+
+ return maxfd;
+}
+
+/****************************************************************************
+ Do a select on an two fd's - with timeout.
+
+ If a local udp message has been pushed onto the
+ queue (this can only happen during oplock break
+ processing) call async_processing()
+
+ If a pending smb message has been pushed onto the
+ queue (this can only happen during oplock break
+ processing) return this next.
+
+ If the first smbfd is ready then read an smb from it.
+ if the second (loopback UDP) fd is ready then read a message
+ from it and setup the buffer header to identify the length
+ and from address.
+ Returns False on timeout or error.
+ Else returns True.
+
+The timeout is in milliseconds
+****************************************************************************/
+
+static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
+ size_t *buffer_len, int timeout,
+ size_t *p_unread, bool *p_encrypted)
+{
+ fd_set r_fds, w_fds;
+ int selrtn;
+ struct timeval to;
+ int maxfd = 0;
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ again:
+
+ if (timeout >= 0) {
+ to.tv_sec = timeout / 1000;
+ to.tv_usec = (timeout % 1000) * 1000;
+ } else {
+ to.tv_sec = SMBD_SELECT_TIMEOUT;
+ to.tv_usec = 0;
+ }
+
+ /*
+ * Note that this call must be before processing any SMB
+ * messages as we need to synchronously process any messages
+ * we may have sent to ourselves from the previous SMB.
+ */
+ message_dispatch(smbd_messaging_context());
+
+ /*
+ * Check to see if we already have a message on the deferred open queue
+ * and it's time to schedule.
+ */
+ if(deferred_open_queue != NULL) {
+ bool pop_message = False;
+ struct pending_message_list *msg = deferred_open_queue;
+
+ if (timeval_is_zero(&msg->end_time)) {
+ pop_message = True;
+ } else {
+ struct timeval tv;
+ SMB_BIG_INT tdif;
+
+ GetTimeOfDay(&tv);
+ tdif = usec_time_diff(&msg->end_time, &tv);
+ if (tdif <= 0) {
+ /* Timed out. Schedule...*/
+ pop_message = True;
+ DEBUG(10,("receive_message_or_smb: queued message timed out.\n"));
+ } else {
+ /* Make a more accurate select timeout. */
+ to.tv_sec = tdif / 1000000;
+ to.tv_usec = tdif % 1000000;
+ DEBUG(10,("receive_message_or_smb: select with timeout of [%u.%06u]\n",
+ (unsigned int)to.tv_sec, (unsigned int)to.tv_usec ));
+ }
+ }
+
+ if (pop_message) {
+
+ *buffer = (char *)talloc_memdup(mem_ctx, msg->buf.data,
+ msg->buf.length);
+ if (*buffer == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ *buffer_len = msg->buf.length;
+ *p_encrypted = msg->encrypted;
+
+ /* We leave this message on the queue so the open code can
+ know this is a retry. */
+ DEBUG(5,("receive_message_or_smb: returning deferred open smb message.\n"));
+ return NT_STATUS_OK;
+ }
+ }
+
+ /*
+ * Setup the select fd sets.
+ */
+
+ FD_ZERO(&r_fds);
+ FD_ZERO(&w_fds);
+
+ /*
+ * Ensure we process oplock break messages by preference.
+ * We have to do this before the select, after the select
+ * and if the select returns EINTR. This is due to the fact
+ * that the selects called from async_processing can eat an EINTR
+ * caused by a signal (we can't take the break message there).
+ * This is hideously complex - *MUST* be simplified for 3.0 ! JRA.
+ */
+
+ if (oplock_message_waiting(&r_fds)) {
+ DEBUG(10,("receive_message_or_smb: oplock_message is waiting.\n"));
+ async_processing(&r_fds);
+ /*
+ * After async processing we must go and do the select again, as
+ * the state of the flag in fds for the server file descriptor is
+ * indeterminate - we may have done I/O on it in the oplock processing. JRA.
+ */
+ goto again;
+ }
+
+ /*
+ * Are there any timed events waiting ? If so, ensure we don't
+ * select for longer than it would take to wait for them.
+ */
+
+ {
+ struct timeval now;
+ GetTimeOfDay(&now);
+
+ event_add_to_select_args(smbd_event_context(), &now,
+ &r_fds, &w_fds, &to, &maxfd);
+ }
+
+ if (timeval_is_zero(&to)) {
+ /* Process a timed event now... */
+ if (run_events(smbd_event_context(), 0, NULL, NULL)) {
+ goto again;
+ }
+ }
+
+ {
+ int sav;
+ START_PROFILE(smbd_idle);
+
+ maxfd = select_on_fd(smbd_server_fd(), maxfd, &r_fds);
+ maxfd = select_on_fd(oplock_notify_fd(), maxfd, &r_fds);
+
+ selrtn = sys_select(maxfd+1,&r_fds,&w_fds,NULL,&to);
+ sav = errno;
+
+ END_PROFILE(smbd_idle);
+ errno = sav;
+ }
+
+ if (run_events(smbd_event_context(), selrtn, &r_fds, &w_fds)) {
+ goto again;
+ }
+
+ /* if we get EINTR then maybe we have received an oplock
+ signal - treat this as select returning 1. This is ugly, but
+ is the best we can do until the oplock code knows more about
+ signals */
+ if (selrtn == -1 && errno == EINTR) {
+ async_processing(&r_fds);
+ /*
+ * After async processing we must go and do the select again, as
+ * the state of the flag in fds for the server file descriptor is
+ * indeterminate - we may have done I/O on it in the oplock processing. JRA.
+ */
+ goto again;
+ }
+
+ /* Check if error */
+ if (selrtn == -1) {
+ /* something is wrong. Maybe the socket is dead? */
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Did we timeout ? */
+ if (selrtn == 0) {
+ return NT_STATUS_IO_TIMEOUT;
+ }
+
+ /*
+ * Ensure we process oplock break messages by preference.
+ * This is IMPORTANT ! Otherwise we can starve other processes
+ * sending us an oplock break message. JRA.
+ */
+
+ if (oplock_message_waiting(&r_fds)) {
+ async_processing(&r_fds);
+ /*
+ * After async processing we must go and do the select again, as
+ * the state of the flag in fds for the server file descriptor is
+ * indeterminate - we may have done I/O on it in the oplock processing. JRA.
+ */
+ goto again;
+ }
+
+ /*
+ * We've just woken up from a protentially long select sleep.
+ * Ensure we process local messages as we need to synchronously
+ * process any messages from other smbd's to avoid file rename race
+ * conditions. This call is cheap if there are no messages waiting.
+ * JRA.
+ */
+ message_dispatch(smbd_messaging_context());
+
+ status = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0,
+ p_unread, p_encrypted, &len);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *buffer_len = len;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Only allow 5 outstanding trans requests. We're allocating memory, so
+ * prevent a DoS.
+ */
+
+NTSTATUS allow_new_trans(struct trans_state *list, int mid)
+{
+ int count = 0;
+ for (; list != NULL; list = list->next) {
+
+ if (list->mid == mid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ count += 1;
+ }
+ if (count > 5) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ We're terminating and have closed all our files/connections etc.
+ If there are any pending local messages we need to respond to them
+ before termination so that other smbds don't think we just died whilst
+ holding oplocks.
+****************************************************************************/
+
+void respond_to_all_remaining_local_messages(void)
+{
+ /*
+ * Assert we have no exclusive open oplocks.
+ */
+
+ if(get_number_of_exclusive_open_oplocks()) {
+ DEBUG(0,("respond_to_all_remaining_local_messages: PANIC : we have %d exclusive oplocks.\n",
+ get_number_of_exclusive_open_oplocks() ));
+ return;
+ }
+
+ process_kernel_oplocks(smbd_messaging_context(), NULL);
+
+ return;
+}
+
+
+/*
+These flags determine some of the permissions required to do an operation
+
+Note that I don't set NEED_WRITE on some write operations because they
+are used by some brain-dead clients when printing, and I don't want to
+force write permissions on print services.
+*/
+#define AS_USER (1<<0)
+#define NEED_WRITE (1<<1) /* Must be paired with AS_USER */
+#define TIME_INIT (1<<2)
+#define CAN_IPC (1<<3) /* Must be paired with AS_USER */
+#define AS_GUEST (1<<5) /* Must *NOT* be paired with AS_USER */
+#define DO_CHDIR (1<<6)
+
+/*
+ define a list of possible SMB messages and their corresponding
+ functions. Any message that has a NULL function is unimplemented -
+ please feel free to contribute implementations!
+*/
+static const struct smb_message_struct {
+ const char *name;
+ void (*fn_new)(struct smb_request *req);
+ int flags;
+} smb_messages[256] = {
+
+/* 0x00 */ { "SMBmkdir",reply_mkdir,AS_USER | NEED_WRITE},
+/* 0x01 */ { "SMBrmdir",reply_rmdir,AS_USER | NEED_WRITE},
+/* 0x02 */ { "SMBopen",reply_open,AS_USER },
+/* 0x03 */ { "SMBcreate",reply_mknew,AS_USER},
+/* 0x04 */ { "SMBclose",reply_close,AS_USER | CAN_IPC },
+/* 0x05 */ { "SMBflush",reply_flush,AS_USER},
+/* 0x06 */ { "SMBunlink",reply_unlink,AS_USER | NEED_WRITE },
+/* 0x07 */ { "SMBmv",reply_mv,AS_USER | NEED_WRITE },
+/* 0x08 */ { "SMBgetatr",reply_getatr,AS_USER},
+/* 0x09 */ { "SMBsetatr",reply_setatr,AS_USER | NEED_WRITE},
+/* 0x0a */ { "SMBread",reply_read,AS_USER},
+/* 0x0b */ { "SMBwrite",reply_write,AS_USER | CAN_IPC },
+/* 0x0c */ { "SMBlock",reply_lock,AS_USER},
+/* 0x0d */ { "SMBunlock",reply_unlock,AS_USER},
+/* 0x0e */ { "SMBctemp",reply_ctemp,AS_USER },
+/* 0x0f */ { "SMBmknew",reply_mknew,AS_USER},
+/* 0x10 */ { "SMBcheckpath",reply_checkpath,AS_USER},
+/* 0x11 */ { "SMBexit",reply_exit,DO_CHDIR},
+/* 0x12 */ { "SMBlseek",reply_lseek,AS_USER},
+/* 0x13 */ { "SMBlockread",reply_lockread,AS_USER},
+/* 0x14 */ { "SMBwriteunlock",reply_writeunlock,AS_USER},
+/* 0x15 */ { NULL, NULL, 0 },
+/* 0x16 */ { NULL, NULL, 0 },
+/* 0x17 */ { NULL, NULL, 0 },
+/* 0x18 */ { NULL, NULL, 0 },
+/* 0x19 */ { NULL, NULL, 0 },
+/* 0x1a */ { "SMBreadbraw",reply_readbraw,AS_USER},
+/* 0x1b */ { "SMBreadBmpx",reply_readbmpx,AS_USER},
+/* 0x1c */ { "SMBreadBs",reply_readbs,AS_USER },
+/* 0x1d */ { "SMBwritebraw",reply_writebraw,AS_USER},
+/* 0x1e */ { "SMBwriteBmpx",reply_writebmpx,AS_USER},
+/* 0x1f */ { "SMBwriteBs",reply_writebs,AS_USER},
+/* 0x20 */ { "SMBwritec", NULL,0},
+/* 0x21 */ { NULL, NULL, 0 },
+/* 0x22 */ { "SMBsetattrE",reply_setattrE,AS_USER | NEED_WRITE },
+/* 0x23 */ { "SMBgetattrE",reply_getattrE,AS_USER },
+/* 0x24 */ { "SMBlockingX",reply_lockingX,AS_USER },
+/* 0x25 */ { "SMBtrans",reply_trans,AS_USER | CAN_IPC },
+/* 0x26 */ { "SMBtranss",reply_transs,AS_USER | CAN_IPC},
+/* 0x27 */ { "SMBioctl",reply_ioctl,0},
+/* 0x28 */ { "SMBioctls", NULL,AS_USER},
+/* 0x29 */ { "SMBcopy",reply_copy,AS_USER | NEED_WRITE },
+/* 0x2a */ { "SMBmove", NULL,AS_USER | NEED_WRITE },
+/* 0x2b */ { "SMBecho",reply_echo,0},
+/* 0x2c */ { "SMBwriteclose",reply_writeclose,AS_USER},
+/* 0x2d */ { "SMBopenX",reply_open_and_X,AS_USER | CAN_IPC },
+/* 0x2e */ { "SMBreadX",reply_read_and_X,AS_USER | CAN_IPC },
+/* 0x2f */ { "SMBwriteX",reply_write_and_X,AS_USER | CAN_IPC },
+/* 0x30 */ { NULL, NULL, 0 },
+/* 0x31 */ { NULL, NULL, 0 },
+/* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC },
+/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER},
+/* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER},
+/* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER},
+/* 0x36 */ { NULL, NULL, 0 },
+/* 0x37 */ { NULL, NULL, 0 },
+/* 0x38 */ { NULL, NULL, 0 },
+/* 0x39 */ { NULL, NULL, 0 },
+/* 0x3a */ { NULL, NULL, 0 },
+/* 0x3b */ { NULL, NULL, 0 },
+/* 0x3c */ { NULL, NULL, 0 },
+/* 0x3d */ { NULL, NULL, 0 },
+/* 0x3e */ { NULL, NULL, 0 },
+/* 0x3f */ { NULL, NULL, 0 },
+/* 0x40 */ { NULL, NULL, 0 },
+/* 0x41 */ { NULL, NULL, 0 },
+/* 0x42 */ { NULL, NULL, 0 },
+/* 0x43 */ { NULL, NULL, 0 },
+/* 0x44 */ { NULL, NULL, 0 },
+/* 0x45 */ { NULL, NULL, 0 },
+/* 0x46 */ { NULL, NULL, 0 },
+/* 0x47 */ { NULL, NULL, 0 },
+/* 0x48 */ { NULL, NULL, 0 },
+/* 0x49 */ { NULL, NULL, 0 },
+/* 0x4a */ { NULL, NULL, 0 },
+/* 0x4b */ { NULL, NULL, 0 },
+/* 0x4c */ { NULL, NULL, 0 },
+/* 0x4d */ { NULL, NULL, 0 },
+/* 0x4e */ { NULL, NULL, 0 },
+/* 0x4f */ { NULL, NULL, 0 },
+/* 0x50 */ { NULL, NULL, 0 },
+/* 0x51 */ { NULL, NULL, 0 },
+/* 0x52 */ { NULL, NULL, 0 },
+/* 0x53 */ { NULL, NULL, 0 },
+/* 0x54 */ { NULL, NULL, 0 },
+/* 0x55 */ { NULL, NULL, 0 },
+/* 0x56 */ { NULL, NULL, 0 },
+/* 0x57 */ { NULL, NULL, 0 },
+/* 0x58 */ { NULL, NULL, 0 },
+/* 0x59 */ { NULL, NULL, 0 },
+/* 0x5a */ { NULL, NULL, 0 },
+/* 0x5b */ { NULL, NULL, 0 },
+/* 0x5c */ { NULL, NULL, 0 },
+/* 0x5d */ { NULL, NULL, 0 },
+/* 0x5e */ { NULL, NULL, 0 },
+/* 0x5f */ { NULL, NULL, 0 },
+/* 0x60 */ { NULL, NULL, 0 },
+/* 0x61 */ { NULL, NULL, 0 },
+/* 0x62 */ { NULL, NULL, 0 },
+/* 0x63 */ { NULL, NULL, 0 },
+/* 0x64 */ { NULL, NULL, 0 },
+/* 0x65 */ { NULL, NULL, 0 },
+/* 0x66 */ { NULL, NULL, 0 },
+/* 0x67 */ { NULL, NULL, 0 },
+/* 0x68 */ { NULL, NULL, 0 },
+/* 0x69 */ { NULL, NULL, 0 },
+/* 0x6a */ { NULL, NULL, 0 },
+/* 0x6b */ { NULL, NULL, 0 },
+/* 0x6c */ { NULL, NULL, 0 },
+/* 0x6d */ { NULL, NULL, 0 },
+/* 0x6e */ { NULL, NULL, 0 },
+/* 0x6f */ { NULL, NULL, 0 },
+/* 0x70 */ { "SMBtcon",reply_tcon,0},
+/* 0x71 */ { "SMBtdis",reply_tdis,DO_CHDIR},
+/* 0x72 */ { "SMBnegprot",reply_negprot,0},
+/* 0x73 */ { "SMBsesssetupX",reply_sesssetup_and_X,0},
+/* 0x74 */ { "SMBulogoffX",reply_ulogoffX, 0}, /* ulogoff doesn't give a valid TID */
+/* 0x75 */ { "SMBtconX",reply_tcon_and_X,0},
+/* 0x76 */ { NULL, NULL, 0 },
+/* 0x77 */ { NULL, NULL, 0 },
+/* 0x78 */ { NULL, NULL, 0 },
+/* 0x79 */ { NULL, NULL, 0 },
+/* 0x7a */ { NULL, NULL, 0 },
+/* 0x7b */ { NULL, NULL, 0 },
+/* 0x7c */ { NULL, NULL, 0 },
+/* 0x7d */ { NULL, NULL, 0 },
+/* 0x7e */ { NULL, NULL, 0 },
+/* 0x7f */ { NULL, NULL, 0 },
+/* 0x80 */ { "SMBdskattr",reply_dskattr,AS_USER},
+/* 0x81 */ { "SMBsearch",reply_search,AS_USER},
+/* 0x82 */ { "SMBffirst",reply_search,AS_USER},
+/* 0x83 */ { "SMBfunique",reply_search,AS_USER},
+/* 0x84 */ { "SMBfclose",reply_fclose,AS_USER},
+/* 0x85 */ { NULL, NULL, 0 },
+/* 0x86 */ { NULL, NULL, 0 },
+/* 0x87 */ { NULL, NULL, 0 },
+/* 0x88 */ { NULL, NULL, 0 },
+/* 0x89 */ { NULL, NULL, 0 },
+/* 0x8a */ { NULL, NULL, 0 },
+/* 0x8b */ { NULL, NULL, 0 },
+/* 0x8c */ { NULL, NULL, 0 },
+/* 0x8d */ { NULL, NULL, 0 },
+/* 0x8e */ { NULL, NULL, 0 },
+/* 0x8f */ { NULL, NULL, 0 },
+/* 0x90 */ { NULL, NULL, 0 },
+/* 0x91 */ { NULL, NULL, 0 },
+/* 0x92 */ { NULL, NULL, 0 },
+/* 0x93 */ { NULL, NULL, 0 },
+/* 0x94 */ { NULL, NULL, 0 },
+/* 0x95 */ { NULL, NULL, 0 },
+/* 0x96 */ { NULL, NULL, 0 },
+/* 0x97 */ { NULL, NULL, 0 },
+/* 0x98 */ { NULL, NULL, 0 },
+/* 0x99 */ { NULL, NULL, 0 },
+/* 0x9a */ { NULL, NULL, 0 },
+/* 0x9b */ { NULL, NULL, 0 },
+/* 0x9c */ { NULL, NULL, 0 },
+/* 0x9d */ { NULL, NULL, 0 },
+/* 0x9e */ { NULL, NULL, 0 },
+/* 0x9f */ { NULL, NULL, 0 },
+/* 0xa0 */ { "SMBnttrans",reply_nttrans, AS_USER | CAN_IPC },
+/* 0xa1 */ { "SMBnttranss",reply_nttranss, AS_USER | CAN_IPC },
+/* 0xa2 */ { "SMBntcreateX",reply_ntcreate_and_X, AS_USER | CAN_IPC },
+/* 0xa3 */ { NULL, NULL, 0 },
+/* 0xa4 */ { "SMBntcancel",reply_ntcancel, 0 },
+/* 0xa5 */ { "SMBntrename",reply_ntrename, AS_USER | NEED_WRITE },
+/* 0xa6 */ { NULL, NULL, 0 },
+/* 0xa7 */ { NULL, NULL, 0 },
+/* 0xa8 */ { NULL, NULL, 0 },
+/* 0xa9 */ { NULL, NULL, 0 },
+/* 0xaa */ { NULL, NULL, 0 },
+/* 0xab */ { NULL, NULL, 0 },
+/* 0xac */ { NULL, NULL, 0 },
+/* 0xad */ { NULL, NULL, 0 },
+/* 0xae */ { NULL, NULL, 0 },
+/* 0xaf */ { NULL, NULL, 0 },
+/* 0xb0 */ { NULL, NULL, 0 },
+/* 0xb1 */ { NULL, NULL, 0 },
+/* 0xb2 */ { NULL, NULL, 0 },
+/* 0xb3 */ { NULL, NULL, 0 },
+/* 0xb4 */ { NULL, NULL, 0 },
+/* 0xb5 */ { NULL, NULL, 0 },
+/* 0xb6 */ { NULL, NULL, 0 },
+/* 0xb7 */ { NULL, NULL, 0 },
+/* 0xb8 */ { NULL, NULL, 0 },
+/* 0xb9 */ { NULL, NULL, 0 },
+/* 0xba */ { NULL, NULL, 0 },
+/* 0xbb */ { NULL, NULL, 0 },
+/* 0xbc */ { NULL, NULL, 0 },
+/* 0xbd */ { NULL, NULL, 0 },
+/* 0xbe */ { NULL, NULL, 0 },
+/* 0xbf */ { NULL, NULL, 0 },
+/* 0xc0 */ { "SMBsplopen",reply_printopen,AS_USER},
+/* 0xc1 */ { "SMBsplwr",reply_printwrite,AS_USER},
+/* 0xc2 */ { "SMBsplclose",reply_printclose,AS_USER},
+/* 0xc3 */ { "SMBsplretq",reply_printqueue,AS_USER},
+/* 0xc4 */ { NULL, NULL, 0 },
+/* 0xc5 */ { NULL, NULL, 0 },
+/* 0xc6 */ { NULL, NULL, 0 },
+/* 0xc7 */ { NULL, NULL, 0 },
+/* 0xc8 */ { NULL, NULL, 0 },
+/* 0xc9 */ { NULL, NULL, 0 },
+/* 0xca */ { NULL, NULL, 0 },
+/* 0xcb */ { NULL, NULL, 0 },
+/* 0xcc */ { NULL, NULL, 0 },
+/* 0xcd */ { NULL, NULL, 0 },
+/* 0xce */ { NULL, NULL, 0 },
+/* 0xcf */ { NULL, NULL, 0 },
+/* 0xd0 */ { "SMBsends",reply_sends,AS_GUEST},
+/* 0xd1 */ { "SMBsendb", NULL,AS_GUEST},
+/* 0xd2 */ { "SMBfwdname", NULL,AS_GUEST},
+/* 0xd3 */ { "SMBcancelf", NULL,AS_GUEST},
+/* 0xd4 */ { "SMBgetmac", NULL,AS_GUEST},
+/* 0xd5 */ { "SMBsendstrt",reply_sendstrt,AS_GUEST},
+/* 0xd6 */ { "SMBsendend",reply_sendend,AS_GUEST},
+/* 0xd7 */ { "SMBsendtxt",reply_sendtxt,AS_GUEST},
+/* 0xd8 */ { NULL, NULL, 0 },
+/* 0xd9 */ { NULL, NULL, 0 },
+/* 0xda */ { NULL, NULL, 0 },
+/* 0xdb */ { NULL, NULL, 0 },
+/* 0xdc */ { NULL, NULL, 0 },
+/* 0xdd */ { NULL, NULL, 0 },
+/* 0xde */ { NULL, NULL, 0 },
+/* 0xdf */ { NULL, NULL, 0 },
+/* 0xe0 */ { NULL, NULL, 0 },
+/* 0xe1 */ { NULL, NULL, 0 },
+/* 0xe2 */ { NULL, NULL, 0 },
+/* 0xe3 */ { NULL, NULL, 0 },
+/* 0xe4 */ { NULL, NULL, 0 },
+/* 0xe5 */ { NULL, NULL, 0 },
+/* 0xe6 */ { NULL, NULL, 0 },
+/* 0xe7 */ { NULL, NULL, 0 },
+/* 0xe8 */ { NULL, NULL, 0 },
+/* 0xe9 */ { NULL, NULL, 0 },
+/* 0xea */ { NULL, NULL, 0 },
+/* 0xeb */ { NULL, NULL, 0 },
+/* 0xec */ { NULL, NULL, 0 },
+/* 0xed */ { NULL, NULL, 0 },
+/* 0xee */ { NULL, NULL, 0 },
+/* 0xef */ { NULL, NULL, 0 },
+/* 0xf0 */ { NULL, NULL, 0 },
+/* 0xf1 */ { NULL, NULL, 0 },
+/* 0xf2 */ { NULL, NULL, 0 },
+/* 0xf3 */ { NULL, NULL, 0 },
+/* 0xf4 */ { NULL, NULL, 0 },
+/* 0xf5 */ { NULL, NULL, 0 },
+/* 0xf6 */ { NULL, NULL, 0 },
+/* 0xf7 */ { NULL, NULL, 0 },
+/* 0xf8 */ { NULL, NULL, 0 },
+/* 0xf9 */ { NULL, NULL, 0 },
+/* 0xfa */ { NULL, NULL, 0 },
+/* 0xfb */ { NULL, NULL, 0 },
+/* 0xfc */ { NULL, NULL, 0 },
+/* 0xfd */ { NULL, NULL, 0 },
+/* 0xfe */ { NULL, NULL, 0 },
+/* 0xff */ { NULL, NULL, 0 }
+
+};
+
+/*******************************************************************
+ allocate and initialize a reply packet
+********************************************************************/
+
+bool create_outbuf(TALLOC_CTX *mem_ctx, const char *inbuf, char **outbuf,
+ uint8_t num_words, uint32_t num_bytes)
+{
+ /*
+ * Protect against integer wrap
+ */
+ if ((num_bytes > 0xffffff)
+ || ((num_bytes + smb_size + num_words*2) > 0xffffff)) {
+ char *msg;
+ if (asprintf(&msg, "num_bytes too large: %u",
+ (unsigned)num_bytes) == -1) {
+ msg = CONST_DISCARD(char *, "num_bytes too large");
+ }
+ smb_panic(msg);
+ }
+
+ *outbuf = TALLOC_ARRAY(mem_ctx, char,
+ smb_size + num_words*2 + num_bytes);
+ if (*outbuf == NULL) {
+ return false;
+ }
+
+ construct_reply_common(inbuf, *outbuf);
+ srv_set_message(*outbuf, num_words, num_bytes, false);
+ /*
+ * Zero out the word area, the caller has to take care of the bcc area
+ * himself
+ */
+ if (num_words != 0) {
+ memset(*outbuf + smb_vwv0, 0, num_words*2);
+ }
+
+ return true;
+}
+
+void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
+{
+ char *outbuf;
+ if (!create_outbuf(req, (char *)req->inbuf, &outbuf, num_words,
+ num_bytes)) {
+ smb_panic("could not allocate output buffer\n");
+ }
+ req->outbuf = (uint8_t *)outbuf;
+}
+
+
+/*******************************************************************
+ Dump a packet to a file.
+********************************************************************/
+
+static void smb_dump(const char *name, int type, const char *data, ssize_t len)
+{
+ int fd, i;
+ char *fname = NULL;
+ if (DEBUGLEVEL < 50) {
+ return;
+ }
+
+ if (len < 4) len = smb_len(data)+4;
+ for (i=1;i<100;i++) {
+ if (asprintf(&fname, "/tmp/%s.%d.%s", name, i,
+ type ? "req" : "resp") == -1) {
+ return;
+ }
+ fd = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0644);
+ if (fd != -1 || errno != EEXIST) break;
+ }
+ if (fd != -1) {
+ ssize_t ret = write(fd, data, len);
+ if (ret != len)
+ DEBUG(0,("smb_dump: problem: write returned %d\n", (int)ret ));
+ close(fd);
+ DEBUG(0,("created %s len %lu\n", fname, (unsigned long)len));
+ }
+ SAFE_FREE(fname);
+}
+
+/****************************************************************************
+ Prepare everything for calling the actual request function, and potentially
+ call the request function via the "new" interface.
+
+ Return False if the "legacy" function needs to be called, everything is
+ prepared.
+
+ Return True if we're done.
+
+ I know this API sucks, but it is the one with the least code change I could
+ find.
+****************************************************************************/
+
+static connection_struct *switch_message(uint8 type, struct smb_request *req, int size)
+{
+ int flags;
+ uint16 session_tag;
+ connection_struct *conn = NULL;
+
+ static uint16 last_session_tag = UID_FIELD_INVALID;
+
+ errno = 0;
+
+ /* Make sure this is an SMB packet. smb_size contains NetBIOS header
+ * so subtract 4 from it. */
+ if (!valid_smb_header(req->inbuf)
+ || (size < (smb_size - 4))) {
+ DEBUG(2,("Non-SMB packet of length %d. Terminating server\n",
+ smb_len(req->inbuf)));
+ exit_server_cleanly("Non-SMB packet");
+ }
+
+ if (smb_messages[type].fn_new == NULL) {
+ DEBUG(0,("Unknown message type %d!\n",type));
+ smb_dump("Unknown", 1, (char *)req->inbuf, size);
+ reply_unknown_new(req, type);
+ return NULL;
+ }
+
+ flags = smb_messages[type].flags;
+
+ /* In share mode security we must ignore the vuid. */
+ session_tag = (lp_security() == SEC_SHARE)
+ ? UID_FIELD_INVALID : req->vuid;
+ conn = req->conn;
+
+ DEBUG(3,("switch message %s (pid %d) conn 0x%lx\n", smb_fn_name(type),
+ (int)sys_getpid(), (unsigned long)conn));
+
+ smb_dump(smb_fn_name(type), 1, (char *)req->inbuf, size);
+
+ /* Ensure this value is replaced in the incoming packet. */
+ SSVAL(req->inbuf,smb_uid,session_tag);
+
+ /*
+ * Ensure the correct username is in current_user_info. This is a
+ * really ugly bugfix for problems with multiple session_setup_and_X's
+ * being done and allowing %U and %G substitutions to work correctly.
+ * There is a reason this code is done here, don't move it unless you
+ * know what you're doing... :-).
+ * JRA.
+ */
+
+ if (session_tag != last_session_tag) {
+ user_struct *vuser = NULL;
+
+ last_session_tag = session_tag;
+ if(session_tag != UID_FIELD_INVALID) {
+ vuser = get_valid_user_struct(session_tag);
+ if (vuser) {
+ set_current_user_info(
+ vuser->server_info->sanitized_username,
+ vuser->server_info->unix_name,
+ pdb_get_fullname(vuser->server_info
+ ->sam_account),
+ pdb_get_domain(vuser->server_info
+ ->sam_account));
+ }
+ }
+ }
+
+ /* Does this call need to be run as the connected user? */
+ if (flags & AS_USER) {
+
+ /* Does this call need a valid tree connection? */
+ if (!conn) {
+ /*
+ * Amazingly, the error code depends on the command
+ * (from Samba4).
+ */
+ if (type == SMBntcreateX) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ } else {
+ reply_doserror(req, ERRSRV, ERRinvnid);
+ }
+ return NULL;
+ }
+
+ if (!change_to_user(conn,session_tag)) {
+ reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRbaduid));
+ return conn;
+ }
+
+ /* All NEED_WRITE and CAN_IPC flags must also have AS_USER. */
+
+ /* Does it need write permission? */
+ if ((flags & NEED_WRITE) && !CAN_WRITE(conn)) {
+ reply_nterror(req, NT_STATUS_MEDIA_WRITE_PROTECTED);
+ return conn;
+ }
+
+ /* IPC services are limited */
+ if (IS_IPC(conn) && !(flags & CAN_IPC)) {
+ reply_doserror(req, ERRSRV,ERRaccess);
+ return conn;
+ }
+ } else {
+ /* This call needs to be run as root */
+ change_to_root_user();
+ }
+
+ /* load service specific parameters */
+ if (conn) {
+ if (req->encrypted) {
+ conn->encrypted_tid = true;
+ /* encrypted required from now on. */
+ conn->encrypt_level = Required;
+ } else if (ENCRYPTION_REQUIRED(conn)) {
+ uint8 com = CVAL(req->inbuf,smb_com);
+ if (com != SMBtrans2 && com != SMBtranss2) {
+ exit_server_cleanly("encryption required "
+ "on connection");
+ return conn;
+ }
+ }
+
+ if (!set_current_service(conn,SVAL(req->inbuf,smb_flg),
+ (flags & (AS_USER|DO_CHDIR)
+ ?True:False))) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return conn;
+ }
+ conn->num_smb_operations++;
+ }
+
+ /* does this protocol need to be run as guest? */
+ if ((flags & AS_GUEST)
+ && (!change_to_guest() ||
+ !check_access(smbd_server_fd(), lp_hostsallow(-1),
+ lp_hostsdeny(-1)))) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return conn;
+ }
+
+ smb_messages[type].fn_new(req);
+ return req->conn;
+}
+
+/****************************************************************************
+ Construct a reply to the incoming packet.
+****************************************************************************/
+
+static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool encrypted)
+{
+ uint8 type = CVAL(inbuf,smb_com);
+ connection_struct *conn;
+ struct smb_request *req;
+
+ chain_size = 0;
+ file_chain_reset();
+ reset_chain_p();
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+ init_smb_request(req, (uint8 *)inbuf, unread_bytes, encrypted);
+
+ conn = switch_message(type, req, size);
+
+ if (req->unread_bytes) {
+ /* writeX failed. drain socket. */
+ if (drain_socket(smbd_server_fd(), req->unread_bytes) !=
+ req->unread_bytes) {
+ smb_panic("failed to drain pending bytes");
+ }
+ req->unread_bytes = 0;
+ }
+
+ if (req->outbuf == NULL) {
+ return;
+ }
+
+ if (CVAL(req->outbuf,0) == 0) {
+ show_msg((char *)req->outbuf);
+ }
+
+ if (!srv_send_smb(smbd_server_fd(),
+ (char *)req->outbuf,
+ IS_CONN_ENCRYPTED(conn)||req->encrypted)) {
+ exit_server_cleanly("construct_reply: srv_send_smb failed.");
+ }
+
+ TALLOC_FREE(req);
+
+ return;
+}
+
+/****************************************************************************
+ Process an smb from the client
+****************************************************************************/
+
+static void process_smb(char *inbuf, size_t nread, size_t unread_bytes, bool encrypted)
+{
+ static int trans_num;
+ int msg_type = CVAL(inbuf,0);
+
+ DO_PROFILE_INC(smb_count);
+
+ if (trans_num == 0) {
+ char addr[INET6_ADDRSTRLEN];
+
+ /* on the first packet, check the global hosts allow/ hosts
+ deny parameters before doing any parsing of the packet
+ passed to us by the client. This prevents attacks on our
+ parsing code from hosts not in the hosts allow list */
+
+ if (!check_access(smbd_server_fd(), lp_hostsallow(-1),
+ lp_hostsdeny(-1))) {
+ /* send a negative session response "not listening on calling name" */
+ static unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
+ DEBUG( 1, ( "Connection denied from %s\n",
+ client_addr(get_client_fd(),addr,sizeof(addr)) ) );
+ (void)srv_send_smb(smbd_server_fd(),(char *)buf,false);
+ exit_server_cleanly("connection denied");
+ }
+ }
+
+ DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
+ smb_len(inbuf) ) );
+ DEBUG( 3, ( "Transaction %d of length %d (%u toread)\n", trans_num,
+ (int)nread,
+ (unsigned int)unread_bytes ));
+
+ if (msg_type != 0) {
+ /*
+ * NetBIOS session request, keepalive, etc.
+ */
+ reply_special(inbuf);
+ return;
+ }
+
+ show_msg(inbuf);
+
+ construct_reply(inbuf,nread,unread_bytes,encrypted);
+
+ trans_num++;
+}
+
+/****************************************************************************
+ Return a string containing the function name of a SMB command.
+****************************************************************************/
+
+const char *smb_fn_name(int type)
+{
+ const char *unknown_name = "SMBunknown";
+
+ if (smb_messages[type].name == NULL)
+ return(unknown_name);
+
+ return(smb_messages[type].name);
+}
+
+/****************************************************************************
+ Helper functions for contruct_reply.
+****************************************************************************/
+
+static uint32 common_flags2 = FLAGS2_LONG_PATH_COMPONENTS|FLAGS2_32_BIT_ERROR_CODES;
+
+void add_to_common_flags2(uint32 v)
+{
+ common_flags2 |= v;
+}
+
+void remove_from_common_flags2(uint32 v)
+{
+ common_flags2 &= ~v;
+}
+
+void construct_reply_common(const char *inbuf, char *outbuf)
+{
+ srv_set_message(outbuf,0,0,false);
+
+ SCVAL(outbuf,smb_com,CVAL(inbuf,smb_com));
+ SIVAL(outbuf,smb_rcls,0);
+ SCVAL(outbuf,smb_flg, FLAG_REPLY | (CVAL(inbuf,smb_flg) & FLAG_CASELESS_PATHNAMES));
+ SSVAL(outbuf,smb_flg2,
+ (SVAL(inbuf,smb_flg2) & FLAGS2_UNICODE_STRINGS) |
+ common_flags2);
+ memset(outbuf+smb_pidhigh,'\0',(smb_tid-smb_pidhigh));
+
+ SSVAL(outbuf,smb_tid,SVAL(inbuf,smb_tid));
+ SSVAL(outbuf,smb_pid,SVAL(inbuf,smb_pid));
+ SSVAL(outbuf,smb_uid,SVAL(inbuf,smb_uid));
+ SSVAL(outbuf,smb_mid,SVAL(inbuf,smb_mid));
+}
+
+/****************************************************************************
+ Construct a chained reply and add it to the already made reply
+****************************************************************************/
+
+void chain_reply(struct smb_request *req)
+{
+ static char *orig_inbuf;
+
+ /*
+ * Dirty little const_discard: We mess with req->inbuf, which is
+ * declared as const. If maybe at some point this routine gets
+ * rewritten, this const_discard could go away.
+ */
+ char *inbuf = CONST_DISCARD(char *, req->inbuf);
+ int size = smb_len(req->inbuf)+4;
+
+ int smb_com1, smb_com2 = CVAL(inbuf,smb_vwv0);
+ unsigned smb_off2 = SVAL(inbuf,smb_vwv1);
+ char *inbuf2;
+ int outsize2;
+ int new_size;
+ char inbuf_saved[smb_wct];
+ char *outbuf = (char *)req->outbuf;
+ size_t outsize = smb_len(outbuf) + 4;
+ size_t outsize_padded;
+ size_t padding;
+ size_t ofs, to_move;
+
+ struct smb_request *req2;
+ size_t caller_outputlen;
+ char *caller_output;
+
+ /* Maybe its not chained, or it's an error packet. */
+ if (smb_com2 == 0xFF || SVAL(outbuf,smb_rcls) != 0) {
+ SCVAL(outbuf,smb_vwv0,0xFF);
+ return;
+ }
+
+ if (chain_size == 0) {
+ /* this is the first part of the chain */
+ orig_inbuf = inbuf;
+ }
+
+ /*
+ * We need to save the output the caller added to the chain so that we
+ * can splice it into the final output buffer later.
+ */
+
+ caller_outputlen = outsize - smb_wct;
+
+ caller_output = (char *)memdup(outbuf + smb_wct, caller_outputlen);
+
+ if (caller_output == NULL) {
+ /* TODO: NT_STATUS_NO_MEMORY */
+ smb_panic("could not dup outbuf");
+ }
+
+ /*
+ * The original Win95 redirector dies on a reply to
+ * a lockingX and read chain unless the chain reply is
+ * 4 byte aligned. JRA.
+ */
+
+ outsize_padded = (outsize + 3) & ~3;
+ padding = outsize_padded - outsize;
+
+ /*
+ * remember how much the caller added to the chain, only counting
+ * stuff after the parameter words
+ */
+ chain_size += (outsize_padded - smb_wct);
+
+ /*
+ * work out pointers into the original packets. The
+ * headers on these need to be filled in
+ */
+ inbuf2 = orig_inbuf + smb_off2 + 4 - smb_wct;
+
+ /* remember the original command type */
+ smb_com1 = CVAL(orig_inbuf,smb_com);
+
+ /* save the data which will be overwritten by the new headers */
+ memcpy(inbuf_saved,inbuf2,smb_wct);
+
+ /* give the new packet the same header as the last part of the SMB */
+ memmove(inbuf2,inbuf,smb_wct);
+
+ /* create the in buffer */
+ SCVAL(inbuf2,smb_com,smb_com2);
+
+ /* work out the new size for the in buffer. */
+ new_size = size - (inbuf2 - inbuf);
+ if (new_size < 0) {
+ DEBUG(0,("chain_reply: chain packet size incorrect "
+ "(orig size = %d, offset = %d)\n",
+ size, (int)(inbuf2 - inbuf) ));
+ exit_server_cleanly("Bad chained packet");
+ return;
+ }
+
+ /* And set it in the header. */
+ smb_setlen(inbuf2, new_size - 4);
+
+ DEBUG(3,("Chained message\n"));
+ show_msg(inbuf2);
+
+ if (!(req2 = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+ init_smb_request(req2, (uint8 *)inbuf2,0, req->encrypted);
+
+ /* process the request */
+ switch_message(smb_com2, req2, new_size);
+
+ /*
+ * We don't accept deferred operations in chained requests.
+ */
+ SMB_ASSERT(req2->outbuf != NULL);
+ outsize2 = smb_len(req2->outbuf)+4;
+
+ /*
+ * Move away the new command output so that caller_output fits in,
+ * copy in the caller_output saved above.
+ */
+
+ SMB_ASSERT(outsize_padded >= smb_wct);
+
+ /*
+ * "ofs" is the space we need for caller_output. Equal to
+ * caller_outputlen plus the padding.
+ */
+
+ ofs = outsize_padded - smb_wct;
+
+ /*
+ * "to_move" is the amount of bytes the secondary routine gave us
+ */
+
+ to_move = outsize2 - smb_wct;
+
+ if (to_move + ofs + smb_wct + chain_size > max_send) {
+ smb_panic("replies too large -- would have to cut");
+ }
+
+ /*
+ * In the "new" API "outbuf" is allocated via reply_outbuf, just for
+ * the first request in the chain. So we have to re-allocate it. In
+ * the "old" API the only outbuf ever used is the global OutBuffer
+ * which is always large enough.
+ */
+
+ outbuf = TALLOC_REALLOC_ARRAY(NULL, outbuf, char,
+ to_move + ofs + smb_wct);
+ if (outbuf == NULL) {
+ smb_panic("could not realloc outbuf");
+ }
+
+ req->outbuf = (uint8 *)outbuf;
+
+ memmove(outbuf + smb_wct + ofs, req2->outbuf + smb_wct, to_move);
+ memcpy(outbuf + smb_wct, caller_output, caller_outputlen);
+
+ /*
+ * copy the new reply header over the old one but preserve the smb_com
+ * field
+ */
+ memmove(outbuf, req2->outbuf, smb_wct);
+ SCVAL(outbuf, smb_com, smb_com1);
+
+ /*
+ * We've just copied in the whole "wct" area from the secondary
+ * function. Fix up the chaining: com2 and the offset need to be
+ * readjusted.
+ */
+
+ SCVAL(outbuf, smb_vwv0, smb_com2);
+ SSVAL(outbuf, smb_vwv1, chain_size + smb_wct - 4);
+
+ if (padding != 0) {
+
+ /*
+ * Due to padding we have some uninitialized bytes after the
+ * caller's output
+ */
+
+ memset(outbuf + outsize, 0, padding);
+ }
+
+ smb_setlen(outbuf, outsize2 + caller_outputlen + padding - 4);
+
+ /*
+ * restore the saved data, being careful not to overwrite any data
+ * from the reply header
+ */
+ memcpy(inbuf2,inbuf_saved,smb_wct);
+
+ SAFE_FREE(caller_output);
+ TALLOC_FREE(req2);
+
+ /*
+ * Reset the chain_size for our caller's offset calculations
+ */
+
+ chain_size -= (outsize_padded - smb_wct);
+
+ return;
+}
+
+/****************************************************************************
+ Setup the needed select timeout in milliseconds.
+****************************************************************************/
+
+static int setup_select_timeout(void)
+{
+ int select_timeout;
+
+ select_timeout = SMBD_SELECT_TIMEOUT*1000;
+
+ if (print_notify_messages_pending()) {
+ select_timeout = MIN(select_timeout, 1000);
+ }
+
+ return select_timeout;
+}
+
+/****************************************************************************
+ Check if services need reloading.
+****************************************************************************/
+
+void check_reload(time_t t)
+{
+ static pid_t mypid = 0;
+ static time_t last_smb_conf_reload_time = 0;
+ static time_t last_printer_reload_time = 0;
+ time_t printcap_cache_time = (time_t)lp_printcap_cache_time();
+
+ if(last_smb_conf_reload_time == 0) {
+ last_smb_conf_reload_time = t;
+ /* Our printing subsystem might not be ready at smbd start up.
+ Then no printer is available till the first printers check
+ is performed. A lower initial interval circumvents this. */
+ if ( printcap_cache_time > 60 )
+ last_printer_reload_time = t - printcap_cache_time + 60;
+ else
+ last_printer_reload_time = t;
+ }
+
+ if (mypid != getpid()) { /* First time or fork happened meanwhile */
+ /* randomize over 60 second the printcap reload to avoid all
+ * process hitting cupsd at the same time */
+ int time_range = 60;
+
+ last_printer_reload_time += random() % time_range;
+ mypid = getpid();
+ }
+
+ if (reload_after_sighup || (t >= last_smb_conf_reload_time+SMBD_RELOAD_CHECK)) {
+ reload_services(True);
+ reload_after_sighup = False;
+ last_smb_conf_reload_time = t;
+ }
+
+ /* 'printcap cache time = 0' disable the feature */
+
+ if ( printcap_cache_time != 0 )
+ {
+ /* see if it's time to reload or if the clock has been set back */
+
+ if ( (t >= last_printer_reload_time+printcap_cache_time)
+ || (t-last_printer_reload_time < 0) )
+ {
+ DEBUG( 3,( "Printcap cache time expired.\n"));
+ reload_printers();
+ last_printer_reload_time = t;
+ }
+ }
+}
+
+/****************************************************************************
+ Process any timeout housekeeping. Return False if the caller should exit.
+****************************************************************************/
+
+static void timeout_processing(int *select_timeout,
+ time_t *last_timeout_processing_time)
+{
+ time_t t;
+
+ *last_timeout_processing_time = t = time(NULL);
+
+ /* become root again if waiting */
+ change_to_root_user();
+
+ /* check if we need to reload services */
+ check_reload(t);
+
+ if(global_machine_password_needs_changing &&
+ /* for ADS we need to do a regular ADS password change, not a domain
+ password change */
+ lp_security() == SEC_DOMAIN) {
+
+ unsigned char trust_passwd_hash[16];
+ time_t lct;
+ void *lock;
+
+ /*
+ * We're in domain level security, and the code that
+ * read the machine password flagged that the machine
+ * password needs changing.
+ */
+
+ /*
+ * First, open the machine password file with an exclusive lock.
+ */
+
+ lock = secrets_get_trust_account_lock(NULL, lp_workgroup());
+
+ if (lock == NULL) {
+ DEBUG(0,("process: unable to lock the machine account password for \
+machine %s in domain %s.\n", global_myname(), lp_workgroup() ));
+ return;
+ }
+
+ if(!secrets_fetch_trust_account_password(lp_workgroup(), trust_passwd_hash, &lct, NULL)) {
+ DEBUG(0,("process: unable to read the machine account password for \
+machine %s in domain %s.\n", global_myname(), lp_workgroup()));
+ TALLOC_FREE(lock);
+ return;
+ }
+
+ /*
+ * Make sure someone else hasn't already done this.
+ */
+
+ if(t < lct + lp_machine_password_timeout()) {
+ global_machine_password_needs_changing = False;
+ TALLOC_FREE(lock);
+ return;
+ }
+
+ /* always just contact the PDC here */
+
+ change_trust_account_password( lp_workgroup(), NULL);
+ global_machine_password_needs_changing = False;
+ TALLOC_FREE(lock);
+ }
+
+ /* update printer queue caches if necessary */
+
+ update_monitored_printq_cache();
+
+ /*
+ * Now we are root, check if the log files need pruning.
+ * Force a log file check.
+ */
+ force_check_log_size();
+ check_log_size();
+
+ /* Send any queued printer notify message to interested smbd's. */
+
+ print_notify_send_messages(smbd_messaging_context(), 0);
+
+ /*
+ * Modify the select timeout depending upon
+ * what we have remaining in our queues.
+ */
+
+ *select_timeout = setup_select_timeout();
+
+ return;
+}
+
+/****************************************************************************
+ Process commands from the client
+****************************************************************************/
+
+void smbd_process(void)
+{
+ time_t last_timeout_processing_time = time(NULL);
+ unsigned int num_smbs = 0;
+ size_t unread_bytes = 0;
+
+ max_recv = MIN(lp_maxxmit(),BUFFER_SIZE);
+
+ while (True) {
+ int select_timeout = setup_select_timeout();
+ int num_echos;
+ char *inbuf = NULL;
+ size_t inbuf_len = 0;
+ bool encrypted = false;
+ TALLOC_CTX *frame = talloc_stackframe_pool(8192);
+
+ errno = 0;
+
+ /* Did someone ask for immediate checks on things like blocking locks ? */
+ if (select_timeout == 0) {
+ timeout_processing(&select_timeout,
+ &last_timeout_processing_time);
+ num_smbs = 0; /* Reset smb counter. */
+ }
+
+ run_events(smbd_event_context(), 0, NULL, NULL);
+
+ while (True) {
+ NTSTATUS status;
+
+ status = receive_message_or_smb(
+ talloc_tos(), &inbuf, &inbuf_len,
+ select_timeout, &unread_bytes, &encrypted);
+
+ if (NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ timeout_processing(
+ &select_timeout,
+ &last_timeout_processing_time);
+ continue;
+ }
+
+ DEBUG(3, ("receive_message_or_smb failed: %s, "
+ "exiting\n", nt_errstr(status)));
+ return;
+
+ num_smbs = 0; /* Reset smb counter. */
+ }
+
+
+ /*
+ * Ensure we do timeout processing if the SMB we just got was
+ * only an echo request. This allows us to set the select
+ * timeout in 'receive_message_or_smb()' to any value we like
+ * without worrying that the client will send echo requests
+ * faster than the select timeout, thus starving out the
+ * essential processing (change notify, blocking locks) that
+ * the timeout code does. JRA.
+ */
+ num_echos = smb_echo_count;
+
+ process_smb(inbuf, inbuf_len, unread_bytes, encrypted);
+
+ TALLOC_FREE(inbuf);
+
+ if (smb_echo_count != num_echos) {
+ timeout_processing(&select_timeout,
+ &last_timeout_processing_time);
+ num_smbs = 0; /* Reset smb counter. */
+ }
+
+ num_smbs++;
+
+ /*
+ * If we are getting smb requests in a constant stream
+ * with no echos, make sure we attempt timeout processing
+ * every select_timeout milliseconds - but only check for this
+ * every 200 smb requests.
+ */
+
+ if ((num_smbs % 200) == 0) {
+ time_t new_check_time = time(NULL);
+ if(new_check_time - last_timeout_processing_time >= (select_timeout/1000)) {
+ timeout_processing(
+ &select_timeout,
+ &last_timeout_processing_time);
+ num_smbs = 0; /* Reset smb counter. */
+ last_timeout_processing_time = new_check_time; /* Reset time. */
+ }
+ }
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMBs is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((num_smbs % 50) == 0 && need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
+ TALLOC_FREE(frame);
+ }
+}
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
new file mode 100644
index 0000000000..f47e89bd22
--- /dev/null
+++ b/source3/smbd/quotas.c
@@ -0,0 +1,1539 @@
+/*
+ Unix SMB/CIFS implementation.
+ support for quotas
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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/>.
+*/
+
+
+/*
+ * This is one of the most system dependent parts of Samba, and its
+ * done a litle differently. Each system has its own way of doing
+ * things :-(
+ */
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+
+/* just a quick hack because sysquotas.h is included before linux/quota.h */
+#ifdef QUOTABLOCK_SIZE
+#undef QUOTABLOCK_SIZE
+#endif
+
+#ifdef WITH_QUOTAS
+
+#if defined(VXFS_QUOTA)
+
+/*
+ * In addition to their native filesystems, some systems have Veritas VxFS.
+ * Declare here, define at end: reduces likely "include" interaction problems.
+ * David Lee <T.D.Lee@durham.ac.uk>
+ */
+bool disk_quotas_vxfs(const char *name, char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize);
+
+#endif /* VXFS_QUOTA */
+
+#ifdef LINUX
+
+#include <sys/types.h>
+#include <mntent.h>
+
+/*
+ * This shouldn't be neccessary - it should be /usr/include/sys/quota.h
+ * So we include all the files has *should* be in the system into a large,
+ * grungy samba_linux_quoatas.h Sometimes I *hate* Linux :-). JRA.
+ */
+
+#include "samba_linux_quota.h"
+
+typedef struct _LINUX_SMB_DISK_QUOTA {
+ SMB_BIG_UINT bsize;
+ SMB_BIG_UINT hardlimit; /* In bsize units. */
+ SMB_BIG_UINT softlimit; /* In bsize units. */
+ SMB_BIG_UINT curblocks; /* In bsize units. */
+ SMB_BIG_UINT ihardlimit; /* inode hard limit. */
+ SMB_BIG_UINT isoftlimit; /* inode soft limit. */
+ SMB_BIG_UINT curinodes; /* Current used inodes. */
+} LINUX_SMB_DISK_QUOTA;
+
+
+#ifdef HAVE_LINUX_DQBLK_XFS_H
+#include <linux/dqblk_xfs.h>
+
+/****************************************************************************
+ Abstract out the XFS Quota Manager quota get call.
+****************************************************************************/
+
+static int get_smb_linux_xfs_quota(char *path, uid_t euser_id, gid_t egrp_id, LINUX_SMB_DISK_QUOTA *dp)
+{
+ struct fs_disk_quota D;
+ int ret;
+
+ ZERO_STRUCT(D);
+
+ ret = quotactl(QCMD(Q_XGETQUOTA,USRQUOTA), path, euser_id, (caddr_t)&D);
+
+ if (ret)
+ ret = quotactl(QCMD(Q_XGETQUOTA,GRPQUOTA), path, egrp_id, (caddr_t)&D);
+
+ if (ret)
+ return ret;
+
+ dp->bsize = (SMB_BIG_UINT)512;
+ dp->softlimit = (SMB_BIG_UINT)D.d_blk_softlimit;
+ dp->hardlimit = (SMB_BIG_UINT)D.d_blk_hardlimit;
+ dp->ihardlimit = (SMB_BIG_UINT)D.d_ino_hardlimit;
+ dp->isoftlimit = (SMB_BIG_UINT)D.d_ino_softlimit;
+ dp->curinodes = (SMB_BIG_UINT)D.d_icount;
+ dp->curblocks = (SMB_BIG_UINT)D.d_bcount;
+
+ return ret;
+}
+#else
+static int get_smb_linux_xfs_quota(char *path, uid_t euser_id, gid_t egrp_id, LINUX_SMB_DISK_QUOTA *dp)
+{
+ DEBUG(0,("XFS quota support not available\n"));
+ errno = ENOSYS;
+ return -1;
+}
+#endif
+
+
+/****************************************************************************
+ Abstract out the old and new Linux quota get calls.
+****************************************************************************/
+
+static int get_smb_linux_v1_quota(char *path, uid_t euser_id, gid_t egrp_id, LINUX_SMB_DISK_QUOTA *dp)
+{
+ struct v1_kern_dqblk D;
+ int ret;
+
+ ZERO_STRUCT(D);
+
+ ret = quotactl(QCMD(Q_V1_GETQUOTA,USRQUOTA), path, euser_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ ret = quotactl(QCMD(Q_V1_GETQUOTA,GRPQUOTA), path, egrp_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ return ret;
+
+ dp->bsize = (SMB_BIG_UINT)QUOTABLOCK_SIZE;
+ dp->softlimit = (SMB_BIG_UINT)D.dqb_bsoftlimit;
+ dp->hardlimit = (SMB_BIG_UINT)D.dqb_bhardlimit;
+ dp->ihardlimit = (SMB_BIG_UINT)D.dqb_ihardlimit;
+ dp->isoftlimit = (SMB_BIG_UINT)D.dqb_isoftlimit;
+ dp->curinodes = (SMB_BIG_UINT)D.dqb_curinodes;
+ dp->curblocks = (SMB_BIG_UINT)D.dqb_curblocks;
+
+ return ret;
+}
+
+static int get_smb_linux_v2_quota(char *path, uid_t euser_id, gid_t egrp_id, LINUX_SMB_DISK_QUOTA *dp)
+{
+ struct v2_kern_dqblk D;
+ int ret;
+
+ ZERO_STRUCT(D);
+
+ ret = quotactl(QCMD(Q_V2_GETQUOTA,USRQUOTA), path, euser_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ ret = quotactl(QCMD(Q_V2_GETQUOTA,GRPQUOTA), path, egrp_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ return ret;
+
+ dp->bsize = (SMB_BIG_UINT)QUOTABLOCK_SIZE;
+ dp->softlimit = (SMB_BIG_UINT)D.dqb_bsoftlimit;
+ dp->hardlimit = (SMB_BIG_UINT)D.dqb_bhardlimit;
+ dp->ihardlimit = (SMB_BIG_UINT)D.dqb_ihardlimit;
+ dp->isoftlimit = (SMB_BIG_UINT)D.dqb_isoftlimit;
+ dp->curinodes = (SMB_BIG_UINT)D.dqb_curinodes;
+ dp->curblocks = ((SMB_BIG_UINT)D.dqb_curspace) / dp->bsize;
+
+ return ret;
+}
+
+/****************************************************************************
+ Brand-new generic quota interface.
+****************************************************************************/
+
+static int get_smb_linux_gen_quota(char *path, uid_t euser_id, gid_t egrp_id, LINUX_SMB_DISK_QUOTA *dp)
+{
+ struct if_dqblk D;
+ int ret;
+
+ ZERO_STRUCT(D);
+
+ ret = quotactl(QCMD(Q_GETQUOTA,USRQUOTA), path, euser_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ ret = quotactl(QCMD(Q_GETQUOTA,GRPQUOTA), path, egrp_id, (caddr_t)&D);
+
+ if (ret && errno != EDQUOT)
+ return ret;
+
+ dp->bsize = (SMB_BIG_UINT)QUOTABLOCK_SIZE;
+ dp->softlimit = (SMB_BIG_UINT)D.dqb_bsoftlimit;
+ dp->hardlimit = (SMB_BIG_UINT)D.dqb_bhardlimit;
+ dp->ihardlimit = (SMB_BIG_UINT)D.dqb_ihardlimit;
+ dp->isoftlimit = (SMB_BIG_UINT)D.dqb_isoftlimit;
+ dp->curinodes = (SMB_BIG_UINT)D.dqb_curinodes;
+ dp->curblocks = ((SMB_BIG_UINT)D.dqb_curspace) / dp->bsize;
+
+ return ret;
+}
+
+/****************************************************************************
+ Try to get the disk space from disk quotas (LINUX version).
+****************************************************************************/
+
+bool disk_quotas(const char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ int r;
+ SMB_STRUCT_STAT S;
+ FILE *fp;
+ LINUX_SMB_DISK_QUOTA D;
+ struct mntent *mnt;
+ SMB_DEV_T devno;
+ int found;
+ uid_t euser_id;
+ gid_t egrp_id;
+
+ ZERO_STRUCT(D);
+
+ euser_id = geteuid();
+ egrp_id = getegid();
+
+ /* find the block device file */
+
+ if ( sys_stat(path, &S) == -1 )
+ return(False) ;
+
+ devno = S.st_dev ;
+
+ if ((fp = setmntent(MOUNTED,"r")) == NULL)
+ return(False) ;
+
+ found = False ;
+
+ while ((mnt = getmntent(fp))) {
+ if ( sys_stat(mnt->mnt_dir,&S) == -1 )
+ continue ;
+
+ if (S.st_dev == devno) {
+ found = True ;
+ break;
+ }
+ }
+
+ endmntent(fp) ;
+
+ if (!found)
+ return(False);
+
+ become_root();
+
+ if (strcmp(mnt->mnt_type, "xfs")==0) {
+ r=get_smb_linux_xfs_quota(mnt->mnt_fsname, euser_id, egrp_id, &D);
+ } else {
+ r=get_smb_linux_gen_quota(mnt->mnt_fsname, euser_id, egrp_id, &D);
+ if (r == -1 && errno != EDQUOT) {
+ r=get_smb_linux_v2_quota(mnt->mnt_fsname, euser_id, egrp_id, &D);
+ if (r == -1 && errno != EDQUOT)
+ r=get_smb_linux_v1_quota(mnt->mnt_fsname, euser_id, egrp_id, &D);
+ }
+ }
+
+ unbecome_root();
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ *bsize = D.bsize;
+ if (r == -1) {
+ if (errno == EDQUOT) {
+ *dfree =0;
+ *dsize =D.curblocks;
+ return (True);
+ } else {
+ return(False);
+ }
+ }
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ return(False);
+ } else {
+ if (D.softlimit == 0)
+ D.softlimit = D.hardlimit;
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return (True);
+}
+
+#elif defined(CRAY)
+
+#include <sys/quota.h>
+#include <mntent.h>
+
+/****************************************************************************
+try to get the disk space from disk quotas (CRAY VERSION)
+****************************************************************************/
+
+bool disk_quotas(const char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ struct mntent *mnt;
+ FILE *fd;
+ SMB_STRUCT_STAT sbuf;
+ SMB_DEV_T devno ;
+ struct q_request request ;
+ struct qf_header header ;
+ int quota_default = 0 ;
+ bool found = false;
+
+ if (sys_stat(path,&sbuf) == -1) {
+ return false;
+ }
+
+ devno = sbuf.st_dev ;
+
+ if ((fd = setmntent(KMTAB)) == NULL) {
+ return false;
+ }
+
+ while ((mnt = getmntent(fd)) != NULL) {
+ if (sys_stat(mnt->mnt_dir,&sbuf) == -1) {
+ continue;
+ }
+ if (sbuf.st_dev == devno) {
+ found = frue ;
+ break;
+ }
+ }
+
+ name = talloc_strdup(talloc_tos(), mnt->mnt_dir);
+ endmntent(fd);
+ if (!found) {
+ return false;
+ }
+
+ if (!name) {
+ return false;
+ }
+
+ request.qf_magic = QF_MAGIC ;
+ request.qf_entry.id = geteuid() ;
+
+ if (quotactl(name, Q_GETQUOTA, &request) == -1) {
+ return false;
+ }
+
+ if (!request.user) {
+ return False;
+ }
+
+ if (request.qf_entry.user_q.f_quota == QFV_DEFAULT) {
+ if (!quota_default) {
+ if (quotactl(name, Q_GETHEADER, &header) == -1) {
+ return false;
+ } else {
+ quota_default = header.user_h.def_fq;
+ }
+ }
+ *dfree = quota_default;
+ } else if (request.qf_entry.user_q.f_quota == QFV_PREVENT) {
+ *dfree = 0;
+ } else {
+ *dfree = request.qf_entry.user_q.f_quota;
+ }
+
+ *dsize = request.qf_entry.user_q.f_use;
+
+ if (*dfree < *dsize) {
+ *dfree = 0;
+ } else {
+ *dfree -= *dsize;
+ }
+
+ *bsize = 4096 ; /* Cray blocksize */
+ return true;
+}
+
+
+#elif defined(SUNOS5) || defined(SUNOS4)
+
+#include <fcntl.h>
+#include <sys/param.h>
+#if defined(SUNOS5)
+#include <sys/fs/ufs_quota.h>
+#include <sys/mnttab.h>
+#include <sys/mntent.h>
+#else /* defined(SUNOS4) */
+#include <ufs/quota.h>
+#include <mntent.h>
+#endif
+
+#if defined(SUNOS5)
+
+/****************************************************************************
+ Allows querying of remote hosts for quotas on NFS mounted shares.
+ Supports normal NFS and AMD mounts.
+ Alan Romeril <a.romeril@ic.ac.uk> July 2K.
+****************************************************************************/
+
+#include <rpc/rpc.h>
+#include <rpc/types.h>
+#include <rpcsvc/rquota.h>
+#include <rpc/nettype.h>
+#include <rpc/xdr.h>
+
+static int quotastat;
+
+static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
+{
+ if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
+ return(0);
+ if (!xdr_int(xdrsp, &args->gqa_uid))
+ return(0);
+ return (1);
+}
+
+static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
+{
+ if (!xdr_int(xdrsp, &quotastat)) {
+ DEBUG(6,("nfs_quotas: Status bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
+ DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
+ DEBUG(6,("nfs_quotas: Active bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
+ DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
+ DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
+ DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
+ return 0;
+ }
+ return (1);
+}
+
+/* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */
+static bool nfs_quotas(char *nfspath, uid_t euser_id, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ uid_t uid = euser_id;
+ struct dqblk D;
+ char *mnttype = nfspath;
+ CLIENT *clnt;
+ struct getquota_rslt gqr;
+ struct getquota_args args;
+ char *cutstr, *pathname, *host, *testpath;
+ int len;
+ static struct timeval timeout = {2,0};
+ enum clnt_stat clnt_stat;
+ bool ret = True;
+
+ *bsize = *dfree = *dsize = (SMB_BIG_UINT)0;
+
+ len=strcspn(mnttype, ":");
+ pathname=strstr(mnttype, ":");
+ cutstr = (char *) SMB_MALLOC(len+1);
+ if (!cutstr)
+ return False;
+
+ memset(cutstr, '\0', len+1);
+ host = strncat(cutstr,mnttype, sizeof(char) * len );
+ DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
+ DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
+ testpath=strchr_m(mnttype, ':');
+ args.gqa_pathp = testpath+1;
+ args.gqa_uid = uid;
+
+ DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
+
+ if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
+ ret = False;
+ goto out;
+ }
+
+ clnt->cl_auth = authunix_create_default();
+ DEBUG(9,("nfs_quotas: auth_success\n"));
+
+ clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
+
+ if (clnt_stat != RPC_SUCCESS) {
+ DEBUG(9,("nfs_quotas: clnt_call fail\n"));
+ ret = False;
+ goto out;
+ }
+
+ /*
+ * quotastat returns 0 if the rpc call fails, 1 if quotas exist, 2 if there is
+ * no quota set, and 3 if no permission to get the quota. If 0 or 3 return
+ * something sensible.
+ */
+
+ switch ( quotastat ) {
+ case 0:
+ DEBUG(9,("nfs_quotas: Remote Quotas Failed! Error \"%i\" \n", quotastat ));
+ ret = False;
+ goto out;
+
+ case 1:
+ DEBUG(9,("nfs_quotas: Good quota data\n"));
+ D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
+ D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
+ D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
+ break;
+
+ case 2:
+ case 3:
+ D.dqb_bsoftlimit = 1;
+ D.dqb_curblocks = 1;
+ DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", quotastat ));
+ break;
+
+ default:
+ DEBUG(9,("nfs_quotas: Remote Quotas Questionable! Error \"%i\" \n", quotastat ));
+ break;
+ }
+
+ DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n",
+ quotastat,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
+ gqr.getquota_rslt_u.gqr_rquota.rq_active,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
+
+ *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks == D.dqb_curblocks == 1)
+ *bsize = 512;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+
+ out:
+
+ if (clnt) {
+ if (clnt->cl_auth)
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ }
+
+ DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ SAFE_FREE(cutstr);
+ DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
+ return ret;
+}
+#endif
+
+/****************************************************************************
+try to get the disk space from disk quotas (SunOS & Solaris2 version)
+Quota code by Peter Urbanec (amiga@cse.unsw.edu.au).
+****************************************************************************/
+
+bool disk_quotas(const char *path,
+ SMB_BIG_UINT *bsize,
+ SMB_BIG_UINT *dfree,
+ SMB_BIG_UINT *dsize)
+{
+ uid_t euser_id;
+ int ret;
+ struct dqblk D;
+#if defined(SUNOS5)
+ struct quotctl command;
+ int file;
+ struct mnttab mnt;
+#else /* SunOS4 */
+ struct mntent *mnt;
+#endif
+ char *name = NULL;
+ FILE *fd;
+ SMB_STRUCT_STAT sbuf;
+ SMB_DEV_T devno;
+ bool found = false;
+
+ euser_id = geteuid();
+
+ if (sys_stat(path,&sbuf) == -1) {
+ return false;
+ }
+
+ devno = sbuf.st_dev ;
+ DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
+ path, (unsigned int)devno));
+#if defined(SUNOS5)
+ if ((fd = sys_fopen(MNTTAB, "r")) == NULL) {
+ return false;
+ }
+
+ while (getmntent(fd, &mnt) == 0) {
+ if (sys_stat(mnt.mnt_mountp, &sbuf) == -1) {
+ continue;
+ }
+
+ DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
+ mnt.mnt_mountp, (unsigned int)devno));
+
+ /* quotas are only on vxfs, UFS or NFS */
+ if ((sbuf.st_dev == devno) && (
+ strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 ||
+ strcmp( mnt.mnt_fstype, "nfs" ) == 0 ||
+ strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) {
+ found = true;
+ name = talloc_asprintf(talloc_tos(),
+ "%s/quotas",
+ mnt.mnt_mountp);
+ break;
+ }
+ }
+
+ fclose(fd);
+#else /* SunOS4 */
+ if ((fd = setmntent(MOUNTED, "r")) == NULL) {
+ return false;
+ }
+
+ while ((mnt = getmntent(fd)) != NULL) {
+ if (sys_stat(mnt->mnt_dir,&sbuf) == -1) {
+ continue;
+ }
+ DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
+ mnt->mnt_dir,
+ (unsigned int)sbuf.st_dev));
+ if (sbuf.st_dev == devno) {
+ found = true;
+ name = talloc_strdup(talloc_tos(),
+ mnt->mnt_fsname);
+ break;
+ }
+ }
+
+ endmntent(fd);
+#endif
+ if (!found) {
+ return false;
+ }
+
+ if (!name) {
+ return false;
+ }
+ become_root();
+
+#if defined(SUNOS5)
+ if (strcmp(mnt.mnt_fstype, "nfs") == 0) {
+ bool retval;
+ DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n",
+ mnt.mnt_special));
+ retval = nfs_quotas(mnt.mnt_special,
+ euser_id, bsize, dfree, dsize);
+ unbecome_root();
+ return retval;
+ }
+
+ DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name));
+ if((file=sys_open(name, O_RDONLY,0))<0) {
+ unbecome_root();
+ return false;
+ }
+ command.op = Q_GETQUOTA;
+ command.uid = euser_id;
+ command.addr = (caddr_t) &D;
+ ret = ioctl(file, Q_QUOTACTL, &command);
+ close(file);
+#else
+ DEBUG(5,("disk_quotas: trying quotactl on device \"%s\"\n", name));
+ ret = quotactl(Q_GETQUOTA, name, euser_id, &D);
+#endif
+
+ unbecome_root();
+
+ if (ret < 0) {
+ DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n",
+ strerror(errno) ));
+
+#if defined(SUNOS5) && defined(VXFS_QUOTA)
+ /* If normal quotactl() fails, try vxfs private calls */
+ set_effective_uid(euser_id);
+ DEBUG(5,("disk_quotas: mount type \"%s\"\n", mnt.mnt_fstype));
+ if ( 0 == strcmp ( mnt.mnt_fstype, "vxfs" )) {
+ bool retval;
+ retval = disk_quotas_vxfs(name, path,
+ bsize, dfree, dsize);
+ return retval;
+ }
+#else
+ return false;
+#endif
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0) {
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+ }
+
+ /* Use softlimit to determine disk space. A user exceeding the quota
+ * is told that there's no space left. Writes might actually work for
+ * a bit if the hardlimit is set higher than softlimit. Effectively
+ * the disk becomes made of rubber latex and begins to expand to
+ * accommodate the user :-)
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+ *bsize = DEV_BSIZE;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ }
+
+ DEBUG(5,("disk_quotas for path \"%s\" returning "
+ "bsize %.0f, dfree %.0f, dsize %.0f\n",
+ path,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ return true;
+}
+
+
+#elif defined(OSF1)
+#include <ufs/quota.h>
+
+/****************************************************************************
+try to get the disk space from disk quotas - OSF1 version
+****************************************************************************/
+
+bool disk_quotas(const char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ int r, save_errno;
+ struct dqblk D;
+ SMB_STRUCT_STAT S;
+ uid_t euser_id;
+
+ /*
+ * This code presumes that OSF1 will only
+ * give out quota info when the real uid
+ * matches the effective uid. JRA.
+ */
+ euser_id = geteuid();
+ save_re_uid();
+ if (set_re_uid() != 0) return False;
+
+ r= quotactl(path,QCMD(Q_GETQUOTA, USRQUOTA),euser_id,(char *) &D);
+ if (r) {
+ save_errno = errno;
+ }
+
+ restore_re_uid();
+
+ *bsize = DEV_BSIZE;
+
+ if (r)
+ {
+ if (save_errno == EDQUOT) /* disk quota exceeded */
+ {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ return (True);
+ }
+ else
+ return (False);
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+
+ if ((D.dqb_curblocks>D.dqb_bsoftlimit)) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ *dsize = D.dqb_bsoftlimit;
+ }
+ return (True);
+}
+
+#elif defined (IRIX6)
+/****************************************************************************
+try to get the disk space from disk quotas (IRIX 6.2 version)
+****************************************************************************/
+
+#include <sys/quota.h>
+#include <mntent.h>
+
+bool disk_quotas(const char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ uid_t euser_id;
+ int r;
+ struct dqblk D;
+ struct fs_disk_quota F;
+ SMB_STRUCT_STAT S;
+ FILE *fp;
+ struct mntent *mnt;
+ SMB_DEV_T devno;
+ int found;
+
+ /* find the block device file */
+
+ if ( sys_stat(path, &S) == -1 ) {
+ return(False) ;
+ }
+
+ devno = S.st_dev ;
+
+ fp = setmntent(MOUNTED,"r");
+ found = False ;
+
+ while ((mnt = getmntent(fp))) {
+ if ( sys_stat(mnt->mnt_dir,&S) == -1 )
+ continue ;
+ if (S.st_dev == devno) {
+ found = True ;
+ break ;
+ }
+ }
+ endmntent(fp) ;
+
+ if (!found) {
+ return(False);
+ }
+
+ euser_id=geteuid();
+ become_root();
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+
+ *bsize = 512;
+
+ if ( 0 == strcmp ( mnt->mnt_type, "efs" ))
+ {
+ r=quotactl (Q_GETQUOTA, mnt->mnt_fsname, euser_id, (caddr_t) &D);
+
+ unbecome_root();
+
+ if (r==-1)
+ return(False);
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.dqb_bsoftlimit && D.dqb_curblocks>=D.dqb_bsoftlimit) ||
+ (D.dqb_bhardlimit && D.dqb_curblocks>=D.dqb_bhardlimit) ||
+ (D.dqb_fsoftlimit && D.dqb_curfiles>=D.dqb_fsoftlimit) ||
+ (D.dqb_fhardlimit && D.dqb_curfiles>=D.dqb_fhardlimit)
+ )
+ {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ }
+ else if (D.dqb_bsoftlimit==0 && D.dqb_bhardlimit==0)
+ {
+ return(False);
+ }
+ else
+ {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ *dsize = D.dqb_bsoftlimit;
+ }
+
+ }
+ else if ( 0 == strcmp ( mnt->mnt_type, "xfs" ))
+ {
+ r=quotactl (Q_XGETQUOTA, mnt->mnt_fsname, euser_id, (caddr_t) &F);
+
+ unbecome_root();
+
+ if (r==-1)
+ {
+ DEBUG(5, ("quotactl for uid=%u: %s", euser_id, strerror(errno)));
+ return(False);
+ }
+
+ /* No quota for this user. */
+ if (F.d_blk_softlimit==0 && F.d_blk_hardlimit==0)
+ {
+ return(False);
+ }
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (F.d_blk_softlimit && F.d_bcount>=F.d_blk_softlimit) ||
+ (F.d_blk_hardlimit && F.d_bcount>=F.d_blk_hardlimit) ||
+ (F.d_ino_softlimit && F.d_icount>=F.d_ino_softlimit) ||
+ (F.d_ino_hardlimit && F.d_icount>=F.d_ino_hardlimit)
+ )
+ {
+ *dfree = 0;
+ *dsize = F.d_bcount;
+ }
+ else
+ {
+ *dfree = (F.d_blk_softlimit - F.d_bcount);
+ *dsize = F.d_blk_softlimit ? F.d_blk_softlimit : F.d_blk_hardlimit;
+ }
+
+ }
+ else
+ {
+ unbecome_root();
+ return(False);
+ }
+
+ return (True);
+
+}
+
+#else
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+#include <ufs/ufs/quota.h>
+#include <machine/param.h>
+#elif AIX
+/* AIX quota patch from Ole Holm Nielsen <ohnielse@fysik.dtu.dk> */
+#include <jfs/quota.h>
+/* AIX 4.X: Rename members of the dqblk structure (ohnielse@fysik.dtu.dk) */
+#define dqb_curfiles dqb_curinodes
+#define dqb_fhardlimit dqb_ihardlimit
+#define dqb_fsoftlimit dqb_isoftlimit
+#ifdef _AIXVERSION_530
+#include <sys/statfs.h>
+#include <sys/vmount.h>
+#endif /* AIX 5.3 */
+#else /* !__FreeBSD__ && !AIX && !__OpenBSD__ && !__DragonFly__ */
+#include <sys/quota.h>
+#include <devnm.h>
+#endif
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+
+#include <rpc/rpc.h>
+#include <rpc/types.h>
+#include <rpcsvc/rquota.h>
+#ifdef HAVE_RPC_NETTYPE_H
+#include <rpc/nettype.h>
+#endif
+#include <rpc/xdr.h>
+
+static int quotastat;
+
+static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
+{
+ if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
+ return(0);
+ if (!xdr_int(xdrsp, &args->gqa_uid))
+ return(0);
+ return (1);
+}
+
+static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
+{
+ if (!xdr_int(xdrsp, &quotastat)) {
+ DEBUG(6,("nfs_quotas: Status bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
+ DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
+ DEBUG(6,("nfs_quotas: Active bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
+ DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
+ DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
+ DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
+ return 0;
+ }
+ return (1);
+}
+
+/* Works on FreeBSD, too. :-) */
+static bool nfs_quotas(char *nfspath, uid_t euser_id, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ uid_t uid = euser_id;
+ struct dqblk D;
+ char *mnttype = nfspath;
+ CLIENT *clnt;
+ struct getquota_rslt gqr;
+ struct getquota_args args;
+ char *cutstr, *pathname, *host, *testpath;
+ int len;
+ static struct timeval timeout = {2,0};
+ enum clnt_stat clnt_stat;
+ bool ret = True;
+
+ *bsize = *dfree = *dsize = (SMB_BIG_UINT)0;
+
+ len=strcspn(mnttype, ":");
+ pathname=strstr(mnttype, ":");
+ cutstr = (char *) SMB_MALLOC(len+1);
+ if (!cutstr)
+ return False;
+
+ memset(cutstr, '\0', len+1);
+ host = strncat(cutstr,mnttype, sizeof(char) * len );
+ DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
+ DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
+ testpath=strchr_m(mnttype, ':');
+ args.gqa_pathp = testpath+1;
+ args.gqa_uid = uid;
+
+ DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
+
+ if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
+ ret = False;
+ goto out;
+ }
+
+ clnt->cl_auth = authunix_create_default();
+ DEBUG(9,("nfs_quotas: auth_success\n"));
+
+ clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, (const xdrproc_t) my_xdr_getquota_args, (caddr_t)&args, (const xdrproc_t) my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
+
+ if (clnt_stat != RPC_SUCCESS) {
+ DEBUG(9,("nfs_quotas: clnt_call fail\n"));
+ ret = False;
+ goto out;
+ }
+
+ /*
+ * quotastat returns 0 if the rpc call fails, 1 if quotas exist, 2 if there is
+ * no quota set, and 3 if no permission to get the quota. If 0 or 3 return
+ * something sensible.
+ */
+
+ switch ( quotastat ) {
+ case 0:
+ DEBUG(9,("nfs_quotas: Remote Quotas Failed! Error \"%i\" \n", quotastat ));
+ ret = False;
+ goto out;
+
+ case 1:
+ DEBUG(9,("nfs_quotas: Good quota data\n"));
+ D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
+ D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
+ D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
+ break;
+
+ case 2:
+ case 3:
+ D.dqb_bsoftlimit = 1;
+ D.dqb_curblocks = 1;
+ DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", quotastat ));
+ break;
+
+ default:
+ DEBUG(9,("nfs_quotas: Remote Quotas Questionable! Error \"%i\" \n", quotastat ));
+ break;
+ }
+
+ DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n",
+ quotastat,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
+ gqr.getquota_rslt_u.gqr_rquota.rq_active,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
+
+ if (D.dqb_bsoftlimit == 0)
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+ if (D.dqb_bsoftlimit == 0)
+ return False;
+
+ *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks == D.dqb_curblocks == 1)
+ *bsize = DEV_BSIZE;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+
+ out:
+
+ if (clnt) {
+ if (clnt->cl_auth)
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ }
+
+ DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ SAFE_FREE(cutstr);
+ DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
+ return ret;
+}
+
+#endif
+
+/****************************************************************************
+try to get the disk space from disk quotas - default version
+****************************************************************************/
+
+bool disk_quotas(const char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ int r;
+ struct dqblk D;
+ uid_t euser_id;
+#if !defined(__FreeBSD__) && !defined(AIX) && !defined(__OpenBSD__) && !defined(__DragonFly__)
+ char dev_disk[256];
+ SMB_STRUCT_STAT S;
+
+ /* find the block device file */
+
+#ifdef HPUX
+ /* Need to set the cache flag to 1 for HPUX. Seems
+ * to have a significant performance boost when
+ * lstat calls on /dev access this function.
+ */
+ if ((sys_stat(path, &S)<0) || (devnm(S_IFBLK, S.st_dev, dev_disk, 256, 1)<0))
+#else
+ if ((sys_stat(path, &S)<0) || (devnm(S_IFBLK, S.st_dev, dev_disk, 256, 0)<0))
+ return (False);
+#endif /* ifdef HPUX */
+
+#endif /* !defined(__FreeBSD__) && !defined(AIX) && !defined(__OpenBSD__) && !defined(__DragonFly__) */
+
+ euser_id = geteuid();
+
+#ifdef HPUX
+ /* for HPUX, real uid must be same as euid to execute quotactl for euid */
+ save_re_uid();
+ if (set_re_uid() != 0) return False;
+
+ r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D);
+
+ restore_re_uid();
+#else
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+ {
+ /* FreeBSD patches from Marty Moll <martym@arbor.edu> */
+ gid_t egrp_id;
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+ SMB_DEV_T devno;
+ struct statfs *mnts;
+ SMB_STRUCT_STAT st;
+ int mntsize, i;
+
+ if (sys_stat(path,&st) < 0)
+ return False;
+ devno = st.st_dev;
+
+ mntsize = getmntinfo(&mnts,MNT_NOWAIT);
+ if (mntsize <= 0)
+ return False;
+
+ for (i = 0; i < mntsize; i++) {
+ if (sys_stat(mnts[i].f_mntonname,&st) < 0)
+ return False;
+ if (st.st_dev == devno)
+ break;
+ }
+ if (i == mntsize)
+ return False;
+#endif
+
+ become_root();
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+ if (strcmp(mnts[i].f_fstypename,"nfs") == 0) {
+ bool retval;
+ retval = nfs_quotas(mnts[i].f_mntfromname,euser_id,bsize,dfree,dsize);
+ unbecome_root();
+ return retval;
+ }
+#endif
+
+ egrp_id = getegid();
+ r= quotactl(path,QCMD(Q_GETQUOTA,USRQUOTA),euser_id,(char *) &D);
+
+ /* As FreeBSD has group quotas, if getting the user
+ quota fails, try getting the group instead. */
+ if (r) {
+ r= quotactl(path,QCMD(Q_GETQUOTA,GRPQUOTA),egrp_id,(char *) &D);
+ }
+
+ unbecome_root();
+ }
+#elif defined(AIX)
+ /* AIX has both USER and GROUP quotas:
+ Get the USER quota (ohnielse@fysik.dtu.dk) */
+#ifdef _AIXVERSION_530
+ {
+ struct statfs statbuf;
+ quota64_t user_quota;
+ if (statfs(path,&statbuf) != 0)
+ return False;
+ if(statbuf.f_vfstype == MNT_J2)
+ {
+ /* For some reason we need to be root for jfs2 */
+ become_root();
+ r = quotactl(path,QCMD(Q_J2GETQUOTA,USRQUOTA),euser_id,(char *) &user_quota);
+ unbecome_root();
+ /* Copy results to old struct to let the following code work as before */
+ D.dqb_curblocks = user_quota.bused;
+ D.dqb_bsoftlimit = user_quota.bsoft;
+ D.dqb_bhardlimit = user_quota.bhard;
+ D.dqb_curfiles = user_quota.iused;
+ D.dqb_fsoftlimit = user_quota.isoft;
+ D.dqb_fhardlimit = user_quota.ihard;
+ }
+ else if(statbuf.f_vfstype == MNT_JFS)
+ {
+#endif /* AIX 5.3 */
+ save_re_uid();
+ if (set_re_uid() != 0)
+ return False;
+ r= quotactl(path,QCMD(Q_GETQUOTA,USRQUOTA),euser_id,(char *) &D);
+ restore_re_uid();
+#ifdef _AIXVERSION_530
+ }
+ else
+ r = 1; /* Fail for other FS-types */
+ }
+#endif /* AIX 5.3 */
+#else /* !__FreeBSD__ && !AIX && !__OpenBSD__ && !__DragonFly__ */
+ r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D);
+#endif /* !__FreeBSD__ && !AIX && !__OpenBSD__ && !__DragonFly__ */
+#endif /* HPUX */
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+ *bsize = DEV_BSIZE;
+#else /* !__FreeBSD__ && !__OpenBSD__ && !__DragonFly__ */
+ *bsize = 1024;
+#endif /*!__FreeBSD__ && !__OpenBSD__ && !__DragonFly__ */
+
+ if (r)
+ {
+ if (errno == EDQUOT)
+ {
+ *dfree =0;
+ *dsize =D.dqb_curblocks;
+ return (True);
+ }
+ else return(False);
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if ((D.dqb_curblocks>D.dqb_bsoftlimit)
+#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__)
+||((D.dqb_curfiles>D.dqb_fsoftlimit) && (D.dqb_fsoftlimit != 0))
+#endif
+ ) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ }
+ else {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ *dsize = D.dqb_bsoftlimit;
+ }
+ return (True);
+}
+
+#endif
+
+#if defined(VXFS_QUOTA)
+
+/****************************************************************************
+Try to get the disk space from Veritas disk quotas.
+ David Lee <T.D.Lee@durham.ac.uk> August 1999.
+
+Background assumptions:
+ Potentially under many Operating Systems. Initially Solaris 2.
+
+ My guess is that Veritas is largely, though not entirely,
+ independent of OS. So I have separated it out.
+
+ There may be some details. For example, OS-specific "include" files.
+
+ It is understood that HPUX 10 somehow gets Veritas quotas without
+ any special effort; if so, this routine need not be compiled in.
+ Dirk De Wachter <Dirk.DeWachter@rug.ac.be>
+
+Warning:
+ It is understood that Veritas do not publicly support this ioctl interface.
+ Rather their preference would be for the user (us) to call the native
+ OS and then for the OS itself to call through to the VxFS filesystem.
+ Presumably HPUX 10, see above, does this.
+
+Hints for porting:
+ Add your OS to "IFLIST" below.
+ Get it to compile successfully:
+ Almost certainly "include"s require attention: see SUNOS5.
+ In the main code above, arrange for it to be called: see SUNOS5.
+ Test!
+
+****************************************************************************/
+
+/* "IFLIST"
+ * This "if" is a list of ports:
+ * if defined(OS1) || defined(OS2) || ...
+ */
+#if defined(SUNOS5)
+
+#if defined(SUNOS5)
+#include <sys/fs/vx_solaris.h>
+#endif
+#include <sys/fs/vx_machdep.h>
+#include <sys/fs/vx_layout.h>
+#include <sys/fs/vx_quota.h>
+#include <sys/fs/vx_aioctl.h>
+#include <sys/fs/vx_ioctl.h>
+
+bool disk_quotas_vxfs(const char *name, char *path, SMB_BIG_UINT *bsize, SMB_BIG_UINT *dfree, SMB_BIG_UINT *dsize)
+{
+ uid_t user_id, euser_id;
+ int ret;
+ struct vx_dqblk D;
+ struct vx_quotctl quotabuf;
+ struct vx_genioctl genbuf;
+ char *qfname;
+ int file;
+
+ /*
+ * "name" may or may not include a trailing "/quotas".
+ * Arranging consistency of calling here in "quotas.c" may not be easy and
+ * it might be easier to examine and adjust it here.
+ * Fortunately, VxFS seems not to mind at present.
+ */
+ qfname = talloc_strdup(talloc_tos(), name);
+ if (!qfname) {
+ return false;
+ }
+ /* pstrcat(qfname, "/quotas") ; */ /* possibly examine and adjust "name" */
+
+ euser_id = geteuid();
+ set_effective_uid(0);
+
+ DEBUG(5,("disk_quotas: looking for VxFS quotas file \"%s\"\n", qfname));
+ if((file=sys_open(qfname, O_RDONLY,0))<0) {
+ set_effective_uid(euser_id);
+ return(False);
+ }
+ genbuf.ioc_cmd = VX_QUOTACTL;
+ genbuf.ioc_up = (void *) &quotabuf;
+
+ quotabuf.cmd = VX_GETQUOTA;
+ quotabuf.uid = euser_id;
+ quotabuf.addr = (caddr_t) &D;
+ ret = ioctl(file, VX_ADMIN_IOCTL, &genbuf);
+ close(file);
+
+ set_effective_uid(euser_id);
+
+ if (ret < 0) {
+ DEBUG(5,("disk_quotas ioctl (VxFS) failed. Error = %s\n", strerror(errno) ));
+ return(False);
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+
+ /* Use softlimit to determine disk space. A user exceeding the quota is told
+ * that there's no space left. Writes might actually work for a bit if the
+ * hardlimit is set higher than softlimit. Effectively the disk becomes
+ * made of rubber latex and begins to expand to accommodate the user :-)
+ */
+ DEBUG(5,("disk_quotas for path \"%s\" block c/s/h %ld/%ld/%ld; file c/s/h %ld/%ld/%ld\n",
+ path, D.dqb_curblocks, D.dqb_bsoftlimit, D.dqb_bhardlimit,
+ D.dqb_curfiles, D.dqb_fsoftlimit, D.dqb_fhardlimit));
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+ *bsize = DEV_BSIZE;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+
+ DEBUG(5,("disk_quotas for path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",
+ path,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ return(True);
+}
+
+#endif /* SUNOS5 || ... */
+
+#endif /* VXFS_QUOTA */
+
+#else /* WITH_QUOTAS */
+
+bool disk_quotas(const char *path,SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+{
+ (*bsize) = 512; /* This value should be ignored */
+
+ /* And just to be sure we set some values that hopefully */
+ /* will be larger that any possible real-world value */
+ (*dfree) = (SMB_BIG_UINT)-1;
+ (*dsize) = (SMB_BIG_UINT)-1;
+
+ /* As we have select not to use quotas, allways fail */
+ return false;
+}
+#endif /* WITH_QUOTAS */
+
+#else /* HAVE_SYS_QUOTAS */
+/* wrapper to the new sys_quota interface
+ this file should be removed later
+ */
+bool disk_quotas(const char *path,SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+{
+ int r;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+
+ id.uid = geteuid();
+
+ ZERO_STRUCT(D);
+ r=sys_get_quota(path, SMB_USER_QUOTA_TYPE, id, &D);
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ *bsize = D.bsize;
+ if (r == -1) {
+ if (errno == EDQUOT) {
+ *dfree =0;
+ *dsize =D.curblocks;
+ return (True);
+ } else {
+ goto try_group_quota;
+ }
+ }
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ goto try_group_quota;
+ } else {
+ if (D.softlimit == 0)
+ D.softlimit = D.hardlimit;
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return True;
+
+try_group_quota:
+ id.gid = getegid();
+
+ ZERO_STRUCT(D);
+ r=sys_get_quota(path, SMB_GROUP_QUOTA_TYPE, id, &D);
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ *bsize = D.bsize;
+ if (r == -1) {
+ if (errno == EDQUOT) {
+ *dfree =0;
+ *dsize =D.curblocks;
+ return (True);
+ } else {
+ return False;
+ }
+ }
+
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ return False;
+ } else {
+ if (D.softlimit == 0)
+ D.softlimit = D.hardlimit;
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return (True);
+}
+#endif /* HAVE_SYS_QUOTAS */
diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c
new file mode 100644
index 0000000000..6933533672
--- /dev/null
+++ b/source3/smbd/reply.c
@@ -0,0 +1,7184 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ 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/>.
+*/
+/*
+ This file handles most of the reply_ calls that the server
+ makes to handle specific protocols
+*/
+
+#include "includes.h"
+
+/* look in server.c for some explanation of these variables */
+extern enum protocol_types Protocol;
+extern int max_recv;
+unsigned int smb_echo_count = 0;
+extern uint32 global_client_caps;
+
+extern bool global_encrypted_passwords_negotiated;
+
+/****************************************************************************
+ Ensure we check the path in *exactly* the same way as W2K for a findfirst/findnext
+ path or anything including wildcards.
+ We're assuming here that '/' is not the second byte in any multibyte char
+ set (a safe assumption). '\\' *may* be the second byte in a multibyte char
+ set.
+****************************************************************************/
+
+/* Custom version for processing POSIX paths. */
+#define IS_PATH_SEP(c,posix_only) ((c) == '/' || (!(posix_only) && (c) == '\\'))
+
+static NTSTATUS check_path_syntax_internal(char *path,
+ bool posix_path,
+ bool *p_last_component_contains_wcard)
+{
+ char *d = path;
+ const char *s = path;
+ NTSTATUS ret = NT_STATUS_OK;
+ bool start_of_name_component = True;
+
+ *p_last_component_contains_wcard = False;
+
+ while (*s) {
+ if (IS_PATH_SEP(*s,posix_path)) {
+ /*
+ * Safe to assume is not the second part of a mb char
+ * as this is handled below.
+ */
+ /* Eat multiple '/' or '\\' */
+ while (IS_PATH_SEP(*s,posix_path)) {
+ s++;
+ }
+ if ((d != path) && (*s != '\0')) {
+ /* We only care about non-leading or trailing '/' or '\\' */
+ *d++ = '/';
+ }
+
+ start_of_name_component = True;
+ /* New component. */
+ *p_last_component_contains_wcard = False;
+ continue;
+ }
+
+ if (start_of_name_component) {
+ if ((s[0] == '.') && (s[1] == '.') && (IS_PATH_SEP(s[2],posix_path) || s[2] == '\0')) {
+ /* Uh oh - "/../" or "\\..\\" or "/..\0" or "\\..\0" ! */
+
+ /*
+ * No mb char starts with '.' so we're safe checking the directory separator here.
+ */
+
+ /* If we just added a '/' - delete it */
+ if ((d > path) && (*(d-1) == '/')) {
+ *(d-1) = '\0';
+ d--;
+ }
+
+ /* Are we at the start ? Can't go back further if so. */
+ if (d <= path) {
+ ret = NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ break;
+ }
+ /* Go back one level... */
+ /* We know this is safe as '/' cannot be part of a mb sequence. */
+ /* NOTE - if this assumption is invalid we are not in good shape... */
+ /* Decrement d first as d points to the *next* char to write into. */
+ for (d--; d > path; d--) {
+ if (*d == '/')
+ break;
+ }
+ s += 2; /* Else go past the .. */
+ /* We're still at the start of a name component, just the previous one. */
+ continue;
+
+ } else if ((s[0] == '.') && ((s[1] == '\0') || IS_PATH_SEP(s[1],posix_path))) {
+ if (posix_path) {
+ /* Eat the '.' */
+ s++;
+ continue;
+ }
+ }
+
+ }
+
+ if (!(*s & 0x80)) {
+ if (!posix_path) {
+ if (*s <= 0x1f) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ switch (*s) {
+ case '*':
+ case '?':
+ case '<':
+ case '>':
+ case '"':
+ *p_last_component_contains_wcard = True;
+ break;
+ default:
+ break;
+ }
+ }
+ *d++ = *s++;
+ } else {
+ size_t siz;
+ /* Get the size of the next MB character. */
+ next_codepoint(s,&siz);
+ switch(siz) {
+ case 5:
+ *d++ = *s++;
+ /*fall through*/
+ case 4:
+ *d++ = *s++;
+ /*fall through*/
+ case 3:
+ *d++ = *s++;
+ /*fall through*/
+ case 2:
+ *d++ = *s++;
+ /*fall through*/
+ case 1:
+ *d++ = *s++;
+ break;
+ default:
+ DEBUG(0,("check_path_syntax_internal: character length assumptions invalid !\n"));
+ *d = '\0';
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+ start_of_name_component = False;
+ }
+
+ *d = '\0';
+
+ return ret;
+}
+
+/****************************************************************************
+ Ensure we check the path in *exactly* the same way as W2K for regular pathnames.
+ No wildcards allowed.
+****************************************************************************/
+
+NTSTATUS check_path_syntax(char *path)
+{
+ bool ignore;
+ return check_path_syntax_internal(path, False, &ignore);
+}
+
+/****************************************************************************
+ Ensure we check the path in *exactly* the same way as W2K for regular pathnames.
+ Wildcards allowed - p_contains_wcard returns true if the last component contained
+ a wildcard.
+****************************************************************************/
+
+NTSTATUS check_path_syntax_wcard(char *path, bool *p_contains_wcard)
+{
+ return check_path_syntax_internal(path, False, p_contains_wcard);
+}
+
+/****************************************************************************
+ Check the path for a POSIX client.
+ We're assuming here that '/' is not the second byte in any multibyte char
+ set (a safe assumption).
+****************************************************************************/
+
+NTSTATUS check_path_syntax_posix(char *path)
+{
+ bool ignore;
+ return check_path_syntax_internal(path, True, &ignore);
+}
+
+/****************************************************************************
+ Pull a string and check the path allowing a wilcard - provide for error return.
+****************************************************************************/
+
+size_t srvstr_get_path_wcard(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16 smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err,
+ bool *contains_wcard)
+{
+ size_t ret;
+
+ *pp_dest = NULL;
+
+ if (src_len == 0) {
+ ret = srvstr_pull_buf_talloc(ctx,
+ inbuf,
+ smb_flags2,
+ pp_dest,
+ src,
+ flags);
+ } else {
+ ret = srvstr_pull_talloc(ctx,
+ inbuf,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags);
+ }
+
+ if (!*pp_dest) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+
+ *contains_wcard = False;
+
+ if (smb_flags2 & FLAGS2_DFS_PATHNAMES) {
+ /*
+ * For a DFS path the function parse_dfs_path()
+ * will do the path processing, just make a copy.
+ */
+ *err = NT_STATUS_OK;
+ return ret;
+ }
+
+ if (lp_posix_pathnames()) {
+ *err = check_path_syntax_posix(*pp_dest);
+ } else {
+ *err = check_path_syntax_wcard(*pp_dest, contains_wcard);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Pull a string and check the path - provide for error return.
+****************************************************************************/
+
+size_t srvstr_get_path(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16 smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err)
+{
+ size_t ret;
+
+ *pp_dest = NULL;
+
+ if (src_len == 0) {
+ ret = srvstr_pull_buf_talloc(ctx,
+ inbuf,
+ smb_flags2,
+ pp_dest,
+ src,
+ flags);
+ } else {
+ ret = srvstr_pull_talloc(ctx,
+ inbuf,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags);
+ }
+
+ if (!*pp_dest) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+
+ if (smb_flags2 & FLAGS2_DFS_PATHNAMES) {
+ /*
+ * For a DFS path the function parse_dfs_path()
+ * will do the path processing, just make a copy.
+ */
+ *err = NT_STATUS_OK;
+ return ret;
+ }
+
+ if (lp_posix_pathnames()) {
+ *err = check_path_syntax_posix(*pp_dest);
+ } else {
+ *err = check_path_syntax(*pp_dest);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file. Basic check for open fsp.
+****************************************************************************/
+
+bool check_fsp_open(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if (!(fsp) || !(conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return False;
+ }
+ if (((conn) != (fsp)->conn) || req->vuid != (fsp)->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return False;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file. Replacement for the
+ CHECK_FSP macro.
+****************************************************************************/
+
+bool check_fsp(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if (!check_fsp_open(conn, req, fsp)) {
+ return False;
+ }
+ if ((fsp)->is_directory) {
+ reply_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return False;
+ }
+ if ((fsp)->fh->fd == -1) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return False;
+ }
+ (fsp)->num_smb_operations++;
+ return True;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a quota fake file. Replacement for
+ the CHECK_NTQUOTA_HANDLE_OK macro.
+****************************************************************************/
+
+bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if (!check_fsp_open(conn, req, fsp)) {
+ return false;
+ }
+
+ if (fsp->is_directory) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle == NULL) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->type != FAKE_FILE_TYPE_QUOTA) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->private_data == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp. Replacement for the FSP_BELONGS_CONN macro
+****************************************************************************/
+
+bool fsp_belongs_conn(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if ((fsp) && (conn) && ((conn)==(fsp)->conn)
+ && (req->vuid == (fsp)->vuid)) {
+ return True;
+ }
+
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return False;
+}
+
+/****************************************************************************
+ Reply to a (netbios-level) special message.
+****************************************************************************/
+
+void reply_special(char *inbuf)
+{
+ int msg_type = CVAL(inbuf,0);
+ int msg_flags = CVAL(inbuf,1);
+ fstring name1,name2;
+ char name_type = 0;
+
+ /*
+ * We only really use 4 bytes of the outbuf, but for the smb_setlen
+ * calculation & friends (srv_send_smb uses that) we need the full smb
+ * header.
+ */
+ char outbuf[smb_size];
+
+ static bool already_got_session = False;
+
+ *name1 = *name2 = 0;
+
+ memset(outbuf, '\0', sizeof(outbuf));
+
+ smb_setlen(outbuf,0);
+
+ switch (msg_type) {
+ case 0x81: /* session request */
+
+ if (already_got_session) {
+ exit_server_cleanly("multiple session request not permitted");
+ }
+
+ SCVAL(outbuf,0,0x82);
+ SCVAL(outbuf,3,0);
+ if (name_len(inbuf+4) > 50 ||
+ name_len(inbuf+4 + name_len(inbuf + 4)) > 50) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ return;
+ }
+ name_extract(inbuf,4,name1);
+ name_type = name_extract(inbuf,4 + name_len(inbuf + 4),name2);
+ DEBUG(2,("netbios connect: name1=%s name2=%s\n",
+ name1,name2));
+
+ set_local_machine_name(name1, True);
+ set_remote_machine_name(name2, True);
+
+ DEBUG(2,("netbios connect: local=%s remote=%s, name type = %x\n",
+ get_local_machine_name(), get_remote_machine_name(),
+ name_type));
+
+ if (name_type == 'R') {
+ /* We are being asked for a pathworks session ---
+ no thanks! */
+ SCVAL(outbuf, 0,0x83);
+ break;
+ }
+
+ /* only add the client's machine name to the list
+ of possibly valid usernames if we are operating
+ in share mode security */
+ if (lp_security() == SEC_SHARE) {
+ add_session_user(get_remote_machine_name());
+ }
+
+ reload_services(True);
+ reopen_logs();
+
+ already_got_session = True;
+ break;
+
+ case 0x89: /* session keepalive request
+ (some old clients produce this?) */
+ SCVAL(outbuf,0,SMBkeepalive);
+ SCVAL(outbuf,3,0);
+ break;
+
+ case 0x82: /* positive session response */
+ case 0x83: /* negative session response */
+ case 0x84: /* retarget session response */
+ DEBUG(0,("Unexpected session response\n"));
+ break;
+
+ case SMBkeepalive: /* session keepalive */
+ default:
+ return;
+ }
+
+ DEBUG(5,("init msg_type=0x%x msg_flags=0x%x\n",
+ msg_type, msg_flags));
+
+ srv_send_smb(smbd_server_fd(), outbuf, false);
+ return;
+}
+
+/****************************************************************************
+ Reply to a tcon.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ const char *service;
+ char *service_buf = NULL;
+ char *password = NULL;
+ char *dev = NULL;
+ int pwlen=0;
+ NTSTATUS nt_status;
+ char *p;
+ DATA_BLOB password_blob;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBtcon);
+
+ if (smb_buflen(req->inbuf) < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ p = smb_buf(req->inbuf)+1;
+ p += srvstr_pull_buf_talloc(ctx, req->inbuf, req->flags2,
+ &service_buf, p, STR_TERMINATE) + 1;
+ pwlen = srvstr_pull_buf_talloc(ctx, req->inbuf, req->flags2,
+ &password, p, STR_TERMINATE) + 1;
+ p += pwlen;
+ p += srvstr_pull_buf_talloc(ctx, req->inbuf, req->flags2,
+ &dev, p, STR_TERMINATE) + 1;
+
+ if (service_buf == NULL || password == NULL || dev == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+ p = strrchr_m(service_buf,'\\');
+ if (p) {
+ service = p+1;
+ } else {
+ service = service_buf;
+ }
+
+ password_blob = data_blob(password, pwlen+1);
+
+ conn = make_connection(service,password_blob,dev,req->vuid,&nt_status);
+ req->conn = conn;
+
+ data_blob_clear_free(&password_blob);
+
+ if (!conn) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ reply_outbuf(req, 2, 0);
+ SSVAL(req->outbuf,smb_vwv0,max_recv);
+ SSVAL(req->outbuf,smb_vwv1,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ DEBUG(3,("tcon service=%s cnum=%d\n",
+ service, conn->cnum));
+
+ END_PROFILE(SMBtcon);
+ return;
+}
+
+/****************************************************************************
+ Reply to a tcon and X.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *service = NULL;
+ DATA_BLOB password;
+ TALLOC_CTX *ctx = talloc_tos();
+ /* what the cleint thinks the device is */
+ char *client_devicetype = NULL;
+ /* what the server tells the client the share represents */
+ const char *server_devicetype;
+ NTSTATUS nt_status;
+ int passlen;
+ char *path = NULL;
+ char *p, *q;
+ uint16 tcon_flags;
+
+ START_PROFILE(SMBtconX);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ passlen = SVAL(req->inbuf,smb_vwv3);
+ tcon_flags = SVAL(req->inbuf,smb_vwv2);
+
+ /* we might have to close an old one */
+ if ((tcon_flags & 0x1) && conn) {
+ close_cnum(conn,req->vuid);
+ req->conn = NULL;
+ conn = NULL;
+ }
+
+ if ((passlen > MAX_PASS_LEN) || (passlen >= smb_buflen(req->inbuf))) {
+ reply_doserror(req, ERRDOS, ERRbuftoosmall);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if (global_encrypted_passwords_negotiated) {
+ password = data_blob_talloc(talloc_tos(), smb_buf(req->inbuf),
+ passlen);
+ if (lp_security() == SEC_SHARE) {
+ /*
+ * Security = share always has a pad byte
+ * after the password.
+ */
+ p = smb_buf(req->inbuf) + passlen + 1;
+ } else {
+ p = smb_buf(req->inbuf) + passlen;
+ }
+ } else {
+ password = data_blob_talloc(talloc_tos(), smb_buf(req->inbuf),
+ passlen+1);
+ /* Ensure correct termination */
+ password.data[passlen]=0;
+ p = smb_buf(req->inbuf) + passlen + 1;
+ }
+
+ p += srvstr_pull_buf_talloc(ctx, req->inbuf, req->flags2, &path, p,
+ STR_TERMINATE);
+
+ if (path == NULL) {
+ data_blob_clear_free(&password);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /*
+ * the service name can be either: \\server\share
+ * or share directly like on the DELL PowerVault 705
+ */
+ if (*path=='\\') {
+ q = strchr_m(path+2,'\\');
+ if (!q) {
+ data_blob_clear_free(&password);
+ reply_doserror(req, ERRDOS, ERRnosuchshare);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ service = q+1;
+ } else {
+ service = path;
+ }
+
+ p += srvstr_pull_talloc(ctx, req->inbuf, req->flags2,
+ &client_devicetype, p,
+ MIN(6,smb_bufrem(req->inbuf, p)), STR_ASCII);
+
+ if (client_devicetype == NULL) {
+ data_blob_clear_free(&password);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ DEBUG(4,("Client requested device type [%s] for share [%s]\n", client_devicetype, service));
+
+ conn = make_connection(service, password, client_devicetype,
+ req->vuid, &nt_status);
+ req->conn =conn;
+
+ data_blob_clear_free(&password);
+
+ if (!conn) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if ( IS_IPC(conn) )
+ server_devicetype = "IPC";
+ else if ( IS_PRINT(conn) )
+ server_devicetype = "LPT1:";
+ else
+ server_devicetype = "A:";
+
+ if (Protocol < PROTOCOL_NT1) {
+ reply_outbuf(req, 2, 0);
+ if (message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ } else {
+ /* NT sets the fstype of IPC$ to the null string */
+ const char *fstype = IS_IPC(conn) ? "" : lp_fstype(SNUM(conn));
+
+ if (tcon_flags & TCONX_FLAG_EXTENDED_RESPONSE) {
+ /* Return permissions. */
+ uint32 perm1 = 0;
+ uint32 perm2 = 0;
+
+ reply_outbuf(req, 7, 0);
+
+ if (IS_IPC(conn)) {
+ perm1 = FILE_ALL_ACCESS;
+ perm2 = FILE_ALL_ACCESS;
+ } else {
+ perm1 = CAN_WRITE(conn) ?
+ SHARE_ALL_ACCESS :
+ SHARE_READ_ONLY;
+ }
+
+ SIVAL(req->outbuf, smb_vwv3, perm1);
+ SIVAL(req->outbuf, smb_vwv5, perm2);
+ } else {
+ reply_outbuf(req, 3, 0);
+ }
+
+ if ((message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1)
+ || (message_push_string(&req->outbuf, fstype,
+ STR_TERMINATE) == -1)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /* what does setting this bit do? It is set by NT4 and
+ may affect the ability to autorun mounted cdroms */
+ SSVAL(req->outbuf, smb_vwv2, SMB_SUPPORT_SEARCH_BITS|
+ (lp_csc_policy(SNUM(conn)) << 2));
+
+ init_dfsroot(conn, req->inbuf, req->outbuf);
+ }
+
+
+ DEBUG(3,("tconX service=%s \n",
+ service));
+
+ /* set the incoming and outgoing tid to the just created one */
+ SSVAL(req->inbuf,smb_tid,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ END_PROFILE(SMBtconX);
+
+ chain_reply(req);
+ return;
+}
+
+/****************************************************************************
+ Reply to an unknown type.
+****************************************************************************/
+
+void reply_unknown_new(struct smb_request *req, uint8 type)
+{
+ DEBUG(0, ("unknown command type (%s): type=%d (0x%X)\n",
+ smb_fn_name(type), type, type));
+ reply_doserror(req, ERRSRV, ERRunknownsmb);
+ return;
+}
+
+/****************************************************************************
+ Reply to an ioctl.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ioctl(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint16 device;
+ uint16 function;
+ uint32 ioctl_code;
+ int replysize;
+ char *p;
+
+ START_PROFILE(SMBioctl);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ device = SVAL(req->inbuf,smb_vwv1);
+ function = SVAL(req->inbuf,smb_vwv2);
+ ioctl_code = (device << 16) + function;
+
+ DEBUG(4, ("Received IOCTL (code 0x%x)\n", ioctl_code));
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ replysize = 32;
+ break;
+ default:
+ reply_doserror(req, ERRSRV, ERRnosupport);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ reply_outbuf(req, 8, replysize+1);
+ SSVAL(req->outbuf,smb_vwv1,replysize); /* Total data bytes returned */
+ SSVAL(req->outbuf,smb_vwv5,replysize); /* Data bytes this buffer */
+ SSVAL(req->outbuf,smb_vwv6,52); /* Offset to data */
+ p = smb_buf(req->outbuf);
+ memset(p, '\0', replysize+1); /* valgrind-safe. */
+ p += 1; /* Allow for alignment */
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ {
+ files_struct *fsp = file_fsp(SVAL(req->inbuf,
+ smb_vwv0));
+ if (!fsp) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ SSVAL(p,0,fsp->rap_print_jobid); /* Job number */
+ srvstr_push((char *)req->outbuf, req->flags2, p+2,
+ global_myname(), 15,
+ STR_TERMINATE|STR_ASCII);
+ if (conn) {
+ srvstr_push((char *)req->outbuf, req->flags2,
+ p+18, lp_servicename(SNUM(conn)),
+ 13, STR_TERMINATE|STR_ASCII);
+ } else {
+ memset(p+18, 0, 13);
+ }
+ break;
+ }
+ }
+
+ END_PROFILE(SMBioctl);
+ return;
+}
+
+/****************************************************************************
+ Strange checkpath NTSTATUS mapping.
+****************************************************************************/
+
+static NTSTATUS map_checkpath_error(const char *inbuf, NTSTATUS status)
+{
+ /* Strange DOS error code semantics only for checkpath... */
+ if (!(SVAL(inbuf,smb_flg2) & FLAGS2_32_BIT_ERROR_CODES)) {
+ if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_INVALID,status)) {
+ /* We need to map to ERRbadpath */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+ return status;
+}
+
+/****************************************************************************
+ Reply to a checkpath.
+****************************************************************************/
+
+void reply_checkpath(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcheckpath);
+
+ srvstr_get_path(ctx,(char *)req->inbuf, req->flags2, &name,
+ smb_buf(req->inbuf) + 1, 0,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = map_checkpath_error((char *)req->inbuf, status);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ name,
+ &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+ goto path_err;
+ }
+
+ DEBUG(3,("reply_checkpath %s mode=%d\n", name, (int)SVAL(req->inbuf,smb_vwv0)));
+
+ status = unix_convert(ctx, conn, name, False, &name, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto path_err;
+ }
+
+ status = check_name(conn, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("reply_checkpath: check_name of %s failed (%s)\n",name,nt_errstr(status)));
+ goto path_err;
+ }
+
+ if (!VALID_STAT(sbuf) && (SMB_VFS_STAT(conn,name,&sbuf) != 0)) {
+ DEBUG(3,("reply_checkpath: stat of %s failed (%s)\n",name,strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ goto path_err;
+ }
+
+ if (!S_ISDIR(sbuf.st_mode)) {
+ reply_botherror(req, NT_STATUS_NOT_A_DIRECTORY,
+ ERRDOS, ERRbadpath);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBcheckpath);
+ return;
+
+ path_err:
+
+ END_PROFILE(SMBcheckpath);
+
+ /* We special case this - as when a Windows machine
+ is parsing a path is steps through the components
+ one at a time - if a component fails it expects
+ ERRbadpath, not ERRbadfile.
+ */
+ status = map_checkpath_error((char *)req->inbuf, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Windows returns different error codes if
+ * the parent directory is valid but not the
+ * last component - it returns NT_STATUS_OBJECT_NAME_NOT_FOUND
+ * for that case and NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * if the path is invalid.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpath);
+ return;
+ }
+
+ reply_nterror(req, status);
+}
+
+/****************************************************************************
+ Reply to a getatr.
+****************************************************************************/
+
+void reply_getatr(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ SMB_STRUCT_STAT sbuf;
+ int mode=0;
+ SMB_OFF_T size=0;
+ time_t mtime=0;
+ char *p;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBgetatr);
+
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname, p,
+ 0, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ fname,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+
+ /* dos smetimes asks for a stat of "" - it returns a "hidden directory"
+ under WfWg - weird! */
+ if (*fname == '\0') {
+ mode = aHIDDEN | aDIR;
+ if (!CAN_WRITE(conn)) {
+ mode |= aRONLY;
+ }
+ size = 0;
+ mtime = 0;
+ } else {
+ status = unix_convert(ctx, conn, fname, False, &fname, NULL,&sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("reply_getatr: check_name of %s failed (%s)\n",fname,nt_errstr(status)));
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+ if (!VALID_STAT(sbuf) && (SMB_VFS_STAT(conn,fname,&sbuf) != 0)) {
+ DEBUG(3,("reply_getatr: stat of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req, ERRDOS,ERRbadfile);
+ END_PROFILE(SMBgetatr);
+ return;
+ }
+
+ mode = dos_mode(conn,fname,&sbuf);
+ size = sbuf.st_size;
+ mtime = sbuf.st_mtime;
+ if (mode & aDIR) {
+ size = 0;
+ }
+ }
+
+ reply_outbuf(req, 10, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,mode);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv3,(uint32)size);
+
+ if (Protocol >= PROTOCOL_NT1) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) | FLAGS2_IS_LONG_NAME);
+ }
+
+ DEBUG(3,("reply_getatr: name=%s mode=%d size=%u\n", fname, mode, (unsigned int)size ) );
+
+ END_PROFILE(SMBgetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a setatr.
+****************************************************************************/
+
+void reply_setatr(struct smb_request *req)
+{
+ struct timespec ts[2];
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ int mode;
+ time_t mtime;
+ SMB_STRUCT_STAT sbuf;
+ char *p;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBsetatr);
+
+ ZERO_STRUCT(ts);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname, p,
+ 0, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ fname,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, fname, False, &fname, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ if (fname[0] == '.' && fname[1] == '\0') {
+ /*
+ * Not sure here is the right place to catch this
+ * condition. Might be moved to somewhere else later -- vl
+ */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ mode = SVAL(req->inbuf,smb_vwv0);
+ mtime = srv_make_unix_date3(req->inbuf+smb_vwv1);
+
+ ts[1] = convert_time_t_to_timespec(mtime);
+ status = smb_set_file_time(conn, NULL, fname,
+ &sbuf, ts, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+
+ if (mode != FILE_ATTRIBUTE_NORMAL) {
+ if (VALID_STAT_OF_DIR(sbuf))
+ mode |= aDIR;
+ else
+ mode &= ~aDIR;
+
+ if (file_set_dosmode(conn,fname,mode,&sbuf,NULL,false) != 0) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsetatr);
+ return;
+ }
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG( 3, ( "setatr name=%s mode=%d\n", fname, mode ) );
+
+ END_PROFILE(SMBsetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a dskattr.
+****************************************************************************/
+
+void reply_dskattr(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ SMB_BIG_UINT dfree,dsize,bsize;
+ START_PROFILE(SMBdskattr);
+
+ if (get_dfree_info(conn,".",True,&bsize,&dfree,&dsize) == (SMB_BIG_UINT)-1) {
+ reply_unixerror(req, ERRHRD, ERRgeneral);
+ END_PROFILE(SMBdskattr);
+ return;
+ }
+
+ reply_outbuf(req, 5, 0);
+
+ if (Protocol <= PROTOCOL_LANMAN2) {
+ double total_space, free_space;
+ /* we need to scale this to a number that DOS6 can handle. We
+ use floating point so we can handle large drives on systems
+ that don't have 64 bit integers
+
+ we end up displaying a maximum of 2G to DOS systems
+ */
+ total_space = dsize * (double)bsize;
+ free_space = dfree * (double)bsize;
+
+ dsize = (SMB_BIG_UINT)((total_space+63*512) / (64*512));
+ dfree = (SMB_BIG_UINT)((free_space+63*512) / (64*512));
+
+ if (dsize > 0xFFFF) dsize = 0xFFFF;
+ if (dfree > 0xFFFF) dfree = 0xFFFF;
+
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,64); /* this must be 64 for dos systems */
+ SSVAL(req->outbuf,smb_vwv2,512); /* and this must be 512 */
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ } else {
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,bsize/512);
+ SSVAL(req->outbuf,smb_vwv2,512);
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ }
+
+ DEBUG(3,("dskattr dfree=%d\n", (unsigned int)dfree));
+
+ END_PROFILE(SMBdskattr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a search.
+ Can be called from SMBsearch, SMBffirst or SMBfunique.
+****************************************************************************/
+
+void reply_search(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *mask = NULL;
+ char *directory = NULL;
+ char *fname = NULL;
+ SMB_OFF_T size;
+ uint32 mode;
+ time_t date;
+ uint32 dirtype;
+ unsigned int numentries = 0;
+ unsigned int maxentries = 0;
+ bool finished = False;
+ char *p;
+ int status_len;
+ char *path = NULL;
+ char status[21];
+ int dptr_num= -1;
+ bool check_descend = False;
+ bool expect_close = False;
+ NTSTATUS nt_status;
+ bool mask_contains_wcard = False;
+ bool allow_long_path_components = (req->flags2 & FLAGS2_LONG_PATH_COMPONENTS) ? True : False;
+ TALLOC_CTX *ctx = talloc_tos();
+ bool ask_sharemode = lp_parm_bool(SNUM(conn), "smbd", "search ask sharemode", true);
+
+ START_PROFILE(SMBsearch);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ if (lp_posix_pathnames()) {
+ reply_unknown_new(req, CVAL(req->inbuf, smb_com));
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ /* If we were called as SMBffirst then we must expect close. */
+ if(CVAL(req->inbuf,smb_com) == SMBffirst) {
+ expect_close = True;
+ }
+
+ reply_outbuf(req, 1, 3);
+ maxentries = SVAL(req->inbuf,smb_vwv0);
+ dirtype = SVAL(req->inbuf,smb_vwv1);
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path_wcard(ctx,
+ (char *)req->inbuf,
+ req->flags2,
+ &path,
+ p,
+ 0,
+ STR_TERMINATE,
+ &nt_status,
+ &mask_contains_wcard);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ nt_status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ path,
+ &path,
+ &mask_contains_wcard);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ p++;
+ status_len = SVAL(p, 0);
+ p += 2;
+
+ /* dirtype &= ~aDIR; */
+
+ if (status_len == 0) {
+ SMB_STRUCT_STAT sbuf;
+
+ nt_status = unix_convert(ctx, conn, path, True,
+ &directory, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ nt_status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ p = strrchr_m(directory,'/');
+ if (!p) {
+ mask = directory;
+ directory = talloc_strdup(ctx,".");
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ } else {
+ *p = 0;
+ mask = p+1;
+ }
+
+ if (*directory == '\0') {
+ directory = talloc_strdup(ctx,".");
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ }
+ memset((char *)status,'\0',21);
+ SCVAL(status,0,(dirtype & 0x1F));
+
+ nt_status = dptr_create(conn,
+ directory,
+ True,
+ expect_close,
+ req->smbpid,
+ mask,
+ mask_contains_wcard,
+ dirtype,
+ &conn->dirptr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ dptr_num = dptr_dnum(conn->dirptr);
+ } else {
+ int status_dirtype;
+
+ memcpy(status,p,21);
+ status_dirtype = CVAL(status,0) & 0x1F;
+ if (status_dirtype != (dirtype & 0x1F)) {
+ dirtype = status_dirtype;
+ }
+
+ conn->dirptr = dptr_fetch(status+12,&dptr_num);
+ if (!conn->dirptr) {
+ goto SearchEmpty;
+ }
+ string_set(&conn->dirpath,dptr_path(dptr_num));
+ mask = dptr_wcard(dptr_num);
+ if (!mask) {
+ goto SearchEmpty;
+ }
+ /*
+ * For a 'continue' search we have no string. So
+ * check from the initial saved string.
+ */
+ mask_contains_wcard = ms_has_wild(mask);
+ dirtype = dptr_attr(dptr_num);
+ }
+
+ DEBUG(4,("dptr_num is %d\n",dptr_num));
+
+ if ((dirtype&0x1F) == aVOLID) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ if (!make_dir_struct(ctx,buf,"???????????",volume_label(SNUM(conn)),
+ 0,aVOLID,0,!allow_long_path_components)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ dptr_fill(buf+12,dptr_num);
+ if (dptr_zero(buf+12) && (status_len==0)) {
+ numentries = 1;
+ } else {
+ numentries = 0;
+ }
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ } else {
+ unsigned int i;
+ maxentries = MIN(
+ maxentries,
+ ((BUFFER_SIZE -
+ ((uint8 *)smb_buf(req->outbuf) + 3 - req->outbuf))
+ /DIR_STRUCT_SIZE));
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ conn->dirpath,lp_dontdescend(SNUM(conn))));
+ if (in_list(conn->dirpath, lp_dontdescend(SNUM(conn)),True)) {
+ check_descend = True;
+ }
+
+ for (i=numentries;(i<maxentries) && !finished;i++) {
+ finished = !get_dir_entry(ctx,
+ conn,
+ mask,
+ dirtype,
+ &fname,
+ &size,
+ &mode,
+ &date,
+ check_descend,
+ ask_sharemode);
+ if (!finished) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ if (!make_dir_struct(ctx,
+ buf,
+ mask,
+ fname,
+ size,
+ mode,
+ date,
+ !allow_long_path_components)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ if (!dptr_fill(buf+12,dptr_num)) {
+ break;
+ }
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+ numentries++;
+ }
+ }
+ }
+
+ SearchEmpty:
+
+ /* If we were called as SMBffirst with smb_search_id == NULL
+ and no entries were found then return error and close dirptr
+ (X/Open spec) */
+
+ if (numentries == 0) {
+ dptr_close(&dptr_num);
+ } else if(expect_close && status_len == 0) {
+ /* Close the dptr - we know it's gone */
+ dptr_close(&dptr_num);
+ }
+
+ /* If we were called as SMBfunique, then we can close the dirptr now ! */
+ if(dptr_num >= 0 && CVAL(req->inbuf,smb_com) == SMBfunique) {
+ dptr_close(&dptr_num);
+ }
+
+ if ((numentries == 0) && !mask_contains_wcard) {
+ reply_botherror(req, STATUS_NO_MORE_FILES, ERRDOS, ERRnofiles);
+ END_PROFILE(SMBsearch);
+ return;
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,numentries);
+ SSVAL(req->outbuf,smb_vwv1,3 + numentries * DIR_STRUCT_SIZE);
+ SCVAL(smb_buf(req->outbuf),0,5);
+ SSVAL(smb_buf(req->outbuf),1,numentries*DIR_STRUCT_SIZE);
+
+ /* The replies here are never long name. */
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) & (~FLAGS2_IS_LONG_NAME));
+ if (!allow_long_path_components) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2)
+ & (~FLAGS2_LONG_PATH_COMPONENTS));
+ }
+
+ /* This SMB *always* returns ASCII names. Remove the unicode bit in flags2. */
+ SSVAL(req->outbuf, smb_flg2,
+ (SVAL(req->outbuf, smb_flg2) & (~FLAGS2_UNICODE_STRINGS)));
+
+ if (!directory) {
+ directory = dptr_path(dptr_num);
+ }
+
+ DEBUG(4,("%s mask=%s path=%s dtype=%d nument=%u of %u\n",
+ smb_fn_name(CVAL(req->inbuf,smb_com)),
+ mask,
+ directory ? directory : "./",
+ dirtype,
+ numentries,
+ maxentries ));
+
+ END_PROFILE(SMBsearch);
+ return;
+}
+
+/****************************************************************************
+ Reply to a fclose (stop directory search).
+****************************************************************************/
+
+void reply_fclose(struct smb_request *req)
+{
+ int status_len;
+ char status[21];
+ int dptr_num= -2;
+ char *p;
+ char *path = NULL;
+ NTSTATUS err;
+ bool path_contains_wcard = False;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBfclose);
+
+ if (lp_posix_pathnames()) {
+ reply_unknown_new(req, CVAL(req->inbuf, smb_com));
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path_wcard(ctx,
+ (char *)req->inbuf,
+ req->flags2,
+ &path,
+ p,
+ 0,
+ STR_TERMINATE,
+ &err,
+ &path_contains_wcard);
+ if (!NT_STATUS_IS_OK(err)) {
+ reply_nterror(req, err);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+ p++;
+ status_len = SVAL(p,0);
+ p += 2;
+
+ if (status_len == 0) {
+ reply_doserror(req, ERRSRV, ERRsrverror);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ memcpy(status,p,21);
+
+ if(dptr_fetch(status+12,&dptr_num)) {
+ /* Close the dptr - we know it's gone */
+ dptr_close(&dptr_num);
+ }
+
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,0);
+
+ DEBUG(3,("search close\n"));
+
+ END_PROFILE(SMBfclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open.
+****************************************************************************/
+
+void reply_open(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ uint32 fattr=0;
+ SMB_OFF_T size = 0;
+ time_t mtime=0;
+ int info;
+ SMB_STRUCT_STAT sbuf;
+ files_struct *fsp;
+ int oplock_request;
+ int deny_mode;
+ uint32 dos_attr;
+ uint32 access_mask;
+ uint32 share_mode;
+ uint32 create_disposition;
+ uint32 create_options = 0;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBopen);
+ return;
+ }
+
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ deny_mode = SVAL(req->inbuf,smb_vwv0);
+ dos_attr = SVAL(req->inbuf,smb_vwv1);
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf)+1, 0,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBopen);
+ return;
+ }
+
+ if (!map_open_params_to_ntcreate(
+ fname, deny_mode, OPENX_FILE_EXISTS_OPEN, &access_mask,
+ &share_mode, &create_disposition, &create_options)) {
+ reply_nterror(req, NT_STATUS_DOS(ERRDOS, ERRbadaccess));
+ END_PROFILE(SMBopen);
+ return;
+ }
+
+ status = create_file(conn, /* conn */
+ req, /* req */
+ 0, /* root_dir_fid */
+ fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ dos_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ &sbuf); /* psbuf */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ END_PROFILE(SMBopen);
+ return;
+ }
+ reply_openerror(req, status);
+ END_PROFILE(SMBopen);
+ return;
+ }
+
+ size = sbuf.st_size;
+ fattr = dos_mode(conn,fsp->fsp_name,&sbuf);
+ mtime = sbuf.st_mtime;
+
+ if (fattr & aDIR) {
+ DEBUG(3,("attempt to open a directory %s\n",fsp->fsp_name));
+ close_file(fsp,ERROR_CLOSE);
+ reply_doserror(req, ERRDOS,ERRnoaccess);
+ END_PROFILE(SMBopen);
+ return;
+ }
+
+ reply_outbuf(req, 7, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv1,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv4,(uint32)size);
+ SSVAL(req->outbuf,smb_vwv6,deny_mode);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+ END_PROFILE(SMBopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open and X.
+****************************************************************************/
+
+void reply_open_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ uint16 open_flags;
+ int deny_mode;
+ uint32 smb_attr;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ int ex_oplock_request;
+ int core_oplock_request;
+ int oplock_request;
+#if 0
+ int smb_sattr = SVAL(req->inbuf,smb_vwv4);
+ uint32 smb_time = make_unix_date3(req->inbuf+smb_vwv6);
+#endif
+ int smb_ofun;
+ uint32 fattr=0;
+ int mtime=0;
+ SMB_STRUCT_STAT sbuf;
+ int smb_action = 0;
+ files_struct *fsp;
+ NTSTATUS status;
+ SMB_BIG_UINT allocation_size;
+ ssize_t retval = -1;
+ uint32 access_mask;
+ uint32 share_mode;
+ uint32 create_disposition;
+ uint32 create_options = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopenX);
+
+ if (req->wct < 15) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBopenX);
+ return;
+ }
+
+ open_flags = SVAL(req->inbuf,smb_vwv2);
+ deny_mode = SVAL(req->inbuf,smb_vwv3);
+ smb_attr = SVAL(req->inbuf,smb_vwv5);
+ ex_oplock_request = EXTENDED_OPLOCK_REQUEST(req->inbuf);
+ core_oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ oplock_request = ex_oplock_request | core_oplock_request;
+ smb_ofun = SVAL(req->inbuf,smb_vwv8);
+ allocation_size = (SMB_BIG_UINT)IVAL(req->inbuf,smb_vwv9);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ reply_open_pipe_and_X(conn, req);
+ } else {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ }
+ END_PROFILE(SMBopenX);
+ return;
+ }
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf), 0, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBopenX);
+ return;
+ }
+
+ if (!map_open_params_to_ntcreate(
+ fname, deny_mode, smb_ofun, &access_mask,
+ &share_mode, &create_disposition, &create_options)) {
+ reply_nterror(req, NT_STATUS_DOS(ERRDOS, ERRbadaccess));
+ END_PROFILE(SMBopenX);
+ return;
+ }
+
+ status = create_file(conn, /* conn */
+ req, /* req */
+ 0, /* root_dir_fid */
+ fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ smb_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* pinfo */
+ &sbuf); /* psbuf */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBopenX);
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ reply_openerror(req, status);
+ return;
+ }
+
+ /* Setting the "size" field in vwv9 and vwv10 causes the file to be set to this size,
+ if the file is truncated or created. */
+ if (((smb_action == FILE_WAS_CREATED) || (smb_action == FILE_WAS_OVERWRITTEN)) && allocation_size) {
+ fsp->initial_allocation_size = smb_roundup(fsp->conn, allocation_size);
+ if (vfs_allocate_file_space(fsp, fsp->initial_allocation_size) == -1) {
+ close_file(fsp,ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ END_PROFILE(SMBopenX);
+ return;
+ }
+ retval = vfs_set_filelen(fsp, (SMB_OFF_T)allocation_size);
+ if (retval < 0) {
+ close_file(fsp,ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ END_PROFILE(SMBopenX);
+ return;
+ }
+ sbuf.st_size = get_allocation_size(conn,fsp,&sbuf);
+ }
+
+ fattr = dos_mode(conn,fsp->fsp_name,&sbuf);
+ mtime = sbuf.st_mtime;
+ if (fattr & aDIR) {
+ close_file(fsp,ERROR_CLOSE);
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBopenX);
+ return;
+ }
+
+ /* If the caller set the extended oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for extended oplock reply.
+ */
+
+ if (ex_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ if(ex_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ /* If the caller set the core oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for core oplock reply.
+ */
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ reply_outbuf(req, 19, 0);
+ } else {
+ reply_outbuf(req, 15, 0);
+ }
+
+ if (core_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(core_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ SSVAL(req->outbuf,smb_vwv2,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv3,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv6,(uint32)sbuf.st_size);
+ SSVAL(req->outbuf,smb_vwv8,GET_OPENX_MODE(deny_mode));
+ SSVAL(req->outbuf,smb_vwv11,smb_action);
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ SIVAL(req->outbuf, smb_vwv15, STD_RIGHT_ALL_ACCESS);
+ }
+
+ END_PROFILE(SMBopenX);
+ chain_reply(req);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBulogoffX.
+****************************************************************************/
+
+void reply_ulogoffX(struct smb_request *req)
+{
+ user_struct *vuser;
+
+ START_PROFILE(SMBulogoffX);
+
+ vuser = get_valid_user_struct(req->vuid);
+
+ if(vuser == NULL) {
+ DEBUG(3,("ulogoff, vuser id %d does not map to user.\n",
+ req->vuid));
+ }
+
+ /* in user level security we are supposed to close any files
+ open by this user */
+ if ((vuser != NULL) && (lp_security() != SEC_SHARE)) {
+ file_close_user(req->vuid);
+ }
+
+ invalidate_vuid(req->vuid);
+
+ reply_outbuf(req, 2, 0);
+
+ DEBUG( 3, ( "ulogoffX vuid=%d\n", req->vuid ) );
+
+ END_PROFILE(SMBulogoffX);
+ chain_reply(req);
+}
+
+/****************************************************************************
+ Reply to a mknew or a create.
+****************************************************************************/
+
+void reply_mknew(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ int com;
+ uint32 fattr = 0;
+ struct timespec ts[2];
+ files_struct *fsp;
+ int oplock_request = 0;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+ uint32 access_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
+ uint32 share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ uint32 create_disposition;
+ uint32 create_options = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcreate);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBcreate);
+ return;
+ }
+
+ fattr = SVAL(req->inbuf,smb_vwv0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ com = SVAL(req->inbuf,smb_com);
+
+ ts[1] =convert_time_t_to_timespec(
+ srv_make_unix_date3(req->inbuf + smb_vwv1));
+ /* mtime. */
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf) + 1, 0,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcreate);
+ return;
+ }
+
+ if (fattr & aVOLID) {
+ DEBUG(0,("Attempt to create file (%s) with volid set - "
+ "please report this\n", fname));
+ }
+
+ if(com == SMBmknew) {
+ /* We should fail if file exists. */
+ create_disposition = FILE_CREATE;
+ } else {
+ /* Create if file doesn't exist, truncate if it does. */
+ create_disposition = FILE_OVERWRITE_IF;
+ }
+
+ status = create_file(conn, /* conn */
+ req, /* req */
+ 0, /* root_dir_fid */
+ fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ fattr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ &sbuf); /* psbuf */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBcreate);
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ reply_openerror(req, status);
+ return;
+ }
+
+ ts[0] = get_atimespec(&sbuf); /* atime. */
+ status = smb_set_file_time(conn, fsp, fsp->fsp_name, &sbuf, ts, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBcreate);
+ reply_openerror(req, status);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG( 2, ( "reply_mknew: file %s\n", fsp->fsp_name ) );
+ DEBUG( 3, ( "reply_mknew %s fd=%d dmode=0x%x\n",
+ fsp->fsp_name, fsp->fh->fd, (unsigned int)fattr ) );
+
+ END_PROFILE(SMBcreate);
+ return;
+}
+
+/****************************************************************************
+ Reply to a create temporary file.
+****************************************************************************/
+
+void reply_ctemp(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *fname = NULL;
+ uint32 fattr;
+ files_struct *fsp;
+ int oplock_request;
+ int tmpfd;
+ SMB_STRUCT_STAT sbuf;
+ char *s;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBctemp);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ fattr = SVAL(req->inbuf,smb_vwv0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &fname,
+ smb_buf(req->inbuf)+1, 0, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+ if (*fname) {
+ fname = talloc_asprintf(ctx,
+ "%s/TMXXXXXX",
+ fname);
+ } else {
+ fname = talloc_strdup(ctx, "TMXXXXXX");
+ }
+
+ if (!fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ fname,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, fname, False, &fname, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ tmpfd = smb_mkstemp(fname);
+ if (tmpfd == -1) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ SMB_VFS_STAT(conn,fname,&sbuf);
+
+ /* We should fail if file does not exist. */
+ status = open_file_ntcreate(conn, req, fname, &sbuf,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN,
+ 0,
+ fattr,
+ oplock_request,
+ NULL, &fsp);
+
+ /* close fd from smb_mkstemp() */
+ close(tmpfd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ END_PROFILE(SMBctemp);
+ return;
+ }
+ reply_openerror(req, status);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ /* the returned filename is relative to the directory */
+ s = strrchr_m(fsp->fsp_name, '/');
+ if (!s) {
+ s = fsp->fsp_name;
+ } else {
+ s++;
+ }
+
+#if 0
+ /* Tested vs W2K3 - this doesn't seem to be here - null terminated filename is the only
+ thing in the byte section. JRA */
+ SSVALS(p, 0, -1); /* what is this? not in spec */
+#endif
+ if (message_push_string(&req->outbuf, s, STR_ASCII|STR_TERMINATE)
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBctemp);
+ return;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG( 2, ( "reply_ctemp: created temp file %s\n", fsp->fsp_name ) );
+ DEBUG( 3, ( "reply_ctemp %s fd=%d umode=0%o\n", fsp->fsp_name,
+ fsp->fh->fd, (unsigned int)sbuf.st_mode ) );
+
+ END_PROFILE(SMBctemp);
+ return;
+}
+
+/*******************************************************************
+ Check if a user is allowed to rename a file.
+********************************************************************/
+
+static NTSTATUS can_rename(connection_struct *conn, files_struct *fsp,
+ uint16 dirtype, SMB_STRUCT_STAT *pst)
+{
+ uint32 fmode;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ fmode = dos_mode(conn, fsp->fsp_name, pst);
+ if ((fmode & ~dirtype) & (aHIDDEN | aSYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (S_ISDIR(pst->st_mode)) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->access_mask & (DELETE_ACCESS|FILE_WRITE_ATTRIBUTES)) {
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+/*******************************************************************
+ * unlink a file with all relevant access checks
+ *******************************************************************/
+
+static NTSTATUS do_unlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *fname,
+ uint32 dirtype)
+{
+ SMB_STRUCT_STAT sbuf;
+ uint32 fattr;
+ files_struct *fsp;
+ uint32 dirtype_orig = dirtype;
+ NTSTATUS status;
+
+ DEBUG(10,("do_unlink: %s, dirtype = %d\n", fname, dirtype ));
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ if (SMB_VFS_LSTAT(conn,fname,&sbuf) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ fattr = dos_mode(conn,fname,&sbuf);
+
+ if (dirtype & FILE_ATTRIBUTE_NORMAL) {
+ dirtype = aDIR|aARCH|aRONLY;
+ }
+
+ dirtype &= (aDIR|aARCH|aRONLY|aHIDDEN|aSYSTEM);
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (!dir_check_ftype(conn, fattr, dirtype)) {
+ if (fattr & aDIR) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype_orig & 0x8000) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+#if 0
+ if ((fattr & dirtype) & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if ((fattr & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype & 0xFF00) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ dirtype &= 0xFF;
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ /* Can't delete a directory. */
+ if (fattr & aDIR) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+#endif
+
+#if 0 /* JRATEST */
+ else if (dirtype & aDIR) /* Asked for a directory and it isn't. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+#endif /* JRATEST */
+
+ /* Fix for bug #3035 from SATOH Fumiyasu <fumiyas@miraclelinux.com>
+
+ On a Windows share, a file with read-only dosmode can be opened with
+ DELETE_ACCESS. But on a Samba share (delete readonly = no), it
+ fails with NT_STATUS_CANNOT_DELETE error.
+
+ This semantic causes a problem that a user can not
+ rename a file with read-only dosmode on a Samba share
+ from a Windows command prompt (i.e. cmd.exe, but can rename
+ from Windows Explorer).
+ */
+
+ if (!lp_delete_readonly(SNUM(conn))) {
+ if (fattr & aRONLY) {
+ return NT_STATUS_CANNOT_DELETE;
+ }
+ }
+
+ /* On open checks the open itself will check the share mode, so
+ don't do it here as we'll get it wrong. */
+
+ status = create_file_unixpath
+ (conn, /* conn */
+ req, /* req */
+ fname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_NON_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ &sbuf); /* psbuf */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_file_unixpath failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, True, &conn->server_info->utok)) {
+ close_file(fsp, NORMAL_CLOSE);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return close_file(fsp,NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ The guts of the unlink command, split out so it may be called by the NT SMB
+ code.
+****************************************************************************/
+
+NTSTATUS unlink_internals(connection_struct *conn, struct smb_request *req,
+ uint32 dirtype, const char *name_in, bool has_wild)
+{
+ const char *directory = NULL;
+ char *mask = NULL;
+ char *name = NULL;
+ char *p = NULL;
+ int count=0;
+ NTSTATUS status = NT_STATUS_OK;
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ status = unix_convert(ctx, conn, name_in, has_wild, &name, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ p = strrchr_m(name,'/');
+ if (!p) {
+ directory = talloc_strdup(ctx, ".");
+ if (!directory) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mask = name;
+ } else {
+ *p = 0;
+ directory = name;
+ mask = p+1;
+ }
+
+ /*
+ * We should only check the mangled cache
+ * here if unix_convert failed. This means
+ * that the path in 'mask' doesn't exist
+ * on the file system and so we need to look
+ * for a possible mangle. This patch from
+ * Tine Smukavec <valentin.smukavec@hermes.si>.
+ */
+
+ if (!VALID_STAT(sbuf) && mangle_is_mangled(mask,conn->params)) {
+ char *new_mask = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ mask,
+ &new_mask,
+ conn->params );
+ if (new_mask) {
+ mask = new_mask;
+ }
+ }
+
+ if (!has_wild) {
+ directory = talloc_asprintf(ctx,
+ "%s/%s",
+ directory,
+ mask);
+ if (!directory) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (dirtype == 0) {
+ dirtype = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = do_unlink(conn, req, directory, dirtype);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ count++;
+ } else {
+ struct smb_Dir *dir_hnd = NULL;
+ long offset = 0;
+ const char *dname;
+
+ if ((dirtype & SAMBA_ATTRIBUTES_MASK) == aDIR) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (strequal(mask,"????????.???")) {
+ mask[0] = '*';
+ mask[1] = '\0';
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dir_hnd = OpenDir(talloc_tos(), conn, directory, mask,
+ dirtype);
+ if (dir_hnd == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* XXXX the CIFS spec says that if bit0 of the flags2 field is set then
+ the pattern matches against the long name, otherwise the short name
+ We don't implement this yet XXXX
+ */
+
+ status = NT_STATUS_NO_SUCH_FILE;
+
+ while ((dname = ReadDirName(dir_hnd, &offset))) {
+ SMB_STRUCT_STAT st;
+ char *fname = NULL;
+
+ if (!is_visible_file(conn, directory, dname, &st, True)) {
+ continue;
+ }
+
+ /* Quick check for "." and ".." */
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ if(!mask_match(dname, mask, conn->case_sensitive)) {
+ continue;
+ }
+
+ fname = talloc_asprintf(ctx, "%s/%s",
+ directory,
+ dname);
+ if (!fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(dir_hnd);
+ return status;
+ }
+
+ status = do_unlink(conn, req, fname, dirtype);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(fname);
+ continue;
+ }
+
+ count++;
+ DEBUG(3,("unlink_internals: successful unlink [%s]\n",
+ fname));
+
+ TALLOC_FREE(fname);
+ }
+ TALLOC_FREE(dir_hnd);
+ }
+
+ if (count == 0 && NT_STATUS_IS_OK(status) && errno != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Reply to a unlink
+****************************************************************************/
+
+void reply_unlink(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ uint32 dirtype;
+ NTSTATUS status;
+ bool path_contains_wcard = False;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBunlink);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBunlink);
+ return;
+ }
+
+ dirtype = SVAL(req->inbuf,smb_vwv0);
+
+ srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &name,
+ smb_buf(req->inbuf) + 1, 0,
+ STR_TERMINATE, &status, &path_contains_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlink);
+ return;
+ }
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ name,
+ &name,
+ &path_contains_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBunlink);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlink);
+ return;
+ }
+
+ DEBUG(3,("reply_unlink : %s\n",name));
+
+ status = unlink_internals(conn, req, dirtype, name,
+ path_contains_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ END_PROFILE(SMBunlink);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlink);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+ END_PROFILE(SMBunlink);
+
+ return;
+}
+
+/****************************************************************************
+ Fail for readbraw.
+****************************************************************************/
+
+static void fail_readraw(void)
+{
+ const char *errstr = talloc_asprintf(talloc_tos(),
+ "FAIL ! reply_readbraw: socket write fail (%s)",
+ strerror(errno));
+ if (!errstr) {
+ errstr = "";
+ }
+ exit_server_cleanly(errstr);
+}
+
+/****************************************************************************
+ Fake (read/write) sendfile. Returns -1 on read or write fail.
+****************************************************************************/
+
+static ssize_t fake_sendfile(files_struct *fsp, SMB_OFF_T startpos,
+ size_t nread)
+{
+ size_t bufsize;
+ size_t tosend = nread;
+ char *buf;
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ bufsize = MIN(nread, 65536);
+
+ if (!(buf = SMB_MALLOC_ARRAY(char, bufsize))) {
+ return -1;
+ }
+
+ while (tosend > 0) {
+ ssize_t ret;
+ size_t cur_read;
+
+ if (tosend > bufsize) {
+ cur_read = bufsize;
+ } else {
+ cur_read = tosend;
+ }
+ ret = read_file(fsp,buf,startpos,cur_read);
+ if (ret == -1) {
+ SAFE_FREE(buf);
+ return -1;
+ }
+
+ /* If we had a short read, fill with zeros. */
+ if (ret < cur_read) {
+ memset(buf, '\0', cur_read - ret);
+ }
+
+ if (write_data(smbd_server_fd(),buf,cur_read) != cur_read) {
+ SAFE_FREE(buf);
+ return -1;
+ }
+ tosend -= cur_read;
+ startpos += cur_read;
+ }
+
+ SAFE_FREE(buf);
+ return (ssize_t)nread;
+}
+
+/****************************************************************************
+ Return a readbraw error (4 bytes of zero).
+****************************************************************************/
+
+static void reply_readbraw_error(void)
+{
+ char header[4];
+ SIVAL(header,0,0);
+ if (write_data(smbd_server_fd(),header,4) != 4) {
+ fail_readraw();
+ }
+}
+
+/****************************************************************************
+ Use sendfile in readbraw.
+****************************************************************************/
+
+void send_file_readbraw(connection_struct *conn,
+ files_struct *fsp,
+ SMB_OFF_T startpos,
+ size_t nread,
+ ssize_t mincount)
+{
+ char *outbuf = NULL;
+ ssize_t ret=0;
+
+#if defined(WITH_SENDFILE)
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ * reply_readbraw has already checked the length.
+ */
+
+ if ( (chain_size == 0) && (nread > 0) && (fsp->base_fsp == NULL) &&
+ (fsp->wcp == NULL) && lp_use_sendfile(SNUM(conn)) ) {
+ char header[4];
+ DATA_BLOB header_blob;
+
+ _smb_setlen(header,nread);
+ header_blob = data_blob_const(header, 4);
+
+ if (SMB_VFS_SENDFILE(smbd_server_fd(), fsp,
+ &header_blob, startpos, nread) == -1) {
+ /* Returning ENOSYS means no data at all was sent.
+ * Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_readbraw;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readbraw: sendfile not available. Faking..\n"));
+
+ if (fake_sendfile(fsp, startpos, nread) == -1) {
+ DEBUG(0,("send_file_readbraw: fake_sendfile failed for file %s (%s).\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readbraw fake_sendfile failed");
+ }
+ return;
+ }
+
+ DEBUG(0,("send_file_readbraw: sendfile failed for file %s (%s). Terminating\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readbraw sendfile failed");
+ }
+
+ return;
+ }
+#endif
+
+normal_readbraw:
+
+ outbuf = TALLOC_ARRAY(NULL, char, nread+4);
+ if (!outbuf) {
+ DEBUG(0,("send_file_readbraw: TALLOC_ARRAY failed for size %u.\n",
+ (unsigned)(nread+4)));
+ reply_readbraw_error();
+ return;
+ }
+
+ if (nread > 0) {
+ ret = read_file(fsp,outbuf+4,startpos,nread);
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (ret < mincount)
+ ret = 0;
+#else
+ if (ret < nread)
+ ret = 0;
+#endif
+ }
+
+ _smb_setlen(outbuf,ret);
+ if (write_data(smbd_server_fd(),outbuf,4+ret) != 4+ret)
+ fail_readraw();
+
+ TALLOC_FREE(outbuf);
+}
+
+/****************************************************************************
+ Reply to a readbraw (core+ protocol).
+****************************************************************************/
+
+void reply_readbraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ ssize_t maxcount,mincount;
+ size_t nread = 0;
+ SMB_OFF_T startpos;
+ files_struct *fsp;
+ SMB_STRUCT_STAT st;
+ SMB_OFF_T size = 0;
+
+ START_PROFILE(SMBreadbraw);
+
+ if (srv_is_signing_active() || is_encrypted_packet(req->inbuf)) {
+ exit_server_cleanly("reply_readbraw: SMB signing/sealing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 8) {
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /*
+ * Special check if an oplock break has been issued
+ * and the readraw request croses on the wire, we must
+ * return a zero length response here.
+ */
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ /*
+ * We have to do a check_fsp by hand here, as
+ * we must always return 4 zero bytes on error,
+ * not a NTSTATUS.
+ */
+
+ if (!fsp || !conn || conn != fsp->conn ||
+ req->vuid != fsp->vuid ||
+ fsp->is_directory || fsp->fh->fd == -1) {
+ /*
+ * fsp could be NULL here so use the value from the packet. JRA.
+ */
+ DEBUG(3,("reply_readbraw: fnum %d not valid "
+ "- cache prime?\n",
+ (int)SVAL(req->inbuf,smb_vwv0)));
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /* Do a "by hand" version of CHECK_READ. */
+ if (!(fsp->can_read ||
+ ((req->flags2 & FLAGS2_READ_PERMIT_EXECUTE) &&
+ (fsp->access_mask & FILE_EXECUTE)))) {
+ DEBUG(3,("reply_readbraw: fnum %d not readable.\n",
+ (int)SVAL(req->inbuf,smb_vwv0)));
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ flush_write_cache(fsp, READRAW_FLUSH);
+
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv1);
+ if(req->wct == 10) {
+ /*
+ * This is a large offset (64 bit) read.
+ */
+#ifdef LARGE_SMB_OFF_T
+
+ startpos |= (((SMB_OFF_T)IVAL(req->inbuf,smb_vwv8)) << 32);
+
+#else /* !LARGE_SMB_OFF_T */
+
+ /*
+ * Ensure we haven't been sent a >32 bit offset.
+ */
+
+ if(IVAL(req->inbuf,smb_vwv8) != 0) {
+ DEBUG(0,("reply_readbraw: large offset "
+ "(%x << 32) used and we don't support "
+ "64 bit offsets.\n",
+ (unsigned int)IVAL(req->inbuf,smb_vwv8) ));
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+#endif /* LARGE_SMB_OFF_T */
+
+ if(startpos < 0) {
+ DEBUG(0,("reply_readbraw: negative 64 bit "
+ "readraw offset (%.0f) !\n",
+ (double)startpos ));
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+ }
+
+ maxcount = (SVAL(req->inbuf,smb_vwv3) & 0xFFFF);
+ mincount = (SVAL(req->inbuf,smb_vwv4) & 0xFFFF);
+
+ /* ensure we don't overrun the packet size */
+ maxcount = MIN(65535,maxcount);
+
+ if (is_locked(fsp,(uint32)req->smbpid,
+ (SMB_BIG_UINT)maxcount,
+ (SMB_BIG_UINT)startpos,
+ READ_LOCK)) {
+ reply_readbraw_error();
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ if (SMB_VFS_FSTAT(fsp, &st) == 0) {
+ size = st.st_size;
+ }
+
+ if (startpos >= size) {
+ nread = 0;
+ } else {
+ nread = MIN(maxcount,(size - startpos));
+ }
+
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (nread < mincount)
+ nread = 0;
+#endif
+
+ DEBUG( 3, ( "reply_readbraw: fnum=%d start=%.0f max=%lu "
+ "min=%lu nread=%lu\n",
+ fsp->fnum, (double)startpos,
+ (unsigned long)maxcount,
+ (unsigned long)mincount,
+ (unsigned long)nread ) );
+
+ send_file_readbraw(conn, fsp, startpos, nread, mincount);
+
+ DEBUG(5,("reply_readbraw finished\n"));
+ END_PROFILE(SMBreadbraw);
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lockread (core+ protocol).
+****************************************************************************/
+
+void reply_lockread(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ ssize_t nread = -1;
+ char *data;
+ SMB_OFF_T startpos;
+ size_t numtoread;
+ NTSTATUS status;
+ files_struct *fsp;
+ struct byte_range_lock *br_lck = NULL;
+ char *p = NULL;
+
+ START_PROFILE(SMBlockread);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req->inbuf)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ release_level_2_oplocks_on_change(fsp);
+
+ numtoread = SVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv2);
+
+ numtoread = MIN(BUFFER_SIZE - (smb_size + 3*2 + 3), numtoread);
+
+ reply_outbuf(req, 5, numtoread + 3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ /*
+ * NB. Discovered by Menny Hamburger at Mainsoft. This is a core+
+ * protocol request that predates the read/write lock concept.
+ * Thus instead of asking for a read lock here we need to ask
+ * for a write lock. JRA.
+ * Note that the requested lock size is unaffected by max_recv.
+ */
+
+ br_lck = do_lock(smbd_messaging_context(),
+ fsp,
+ req->smbpid,
+ (SMB_BIG_UINT)numtoread,
+ (SMB_BIG_UINT)startpos,
+ WRITE_LOCK,
+ WINDOWS_LOCK,
+ False, /* Non-blocking lock. */
+ &status,
+ NULL);
+ TALLOC_FREE(br_lck);
+
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ /*
+ * However the requested READ size IS affected by max_recv. Insanity.... JRA.
+ */
+
+ if (numtoread > max_recv) {
+ DEBUG(0,("reply_lockread: requested read size (%u) is greater than maximum allowed (%u). \
+Returning short read of maximum allowed for compatibility with Windows 2000.\n",
+ (unsigned int)numtoread, (unsigned int)max_recv ));
+ numtoread = MIN(numtoread,max_recv);
+ }
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ srv_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ p = smb_buf(req->outbuf);
+ SCVAL(p,0,0); /* pad byte. */
+ SSVAL(p,1,nread);
+
+ DEBUG(3,("lockread fnum=%d num=%d nread=%d\n",
+ fsp->fnum, (int)numtoread, (int)nread));
+
+ END_PROFILE(SMBlockread);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a read.
+****************************************************************************/
+
+void reply_read(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtoread;
+ ssize_t nread = 0;
+ char *data;
+ SMB_OFF_T startpos;
+ int outsize = 0;
+ files_struct *fsp;
+
+ START_PROFILE(SMBread);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req->inbuf)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ numtoread = SVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv2);
+
+ numtoread = MIN(BUFFER_SIZE-outsize,numtoread);
+
+ /*
+ * The requested read size cannot be greater than max_recv. JRA.
+ */
+ if (numtoread > max_recv) {
+ DEBUG(0,("reply_read: requested read size (%u) is greater than maximum allowed (%u). \
+Returning short read of maximum allowed for compatibility with Windows 2000.\n",
+ (unsigned int)numtoread, (unsigned int)max_recv ));
+ numtoread = MIN(numtoread,max_recv);
+ }
+
+ reply_outbuf(req, 5, numtoread+3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ if (is_locked(fsp, (uint32)req->smbpid, (SMB_BIG_UINT)numtoread,
+ (SMB_BIG_UINT)startpos, READ_LOCK)) {
+ reply_doserror(req, ERRDOS,ERRlock);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (numtoread > 0)
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_unixerror(req, ERRDOS,ERRnoaccess);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ srv_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,nread);
+
+ DEBUG( 3, ( "read fnum=%d num=%d nread=%d\n",
+ fsp->fnum, (int)numtoread, (int)nread ) );
+
+ END_PROFILE(SMBread);
+ return;
+}
+
+/****************************************************************************
+ Setup readX header.
+****************************************************************************/
+
+static int setup_readX_header(char *outbuf, size_t smb_maxcnt)
+{
+ int outsize;
+ char *data;
+
+ outsize = srv_set_message(outbuf,12,smb_maxcnt,False);
+ data = smb_buf(outbuf);
+
+ memset(outbuf+smb_vwv0,'\0',24); /* valgrind init. */
+
+ SCVAL(outbuf,smb_vwv0,0xFF);
+ SSVAL(outbuf,smb_vwv2,0xFFFF); /* Remaining - must be -1. */
+ SSVAL(outbuf,smb_vwv5,smb_maxcnt);
+ SSVAL(outbuf,smb_vwv6,smb_offset(data,outbuf));
+ SSVAL(outbuf,smb_vwv7,(smb_maxcnt >> 16));
+ SSVAL(smb_buf(outbuf),-2,smb_maxcnt);
+ /* Reset the outgoing length, set_message truncates at 0x1FFFF. */
+ _smb_setlen_large(outbuf,(smb_size + 12*2 + smb_maxcnt - 4));
+ return outsize;
+}
+
+/****************************************************************************
+ Reply to a read and X - possibly using sendfile.
+****************************************************************************/
+
+static void send_file_readX(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp, SMB_OFF_T startpos,
+ size_t smb_maxcnt)
+{
+ SMB_STRUCT_STAT sbuf;
+ ssize_t nread = -1;
+
+ if(SMB_VFS_FSTAT(fsp, &sbuf) == -1) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ if (startpos > sbuf.st_size) {
+ smb_maxcnt = 0;
+ } else if (smb_maxcnt > (sbuf.st_size - startpos)) {
+ smb_maxcnt = (sbuf.st_size - startpos);
+ }
+
+ if (smb_maxcnt == 0) {
+ goto normal_read;
+ }
+
+#if defined(WITH_SENDFILE)
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ */
+
+ if ((chain_size == 0) && (CVAL(req->inbuf,smb_vwv0) == 0xFF) &&
+ !is_encrypted_packet(req->inbuf) && (fsp->base_fsp == NULL) &&
+ lp_use_sendfile(SNUM(conn)) && (fsp->wcp == NULL) ) {
+ uint8 headerbuf[smb_size + 12 * 2];
+ DATA_BLOB header;
+
+ /*
+ * Set up the packet header before send. We
+ * assume here the sendfile will work (get the
+ * correct amount of data).
+ */
+
+ header = data_blob_const(headerbuf, sizeof(headerbuf));
+
+ construct_reply_common((char *)req->inbuf, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ if ((nread = SMB_VFS_SENDFILE(smbd_server_fd(), fsp, &header, startpos, smb_maxcnt)) == -1) {
+ /* Returning ENOSYS or EINVAL means no data at all was sent.
+ Do this as a normal read. */
+ if (errno == ENOSYS || errno == EINVAL) {
+ goto normal_read;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readX: sendfile not available. Faking..\n"));
+ nread = fake_sendfile(fsp, startpos,
+ smb_maxcnt);
+ if (nread == -1) {
+ DEBUG(0,("send_file_readX: fake_sendfile failed for file %s (%s).\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ DEBUG( 3, ( "send_file_readX: fake_sendfile fnum=%d max=%d nread=%d\n",
+ fsp->fnum, (int)smb_maxcnt, (int)nread ) );
+ /* No outbuf here means successful sendfile. */
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ DEBUG(0,("send_file_readX: sendfile failed for file %s (%s). Terminating\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readX sendfile failed");
+ }
+
+ DEBUG( 3, ( "send_file_readX: sendfile fnum=%d max=%d nread=%d\n",
+ fsp->fnum, (int)smb_maxcnt, (int)nread ) );
+ /* No outbuf here means successful sendfile. */
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+#endif
+
+normal_read:
+
+ if ((smb_maxcnt & 0xFF0000) > 0x10000) {
+ uint8 headerbuf[smb_size + 2*12];
+
+ construct_reply_common((char *)req->inbuf, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ /* Send out the header. */
+ if (write_data(smbd_server_fd(), (char *)headerbuf,
+ sizeof(headerbuf)) != sizeof(headerbuf)) {
+ DEBUG(0,("send_file_readX: write_data failed for file %s (%s). Terminating\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readX sendfile failed");
+ }
+ nread = fake_sendfile(fsp, startpos, smb_maxcnt);
+ if (nread == -1) {
+ DEBUG(0,("send_file_readX: fake_sendfile failed for file %s (%s).\n",
+ fsp->fsp_name, strerror(errno) ));
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ reply_outbuf(req, 12, smb_maxcnt);
+
+ nread = read_file(fsp, smb_buf(req->outbuf), startpos, smb_maxcnt);
+ if (nread < 0) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ setup_readX_header((char *)req->outbuf, nread);
+
+ DEBUG( 3, ( "send_file_readX fnum=%d max=%d nread=%d\n",
+ fsp->fnum, (int)smb_maxcnt, (int)nread ) );
+
+ chain_reply(req);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+****************************************************************************/
+
+void reply_read_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ SMB_OFF_T startpos;
+ size_t smb_maxcnt;
+ bool big_readX = False;
+#if 0
+ size_t smb_mincnt = SVAL(req->inbuf,smb_vwv6);
+#endif
+
+ START_PROFILE(SMBreadX);
+
+ if ((req->wct != 10) && (req->wct != 12)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv2));
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv3);
+ smb_maxcnt = SVAL(req->inbuf,smb_vwv5);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_read_and_X(req);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req->inbuf)) {
+ reply_doserror(req, ERRDOS,ERRbadaccess);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (global_client_caps & CAP_LARGE_READX) {
+ size_t upper_size = SVAL(req->inbuf,smb_vwv7);
+ smb_maxcnt |= (upper_size<<16);
+ if (upper_size > 1) {
+ /* Can't do this on a chained packet. */
+ if ((CVAL(req->inbuf,smb_vwv0) != 0xFF)) {
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+ /* We currently don't do this on signed or sealed data. */
+ if (srv_is_signing_active() || is_encrypted_packet(req->inbuf)) {
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+ /* Is there room in the reply for this data ? */
+ if (smb_maxcnt > (0xFFFFFF - (smb_size -4 + 12*2))) {
+ reply_nterror(req,
+ NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+ big_readX = True;
+ }
+ }
+
+ if (req->wct == 12) {
+#ifdef LARGE_SMB_OFF_T
+ /*
+ * This is a large offset (64 bit) read.
+ */
+ startpos |= (((SMB_OFF_T)IVAL(req->inbuf,smb_vwv10)) << 32);
+
+#else /* !LARGE_SMB_OFF_T */
+
+ /*
+ * Ensure we haven't been sent a >32 bit offset.
+ */
+
+ if(IVAL(req->inbuf,smb_vwv10) != 0) {
+ DEBUG(0,("reply_read_and_X - large offset (%x << 32) "
+ "used and we don't support 64 bit offsets.\n",
+ (unsigned int)IVAL(req->inbuf,smb_vwv10) ));
+ END_PROFILE(SMBreadX);
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ return;
+ }
+
+#endif /* LARGE_SMB_OFF_T */
+
+ }
+
+ if (is_locked(fsp, (uint32)req->smbpid, (SMB_BIG_UINT)smb_maxcnt,
+ (SMB_BIG_UINT)startpos, READ_LOCK)) {
+ END_PROFILE(SMBreadX);
+ reply_doserror(req, ERRDOS, ERRlock);
+ return;
+ }
+
+ if (!big_readX &&
+ schedule_aio_read_and_X(conn, req, fsp, startpos, smb_maxcnt)) {
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ send_file_readX(conn, req, fsp, startpos, smb_maxcnt);
+
+ END_PROFILE(SMBreadX);
+ return;
+}
+
+/****************************************************************************
+ Error replies to writebraw must have smb_wct == 1. Fix this up.
+****************************************************************************/
+
+void error_to_writebrawerr(struct smb_request *req)
+{
+ uint8 *old_outbuf = req->outbuf;
+
+ reply_outbuf(req, 1, 0);
+
+ memcpy(req->outbuf, old_outbuf, smb_size);
+ TALLOC_FREE(old_outbuf);
+}
+
+/****************************************************************************
+ Reply to a writebraw (core+ or LANMAN1.0 protocol).
+****************************************************************************/
+
+void reply_writebraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *buf = NULL;
+ ssize_t nwritten=0;
+ ssize_t total_written=0;
+ size_t numtowrite=0;
+ size_t tcount;
+ SMB_OFF_T startpos;
+ char *data=NULL;
+ bool write_through;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwritebraw);
+
+ /*
+ * If we ever reply with an error, it must have the SMB command
+ * type of SMBwritec, not SMBwriteBraw, as this tells the client
+ * we're finished.
+ */
+ SCVAL(req->inbuf,smb_com,SMBwritec);
+
+ if (srv_is_signing_active()) {
+ END_PROFILE(SMBwritebraw);
+ exit_server_cleanly("reply_writebraw: SMB signing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 12) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+ if (!check_fsp(conn, req, fsp)) {
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ tcount = IVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv3);
+ write_through = BITSETW(req->inbuf+smb_vwv7,0);
+
+ /* We have to deal with slightly different formats depending
+ on whether we are using the core+ or lanman1.0 protocol */
+
+ if(Protocol <= PROTOCOL_COREPLUS) {
+ numtowrite = SVAL(smb_buf(req->inbuf),-2);
+ data = smb_buf(req->inbuf);
+ } else {
+ numtowrite = SVAL(req->inbuf,smb_vwv10);
+ data = smb_base(req->inbuf) + SVAL(req->inbuf, smb_vwv11);
+ }
+
+ /* Ensure we don't write bytes past the end of this packet. */
+ if (data + numtowrite > smb_base(req->inbuf) + smb_len(req->inbuf)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (is_locked(fsp,(uint32)req->smbpid,(SMB_BIG_UINT)tcount,
+ (SMB_BIG_UINT)startpos, WRITE_LOCK)) {
+ reply_doserror(req, ERRDOS, ERRlock);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (numtowrite>0) {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ DEBUG(3,("reply_writebraw: initial write fnum=%d start=%.0f num=%d "
+ "wrote=%d sync=%d\n",
+ fsp->fnum, (double)startpos, (int)numtowrite,
+ (int)nwritten, (int)write_through));
+
+ if (nwritten < (ssize_t)numtowrite) {
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ total_written = nwritten;
+
+ /* Allocate a buffer of 64k + length. */
+ buf = TALLOC_ARRAY(NULL, char, 65540);
+ if (!buf) {
+ reply_doserror(req, ERRDOS, ERRnomem);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ /* Return a SMBwritebraw message to the redirector to tell
+ * it to send more bytes */
+
+ memcpy(buf, req->inbuf, smb_size);
+ srv_set_message(buf,Protocol>PROTOCOL_COREPLUS?1:0,0,True);
+ SCVAL(buf,smb_com,SMBwritebraw);
+ SSVALS(buf,smb_vwv0,0xFFFF);
+ show_msg(buf);
+ if (!srv_send_smb(smbd_server_fd(),
+ buf,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("reply_writebraw: srv_send_smb "
+ "failed.");
+ }
+
+ /* Now read the raw data into the buffer and write it */
+ status = read_smb_length(smbd_server_fd(), buf, SMB_SECONDARY_WAIT,
+ &numtowrite);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ /* Set up outbuf to return the correct size */
+ reply_outbuf(req, 1, 0);
+
+ if (numtowrite != 0) {
+
+ if (numtowrite > 0xFFFF) {
+ DEBUG(0,("reply_writebraw: Oversize secondary write "
+ "raw requested (%u). Terminating\n",
+ (unsigned int)numtowrite ));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ if (tcount > nwritten+numtowrite) {
+ DEBUG(3,("reply_writebraw: Client overestimated the "
+ "write %d %d %d\n",
+ (int)tcount,(int)nwritten,(int)numtowrite));
+ }
+
+ status = read_data(smbd_server_fd(), buf+4, numtowrite);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("reply_writebraw: Oversize secondary write "
+ "raw read failed (%s). Terminating\n",
+ nt_errstr(status)));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite);
+ if (nwritten == -1) {
+ TALLOC_FREE(buf);
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ if (nwritten > 0) {
+ total_written += nwritten;
+ }
+ }
+
+ TALLOC_FREE(buf);
+ SSVAL(req->outbuf,smb_vwv0,total_written);
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writebraw: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ reply_nterror(req, status);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ DEBUG(3,("reply_writebraw: secondart write fnum=%d start=%.0f num=%d "
+ "wrote=%d\n",
+ fsp->fnum, (double)startpos, (int)numtowrite,
+ (int)total_written));
+
+ /* We won't return a status if write through is not selected - this
+ * follows what WfWg does */
+ END_PROFILE(SMBwritebraw);
+
+ if (!write_through && total_written==tcount) {
+
+#if RABBIT_PELLET_FIX
+ /*
+ * Fix for "rabbit pellet" mode, trigger an early TCP ack by
+ * sending a SMBkeepalive. Thanks to DaveCB at Sun for this.
+ * JRA.
+ */
+ if (!send_keepalive(smbd_server_fd())) {
+ exit_server_cleanly("reply_writebraw: send of "
+ "keepalive failed");
+ }
+#endif
+ TALLOC_FREE(req->outbuf);
+ }
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a writeunlock (core+).
+****************************************************************************/
+
+void reply_writeunlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ ssize_t nwritten = -1;
+ size_t numtowrite;
+ SMB_OFF_T startpos;
+ char *data;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp;
+
+ START_PROFILE(SMBwriteunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS,ERRbadaccess);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ numtowrite = SVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv2);
+ data = smb_buf(req->inbuf) + 3;
+
+ if (numtowrite
+ && is_locked(fsp, (uint32)req->smbpid, (SMB_BIG_UINT)numtowrite,
+ (SMB_BIG_UINT)startpos, WRITE_LOCK)) {
+ reply_doserror(req, ERRDOS, ERRlock);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ /* The special X/Open SMB protocol handling of
+ zero length writes is *NOT* done for
+ this call */
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ status = sync_file(conn, fsp, False /* write through */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writeunlock: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ if(((nwritten < numtowrite) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ if (numtowrite) {
+ status = do_unlock(smbd_messaging_context(),
+ fsp,
+ req->smbpid,
+ (SMB_BIG_UINT)numtowrite,
+ (SMB_BIG_UINT)startpos,
+ WINDOWS_LOCK);
+
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+ }
+
+ reply_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3,("writeunlock fnum=%d num=%d wrote=%d\n",
+ fsp->fnum, (int)numtowrite, (int)nwritten));
+
+ END_PROFILE(SMBwriteunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a write.
+****************************************************************************/
+
+void reply_write(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ ssize_t nwritten = -1;
+ SMB_OFF_T startpos;
+ char *data;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwrite);
+
+ if (req->wct < 5) {
+ END_PROFILE(SMBwrite);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_write(req);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ numtowrite = SVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv2);
+ data = smb_buf(req->inbuf) + 3;
+
+ if (is_locked(fsp, (uint32)req->smbpid, (SMB_BIG_UINT)numtowrite,
+ (SMB_BIG_UINT)startpos, WRITE_LOCK)) {
+ reply_doserror(req, ERRDOS, ERRlock);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ /*
+ * X/Open SMB protocol says that if smb_vwv1 is
+ * zero then the file size should be extended or
+ * truncated to the size given in smb_vwv[2-3].
+ */
+
+ if(numtowrite == 0) {
+ /*
+ * This is actually an allocate call, and set EOF. JRA.
+ */
+ nwritten = vfs_allocate_file_space(fsp, (SMB_OFF_T)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+ nwritten = vfs_set_filelen(fsp, (SMB_OFF_T)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+ trigger_write_time_update_immediate(fsp);
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ status = sync_file(conn, fsp, False);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ reply_nterror(req, status);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3,("write fnum=%d num=%d wrote=%d\n", fsp->fnum, (int)numtowrite, (int)nwritten));
+
+ END_PROFILE(SMBwrite);
+ return;
+}
+
+/****************************************************************************
+ Ensure a buffer is a valid writeX for recvfile purposes.
+****************************************************************************/
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+bool is_valid_writeX_buffer(const uint8_t *inbuf)
+{
+ size_t numtowrite;
+ connection_struct *conn = NULL;
+ unsigned int doff = 0;
+ size_t len = smb_len_large(inbuf);
+
+ if (is_encrypted_packet(inbuf)) {
+ /* Can't do this on encrypted
+ * connections. */
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_com) != SMBwriteX) {
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_vwv0) != 0xFF ||
+ CVAL(inbuf,smb_wct) != 14) {
+ DEBUG(10,("is_valid_writeX_buffer: chained or "
+ "invalid word length.\n"));
+ return false;
+ }
+
+ conn = conn_find(SVAL(inbuf, smb_tid));
+ if (conn == NULL) {
+ DEBUG(10,("is_valid_writeX_buffer: bad tid\n"));
+ return false;
+ }
+ if (IS_IPC(conn)) {
+ DEBUG(10,("is_valid_writeX_buffer: IPC$ tid\n"));
+ return false;
+ }
+ doff = SVAL(inbuf,smb_vwv11);
+
+ numtowrite = SVAL(inbuf,smb_vwv10);
+
+ if (len > doff && len - doff > 0xFFFF) {
+ numtowrite |= (((size_t)SVAL(inbuf,smb_vwv9))<<16);
+ }
+
+ if (numtowrite == 0) {
+ DEBUG(10,("is_valid_writeX_buffer: zero write\n"));
+ return false;
+ }
+
+ /* Ensure the sizes match up. */
+ if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ /* no pad byte...old smbclient :-( */
+ DEBUG(10,("is_valid_writeX_buffer: small doff %u (min %u)\n",
+ (unsigned int)doff,
+ (unsigned int)STANDARD_WRITE_AND_X_HEADER_SIZE));
+ return false;
+ }
+
+ if (len - doff != numtowrite) {
+ DEBUG(10,("is_valid_writeX_buffer: doff mismatch "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+ return false;
+ }
+
+ DEBUG(10,("is_valid_writeX_buffer: true "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+
+ return true;
+}
+
+/****************************************************************************
+ Reply to a write and X.
+****************************************************************************/
+
+void reply_write_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ SMB_OFF_T startpos;
+ size_t numtowrite;
+ bool write_through;
+ ssize_t nwritten;
+ unsigned int smb_doff;
+ unsigned int smblen;
+ char *data;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwriteX);
+
+ if ((req->wct != 12) && (req->wct != 14)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ numtowrite = SVAL(req->inbuf,smb_vwv10);
+ smb_doff = SVAL(req->inbuf,smb_vwv11);
+ smblen = smb_len(req->inbuf);
+
+ if (req->unread_bytes > 0xFFFF ||
+ (smblen > smb_doff &&
+ smblen - smb_doff > 0xFFFF)) {
+ numtowrite |= (((size_t)SVAL(req->inbuf,smb_vwv9))<<16);
+ }
+
+ if (req->unread_bytes) {
+ /* Can't do a recvfile write on IPC$ */
+ if (IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+ if (numtowrite != req->unread_bytes) {
+ reply_doserror(req, ERRDOS, ERRbadmem);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+ } else {
+ if (smb_doff > smblen || smb_doff + numtowrite < numtowrite ||
+ smb_doff + numtowrite > smblen) {
+ reply_doserror(req, ERRDOS, ERRbadmem);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (req->unread_bytes) {
+ reply_doserror(req, ERRDOS, ERRbadmem);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+ reply_pipe_write_and_X(req);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv2));
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv3);
+ write_through = BITSETW(req->inbuf+smb_vwv7,0);
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ data = smb_base(req->inbuf) + smb_doff;
+
+ if(req->wct == 14) {
+#ifdef LARGE_SMB_OFF_T
+ /*
+ * This is a large offset (64 bit) write.
+ */
+ startpos |= (((SMB_OFF_T)IVAL(req->inbuf,smb_vwv12)) << 32);
+
+#else /* !LARGE_SMB_OFF_T */
+
+ /*
+ * Ensure we haven't been sent a >32 bit offset.
+ */
+
+ if(IVAL(req->inbuf,smb_vwv12) != 0) {
+ DEBUG(0,("reply_write_and_X - large offset (%x << 32) "
+ "used and we don't support 64 bit offsets.\n",
+ (unsigned int)IVAL(req->inbuf,smb_vwv12) ));
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+#endif /* LARGE_SMB_OFF_T */
+ }
+
+ if (is_locked(fsp,(uint32)req->smbpid,
+ (SMB_BIG_UINT)numtowrite,
+ (SMB_BIG_UINT)startpos, WRITE_LOCK)) {
+ reply_doserror(req, ERRDOS, ERRlock);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ /* X/Open SMB protocol says that, unlike SMBwrite
+ if the length is zero then NO truncation is
+ done, just a write of zero. To truncate a file,
+ use SMBwrite. */
+
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+
+ if ((req->unread_bytes == 0) &&
+ schedule_aio_write_and_X(conn, req, fsp, data, startpos,
+ numtowrite)) {
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ reply_outbuf(req, 6, 0);
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+ SSVAL(req->outbuf,smb_vwv4,nwritten>>16);
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3,("writeX fnum=%d num=%d wrote=%d\n",
+ fsp->fnum, (int)numtowrite, (int)nwritten));
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write_and_X: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteX);
+ return;
+ }
+
+ END_PROFILE(SMBwriteX);
+ chain_reply(req);
+ return;
+}
+
+/****************************************************************************
+ Reply to a lseek.
+****************************************************************************/
+
+void reply_lseek(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ SMB_OFF_T startpos;
+ SMB_OFF_T res= -1;
+ int mode,umode;
+ files_struct *fsp;
+
+ START_PROFILE(SMBlseek);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ flush_write_cache(fsp, SEEK_FLUSH);
+
+ mode = SVAL(req->inbuf,smb_vwv1) & 3;
+ /* NB. This doesn't use IVAL_TO_SMB_OFF_T as startpos can be signed in this case. */
+ startpos = (SMB_OFF_T)IVALS(req->inbuf,smb_vwv2);
+
+ switch (mode) {
+ case 0:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ case 1:
+ umode = SEEK_CUR;
+ res = fsp->fh->pos + startpos;
+ break;
+ case 2:
+ umode = SEEK_END;
+ break;
+ default:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ }
+
+ if (umode == SEEK_END) {
+ if((res = SMB_VFS_LSEEK(fsp,startpos,umode)) == -1) {
+ if(errno == EINVAL) {
+ SMB_OFF_T current_pos = startpos;
+ SMB_STRUCT_STAT sbuf;
+
+ if(SMB_VFS_FSTAT(fsp, &sbuf) == -1) {
+ reply_unixerror(req, ERRDOS,
+ ERRnoaccess);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ current_pos += sbuf.st_size;
+ if(current_pos < 0)
+ res = SMB_VFS_LSEEK(fsp,0,SEEK_SET);
+ }
+ }
+
+ if(res == -1) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+ }
+
+ fsp->fh->pos = res;
+
+ reply_outbuf(req, 2, 0);
+ SIVAL(req->outbuf,smb_vwv0,res);
+
+ DEBUG(3,("lseek fnum=%d ofs=%.0f newpos = %.0f mode=%d\n",
+ fsp->fnum, (double)startpos, (double)res, mode));
+
+ END_PROFILE(SMBlseek);
+ return;
+}
+
+/****************************************************************************
+ Reply to a flush.
+****************************************************************************/
+
+void reply_flush(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint16 fnum;
+ files_struct *fsp;
+
+ START_PROFILE(SMBflush);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fnum = SVAL(req->inbuf,smb_vwv0);
+ fsp = file_fsp(fnum);
+
+ if ((fnum != 0xFFFF) && !check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ if (!fsp) {
+ file_sync_all(conn);
+ } else {
+ NTSTATUS status = sync_file(conn, fsp, True);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_flush: sync_file for %s returned %s\n",
+ fsp->fsp_name, nt_errstr(status) ));
+ reply_nterror(req, status);
+ END_PROFILE(SMBflush);
+ return;
+ }
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG(3,("flush\n"));
+ END_PROFILE(SMBflush);
+ return;
+}
+
+/****************************************************************************
+ Reply to a exit.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_exit(struct smb_request *req)
+{
+ START_PROFILE(SMBexit);
+
+ file_close_pid(req->smbpid, req->vuid);
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG(3,("exit\n"));
+
+ END_PROFILE(SMBexit);
+ return;
+}
+
+/****************************************************************************
+ Reply to a close - has to deal with closing a directory opened by NT SMB's.
+****************************************************************************/
+
+void reply_close(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ START_PROFILE(SMBclose);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ /* If it's an IPC, pass off to the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_close(conn, req);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ /*
+ * We can only use CHECK_FSP if we know it's not a directory.
+ */
+
+ if(!fsp || (fsp->conn != conn) || (fsp->vuid != req->vuid)) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ if(fsp->is_directory) {
+ /*
+ * Special case - close NT SMB directory handle.
+ */
+ DEBUG(3,("close directory fnum=%d\n", fsp->fnum));
+ status = close_file(fsp,NORMAL_CLOSE);
+ } else {
+ time_t t;
+ /*
+ * Close ordinary file.
+ */
+
+ DEBUG(3,("close fd=%d fnum=%d (numopen=%d)\n",
+ fsp->fh->fd, fsp->fnum,
+ conn->num_files_open));
+
+ /*
+ * Take care of any time sent in the close.
+ */
+
+ t = srv_make_unix_date3(req->inbuf+smb_vwv1);
+ set_close_write_time(fsp, convert_time_t_to_timespec(t));
+
+ /*
+ * close_file() returns the unix errno if an error
+ * was detected on close - normally this is due to
+ * a disk full error. If not then it was probably an I/O error.
+ */
+
+ status = close_file(fsp,NORMAL_CLOSE);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+ END_PROFILE(SMBclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a writeclose (Core+ protocol).
+****************************************************************************/
+
+void reply_writeclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ ssize_t nwritten = -1;
+ NTSTATUS close_status = NT_STATUS_OK;
+ SMB_OFF_T startpos;
+ char *data;
+ struct timespec mtime;
+ files_struct *fsp;
+
+ START_PROFILE(SMBwriteclose);
+
+ if (req->wct < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS,ERRbadaccess);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ numtowrite = SVAL(req->inbuf,smb_vwv1);
+ startpos = IVAL_TO_SMB_OFF_T(req->inbuf,smb_vwv2);
+ mtime = convert_time_t_to_timespec(srv_make_unix_date3(
+ req->inbuf+smb_vwv4));
+ data = smb_buf(req->inbuf) + 1;
+
+ if (numtowrite
+ && is_locked(fsp, (uint32)req->smbpid, (SMB_BIG_UINT)numtowrite,
+ (SMB_BIG_UINT)startpos, WRITE_LOCK)) {
+ reply_doserror(req, ERRDOS,ERRlock);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+
+ set_close_write_time(fsp, mtime);
+
+ /*
+ * More insanity. W2K only closes the file if writelen > 0.
+ * JRA.
+ */
+
+ if (numtowrite) {
+ DEBUG(3,("reply_writeclose: zero length write doesn't close file %s\n",
+ fsp->fsp_name ));
+ close_status = close_file(fsp,NORMAL_CLOSE);
+ }
+
+ DEBUG(3,("writeclose fnum=%d num=%d wrote=%d (numopen=%d)\n",
+ fsp->fnum, (int)numtowrite, (int)nwritten,
+ conn->num_files_open));
+
+ if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_doserror(req, ERRHRD, ERRdiskfull);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ if(!NT_STATUS_IS_OK(close_status)) {
+ reply_nterror(req, close_status);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+ END_PROFILE(SMBwriteclose);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lock.
+****************************************************************************/
+
+void reply_lock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ SMB_BIG_UINT count,offset;
+ NTSTATUS status;
+ files_struct *fsp;
+ struct byte_range_lock *br_lck = NULL;
+
+ START_PROFILE(SMBlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ release_level_2_oplocks_on_change(fsp);
+
+ count = (SMB_BIG_UINT)IVAL(req->inbuf,smb_vwv1);
+ offset = (SMB_BIG_UINT)IVAL(req->inbuf,smb_vwv3);
+
+ DEBUG(3,("lock fd=%d fnum=%d offset=%.0f count=%.0f\n",
+ fsp->fh->fd, fsp->fnum, (double)offset, (double)count));
+
+ br_lck = do_lock(smbd_messaging_context(),
+ fsp,
+ req->smbpid,
+ count,
+ offset,
+ WRITE_LOCK,
+ WINDOWS_LOCK,
+ False, /* Non-blocking lock. */
+ &status,
+ NULL);
+
+ TALLOC_FREE(br_lck);
+
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBlock);
+ return;
+}
+
+/****************************************************************************
+ Reply to a unlock.
+****************************************************************************/
+
+void reply_unlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ SMB_BIG_UINT count,offset;
+ NTSTATUS status;
+ files_struct *fsp;
+
+ START_PROFILE(SMBunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ count = (SMB_BIG_UINT)IVAL(req->inbuf,smb_vwv1);
+ offset = (SMB_BIG_UINT)IVAL(req->inbuf,smb_vwv3);
+
+ status = do_unlock(smbd_messaging_context(),
+ fsp,
+ req->smbpid,
+ count,
+ offset,
+ WINDOWS_LOCK);
+
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ DEBUG( 3, ( "unlock fd=%d fnum=%d offset=%.0f count=%.0f\n",
+ fsp->fh->fd, fsp->fnum, (double)offset, (double)count ) );
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a tdis.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tdis(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ START_PROFILE(SMBtdis);
+
+ if (!conn) {
+ DEBUG(4,("Invalid connection in tdis\n"));
+ reply_doserror(req, ERRSRV, ERRinvnid);
+ END_PROFILE(SMBtdis);
+ return;
+ }
+
+ conn->used = False;
+
+ close_cnum(conn,req->vuid);
+ req->conn = NULL;
+
+ reply_outbuf(req, 0, 0);
+ END_PROFILE(SMBtdis);
+ return;
+}
+
+/****************************************************************************
+ Reply to a echo.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_echo(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int smb_reverb;
+ int seq_num;
+ unsigned int data_len = smb_buflen(req->inbuf);
+
+ START_PROFILE(SMBecho);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBecho);
+ return;
+ }
+
+ if (data_len > BUFFER_SIZE) {
+ DEBUG(0,("reply_echo: data_len too large.\n"));
+ reply_nterror(req, NT_STATUS_INSUFFICIENT_RESOURCES);
+ END_PROFILE(SMBecho);
+ return;
+ }
+
+ smb_reverb = SVAL(req->inbuf,smb_vwv0);
+
+ reply_outbuf(req, 1, data_len);
+
+ /* copy any incoming data back out */
+ if (data_len > 0) {
+ memcpy(smb_buf(req->outbuf),smb_buf(req->inbuf),data_len);
+ }
+
+ if (smb_reverb > 100) {
+ DEBUG(0,("large reverb (%d)?? Setting to 100\n",smb_reverb));
+ smb_reverb = 100;
+ }
+
+ for (seq_num =1 ; seq_num <= smb_reverb ; seq_num++) {
+ SSVAL(req->outbuf,smb_vwv0,seq_num);
+
+ show_msg((char *)req->outbuf);
+ if (!srv_send_smb(smbd_server_fd(),
+ (char *)req->outbuf,
+ IS_CONN_ENCRYPTED(conn)||req->encrypted))
+ exit_server_cleanly("reply_echo: srv_send_smb failed.");
+ }
+
+ DEBUG(3,("echo %d times\n", smb_reverb));
+
+ TALLOC_FREE(req->outbuf);
+
+ smb_echo_count++;
+
+ END_PROFILE(SMBecho);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printopen.
+****************************************************************************/
+
+void reply_printopen(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ /* Open for exclusive use, write only. */
+ status = print_fsp_open(conn, NULL, req->vuid, &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ DEBUG(3,("openprint fd=%d fnum=%d\n",
+ fsp->fh->fd, fsp->fnum));
+
+ END_PROFILE(SMBsplopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printclose.
+****************************************************************************/
+
+void reply_printclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRerror));
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ DEBUG(3,("printclose fd=%d fnum=%d\n",
+ fsp->fh->fd,fsp->fnum));
+
+ status = close_file(fsp,NORMAL_CLOSE);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsplclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printqueue.
+****************************************************************************/
+
+void reply_printqueue(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int max_count;
+ int start_index;
+
+ START_PROFILE(SMBsplretq);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ max_count = SVAL(req->inbuf,smb_vwv0);
+ start_index = SVAL(req->inbuf,smb_vwv1);
+
+ /* we used to allow the client to get the cnum wrong, but that
+ is really quite gross and only worked when there was only
+ one printer - I think we should now only accept it if they
+ get it right (tridge) */
+ if (!CAN_PRINT(conn)) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ reply_outbuf(req, 2, 3);
+ SSVAL(req->outbuf,smb_vwv0,0);
+ SSVAL(req->outbuf,smb_vwv1,0);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,0);
+
+ DEBUG(3,("printqueue start_index=%d max_count=%d\n",
+ start_index, max_count));
+
+ {
+ print_queue_struct *queue = NULL;
+ print_status_struct status;
+ int count = print_queue_status(SNUM(conn), &queue, &status);
+ int num_to_get = ABS(max_count);
+ int first = (max_count>0?start_index:start_index+max_count+1);
+ int i;
+
+ if (first >= count)
+ num_to_get = 0;
+ else
+ num_to_get = MIN(num_to_get,count-first);
+
+
+ for (i=first;i<first+num_to_get;i++) {
+ char blob[28];
+ char *p = blob;
+
+ srv_put_dos_date2(p,0,queue[i].time);
+ SCVAL(p,4,(queue[i].status==LPQ_PRINTING?2:3));
+ SSVAL(p,5, queue[i].job);
+ SIVAL(p,7,queue[i].size);
+ SCVAL(p,11,0);
+ srvstr_push(blob, req->flags2, p+12,
+ queue[i].fs_user, 16, STR_ASCII);
+
+ if (message_push_blob(
+ &req->outbuf,
+ data_blob_const(
+ blob, sizeof(blob))) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+ }
+
+ if (count > 0) {
+ SSVAL(req->outbuf,smb_vwv0,count);
+ SSVAL(req->outbuf,smb_vwv1,
+ (max_count>0?first+count:first-1));
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,28*count);
+ }
+
+ SAFE_FREE(queue);
+
+ DEBUG(3,("%d entries returned in queue\n",count));
+ }
+
+ END_PROFILE(SMBsplretq);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printwrite.
+****************************************************************************/
+
+void reply_printwrite(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int numtowrite;
+ char *data;
+ files_struct *fsp;
+
+ START_PROFILE(SMBsplwr);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ if (!CHECK_WRITE(fsp)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ numtowrite = SVAL(smb_buf(req->inbuf),1);
+
+ if (smb_buflen(req->inbuf) < numtowrite + 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ data = smb_buf(req->inbuf) + 3;
+
+ if (write_file(req,fsp,data,-1,numtowrite) != numtowrite) {
+ reply_unixerror(req, ERRHRD, ERRdiskfull);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ DEBUG( 3, ( "printwrite fnum=%d num=%d\n", fsp->fnum, numtowrite ) );
+
+ END_PROFILE(SMBsplwr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a mkdir.
+****************************************************************************/
+
+void reply_mkdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *directory = NULL;
+ NTSTATUS status;
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBmkdir);
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &directory,
+ smb_buf(req->inbuf) + 1, 0,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ directory,
+ &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, directory, False, &directory, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+
+ status = create_directory(conn, req, directory);
+
+ DEBUG(5, ("create_directory returned %s\n", nt_errstr(status)));
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ if (!use_nt_status()
+ && NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * Yes, in the DOS error code case we get a
+ * ERRDOS:ERRnoaccess here. See BASE-SAMBA3ERROR
+ * samba4 torture test.
+ */
+ status = NT_STATUS_DOS(ERRDOS, ERRnoaccess);
+ }
+
+ reply_nterror(req, status);
+ END_PROFILE(SMBmkdir);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG( 3, ( "mkdir %s\n", directory ) );
+
+ END_PROFILE(SMBmkdir);
+ return;
+}
+
+/****************************************************************************
+ Static function used by reply_rmdir to delete an entire directory
+ tree recursively. Return True on ok, False on fail.
+****************************************************************************/
+
+static bool recursive_rmdir(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *directory)
+{
+ const char *dname = NULL;
+ bool ret = True;
+ long offset = 0;
+ struct smb_Dir *dir_hnd = OpenDir(talloc_tos(), conn, directory,
+ NULL, 0);
+
+ if(dir_hnd == NULL)
+ return False;
+
+ while((dname = ReadDirName(dir_hnd, &offset))) {
+ char *fullname = NULL;
+ SMB_STRUCT_STAT st;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ if (!is_visible_file(conn, directory, dname, &st, False)) {
+ continue;
+ }
+
+ /* Construct the full name. */
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ directory,
+ dname);
+ if (!fullname) {
+ errno = ENOMEM;
+ ret = False;
+ break;
+ }
+
+ if(SMB_VFS_LSTAT(conn,fullname, &st) != 0) {
+ ret = False;
+ break;
+ }
+
+ if(st.st_mode & S_IFDIR) {
+ if(!recursive_rmdir(ctx, conn, fullname)) {
+ ret = False;
+ break;
+ }
+ if(SMB_VFS_RMDIR(conn,fullname) != 0) {
+ ret = False;
+ break;
+ }
+ } else if(SMB_VFS_UNLINK(conn,fullname) != 0) {
+ ret = False;
+ break;
+ }
+ TALLOC_FREE(fullname);
+ }
+ TALLOC_FREE(dir_hnd);
+ return ret;
+}
+
+/****************************************************************************
+ The internals of the rmdir code - called elsewhere.
+****************************************************************************/
+
+NTSTATUS rmdir_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *directory)
+{
+ int ret;
+ SMB_STRUCT_STAT st;
+
+ /* Might be a symlink. */
+ if(SMB_VFS_LSTAT(conn, directory, &st) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (S_ISLNK(st.st_mode)) {
+ /* Is what it points to a directory ? */
+ if(SMB_VFS_STAT(conn, directory, &st) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ if (!(S_ISDIR(st.st_mode))) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+ ret = SMB_VFS_UNLINK(conn,directory);
+ } else {
+ ret = SMB_VFS_RMDIR(conn,directory);
+ }
+ if (ret == 0) {
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ directory);
+ return NT_STATUS_OK;
+ }
+
+ if(((errno == ENOTEMPTY)||(errno == EEXIST)) && lp_veto_files(SNUM(conn))) {
+ /*
+ * Check to see if the only thing in this directory are
+ * vetoed files/directories. If so then delete them and
+ * retry. If we fail to delete any of them (and we *don't*
+ * do a recursive delete) then fail the rmdir.
+ */
+ const char *dname;
+ long dirpos = 0;
+ struct smb_Dir *dir_hnd = OpenDir(talloc_tos(), conn,
+ directory, NULL, 0);
+
+ if(dir_hnd == NULL) {
+ errno = ENOTEMPTY;
+ goto err;
+ }
+
+ while ((dname = ReadDirName(dir_hnd,&dirpos))) {
+ if((strcmp(dname, ".") == 0) || (strcmp(dname, "..")==0))
+ continue;
+ if (!is_visible_file(conn, directory, dname, &st, False))
+ continue;
+ if(!IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(dir_hnd);
+ errno = ENOTEMPTY;
+ goto err;
+ }
+ }
+
+ /* We only have veto files/directories. Recursive delete. */
+
+ RewindDir(dir_hnd,&dirpos);
+ while ((dname = ReadDirName(dir_hnd,&dirpos))) {
+ char *fullname = NULL;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+ if (!is_visible_file(conn, directory, dname, &st, False)) {
+ continue;
+ }
+
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ directory,
+ dname);
+
+ if(!fullname) {
+ errno = ENOMEM;
+ break;
+ }
+
+ if(SMB_VFS_LSTAT(conn,fullname, &st) != 0) {
+ break;
+ }
+ if(st.st_mode & S_IFDIR) {
+ if(lp_recursive_veto_delete(SNUM(conn))) {
+ if(!recursive_rmdir(ctx, conn, fullname))
+ break;
+ }
+ if(SMB_VFS_RMDIR(conn,fullname) != 0) {
+ break;
+ }
+ } else if(SMB_VFS_UNLINK(conn,fullname) != 0) {
+ break;
+ }
+ TALLOC_FREE(fullname);
+ }
+ TALLOC_FREE(dir_hnd);
+ /* Retry the rmdir */
+ ret = SMB_VFS_RMDIR(conn,directory);
+ }
+
+ err:
+
+ if (ret != 0) {
+ DEBUG(3,("rmdir_internals: couldn't remove directory %s : "
+ "%s\n", directory,strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ directory);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a rmdir.
+****************************************************************************/
+
+void reply_rmdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *directory = NULL;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBrmdir);
+
+ srvstr_get_path(ctx, (char *)req->inbuf, req->flags2, &directory,
+ smb_buf(req->inbuf) + 1, 0,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ directory,
+ &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, directory, False, &directory,
+ NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+
+ dptr_closepath(directory, req->smbpid);
+ status = rmdir_internals(ctx, conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBrmdir);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG( 3, ( "rmdir %s\n", directory ) );
+
+ END_PROFILE(SMBrmdir);
+ return;
+}
+
+/*******************************************************************
+ Resolve wildcards in a filename rename.
+********************************************************************/
+
+static bool resolve_wildcards(TALLOC_CTX *ctx,
+ const char *name1,
+ const char *name2,
+ char **pp_newname)
+{
+ char *name2_copy = NULL;
+ char *root1 = NULL;
+ char *root2 = NULL;
+ char *ext1 = NULL;
+ char *ext2 = NULL;
+ char *p,*p2, *pname1, *pname2;
+
+ name2_copy = talloc_strdup(ctx, name2);
+ if (!name2_copy) {
+ return False;
+ }
+
+ pname1 = strrchr_m(name1,'/');
+ pname2 = strrchr_m(name2_copy,'/');
+
+ if (!pname1 || !pname2) {
+ return False;
+ }
+
+ /* Truncate the copy of name2 at the last '/' */
+ *pname2 = '\0';
+
+ /* Now go past the '/' */
+ pname1++;
+ pname2++;
+
+ root1 = talloc_strdup(ctx, pname1);
+ root2 = talloc_strdup(ctx, pname2);
+
+ if (!root1 || !root2) {
+ return False;
+ }
+
+ p = strrchr_m(root1,'.');
+ if (p) {
+ *p = 0;
+ ext1 = talloc_strdup(ctx, p+1);
+ } else {
+ ext1 = talloc_strdup(ctx, "");
+ }
+ p = strrchr_m(root2,'.');
+ if (p) {
+ *p = 0;
+ ext2 = talloc_strdup(ctx, p+1);
+ } else {
+ ext2 = talloc_strdup(ctx, "");
+ }
+
+ if (!ext1 || !ext2) {
+ return False;
+ }
+
+ p = root1;
+ p2 = root2;
+ while (*p2) {
+ if (*p2 == '?') {
+ /* Hmmm. Should this be mb-aware ? */
+ *p2 = *p;
+ p2++;
+ } else if (*p2 == '*') {
+ *p2 = '\0';
+ root2 = talloc_asprintf(ctx, "%s%s",
+ root2,
+ p);
+ if (!root2) {
+ return False;
+ }
+ break;
+ } else {
+ p2++;
+ }
+ if (*p) {
+ p++;
+ }
+ }
+
+ p = ext1;
+ p2 = ext2;
+ while (*p2) {
+ if (*p2 == '?') {
+ /* Hmmm. Should this be mb-aware ? */
+ *p2 = *p;
+ p2++;
+ } else if (*p2 == '*') {
+ *p2 = '\0';
+ ext2 = talloc_asprintf(ctx, "%s%s",
+ ext2,
+ p);
+ if (!ext2) {
+ return False;
+ }
+ break;
+ } else {
+ p2++;
+ }
+ if (*p) {
+ p++;
+ }
+ }
+
+ if (*ext2) {
+ *pp_newname = talloc_asprintf(ctx, "%s/%s.%s",
+ name2_copy,
+ root2,
+ ext2);
+ } else {
+ *pp_newname = talloc_asprintf(ctx, "%s/%s",
+ name2_copy,
+ root2);
+ }
+
+ if (!*pp_newname) {
+ return False;
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Ensure open files have their names updated. Updated to notify other smbd's
+ asynchronously.
+****************************************************************************/
+
+static void rename_open_files(connection_struct *conn,
+ struct share_mode_lock *lck,
+ const char *newname)
+{
+ files_struct *fsp;
+ bool did_rename = False;
+
+ for(fsp = file_find_di_first(lck->id); fsp;
+ fsp = file_find_di_next(fsp)) {
+ /* fsp_name is a relative path under the fsp. To change this for other
+ sharepaths we need to manipulate relative paths. */
+ /* TODO - create the absolute path and manipulate the newname
+ relative to the sharepath. */
+ if (!strequal(fsp->conn->connectpath, conn->connectpath)) {
+ continue;
+ }
+ DEBUG(10,("rename_open_files: renaming file fnum %d (file_id %s) from %s -> %s\n",
+ fsp->fnum, file_id_string_tos(&fsp->file_id),
+ fsp->fsp_name, newname ));
+ string_set(&fsp->fsp_name, newname);
+ did_rename = True;
+ }
+
+ if (!did_rename) {
+ DEBUG(10,("rename_open_files: no open files on file_id %s for %s\n",
+ file_id_string_tos(&lck->id), newname ));
+ }
+
+ /* Send messages to all smbd's (not ourself) that the name has changed. */
+ rename_share_filename(smbd_messaging_context(), lck, conn->connectpath,
+ newname);
+}
+
+/****************************************************************************
+ We need to check if the source path is a parent directory of the destination
+ (ie. a rename of /foo/bar/baz -> /foo/bar/baz/bibble/bobble. If so we must
+ refuse the rename with a sharing violation. Under UNIX the above call can
+ *succeed* if /foo/bar/baz is a symlink to another area in the share. We
+ probably need to check that the client is a Windows one before disallowing
+ this as a UNIX client (one with UNIX extensions) can know the source is a
+ symlink and make this decision intelligently. Found by an excellent bug
+ report from <AndyLiebman@aol.com>.
+****************************************************************************/
+
+static bool rename_path_prefix_equal(const char *src, const char *dest)
+{
+ const char *psrc = src;
+ const char *pdst = dest;
+ size_t slen;
+
+ if (psrc[0] == '.' && psrc[1] == '/') {
+ psrc += 2;
+ }
+ if (pdst[0] == '.' && pdst[1] == '/') {
+ pdst += 2;
+ }
+ if ((slen = strlen(psrc)) > strlen(pdst)) {
+ return False;
+ }
+ return ((memcmp(psrc, pdst, slen) == 0) && pdst[slen] == '/');
+}
+
+/*
+ * Do the notify calls from a rename
+ */
+
+static void notify_rename(connection_struct *conn, bool is_dir,
+ const char *oldpath, const char *newpath)
+{
+ char *olddir, *newdir;
+ const char *oldname, *newname;
+ uint32 mask;
+
+ mask = is_dir ? FILE_NOTIFY_CHANGE_DIR_NAME
+ : FILE_NOTIFY_CHANGE_FILE_NAME;
+
+ if (!parent_dirname_talloc(NULL, oldpath, &olddir, &oldname)
+ || !parent_dirname_talloc(NULL, newpath, &newdir, &newname)) {
+ TALLOC_FREE(olddir);
+ return;
+ }
+
+ if (strcmp(olddir, newdir) == 0) {
+ notify_fname(conn, NOTIFY_ACTION_OLD_NAME, mask, oldpath);
+ notify_fname(conn, NOTIFY_ACTION_NEW_NAME, mask, newpath);
+ }
+ else {
+ notify_fname(conn, NOTIFY_ACTION_REMOVED, mask, oldpath);
+ notify_fname(conn, NOTIFY_ACTION_ADDED, mask, newpath);
+ }
+ TALLOC_FREE(olddir);
+ TALLOC_FREE(newdir);
+
+ /* this is a strange one. w2k3 gives an additional event for
+ CHANGE_ATTRIBUTES and CHANGE_CREATION on the new file when renaming
+ files, but not directories */
+ if (!is_dir) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES
+ |FILE_NOTIFY_CHANGE_CREATION,
+ newpath);
+ }
+}
+
+/****************************************************************************
+ Rename an open file - given an fsp.
+****************************************************************************/
+
+NTSTATUS rename_internals_fsp(connection_struct *conn,
+ files_struct *fsp,
+ char *newname,
+ const char *newname_last_component,
+ uint32 attrs,
+ bool replace_if_exists)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ SMB_STRUCT_STAT sbuf, sbuf1;
+ NTSTATUS status = NT_STATUS_OK;
+ struct share_mode_lock *lck = NULL;
+ bool dst_exists;
+
+ ZERO_STRUCT(sbuf);
+
+ status = check_name(conn, newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ensure newname contains a '/' */
+ if(strrchr_m(newname,'/') == 0) {
+ newname = talloc_asprintf(ctx,
+ "./%s",
+ newname);
+ if (!newname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /*
+ * Check for special case with case preserving and not
+ * case sensitive. If the old last component differs from the original
+ * last component only by case, then we should allow
+ * the rename (user is trying to change the case of the
+ * filename).
+ */
+
+ if((conn->case_sensitive == False) && (conn->case_preserve == True) &&
+ strequal(newname, fsp->fsp_name)) {
+ char *p;
+ char *newname_modified_last_component = NULL;
+
+ /*
+ * Get the last component of the modified name.
+ * Note that we guarantee that newname contains a '/'
+ * character above.
+ */
+ p = strrchr_m(newname,'/');
+ newname_modified_last_component = talloc_strdup(ctx,
+ p+1);
+ if (!newname_modified_last_component) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if(strcsequal(newname_modified_last_component,
+ newname_last_component) == False) {
+ /*
+ * Replace the modified last component with
+ * the original.
+ */
+ *p = '\0'; /* Truncate at the '/' */
+ newname = talloc_asprintf(ctx,
+ "%s/%s",
+ newname,
+ newname_last_component);
+ }
+ }
+
+ /*
+ * If the src and dest names are identical - including case,
+ * don't do the rename, just return success.
+ */
+
+ if (strcsequal(fsp->fsp_name, newname)) {
+ DEBUG(3,("rename_internals_fsp: identical names in rename %s - returning success\n",
+ newname));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Have vfs_object_exist also fill sbuf1
+ */
+ dst_exists = vfs_object_exist(conn, newname, &sbuf1);
+
+ if(!replace_if_exists && dst_exists) {
+ DEBUG(3,("rename_internals_fsp: dest exists doing rename %s -> %s\n",
+ fsp->fsp_name,newname));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ if (dst_exists) {
+ struct file_id fileid = vfs_file_id_from_sbuf(conn, &sbuf1);
+ files_struct *dst_fsp = file_find_di_first(fileid);
+ if (dst_fsp) {
+ DEBUG(3, ("rename_internals_fsp: Target file open\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ if (fsp->fh->fd != -1) {
+ if (SMB_VFS_FSTAT(fsp, &sbuf) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+ if (SMB_VFS_STAT(conn,fsp->fsp_name,&sbuf) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ status = can_rename(conn, fsp, attrs, &sbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), fsp->fsp_name,newname));
+ if (NT_STATUS_EQUAL(status,NT_STATUS_SHARING_VIOLATION))
+ status = NT_STATUS_ACCESS_DENIED;
+ return status;
+ }
+
+ if (rename_path_prefix_equal(fsp->fsp_name, newname)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+
+ /*
+ * We have the file open ourselves, so not being able to get the
+ * corresponding share mode lock is a fatal error.
+ */
+
+ SMB_ASSERT(lck != NULL);
+
+ if(SMB_VFS_RENAME(conn,fsp->fsp_name, newname) == 0) {
+ uint32 create_options = fsp->fh->private_options;
+
+ DEBUG(3,("rename_internals_fsp: succeeded doing rename on %s -> %s\n",
+ fsp->fsp_name,newname));
+
+ rename_open_files(conn, lck, newname);
+
+ notify_rename(conn, fsp->is_directory, fsp->fsp_name, newname);
+
+ /*
+ * A rename acts as a new file create w.r.t. allowing an initial delete
+ * on close, probably because in Windows there is a new handle to the
+ * new file. If initial delete on close was requested but not
+ * originally set, we need to set it here. This is probably not 100% correct,
+ * but will work for the CIFSFS client which in non-posix mode
+ * depends on these semantics. JRA.
+ */
+
+ set_allow_initial_delete_on_close(lck, fsp, True);
+
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, True, 0);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *inital* delete on close flag,
+ * not the regular one. The magic gets handled in close. */
+ fsp->initial_delete_on_close = True;
+ }
+ }
+ TALLOC_FREE(lck);
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(lck);
+
+ if (errno == ENOTDIR || errno == EISDIR) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(3,("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), fsp->fsp_name,newname));
+
+ return status;
+}
+
+/****************************************************************************
+ The guts of the rename command, split out so it may be called by the NT SMB
+ code.
+****************************************************************************/
+
+NTSTATUS rename_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ const char *name_in,
+ const char *newname_in,
+ uint32 attrs,
+ bool replace_if_exists,
+ bool src_has_wild,
+ bool dest_has_wild,
+ uint32_t access_mask)
+{
+ char *directory = NULL;
+ char *mask = NULL;
+ char *last_component_src = NULL;
+ char *last_component_dest = NULL;
+ char *name = NULL;
+ char *newname = NULL;
+ char *p;
+ int count=0;
+ NTSTATUS status = NT_STATUS_OK;
+ SMB_STRUCT_STAT sbuf1, sbuf2;
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname;
+ long offset = 0;
+
+ ZERO_STRUCT(sbuf1);
+ ZERO_STRUCT(sbuf2);
+
+ status = unix_convert(ctx, conn, name_in, src_has_wild, &name,
+ &last_component_src, &sbuf1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = unix_convert(ctx, conn, newname_in, dest_has_wild, &newname,
+ &last_component_dest, &sbuf2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Split the old name into directory and last component
+ * strings. Note that unix_convert may have stripped off a
+ * leading ./ from both name and newname if the rename is
+ * at the root of the share. We need to make sure either both
+ * name and newname contain a / character or neither of them do
+ * as this is checked in resolve_wildcards().
+ */
+
+ p = strrchr_m(name,'/');
+ if (!p) {
+ directory = talloc_strdup(ctx, ".");
+ if (!directory) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mask = name;
+ } else {
+ *p = 0;
+ directory = talloc_strdup(ctx, name);
+ if (!directory) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mask = p+1;
+ *p = '/'; /* Replace needed for exceptional test below. */
+ }
+
+ /*
+ * We should only check the mangled cache
+ * here if unix_convert failed. This means
+ * that the path in 'mask' doesn't exist
+ * on the file system and so we need to look
+ * for a possible mangle. This patch from
+ * Tine Smukavec <valentin.smukavec@hermes.si>.
+ */
+
+ if (!VALID_STAT(sbuf1) && mangle_is_mangled(mask, conn->params)) {
+ char *new_mask = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ mask,
+ &new_mask,
+ conn->params );
+ if (new_mask) {
+ mask = new_mask;
+ }
+ }
+
+ if (!src_has_wild) {
+ files_struct *fsp;
+
+ /*
+ * No wildcards - just process the one file.
+ */
+ bool is_short_name = mangle_is_8_3(name, True, conn->params);
+
+ /* Add a terminating '/' to the directory name. */
+ directory = talloc_asprintf_append(directory,
+ "/%s",
+ mask);
+ if (!directory) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Ensure newname contains a '/' also */
+ if(strrchr_m(newname,'/') == 0) {
+ newname = talloc_asprintf(ctx,
+ "./%s",
+ newname);
+ if (!newname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ DEBUG(3, ("rename_internals: case_sensitive = %d, "
+ "case_preserve = %d, short case preserve = %d, "
+ "directory = %s, newname = %s, "
+ "last_component_dest = %s, is_8_3 = %d\n",
+ conn->case_sensitive, conn->case_preserve,
+ conn->short_case_preserve, directory,
+ newname, last_component_dest, is_short_name));
+
+ /* The dest name still may have wildcards. */
+ if (dest_has_wild) {
+ char *mod_newname = NULL;
+ if (!resolve_wildcards(ctx,
+ directory,newname,&mod_newname)) {
+ DEBUG(6, ("rename_internals: resolve_wildcards "
+ "%s %s failed\n",
+ directory,
+ newname));
+ return NT_STATUS_NO_MEMORY;
+ }
+ newname = mod_newname;
+ }
+
+ ZERO_STRUCT(sbuf1);
+ SMB_VFS_STAT(conn, directory, &sbuf1);
+
+ status = S_ISDIR(sbuf1.st_mode) ?
+ open_directory(conn, req, directory, &sbuf1,
+ access_mask,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0, 0, NULL,
+ &fsp)
+ : open_file_ntcreate(conn, req, directory, &sbuf1,
+ access_mask,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0, 0, 0, NULL,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not open rename source %s: %s\n",
+ directory, nt_errstr(status)));
+ return status;
+ }
+
+ status = rename_internals_fsp(conn, fsp, newname,
+ last_component_dest,
+ attrs, replace_if_exists);
+
+ close_file(fsp, NORMAL_CLOSE);
+
+ DEBUG(3, ("rename_internals: Error %s rename %s -> %s\n",
+ nt_errstr(status), directory,newname));
+
+ return status;
+ }
+
+ /*
+ * Wildcards - process each file that matches.
+ */
+ if (strequal(mask,"????????.???")) {
+ mask[0] = '*';
+ mask[1] = '\0';
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dir_hnd = OpenDir(talloc_tos(), conn, directory, mask, attrs);
+ if (dir_hnd == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = NT_STATUS_NO_SUCH_FILE;
+ /*
+ * Was status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ * - gentest fix. JRA
+ */
+
+ while ((dname = ReadDirName(dir_hnd, &offset))) {
+ files_struct *fsp = NULL;
+ char *fname = NULL;
+ char *destname = NULL;
+ bool sysdir_entry = False;
+
+ /* Quick check for "." and ".." */
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ if (attrs & aDIR) {
+ sysdir_entry = True;
+ } else {
+ continue;
+ }
+ }
+
+ if (!is_visible_file(conn, directory, dname, &sbuf1, False)) {
+ continue;
+ }
+
+ if(!mask_match(dname, mask, conn->case_sensitive)) {
+ continue;
+ }
+
+ if (sysdir_entry) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ break;
+ }
+
+ fname = talloc_asprintf(ctx,
+ "%s/%s",
+ directory,
+ dname);
+ if (!fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!resolve_wildcards(ctx,
+ fname,newname,&destname)) {
+ DEBUG(6, ("resolve_wildcards %s %s failed\n",
+ fname, destname));
+ TALLOC_FREE(fname);
+ continue;
+ }
+ if (!destname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(sbuf1);
+ SMB_VFS_STAT(conn, fname, &sbuf1);
+
+ status = S_ISDIR(sbuf1.st_mode) ?
+ open_directory(conn, req, fname, &sbuf1,
+ access_mask,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0, 0, NULL,
+ &fsp)
+ : open_file_ntcreate(conn, req, fname, &sbuf1,
+ access_mask,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0, 0, 0, NULL,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("rename_internals: open_file_ntcreate "
+ "returned %s rename %s -> %s\n",
+ nt_errstr(status), directory, newname));
+ break;
+ }
+
+ status = rename_internals_fsp(conn, fsp, destname, dname,
+ attrs, replace_if_exists);
+
+ close_file(fsp, NORMAL_CLOSE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("rename_internals_fsp returned %s for "
+ "rename %s -> %s\n", nt_errstr(status),
+ directory, newname));
+ break;
+ }
+
+ count++;
+
+ DEBUG(3,("rename_internals: doing rename on %s -> "
+ "%s\n",fname,destname));
+
+ TALLOC_FREE(fname);
+ TALLOC_FREE(destname);
+ }
+ TALLOC_FREE(dir_hnd);
+
+ if (count == 0 && NT_STATUS_IS_OK(status) && errno != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Reply to a mv.
+****************************************************************************/
+
+void reply_mv(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ char *newname = NULL;
+ char *p;
+ uint32 attrs;
+ NTSTATUS status;
+ bool src_has_wcard = False;
+ bool dest_has_wcard = False;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBmv);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBmv);
+ return;
+ }
+
+ attrs = SVAL(req->inbuf,smb_vwv0);
+
+ p = smb_buf(req->inbuf) + 1;
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &name, p,
+ 0, STR_TERMINATE, &status,
+ &src_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBmv);
+ return;
+ }
+ p++;
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &newname, p,
+ 0, STR_TERMINATE, &status,
+ &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBmv);
+ return;
+ }
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ name,
+ &name,
+ &src_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBmv);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBmv);
+ return;
+ }
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ newname,
+ &newname,
+ &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBmv);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBmv);
+ return;
+ }
+
+ DEBUG(3,("reply_mv : %s -> %s\n",name,newname));
+
+ status = rename_internals(ctx, conn, req, name, newname, attrs, False,
+ src_has_wcard, dest_has_wcard, DELETE_ACCESS);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ END_PROFILE(SMBmv);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBmv);
+ return;
+ }
+
+ reply_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBmv);
+ return;
+}
+
+/*******************************************************************
+ Copy a file as part of a reply_copy.
+******************************************************************/
+
+/*
+ * TODO: check error codes on all callers
+ */
+
+NTSTATUS copy_file(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *src,
+ const char *dest1,
+ int ofun,
+ int count,
+ bool target_is_directory)
+{
+ SMB_STRUCT_STAT src_sbuf, sbuf2;
+ SMB_OFF_T ret=-1;
+ files_struct *fsp1,*fsp2;
+ char *dest = NULL;
+ uint32 dosattrs;
+ uint32 new_create_disposition;
+ NTSTATUS status;
+
+ dest = talloc_strdup(ctx, dest1);
+ if (!dest) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (target_is_directory) {
+ const char *p = strrchr_m(src,'/');
+ if (p) {
+ p++;
+ } else {
+ p = src;
+ }
+ dest = talloc_asprintf_append(dest,
+ "/%s",
+ p);
+ if (!dest) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!vfs_file_exist(conn,src,&src_sbuf)) {
+ TALLOC_FREE(dest);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!target_is_directory && count) {
+ new_create_disposition = FILE_OPEN;
+ } else {
+ if (!map_open_params_to_ntcreate(dest1,0,ofun,
+ NULL, NULL, &new_create_disposition, NULL)) {
+ TALLOC_FREE(dest);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ status = open_file_ntcreate(conn, NULL, src, &src_sbuf,
+ FILE_GENERIC_READ,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN,
+ 0,
+ FILE_ATTRIBUTE_NORMAL,
+ INTERNAL_OPEN_ONLY,
+ NULL, &fsp1);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(dest);
+ return status;
+ }
+
+ dosattrs = dos_mode(conn, src, &src_sbuf);
+ if (SMB_VFS_STAT(conn,dest,&sbuf2) == -1) {
+ ZERO_STRUCTP(&sbuf2);
+ }
+
+ status = open_file_ntcreate(conn, NULL, dest, &sbuf2,
+ FILE_GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ new_create_disposition,
+ 0,
+ dosattrs,
+ INTERNAL_OPEN_ONLY,
+ NULL, &fsp2);
+
+ TALLOC_FREE(dest);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file(fsp1,ERROR_CLOSE);
+ return status;
+ }
+
+ if ((ofun&3) == 1) {
+ if(SMB_VFS_LSEEK(fsp2,0,SEEK_END) == -1) {
+ DEBUG(0,("copy_file: error - vfs lseek returned error %s\n", strerror(errno) ));
+ /*
+ * Stop the copy from occurring.
+ */
+ ret = -1;
+ src_sbuf.st_size = 0;
+ }
+ }
+
+ if (src_sbuf.st_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, src_sbuf.st_size);
+ }
+
+ close_file(fsp1,NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, get_mtimespec(&src_sbuf));
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ status = close_file(fsp2,NORMAL_CLOSE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (ret != (SMB_OFF_T)src_sbuf.st_size) {
+ return NT_STATUS_DISK_FULL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a file copy.
+****************************************************************************/
+
+void reply_copy(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ char *newname = NULL;
+ char *directory = NULL;
+ char *mask = NULL;
+ char *p;
+ int count=0;
+ int error = ERRnoaccess;
+ int err = 0;
+ int tid2;
+ int ofun;
+ int flags;
+ bool target_is_directory=False;
+ bool source_has_wild = False;
+ bool dest_has_wild = False;
+ SMB_STRUCT_STAT sbuf1, sbuf2;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcopy);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ tid2 = SVAL(req->inbuf,smb_vwv0);
+ ofun = SVAL(req->inbuf,smb_vwv1);
+ flags = SVAL(req->inbuf,smb_vwv2);
+
+ p = smb_buf(req->inbuf);
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &name, p,
+ 0, STR_TERMINATE, &status,
+ &source_has_wild);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ p += srvstr_get_path_wcard(ctx, (char *)req->inbuf, req->flags2, &newname, p,
+ 0, STR_TERMINATE, &status,
+ &dest_has_wild);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ DEBUG(3,("reply_copy : %s -> %s\n",name,newname));
+
+ if (tid2 != conn->cnum) {
+ /* can't currently handle inter share copies XXXX */
+ DEBUG(3,("Rejecting inter-share copy\n"));
+ reply_doserror(req, ERRSRV, ERRinvdevice);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ name,
+ &name,
+ &source_has_wild);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ newname,
+ &newname,
+ &dest_has_wild);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, name, source_has_wild,
+ &name, NULL, &sbuf1);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, newname, dest_has_wild,
+ &newname, NULL, &sbuf2);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ target_is_directory = VALID_STAT_OF_DIR(sbuf2);
+
+ if ((flags&1) && target_is_directory) {
+ reply_doserror(req, ERRDOS, ERRbadfile);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ if ((flags&2) && !target_is_directory) {
+ reply_doserror(req, ERRDOS, ERRbadpath);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ if ((flags&(1<<5)) && VALID_STAT_OF_DIR(sbuf1)) {
+ /* wants a tree copy! XXXX */
+ DEBUG(3,("Rejecting tree copy\n"));
+ reply_doserror(req, ERRSRV, ERRerror);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ p = strrchr_m(name,'/');
+ if (!p) {
+ directory = talloc_strdup(ctx, "./");
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ mask = name;
+ } else {
+ *p = 0;
+ directory = talloc_strdup(ctx, name);
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ mask = p+1;
+ }
+
+ /*
+ * We should only check the mangled cache
+ * here if unix_convert failed. This means
+ * that the path in 'mask' doesn't exist
+ * on the file system and so we need to look
+ * for a possible mangle. This patch from
+ * Tine Smukavec <valentin.smukavec@hermes.si>.
+ */
+
+ if (!VALID_STAT(sbuf1) && mangle_is_mangled(mask, conn->params)) {
+ char *new_mask = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ mask,
+ &new_mask,
+ conn->params );
+ if (new_mask) {
+ mask = new_mask;
+ }
+ }
+
+ if (!source_has_wild) {
+ directory = talloc_asprintf_append(directory,
+ "/%s",
+ mask);
+ if (dest_has_wild) {
+ char *mod_newname = NULL;
+ if (!resolve_wildcards(ctx,
+ directory,newname,&mod_newname)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+ newname = mod_newname;
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = check_name(conn, newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = copy_file(ctx,conn,directory,newname,ofun,
+ count,target_is_directory);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ } else {
+ count++;
+ }
+ } else {
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname = NULL;
+ long offset = 0;
+
+ if (strequal(mask,"????????.???")) {
+ mask[0] = '*';
+ mask[1] = '\0';
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ dir_hnd = OpenDir(talloc_tos(), conn, directory, mask, 0);
+ if (dir_hnd == NULL) {
+ status = map_nt_error_from_unix(errno);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ error = ERRbadfile;
+
+ while ((dname = ReadDirName(dir_hnd, &offset))) {
+ char *destname = NULL;
+ char *fname = NULL;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ if (!is_visible_file(conn, directory, dname, &sbuf1, False)) {
+ continue;
+ }
+
+ if(!mask_match(dname, mask, conn->case_sensitive)) {
+ continue;
+ }
+
+ error = ERRnoaccess;
+ fname = talloc_asprintf(ctx,
+ "%s/%s",
+ directory,
+ dname);
+ if (!fname) {
+ TALLOC_FREE(dir_hnd);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ if (!resolve_wildcards(ctx,
+ fname,newname,&destname)) {
+ continue;
+ }
+ if (!destname) {
+ TALLOC_FREE(dir_hnd);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(dir_hnd);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ status = check_name(conn, destname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(dir_hnd);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ DEBUG(3,("reply_copy : doing copy on %s -> %s\n",fname, destname));
+
+ status = copy_file(ctx,conn,fname,destname,ofun,
+ count,target_is_directory);
+ if (NT_STATUS_IS_OK(status)) {
+ count++;
+ }
+ TALLOC_FREE(fname);
+ TALLOC_FREE(destname);
+ }
+ TALLOC_FREE(dir_hnd);
+ }
+
+ if (count == 0) {
+ if(err) {
+ /* Error on close... */
+ errno = err;
+ reply_unixerror(req, ERRHRD, ERRgeneral);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ reply_doserror(req, ERRDOS, error);
+ END_PROFILE(SMBcopy);
+ return;
+ }
+
+ reply_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,count);
+
+ END_PROFILE(SMBcopy);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Get a lock pid, dealing with large count requests.
+****************************************************************************/
+
+uint32 get_lock_pid( char *data, int data_offset, bool large_file_format)
+{
+ if(!large_file_format)
+ return (uint32)SVAL(data,SMB_LPID_OFFSET(data_offset));
+ else
+ return (uint32)SVAL(data,SMB_LARGE_LPID_OFFSET(data_offset));
+}
+
+/****************************************************************************
+ Get a lock count, dealing with large count requests.
+****************************************************************************/
+
+SMB_BIG_UINT get_lock_count( char *data, int data_offset, bool large_file_format)
+{
+ SMB_BIG_UINT count = 0;
+
+ if(!large_file_format) {
+ count = (SMB_BIG_UINT)IVAL(data,SMB_LKLEN_OFFSET(data_offset));
+ } else {
+
+#if defined(HAVE_LONGLONG)
+ count = (((SMB_BIG_UINT) IVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset))) << 32) |
+ ((SMB_BIG_UINT) IVAL(data,SMB_LARGE_LKLEN_OFFSET_LOW(data_offset)));
+#else /* HAVE_LONGLONG */
+
+ /*
+ * NT4.x seems to be broken in that it sends large file (64 bit)
+ * lockingX calls even if the CAP_LARGE_FILES was *not*
+ * negotiated. For boxes without large unsigned ints truncate the
+ * lock count by dropping the top 32 bits.
+ */
+
+ if(IVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset)) != 0) {
+ DEBUG(3,("get_lock_count: truncating lock count (high)0x%x (low)0x%x to just low count.\n",
+ (unsigned int)IVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset)),
+ (unsigned int)IVAL(data,SMB_LARGE_LKLEN_OFFSET_LOW(data_offset)) ));
+ SIVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset),0);
+ }
+
+ count = (SMB_BIG_UINT)IVAL(data,SMB_LARGE_LKLEN_OFFSET_LOW(data_offset));
+#endif /* HAVE_LONGLONG */
+ }
+
+ return count;
+}
+
+#if !defined(HAVE_LONGLONG)
+/****************************************************************************
+ Pathetically try and map a 64 bit lock offset into 31 bits. I hate Windows :-).
+****************************************************************************/
+
+static uint32 map_lock_offset(uint32 high, uint32 low)
+{
+ unsigned int i;
+ uint32 mask = 0;
+ uint32 highcopy = high;
+
+ /*
+ * Try and find out how many significant bits there are in high.
+ */
+
+ for(i = 0; highcopy; i++)
+ highcopy >>= 1;
+
+ /*
+ * We use 31 bits not 32 here as POSIX
+ * lock offsets may not be negative.
+ */
+
+ mask = (~0) << (31 - i);
+
+ if(low & mask)
+ return 0; /* Fail. */
+
+ high <<= (31 - i);
+
+ return (high|low);
+}
+#endif /* !defined(HAVE_LONGLONG) */
+
+/****************************************************************************
+ Get a lock offset, dealing with large offset requests.
+****************************************************************************/
+
+SMB_BIG_UINT get_lock_offset( char *data, int data_offset, bool large_file_format, bool *err)
+{
+ SMB_BIG_UINT offset = 0;
+
+ *err = False;
+
+ if(!large_file_format) {
+ offset = (SMB_BIG_UINT)IVAL(data,SMB_LKOFF_OFFSET(data_offset));
+ } else {
+
+#if defined(HAVE_LONGLONG)
+ offset = (((SMB_BIG_UINT) IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset))) << 32) |
+ ((SMB_BIG_UINT) IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset)));
+#else /* HAVE_LONGLONG */
+
+ /*
+ * NT4.x seems to be broken in that it sends large file (64 bit)
+ * lockingX calls even if the CAP_LARGE_FILES was *not*
+ * negotiated. For boxes without large unsigned ints mangle the
+ * lock offset by mapping the top 32 bits onto the lower 32.
+ */
+
+ if(IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset)) != 0) {
+ uint32 low = IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset));
+ uint32 high = IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset));
+ uint32 new_low = 0;
+
+ if((new_low = map_lock_offset(high, low)) == 0) {
+ *err = True;
+ return (SMB_BIG_UINT)-1;
+ }
+
+ DEBUG(3,("get_lock_offset: truncating lock offset (high)0x%x (low)0x%x to offset 0x%x.\n",
+ (unsigned int)high, (unsigned int)low, (unsigned int)new_low ));
+ SIVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset),0);
+ SIVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset),new_low);
+ }
+
+ offset = (SMB_BIG_UINT)IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset));
+#endif /* HAVE_LONGLONG */
+ }
+
+ return offset;
+}
+
+/****************************************************************************
+ Reply to a lockingX request.
+****************************************************************************/
+
+void reply_lockingX(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ unsigned char locktype;
+ unsigned char oplocklevel;
+ uint16 num_ulocks;
+ uint16 num_locks;
+ SMB_BIG_UINT count = 0, offset = 0;
+ uint32 lock_pid;
+ int32 lock_timeout;
+ int i;
+ char *data;
+ bool large_file_format;
+ bool err;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ START_PROFILE(SMBlockingX);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv2));
+ locktype = CVAL(req->inbuf,smb_vwv3);
+ oplocklevel = CVAL(req->inbuf,smb_vwv3+1);
+ num_ulocks = SVAL(req->inbuf,smb_vwv6);
+ num_locks = SVAL(req->inbuf,smb_vwv7);
+ lock_timeout = IVAL(req->inbuf,smb_vwv4);
+ large_file_format = (locktype & LOCKING_ANDX_LARGE_FILES)?True:False;
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ data = smb_buf(req->inbuf);
+
+ if (locktype & LOCKING_ANDX_CHANGE_LOCKTYPE) {
+ /* we don't support these - and CANCEL_LOCK makes w2k
+ and XP reboot so I don't really want to be
+ compatible! (tridge) */
+ reply_nterror(req, NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks));
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /* Check if this is an oplock break on a file
+ we have granted an oplock on.
+ */
+ if ((locktype & LOCKING_ANDX_OPLOCK_RELEASE)) {
+ /* Client can insist on breaking to none. */
+ bool break_to_none = (oplocklevel == 0);
+ bool result;
+
+ DEBUG(5,("reply_lockingX: oplock break reply (%u) from client "
+ "for fnum = %d\n", (unsigned int)oplocklevel,
+ fsp->fnum ));
+
+ /*
+ * Make sure we have granted an exclusive or batch oplock on
+ * this file.
+ */
+
+ if (fsp->oplock_type == 0) {
+
+ /* The Samba4 nbench simulator doesn't understand
+ the difference between break to level2 and break
+ to none from level2 - it sends oplock break
+ replies in both cases. Don't keep logging an error
+ message here - just ignore it. JRA. */
+
+ DEBUG(5,("reply_lockingX: Error : oplock break from "
+ "client for fnum = %d (oplock=%d) and no "
+ "oplock granted on this file (%s).\n",
+ fsp->fnum, fsp->oplock_type, fsp->fsp_name));
+
+ /* if this is a pure oplock break request then don't
+ * send a reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ END_PROFILE(SMBlockingX);
+ return;
+ } else {
+ END_PROFILE(SMBlockingX);
+ reply_doserror(req, ERRDOS, ERRlock);
+ return;
+ }
+ }
+
+ if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
+ (break_to_none)) {
+ result = remove_oplock(fsp);
+ } else {
+ result = downgrade_oplock(fsp);
+ }
+
+ if (!result) {
+ DEBUG(0, ("reply_lockingX: error in removing "
+ "oplock on file %s\n", fsp->fsp_name));
+ /* Hmmm. Is this panic justified? */
+ smb_panic("internal tdb error");
+ }
+
+ reply_to_oplock_break_requests(fsp);
+
+ /* if this is a pure oplock break request then don't send a
+ * reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ /* Sanity check - ensure a pure oplock break is not a
+ chained request. */
+ if(CVAL(req->inbuf,smb_vwv0) != 0xff)
+ DEBUG(0,("reply_lockingX: Error : pure oplock "
+ "break is a chained %d request !\n",
+ (unsigned int)CVAL(req->inbuf,
+ smb_vwv0) ));
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ }
+
+ /*
+ * We do this check *after* we have checked this is not a oplock break
+ * response message. JRA.
+ */
+
+ release_level_2_oplocks_on_change(fsp);
+
+ if (smb_buflen(req->inbuf) <
+ (num_ulocks + num_locks) * (large_file_format ? 20 : 10)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /* Data now points at the beginning of the list
+ of smb_unlkrng structs */
+ for(i = 0; i < (int)num_ulocks; i++) {
+ lock_pid = get_lock_pid( data, i, large_file_format);
+ count = get_lock_count( data, i, large_file_format);
+ offset = get_lock_offset( data, i, large_file_format, &err);
+
+ /*
+ * There is no error code marked "stupid client bug".... :-).
+ */
+ if(err) {
+ END_PROFILE(SMBlockingX);
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ DEBUG(10,("reply_lockingX: unlock start=%.0f, len=%.0f for "
+ "pid %u, file %s\n", (double)offset, (double)count,
+ (unsigned int)lock_pid, fsp->fsp_name ));
+
+ status = do_unlock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ WINDOWS_LOCK);
+
+ if (NT_STATUS_V(status)) {
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ /* Setup the timeout in seconds. */
+
+ if (!lp_blocking_locks(SNUM(conn))) {
+ lock_timeout = 0;
+ }
+
+ /* Now do any requested locks */
+ data += ((large_file_format ? 20 : 10)*num_ulocks);
+
+ /* Data now points at the beginning of the list
+ of smb_lkrng structs */
+
+ for(i = 0; i < (int)num_locks; i++) {
+ enum brl_type lock_type = ((locktype & LOCKING_ANDX_SHARED_LOCK) ?
+ READ_LOCK:WRITE_LOCK);
+ lock_pid = get_lock_pid( data, i, large_file_format);
+ count = get_lock_count( data, i, large_file_format);
+ offset = get_lock_offset( data, i, large_file_format, &err);
+
+ /*
+ * There is no error code marked "stupid client bug".... :-).
+ */
+ if(err) {
+ END_PROFILE(SMBlockingX);
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ DEBUG(10,("reply_lockingX: lock start=%.0f, len=%.0f for pid "
+ "%u, file %s timeout = %d\n", (double)offset,
+ (double)count, (unsigned int)lock_pid,
+ fsp->fsp_name, (int)lock_timeout ));
+
+ if (locktype & LOCKING_ANDX_CANCEL_LOCK) {
+ if (lp_blocking_locks(SNUM(conn))) {
+
+ /* Schedule a message to ourselves to
+ remove the blocking lock record and
+ return the right error. */
+
+ if (!blocking_lock_cancel(fsp,
+ lock_pid,
+ offset,
+ count,
+ WINDOWS_LOCK,
+ locktype,
+ NT_STATUS_FILE_LOCK_CONFLICT)) {
+ END_PROFILE(SMBlockingX);
+ reply_nterror(
+ req,
+ NT_STATUS_DOS(
+ ERRDOS,
+ ERRcancelviolation));
+ return;
+ }
+ }
+ /* Remove a matching pending lock. */
+ status = do_lock_cancel(fsp,
+ lock_pid,
+ count,
+ offset,
+ WINDOWS_LOCK);
+ } else {
+ bool blocking_lock = lock_timeout ? True : False;
+ bool defer_lock = False;
+ struct byte_range_lock *br_lck;
+ uint32 block_smbpid;
+
+ br_lck = do_lock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ lock_type,
+ WINDOWS_LOCK,
+ blocking_lock,
+ &status,
+ &block_smbpid);
+
+ if (br_lck && blocking_lock && ERROR_WAS_LOCK_DENIED(status)) {
+ /* Windows internal resolution for blocking locks seems
+ to be about 200ms... Don't wait for less than that. JRA. */
+ if (lock_timeout != -1 && lock_timeout < lp_lock_spin_time()) {
+ lock_timeout = lp_lock_spin_time();
+ }
+ defer_lock = True;
+ }
+
+ /* This heuristic seems to match W2K3 very well. If a
+ lock sent with timeout of zero would fail with NT_STATUS_FILE_LOCK_CONFLICT
+ it pretends we asked for a timeout of between 150 - 300 milliseconds as
+ far as I can tell. Replacement for do_lock_spin(). JRA. */
+
+ if (br_lck && lp_blocking_locks(SNUM(conn)) && !blocking_lock &&
+ NT_STATUS_EQUAL((status), NT_STATUS_FILE_LOCK_CONFLICT)) {
+ defer_lock = True;
+ lock_timeout = lp_lock_spin_time();
+ }
+
+ if (br_lck && defer_lock) {
+ /*
+ * A blocking lock was requested. Package up
+ * this smb into a queued request and push it
+ * onto the blocking lock queue.
+ */
+ if(push_blocking_lock_request(br_lck,
+ req,
+ fsp,
+ lock_timeout,
+ i,
+ lock_pid,
+ lock_type,
+ WINDOWS_LOCK,
+ offset,
+ count,
+ block_smbpid)) {
+ TALLOC_FREE(br_lck);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ }
+
+ TALLOC_FREE(br_lck);
+ }
+
+ if (NT_STATUS_V(status)) {
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ /* If any of the above locks failed, then we must unlock
+ all of the previous locks (X/Open spec). */
+
+ if (!(locktype & LOCKING_ANDX_CANCEL_LOCK) &&
+ (i != num_locks) &&
+ (num_locks != 0)) {
+ /*
+ * Ensure we don't do a remove on the lock that just failed,
+ * as under POSIX rules, if we have a lock already there, we
+ * will delete it (and we shouldn't) .....
+ */
+ for(i--; i >= 0; i--) {
+ lock_pid = get_lock_pid( data, i, large_file_format);
+ count = get_lock_count( data, i, large_file_format);
+ offset = get_lock_offset( data, i, large_file_format,
+ &err);
+
+ /*
+ * There is no error code marked "stupid client
+ * bug".... :-).
+ */
+ if(err) {
+ END_PROFILE(SMBlockingX);
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ do_unlock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ WINDOWS_LOCK);
+ }
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, status);
+ return;
+ }
+
+ reply_outbuf(req, 2, 0);
+
+ DEBUG(3, ("lockingX fnum=%d type=%d num_locks=%d num_ulocks=%d\n",
+ fsp->fnum, (unsigned int)locktype, num_locks, num_ulocks));
+
+ END_PROFILE(SMBlockingX);
+ chain_reply(req);
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a SMBreadbmpx (read block multiplex) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBmpx);
+ reply_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBreadbs (read block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbs(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBs);
+ reply_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBsetattrE.
+****************************************************************************/
+
+void reply_setattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct timespec ts[2];
+ files_struct *fsp;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsetattrE);
+
+ if (req->wct < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsetattrE);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ END_PROFILE(SMBsetattrE);
+ return;
+ }
+
+
+ /*
+ * Convert the DOS times into unix times. Ignore create
+ * time as UNIX can't set this.
+ */
+
+ ts[0] = convert_time_t_to_timespec(
+ srv_make_unix_date2(req->inbuf+smb_vwv3)); /* atime. */
+ ts[1] = convert_time_t_to_timespec(
+ srv_make_unix_date2(req->inbuf+smb_vwv5)); /* mtime. */
+
+ reply_outbuf(req, 0, 0);
+
+ /*
+ * Patch from Ray Frush <frush@engr.colostate.edu>
+ * Sometimes times are sent as zero - ignore them.
+ */
+
+ /* Ensure we have a valid stat struct for the source. */
+ if (fsp->fh->fd != -1) {
+ if (SMB_VFS_FSTAT(fsp, &sbuf) == -1) {
+ status = map_nt_error_from_unix(errno);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetattrE);
+ return;
+ }
+ } else {
+ if (SMB_VFS_STAT(conn, fsp->fsp_name, &sbuf) == -1) {
+ status = map_nt_error_from_unix(errno);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsetattrE);
+ return;
+ }
+ }
+
+ status = smb_set_file_time(conn, fsp, fsp->fsp_name,
+ &sbuf, ts, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBsetattrE);
+ return;
+ }
+
+ DEBUG( 3, ( "reply_setattrE fnum=%d actime=%u modtime=%u\n",
+ fsp->fnum,
+ (unsigned int)ts[0].tv_sec,
+ (unsigned int)ts[1].tv_sec));
+
+ END_PROFILE(SMBsetattrE);
+ return;
+}
+
+
+/* Back from the dead for OS/2..... JRA. */
+
+/****************************************************************************
+ Reply to a SMBwritebmpx (write block multiplex primary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBmpx);
+ reply_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBwritebs (write block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebs(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBs);
+ reply_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBgetattrE.
+****************************************************************************/
+
+void reply_getattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ SMB_STRUCT_STAT sbuf;
+ int mode;
+ files_struct *fsp;
+ struct timespec create_ts;
+
+ START_PROFILE(SMBgetattrE);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(req->inbuf,smb_vwv0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ /* Do an fstat on this file */
+ if(fsp_stat(fsp, &sbuf)) {
+ reply_unixerror(req, ERRDOS, ERRnoaccess);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ mode = dos_mode(conn,fsp->fsp_name,&sbuf);
+
+ /*
+ * Convert the times into dos times. Set create
+ * date to be last modify date as UNIX doesn't save
+ * this.
+ */
+
+ reply_outbuf(req, 11, 0);
+
+ create_ts = get_create_timespec(&sbuf,
+ lp_fake_dir_create_times(SNUM(conn)));
+ srv_put_dos_date2((char *)req->outbuf, smb_vwv0, create_ts.tv_sec);
+ srv_put_dos_date2((char *)req->outbuf, smb_vwv2, sbuf.st_atime);
+ /* Should we check pending modtime here ? JRA */
+ srv_put_dos_date2((char *)req->outbuf, smb_vwv4, sbuf.st_mtime);
+
+ if (mode & aDIR) {
+ SIVAL(req->outbuf, smb_vwv6, 0);
+ SIVAL(req->outbuf, smb_vwv8, 0);
+ } else {
+ uint32 allocation_size = get_allocation_size(conn,fsp, &sbuf);
+ SIVAL(req->outbuf, smb_vwv6, (uint32)sbuf.st_size);
+ SIVAL(req->outbuf, smb_vwv8, allocation_size);
+ }
+ SSVAL(req->outbuf,smb_vwv10, mode);
+
+ DEBUG( 3, ( "reply_getattrE fnum=%d\n", fsp->fnum));
+
+ END_PROFILE(SMBgetattrE);
+ return;
+}
diff --git a/source3/smbd/seal.c b/source3/smbd/seal.c
new file mode 100644
index 0000000000..e9dc46aa3c
--- /dev/null
+++ b/source3/smbd/seal.c
@@ -0,0 +1,742 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Transport encryption (sealing) code - server code.
+ Copyright (C) Jeremy Allison 2007.
+
+ 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"
+
+/******************************************************************************
+ Server side encryption.
+******************************************************************************/
+
+/******************************************************************************
+ Global server state.
+******************************************************************************/
+
+struct smb_srv_trans_enc_ctx {
+ struct smb_trans_enc_state *es;
+ AUTH_NTLMSSP_STATE *auth_ntlmssp_state; /* Must be kept in sync with pointer in ec->ntlmssp_state. */
+};
+
+static struct smb_srv_trans_enc_ctx *partial_srv_trans_enc_ctx;
+static struct smb_srv_trans_enc_ctx *srv_trans_enc_ctx;
+
+/******************************************************************************
+ Return global enc context - this must change if we ever do multiple contexts.
+******************************************************************************/
+
+uint16_t srv_enc_ctx(void)
+{
+ return srv_trans_enc_ctx->es->enc_ctx_num;
+}
+
+/******************************************************************************
+ Is this an incoming encrypted packet ?
+******************************************************************************/
+
+bool is_encrypted_packet(const uint8_t *inbuf)
+{
+ NTSTATUS status;
+ uint16_t enc_num;
+
+ /* Ignore non-session messages or non 0xFF'E' messages. */
+ if(CVAL(inbuf,0) || !(inbuf[4] == 0xFF && inbuf[5] == 'E')) {
+ return false;
+ }
+
+ status = get_enc_ctx_num(inbuf, &enc_num);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /* Encrypted messages are 0xFF'E'<ctx> */
+ if (srv_trans_enc_ctx && enc_num == srv_enc_ctx()) {
+ return true;
+ }
+ return false;
+}
+
+/******************************************************************************
+ Create an auth_ntlmssp_state and ensure pointer copy is correct.
+******************************************************************************/
+
+static NTSTATUS make_auth_ntlmssp(struct smb_srv_trans_enc_ctx *ec)
+{
+ NTSTATUS status = auth_ntlmssp_start(&ec->auth_ntlmssp_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ /*
+ * We must remember to update the pointer copy for the common
+ * functions after any auth_ntlmssp_start/auth_ntlmssp_end.
+ */
+ ec->es->s.ntlmssp_state = ec->auth_ntlmssp_state->ntlmssp_state;
+ return status;
+}
+
+/******************************************************************************
+ Destroy an auth_ntlmssp_state and ensure pointer copy is correct.
+******************************************************************************/
+
+static void destroy_auth_ntlmssp(struct smb_srv_trans_enc_ctx *ec)
+{
+ /*
+ * We must remember to update the pointer copy for the common
+ * functions after any auth_ntlmssp_start/auth_ntlmssp_end.
+ */
+
+ if (ec->auth_ntlmssp_state) {
+ auth_ntlmssp_end(&ec->auth_ntlmssp_state);
+ /* The auth_ntlmssp_end killed this already. */
+ ec->es->s.ntlmssp_state = NULL;
+ }
+}
+
+#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5)
+
+/******************************************************************************
+ Import a name.
+******************************************************************************/
+
+static NTSTATUS get_srv_gss_creds(const char *service,
+ const char *name,
+ gss_cred_usage_t cred_type,
+ gss_cred_id_t *p_srv_cred)
+{
+ OM_uint32 ret;
+ OM_uint32 min;
+ gss_name_t srv_name;
+ gss_buffer_desc input_name;
+ char *host_princ_s = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ gss_OID_desc nt_hostbased_service =
+ {10, CONST_DISCARD(char *,"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")};
+
+ asprintf(&host_princ_s, "%s@%s", service, name);
+ if (host_princ_s == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ input_name.value = host_princ_s;
+ input_name.length = strlen(host_princ_s) + 1;
+
+ ret = gss_import_name(&min,
+ &input_name,
+ &nt_hostbased_service,
+ &srv_name);
+
+ DEBUG(10,("get_srv_gss_creds: imported name %s\n",
+ host_princ_s ));
+
+ if (ret != GSS_S_COMPLETE) {
+ SAFE_FREE(host_princ_s);
+ return map_nt_error_from_gss(ret, min);
+ }
+
+ /*
+ * We're accessing the krb5.keytab file here.
+ * ensure we have permissions to do so.
+ */
+ become_root();
+
+ ret = gss_acquire_cred(&min,
+ srv_name,
+ GSS_C_INDEFINITE,
+ GSS_C_NULL_OID_SET,
+ cred_type,
+ p_srv_cred,
+ NULL,
+ NULL);
+ unbecome_root();
+
+ if (ret != GSS_S_COMPLETE) {
+ ADS_STATUS adss = ADS_ERROR_GSS(ret, min);
+ DEBUG(10,("get_srv_gss_creds: gss_acquire_cred failed with %s\n",
+ ads_errstr(adss)));
+ status = map_nt_error_from_gss(ret, min);
+ }
+
+ SAFE_FREE(host_princ_s);
+ gss_release_name(&min, &srv_name);
+ return status;
+}
+
+/******************************************************************************
+ Create a gss state.
+ Try and get the cifs/server@realm principal first, then fall back to
+ host/server@realm.
+******************************************************************************/
+
+static NTSTATUS make_auth_gss(struct smb_srv_trans_enc_ctx *ec)
+{
+ NTSTATUS status;
+ gss_cred_id_t srv_cred;
+ fstring fqdn;
+
+ name_to_fqdn(fqdn, global_myname());
+ strlower_m(fqdn);
+
+ status = get_srv_gss_creds("cifs", fqdn, GSS_C_ACCEPT, &srv_cred);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = get_srv_gss_creds("host", fqdn, GSS_C_ACCEPT, &srv_cred);
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+ }
+
+ ec->es->s.gss_state = SMB_MALLOC_P(struct smb_tran_enc_state_gss);
+ if (!ec->es->s.gss_state) {
+ OM_uint32 min;
+ gss_release_cred(&min, &srv_cred);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ZERO_STRUCTP(ec->es->s.gss_state);
+ ec->es->s.gss_state->creds = srv_cred;
+
+ /* No context yet. */
+ ec->es->s.gss_state->gss_ctx = GSS_C_NO_CONTEXT;
+
+ return NT_STATUS_OK;
+}
+#endif
+
+/******************************************************************************
+ Shutdown a server encryption context.
+******************************************************************************/
+
+static void srv_free_encryption_context(struct smb_srv_trans_enc_ctx **pp_ec)
+{
+ struct smb_srv_trans_enc_ctx *ec = *pp_ec;
+
+ if (!ec) {
+ return;
+ }
+
+ if (ec->es) {
+ switch (ec->es->smb_enc_type) {
+ case SMB_TRANS_ENC_NTLM:
+ destroy_auth_ntlmssp(ec);
+ break;
+#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5)
+ case SMB_TRANS_ENC_GSS:
+ break;
+#endif
+ }
+ common_free_encryption_state(&ec->es);
+ }
+
+ SAFE_FREE(ec);
+ *pp_ec = NULL;
+}
+
+/******************************************************************************
+ Create a server encryption context.
+******************************************************************************/
+
+static NTSTATUS make_srv_encryption_context(enum smb_trans_enc_type smb_enc_type, struct smb_srv_trans_enc_ctx **pp_ec)
+{
+ struct smb_srv_trans_enc_ctx *ec;
+
+ *pp_ec = NULL;
+
+ ec = SMB_MALLOC_P(struct smb_srv_trans_enc_ctx);
+ if (!ec) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ZERO_STRUCTP(partial_srv_trans_enc_ctx);
+ ec->es = SMB_MALLOC_P(struct smb_trans_enc_state);
+ if (!ec->es) {
+ SAFE_FREE(ec);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ZERO_STRUCTP(ec->es);
+ ec->es->smb_enc_type = smb_enc_type;
+ switch (smb_enc_type) {
+ case SMB_TRANS_ENC_NTLM:
+ {
+ NTSTATUS status = make_auth_ntlmssp(ec);
+ if (!NT_STATUS_IS_OK(status)) {
+ srv_free_encryption_context(&ec);
+ return status;
+ }
+ }
+ break;
+
+#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5)
+ case SMB_TRANS_ENC_GSS:
+ /* Acquire our credentials by calling gss_acquire_cred here. */
+ {
+ NTSTATUS status = make_auth_gss(ec);
+ if (!NT_STATUS_IS_OK(status)) {
+ srv_free_encryption_context(&ec);
+ return status;
+ }
+ }
+ break;
+#endif
+ default:
+ srv_free_encryption_context(&ec);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ *pp_ec = ec;
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Free an encryption-allocated buffer.
+******************************************************************************/
+
+void srv_free_enc_buffer(char *buf)
+{
+ /* We know this is an smb buffer, and we
+ * didn't malloc, only copy, for a keepalive,
+ * so ignore non-session messages. */
+
+ if(CVAL(buf,0)) {
+ return;
+ }
+
+ if (srv_trans_enc_ctx) {
+ common_free_enc_buffer(srv_trans_enc_ctx->es, buf);
+ }
+}
+
+/******************************************************************************
+ Decrypt an incoming buffer.
+******************************************************************************/
+
+NTSTATUS srv_decrypt_buffer(char *buf)
+{
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_decrypt_buffer(srv_trans_enc_ctx->es, buf);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Encrypt an outgoing buffer. Return the encrypted pointer in buf_out.
+******************************************************************************/
+
+NTSTATUS srv_encrypt_buffer(char *buf, char **buf_out)
+{
+ *buf_out = buf;
+
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_encrypt_buffer(srv_trans_enc_ctx->es, buf, buf_out);
+ }
+ /* Not encrypting. */
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Do the gss encryption negotiation. Parameters are in/out.
+ Until success we do everything on the partial enc ctx.
+******************************************************************************/
+
+#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5)
+static NTSTATUS srv_enc_spnego_gss_negotiate(unsigned char **ppdata, size_t *p_data_size, DATA_BLOB secblob)
+{
+ OM_uint32 ret;
+ OM_uint32 min;
+ OM_uint32 flags = 0;
+ gss_buffer_desc in_buf, out_buf;
+ struct smb_tran_enc_state_gss *gss_state;
+ DATA_BLOB auth_reply = data_blob_null;
+ DATA_BLOB response = data_blob_null;
+ NTSTATUS status;
+
+ if (!partial_srv_trans_enc_ctx) {
+ status = make_srv_encryption_context(SMB_TRANS_ENC_GSS, &partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ gss_state = partial_srv_trans_enc_ctx->es->s.gss_state;
+
+ in_buf.value = secblob.data;
+ in_buf.length = secblob.length;
+
+ out_buf.value = NULL;
+ out_buf.length = 0;
+
+ become_root();
+
+ ret = gss_accept_sec_context(&min,
+ &gss_state->gss_ctx,
+ gss_state->creds,
+ &in_buf,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ NULL,
+ NULL, /* Ignore oids. */
+ &out_buf, /* To return. */
+ &flags,
+ NULL, /* Ingore time. */
+ NULL); /* Ignore delegated creds. */
+ unbecome_root();
+
+ status = gss_err_to_ntstatus(ret, min);
+ if (ret != GSS_S_COMPLETE && ret != GSS_S_CONTINUE_NEEDED) {
+ return status;
+ }
+
+ /* Ensure we've got sign+seal available. */
+ if (ret == GSS_S_COMPLETE) {
+ if ((flags & (GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG|GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG)) !=
+ (GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG|GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG)) {
+ DEBUG(0,("srv_enc_spnego_gss_negotiate: quality of service not good enough "
+ "for SMB sealing.\n"));
+ gss_release_buffer(&min, &out_buf);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ auth_reply = data_blob(out_buf.value, out_buf.length);
+ gss_release_buffer(&min, &out_buf);
+
+ /* Wrap in SPNEGO. */
+ response = spnego_gen_auth_response(&auth_reply, status, OID_KERBEROS5);
+ data_blob_free(&auth_reply);
+
+ SAFE_FREE(*ppdata);
+ *ppdata = response.data;
+ *p_data_size = response.length;
+
+ return status;
+}
+#endif
+
+/******************************************************************************
+ Do the NTLM SPNEGO (or raw) encryption negotiation. Parameters are in/out.
+ Until success we do everything on the partial enc ctx.
+******************************************************************************/
+
+static NTSTATUS srv_enc_ntlm_negotiate(unsigned char **ppdata, size_t *p_data_size, DATA_BLOB secblob, bool spnego_wrap)
+{
+ NTSTATUS status;
+ DATA_BLOB chal = data_blob_null;
+ DATA_BLOB response = data_blob_null;
+
+ status = make_srv_encryption_context(SMB_TRANS_ENC_NTLM, &partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = auth_ntlmssp_update(partial_srv_trans_enc_ctx->auth_ntlmssp_state, secblob, &chal);
+
+ /* status here should be NT_STATUS_MORE_PROCESSING_REQUIRED
+ * for success ... */
+
+ if (spnego_wrap) {
+ response = spnego_gen_auth_response(&chal, status, OID_NTLMSSP);
+ data_blob_free(&chal);
+ } else {
+ /* Return the raw blob. */
+ response = chal;
+ }
+
+ SAFE_FREE(*ppdata);
+ *ppdata = response.data;
+ *p_data_size = response.length;
+ return status;
+}
+
+/******************************************************************************
+ Do the SPNEGO encryption negotiation. Parameters are in/out.
+ Based off code in smbd/sesssionsetup.c
+ Until success we do everything on the partial enc ctx.
+******************************************************************************/
+
+static NTSTATUS srv_enc_spnego_negotiate(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob_null;
+ DATA_BLOB secblob = data_blob_null;
+ char *kerb_mech = NULL;
+
+ blob = data_blob_const(*ppdata, *p_data_size);
+
+ status = parse_spnego_mechanisms(blob, &secblob, &kerb_mech);
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ /* We should have no partial context at this point. */
+
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+
+ if (kerb_mech) {
+ SAFE_FREE(kerb_mech);
+
+#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5)
+ status = srv_enc_spnego_gss_negotiate(ppdata, p_data_size, secblob);
+#else
+ /* Currently we don't SPNEGO negotiate
+ * back to NTLMSSP as we do in sessionsetupX. We should... */
+ return NT_STATUS_LOGON_FAILURE;
+#endif
+ } else {
+ status = srv_enc_ntlm_negotiate(ppdata, p_data_size, secblob, true);
+ }
+
+ data_blob_free(&secblob);
+
+ if (!NT_STATUS_EQUAL(status,NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) {
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ return nt_status_squash(status);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return the context we're using for this encryption state. */
+ if (!(*pparam = SMB_MALLOC_ARRAY(unsigned char, 2))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(*pparam,0,partial_srv_trans_enc_ctx->es->enc_ctx_num);
+ *p_param_size = 2;
+ }
+
+ return status;
+}
+
+/******************************************************************************
+ Complete a SPNEGO encryption negotiation. Parameters are in/out.
+ We only get this for a NTLM auth second stage.
+******************************************************************************/
+
+static NTSTATUS srv_enc_spnego_ntlm_auth(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob_null;
+ DATA_BLOB auth = data_blob_null;
+ DATA_BLOB auth_reply = data_blob_null;
+ DATA_BLOB response = data_blob_null;
+ struct smb_srv_trans_enc_ctx *ec = partial_srv_trans_enc_ctx;
+
+ /* We must have a partial context here. */
+
+ if (!ec || !ec->es || ec->auth_ntlmssp_state == NULL || ec->es->smb_enc_type != SMB_TRANS_ENC_NTLM) {
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ blob = data_blob_const(*ppdata, *p_data_size);
+ if (!spnego_parse_auth(blob, &auth)) {
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = auth_ntlmssp_update(ec->auth_ntlmssp_state, auth, &auth_reply);
+ data_blob_free(&auth);
+
+ /* From RFC4178.
+ *
+ * supportedMech
+ *
+ * This field SHALL only be present in the first reply from the
+ * target.
+ * So set mechOID to NULL here.
+ */
+
+ response = spnego_gen_auth_response(&auth_reply, status, NULL);
+ data_blob_free(&auth_reply);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return the context we're using for this encryption state. */
+ if (!(*pparam = SMB_MALLOC_ARRAY(unsigned char, 2))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(*pparam,0,ec->es->enc_ctx_num);
+ *p_param_size = 2;
+ }
+
+ SAFE_FREE(*ppdata);
+ *ppdata = response.data;
+ *p_data_size = response.length;
+ return status;
+}
+
+/******************************************************************************
+ Raw NTLM encryption negotiation. Parameters are in/out.
+ This function does both steps.
+******************************************************************************/
+
+static NTSTATUS srv_enc_raw_ntlm_auth(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob_const(*ppdata, *p_data_size);
+ DATA_BLOB response = data_blob_null;
+ struct smb_srv_trans_enc_ctx *ec;
+
+ if (!partial_srv_trans_enc_ctx) {
+ /* This is the initial step. */
+ status = srv_enc_ntlm_negotiate(ppdata, p_data_size, blob, false);
+ if (!NT_STATUS_EQUAL(status,NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) {
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ return nt_status_squash(status);
+ }
+ return status;
+ }
+
+ ec = partial_srv_trans_enc_ctx;
+ if (!ec || !ec->es || ec->auth_ntlmssp_state == NULL || ec->es->smb_enc_type != SMB_TRANS_ENC_NTLM) {
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Second step. */
+ status = auth_ntlmssp_update(partial_srv_trans_enc_ctx->auth_ntlmssp_state, blob, &response);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return the context we're using for this encryption state. */
+ if (!(*pparam = SMB_MALLOC_ARRAY(unsigned char, 2))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(*pparam,0,ec->es->enc_ctx_num);
+ *p_param_size = 2;
+ }
+
+ /* Return the raw blob. */
+ SAFE_FREE(*ppdata);
+ *ppdata = response.data;
+ *p_data_size = response.length;
+ return status;
+}
+
+/******************************************************************************
+ Do the SPNEGO encryption negotiation. Parameters are in/out.
+******************************************************************************/
+
+NTSTATUS srv_request_encryption_setup(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ unsigned char *pdata = *ppdata;
+
+ SAFE_FREE(*pparam);
+ *p_param_size = 0;
+
+ if (*p_data_size < 1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pdata[0] == ASN1_APPLICATION(0)) {
+ /* its a negTokenTarg packet */
+ return srv_enc_spnego_negotiate(conn, ppdata, p_data_size, pparam, p_param_size);
+ }
+
+ if (pdata[0] == ASN1_CONTEXT(1)) {
+ /* It's an auth packet */
+ return srv_enc_spnego_ntlm_auth(conn, ppdata, p_data_size, pparam, p_param_size);
+ }
+
+ /* Maybe it's a raw unwrapped auth ? */
+ if (*p_data_size < 7) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (strncmp((char *)pdata, "NTLMSSP", 7) == 0) {
+ return srv_enc_raw_ntlm_auth(conn, ppdata, p_data_size, pparam, p_param_size);
+ }
+
+ DEBUG(1,("srv_request_encryption_setup: Unknown packet\n"));
+
+ return NT_STATUS_LOGON_FAILURE;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+static NTSTATUS check_enc_good(struct smb_srv_trans_enc_ctx *ec)
+{
+ if (!ec || !ec->es) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ if (ec->es->smb_enc_type == SMB_TRANS_ENC_NTLM) {
+ if ((ec->es->s.ntlmssp_state->neg_flags & (NTLMSSP_NEGOTIATE_SIGN|NTLMSSP_NEGOTIATE_SEAL)) !=
+ (NTLMSSP_NEGOTIATE_SIGN|NTLMSSP_NEGOTIATE_SEAL)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+ /* Todo - check gssapi case. */
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+NTSTATUS srv_encryption_start(connection_struct *conn)
+{
+ NTSTATUS status;
+
+ /* Check that we are really doing sign+seal. */
+ status = check_enc_good(partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Throw away the context we're using currently (if any). */
+ srv_free_encryption_context(&srv_trans_enc_ctx);
+
+ /* Steal the partial pointer. Deliberate shallow copy. */
+ srv_trans_enc_ctx = partial_srv_trans_enc_ctx;
+ srv_trans_enc_ctx->es->enc_on = true;
+
+ partial_srv_trans_enc_ctx = NULL;
+
+ DEBUG(1,("srv_encryption_start: context negotiated\n"));
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Shutdown all server contexts.
+******************************************************************************/
+
+void server_encryption_shutdown(void)
+{
+ srv_free_encryption_context(&partial_srv_trans_enc_ctx);
+ srv_free_encryption_context(&srv_trans_enc_ctx);
+}
diff --git a/source3/smbd/sec_ctx.c b/source3/smbd/sec_ctx.c
new file mode 100644
index 0000000000..a618f06e6b
--- /dev/null
+++ b/source3/smbd/sec_ctx.c
@@ -0,0 +1,476 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Tim Potter 2000
+
+ 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"
+
+extern struct current_user current_user;
+
+struct sec_ctx {
+ UNIX_USER_TOKEN ut;
+ NT_USER_TOKEN *token;
+};
+
+/* A stack of security contexts. We include the current context as being
+ the first one, so there is room for another MAX_SEC_CTX_DEPTH more. */
+
+static struct sec_ctx sec_ctx_stack[MAX_SEC_CTX_DEPTH + 1];
+static int sec_ctx_stack_ndx;
+
+/****************************************************************************
+ Are two UNIX tokens equal ?
+****************************************************************************/
+
+bool unix_token_equal(const UNIX_USER_TOKEN *t1, const UNIX_USER_TOKEN *t2)
+{
+ if (t1->uid != t2->uid || t1->gid != t2->gid ||
+ t1->ngroups != t2->ngroups) {
+ return false;
+ }
+ if (memcmp(t1->groups, t2->groups,
+ t1->ngroups*sizeof(gid_t)) != 0) {
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ Become the specified uid.
+****************************************************************************/
+
+static bool become_uid(uid_t uid)
+{
+ /* Check for dodgy uid values */
+
+ if (uid == (uid_t)-1 ||
+ ((sizeof(uid_t) == 2) && (uid == (uid_t)65535))) {
+ static int done;
+
+ if (!done) {
+ DEBUG(1,("WARNING: using uid %d is a security risk\n",
+ (int)uid));
+ done = 1;
+ }
+ }
+
+ /* Set effective user id */
+
+ set_effective_uid(uid);
+
+ DO_PROFILE_INC(uid_changes);
+ return True;
+}
+
+/****************************************************************************
+ Become the specified gid.
+****************************************************************************/
+
+static bool become_gid(gid_t gid)
+{
+ /* Check for dodgy gid values */
+
+ if (gid == (gid_t)-1 || ((sizeof(gid_t) == 2) &&
+ (gid == (gid_t)65535))) {
+ static int done;
+
+ if (!done) {
+ DEBUG(1,("WARNING: using gid %d is a security risk\n",
+ (int)gid));
+ done = 1;
+ }
+ }
+
+ /* Set effective group id */
+
+ set_effective_gid(gid);
+ return True;
+}
+
+/****************************************************************************
+ Become the specified uid and gid.
+****************************************************************************/
+
+static bool become_id(uid_t uid, gid_t gid)
+{
+ return become_gid(gid) && become_uid(uid);
+}
+
+/****************************************************************************
+ Drop back to root privileges in order to change to another user.
+****************************************************************************/
+
+static void gain_root(void)
+{
+ if (non_root_mode()) {
+ return;
+ }
+
+ if (geteuid() != 0) {
+ set_effective_uid(0);
+
+ if (geteuid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "uid system\n"));
+ }
+ }
+
+ if (getegid() != 0) {
+ set_effective_gid(0);
+
+ if (getegid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "gid system\n"));
+ }
+ }
+}
+
+/****************************************************************************
+ Get the list of current groups.
+****************************************************************************/
+
+static int get_current_groups(gid_t gid, size_t *p_ngroups, gid_t **p_groups)
+{
+ int i;
+ gid_t grp;
+ int ngroups;
+ gid_t *groups = NULL;
+
+ (*p_ngroups) = 0;
+ (*p_groups) = NULL;
+
+ /* this looks a little strange, but is needed to cope with
+ systems that put the current egid in the group list
+ returned from getgroups() (tridge) */
+ save_re_gid();
+ set_effective_gid(gid);
+ setgid(gid);
+
+ ngroups = sys_getgroups(0,&grp);
+ if (ngroups <= 0) {
+ goto fail;
+ }
+
+ if((groups = SMB_MALLOC_ARRAY(gid_t, ngroups+1)) == NULL) {
+ DEBUG(0,("setup_groups malloc fail !\n"));
+ goto fail;
+ }
+
+ if ((ngroups = sys_getgroups(ngroups,groups)) == -1) {
+ goto fail;
+ }
+
+ restore_re_gid();
+
+ (*p_ngroups) = ngroups;
+ (*p_groups) = groups;
+
+ DEBUG( 3, ( "get_current_groups: user is in %u groups: ", ngroups));
+ for (i = 0; i < ngroups; i++ ) {
+ DEBUG( 3, ( "%s%d", (i ? ", " : ""), (int)groups[i] ) );
+ }
+ DEBUG( 3, ( "\n" ) );
+
+ return ngroups;
+
+fail:
+ SAFE_FREE(groups);
+ restore_re_gid();
+ return -1;
+}
+
+/****************************************************************************
+ Create a new security context on the stack. It is the same as the old
+ one. User changes are done using the set_sec_ctx() function.
+****************************************************************************/
+
+bool push_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+
+ /* Check we don't overflow our stack */
+
+ if (sec_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Security context stack overflow!\n"));
+ smb_panic("Security context stack overflow!");
+ }
+
+ /* Store previous user context */
+
+ sec_ctx_stack_ndx++;
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ DEBUG(3, ("push_sec_ctx(%u, %u) : sec_ctx_stack_ndx = %d\n",
+ (unsigned int)ctx_p->ut.uid, (unsigned int)ctx_p->ut.gid, sec_ctx_stack_ndx ));
+
+ ctx_p->token = dup_nt_token(NULL,
+ sec_ctx_stack[sec_ctx_stack_ndx-1].token);
+
+ ctx_p->ut.ngroups = sys_getgroups(0, NULL);
+
+ if (ctx_p->ut.ngroups != 0) {
+ if (!(ctx_p->ut.groups = SMB_MALLOC_ARRAY(gid_t, ctx_p->ut.ngroups))) {
+ DEBUG(0, ("Out of memory in push_sec_ctx()\n"));
+ TALLOC_FREE(ctx_p->token);
+ return False;
+ }
+
+ sys_getgroups(ctx_p->ut.ngroups, ctx_p->ut.groups);
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Change UNIX security context. Calls panic if not successful so no return value.
+****************************************************************************/
+
+#ifndef HAVE_DARWIN_INITGROUPS
+
+/* Normal credential switch path. */
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ /* Start context switch */
+ gain_root();
+#ifdef HAVE_SETGROUPS
+ if (sys_setgroups(gid, ngroups, groups) != 0 && !non_root_mode()) {
+ smb_panic("sys_setgroups failed");
+ }
+#endif
+ become_id(uid, gid);
+ /* end context switch */
+}
+
+#else /* HAVE_DARWIN_INITGROUPS */
+
+/* The Darwin groups implementation is a little unusual. The list of
+* groups in the kernel credential is not exhaustive, but more like
+* a cache. The full group list is held in userspace and checked
+* dynamically.
+*
+* This is an optional mechanism, and setgroups(2) opts out
+* of it. That is, if you call setgroups, then the list of groups you
+* set are the only groups that are ever checked. This is not what we
+* want. We want to opt in to the dynamic resolution mechanism, so we
+* need to specify the uid of the user whose group list (cache) we are
+* setting.
+*
+* The Darwin rules are:
+* 1. Thou shalt setegid, initgroups and seteuid IN THAT ORDER
+* 2. Thou shalt not pass more that NGROUPS_MAX to initgroups
+* 3. Thou shalt leave the first entry in the groups list well alone
+*/
+
+#include <sys/syscall.h>
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ int max = groups_max();
+
+ /* Start context switch */
+ gain_root();
+
+ become_gid(gid);
+
+
+ if (syscall(SYS_initgroups, (ngroups > max) ? max : ngroups,
+ groups, uid) == -1 && !non_root_mode()) {
+ DEBUG(0, ("WARNING: failed to set group list "
+ "(%d groups) for UID %ld: %s\n",
+ ngroups, uid, strerror(errno)));
+ smb_panic("sys_setgroups failed");
+ }
+
+ become_uid(uid);
+ /* end context switch */
+}
+
+#endif /* HAVE_DARWIN_INITGROUPS */
+
+/****************************************************************************
+ Set the current security context to a given user.
+****************************************************************************/
+
+void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, NT_USER_TOKEN *token)
+{
+ struct sec_ctx *ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Set the security context */
+
+ DEBUG(3, ("setting sec ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)uid, (unsigned int)gid, sec_ctx_stack_ndx));
+
+ debug_nt_user_token(DBGC_CLASS, 5, token);
+ debug_unix_user_token(DBGC_CLASS, 5, uid, gid, ngroups, groups);
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(uid, gid, ngroups, groups);
+
+ ctx_p->ut.ngroups = ngroups;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ if (token && (token == ctx_p->token)) {
+ smb_panic("DUPLICATE_TOKEN");
+ }
+
+ TALLOC_FREE(ctx_p->token);
+
+ if (ngroups) {
+ ctx_p->ut.groups = (gid_t *)memdup(groups,
+ sizeof(gid_t) * ngroups);
+ if (!ctx_p->ut.groups) {
+ smb_panic("memdup failed");
+ }
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ if (token) {
+ ctx_p->token = dup_nt_token(NULL, token);
+ if (!ctx_p->token) {
+ smb_panic("dup_nt_token failed");
+ }
+ } else {
+ ctx_p->token = NULL;
+ }
+
+ ctx_p->ut.uid = uid;
+ ctx_p->ut.gid = gid;
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = uid;
+ current_user.ut.gid = gid;
+ current_user.ut.ngroups = ngroups;
+ current_user.ut.groups = groups;
+ current_user.nt_user_token = ctx_p->token;
+}
+
+/****************************************************************************
+ Become root context.
+****************************************************************************/
+
+void set_root_sec_ctx(void)
+{
+ /* May need to worry about supplementary groups at some stage */
+
+ set_sec_ctx(0, 0, 0, NULL, NULL);
+}
+
+/****************************************************************************
+ Pop a security context from the stack.
+****************************************************************************/
+
+bool pop_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+ struct sec_ctx *prev_ctx_p;
+
+ /* Check for stack underflow */
+
+ if (sec_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Security context stack underflow!\n"));
+ smb_panic("Security context stack underflow!");
+ }
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Clear previous user info */
+
+ ctx_p->ut.uid = (uid_t)-1;
+ ctx_p->ut.gid = (gid_t)-1;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ ctx_p->ut.ngroups = 0;
+
+ TALLOC_FREE(ctx_p->token);
+
+ /* Pop back previous user */
+
+ sec_ctx_stack_ndx--;
+
+ prev_ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(prev_ctx_p->ut.uid,
+ prev_ctx_p->ut.gid,
+ prev_ctx_p->ut.ngroups,
+ prev_ctx_p->ut.groups);
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = prev_ctx_p->ut.uid;
+ current_user.ut.gid = prev_ctx_p->ut.gid;
+ current_user.ut.ngroups = prev_ctx_p->ut.ngroups;
+ current_user.ut.groups = prev_ctx_p->ut.groups;
+ current_user.nt_user_token = prev_ctx_p->token;
+
+ DEBUG(3, ("pop_sec_ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)geteuid(), (unsigned int)getegid(), sec_ctx_stack_ndx));
+
+ return True;
+}
+
+/* Initialise the security context system */
+
+void init_sec_ctx(void)
+{
+ int i;
+ struct sec_ctx *ctx_p;
+
+ /* Initialise security context stack */
+
+ memset(sec_ctx_stack, 0, sizeof(struct sec_ctx) * MAX_SEC_CTX_DEPTH);
+
+ for (i = 0; i < MAX_SEC_CTX_DEPTH; i++) {
+ sec_ctx_stack[i].ut.uid = (uid_t)-1;
+ sec_ctx_stack[i].ut.gid = (gid_t)-1;
+ }
+
+ /* Initialise first level of stack. It is the current context */
+ ctx_p = &sec_ctx_stack[0];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ get_current_groups(ctx_p->ut.gid, &ctx_p->ut.ngroups, &ctx_p->ut.groups);
+
+ ctx_p->token = NULL; /* Maps to guest user. */
+
+ /* Initialise current_user global */
+
+ current_user.ut.uid = ctx_p->ut.uid;
+ current_user.ut.gid = ctx_p->ut.gid;
+ current_user.ut.ngroups = ctx_p->ut.ngroups;
+ current_user.ut.groups = ctx_p->ut.groups;
+
+ /* The conn and vuid are usually taken care of by other modules.
+ We initialise them here. */
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+ current_user.nt_user_token = NULL;
+}
diff --git a/source3/smbd/server.c b/source3/smbd/server.c
new file mode 100644
index 0000000000..53116f3d98
--- /dev/null
+++ b/source3/smbd/server.c
@@ -0,0 +1,1471 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+
+ 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"
+
+static_decl_rpc;
+
+static int am_parent = 1;
+
+extern struct auth_context *negprot_global_auth_context;
+extern SIG_ATOMIC_T got_sig_term;
+extern SIG_ATOMIC_T reload_after_sighup;
+static SIG_ATOMIC_T got_sig_cld;
+
+#ifdef WITH_DFS
+extern int dcelogin_atmost_once;
+#endif /* WITH_DFS */
+
+/* really we should have a top level context structure that has the
+ client file descriptor as an element. That would require a major rewrite :(
+
+ the following 2 functions are an alternative - they make the file
+ descriptor private to smbd
+ */
+static int server_fd = -1;
+
+int smbd_server_fd(void)
+{
+ return server_fd;
+}
+
+static void smbd_set_server_fd(int fd)
+{
+ server_fd = fd;
+}
+
+int get_client_fd(void)
+{
+ return server_fd;
+}
+
+int client_get_tcp_info(struct sockaddr_in *server, struct sockaddr_in *client)
+{
+ socklen_t length;
+ if (server_fd == -1) {
+ return -1;
+ }
+ length = sizeof(*server);
+ if (getsockname(server_fd, (struct sockaddr *)server, &length) != 0) {
+ return -1;
+ }
+ length = sizeof(*client);
+ if (getpeername(server_fd, (struct sockaddr *)client, &length) != 0) {
+ return -1;
+ }
+ return 0;
+}
+
+struct event_context *smbd_event_context(void)
+{
+ static struct event_context *ctx;
+
+ if (!ctx && !(ctx = event_context_init(NULL))) {
+ smb_panic("Could not init smbd event context");
+ }
+ return ctx;
+}
+
+struct messaging_context *smbd_messaging_context(void)
+{
+ static struct messaging_context *ctx;
+
+ if (ctx == NULL) {
+ ctx = messaging_init(NULL, server_id_self(),
+ smbd_event_context());
+ }
+ if (ctx == NULL) {
+ DEBUG(0, ("Could not init smbd messaging context.\n"));
+ }
+ return ctx;
+}
+
+struct memcache *smbd_memcache(void)
+{
+ static struct memcache *cache;
+
+ if (!cache
+ && !(cache = memcache_init(NULL,
+ lp_max_stat_cache_size()*1024))) {
+
+ smb_panic("Could not init smbd memcache");
+ }
+ return cache;
+}
+
+/*******************************************************************
+ What to do when smb.conf is updated.
+ ********************************************************************/
+
+static void smb_conf_updated(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(10,("smb_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ reload_services(False);
+}
+
+
+/*******************************************************************
+ Delete a statcache entry.
+ ********************************************************************/
+
+static void smb_stat_cache_delete(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_tnype,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ const char *name = (const char *)data->data;
+ DEBUG(10,("smb_stat_cache_delete: delete name %s\n", name));
+ stat_cache_delete(name);
+}
+
+/****************************************************************************
+ Terminate signal.
+****************************************************************************/
+
+static void sig_term(void)
+{
+ got_sig_term = 1;
+ sys_select_signal(SIGTERM);
+}
+
+/****************************************************************************
+ Catch a sighup.
+****************************************************************************/
+
+static void sig_hup(int sig)
+{
+ reload_after_sighup = 1;
+ sys_select_signal(SIGHUP);
+}
+
+/****************************************************************************
+ Catch a sigcld
+****************************************************************************/
+static void sig_cld(int sig)
+{
+ got_sig_cld = 1;
+ sys_select_signal(SIGCLD);
+}
+
+/****************************************************************************
+ Send a SIGTERM to our process group.
+*****************************************************************************/
+
+static void killkids(void)
+{
+ if(am_parent) kill(0,SIGTERM);
+}
+
+/****************************************************************************
+ Process a sam sync message - not sure whether to do this here or
+ somewhere else.
+****************************************************************************/
+
+static void msg_sam_sync(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(10, ("** sam sync message received, ignoring\n"));
+}
+
+
+/****************************************************************************
+ Open the socket communication - inetd.
+****************************************************************************/
+
+static bool open_sockets_inetd(void)
+{
+ /* Started from inetd. fd 0 is the socket. */
+ /* We will abort gracefully when the client or remote system
+ goes away */
+ smbd_set_server_fd(dup(0));
+
+ /* close our standard file descriptors */
+ close_low_fds(False); /* Don't close stderr */
+
+ set_socket_options(smbd_server_fd(),"SO_KEEPALIVE");
+ set_socket_options(smbd_server_fd(), lp_socket_options());
+
+ return True;
+}
+
+static void msg_exit_server(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(3, ("got a SHUTDOWN message\n"));
+ exit_server_cleanly(NULL);
+}
+
+#ifdef DEVELOPER
+static void msg_inject_fault(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ int sig;
+
+ if (data->length != sizeof(sig)) {
+
+ DEBUG(0, ("Process %s sent bogus signal injection request\n",
+ procid_str_static(&src)));
+ return;
+ }
+
+ sig = *(int *)data->data;
+ if (sig == -1) {
+ exit_server("internal error injected");
+ return;
+ }
+
+#if HAVE_STRSIGNAL
+ DEBUG(0, ("Process %s requested injection of signal %d (%s)\n",
+ procid_str_static(&src), sig, strsignal(sig)));
+#else
+ DEBUG(0, ("Process %s requested injection of signal %d\n",
+ procid_str_static(&src), sig));
+#endif
+
+ kill(sys_getpid(), sig);
+}
+#endif /* DEVELOPER */
+
+struct child_pid {
+ struct child_pid *prev, *next;
+ pid_t pid;
+};
+
+static struct child_pid *children;
+static int num_children;
+
+static void add_child_pid(pid_t pid)
+{
+ struct child_pid *child;
+
+ if (lp_max_smbd_processes() == 0) {
+ /* Don't bother with the child list if we don't care anyway */
+ return;
+ }
+
+ child = SMB_MALLOC_P(struct child_pid);
+ if (child == NULL) {
+ DEBUG(0, ("Could not add child struct -- malloc failed\n"));
+ return;
+ }
+ child->pid = pid;
+ DLIST_ADD(children, child);
+ num_children += 1;
+}
+
+static void remove_child_pid(pid_t pid, bool unclean_shutdown)
+{
+ struct child_pid *child;
+
+ if (unclean_shutdown) {
+ /* a child terminated uncleanly so tickle all processes to see
+ if they can grab any of the pending locks
+ */
+ DEBUG(3,(__location__ " Unclean shutdown of pid %u\n", pid));
+ messaging_send_buf(smbd_messaging_context(), procid_self(),
+ MSG_SMB_BRL_VALIDATE, NULL, 0);
+ message_send_all(smbd_messaging_context(),
+ MSG_SMB_UNLOCK, NULL, 0, NULL);
+ }
+
+ if (lp_max_smbd_processes() == 0) {
+ /* Don't bother with the child list if we don't care anyway */
+ return;
+ }
+
+ for (child = children; child != NULL; child = child->next) {
+ if (child->pid == pid) {
+ struct child_pid *tmp = child;
+ DLIST_REMOVE(children, child);
+ SAFE_FREE(tmp);
+ num_children -= 1;
+ return;
+ }
+ }
+
+ DEBUG(0, ("Could not find child %d -- ignoring\n", (int)pid));
+}
+
+/****************************************************************************
+ Have we reached the process limit ?
+****************************************************************************/
+
+static bool allowable_number_of_smbd_processes(void)
+{
+ int max_processes = lp_max_smbd_processes();
+
+ if (!max_processes)
+ return True;
+
+ return num_children < max_processes;
+}
+
+/****************************************************************************
+ Open the socket communication.
+****************************************************************************/
+
+static bool open_sockets_smbd(bool is_daemon, bool interactive, const char *smb_ports)
+{
+ int num_interfaces = iface_count();
+ int num_sockets = 0;
+ int fd_listenset[FD_SETSIZE];
+ fd_set listen_set;
+ int s;
+ int maxfd = 0;
+ int i;
+ char *ports;
+ struct dns_reg_state * dns_reg = NULL;
+ unsigned dns_port = 0;
+
+ if (!is_daemon) {
+ return open_sockets_inetd();
+ }
+
+#ifdef HAVE_ATEXIT
+ {
+ static int atexit_set;
+ if(atexit_set == 0) {
+ atexit_set=1;
+ atexit(killkids);
+ }
+ }
+#endif
+
+ /* Stop zombies */
+ CatchSignal(SIGCLD, sig_cld);
+
+ FD_ZERO(&listen_set);
+
+ /* use a reasonable default set of ports - listing on 445 and 139 */
+ if (!smb_ports) {
+ ports = lp_smb_ports();
+ if (!ports || !*ports) {
+ ports = smb_xstrdup(SMB_PORTS);
+ } else {
+ ports = smb_xstrdup(ports);
+ }
+ } else {
+ ports = smb_xstrdup(smb_ports);
+ }
+
+ if (lp_interfaces() && lp_bind_interfaces_only()) {
+ /* We have been given an interfaces line, and been
+ told to only bind to those interfaces. Create a
+ socket per interface and bind to only these.
+ */
+
+ /* Now open a listen socket for each of the
+ interfaces. */
+ for(i = 0; i < num_interfaces; i++) {
+ TALLOC_CTX *frame = NULL;
+ const struct sockaddr_storage *ifss =
+ iface_n_sockaddr_storage(i);
+ char *tok;
+ const char *ptr;
+
+ if (ifss == NULL) {
+ DEBUG(0,("open_sockets_smbd: "
+ "interface %d has NULL IP address !\n",
+ i));
+ continue;
+ }
+
+ frame = talloc_stackframe();
+ for (ptr=ports;
+ next_token_talloc(frame,&ptr, &tok, " \t,");) {
+ unsigned port = atoi(tok);
+ if (port == 0 || port > 0xffff) {
+ continue;
+ }
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ s = fd_listenset[num_sockets] =
+ open_socket_in(SOCK_STREAM,
+ port,
+ num_sockets == 0 ? 0 : 2,
+ ifss,
+ true);
+ if(s == -1) {
+ continue;
+ }
+
+ /* ready to listen */
+ set_socket_options(s,"SO_KEEPALIVE");
+ set_socket_options(s,lp_socket_options());
+
+ /* Set server socket to
+ * non-blocking for the accept. */
+ set_blocking(s,False);
+
+ if (listen(s, SMBD_LISTEN_BACKLOG) == -1) {
+ DEBUG(0,("open_sockets_smbd: listen: "
+ "%s\n", strerror(errno)));
+ close(s);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ FD_SET(s,&listen_set);
+ maxfd = MAX( maxfd, s);
+
+ num_sockets++;
+ if (num_sockets >= FD_SETSIZE) {
+ DEBUG(0,("open_sockets_smbd: Too "
+ "many sockets to bind to\n"));
+ TALLOC_FREE(frame);
+ return False;
+ }
+ }
+ TALLOC_FREE(frame);
+ }
+ } else {
+ /* Just bind to 0.0.0.0 - accept connections
+ from anywhere. */
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *tok;
+ const char *ptr;
+ const char *sock_addr = lp_socket_address();
+ char *sock_tok;
+ const char *sock_ptr;
+
+ if (sock_addr[0] == '\0' ||
+ strequal(sock_addr, "0.0.0.0") ||
+ strequal(sock_addr, "::")) {
+#if HAVE_IPV6
+ sock_addr = "::,0.0.0.0";
+#else
+ sock_addr = "0.0.0.0";
+#endif
+ }
+
+ for (sock_ptr=sock_addr;
+ next_token_talloc(frame, &sock_ptr, &sock_tok, " \t,"); ) {
+ for (ptr=ports; next_token_talloc(frame, &ptr, &tok, " \t,"); ) {
+ struct sockaddr_storage ss;
+
+ unsigned port = atoi(tok);
+ if (port == 0 || port > 0xffff) {
+ continue;
+ }
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ /* open an incoming socket */
+ if (!interpret_string_addr(&ss, sock_tok,
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ continue;
+ }
+
+ s = open_socket_in(SOCK_STREAM,
+ port,
+ num_sockets == 0 ? 0 : 2,
+ &ss,
+ true);
+ if (s == -1) {
+ continue;
+ }
+
+ /* ready to listen */
+ set_socket_options(s,"SO_KEEPALIVE");
+ set_socket_options(s,lp_socket_options());
+
+ /* Set server socket to non-blocking
+ * for the accept. */
+ set_blocking(s,False);
+
+ if (listen(s, SMBD_LISTEN_BACKLOG) == -1) {
+ DEBUG(0,("open_sockets_smbd: "
+ "listen: %s\n",
+ strerror(errno)));
+ close(s);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ fd_listenset[num_sockets] = s;
+ FD_SET(s,&listen_set);
+ maxfd = MAX( maxfd, s);
+
+ num_sockets++;
+
+ if (num_sockets >= FD_SETSIZE) {
+ DEBUG(0,("open_sockets_smbd: Too "
+ "many sockets to bind to\n"));
+ TALLOC_FREE(frame);
+ return False;
+ }
+ }
+ }
+ TALLOC_FREE(frame);
+ }
+
+ SAFE_FREE(ports);
+
+ if (num_sockets == 0) {
+ DEBUG(0,("open_sockets_smbd: No "
+ "sockets available to bind to.\n"));
+ return false;
+ }
+
+ /* Setup the main smbd so that we can get messages. Note that
+ do this after starting listening. This is needed as when in
+ clustered mode, ctdb won't allow us to start doing database
+ operations until it has gone thru a full startup, which
+ includes checking to see that smbd is listening. */
+ claim_connection(NULL,"",
+ FLAG_MSG_GENERAL|FLAG_MSG_SMBD|FLAG_MSG_DBWRAP);
+
+ /* Listen to messages */
+
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_SAM_SYNC, msg_sam_sync);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SHUTDOWN, msg_exit_server);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_FILE_RENAME, msg_file_was_renamed);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_CONF_UPDATED, smb_conf_updated);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_STAT_CACHE_DELETE, smb_stat_cache_delete);
+ brl_register_msgs(smbd_messaging_context());
+
+#ifdef CLUSTER_SUPPORT
+ if (lp_clustering()) {
+ ctdbd_register_reconfigure(messaging_ctdbd_connection());
+ }
+#endif
+
+#ifdef DEVELOPER
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_INJECT_FAULT, msg_inject_fault);
+#endif
+
+ /* now accept incoming connections - forking a new process
+ for each incoming connection */
+ DEBUG(2,("waiting for a connection\n"));
+ while (1) {
+ struct timeval now, idle_timeout;
+ fd_set r_fds, w_fds;
+ int num;
+
+ /* Ensure we respond to PING and DEBUG messages from the main smbd. */
+ message_dispatch(smbd_messaging_context());
+
+ if (got_sig_cld) {
+ pid_t pid;
+ int status;
+
+ got_sig_cld = False;
+
+ while ((pid = sys_waitpid(-1, &status, WNOHANG)) > 0) {
+ bool unclean_shutdown = False;
+
+ /* If the child terminated normally, assume
+ it was an unclean shutdown unless the
+ status is 0
+ */
+ if (WIFEXITED(status)) {
+ unclean_shutdown = WEXITSTATUS(status);
+ }
+ /* If the child terminated due to a signal
+ we always assume it was unclean.
+ */
+ if (WIFSIGNALED(status)) {
+ unclean_shutdown = True;
+ }
+ remove_child_pid(pid, unclean_shutdown);
+ }
+ }
+
+ idle_timeout = timeval_zero();
+
+ memcpy((char *)&r_fds, (char *)&listen_set,
+ sizeof(listen_set));
+ FD_ZERO(&w_fds);
+ GetTimeOfDay(&now);
+
+ /* Kick off our mDNS registration. */
+ if (dns_port != 0) {
+ dns_register_smbd(&dns_reg, dns_port, &maxfd,
+ &r_fds, &idle_timeout);
+ }
+
+ event_add_to_select_args(smbd_event_context(), &now,
+ &r_fds, &w_fds, &idle_timeout,
+ &maxfd);
+
+ num = sys_select(maxfd+1,&r_fds,&w_fds,NULL,
+ timeval_is_zero(&idle_timeout) ?
+ NULL : &idle_timeout);
+
+ if (num == -1 && errno == EINTR) {
+ if (got_sig_term) {
+ exit_server_cleanly(NULL);
+ }
+
+ /* check for sighup processing */
+ if (reload_after_sighup) {
+ change_to_root_user();
+ DEBUG(1,("Reloading services after SIGHUP\n"));
+ reload_services(False);
+ reload_after_sighup = 0;
+ }
+
+ continue;
+ }
+
+
+ /* If the idle timeout fired and we don't have any connected
+ * users, exit gracefully. We should be running under a process
+ * controller that will restart us if necessry.
+ */
+ if (num == 0 && count_all_current_connections() == 0) {
+ exit_server_cleanly("idle timeout");
+ }
+
+ /* process pending nDNS responses */
+ if (dns_register_smbd_reply(dns_reg, &r_fds, &idle_timeout)) {
+ --num;
+ }
+
+ if (run_events(smbd_event_context(), num, &r_fds, &w_fds)) {
+ continue;
+ }
+
+ /* check if we need to reload services */
+ check_reload(time(NULL));
+
+ /* Find the sockets that are read-ready -
+ accept on these. */
+ for( ; num > 0; num--) {
+ struct sockaddr addr;
+ socklen_t in_addrlen = sizeof(addr);
+ pid_t child = 0;
+
+ s = -1;
+ for(i = 0; i < num_sockets; i++) {
+ if(FD_ISSET(fd_listenset[i],&r_fds)) {
+ s = fd_listenset[i];
+ /* Clear this so we don't look
+ at it again. */
+ FD_CLR(fd_listenset[i],&r_fds);
+ break;
+ }
+ }
+
+ smbd_set_server_fd(accept(s,&addr,&in_addrlen));
+
+ if (smbd_server_fd() == -1 && errno == EINTR)
+ continue;
+
+ if (smbd_server_fd() == -1) {
+ DEBUG(2,("open_sockets_smbd: accept: %s\n",
+ strerror(errno)));
+ continue;
+ }
+
+ /* Ensure child is set to blocking mode */
+ set_blocking(smbd_server_fd(),True);
+
+ if (smbd_server_fd() != -1 && interactive)
+ return True;
+
+ if (allowable_number_of_smbd_processes() &&
+ smbd_server_fd() != -1 &&
+ ((child = sys_fork())==0)) {
+ char remaddr[INET6_ADDRSTRLEN];
+
+ /* Child code ... */
+
+ /* Stop zombies, the parent explicitly handles
+ * them, counting worker smbds. */
+ CatchChild();
+
+ /* close the listening socket(s) */
+ for(i = 0; i < num_sockets; i++)
+ close(fd_listenset[i]);
+
+ /* close our mDNS daemon handle */
+ dns_register_close(&dns_reg);
+
+ /* close our standard file
+ descriptors */
+ close_low_fds(False);
+ am_parent = 0;
+
+ set_socket_options(smbd_server_fd(),"SO_KEEPALIVE");
+ set_socket_options(smbd_server_fd(),
+ lp_socket_options());
+
+ /* this is needed so that we get decent entries
+ in smbstatus for port 445 connects */
+ set_remote_machine_name(get_peer_addr(smbd_server_fd(),
+ remaddr,
+ sizeof(remaddr)),
+ false);
+
+ if (!reinit_after_fork(
+ smbd_messaging_context(), true)) {
+ DEBUG(0,("reinit_after_fork() failed\n"));
+ smb_panic("reinit_after_fork() failed");
+ }
+
+ return True;
+ }
+ /* The parent doesn't need this socket */
+ close(smbd_server_fd());
+
+ /* Sun May 6 18:56:14 2001 ackley@cs.unm.edu:
+ Clear the closed fd info out of server_fd --
+ and more importantly, out of client_fd in
+ util_sock.c, to avoid a possible
+ getpeername failure if we reopen the logs
+ and use %I in the filename.
+ */
+
+ smbd_set_server_fd(-1);
+
+ if (child != 0) {
+ add_child_pid(child);
+ }
+
+ /* Force parent to check log size after
+ * spawning child. Fix from
+ * klausr@ITAP.Physik.Uni-Stuttgart.De. The
+ * parent smbd will log to logserver.smb. It
+ * writes only two messages for each child
+ * started/finished. But each child writes,
+ * say, 50 messages also in logserver.smb,
+ * begining with the debug_count of the
+ * parent, before the child opens its own log
+ * file logserver.client. In a worst case
+ * scenario the size of logserver.smb would be
+ * checked after about 50*50=2500 messages
+ * (ca. 100kb).
+ * */
+ force_check_log_size();
+
+ } /* end for num */
+ } /* end while 1 */
+
+/* NOTREACHED return True; */
+}
+
+/****************************************************************************
+ Reload printers
+**************************************************************************/
+void reload_printers(void)
+{
+ int snum;
+ int n_services = lp_numservices();
+ int pnum = lp_servicenumber(PRINTERS_NAME);
+ const char *pname;
+
+ pcap_cache_reload();
+
+ /* remove stale printers */
+ for (snum = 0; snum < n_services; snum++) {
+ /* avoid removing PRINTERS_NAME or non-autoloaded printers */
+ if (snum == pnum || !(lp_snum_ok(snum) && lp_print_ok(snum) &&
+ lp_autoloaded(snum)))
+ continue;
+
+ pname = lp_printername(snum);
+ if (!pcap_printername_ok(pname)) {
+ DEBUG(3, ("removing stale printer %s\n", pname));
+
+ if (is_printer_published(NULL, snum, NULL))
+ nt_printer_publish(NULL, snum, SPOOL_DS_UNPUBLISH);
+ del_a_printer(pname);
+ lp_killservice(snum);
+ }
+ }
+
+ load_printers();
+}
+
+/****************************************************************************
+ Reload the services file.
+**************************************************************************/
+
+bool reload_services(bool test)
+{
+ bool ret;
+
+ if (lp_loaded()) {
+ char *fname = lp_configfile();
+ if (file_exist(fname, NULL) &&
+ !strcsequal(fname, get_dyn_CONFIGFILE())) {
+ set_dyn_CONFIGFILE(fname);
+ test = False;
+ }
+ }
+
+ reopen_logs();
+
+ if (test && !lp_file_list_changed())
+ return(True);
+
+ lp_killunused(conn_snum_used);
+
+ ret = lp_load(get_dyn_CONFIGFILE(), False, False, True, True);
+
+ reload_printers();
+
+ /* perhaps the config filename is now set */
+ if (!test)
+ reload_services(True);
+
+ reopen_logs();
+
+ load_interfaces();
+
+ if (smbd_server_fd() != -1) {
+ set_socket_options(smbd_server_fd(),"SO_KEEPALIVE");
+ set_socket_options(smbd_server_fd(), lp_socket_options());
+ }
+
+ mangle_reset_cache();
+ reset_stat_cache();
+
+ /* this forces service parameters to be flushed */
+ set_current_service(NULL,0,True);
+
+ return(ret);
+}
+
+/****************************************************************************
+ Exit the server.
+****************************************************************************/
+
+/* Reasons for shutting down a server process. */
+enum server_exit_reason { SERVER_EXIT_NORMAL, SERVER_EXIT_ABNORMAL };
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *const reason) NORETURN_ATTRIBUTE;
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *const reason)
+{
+ static int firsttime=1;
+ bool had_open_conn;
+
+ if (!firsttime)
+ exit(0);
+ firsttime = 0;
+
+ change_to_root_user();
+
+ if (negprot_global_auth_context) {
+ (negprot_global_auth_context->free)(&negprot_global_auth_context);
+ }
+
+ had_open_conn = conn_close_all();
+
+ invalidate_all_vuids();
+
+ /* 3 second timeout. */
+ print_notify_send_messages(smbd_messaging_context(), 3);
+
+ /* delete our entry in the connections database. */
+ yield_connection(NULL,"");
+
+ respond_to_all_remaining_local_messages();
+
+#ifdef WITH_DFS
+ if (dcelogin_atmost_once) {
+ dfs_unlogin();
+ }
+#endif
+
+#ifdef USE_DMAPI
+ /* Destroy Samba DMAPI session only if we are master smbd process */
+ if (am_parent) {
+ if (!dmapi_destroy_session()) {
+ DEBUG(0,("Unable to close Samba DMAPI session\n"));
+ }
+ }
+#endif
+
+ locking_end();
+ printing_end();
+
+ if (how != SERVER_EXIT_NORMAL) {
+ int oldlevel = DEBUGLEVEL;
+
+ DEBUGLEVEL = 10;
+
+ DEBUGSEP(0);
+ DEBUG(0,("Abnormal server exit: %s\n",
+ reason ? reason : "no explanation provided"));
+ DEBUGSEP(0);
+
+ log_stack_trace();
+
+ DEBUGLEVEL = oldlevel;
+ dump_core();
+
+ } else {
+ DEBUG(3,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ }
+
+ /* if we had any open SMB connections when we exited then we
+ need to tell the parent smbd so that it can trigger a retry
+ of any locks we may have been holding or open files we were
+ blocking */
+ if (had_open_conn) {
+ exit(1);
+ } else {
+ exit(0);
+ }
+}
+
+void exit_server(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_ABNORMAL, explanation);
+}
+
+void exit_server_cleanly(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_NORMAL, explanation);
+}
+
+void exit_server_fault(void)
+{
+ exit_server("critical server fault");
+}
+
+
+/****************************************************************************
+received when we should release a specific IP
+****************************************************************************/
+static void release_ip(const char *ip, void *priv)
+{
+ char addr[INET6_ADDRSTRLEN];
+
+ if (strcmp(client_socket_addr(get_client_fd(),addr,sizeof(addr)), ip) == 0) {
+ /* we can't afford to do a clean exit - that involves
+ database writes, which would potentially mean we
+ are still running after the failover has finished -
+ we have to get rid of this process ID straight
+ away */
+ DEBUG(0,("Got release IP message for our IP %s - exiting immediately\n",
+ ip));
+ /* note we must exit with non-zero status so the unclean handler gets
+ called in the parent, so that the brl database is tickled */
+ _exit(1);
+ }
+}
+
+static void msg_release_ip(struct messaging_context *msg_ctx, void *private_data,
+ uint32_t msg_type, struct server_id server_id, DATA_BLOB *data)
+{
+ release_ip((char *)data->data, NULL);
+}
+
+/****************************************************************************
+ Initialise connect, service and file structs.
+****************************************************************************/
+
+static bool init_structs(void )
+{
+ /*
+ * Set the machine NETBIOS name if not already
+ * set from the config file.
+ */
+
+ if (!init_names())
+ return False;
+
+ conn_init();
+
+ file_init();
+
+ /* for RPC pipes */
+ init_rpc_pipe_hnd();
+
+ init_dptrs();
+
+ if (!secrets_init())
+ return False;
+
+ return True;
+}
+
+/*
+ * Send keepalive packets to our client
+ */
+static bool keepalive_fn(const struct timeval *now, void *private_data)
+{
+ if (!send_keepalive(smbd_server_fd())) {
+ DEBUG( 2, ( "Keepalive failed - exiting.\n" ) );
+ return False;
+ }
+ return True;
+}
+
+/*
+ * Do the recurring check if we're idle
+ */
+static bool deadtime_fn(const struct timeval *now, void *private_data)
+{
+ if ((conn_num_open() == 0)
+ || (conn_idle_all(now->tv_sec))) {
+ DEBUG( 2, ( "Closing idle connection\n" ) );
+ messaging_send(smbd_messaging_context(), procid_self(),
+ MSG_SHUTDOWN, &data_blob_null);
+ return False;
+ }
+
+ return True;
+}
+
+
+/****************************************************************************
+ main program.
+****************************************************************************/
+
+/* Declare prototype for build_options() to avoid having to run it through
+ mkproto.h. Mixing $(builddir) and $(srcdir) source files in the current
+ prototype generation system is too complicated. */
+
+extern void build_options(bool screen);
+
+ int main(int argc,const char *argv[])
+{
+ /* shall I run as a daemon */
+ static bool is_daemon = False;
+ static bool interactive = False;
+ static bool Fork = True;
+ static bool no_process_group = False;
+ static bool log_stdout = False;
+ static char *ports = NULL;
+ static char *profile_level = NULL;
+ int opt;
+ poptContext pc;
+ bool print_build_options = False;
+ enum {
+ OPT_DAEMON = 1000,
+ OPT_INTERACTIVE,
+ OPT_FORK,
+ OPT_NO_PROCESS_GROUP,
+ OPT_LOG_STDOUT
+ };
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"daemon", 'D', POPT_ARG_NONE, NULL, OPT_DAEMON, "Become a daemon (default)" },
+ {"interactive", 'i', POPT_ARG_NONE, NULL, OPT_INTERACTIVE, "Run interactive (not a daemon)"},
+ {"foreground", 'F', POPT_ARG_NONE, NULL, OPT_FORK, "Run daemon in foreground (for daemontools, etc.)" },
+ {"no-process-group", '\0', POPT_ARG_NONE, NULL, OPT_NO_PROCESS_GROUP, "Don't create a new process group" },
+ {"log-stdout", 'S', POPT_ARG_NONE, NULL, OPT_LOG_STDOUT, "Log to stdout" },
+ {"build-options", 'b', POPT_ARG_NONE, NULL, 'b', "Print build options" },
+ {"port", 'p', POPT_ARG_STRING, &ports, 0, "Listen on the specified ports"},
+ {"profiling-level", 'P', POPT_ARG_STRING, &profile_level, 0, "Set profiling level","PROFILE_LEVEL"},
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DYNCONFIG
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe(); /* Setup tos. */
+
+ TimeInit();
+
+#ifdef HAVE_SET_AUTH_PARAMETERS
+ set_auth_parameters(argc,argv);
+#endif
+
+ pc = poptGetContext("smbd", argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case OPT_DAEMON:
+ is_daemon = true;
+ break;
+ case OPT_INTERACTIVE:
+ interactive = true;
+ break;
+ case OPT_FORK:
+ Fork = false;
+ break;
+ case OPT_NO_PROCESS_GROUP:
+ no_process_group = true;
+ break;
+ case OPT_LOG_STDOUT:
+ log_stdout = true;
+ break;
+ case 'b':
+ print_build_options = True;
+ break;
+ default:
+ d_fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+ poptFreeContext(pc);
+
+ if (interactive) {
+ Fork = False;
+ log_stdout = True;
+ }
+
+ setup_logging(argv[0],log_stdout);
+
+ if (print_build_options) {
+ build_options(True); /* Display output to screen as well as debug */
+ exit(0);
+ }
+
+ load_case_tables();
+
+#ifdef HAVE_SETLUID
+ /* needed for SecureWare on SCO */
+ setluid(0);
+#endif
+
+ sec_init();
+
+ set_remote_machine_name("smbd", False);
+
+ if (interactive && (DEBUGLEVEL >= 9)) {
+ talloc_enable_leak_report();
+ }
+
+ if (log_stdout && Fork) {
+ DEBUG(0,("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n"));
+ exit(1);
+ }
+
+ /* we want to re-seed early to prevent time delays causing
+ client problems at a later date. (tridge) */
+ generate_random_buffer(NULL, 0);
+
+ /* make absolutely sure we run as root - to handle cases where people
+ are crazy enough to have it setuid */
+
+ gain_root_privilege();
+ gain_root_group_privilege();
+
+ fault_setup((void (*)(void *))exit_server_fault);
+ dump_core_setup("smbd");
+
+ CatchSignal(SIGTERM , SIGNAL_CAST sig_term);
+ CatchSignal(SIGHUP,SIGNAL_CAST sig_hup);
+
+ /* we are never interested in SIGPIPE */
+ BlockSignals(True,SIGPIPE);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(True,SIGFPE);
+#endif
+
+#if defined(SIGUSR2)
+ /* We are no longer interested in USR2 */
+ BlockSignals(True,SIGUSR2);
+#endif
+
+ /* POSIX demands that signals are inherited. If the invoking process has
+ * these signals masked, we will have problems, as we won't recieve them. */
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGTERM);
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ umask(0);
+
+ init_sec_ctx();
+
+ reopen_logs();
+
+ DEBUG(0,("smbd version %s started.\n", SAMBA_VERSION_STRING));
+ DEBUGADD(0,("%s\n", COPYRIGHT_STARTUP_MESSAGE));
+
+ DEBUG(2,("uid=%d gid=%d euid=%d egid=%d\n",
+ (int)getuid(),(int)getgid(),(int)geteuid(),(int)getegid()));
+
+ /* Output the build options to the debug log */
+ build_options(False);
+
+ if (sizeof(uint16) < 2 || sizeof(uint32) < 4) {
+ DEBUG(0,("ERROR: Samba is not configured correctly for the word size on your machine\n"));
+ exit(1);
+ }
+
+ if (!lp_load_initial_only(get_dyn_CONFIGFILE())) {
+ DEBUG(0, ("error opening config file\n"));
+ exit(1);
+ }
+
+ if (smbd_messaging_context() == NULL)
+ exit(1);
+
+ if (!reload_services(False))
+ return(-1);
+
+ init_structs();
+
+#ifdef WITH_PROFILE
+ if (!profile_setup(smbd_messaging_context(), False)) {
+ DEBUG(0,("ERROR: failed to setup profiling\n"));
+ return -1;
+ }
+ if (profile_level != NULL) {
+ int pl = atoi(profile_level);
+ struct server_id src;
+
+ DEBUG(1, ("setting profiling level: %s\n",profile_level));
+ src.pid = getpid();
+ set_profile_level(pl, src);
+ }
+#endif
+
+ DEBUG(3,( "loaded services\n"));
+
+ if (!is_daemon && !is_a_socket(0)) {
+ if (!interactive)
+ DEBUG(0,("standard input is not a socket, assuming -D option\n"));
+
+ /*
+ * Setting is_daemon here prevents us from eventually calling
+ * the open_sockets_inetd()
+ */
+
+ is_daemon = True;
+ }
+
+ if (is_daemon && !interactive) {
+ DEBUG( 3, ( "Becoming a daemon.\n" ) );
+ become_daemon(Fork, no_process_group);
+ }
+
+#if HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (interactive && !no_process_group)
+ setpgid( (pid_t)0, (pid_t)0);
+#endif
+
+ if (!directory_exist(lp_lockdir(), NULL))
+ mkdir(lp_lockdir(), 0755);
+
+ if (is_daemon)
+ pidfile_create("smbd");
+
+ if (!reinit_after_fork(smbd_messaging_context(), false)) {
+ DEBUG(0,("reinit_after_fork() failed\n"));
+ exit(1);
+ }
+
+ /* Setup all the TDB's - including CLEAR_IF_FIRST tdb's. */
+
+ if (smbd_memcache() == NULL) {
+ exit(1);
+ }
+
+ memcache_set_global(smbd_memcache());
+
+ /* Initialise the password backed before the global_sam_sid
+ to ensure that we fetch from ldap before we make a domain sid up */
+
+ if(!initialize_password_db(False, smbd_event_context()))
+ exit(1);
+
+ if (!secrets_init()) {
+ DEBUG(0, ("ERROR: smbd can not open secrets.tdb\n"));
+ exit(1);
+ }
+
+ if(!get_global_sam_sid()) {
+ DEBUG(0,("ERROR: Samba cannot create a SAM SID.\n"));
+ exit(1);
+ }
+
+ if (!session_init())
+ exit(1);
+
+ if (!connections_init(True))
+ exit(1);
+
+ if (!locking_init())
+ exit(1);
+
+ namecache_enable();
+
+ if (!W_ERROR_IS_OK(registry_init_full()))
+ exit(1);
+
+#if 0
+ if (!init_svcctl_db())
+ exit(1);
+#endif
+
+ if (!print_backend_init(smbd_messaging_context()))
+ exit(1);
+
+ if (!init_guest_info()) {
+ DEBUG(0,("ERROR: failed to setup guest info.\n"));
+ return -1;
+ }
+
+ /* only start the background queue daemon if we are
+ running as a daemon -- bad things will happen if
+ smbd is launched via inetd and we fork a copy of
+ ourselves here */
+
+ if (is_daemon && !interactive
+ && lp_parm_bool(-1, "smbd", "backgroundqueue", true)) {
+ start_background_queue();
+ }
+
+ if (!open_sockets_smbd(is_daemon, interactive, ports))
+ exit(1);
+
+ /*
+ * everything after this point is run after the fork()
+ */
+
+ static_init_rpc;
+
+ init_modules();
+
+ /* Possibly reload the services file. Only worth doing in
+ * daemon mode. In inetd mode, we know we only just loaded this.
+ */
+ if (is_daemon) {
+ reload_services(True);
+ }
+
+ if (!init_account_policy()) {
+ DEBUG(0,("Could not open account policy tdb.\n"));
+ exit(1);
+ }
+
+ if (*lp_rootdir()) {
+ if (sys_chroot(lp_rootdir()) == 0)
+ DEBUG(2,("Changed root to %s\n", lp_rootdir()));
+ }
+
+ /* Setup oplocks */
+ if (!init_oplocks(smbd_messaging_context()))
+ exit(1);
+
+ /* Setup aio signal handler. */
+ initialize_async_io_handler();
+
+ /* register our message handlers */
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_FORCE_TDIS, msg_force_tdis);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_RELEASE_IP, msg_release_ip);
+ messaging_register(smbd_messaging_context(), NULL,
+ MSG_SMB_CLOSE_FILE, msg_close_file);
+
+ if ((lp_keepalive() != 0)
+ && !(event_add_idle(smbd_event_context(), NULL,
+ timeval_set(lp_keepalive(), 0),
+ "keepalive", keepalive_fn,
+ NULL))) {
+ DEBUG(0, ("Could not add keepalive event\n"));
+ exit(1);
+ }
+
+ if (!(event_add_idle(smbd_event_context(), NULL,
+ timeval_set(IDLE_CLOSED_TIMEOUT, 0),
+ "deadtime", deadtime_fn, NULL))) {
+ DEBUG(0, ("Could not add deadtime event\n"));
+ exit(1);
+ }
+
+#ifdef CLUSTER_SUPPORT
+
+ if (lp_clustering()) {
+ /*
+ * We need to tell ctdb about our client's TCP
+ * connection, so that for failover ctdbd can send
+ * tickle acks, triggering a reconnection by the
+ * client.
+ */
+
+ struct sockaddr_in srv, clnt;
+
+ if (client_get_tcp_info(&srv, &clnt) == 0) {
+
+ NTSTATUS status;
+
+ status = ctdbd_register_ips(
+ messaging_ctdbd_connection(),
+ &srv, &clnt, release_ip, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("ctdbd_register_ips failed: %s\n",
+ nt_errstr(status)));
+ }
+ } else
+ {
+ DEBUG(0,("Unable to get tcp info for "
+ "CTDB_CONTROL_TCP_CLIENT: %s\n",
+ strerror(errno)));
+ }
+ }
+
+#endif
+
+ TALLOC_FREE(frame);
+
+ smbd_process();
+
+ namecache_shutdown();
+
+ exit_server_cleanly(NULL);
+ return(0);
+}
diff --git a/source3/smbd/service.c b/source3/smbd/service.c
new file mode 100644
index 0000000000..0b851f1e48
--- /dev/null
+++ b/source3/smbd/service.c
@@ -0,0 +1,1353 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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"
+
+extern userdom_struct current_user_info;
+
+static bool canonicalize_connect_path(connection_struct *conn)
+{
+#ifdef REALPATH_TAKES_NULL
+ bool ret;
+ char *resolved_name = SMB_VFS_REALPATH(conn,conn->connectpath,NULL);
+ if (!resolved_name) {
+ return false;
+ }
+ ret = set_conn_connectpath(conn,resolved_name);
+ SAFE_FREE(resolved_name);
+ return ret;
+#else
+ char resolved_name_buf[PATH_MAX+1];
+ char *resolved_name = SMB_VFS_REALPATH(conn,conn->connectpath,resolved_name_buf);
+ if (!resolved_name) {
+ return false;
+ }
+ return set_conn_connectpath(conn,resolved_name);
+#endif /* REALPATH_TAKES_NULL */
+}
+
+/****************************************************************************
+ Ensure when setting connectpath it is a canonicalized (no ./ // or ../)
+ absolute path stating in / and not ending in /.
+ Observent people will notice a similarity between this and check_path_syntax :-).
+****************************************************************************/
+
+bool set_conn_connectpath(connection_struct *conn, const char *connectpath)
+{
+ char *destname;
+ char *d;
+ const char *s = connectpath;
+ bool start_of_name_component = true;
+
+ destname = SMB_STRDUP(connectpath);
+ if (!destname) {
+ return false;
+ }
+ d = destname;
+
+ *d++ = '/'; /* Always start with root. */
+
+ while (*s) {
+ if (*s == '/') {
+ /* Eat multiple '/' */
+ while (*s == '/') {
+ s++;
+ }
+ if ((d > destname + 1) && (*s != '\0')) {
+ *d++ = '/';
+ }
+ start_of_name_component = True;
+ continue;
+ }
+
+ if (start_of_name_component) {
+ if ((s[0] == '.') && (s[1] == '.') && (s[2] == '/' || s[2] == '\0')) {
+ /* Uh oh - "/../" or "/..\0" ! */
+
+ /* Go past the ../ or .. */
+ if (s[2] == '/') {
+ s += 3;
+ } else {
+ s += 2; /* Go past the .. */
+ }
+
+ /* If we just added a '/' - delete it */
+ if ((d > destname) && (*(d-1) == '/')) {
+ *(d-1) = '\0';
+ d--;
+ }
+
+ /* Are we at the start ? Can't go back further if so. */
+ if (d <= destname) {
+ *d++ = '/'; /* Can't delete root */
+ continue;
+ }
+ /* Go back one level... */
+ /* Decrement d first as d points to the *next* char to write into. */
+ for (d--; d > destname; d--) {
+ if (*d == '/') {
+ break;
+ }
+ }
+ /* We're still at the start of a name component, just the previous one. */
+ continue;
+ } else if ((s[0] == '.') && ((s[1] == '\0') || s[1] == '/')) {
+ /* Component of pathname can't be "." only - skip the '.' . */
+ if (s[1] == '/') {
+ s += 2;
+ } else {
+ s++;
+ }
+ continue;
+ }
+ }
+
+ if (!(*s & 0x80)) {
+ *d++ = *s++;
+ } else {
+ size_t siz;
+ /* Get the size of the next MB character. */
+ next_codepoint(s,&siz);
+ switch(siz) {
+ case 5:
+ *d++ = *s++;
+ /*fall through*/
+ case 4:
+ *d++ = *s++;
+ /*fall through*/
+ case 3:
+ *d++ = *s++;
+ /*fall through*/
+ case 2:
+ *d++ = *s++;
+ /*fall through*/
+ case 1:
+ *d++ = *s++;
+ break;
+ default:
+ break;
+ }
+ }
+ start_of_name_component = false;
+ }
+ *d = '\0';
+
+ /* And must not end in '/' */
+ if (d > destname + 1 && (*(d-1) == '/')) {
+ *(d-1) = '\0';
+ }
+
+ DEBUG(10,("set_conn_connectpath: service %s, connectpath = %s\n",
+ lp_servicename(SNUM(conn)), destname ));
+
+ string_set(&conn->connectpath, destname);
+ SAFE_FREE(destname);
+ return true;
+}
+
+/****************************************************************************
+ Load parameters specific to a connection/service.
+****************************************************************************/
+
+bool set_current_service(connection_struct *conn, uint16 flags, bool do_chdir)
+{
+ static connection_struct *last_conn;
+ static uint16 last_flags;
+ int snum;
+
+ if (!conn) {
+ last_conn = NULL;
+ return(False);
+ }
+
+ conn->lastused_count++;
+
+ snum = SNUM(conn);
+
+ if (do_chdir &&
+ vfs_ChDir(conn,conn->connectpath) != 0 &&
+ vfs_ChDir(conn,conn->origpath) != 0) {
+ DEBUG(0,("chdir (%s) failed\n",
+ conn->connectpath));
+ return(False);
+ }
+
+ if ((conn == last_conn) && (last_flags == flags)) {
+ return(True);
+ }
+
+ last_conn = conn;
+ last_flags = flags;
+
+ /* Obey the client case sensitivity requests - only for clients that support it. */
+ switch (lp_casesensitive(snum)) {
+ case Auto:
+ {
+ /* We need this uglyness due to DOS/Win9x clients that lie about case insensitivity. */
+ enum remote_arch_types ra_type = get_remote_arch();
+ if ((ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ /* Client can't support per-packet case sensitive pathnames. */
+ conn->case_sensitive = False;
+ } else {
+ conn->case_sensitive = !(flags & FLAG_CASELESS_PATHNAMES);
+ }
+ }
+ break;
+ case True:
+ conn->case_sensitive = True;
+ break;
+ default:
+ conn->case_sensitive = False;
+ break;
+ }
+ return(True);
+}
+
+static int load_registry_service(const char *servicename)
+{
+ struct registry_key *key;
+ char *path;
+ WERROR err;
+
+ uint32 i;
+ char *value_name;
+ struct registry_value *value;
+
+ int res = -1;
+
+ if (!lp_registry_shares()) {
+ return -1;
+ }
+
+ if (strequal(servicename, GLOBAL_NAME)) {
+ return -2;
+ }
+
+ if (asprintf(&path, "%s\\%s", KEY_SMBCONF, servicename) == -1) {
+ return -1;
+ }
+
+ err = reg_open_path(NULL, path, REG_KEY_READ, get_root_nt_token(),
+ &key);
+ SAFE_FREE(path);
+
+ if (!W_ERROR_IS_OK(err)) {
+ return -1;
+ }
+
+ res = lp_add_service(servicename, -1);
+ if (res == -1) {
+ goto error;
+ }
+
+ for (i=0;
+ W_ERROR_IS_OK(reg_enumvalue(key, key, i, &value_name, &value));
+ i++) {
+ switch (value->type) {
+ case REG_DWORD: {
+ char *tmp;
+ if (asprintf(&tmp, "%d", value->v.dword) == -1) {
+ continue;
+ }
+ lp_do_parameter(res, value_name, tmp);
+ SAFE_FREE(tmp);
+ break;
+ }
+ case REG_SZ: {
+ lp_do_parameter(res, value_name, value->v.sz.str);
+ break;
+ }
+ default:
+ /* Ignore all the rest */
+ break;
+ }
+
+ TALLOC_FREE(value_name);
+ TALLOC_FREE(value);
+ }
+
+ error:
+
+ TALLOC_FREE(key);
+ return res;
+}
+
+void load_registry_shares(void)
+{
+ struct registry_key *key;
+ char *name;
+ WERROR err;
+ int i;
+
+ DEBUG(8, ("load_registry_shares()\n"));
+ if (!lp_registry_shares()) {
+ return;
+ }
+
+ err = reg_open_path(NULL, KEY_SMBCONF, REG_KEY_READ,
+ get_root_nt_token(), &key);
+ if (!(W_ERROR_IS_OK(err))) {
+ return;
+ }
+
+ for (i=0; W_ERROR_IS_OK(reg_enumkey(key, key, i, &name, NULL)); i++) {
+ load_registry_service(name);
+ TALLOC_FREE(name);
+ }
+
+ TALLOC_FREE(key);
+ return;
+}
+
+/****************************************************************************
+ Add a home service. Returns the new service number or -1 if fail.
+****************************************************************************/
+
+int add_home_service(const char *service, const char *username, const char *homedir)
+{
+ int iHomeService;
+
+ if (!service || !homedir)
+ return -1;
+
+ if ((iHomeService = lp_servicenumber(HOMES_NAME)) < 0) {
+ if ((iHomeService = load_registry_service(HOMES_NAME)) < 0) {
+ return -1;
+ }
+ }
+
+ /*
+ * If this is a winbindd provided username, remove
+ * the domain component before adding the service.
+ * Log a warning if the "path=" parameter does not
+ * include any macros.
+ */
+
+ {
+ const char *p = strchr(service,*lp_winbind_separator());
+
+ /* We only want the 'user' part of the string */
+ if (p) {
+ service = p + 1;
+ }
+ }
+
+ if (!lp_add_home(service, iHomeService, username, homedir)) {
+ return -1;
+ }
+
+ return lp_servicenumber(service);
+
+}
+
+/**
+ * Find a service entry.
+ *
+ * @param service is modified (to canonical form??)
+ **/
+
+int find_service(fstring service)
+{
+ int iService;
+
+ all_string_sub(service,"\\","/",0);
+
+ iService = lp_servicenumber(service);
+
+ /* now handle the special case of a home directory */
+ if (iService < 0) {
+ char *phome_dir = get_user_home_dir(talloc_tos(), service);
+
+ if(!phome_dir) {
+ /*
+ * Try mapping the servicename, it may
+ * be a Windows to unix mapped user name.
+ */
+ if(map_username(service))
+ phome_dir = get_user_home_dir(
+ talloc_tos(), service);
+ }
+
+ DEBUG(3,("checking for home directory %s gave %s\n",service,
+ phome_dir?phome_dir:"(NULL)"));
+
+ iService = add_home_service(service,service /* 'username' */, phome_dir);
+ }
+
+ /* If we still don't have a service, attempt to add it as a printer. */
+ if (iService < 0) {
+ int iPrinterService;
+
+ if ((iPrinterService = lp_servicenumber(PRINTERS_NAME)) < 0) {
+ iPrinterService = load_registry_service(PRINTERS_NAME);
+ }
+ if (iPrinterService) {
+ DEBUG(3,("checking whether %s is a valid printer name...\n", service));
+ if (pcap_printername_ok(service)) {
+ DEBUG(3,("%s is a valid printer name\n", service));
+ DEBUG(3,("adding %s as a printer service\n", service));
+ lp_add_printer(service, iPrinterService);
+ iService = lp_servicenumber(service);
+ if (iService < 0) {
+ DEBUG(0,("failed to add %s as a printer service!\n", service));
+ }
+ } else {
+ DEBUG(3,("%s is not a valid printer name\n", service));
+ }
+ }
+ }
+
+ /* Check for default vfs service? Unsure whether to implement this */
+ if (iService < 0) {
+ }
+
+ if (iService < 0) {
+ iService = load_registry_service(service);
+ }
+
+ /* Is it a usershare service ? */
+ if (iService < 0 && *lp_usershare_path()) {
+ /* Ensure the name is canonicalized. */
+ strlower_m(service);
+ iService = load_usershare_service(service);
+ }
+
+ /* just possibly it's a default service? */
+ if (iService < 0) {
+ char *pdefservice = lp_defaultservice();
+ if (pdefservice && *pdefservice && !strequal(pdefservice,service) && !strstr_m(service,"..")) {
+ /*
+ * We need to do a local copy here as lp_defaultservice()
+ * returns one of the rotating lp_string buffers that
+ * could get overwritten by the recursive find_service() call
+ * below. Fix from Josef Hinteregger <joehtg@joehtg.co.at>.
+ */
+ char *defservice = SMB_STRDUP(pdefservice);
+
+ if (!defservice) {
+ goto fail;
+ }
+
+ /* Disallow anything except explicit share names. */
+ if (strequal(defservice,HOMES_NAME) ||
+ strequal(defservice, PRINTERS_NAME) ||
+ strequal(defservice, "IPC$")) {
+ SAFE_FREE(defservice);
+ goto fail;
+ }
+
+ iService = find_service(defservice);
+ if (iService >= 0) {
+ all_string_sub(service, "_","/",0);
+ iService = lp_add_service(service, iService);
+ }
+ SAFE_FREE(defservice);
+ }
+ }
+
+ if (iService >= 0) {
+ if (!VALID_SNUM(iService)) {
+ DEBUG(0,("Invalid snum %d for %s\n",iService, service));
+ iService = -1;
+ }
+ }
+
+ fail:
+
+ if (iService < 0)
+ DEBUG(3,("find_service() failed to find service %s\n", service));
+
+ return (iService);
+}
+
+
+/****************************************************************************
+ do some basic sainity checks on the share.
+ This function modifies dev, ecode.
+****************************************************************************/
+
+static NTSTATUS share_sanity_checks(int snum, fstring dev)
+{
+
+ if (!lp_snum_ok(snum) ||
+ !check_access(smbd_server_fd(),
+ lp_hostsallow(snum), lp_hostsdeny(snum))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dev[0] == '?' || !dev[0]) {
+ if (lp_print_ok(snum)) {
+ fstrcpy(dev,"LPT1:");
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ fstrcpy(dev, "IPC");
+ } else {
+ fstrcpy(dev,"A:");
+ }
+ }
+
+ strupper_m(dev);
+
+ if (lp_print_ok(snum)) {
+ if (!strequal(dev, "LPT1:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ if (!strequal(dev, "IPC")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (!strequal(dev, "A:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+
+ /* Behave as a printer if we are supposed to */
+ if (lp_print_ok(snum) && (strcmp(dev, "A:") == 0)) {
+ fstrcpy(dev, "LPT1:");
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Go through lookup_name etc to find the force'd group.
+ *
+ * Create a new token from src_token, replacing the primary group sid with the
+ * one found.
+ */
+
+static NTSTATUS find_forced_group(bool force_user,
+ int snum, const char *username,
+ DOM_SID *pgroup_sid,
+ gid_t *pgid)
+{
+ NTSTATUS result = NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_CTX *frame = talloc_stackframe();
+ DOM_SID group_sid;
+ enum lsa_SidType type;
+ char *groupname;
+ bool user_must_be_member = False;
+ gid_t gid;
+
+ groupname = talloc_strdup(talloc_tos(), lp_force_group(snum));
+ if (groupname == NULL) {
+ DEBUG(1, ("talloc_strdup failed\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (groupname[0] == '+') {
+ user_must_be_member = True;
+ groupname += 1;
+ }
+
+ groupname = talloc_string_sub(talloc_tos(), groupname,
+ "%S", lp_servicename(snum));
+ if (groupname == NULL) {
+ DEBUG(1, ("talloc_string_sub failed\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!lookup_name_smbconf(talloc_tos(), groupname,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &group_sid, &type)) {
+ DEBUG(10, ("lookup_name_smbconf(%s) failed\n",
+ groupname));
+ goto done;
+ }
+
+ if ((type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DEBUG(10, ("%s is a %s, not a group\n", groupname,
+ sid_type_lookup(type)));
+ goto done;
+ }
+
+ if (!sid_to_gid(&group_sid, &gid)) {
+ DEBUG(10, ("sid_to_gid(%s) for %s failed\n",
+ sid_string_dbg(&group_sid), groupname));
+ goto done;
+ }
+
+ /*
+ * If the user has been forced and the forced group starts with a '+',
+ * then we only set the group to be the forced group if the forced
+ * user is a member of that group. Otherwise, the meaning of the '+'
+ * would be ignored.
+ */
+
+ if (force_user && user_must_be_member) {
+ if (user_in_group_sid(username, &group_sid)) {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DEBUG(3,("Forced group %s for member %s\n",
+ groupname, username));
+ } else {
+ DEBUG(0,("find_forced_group: forced user %s is not a member "
+ "of forced group %s. Disallowing access.\n",
+ username, groupname ));
+ result = NT_STATUS_MEMBER_NOT_IN_GROUP;
+ goto done;
+ }
+ } else {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DEBUG(3,("Forced group %s\n", groupname));
+ }
+
+ result = NT_STATUS_OK;
+ done:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+/****************************************************************************
+ Create an auth_serversupplied_info structure for a connection_struct
+****************************************************************************/
+
+static NTSTATUS create_connection_server_info(TALLOC_CTX *mem_ctx, int snum,
+ struct auth_serversupplied_info *vuid_serverinfo,
+ DATA_BLOB password,
+ struct auth_serversupplied_info **presult)
+{
+ if (lp_guest_only(snum)) {
+ return make_server_info_guest(mem_ctx, presult);
+ }
+
+ if (vuid_serverinfo != NULL) {
+
+ struct auth_serversupplied_info *result;
+
+ /*
+ * This is the normal security != share case where we have a
+ * valid vuid from the session setup. */
+
+ if (vuid_serverinfo->guest) {
+ if (!lp_guest_ok(snum)) {
+ DEBUG(2, ("guest user (from session setup) "
+ "not permitted to access this share "
+ "(%s)\n", lp_servicename(snum)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ if (!user_ok_token(vuid_serverinfo->unix_name,
+ pdb_get_domain(vuid_serverinfo->sam_account),
+ vuid_serverinfo->ptok, snum)) {
+ DEBUG(2, ("user '%s' (from session setup) not "
+ "permitted to access this share "
+ "(%s)\n",
+ vuid_serverinfo->unix_name,
+ lp_servicename(snum)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ result = copy_serverinfo(mem_ctx, vuid_serverinfo);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+ }
+
+ if (lp_security() == SEC_SHARE) {
+
+ fstring user;
+ bool guest;
+
+ /* add the sharename as a possible user name if we
+ are in share mode security */
+
+ add_session_user(lp_servicename(snum));
+
+ /* shall we let them in? */
+
+ if (!authorise_login(snum,user,password,&guest)) {
+ DEBUG( 2, ( "Invalid username/password for [%s]\n",
+ lp_servicename(snum)) );
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ return make_serverinfo_from_username(mem_ctx, user, guest,
+ presult);
+ }
+
+ DEBUG(0, ("invalid VUID (vuser) but not in security=share\n"));
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+
+/****************************************************************************
+ Make a connection, given the snum to connect to, and the vuser of the
+ connecting user if appropriate.
+****************************************************************************/
+
+static connection_struct *make_connection_snum(int snum, user_struct *vuser,
+ DATA_BLOB password,
+ const char *pdev,
+ NTSTATUS *pstatus)
+{
+ connection_struct *conn;
+ SMB_STRUCT_STAT st;
+ fstring dev;
+ int ret;
+ char addr[INET6_ADDRSTRLEN];
+ bool on_err_call_dis_hook = false;
+ NTSTATUS status;
+
+ fstrcpy(dev, pdev);
+ SET_STAT_INVALID(st);
+
+ if (NT_STATUS_IS_ERR(*pstatus = share_sanity_checks(snum, dev))) {
+ return NULL;
+ }
+
+ conn = conn_new();
+ if (!conn) {
+ DEBUG(0,("Couldn't find free connection.\n"));
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ conn->params->service = snum;
+
+ status = create_connection_server_info(
+ conn, snum, vuser ? vuser->server_info : NULL, password,
+ &conn->server_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("create_connection_server_info failed: %s\n",
+ nt_errstr(status)));
+ *pstatus = status;
+ conn_free(conn);
+ return NULL;
+ }
+
+ if ((lp_guest_only(snum)) || (lp_security() == SEC_SHARE)) {
+ conn->force_user = true;
+ }
+
+ add_session_user(conn->server_info->unix_name);
+
+ safe_strcpy(conn->client_address,
+ client_addr(get_client_fd(),addr,sizeof(addr)),
+ sizeof(conn->client_address)-1);
+ conn->num_files_open = 0;
+ conn->lastused = conn->lastused_count = time(NULL);
+ conn->used = True;
+ conn->printer = (strncmp(dev,"LPT",3) == 0);
+ conn->ipc = ( (strncmp(dev,"IPC",3) == 0) ||
+ ( lp_enable_asu_support() && strequal(dev,"ADMIN$")) );
+ conn->dirptr = NULL;
+
+ /* Case options for the share. */
+ if (lp_casesensitive(snum) == Auto) {
+ /* We will be setting this per packet. Set to be case
+ * insensitive for now. */
+ conn->case_sensitive = False;
+ } else {
+ conn->case_sensitive = (bool)lp_casesensitive(snum);
+ }
+
+ conn->case_preserve = lp_preservecase(snum);
+ conn->short_case_preserve = lp_shortpreservecase(snum);
+
+ conn->encrypt_level = lp_smb_encrypt(snum);
+
+ conn->veto_list = NULL;
+ conn->hide_list = NULL;
+ conn->veto_oplock_list = NULL;
+ conn->aio_write_behind_list = NULL;
+ string_set(&conn->dirpath,"");
+
+ conn->read_only = lp_readonly(SNUM(conn));
+ conn->admin_user = False;
+
+ if (*lp_force_user(snum)) {
+
+ /*
+ * Replace conn->server_info with a completely faked up one
+ * from the username we are forced into :-)
+ */
+
+ char *fuser;
+ struct auth_serversupplied_info *forced_serverinfo;
+
+ fuser = talloc_string_sub(conn, lp_force_user(snum), "%S",
+ lp_servicename(snum));
+ if (fuser == NULL) {
+ conn_free(conn);
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ status = make_serverinfo_from_username(
+ conn, fuser, conn->server_info->guest,
+ &forced_serverinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn_free(conn);
+ *pstatus = status;
+ return NULL;
+ }
+
+ TALLOC_FREE(conn->server_info);
+ conn->server_info = forced_serverinfo;
+
+ conn->force_user = True;
+ DEBUG(3,("Forced user %s\n", fuser));
+ }
+
+ /*
+ * If force group is true, then override
+ * any groupid stored for the connecting user.
+ */
+
+ if (*lp_force_group(snum)) {
+
+ status = find_forced_group(
+ conn->force_user, snum, conn->server_info->unix_name,
+ &conn->server_info->ptok->user_sids[1],
+ &conn->server_info->utok.gid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ conn_free(conn);
+ *pstatus = status;
+ return NULL;
+ }
+ }
+
+ conn->vuid = (vuser != NULL) ? vuser->vuid : UID_FIELD_INVALID;
+
+ {
+ char *s = talloc_sub_advanced(talloc_tos(),
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ lp_pathname(snum));
+ if (!s) {
+ conn_free(conn);
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if (!set_conn_connectpath(conn,s)) {
+ TALLOC_FREE(s);
+ conn_free(conn);
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+ DEBUG(3,("Connect path is '%s' for service [%s]\n",s,
+ lp_servicename(snum)));
+ TALLOC_FREE(s);
+ }
+
+ /*
+ * New code to check if there's a share security descripter
+ * added from NT server manager. This is done after the
+ * smb.conf checks are done as we need a uid and token. JRA.
+ *
+ */
+
+ {
+ bool can_write = False;
+
+ can_write = share_access_check(conn->server_info->ptok,
+ lp_servicename(snum),
+ FILE_WRITE_DATA);
+
+ if (!can_write) {
+ if (!share_access_check(conn->server_info->ptok,
+ lp_servicename(snum),
+ FILE_READ_DATA)) {
+ /* No access, read or write. */
+ DEBUG(0,("make_connection: connection to %s "
+ "denied due to security "
+ "descriptor.\n",
+ lp_servicename(snum)));
+ conn_free(conn);
+ *pstatus = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ } else {
+ conn->read_only = True;
+ }
+ }
+ }
+ /* Initialise VFS function pointers */
+
+ if (!smbd_vfs_init(conn)) {
+ DEBUG(0, ("vfs_init failed for service %s\n",
+ lp_servicename(snum)));
+ conn_free(conn);
+ *pstatus = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ /*
+ * If widelinks are disallowed we need to canonicalise the connect
+ * path here to ensure we don't have any symlinks in the
+ * connectpath. We will be checking all paths on this connection are
+ * below this directory. We must do this after the VFS init as we
+ * depend on the realpath() pointer in the vfs table. JRA.
+ */
+ if (!lp_widelinks(snum)) {
+ if (!canonicalize_connect_path(conn)) {
+ DEBUG(0, ("canonicalize_connect_path failed "
+ "for service %s, path %s\n",
+ lp_servicename(snum),
+ conn->connectpath));
+ conn_free(conn);
+ *pstatus = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+ }
+
+ if ((!conn->printer) && (!conn->ipc)) {
+ conn->notify_ctx = notify_init(conn, server_id_self(),
+ smbd_messaging_context(),
+ smbd_event_context(),
+ conn);
+ }
+
+/* ROOT Activities: */
+ /*
+ * Enforce the max connections parameter.
+ */
+
+ if ((lp_max_connections(snum) > 0)
+ && (count_current_connections(lp_servicename(SNUM(conn)), True) >=
+ lp_max_connections(snum))) {
+
+ DEBUG(1, ("Max connections (%d) exceeded for %s\n",
+ lp_max_connections(snum), lp_servicename(snum)));
+ conn_free(conn);
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ /*
+ * Get us an entry in the connections db
+ */
+ if (!claim_connection(conn, lp_servicename(snum), 0)) {
+ DEBUG(1, ("Could not store connections entry\n"));
+ conn_free(conn);
+ *pstatus = NT_STATUS_INTERNAL_DB_ERROR;
+ return NULL;
+ }
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+ /* execute any "root preexec = " line */
+ if (*lp_rootpreexec(snum)) {
+ char *cmd = talloc_sub_advanced(talloc_tos(),
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ lp_rootpreexec(snum));
+ DEBUG(5,("cmd=%s\n",cmd));
+ ret = smbrun(cmd,NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_rootpreexec_close(snum)) {
+ DEBUG(1,("root preexec gave %d - failing "
+ "connection\n", ret));
+ yield_connection(conn, lp_servicename(snum));
+ conn_free(conn);
+ *pstatus = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+ }
+
+/* USER Activites: */
+ if (!change_to_user(conn, conn->vuid)) {
+ /* No point continuing if they fail the basic checks */
+ DEBUG(0,("Can't become connected user!\n"));
+ yield_connection(conn, lp_servicename(snum));
+ conn_free(conn);
+ *pstatus = NT_STATUS_LOGON_FAILURE;
+ return NULL;
+ }
+
+ /* Remember that a different vuid can connect later without these
+ * checks... */
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+
+ /* execute any "preexec = " line */
+ if (*lp_preexec(snum)) {
+ char *cmd = talloc_sub_advanced(talloc_tos(),
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ lp_preexec(snum));
+ ret = smbrun(cmd,NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_preexec_close(snum)) {
+ DEBUG(1,("preexec gave %d - failing connection\n",
+ ret));
+ *pstatus = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ }
+
+#ifdef WITH_FAKE_KASERVER
+ if (lp_afs_share(snum)) {
+ afs_login(conn);
+ }
+#endif
+
+ /* Add veto/hide lists */
+ if (!IS_IPC(conn) && !IS_PRINT(conn)) {
+ set_namearray( &conn->veto_list, lp_veto_files(snum));
+ set_namearray( &conn->hide_list, lp_hide_files(snum));
+ set_namearray( &conn->veto_oplock_list, lp_veto_oplocks(snum));
+ set_namearray( &conn->aio_write_behind_list,
+ lp_aio_write_behind(snum));
+ }
+
+ /* Invoke VFS make connection hook - do this before the VFS_STAT call
+ to allow any filesystems needing user credentials to initialize
+ themselves. */
+
+ if (SMB_VFS_CONNECT(conn, lp_servicename(snum),
+ conn->server_info->unix_name) < 0) {
+ DEBUG(0,("make_connection: VFS make connection failed!\n"));
+ *pstatus = NT_STATUS_UNSUCCESSFUL;
+ goto err_root_exit;
+ }
+
+ /* Any error exit after here needs to call the disconnect hook. */
+ on_err_call_dis_hook = true;
+
+ /* win2000 does not check the permissions on the directory
+ during the tree connect, instead relying on permission
+ check during individual operations. To match this behaviour
+ I have disabled this chdir check (tridge) */
+ /* the alternative is just to check the directory exists */
+ if ((ret = SMB_VFS_STAT(conn, conn->connectpath, &st)) != 0 ||
+ !S_ISDIR(st.st_mode)) {
+ if (ret == 0 && !S_ISDIR(st.st_mode)) {
+ DEBUG(0,("'%s' is not a directory, when connecting to "
+ "[%s]\n", conn->connectpath,
+ lp_servicename(snum)));
+ } else {
+ DEBUG(0,("'%s' does not exist or permission denied "
+ "when connecting to [%s] Error was %s\n",
+ conn->connectpath, lp_servicename(snum),
+ strerror(errno) ));
+ }
+ *pstatus = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+
+ string_set(&conn->origpath,conn->connectpath);
+
+#if SOFTLINK_OPTIMISATION
+ /* resolve any soft links early if possible */
+ if (vfs_ChDir(conn,conn->connectpath) == 0) {
+ TALLOC_CTX *ctx = talloc_tos();
+ char *s = vfs_GetWd(ctx,s);
+ if (!s) {
+ *status = map_nt_error_from_unix(errno);
+ goto err_root_exit;
+ }
+ if (!set_conn_connectpath(conn,s)) {
+ *status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+ vfs_ChDir(conn,conn->connectpath);
+ }
+#endif
+
+ /* Figure out the characteristics of the underlying filesystem. This
+ * assumes that all the filesystem mounted withing a share path have
+ * the same characteristics, which is likely but not guaranteed.
+ */
+
+ conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn);
+
+ /*
+ * Print out the 'connected as' stuff here as we need
+ * to know the effective uid and gid we will be using
+ * (at least initially).
+ */
+
+ if( DEBUGLVL( IS_IPC(conn) ? 3 : 1 ) ) {
+ dbgtext( "%s (%s) ", get_remote_machine_name(),
+ conn->client_address );
+ dbgtext( "%s", srv_is_signing_active() ? "signed " : "");
+ dbgtext( "connect to service %s ", lp_servicename(snum) );
+ dbgtext( "initially as user %s ",
+ conn->server_info->unix_name );
+ dbgtext( "(uid=%d, gid=%d) ", (int)geteuid(), (int)getegid() );
+ dbgtext( "(pid %d)\n", (int)sys_getpid() );
+ }
+
+ /* we've finished with the user stuff - go back to root */
+ change_to_root_user();
+ return(conn);
+
+ err_root_exit:
+
+ change_to_root_user();
+ if (on_err_call_dis_hook) {
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+ }
+ yield_connection(conn, lp_servicename(snum));
+ conn_free(conn);
+ return NULL;
+}
+
+/***************************************************************************************
+ Simple wrapper function for make_connection() to include a call to
+ vfs_chdir()
+ **************************************************************************************/
+
+connection_struct *make_connection_with_chdir(const char *service_in,
+ DATA_BLOB password,
+ const char *dev, uint16 vuid,
+ NTSTATUS *status)
+{
+ connection_struct *conn = NULL;
+
+ conn = make_connection(service_in, password, dev, vuid, status);
+
+ /*
+ * make_connection() does not change the directory for us any more
+ * so we have to do it as a separate step --jerry
+ */
+
+ if ( conn && vfs_ChDir(conn,conn->connectpath) != 0 ) {
+ DEBUG(0,("make_connection_with_chdir: Can't change "
+ "directory to %s for [print$] (%s)\n",
+ conn->connectpath,strerror(errno)));
+ yield_connection(conn, lp_servicename(SNUM(conn)));
+ conn_free(conn);
+ *status = NT_STATUS_UNSUCCESSFUL;
+ return NULL;
+ }
+
+ return conn;
+}
+
+/****************************************************************************
+ Make a connection to a service.
+ *
+ * @param service
+****************************************************************************/
+
+connection_struct *make_connection(const char *service_in, DATA_BLOB password,
+ const char *pdev, uint16 vuid,
+ NTSTATUS *status)
+{
+ uid_t euid;
+ user_struct *vuser = NULL;
+ fstring service;
+ fstring dev;
+ int snum = -1;
+ char addr[INET6_ADDRSTRLEN];
+
+ fstrcpy(dev, pdev);
+
+ /* This must ONLY BE CALLED AS ROOT. As it exits this function as
+ * root. */
+ if (!non_root_mode() && (euid = geteuid()) != 0) {
+ DEBUG(0,("make_connection: PANIC ERROR. Called as nonroot "
+ "(%u)\n", (unsigned int)euid ));
+ smb_panic("make_connection: PANIC ERROR. Called as nonroot\n");
+ }
+
+ if (conn_num_open() > 2047) {
+ *status = NT_STATUS_INSUFF_SERVER_RESOURCES;
+ return NULL;
+ }
+
+ if(lp_security() != SEC_SHARE) {
+ vuser = get_valid_user_struct(vuid);
+ if (!vuser) {
+ DEBUG(1,("make_connection: refusing to connect with "
+ "no session setup\n"));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+ }
+
+ /* Logic to try and connect to the correct [homes] share, preferably
+ without too many getpwnam() lookups. This is particulary nasty for
+ winbind usernames, where the share name isn't the same as unix
+ username.
+
+ The snum of the homes share is stored on the vuser at session setup
+ time.
+ */
+
+ if (strequal(service_in,HOMES_NAME)) {
+ if(lp_security() != SEC_SHARE) {
+ DATA_BLOB no_pw = data_blob_null;
+ if (vuser->homes_snum == -1) {
+ DEBUG(2, ("[homes] share not available for "
+ "this user because it was not found "
+ "or created at session setup "
+ "time\n"));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+ DEBUG(5, ("making a connection to [homes] service "
+ "created at session setup time\n"));
+ return make_connection_snum(vuser->homes_snum,
+ vuser, no_pw,
+ dev, status);
+ } else {
+ /* Security = share. Try with
+ * current_user_info.smb_name as the username. */
+ if (*current_user_info.smb_name) {
+ fstring unix_username;
+ fstrcpy(unix_username,
+ current_user_info.smb_name);
+ map_username(unix_username);
+ snum = find_service(unix_username);
+ }
+ if (snum != -1) {
+ DEBUG(5, ("making a connection to 'homes' "
+ "service %s based on "
+ "security=share\n", service_in));
+ return make_connection_snum(snum, NULL,
+ password,
+ dev, status);
+ }
+ }
+ } else if ((lp_security() != SEC_SHARE) && (vuser->homes_snum != -1)
+ && strequal(service_in,
+ lp_servicename(vuser->homes_snum))) {
+ DATA_BLOB no_pw = data_blob_null;
+ DEBUG(5, ("making a connection to 'homes' service [%s] "
+ "created at session setup time\n", service_in));
+ return make_connection_snum(vuser->homes_snum,
+ vuser, no_pw,
+ dev, status);
+ }
+
+ fstrcpy(service, service_in);
+
+ strlower_m(service);
+
+ snum = find_service(service);
+
+ if (snum < 0) {
+ if (strequal(service,"IPC$") ||
+ (lp_enable_asu_support() && strequal(service,"ADMIN$"))) {
+ DEBUG(3,("refusing IPC connection to %s\n", service));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+
+ DEBUG(0,("%s (%s) couldn't find service %s\n",
+ get_remote_machine_name(),
+ client_addr(get_client_fd(),addr,sizeof(addr)),
+ service));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ /* Handle non-Dfs clients attempting connections to msdfs proxy */
+ if (lp_host_msdfs() && (*lp_msdfs_proxy(snum) != '\0')) {
+ DEBUG(3, ("refusing connection to dfs proxy share '%s' "
+ "(pointing to %s)\n",
+ service, lp_msdfs_proxy(snum)));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ DEBUG(5, ("making a connection to 'normal' service %s\n", service));
+
+ return make_connection_snum(snum, vuser,
+ password,
+ dev, status);
+}
+
+/****************************************************************************
+ Close a cnum.
+****************************************************************************/
+
+void close_cnum(connection_struct *conn, uint16 vuid)
+{
+ if (IS_IPC(conn)) {
+ pipe_close_conn(conn);
+ } else {
+ file_close_conn(conn);
+ dptr_closecnum(conn);
+ }
+
+ change_to_root_user();
+
+ DEBUG(IS_IPC(conn)?3:1, ("%s (%s) closed connection to service %s\n",
+ get_remote_machine_name(),
+ conn->client_address,
+ lp_servicename(SNUM(conn))));
+
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+
+ yield_connection(conn, lp_servicename(SNUM(conn)));
+
+ /* make sure we leave the directory available for unmount */
+ vfs_ChDir(conn, "/");
+
+ /* execute any "postexec = " line */
+ if (*lp_postexec(SNUM(conn)) &&
+ change_to_user(conn, vuid)) {
+ char *cmd = talloc_sub_advanced(talloc_tos(),
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ lp_postexec(SNUM(conn)));
+ smbrun(cmd,NULL);
+ TALLOC_FREE(cmd);
+ change_to_root_user();
+ }
+
+ change_to_root_user();
+ /* execute any "root postexec = " line */
+ if (*lp_rootpostexec(SNUM(conn))) {
+ char *cmd = talloc_sub_advanced(talloc_tos(),
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name,
+ conn->connectpath,
+ conn->server_info->utok.gid,
+ conn->server_info->sanitized_username,
+ pdb_get_domain(conn->server_info->sam_account),
+ lp_rootpostexec(SNUM(conn)));
+ smbrun(cmd,NULL);
+ TALLOC_FREE(cmd);
+ }
+
+ conn_free(conn);
+}
diff --git a/source3/smbd/session.c b/source3/smbd/session.c
new file mode 100644
index 0000000000..3b431a19be
--- /dev/null
+++ b/source3/smbd/session.c
@@ -0,0 +1,329 @@
+/*
+ Unix SMB/CIFS implementation.
+ session handling for utmp and PAM
+
+ Copyright (C) tridge@samba.org 2001
+ Copyright (C) abartlet@samba.org 2001
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ 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/>.
+*/
+
+/* a "session" is claimed when we do a SessionSetupX operation
+ and is yielded when the corresponding vuid is destroyed.
+
+ sessions are used to populate utmp and PAM session structures
+*/
+
+#include "includes.h"
+
+/********************************************************************
+********************************************************************/
+
+static struct db_context *session_db_ctx(void)
+{
+ static struct db_context *ctx;
+
+ if (ctx)
+ return ctx;
+
+ ctx = db_open(NULL, lock_path("sessionid.tdb"), 0,
+ TDB_CLEAR_IF_FIRST|TDB_DEFAULT,
+ O_RDWR | O_CREAT, 0644);
+ return ctx;
+}
+
+bool session_init(void)
+{
+ if (session_db_ctx() == NULL) {
+ DEBUG(1,("session_init: failed to open sessionid tdb\n"));
+ return False;
+ }
+
+ return True;
+}
+
+/********************************************************************
+ called when a session is created
+********************************************************************/
+
+bool session_claim(user_struct *vuser)
+{
+ TDB_DATA key, data;
+ int i = 0;
+ struct sessionid sessionid;
+ struct server_id pid = procid_self();
+ fstring keystr;
+ const char * hostname;
+ struct db_context *ctx;
+ struct db_record *rec;
+ NTSTATUS status;
+ char addr[INET6_ADDRSTRLEN];
+
+ vuser->session_keystr = NULL;
+
+ /* don't register sessions for the guest user - its just too
+ expensive to go through pam session code for browsing etc */
+ if (vuser->server_info->guest) {
+ return True;
+ }
+
+ if (!(ctx = session_db_ctx())) {
+ return False;
+ }
+
+ ZERO_STRUCT(sessionid);
+
+ data.dptr = NULL;
+ data.dsize = 0;
+
+ if (lp_utmp()) {
+
+ for (i=1;i<MAX_SESSION_ID;i++) {
+
+ /*
+ * This is very inefficient and needs fixing -- vl
+ */
+
+ struct server_id sess_pid;
+
+ snprintf(keystr, sizeof(keystr), "ID/%d", i);
+ key = string_term_tdb_data(keystr);
+
+ rec = ctx->fetch_locked(ctx, NULL, key);
+
+ if (rec == NULL) {
+ DEBUG(1, ("Could not lock \"%s\"\n", keystr));
+ return False;
+ }
+
+ if (rec->value.dsize != sizeof(sessionid)) {
+ DEBUG(1, ("Re-using invalid record\n"));
+ break;
+ }
+
+ sess_pid = ((struct sessionid *)rec->value.dptr)->pid;
+
+ if (!process_exists(sess_pid)) {
+ DEBUG(5, ("%s has died -- re-using session\n",
+ procid_str_static(&sess_pid)));
+ break;
+ }
+
+ TALLOC_FREE(rec);
+ }
+
+ if (i == MAX_SESSION_ID) {
+ SMB_ASSERT(rec == NULL);
+ DEBUG(1,("session_claim: out of session IDs "
+ "(max is %d)\n", MAX_SESSION_ID));
+ return False;
+ }
+
+ snprintf(sessionid.id_str, sizeof(sessionid.id_str),
+ SESSION_UTMP_TEMPLATE, i);
+ } else
+ {
+ snprintf(keystr, sizeof(keystr), "ID/%s/%u",
+ procid_str_static(&pid), vuser->vuid);
+ key = string_term_tdb_data(keystr);
+
+ rec = ctx->fetch_locked(ctx, NULL, key);
+
+ if (rec == NULL) {
+ DEBUG(1, ("Could not lock \"%s\"\n", keystr));
+ return False;
+ }
+
+ snprintf(sessionid.id_str, sizeof(sessionid.id_str),
+ SESSION_TEMPLATE, (long unsigned int)sys_getpid(),
+ vuser->vuid);
+ }
+
+ SMB_ASSERT(rec != NULL);
+
+ /* If 'hostname lookup' == yes, then do the DNS lookup. This is
+ needed because utmp and PAM both expect DNS names
+
+ client_name() handles this case internally.
+ */
+
+ hostname = client_name(get_client_fd());
+ if (strcmp(hostname, "UNKNOWN") == 0) {
+ hostname = client_addr(get_client_fd(),addr,sizeof(addr));
+ }
+
+ fstrcpy(sessionid.username, vuser->server_info->unix_name);
+ fstrcpy(sessionid.hostname, hostname);
+ sessionid.id_num = i; /* Only valid for utmp sessions */
+ sessionid.pid = pid;
+ sessionid.uid = vuser->server_info->utok.uid;
+ sessionid.gid = vuser->server_info->utok.gid;
+ fstrcpy(sessionid.remote_machine, get_remote_machine_name());
+ fstrcpy(sessionid.ip_addr_str,
+ client_addr(get_client_fd(),addr,sizeof(addr)));
+ sessionid.connect_start = time(NULL);
+
+ if (!smb_pam_claim_session(sessionid.username, sessionid.id_str,
+ sessionid.hostname)) {
+ DEBUG(1,("pam_session rejected the session for %s [%s]\n",
+ sessionid.username, sessionid.id_str));
+
+ TALLOC_FREE(rec);
+ return False;
+ }
+
+ data.dptr = (uint8 *)&sessionid;
+ data.dsize = sizeof(sessionid);
+
+ status = rec->store(rec, data, TDB_REPLACE);
+
+ TALLOC_FREE(rec);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("session_claim: unable to create session id "
+ "record: %s\n", nt_errstr(status)));
+ return False;
+ }
+
+ if (lp_utmp()) {
+ sys_utmp_claim(sessionid.username, sessionid.hostname,
+ sessionid.ip_addr_str,
+ sessionid.id_str, sessionid.id_num);
+ }
+
+ vuser->session_keystr = talloc_strdup(vuser, keystr);
+ if (!vuser->session_keystr) {
+ DEBUG(0, ("session_claim: talloc_strdup() failed for session_keystr\n"));
+ return False;
+ }
+ return True;
+}
+
+/********************************************************************
+ called when a session is destroyed
+********************************************************************/
+
+void session_yield(user_struct *vuser)
+{
+ TDB_DATA key;
+ struct sessionid sessionid;
+ struct db_context *ctx;
+ struct db_record *rec;
+
+ if (!(ctx = session_db_ctx())) return;
+
+ if (!vuser->session_keystr) {
+ return;
+ }
+
+ key = string_term_tdb_data(vuser->session_keystr);
+
+ if (!(rec = ctx->fetch_locked(ctx, NULL, key))) {
+ return;
+ }
+
+ if (rec->value.dsize != sizeof(sessionid))
+ return;
+
+ memcpy(&sessionid, rec->value.dptr, sizeof(sessionid));
+
+ if (lp_utmp()) {
+ sys_utmp_yield(sessionid.username, sessionid.hostname,
+ sessionid.ip_addr_str,
+ sessionid.id_str, sessionid.id_num);
+ }
+
+ smb_pam_close_session(sessionid.username, sessionid.id_str,
+ sessionid.hostname);
+
+ rec->delete_rec(rec);
+
+ TALLOC_FREE(rec);
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool session_traverse(int (*fn)(struct db_record *db,
+ void *private_data),
+ void *private_data)
+{
+ struct db_context *ctx;
+
+ if (!(ctx = session_db_ctx())) {
+ DEBUG(3, ("No tdb opened\n"));
+ return False;
+ }
+
+ ctx->traverse_read(ctx, fn, private_data);
+ return True;
+}
+
+/********************************************************************
+********************************************************************/
+
+struct session_list {
+ TALLOC_CTX *mem_ctx;
+ int count;
+ struct sessionid *sessions;
+};
+
+static int gather_sessioninfo(struct db_record *rec, void *state)
+{
+ struct session_list *sesslist = (struct session_list *) state;
+ const struct sessionid *current =
+ (const struct sessionid *) rec->value.dptr;
+
+ sesslist->sessions = TALLOC_REALLOC_ARRAY(
+ sesslist->mem_ctx, sesslist->sessions, struct sessionid,
+ sesslist->count+1);
+
+ if (!sesslist->sessions) {
+ sesslist->count = 0;
+ return -1;
+ }
+
+ memcpy(&sesslist->sessions[sesslist->count], current,
+ sizeof(struct sessionid));
+
+ sesslist->count++;
+
+ DEBUG(7,("gather_sessioninfo session from %s@%s\n",
+ current->username, current->remote_machine));
+
+ return 0;
+}
+
+/********************************************************************
+********************************************************************/
+
+int list_sessions(TALLOC_CTX *mem_ctx, struct sessionid **session_list)
+{
+ struct session_list sesslist;
+
+ sesslist.mem_ctx = mem_ctx;
+ sesslist.count = 0;
+ sesslist.sessions = NULL;
+
+ if (!session_traverse(gather_sessioninfo, (void *) &sesslist)) {
+ DEBUG(3, ("Session traverse failed\n"));
+ SAFE_FREE(sesslist.sessions);
+ *session_list = NULL;
+ return 0;
+ }
+
+ *session_list = sesslist.sessions;
+ return sesslist.count;
+}
diff --git a/source3/smbd/sesssetup.c b/source3/smbd/sesssetup.c
new file mode 100644
index 0000000000..9c9d0a97bc
--- /dev/null
+++ b/source3/smbd/sesssetup.c
@@ -0,0 +1,1820 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle SMBsessionsetup
+ Copyright (C) Andrew Tridgell 1998-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Volker Lendecke 2007
+ Copyright (C) Jeremy Allison 2007
+
+ 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"
+
+extern struct auth_context *negprot_global_auth_context;
+extern bool global_encrypted_passwords_negotiated;
+extern bool global_spnego_negotiated;
+extern enum protocol_types Protocol;
+extern int max_send;
+
+uint32 global_client_caps = 0;
+
+/*
+ on a logon error possibly map the error to success if "map to guest"
+ is set approriately
+*/
+static NTSTATUS do_map_to_guest(NTSTATUS status,
+ auth_serversupplied_info **server_info,
+ const char *user, const char *domain)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ if ((lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_USER) ||
+ (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_PASSWORD)) {
+ DEBUG(3,("No such user %s [%s] - using guest account\n",
+ user, domain));
+ status = make_server_info_guest(NULL, server_info);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) {
+ if (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_PASSWORD) {
+ DEBUG(3,("Registered username %s for guest access\n",
+ user));
+ status = make_server_info_guest(NULL, server_info);
+ }
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Add the standard 'Samba' signature to the end of the session setup.
+****************************************************************************/
+
+static int push_signature(uint8 **outbuf)
+{
+ char *lanman;
+ int result, tmp;
+
+ result = 0;
+
+ tmp = message_push_string(outbuf, "Unix", STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ if (asprintf(&lanman, "Samba %s", SAMBA_VERSION_STRING) != -1) {
+ tmp = message_push_string(outbuf, lanman, STR_TERMINATE);
+ SAFE_FREE(lanman);
+ }
+ else {
+ tmp = message_push_string(outbuf, "Samba", STR_TERMINATE);
+ }
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ tmp = message_push_string(outbuf, lp_workgroup(), STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ return result;
+}
+
+/****************************************************************************
+ Start the signing engine if needed. Don't fail signing here.
+****************************************************************************/
+
+static void sessionsetup_start_signing_engine(
+ const auth_serversupplied_info *server_info,
+ const uint8 *inbuf)
+{
+ if (!server_info->guest && !srv_signing_started()) {
+ /* We need to start the signing engine
+ * here but a W2K client sends the old
+ * "BSRSPYL " signature instead of the
+ * correct one. Subsequent packets will
+ * be correct.
+ */
+ srv_check_sign_mac((char *)inbuf, False);
+ }
+}
+
+/****************************************************************************
+ Send a security blob via a session setup reply.
+****************************************************************************/
+
+static void reply_sesssetup_blob(struct smb_request *req,
+ DATA_BLOB blob,
+ NTSTATUS nt_status)
+{
+ if (!NT_STATUS_IS_OK(nt_status) &&
+ !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ } else {
+ nt_status = nt_status_squash(nt_status);
+ SIVAL(req->outbuf, smb_rcls, NT_STATUS_V(nt_status));
+ SSVAL(req->outbuf, smb_vwv0, 0xFF); /* no chaining possible */
+ SSVAL(req->outbuf, smb_vwv3, blob.length);
+
+ if ((message_push_blob(&req->outbuf, blob) == -1)
+ || (push_signature(&req->outbuf) == -1)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ show_msg((char *)req->outbuf);
+ srv_send_smb(smbd_server_fd(),(char *)req->outbuf,req->encrypted);
+ TALLOC_FREE(req->outbuf);
+}
+
+/****************************************************************************
+ Do a 'guest' logon, getting back the
+****************************************************************************/
+
+static NTSTATUS check_guest_password(auth_serversupplied_info **server_info)
+{
+ struct auth_context *auth_context;
+ auth_usersupplied_info *user_info = NULL;
+
+ NTSTATUS nt_status;
+ unsigned char chal[8];
+
+ ZERO_STRUCT(chal);
+
+ DEBUG(3,("Got anonymous request\n"));
+
+ if (!NT_STATUS_IS_OK(nt_status = make_auth_context_fixed(&auth_context,
+ chal))) {
+ return nt_status;
+ }
+
+ if (!make_user_info_guest(&user_info)) {
+ (auth_context->free)(&auth_context);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = auth_context->check_ntlm_password(auth_context,
+ user_info,
+ server_info);
+ (auth_context->free)(&auth_context);
+ free_user_info(&user_info);
+ return nt_status;
+}
+
+
+#ifdef HAVE_KRB5
+
+#if 0
+/* Experiment that failed. See "only happens with a KDC" comment below. */
+/****************************************************************************
+ Cerate a clock skew error blob for a Windows client.
+****************************************************************************/
+
+static bool make_krb5_skew_error(DATA_BLOB *pblob_out)
+{
+ krb5_context context = NULL;
+ krb5_error_code kerr = 0;
+ krb5_data reply;
+ krb5_principal host_princ = NULL;
+ char *host_princ_s = NULL;
+ bool ret = False;
+
+ *pblob_out = data_blob_null;
+
+ initialize_krb5_error_table();
+ kerr = krb5_init_context(&context);
+ if (kerr) {
+ return False;
+ }
+ /* Create server principal. */
+ asprintf(&host_princ_s, "%s$@%s", global_myname(), lp_realm());
+ if (!host_princ_s) {
+ goto out;
+ }
+ strlower_m(host_princ_s);
+
+ kerr = smb_krb5_parse_name(context, host_princ_s, &host_princ);
+ if (kerr) {
+ DEBUG(10,("make_krb5_skew_error: smb_krb5_parse_name failed "
+ "for name %s: Error %s\n",
+ host_princ_s, error_message(kerr) ));
+ goto out;
+ }
+
+ kerr = smb_krb5_mk_error(context, KRB5KRB_AP_ERR_SKEW,
+ host_princ, &reply);
+ if (kerr) {
+ DEBUG(10,("make_krb5_skew_error: smb_krb5_mk_error "
+ "failed: Error %s\n",
+ error_message(kerr) ));
+ goto out;
+ }
+
+ *pblob_out = data_blob(reply.data, reply.length);
+ kerberos_free_data_contents(context,&reply);
+ ret = True;
+
+ out:
+
+ if (host_princ_s) {
+ SAFE_FREE(host_princ_s);
+ }
+ if (host_princ) {
+ krb5_free_principal(context, host_princ);
+ }
+ krb5_free_context(context);
+ return ret;
+}
+#endif
+
+/****************************************************************************
+ Reply to a session setup spnego negotiate packet for kerberos.
+****************************************************************************/
+
+static void reply_spnego_kerberos(struct smb_request *req,
+ DATA_BLOB *secblob,
+ const char *mechOID,
+ uint16 vuid,
+ bool *p_invalidate_vuid)
+{
+ TALLOC_CTX *mem_ctx;
+ DATA_BLOB ticket;
+ char *client, *p, *domain;
+ fstring netbios_domain_name;
+ struct passwd *pw;
+ fstring user;
+ int sess_vuid = req->vuid;
+ NTSTATUS ret = NT_STATUS_OK;
+ struct PAC_DATA *pac_data = NULL;
+ DATA_BLOB ap_rep, ap_rep_wrapped, response;
+ auth_serversupplied_info *server_info = NULL;
+ DATA_BLOB session_key = data_blob_null;
+ uint8 tok_id[2];
+ DATA_BLOB nullblob = data_blob_null;
+ fstring real_username;
+ bool map_domainuser_to_guest = False;
+ bool username_was_mapped;
+ struct PAC_LOGON_INFO *logon_info = NULL;
+
+ ZERO_STRUCT(ticket);
+ ZERO_STRUCT(ap_rep);
+ ZERO_STRUCT(ap_rep_wrapped);
+ ZERO_STRUCT(response);
+
+ /* Normally we will always invalidate the intermediate vuid. */
+ *p_invalidate_vuid = True;
+
+ mem_ctx = talloc_init("reply_spnego_kerberos");
+ if (mem_ctx == NULL) {
+ reply_nterror(req, nt_status_squash(NT_STATUS_NO_MEMORY));
+ return;
+ }
+
+ if (!spnego_parse_krb5_wrap(*secblob, &ticket, tok_id)) {
+ talloc_destroy(mem_ctx);
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ ret = ads_verify_ticket(mem_ctx, lp_realm(), 0, &ticket,
+ &client, &pac_data, &ap_rep,
+ &session_key, True);
+
+ data_blob_free(&ticket);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+#if 0
+ /* Experiment that failed.
+ * See "only happens with a KDC" comment below. */
+
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
+
+ /*
+ * Windows in this case returns
+ * NT_STATUS_MORE_PROCESSING_REQUIRED
+ * with a negTokenTarg blob containing an krb5_error
+ * struct ASN1 encoded containing KRB5KRB_AP_ERR_SKEW.
+ * The client then fixes its clock and continues rather
+ * than giving an error. JRA.
+ * -- Looks like this only happens with a KDC. JRA.
+ */
+
+ bool ok = make_krb5_skew_error(&ap_rep);
+ if (!ok) {
+ talloc_destroy(mem_ctx);
+ return ERROR_NT(nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ }
+ ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep,
+ TOK_ID_KRB_ERROR);
+ response = spnego_gen_auth_response(&ap_rep_wrapped,
+ ret, OID_KERBEROS5_OLD);
+ reply_sesssetup_blob(conn, inbuf, outbuf, response,
+ NT_STATUS_MORE_PROCESSING_REQUIRED);
+
+ /*
+ * In this one case we don't invalidate the
+ * intermediate vuid as we're expecting the client
+ * to re-use it for the next sessionsetupX packet. JRA.
+ */
+
+ *p_invalidate_vuid = False;
+
+ data_blob_free(&ap_rep);
+ data_blob_free(&ap_rep_wrapped);
+ data_blob_free(&response);
+ talloc_destroy(mem_ctx);
+ return -1; /* already replied */
+ }
+#else
+ if (!NT_STATUS_EQUAL(ret, NT_STATUS_TIME_DIFFERENCE_AT_DC)) {
+ ret = NT_STATUS_LOGON_FAILURE;
+ }
+#endif
+ DEBUG(1,("Failed to verify incoming ticket with error %s!\n",
+ nt_errstr(ret)));
+ talloc_destroy(mem_ctx);
+ reply_nterror(req, nt_status_squash(ret));
+ return;
+ }
+
+ DEBUG(3,("Ticket name is [%s]\n", client));
+
+ p = strchr_m(client, '@');
+ if (!p) {
+ DEBUG(3,("Doesn't look like a valid principal\n"));
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ SAFE_FREE(client);
+ talloc_destroy(mem_ctx);
+ reply_nterror(req,nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ *p = 0;
+
+ /* save the PAC data if we have it */
+
+ if (pac_data) {
+ logon_info = get_logon_info_from_pac(pac_data);
+ if (logon_info) {
+ netsamlogon_cache_store( client, &logon_info->info3 );
+ }
+ }
+
+ if (!strequal(p+1, lp_realm())) {
+ DEBUG(3,("Ticket for foreign realm %s@%s\n", client, p+1));
+ if (!lp_allow_trusted_domains()) {
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ SAFE_FREE(client);
+ talloc_destroy(mem_ctx);
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+ }
+
+ /* this gives a fully qualified user name (ie. with full realm).
+ that leads to very long usernames, but what else can we do? */
+
+ domain = p+1;
+
+ if (logon_info && logon_info->info3.base.domain.string) {
+ fstrcpy(netbios_domain_name,
+ logon_info->info3.base.domain.string);
+ domain = netbios_domain_name;
+ DEBUG(10, ("Mapped to [%s] (using PAC)\n", domain));
+
+ } else {
+
+ /* If we have winbind running, we can (and must) shorten the
+ username by using the short netbios name. Otherwise we will
+ have inconsistent user names. With Kerberos, we get the
+ fully qualified realm, with ntlmssp we get the short
+ name. And even w2k3 does use ntlmssp if you for example
+ connect to an ip address. */
+
+ wbcErr wbc_status;
+ struct wbcDomainInfo *info = NULL;
+
+ DEBUG(10, ("Mapping [%s] to short name\n", domain));
+
+ wbc_status = wbcDomainInfo(domain, &info);
+
+ if (WBC_ERROR_IS_OK(wbc_status)) {
+
+ fstrcpy(netbios_domain_name,
+ info->short_name);
+
+ wbcFreeMemory(info);
+ domain = netbios_domain_name;
+ DEBUG(10, ("Mapped to [%s] (using Winbind)\n", domain));
+ } else {
+ DEBUG(3, ("Could not find short name: %s\n",
+ wbcErrorString(wbc_status)));
+ }
+ }
+
+ fstr_sprintf(user, "%s%c%s", domain, *lp_winbind_separator(), client);
+
+ /* lookup the passwd struct, create a new user if necessary */
+
+ username_was_mapped = map_username( user );
+
+ pw = smb_getpwnam( mem_ctx, user, real_username, True );
+
+ if (pw) {
+ /* if a real user check pam account restrictions */
+ /* only really perfomed if "obey pam restriction" is true */
+ /* do this before an eventual mapping to guest occurs */
+ ret = smb_pam_accountcheck(pw->pw_name);
+ if ( !NT_STATUS_IS_OK(ret)) {
+ DEBUG(1,("PAM account restriction "
+ "prevents user login\n"));
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ TALLOC_FREE(mem_ctx);
+ reply_nterror(req, nt_status_squash(ret));
+ return;
+ }
+ }
+
+ if (!pw) {
+
+ /* this was originally the behavior of Samba 2.2, if a user
+ did not have a local uid but has been authenticated, then
+ map them to a guest account */
+
+ if (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_UID){
+ map_domainuser_to_guest = True;
+ fstrcpy(user,lp_guestaccount());
+ pw = smb_getpwnam( mem_ctx, user, real_username, True );
+ }
+
+ /* extra sanity check that the guest account is valid */
+
+ if ( !pw ) {
+ DEBUG(1,("Username %s is invalid on this system\n",
+ user));
+ SAFE_FREE(client);
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ TALLOC_FREE(mem_ctx);
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+ }
+
+ /* setup the string used by %U */
+
+ sub_set_smb_name( real_username );
+ reload_services(True);
+
+ if ( map_domainuser_to_guest ) {
+ make_server_info_guest(NULL, &server_info);
+ } else if (logon_info) {
+ /* pass the unmapped username here since map_username()
+ will be called again from inside make_server_info_info3() */
+
+ ret = make_server_info_info3(mem_ctx, client, domain,
+ &server_info, &logon_info->info3);
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(1,("make_server_info_info3 failed: %s!\n",
+ nt_errstr(ret)));
+ SAFE_FREE(client);
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ TALLOC_FREE(mem_ctx);
+ reply_nterror(req, nt_status_squash(ret));
+ return;
+ }
+
+ } else {
+ ret = make_server_info_pw(&server_info, real_username, pw);
+
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(1,("make_server_info_pw failed: %s!\n",
+ nt_errstr(ret)));
+ SAFE_FREE(client);
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ TALLOC_FREE(mem_ctx);
+ reply_nterror(req, nt_status_squash(ret));
+ return;
+ }
+
+ /* make_server_info_pw does not set the domain. Without this
+ * we end up with the local netbios name in substitutions for
+ * %D. */
+
+ if (server_info->sam_account != NULL) {
+ pdb_set_domain(server_info->sam_account,
+ domain, PDB_SET);
+ }
+ }
+
+ server_info->nss_token |= username_was_mapped;
+
+ /* we need to build the token for the user. make_server_info_guest()
+ already does this */
+
+ if ( !server_info->ptok ) {
+ ret = create_local_token( server_info );
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(10,("failed to create local token: %s\n",
+ nt_errstr(ret)));
+ SAFE_FREE(client);
+ data_blob_free(&ap_rep);
+ data_blob_free(&session_key);
+ TALLOC_FREE( mem_ctx );
+ TALLOC_FREE( server_info );
+ reply_nterror(req, nt_status_squash(ret));
+ return;
+ }
+ }
+
+ /* register_existing_vuid keeps the server info */
+ /* register_existing_vuid takes ownership of session_key on success,
+ * no need to free after this on success. A better interface would copy
+ * it.... */
+
+ if (!is_partial_auth_vuid(sess_vuid)) {
+ sess_vuid = register_initial_vuid();
+ }
+
+ data_blob_free(&server_info->user_session_key);
+ server_info->user_session_key = session_key;
+ session_key = data_blob_null;
+
+ sess_vuid = register_existing_vuid(sess_vuid,
+ server_info,
+ nullblob,
+ client);
+
+ SAFE_FREE(client);
+
+ reply_outbuf(req, 4, 0);
+ SSVAL(req->outbuf,smb_uid,sess_vuid);
+
+ if (sess_vuid == UID_FIELD_INVALID ) {
+ ret = NT_STATUS_LOGON_FAILURE;
+ } else {
+ /* current_user_info is changed on new vuid */
+ reload_services( True );
+
+ SSVAL(req->outbuf, smb_vwv3, 0);
+
+ if (server_info->guest) {
+ SSVAL(req->outbuf,smb_vwv2,1);
+ }
+
+ SSVAL(req->outbuf, smb_uid, sess_vuid);
+
+ sessionsetup_start_signing_engine(server_info, req->inbuf);
+ /* Successful logon. Keep this vuid. */
+ *p_invalidate_vuid = False;
+ }
+
+ /* wrap that up in a nice GSS-API wrapping */
+ if (NT_STATUS_IS_OK(ret)) {
+ ap_rep_wrapped = spnego_gen_krb5_wrap(ap_rep,
+ TOK_ID_KRB_AP_REP);
+ } else {
+ ap_rep_wrapped = data_blob_null;
+ }
+ response = spnego_gen_auth_response(&ap_rep_wrapped, ret,
+ mechOID);
+ reply_sesssetup_blob(req, response, ret);
+
+ data_blob_free(&ap_rep);
+ data_blob_free(&ap_rep_wrapped);
+ data_blob_free(&response);
+ TALLOC_FREE(mem_ctx);
+}
+
+#endif
+
+/****************************************************************************
+ Send a session setup reply, wrapped in SPNEGO.
+ Get vuid and check first.
+ End the NTLMSSP exchange context if we are OK/complete fail
+ This should be split into two functions, one to handle each
+ leg of the NTLM auth steps.
+***************************************************************************/
+
+static void reply_spnego_ntlmssp(struct smb_request *req,
+ uint16 vuid,
+ AUTH_NTLMSSP_STATE **auth_ntlmssp_state,
+ DATA_BLOB *ntlmssp_blob, NTSTATUS nt_status,
+ const char *OID,
+ bool wrap)
+{
+ DATA_BLOB response;
+ struct auth_serversupplied_info *server_info = NULL;
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ server_info = (*auth_ntlmssp_state)->server_info;
+ } else {
+ nt_status = do_map_to_guest(nt_status,
+ &server_info,
+ (*auth_ntlmssp_state)->ntlmssp_state->user,
+ (*auth_ntlmssp_state)->ntlmssp_state->domain);
+ }
+
+ reply_outbuf(req, 4, 0);
+
+ SSVAL(req->outbuf, smb_uid, vuid);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ DATA_BLOB nullblob = data_blob_null;
+
+ if (!is_partial_auth_vuid(vuid)) {
+ nt_status = NT_STATUS_LOGON_FAILURE;
+ goto out;
+ }
+
+ data_blob_free(&server_info->user_session_key);
+ server_info->user_session_key =
+ data_blob_talloc(
+ server_info,
+ (*auth_ntlmssp_state)->ntlmssp_state->session_key.data,
+ (*auth_ntlmssp_state)->ntlmssp_state->session_key.length);
+
+ /* register_existing_vuid keeps the server info */
+ if (register_existing_vuid(vuid,
+ server_info, nullblob,
+ (*auth_ntlmssp_state)->ntlmssp_state->user) !=
+ vuid) {
+ nt_status = NT_STATUS_LOGON_FAILURE;
+ goto out;
+ }
+
+ (*auth_ntlmssp_state)->server_info = NULL;
+
+ /* current_user_info is changed on new vuid */
+ reload_services( True );
+
+ SSVAL(req->outbuf, smb_vwv3, 0);
+
+ if (server_info->guest) {
+ SSVAL(req->outbuf,smb_vwv2,1);
+ }
+
+ sessionsetup_start_signing_engine(server_info,
+ (uint8 *)req->inbuf);
+ }
+
+ out:
+
+ if (wrap) {
+ response = spnego_gen_auth_response(ntlmssp_blob,
+ nt_status, OID);
+ } else {
+ response = *ntlmssp_blob;
+ }
+
+ reply_sesssetup_blob(req, response, nt_status);
+ if (wrap) {
+ data_blob_free(&response);
+ }
+
+ /* NT_STATUS_MORE_PROCESSING_REQUIRED from our NTLMSSP code tells us,
+ and the other end, that we are not finished yet. */
+
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ /* NB. This is *NOT* an error case. JRA */
+ auth_ntlmssp_end(auth_ntlmssp_state);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ }
+ }
+}
+
+/****************************************************************************
+ Is this a krb5 mechanism ?
+****************************************************************************/
+
+NTSTATUS parse_spnego_mechanisms(DATA_BLOB blob_in,
+ DATA_BLOB *pblob_out,
+ char **kerb_mechOID)
+{
+ char *OIDs[ASN1_MAX_OIDS];
+ int i;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ *kerb_mechOID = NULL;
+
+ /* parse out the OIDs and the first sec blob */
+ if (!parse_negTokenTarg(blob_in, OIDs, pblob_out)) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ /* only look at the first OID for determining the mechToken --
+ according to RFC2478, we should choose the one we want
+ and renegotiate, but i smell a client bug here..
+
+ Problem observed when connecting to a member (samba box)
+ of an AD domain as a user in a Samba domain. Samba member
+ server sent back krb5/mskrb5/ntlmssp as mechtypes, but the
+ client (2ksp3) replied with ntlmssp/mskrb5/krb5 and an
+ NTLMSSP mechtoken. --jerry */
+
+#ifdef HAVE_KRB5
+ if (strcmp(OID_KERBEROS5, OIDs[0]) == 0 ||
+ strcmp(OID_KERBEROS5_OLD, OIDs[0]) == 0) {
+ *kerb_mechOID = SMB_STRDUP(OIDs[0]);
+ if (*kerb_mechOID == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ }
+ }
+#endif
+
+ for (i=0;OIDs[i];i++) {
+ DEBUG(5,("parse_spnego_mechanisms: Got OID %s\n", OIDs[i]));
+ free(OIDs[i]);
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Fall back from krb5 to NTLMSSP.
+****************************************************************************/
+
+static void reply_spnego_downgrade_to_ntlmssp(struct smb_request *req,
+ uint16 vuid)
+{
+ DATA_BLOB response;
+
+ reply_outbuf(req, 4, 0);
+ SSVAL(req->outbuf,smb_uid,vuid);
+
+ DEBUG(3,("reply_spnego_downgrade_to_ntlmssp: Got krb5 ticket in SPNEGO "
+ "but set to downgrade to NTLMSSP\n"));
+
+ response = spnego_gen_auth_response(NULL,
+ NT_STATUS_MORE_PROCESSING_REQUIRED,
+ OID_NTLMSSP);
+ reply_sesssetup_blob(req, response, NT_STATUS_MORE_PROCESSING_REQUIRED);
+ data_blob_free(&response);
+}
+
+/****************************************************************************
+ Reply to a session setup spnego negotiate packet.
+****************************************************************************/
+
+static void reply_spnego_negotiate(struct smb_request *req,
+ uint16 vuid,
+ DATA_BLOB blob1,
+ AUTH_NTLMSSP_STATE **auth_ntlmssp_state)
+{
+ DATA_BLOB secblob;
+ DATA_BLOB chal;
+ char *kerb_mech = NULL;
+ NTSTATUS status;
+
+ status = parse_spnego_mechanisms(blob1, &secblob, &kerb_mech);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ DEBUG(3,("reply_spnego_negotiate: Got secblob of size %lu\n",
+ (unsigned long)secblob.length));
+
+#ifdef HAVE_KRB5
+ if (kerb_mech && ((lp_security()==SEC_ADS) ||
+ lp_use_kerberos_keytab()) ) {
+ bool destroy_vuid = True;
+ reply_spnego_kerberos(req, &secblob, kerb_mech,
+ vuid, &destroy_vuid);
+ data_blob_free(&secblob);
+ if (destroy_vuid) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ }
+ SAFE_FREE(kerb_mech);
+ return;
+ }
+#endif
+
+ if (*auth_ntlmssp_state) {
+ auth_ntlmssp_end(auth_ntlmssp_state);
+ }
+
+ if (kerb_mech) {
+ data_blob_free(&secblob);
+ /* The mechtoken is a krb5 ticket, but
+ * we need to fall back to NTLM. */
+ reply_spnego_downgrade_to_ntlmssp(req, vuid);
+ SAFE_FREE(kerb_mech);
+ return;
+ }
+
+ status = auth_ntlmssp_start(auth_ntlmssp_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ status = auth_ntlmssp_update(*auth_ntlmssp_state,
+ secblob, &chal);
+
+ data_blob_free(&secblob);
+
+ reply_spnego_ntlmssp(req, vuid, auth_ntlmssp_state,
+ &chal, status, OID_NTLMSSP, true);
+
+ data_blob_free(&chal);
+
+ /* already replied */
+ return;
+}
+
+/****************************************************************************
+ Reply to a session setup spnego auth packet.
+****************************************************************************/
+
+static void reply_spnego_auth(struct smb_request *req,
+ uint16 vuid,
+ DATA_BLOB blob1,
+ AUTH_NTLMSSP_STATE **auth_ntlmssp_state)
+{
+ DATA_BLOB auth = data_blob_null;
+ DATA_BLOB auth_reply = data_blob_null;
+ DATA_BLOB secblob = data_blob_null;
+ NTSTATUS status = NT_STATUS_LOGON_FAILURE;
+
+ if (!spnego_parse_auth(blob1, &auth)) {
+#if 0
+ file_save("auth.dat", blob1.data, blob1.length);
+#endif
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ if (auth.data[0] == ASN1_APPLICATION(0)) {
+ /* Might be a second negTokenTarg packet */
+ char *kerb_mech = NULL;
+
+ status = parse_spnego_mechanisms(auth, &secblob, &kerb_mech);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ DEBUG(3,("reply_spnego_auth: Got secblob of size %lu\n",
+ (unsigned long)secblob.length));
+#ifdef HAVE_KRB5
+ if (kerb_mech && ((lp_security()==SEC_ADS) ||
+ lp_use_kerberos_keytab()) ) {
+ bool destroy_vuid = True;
+ reply_spnego_kerberos(req, &secblob, kerb_mech,
+ vuid, &destroy_vuid);
+ data_blob_free(&secblob);
+ data_blob_free(&auth);
+ if (destroy_vuid) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ }
+ SAFE_FREE(kerb_mech);
+ return;
+ }
+#endif
+ /* Can't blunder into NTLMSSP auth if we have
+ * a krb5 ticket. */
+
+ if (kerb_mech) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ DEBUG(3,("reply_spnego_auth: network "
+ "misconfiguration, client sent us a "
+ "krb5 ticket and kerberos security "
+ "not enabled"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ SAFE_FREE(kerb_mech);
+ }
+ }
+
+ /* If we get here it wasn't a negTokenTarg auth packet. */
+ data_blob_free(&secblob);
+
+ if (!*auth_ntlmssp_state) {
+ status = auth_ntlmssp_start(auth_ntlmssp_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ status = auth_ntlmssp_update(*auth_ntlmssp_state,
+ auth, &auth_reply);
+
+ data_blob_free(&auth);
+
+ /* Don't send the mechid as we've already sent this (RFC4178). */
+
+ reply_spnego_ntlmssp(req, vuid,
+ auth_ntlmssp_state,
+ &auth_reply, status, NULL, true);
+
+ data_blob_free(&auth_reply);
+
+ /* and tell smbd that we have already replied to this packet */
+ return;
+}
+
+/****************************************************************************
+ List to store partial SPNEGO auth fragments.
+****************************************************************************/
+
+static struct pending_auth_data *pd_list;
+
+/****************************************************************************
+ Delete an entry on the list.
+****************************************************************************/
+
+static void delete_partial_auth(struct pending_auth_data *pad)
+{
+ if (!pad) {
+ return;
+ }
+ DLIST_REMOVE(pd_list, pad);
+ data_blob_free(&pad->partial_data);
+ SAFE_FREE(pad);
+}
+
+/****************************************************************************
+ Search for a partial SPNEGO auth fragment matching an smbpid.
+****************************************************************************/
+
+static struct pending_auth_data *get_pending_auth_data(uint16 smbpid)
+{
+ struct pending_auth_data *pad;
+
+ for (pad = pd_list; pad; pad = pad->next) {
+ if (pad->smbpid == smbpid) {
+ break;
+ }
+ }
+ return pad;
+}
+
+/****************************************************************************
+ Check the size of an SPNEGO blob. If we need more return
+ NT_STATUS_MORE_PROCESSING_REQUIRED, else return NT_STATUS_OK. Don't allow
+ the blob to be more than 64k.
+****************************************************************************/
+
+static NTSTATUS check_spnego_blob_complete(uint16 smbpid, uint16 vuid,
+ DATA_BLOB *pblob)
+{
+ struct pending_auth_data *pad = NULL;
+ ASN1_DATA data;
+ size_t needed_len = 0;
+
+ pad = get_pending_auth_data(smbpid);
+
+ /* Ensure we have some data. */
+ if (pblob->length == 0) {
+ /* Caller can cope. */
+ DEBUG(2,("check_spnego_blob_complete: zero blob length !\n"));
+ delete_partial_auth(pad);
+ return NT_STATUS_OK;
+ }
+
+ /* Were we waiting for more data ? */
+ if (pad) {
+ DATA_BLOB tmp_blob;
+ size_t copy_len = MIN(65536, pblob->length);
+
+ /* Integer wrap paranoia.... */
+
+ if (pad->partial_data.length + copy_len <
+ pad->partial_data.length ||
+ pad->partial_data.length + copy_len < copy_len) {
+
+ DEBUG(2,("check_spnego_blob_complete: integer wrap "
+ "pad->partial_data.length = %u, "
+ "copy_len = %u\n",
+ (unsigned int)pad->partial_data.length,
+ (unsigned int)copy_len ));
+
+ delete_partial_auth(pad);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("check_spnego_blob_complete: "
+ "pad->partial_data.length = %u, "
+ "pad->needed_len = %u, "
+ "copy_len = %u, "
+ "pblob->length = %u,\n",
+ (unsigned int)pad->partial_data.length,
+ (unsigned int)pad->needed_len,
+ (unsigned int)copy_len,
+ (unsigned int)pblob->length ));
+
+ tmp_blob = data_blob(NULL,
+ pad->partial_data.length + copy_len);
+
+ /* Concatenate the two (up to copy_len) bytes. */
+ memcpy(tmp_blob.data,
+ pad->partial_data.data,
+ pad->partial_data.length);
+ memcpy(tmp_blob.data + pad->partial_data.length,
+ pblob->data,
+ copy_len);
+
+ /* Replace the partial data. */
+ data_blob_free(&pad->partial_data);
+ pad->partial_data = tmp_blob;
+ ZERO_STRUCT(tmp_blob);
+
+ /* Are we done ? */
+ if (pblob->length >= pad->needed_len) {
+ /* Yes, replace pblob. */
+ data_blob_free(pblob);
+ *pblob = pad->partial_data;
+ ZERO_STRUCT(pad->partial_data);
+ delete_partial_auth(pad);
+ return NT_STATUS_OK;
+ }
+
+ /* Still need more data. */
+ pad->needed_len -= copy_len;
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ if ((pblob->data[0] != ASN1_APPLICATION(0)) &&
+ (pblob->data[0] != ASN1_CONTEXT(1))) {
+ /* Not something we can determine the
+ * length of.
+ */
+ return NT_STATUS_OK;
+ }
+
+ /* This is a new SPNEGO sessionsetup - see if
+ * the data given in this blob is enough.
+ */
+
+ asn1_load(&data, *pblob);
+ asn1_start_tag(&data, pblob->data[0]);
+ if (data.has_error || data.nesting == NULL) {
+ asn1_free(&data);
+ /* Let caller catch. */
+ return NT_STATUS_OK;
+ }
+
+ /* Integer wrap paranoia.... */
+
+ if (data.nesting->taglen + data.nesting->start < data.nesting->taglen ||
+ data.nesting->taglen + data.nesting->start < data.nesting->start) {
+
+ DEBUG(2,("check_spnego_blob_complete: integer wrap "
+ "data.nesting->taglen = %u, "
+ "data.nesting->start = %u\n",
+ (unsigned int)data.nesting->taglen,
+ (unsigned int)data.nesting->start ));
+
+ asn1_free(&data);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Total length of the needed asn1 is the tag length
+ * plus the current offset. */
+
+ needed_len = data.nesting->taglen + data.nesting->start;
+ asn1_free(&data);
+
+ DEBUG(10,("check_spnego_blob_complete: needed_len = %u, "
+ "pblob->length = %u\n",
+ (unsigned int)needed_len,
+ (unsigned int)pblob->length ));
+
+ if (needed_len <= pblob->length) {
+ /* Nothing to do - blob is complete. */
+ return NT_STATUS_OK;
+ }
+
+ /* Refuse the blob if it's bigger than 64k. */
+ if (needed_len > 65536) {
+ DEBUG(2,("check_spnego_blob_complete: needed_len "
+ "too large (%u)\n",
+ (unsigned int)needed_len ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* We must store this blob until complete. */
+ if (!(pad = SMB_MALLOC_P(struct pending_auth_data))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pad->needed_len = needed_len - pblob->length;
+ pad->partial_data = data_blob(pblob->data, pblob->length);
+ if (pad->partial_data.data == NULL) {
+ SAFE_FREE(pad);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pad->smbpid = smbpid;
+ pad->vuid = vuid;
+ DLIST_ADD(pd_list, pad);
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static void reply_sesssetup_and_X_spnego(struct smb_request *req)
+{
+ uint8 *p;
+ DATA_BLOB blob1;
+ size_t bufrem;
+ fstring native_os, native_lanman, primary_domain;
+ const char *p2;
+ uint16 data_blob_len = SVAL(req->inbuf, smb_vwv7);
+ enum remote_arch_types ra_type = get_remote_arch();
+ int vuid = SVAL(req->inbuf,smb_uid);
+ user_struct *vuser = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint16 smbpid = req->smbpid;
+ uint16 smb_flag2 = req->flags2;
+
+ DEBUG(3,("Doing spnego session setup\n"));
+
+ if (global_client_caps == 0) {
+ global_client_caps = IVAL(req->inbuf,smb_vwv10);
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+ }
+
+ }
+
+ p = (uint8 *)smb_buf(req->inbuf);
+
+ if (data_blob_len == 0) {
+ /* an invalid request */
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ bufrem = smb_bufrem(req->inbuf, p);
+ /* pull the spnego blob */
+ blob1 = data_blob(p, MIN(bufrem, data_blob_len));
+
+#if 0
+ file_save("negotiate.dat", blob1.data, blob1.length);
+#endif
+
+ p2 = (char *)req->inbuf + smb_vwv13 + data_blob_len;
+ p2 += srvstr_pull_buf(req->inbuf, smb_flag2, native_os, p2,
+ sizeof(native_os), STR_TERMINATE);
+ p2 += srvstr_pull_buf(req->inbuf, smb_flag2, native_lanman, p2,
+ sizeof(native_lanman), STR_TERMINATE);
+ p2 += srvstr_pull_buf(req->inbuf, smb_flag2, primary_domain, p2,
+ sizeof(primary_domain), STR_TERMINATE);
+ DEBUG(3,("NativeOS=[%s] NativeLanMan=[%s] PrimaryDomain=[%s]\n",
+ native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ /* Vista sets neither the OS or lanman strings */
+
+ if ( !strlen(native_os) && !strlen(native_lanman) )
+ set_remote_arch(RA_VISTA);
+
+ /* Windows 2003 doesn't set the native lanman string,
+ but does set primary domain which is a bug I think */
+
+ if ( !strlen(native_lanman) ) {
+ ra_lanman_string( primary_domain );
+ } else {
+ ra_lanman_string( native_lanman );
+ }
+ }
+
+ /* Did we get a valid vuid ? */
+ if (!is_partial_auth_vuid(vuid)) {
+ /* No, then try and see if this is an intermediate sessionsetup
+ * for a large SPNEGO packet. */
+ struct pending_auth_data *pad = get_pending_auth_data(smbpid);
+ if (pad) {
+ DEBUG(10,("reply_sesssetup_and_X_spnego: found "
+ "pending vuid %u\n",
+ (unsigned int)pad->vuid ));
+ vuid = pad->vuid;
+ }
+ }
+
+ /* Do we have a valid vuid now ? */
+ if (!is_partial_auth_vuid(vuid)) {
+ /* No, start a new authentication setup. */
+ vuid = register_initial_vuid();
+ if (vuid == UID_FIELD_INVALID) {
+ data_blob_free(&blob1);
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ return;
+ }
+ }
+
+ vuser = get_partial_auth_user_struct(vuid);
+ /* This MUST be valid. */
+ if (!vuser) {
+ smb_panic("reply_sesssetup_and_X_spnego: invalid vuid.");
+ }
+
+ /* Large (greater than 4k) SPNEGO blobs are split into multiple
+ * sessionsetup requests as the Windows limit on the security blob
+ * field is 4k. Bug #4400. JRA.
+ */
+
+ status = check_spnego_blob_complete(smbpid, vuid, &blob1);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ /* Real error - kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ }
+ data_blob_free(&blob1);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (blob1.data[0] == ASN1_APPLICATION(0)) {
+
+ /* its a negTokenTarg packet */
+
+ reply_spnego_negotiate(req, vuid, blob1,
+ &vuser->auth_ntlmssp_state);
+ data_blob_free(&blob1);
+ return;
+ }
+
+ if (blob1.data[0] == ASN1_CONTEXT(1)) {
+
+ /* its a auth packet */
+
+ reply_spnego_auth(req, vuid, blob1,
+ &vuser->auth_ntlmssp_state);
+ data_blob_free(&blob1);
+ return;
+ }
+
+ if (strncmp((char *)(blob1.data), "NTLMSSP", 7) == 0) {
+ DATA_BLOB chal;
+
+ if (!vuser->auth_ntlmssp_state) {
+ status = auth_ntlmssp_start(&vuser->auth_ntlmssp_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Kill the intermediate vuid */
+ invalidate_vuid(vuid);
+ data_blob_free(&blob1);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ status = auth_ntlmssp_update(vuser->auth_ntlmssp_state,
+ blob1, &chal);
+
+ data_blob_free(&blob1);
+
+ reply_spnego_ntlmssp(req, vuid,
+ &vuser->auth_ntlmssp_state,
+ &chal, status, OID_NTLMSSP, false);
+ data_blob_free(&chal);
+ return;
+ }
+
+ /* what sort of packet is this? */
+ DEBUG(1,("Unknown packet in reply_sesssetup_and_X_spnego\n"));
+
+ data_blob_free(&blob1);
+
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+}
+
+/****************************************************************************
+ On new VC == 0, shutdown *all* old connections and users.
+ It seems that only NT4.x does this. At W2K and above (XP etc.).
+ a new session setup with VC==0 is ignored.
+****************************************************************************/
+
+static int shutdown_other_smbds(struct db_record *rec,
+ const struct connections_key *key,
+ const struct connections_data *crec,
+ void *private_data)
+{
+ const char *ip = (const char *)private_data;
+
+ if (!process_exists(crec->pid)) {
+ return 0;
+ }
+
+ if (procid_is_me(&crec->pid)) {
+ return 0;
+ }
+
+ if (strcmp(ip, crec->addr) != 0) {
+ return 0;
+ }
+
+ DEBUG(0,("shutdown_other_smbds: shutting down pid %d "
+ "(IP %s)\n", procid_to_pid(&crec->pid), ip));
+
+ messaging_send(smbd_messaging_context(), crec->pid, MSG_SHUTDOWN,
+ &data_blob_null);
+ return 0;
+}
+
+static void setup_new_vc_session(void)
+{
+ char addr[INET6_ADDRSTRLEN];
+
+ DEBUG(2,("setup_new_vc_session: New VC == 0, if NT4.x "
+ "compatible we would close all old resources.\n"));
+#if 0
+ conn_close_all();
+ invalidate_all_vuids();
+#endif
+ if (lp_reset_on_zero_vc()) {
+ connections_forall(shutdown_other_smbds,
+ CONST_DISCARD(void *,
+ client_addr(get_client_fd(),addr,sizeof(addr))));
+ }
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+****************************************************************************/
+
+void reply_sesssetup_and_X(struct smb_request *req)
+{
+ int sess_vuid;
+ int smb_bufsize;
+ DATA_BLOB lm_resp;
+ DATA_BLOB nt_resp;
+ DATA_BLOB plaintext_password;
+ fstring user;
+ fstring sub_user; /* Sainitised username for substituion */
+ fstring domain;
+ fstring native_os;
+ fstring native_lanman;
+ fstring primary_domain;
+ static bool done_sesssetup = False;
+ auth_usersupplied_info *user_info = NULL;
+ auth_serversupplied_info *server_info = NULL;
+ uint16 smb_flag2 = req->flags2;
+
+ NTSTATUS nt_status;
+
+ bool doencrypt = global_encrypted_passwords_negotiated;
+
+ START_PROFILE(SMBsesssetupX);
+
+ ZERO_STRUCT(lm_resp);
+ ZERO_STRUCT(nt_resp);
+ ZERO_STRUCT(plaintext_password);
+
+ DEBUG(3,("wct=%d flg2=0x%x\n", req->wct, req->flags2));
+
+ /* a SPNEGO session setup has 12 command words, whereas a normal
+ NT1 session setup has 13. See the cifs spec. */
+ if (req->wct == 12 &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+
+ if (!global_spnego_negotiated) {
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at SPNEGO session setup when it was not "
+ "negotiated.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (SVAL(req->inbuf,smb_vwv4) == 0) {
+ setup_new_vc_session();
+ }
+
+ reply_sesssetup_and_X_spnego(req);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ smb_bufsize = SVAL(req->inbuf,smb_vwv2);
+
+ if (Protocol < PROTOCOL_NT1) {
+ uint16 passlen1 = SVAL(req->inbuf,smb_vwv7);
+
+ /* Never do NT status codes with protocols before NT1 as we
+ * don't get client caps. */
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+
+ if ((passlen1 > MAX_PASS_LEN)
+ || (passlen1 > smb_bufrem(req->inbuf,
+ smb_buf(req->inbuf)))) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (doencrypt) {
+ lm_resp = data_blob(smb_buf(req->inbuf), passlen1);
+ } else {
+ plaintext_password = data_blob(smb_buf(req->inbuf),
+ passlen1+1);
+ /* Ensure null termination */
+ plaintext_password.data[passlen1] = 0;
+ }
+
+ srvstr_pull_buf(req->inbuf, req->flags2, user,
+ smb_buf(req->inbuf)+passlen1, sizeof(user),
+ STR_TERMINATE);
+ *domain = 0;
+
+ } else {
+ uint16 passlen1 = SVAL(req->inbuf,smb_vwv7);
+ uint16 passlen2 = SVAL(req->inbuf,smb_vwv8);
+ enum remote_arch_types ra_type = get_remote_arch();
+ char *p = smb_buf(req->inbuf);
+ char *save_p = smb_buf(req->inbuf);
+ uint16 byte_count;
+
+
+ if(global_client_caps == 0) {
+ global_client_caps = IVAL(req->inbuf,smb_vwv11);
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(
+ FLAGS2_32_BIT_ERROR_CODES);
+ }
+
+ /* client_caps is used as final determination if
+ * client is NT or Win95. This is needed to return
+ * the correct error codes in some circumstances.
+ */
+
+ if(ra_type == RA_WINNT || ra_type == RA_WIN2K ||
+ ra_type == RA_WIN95) {
+ if(!(global_client_caps & (CAP_NT_SMBS|
+ CAP_STATUS32))) {
+ set_remote_arch( RA_WIN95);
+ }
+ }
+ }
+
+ if (!doencrypt) {
+ /* both Win95 and WinNT stuff up the password
+ * lengths for non-encrypting systems. Uggh.
+
+ if passlen1==24 its a win95 system, and its setting
+ the password length incorrectly. Luckily it still
+ works with the default code because Win95 will null
+ terminate the password anyway
+
+ if passlen1>0 and passlen2>0 then maybe its a NT box
+ and its setting passlen2 to some random value which
+ really stuffs things up. we need to fix that one. */
+
+ if (passlen1 > 0 && passlen2 > 0 && passlen2 != 24 &&
+ passlen2 != 1) {
+ passlen2 = 0;
+ }
+ }
+
+ /* check for nasty tricks */
+ if (passlen1 > MAX_PASS_LEN
+ || passlen1 > smb_bufrem(req->inbuf, p)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (passlen2 > MAX_PASS_LEN
+ || passlen2 > smb_bufrem(req->inbuf, p+passlen1)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* Save the lanman2 password and the NT md4 password. */
+
+ if ((doencrypt) && (passlen1 != 0) && (passlen1 != 24)) {
+ doencrypt = False;
+ }
+
+ if (doencrypt) {
+ lm_resp = data_blob(p, passlen1);
+ nt_resp = data_blob(p+passlen1, passlen2);
+ } else if (lp_security() != SEC_SHARE) {
+ /*
+ * In share level we should ignore any passwords, so
+ * only read them if we're not.
+ */
+ char *pass = NULL;
+ bool unic= smb_flag2 & FLAGS2_UNICODE_STRINGS;
+
+ if (unic && (passlen2 == 0) && passlen1) {
+ /* Only a ascii plaintext password was sent. */
+ (void)srvstr_pull_talloc(talloc_tos(),
+ req->inbuf,
+ req->flags2,
+ &pass,
+ smb_buf(req->inbuf),
+ passlen1,
+ STR_TERMINATE|STR_ASCII);
+ } else {
+ (void)srvstr_pull_talloc(talloc_tos(),
+ req->inbuf,
+ req->flags2,
+ &pass,
+ smb_buf(req->inbuf),
+ unic ? passlen2 : passlen1,
+ STR_TERMINATE);
+ }
+ if (!pass) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ plaintext_password = data_blob(pass, strlen(pass)+1);
+ }
+
+ p += passlen1 + passlen2;
+ p += srvstr_pull_buf(req->inbuf, req->flags2, user, p,
+ sizeof(user), STR_TERMINATE);
+ p += srvstr_pull_buf(req->inbuf, req->flags2, domain, p,
+ sizeof(domain), STR_TERMINATE);
+ p += srvstr_pull_buf(req->inbuf, req->flags2, native_os,
+ p, sizeof(native_os), STR_TERMINATE);
+ p += srvstr_pull_buf(req->inbuf, req->flags2,
+ native_lanman, p, sizeof(native_lanman),
+ STR_TERMINATE);
+
+ /* not documented or decoded by Ethereal but there is one more
+ * string in the extra bytes which is the same as the
+ * PrimaryDomain when using extended security. Windows NT 4
+ * and 2003 use this string to store the native lanman string.
+ * Windows 9x does not include a string here at all so we have
+ * to check if we have any extra bytes left */
+
+ byte_count = SVAL(req->inbuf, smb_vwv13);
+ if ( PTR_DIFF(p, save_p) < byte_count) {
+ p += srvstr_pull_buf(req->inbuf, req->flags2,
+ primary_domain, p,
+ sizeof(primary_domain),
+ STR_TERMINATE);
+ } else {
+ fstrcpy( primary_domain, "null" );
+ }
+
+ DEBUG(3,("Domain=[%s] NativeOS=[%s] NativeLanMan=[%s] "
+ "PrimaryDomain=[%s]\n",
+ domain, native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ if ( strlen(native_lanman) == 0 )
+ ra_lanman_string( primary_domain );
+ else
+ ra_lanman_string( native_lanman );
+ }
+
+ }
+
+ if (SVAL(req->inbuf,smb_vwv4) == 0) {
+ setup_new_vc_session();
+ }
+
+ DEBUG(3,("sesssetupX:name=[%s]\\[%s]@[%s]\n",
+ domain, user, get_remote_machine_name()));
+
+ if (*user) {
+ if (global_spnego_negotiated) {
+
+ /* This has to be here, because this is a perfectly
+ * valid behaviour for guest logons :-( */
+
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at 'normal' session setup after "
+ "negotiating spnego.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ fstrcpy(sub_user, user);
+ } else {
+ fstrcpy(sub_user, lp_guestaccount());
+ }
+
+ sub_set_smb_name(sub_user);
+
+ reload_services(True);
+
+ if (lp_security() == SEC_SHARE) {
+ /* In share level we should ignore any passwords */
+
+ data_blob_free(&lm_resp);
+ data_blob_free(&nt_resp);
+ data_blob_clear_free(&plaintext_password);
+
+ map_username(sub_user);
+ add_session_user(sub_user);
+ add_session_workgroup(domain);
+ /* Then force it to null for the benfit of the code below */
+ *user = 0;
+ }
+
+ if (!*user) {
+
+ nt_status = check_guest_password(&server_info);
+
+ } else if (doencrypt) {
+ if (!negprot_global_auth_context) {
+ DEBUG(0, ("reply_sesssetup_and_X: Attempted encrypted "
+ "session setup without negprot denied!\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ nt_status = make_user_info_for_reply_enc(&user_info, user,
+ domain,
+ lm_resp, nt_resp);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ nt_status = negprot_global_auth_context->check_ntlm_password(
+ negprot_global_auth_context,
+ user_info,
+ &server_info);
+ }
+ } else {
+ struct auth_context *plaintext_auth_context = NULL;
+ const uint8 *chal;
+
+ nt_status = make_auth_context_subsystem(
+ &plaintext_auth_context);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ chal = plaintext_auth_context->get_ntlm_challenge(
+ plaintext_auth_context);
+
+ if (!make_user_info_for_reply(&user_info,
+ user, domain, chal,
+ plaintext_password)) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ nt_status = plaintext_auth_context->check_ntlm_password(
+ plaintext_auth_context,
+ user_info,
+ &server_info);
+
+ (plaintext_auth_context->free)(
+ &plaintext_auth_context);
+ }
+ }
+ }
+
+ free_user_info(&user_info);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ nt_status = do_map_to_guest(nt_status, &server_info,
+ user, domain);
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ data_blob_free(&nt_resp);
+ data_blob_free(&lm_resp);
+ data_blob_clear_free(&plaintext_password);
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* Ensure we can't possible take a code path leading to a
+ * null defref. */
+ if (!server_info) {
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (!server_info->ptok) {
+ nt_status = create_local_token(server_info);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(10, ("create_local_token failed: %s\n",
+ nt_errstr(nt_status)));
+ data_blob_free(&nt_resp);
+ data_blob_free(&lm_resp);
+ data_blob_clear_free(&plaintext_password);
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ }
+
+ data_blob_clear_free(&plaintext_password);
+
+ /* it's ok - setup a reply */
+ reply_outbuf(req, 3, 0);
+ if (Protocol >= PROTOCOL_NT1) {
+ push_signature(&req->outbuf);
+ /* perhaps grab OS version here?? */
+ }
+
+ if (server_info->guest) {
+ SSVAL(req->outbuf,smb_vwv2,1);
+ }
+
+ /* register the name and uid as being validated, so further connections
+ to a uid can get through without a password, on the same VC */
+
+ if (lp_security() == SEC_SHARE) {
+ sess_vuid = UID_FIELD_INVALID;
+ TALLOC_FREE(server_info);
+ } else {
+ /* Ignore the initial vuid. */
+ sess_vuid = register_initial_vuid();
+ if (sess_vuid == UID_FIELD_INVALID) {
+ data_blob_free(&nt_resp);
+ data_blob_free(&lm_resp);
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ /* register_existing_vuid keeps the server info */
+ sess_vuid = register_existing_vuid(sess_vuid,
+ server_info,
+ nt_resp.data ? nt_resp : lm_resp,
+ sub_user);
+ if (sess_vuid == UID_FIELD_INVALID) {
+ data_blob_free(&nt_resp);
+ data_blob_free(&lm_resp);
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* current_user_info is changed on new vuid */
+ reload_services( True );
+
+ sessionsetup_start_signing_engine(server_info, req->inbuf);
+ }
+
+ data_blob_free(&nt_resp);
+ data_blob_free(&lm_resp);
+
+ SSVAL(req->outbuf,smb_uid,sess_vuid);
+ SSVAL(req->inbuf,smb_uid,sess_vuid);
+
+ if (!done_sesssetup)
+ max_send = MIN(max_send,smb_bufsize);
+
+ done_sesssetup = True;
+
+ END_PROFILE(SMBsesssetupX);
+ chain_reply(req);
+ return;
+}
diff --git a/source3/smbd/share_access.c b/source3/smbd/share_access.c
new file mode 100644
index 0000000000..f5f79c86e5
--- /dev/null
+++ b/source3/smbd/share_access.c
@@ -0,0 +1,280 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access based on valid users, read list and friends
+ Copyright (C) Volker Lendecke 2005
+
+ 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"
+
+/*
+ * No prefix means direct username
+ * @name means netgroup first, then unix group
+ * &name means netgroup
+ * +name means unix group
+ * + and & may be combined
+ */
+
+static bool do_group_checks(const char **name, const char **pattern)
+{
+ if ((*name)[0] == '@') {
+ *pattern = "&+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '+') && ((*name)[1] == '&')) {
+ *pattern = "+&";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '+') {
+ *pattern = "+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '&') && ((*name)[1] == '+')) {
+ *pattern = "&+";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '&') {
+ *pattern = "&";
+ *name += 1;
+ return True;
+ }
+
+ return False;
+}
+
+static bool token_contains_name(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct nt_user_token *token,
+ const char *name)
+{
+ const char *prefix;
+ DOM_SID sid;
+ enum lsa_SidType type;
+
+ if (username != NULL) {
+ name = talloc_sub_basic(mem_ctx, username, domain, name);
+ }
+ if (sharename != NULL) {
+ name = talloc_string_sub(mem_ctx, name, "%S", sharename);
+ }
+
+ if (name == NULL) {
+ /* This is too security sensitive, better panic than return a
+ * result that might be interpreted in a wrong way. */
+ smb_panic("substitutions failed");
+ }
+
+ /* check to see is we already have a SID */
+
+ if ( string_to_sid( &sid, name ) ) {
+ DEBUG(5,("token_contains_name: Checking for SID [%s] in token\n", name));
+ return nt_token_check_sid( &sid, token );
+ }
+
+ if (!do_group_checks(&name, &prefix)) {
+ if (!lookup_name_smbconf(mem_ctx, name, LOOKUP_NAME_ALL,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if (type != SID_NAME_USER) {
+ DEBUG(5, ("%s is a %s, expected a user\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ return nt_token_check_sid(&sid, token);
+ }
+
+ for (/* initialized above */ ; *prefix != '\0'; prefix++) {
+ if (*prefix == '+') {
+ if (!lookup_name_smbconf(mem_ctx, name,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if ((type != SID_NAME_DOM_GRP) &&
+ (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DEBUG(5, ("%s is a %s, expected a group\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ if (nt_token_check_sid(&sid, token)) {
+ return True;
+ }
+ continue;
+ }
+ if (*prefix == '&') {
+ if (user_in_netgroup(username, name)) {
+ return True;
+ }
+ continue;
+ }
+ smb_panic("got invalid prefix from do_groups_check");
+ }
+ return False;
+}
+
+/*
+ * Check whether a user is contained in the list provided.
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool token_contains_name_in_list(const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct nt_user_token *token,
+ const char **list)
+{
+ TALLOC_CTX *mem_ctx;
+
+ if (list == NULL) {
+ return False;
+ }
+
+ if ( (mem_ctx = talloc_new(NULL)) == NULL ) {
+ smb_panic("talloc_new failed");
+ }
+
+ while (*list != NULL) {
+ if (token_contains_name(mem_ctx, username, domain, sharename,
+ token, *list)) {
+ TALLOC_FREE(mem_ctx);
+ return True;
+ }
+ list += 1;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ return False;
+}
+
+/*
+ * Check whether the user described by "token" has access to share snum.
+ *
+ * This looks at "invalid users", "valid users" and "only user/username"
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool user_ok_token(const char *username, const char *domain,
+ struct nt_user_token *token, int snum)
+{
+ if (lp_invalid_users(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(snum),
+ token,
+ lp_invalid_users(snum))) {
+ DEBUG(10, ("User %s in 'invalid users'\n", username));
+ return False;
+ }
+ }
+
+ if (lp_valid_users(snum) != NULL) {
+ if (!token_contains_name_in_list(username, domain,
+ lp_servicename(snum), token,
+ lp_valid_users(snum))) {
+ DEBUG(10, ("User %s not in 'valid users'\n",
+ username));
+ return False;
+ }
+ }
+
+ if (lp_onlyuser(snum)) {
+ const char *list[2];
+ list[0] = lp_username(snum);
+ list[1] = NULL;
+ if ((list[0] == NULL) || (*list[0] == '\0')) {
+ DEBUG(0, ("'only user = yes' and no 'username ='\n"));
+ return False;
+ }
+ if (!token_contains_name_in_list(NULL, domain,
+ lp_servicename(snum),
+ token, list)) {
+ DEBUG(10, ("%s != 'username'\n", username));
+ return False;
+ }
+ }
+
+ DEBUG(10, ("user_ok_token: share %s is ok for unix user %s\n",
+ lp_servicename(snum), username));
+
+ return True;
+}
+
+/*
+ * Check whether the user described by "token" is restricted to read-only
+ * access on share snum.
+ *
+ * This looks at "invalid users", "valid users" and "only user/username"
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool is_share_read_only_for_token(const char *username,
+ const char *domain,
+ struct nt_user_token *token, int snum)
+{
+ bool result = lp_readonly(snum);
+
+ if (lp_readlist(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(snum), token,
+ lp_readlist(snum))) {
+ result = True;
+ }
+ }
+
+ if (lp_writelist(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(snum), token,
+ lp_writelist(snum))) {
+ result = False;
+ }
+ }
+
+ DEBUG(10,("is_share_read_only_for_user: share %s is %s for unix user "
+ "%s\n", lp_servicename(snum),
+ result ? "read-only" : "read-write", username));
+
+ return result;
+}
diff --git a/source3/smbd/srvstr.c b/source3/smbd/srvstr.c
new file mode 100644
index 0000000000..bae77d5d4b
--- /dev/null
+++ b/source3/smbd/srvstr.c
@@ -0,0 +1,77 @@
+/*
+ Unix SMB/CIFS implementation.
+ server specific string routines
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+
+ 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"
+extern int max_send;
+
+/* Make sure we can't write a string past the end of the buffer */
+
+size_t srvstr_push_fn(const char *function, unsigned int line,
+ const char *base_ptr, uint16 smb_flags2, void *dest,
+ const char *src, int dest_len, int flags)
+{
+ if (dest_len < 0) {
+ return 0;
+ }
+
+ /* 'normal' push into size-specified buffer */
+ return push_string_fn(function, line, base_ptr, smb_flags2, dest, src,
+ dest_len, flags);
+}
+
+/*******************************************************************
+ Add a string to the end of a smb_buf, adjusting bcc and smb_len.
+ Return the bytes added
+********************************************************************/
+
+ssize_t message_push_string(uint8 **outbuf, const char *str, int flags)
+{
+ size_t buf_size = smb_len(*outbuf) + 4;
+ size_t grow_size;
+ size_t result;
+ uint8 *tmp;
+
+ /*
+ * We need to over-allocate, now knowing what srvstr_push will
+ * actually use. This is very generous by incorporating potential
+ * padding, the terminating 0 and at most 4 chars per UTF-16 code
+ * point.
+ */
+ grow_size = (strlen(str) + 2) * 4;
+
+ if (!(tmp = TALLOC_REALLOC_ARRAY(NULL, *outbuf, uint8,
+ buf_size + grow_size))) {
+ DEBUG(0, ("talloc failed\n"));
+ return -1;
+ }
+
+ result = srvstr_push((char *)tmp, SVAL(tmp, smb_flg2),
+ tmp + buf_size, str, grow_size, flags);
+
+ if (result == (size_t)-1) {
+ DEBUG(0, ("srvstr_push failed\n"));
+ return -1;
+ }
+ set_message_bcc((char *)tmp, smb_buflen(tmp) + result);
+
+ *outbuf = tmp;
+
+ return result;
+}
diff --git a/source3/smbd/statcache.c b/source3/smbd/statcache.c
new file mode 100644
index 0000000000..72fed008a2
--- /dev/null
+++ b/source3/smbd/statcache.c
@@ -0,0 +1,391 @@
+/*
+ Unix SMB/CIFS implementation.
+ stat cache code
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1999-2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+ Copyright (C) Volker Lendecke 2007
+
+ 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"
+
+/****************************************************************************
+ Stat cache code used in unix_convert.
+*****************************************************************************/
+
+/**
+ * Add an entry into the stat cache.
+ *
+ * @param full_orig_name The original name as specified by the client
+ * @param orig_translated_path The name on our filesystem.
+ *
+ * @note Only the first strlen(orig_translated_path) characters are stored
+ * into the cache. This means that full_orig_name will be internally
+ * truncated.
+ *
+ */
+
+void stat_cache_add( const char *full_orig_name,
+ char *translated_path,
+ bool case_sensitive)
+{
+ size_t translated_path_length;
+ char *original_path;
+ size_t original_path_length;
+ char saved_char;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!lp_stat_cache()) {
+ return;
+ }
+
+ /*
+ * Don't cache trivial valid directory entries such as . and ..
+ */
+
+ if ((*full_orig_name == '\0')
+ || ISDOT(full_orig_name) || ISDOTDOT(full_orig_name)) {
+ return;
+ }
+
+ /*
+ * If we are in case insentive mode, we don't need to
+ * store names that need no translation - else, it
+ * would be a waste.
+ */
+
+ if (case_sensitive && (strcmp(full_orig_name, translated_path) == 0)) {
+ return;
+ }
+
+ /*
+ * Remove any trailing '/' characters from the
+ * translated path.
+ */
+
+ translated_path_length = strlen(translated_path);
+
+ if(translated_path[translated_path_length-1] == '/') {
+ translated_path_length--;
+ }
+
+ if(case_sensitive) {
+ original_path = talloc_strdup(ctx,full_orig_name);
+ } else {
+ original_path = talloc_strdup_upper(ctx,full_orig_name);
+ }
+
+ if (!original_path) {
+ return;
+ }
+
+ original_path_length = strlen(original_path);
+
+ if(original_path[original_path_length-1] == '/') {
+ original_path[original_path_length-1] = '\0';
+ original_path_length--;
+ }
+
+ if (original_path_length != translated_path_length) {
+ if (original_path_length < translated_path_length) {
+ DEBUG(0, ("OOPS - tried to store stat cache entry "
+ "for weird length paths [%s] %lu and [%s] %lu)!\n",
+ original_path,
+ (unsigned long)original_path_length,
+ translated_path,
+ (unsigned long)translated_path_length));
+ TALLOC_FREE(original_path);
+ return;
+ }
+
+ /* we only want to index by the first part of original_path,
+ up to the length of translated_path */
+
+ original_path[translated_path_length] = '\0';
+ original_path_length = translated_path_length;
+ }
+
+ /* Ensure we're null terminated. */
+ saved_char = translated_path[translated_path_length];
+ translated_path[translated_path_length] = '\0';
+
+ /*
+ * New entry or replace old entry.
+ */
+
+ memcache_add(
+ smbd_memcache(), STAT_CACHE,
+ data_blob_const(original_path, original_path_length),
+ data_blob_const(translated_path, translated_path_length + 1));
+
+ DEBUG(5,("stat_cache_add: Added entry (%lx:size %x) %s -> %s\n",
+ (unsigned long)translated_path,
+ (unsigned int)translated_path_length,
+ original_path,
+ translated_path));
+
+ translated_path[translated_path_length] = saved_char;
+ TALLOC_FREE(original_path);
+}
+
+/**
+ * Look through the stat cache for an entry
+ *
+ * @param conn A connection struct to do the stat() with.
+ * @param name The path we are attempting to cache, modified by this routine
+ * to be correct as far as the cache can tell us. We assume that
+ * it is a talloc'ed string from top of stack, we free it if
+ * necessary.
+ * @param dirpath The path as far as the stat cache told us. Also talloced
+ * from top of stack.
+ * @param start A pointer into name, for where to 'start' in fixing the rest
+ * of the name up.
+ * @param psd A stat buffer, NOT from the cache, but just a side-effect.
+ *
+ * @return True if we translated (and did a scuccessful stat on) the entire
+ * name.
+ *
+ */
+
+bool stat_cache_lookup(connection_struct *conn,
+ char **pp_name,
+ char **pp_dirpath,
+ char **pp_start,
+ SMB_STRUCT_STAT *pst)
+{
+ char *chk_name;
+ size_t namelen;
+ bool sizechanged = False;
+ unsigned int num_components = 0;
+ char *translated_path;
+ size_t translated_path_length;
+ DATA_BLOB data_val;
+ char *name;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ *pp_dirpath = NULL;
+ *pp_start = *pp_name;
+
+ if (!lp_stat_cache()) {
+ return False;
+ }
+
+ name = *pp_name;
+ namelen = strlen(name);
+
+ DO_PROFILE_INC(statcache_lookups);
+
+ /*
+ * Don't lookup trivial valid directory entries.
+ */
+ if ((*name == '\0') || ISDOT(name) || ISDOTDOT(name)) {
+ return False;
+ }
+
+ if (conn->case_sensitive) {
+ chk_name = talloc_strdup(ctx,name);
+ if (!chk_name) {
+ DEBUG(0, ("stat_cache_lookup: strdup failed!\n"));
+ return False;
+ }
+
+ } else {
+ chk_name = talloc_strdup_upper(ctx,name);
+ if (!chk_name) {
+ DEBUG(0, ("stat_cache_lookup: strdup_upper failed!\n"));
+ return False;
+ }
+
+ /*
+ * In some language encodings the length changes
+ * if we uppercase. We need to treat this differently
+ * below.
+ */
+ if (strlen(chk_name) != namelen) {
+ sizechanged = True;
+ }
+ }
+
+ while (1) {
+ char *sp;
+
+ data_val = data_blob_null;
+
+ if (memcache_lookup(
+ smbd_memcache(), STAT_CACHE,
+ data_blob_const(chk_name, strlen(chk_name)),
+ &data_val)) {
+ break;
+ }
+
+ DEBUG(10,("stat_cache_lookup: lookup failed for name [%s]\n",
+ chk_name ));
+ /*
+ * Didn't find it - remove last component for next try.
+ */
+ if (!(sp = strrchr_m(chk_name, '/'))) {
+ /*
+ * We reached the end of the name - no match.
+ */
+ DO_PROFILE_INC(statcache_misses);
+ TALLOC_FREE(chk_name);
+ return False;
+ }
+
+ *sp = '\0';
+
+ /*
+ * Count the number of times we have done this, we'll
+ * need it when reconstructing the string.
+ */
+
+ if (sizechanged) {
+ num_components++;
+ }
+
+ if ((*chk_name == '\0')
+ || ISDOT(chk_name) || ISDOTDOT(chk_name)) {
+ DO_PROFILE_INC(statcache_misses);
+ TALLOC_FREE(chk_name);
+ return False;
+ }
+ }
+
+ translated_path = talloc_strdup(ctx,(char *)data_val.data);
+ if (!translated_path) {
+ smb_panic("talloc failed");
+ }
+ translated_path_length = data_val.length - 1;
+
+ DEBUG(10,("stat_cache_lookup: lookup succeeded for name [%s] "
+ "-> [%s]\n", chk_name, translated_path ));
+ DO_PROFILE_INC(statcache_hits);
+
+ if (SMB_VFS_STAT(conn, translated_path, pst) != 0) {
+ /* Discard this entry - it doesn't exist in the filesystem. */
+ memcache_delete(smbd_memcache(), STAT_CACHE,
+ data_blob_const(chk_name, strlen(chk_name)));
+ TALLOC_FREE(chk_name);
+ TALLOC_FREE(translated_path);
+ return False;
+ }
+
+ if (!sizechanged) {
+ memcpy(*pp_name, translated_path,
+ MIN(namelen, translated_path_length));
+ } else {
+ if (num_components == 0) {
+ name = talloc_strndup(ctx, translated_path,
+ translated_path_length);
+ } else {
+ char *sp;
+
+ sp = strnrchr_m(name, '/', num_components);
+ if (sp) {
+ name = talloc_asprintf(ctx,"%.*s%s",
+ (int)translated_path_length,
+ translated_path, sp);
+ } else {
+ name = talloc_strndup(ctx,
+ translated_path,
+ translated_path_length);
+ }
+ }
+ if (name == NULL) {
+ /*
+ * TODO: Get us out of here with a real error message
+ */
+ smb_panic("talloc failed");
+ }
+ TALLOC_FREE(*pp_name);
+ *pp_name = name;
+ }
+
+
+ /* set pointer for 'where to start' on fixing the rest of the name */
+ *pp_start = &name[translated_path_length];
+ if (**pp_start == '/') {
+ ++*pp_start;
+ }
+
+ *pp_dirpath = translated_path;
+ TALLOC_FREE(chk_name);
+ return (namelen == translated_path_length);
+}
+
+/***************************************************************************
+ Tell all smbd's to delete an entry.
+**************************************************************************/
+
+void send_stat_cache_delete_message(const char *name)
+{
+#ifdef DEVELOPER
+ message_send_all(smbd_messaging_context(),
+ MSG_SMB_STAT_CACHE_DELETE,
+ name,
+ strlen(name)+1,
+ NULL);
+#endif
+}
+
+/***************************************************************************
+ Delete an entry.
+**************************************************************************/
+
+void stat_cache_delete(const char *name)
+{
+ char *lname = talloc_strdup_upper(talloc_tos(), name);
+
+ if (!lname) {
+ return;
+ }
+ DEBUG(10,("stat_cache_delete: deleting name [%s] -> %s\n",
+ lname, name ));
+
+ memcache_delete(smbd_memcache(), STAT_CACHE,
+ data_blob_const(lname, talloc_get_size(lname)-1));
+ TALLOC_FREE(lname);
+}
+
+/***************************************************************
+ Compute a hash value based on a string key value.
+ The function returns the bucket index number for the hashed key.
+ JRA. Use a djb-algorithm hash for speed.
+***************************************************************/
+
+unsigned int fast_string_hash(TDB_DATA *key)
+{
+ unsigned int n = 0;
+ const char *p;
+ for (p = (const char *)key->dptr; *p != '\0'; p++) {
+ n = ((n << 5) + n) ^ (unsigned int)(*p);
+ }
+ return n;
+}
+
+/***************************************************************************
+ Initializes or clears the stat cache.
+**************************************************************************/
+
+bool reset_stat_cache( void )
+{
+ if (!lp_stat_cache())
+ return True;
+
+ memcache_flush(smbd_memcache(), STAT_CACHE);
+
+ return True;
+}
diff --git a/source3/smbd/statvfs.c b/source3/smbd/statvfs.c
new file mode 100644
index 0000000000..0e9a2c2ebe
--- /dev/null
+++ b/source3/smbd/statvfs.c
@@ -0,0 +1,149 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS API's statvfs abstraction
+ Copyright (C) Alexander Bokovoy 2005
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006
+
+ 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"
+
+#if defined(LINUX) && defined(HAVE_FSID_INT)
+static int linux_statvfs(const char *path, vfs_statvfs_struct *statbuf)
+{
+ struct statvfs statvfs_buf;
+ int result;
+
+ result = statvfs(path, &statvfs_buf);
+
+ if (!result) {
+ statbuf->OptimalTransferSize = statvfs_buf.f_frsize;
+ statbuf->BlockSize = statvfs_buf.f_bsize;
+ statbuf->TotalBlocks = statvfs_buf.f_blocks;
+ statbuf->BlocksAvail = statvfs_buf.f_bfree;
+ statbuf->UserBlocksAvail = statvfs_buf.f_bavail;
+ statbuf->TotalFileNodes = statvfs_buf.f_files;
+ statbuf->FreeFileNodes = statvfs_buf.f_ffree;
+ statbuf->FsIdentifier = statvfs_buf.f_fsid;
+
+ /* Good defaults for Linux filesystems are case sensitive
+ * and case preserving.
+ */
+ statbuf->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+ }
+ return result;
+}
+#endif
+
+#if defined(DARWINOS)
+
+#include <sys/attr.h>
+
+static int darwin_fs_capabilities(const char * path)
+{
+ int caps = 0;
+ vol_capabilities_attr_t *vcaps;
+ struct attrlist attrlist;
+ char attrbuf[sizeof(u_int32_t) + sizeof(vol_capabilities_attr_t)];
+
+#define FORMAT_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_FORMAT] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_FORMAT] & (cap)) )
+
+#define INTERFACE_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_INTERFACES] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_INTERFACES] & (cap)) )
+
+ ZERO_STRUCT(attrlist);
+ attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attrlist.volattr = ATTR_VOL_CAPABILITIES;
+
+ if (getattrlist(path, &attrlist, attrbuf, sizeof(attrbuf), 0) != 0) {
+ DEBUG(0, ("getattrlist for %s capabilities failed: %s\n",
+ path, strerror(errno)));
+ /* Return no capabilities on failure. */
+ return 0;
+ }
+
+ vcaps =
+ (vol_capabilities_attr_t *)(attrbuf + sizeof(u_int32_t));
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_SPARSE_FILES)) {
+ caps |= FILE_SUPPORTS_SPARSE_FILES;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_SENSITIVE)) {
+ caps |= FILE_CASE_SENSITIVE_SEARCH;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_PRESERVING)) {
+ caps |= FILE_CASE_PRESERVED_NAMES;
+ }
+
+ if (INTERFACE_CAP(vcaps, VOL_CAP_INT_EXTENDED_SECURITY)) {
+ caps |= FILE_PERSISTENT_ACLS;
+ }
+
+ return caps;
+}
+
+static int darwin_statvfs(const char *path, vfs_statvfs_struct *statbuf)
+{
+ struct statfs sbuf;
+ int ret;
+
+ ret = statfs(path, &sbuf);
+ if (ret != 0) {
+ return ret;
+ }
+
+ statbuf->OptimalTransferSize = sbuf.f_iosize;
+ statbuf->BlockSize = sbuf.f_bsize;
+ statbuf->TotalBlocks = sbuf.f_blocks;
+ statbuf->BlocksAvail = sbuf.f_bfree;
+ statbuf->UserBlocksAvail = sbuf.f_bavail;
+ statbuf->TotalFileNodes = sbuf.f_files;
+ statbuf->FreeFileNodes = sbuf.f_ffree;
+ statbuf->FsIdentifier = *(SMB_BIG_UINT *)(&sbuf.f_fsid); /* Ick. */
+ statbuf->FsCapabilities = darwin_fs_capabilities(sbuf.f_mntonname);
+
+ return 0;
+}
+#endif
+
+/*
+ sys_statvfs() is an abstraction layer over system-dependent statvfs()/statfs()
+ for particular POSIX systems. Due to controversy of what is considered more important
+ between LSB and FreeBSD/POSIX.1 (IEEE Std 1003.1-2001) we need to abstract the interface
+ so that particular OS would use its preffered interface.
+*/
+int sys_statvfs(const char *path, vfs_statvfs_struct *statbuf)
+{
+#if defined(LINUX) && defined(HAVE_FSID_INT)
+ return linux_statvfs(path, statbuf);
+#elif defined(DARWINOS)
+ return darwin_statvfs(path, statbuf);
+#else
+ /* BB change this to return invalid level */
+#ifdef EOPNOTSUPP
+ return EOPNOTSUPP;
+#else
+ return -1;
+#endif /* EOPNOTSUPP */
+#endif /* LINUX */
+
+}
diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c
new file mode 100644
index 0000000000..2e2da5cc71
--- /dev/null
+++ b/source3/smbd/trans2.c
@@ -0,0 +1,7846 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ 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"
+
+extern int max_send;
+extern enum protocol_types Protocol;
+extern uint32 global_client_caps;
+
+#define get_file_size(sbuf) ((sbuf).st_size)
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+static char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+
+static char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+
+/********************************************************************
+ Roundup a value to the nearest allocation roundup size boundary.
+ Only do this for Windows clients.
+********************************************************************/
+
+SMB_BIG_UINT smb_roundup(connection_struct *conn, SMB_BIG_UINT val)
+{
+ SMB_BIG_UINT rval = lp_allocation_roundup_size(SNUM(conn));
+
+ /* Only roundup for Windows clients. */
+ enum remote_arch_types ra_type = get_remote_arch();
+ if (rval && (ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ val = SMB_ROUNDUP(val,rval);
+ }
+ return val;
+}
+
+/********************************************************************
+ Given a stat buffer return the allocated size on disk, taking into
+ account sparse files.
+********************************************************************/
+
+SMB_BIG_UINT get_allocation_size(connection_struct *conn, files_struct *fsp, const SMB_STRUCT_STAT *sbuf)
+{
+ SMB_BIG_UINT ret;
+
+ if(S_ISDIR(sbuf->st_mode)) {
+ return 0;
+ }
+
+#if defined(HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
+ ret = (SMB_BIG_UINT)STAT_ST_BLOCKSIZE * (SMB_BIG_UINT)sbuf->st_blocks;
+#else
+ ret = (SMB_BIG_UINT)get_file_size(*sbuf);
+#endif
+
+ if (fsp && fsp->initial_allocation_size)
+ ret = MAX(ret,fsp->initial_allocation_size);
+
+ return smb_roundup(conn, ret);
+}
+
+/****************************************************************************
+ Utility functions for dealing with extended attributes.
+****************************************************************************/
+
+/****************************************************************************
+ Refuse to allow clients to overwrite our private xattrs.
+****************************************************************************/
+
+static bool samba_private_attr_name(const char *unix_ea_name)
+{
+ static const char *prohibited_ea_names[] = {
+ SAMBA_POSIX_INHERITANCE_EA_NAME,
+ SAMBA_XATTR_DOS_ATTRIB,
+ NULL
+ };
+
+ int i;
+
+ for (i = 0; prohibited_ea_names[i]; i++) {
+ if (strequal( prohibited_ea_names[i], unix_ea_name))
+ return true;
+ }
+ if (StrnCaseCmp(unix_ea_name, SAMBA_XATTR_DOSSTREAM_PREFIX,
+ strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) == 0) {
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Get one EA value. Fill in a struct ea_struct.
+****************************************************************************/
+
+NTSTATUS get_ea_value(TALLOC_CTX *mem_ctx, connection_struct *conn,
+ files_struct *fsp, const char *fname,
+ const char *ea_name, struct ea_struct *pea)
+{
+ /* Get the value of this xattr. Max size is 64k. */
+ size_t attr_size = 256;
+ char *val = NULL;
+ ssize_t sizeret;
+
+ again:
+
+ val = TALLOC_REALLOC_ARRAY(mem_ctx, val, char, attr_size);
+ if (!val) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (fsp && fsp->fh->fd != -1) {
+ sizeret = SMB_VFS_FGETXATTR(fsp, ea_name, val, attr_size);
+ } else {
+ sizeret = SMB_VFS_GETXATTR(conn, fname, ea_name, val, attr_size);
+ }
+
+ if (sizeret == -1 && errno == ERANGE && attr_size != 65536) {
+ attr_size = 65536;
+ goto again;
+ }
+
+ if (sizeret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10,("get_ea_value: EA %s is of length %u\n", ea_name, (unsigned int)sizeret));
+ dump_data(10, (uint8 *)val, sizeret);
+
+ pea->flags = 0;
+ if (strnequal(ea_name, "user.", 5)) {
+ pea->name = talloc_strdup(mem_ctx, &ea_name[5]);
+ } else {
+ pea->name = talloc_strdup(mem_ctx, ea_name);
+ }
+ if (pea->name == NULL) {
+ TALLOC_FREE(val);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pea->value.data = (unsigned char *)val;
+ pea->value.length = (size_t)sizeret;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS get_ea_names_from_file(TALLOC_CTX *mem_ctx, connection_struct *conn,
+ files_struct *fsp, const char *fname,
+ char ***pnames, size_t *pnum_names)
+{
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t ea_namelist_size = 1024;
+ char *ea_namelist = NULL;
+
+ char *p;
+ char **names, **tmp;
+ size_t num_names;
+ ssize_t sizeret = -1;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ *pnames = NULL;
+ *pnum_names = 0;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * TALLOC the result early to get the talloc hierarchy right.
+ */
+
+ names = TALLOC_ARRAY(mem_ctx, char *, 1);
+ if (names == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ while (ea_namelist_size <= 65536) {
+
+ ea_namelist = TALLOC_REALLOC_ARRAY(
+ names, ea_namelist, char, ea_namelist_size);
+ if (ea_namelist == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (fsp && fsp->fh->fd != -1) {
+ sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist,
+ ea_namelist_size);
+ } else {
+ sizeret = SMB_VFS_LISTXATTR(conn, fname, ea_namelist,
+ ea_namelist_size);
+ }
+
+ if ((sizeret == -1) && (errno == ERANGE)) {
+ ea_namelist_size *= 2;
+ }
+ else {
+ break;
+ }
+ }
+
+ if (sizeret == -1) {
+ TALLOC_FREE(names);
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10, ("get_ea_list_from_file: ea_namelist size = %u\n",
+ (unsigned int)sizeret));
+
+ if (sizeret == 0) {
+ TALLOC_FREE(names);
+ *pnames = NULL;
+ *pnum_names = 0;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Ensure the result is 0-terminated
+ */
+
+ if (ea_namelist[sizeret-1] != '\0') {
+ TALLOC_FREE(names);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * count the names
+ */
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ num_names += 1;
+ }
+
+ tmp = TALLOC_REALLOC_ARRAY(mem_ctx, names, char *, num_names);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ names = tmp;
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ names[num_names++] = p;
+ }
+
+ *pnames = names;
+ *pnum_names = num_names;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Return a linked list of the total EA's. Plus the total size
+****************************************************************************/
+
+static struct ea_list *get_ea_list_from_file(TALLOC_CTX *mem_ctx, connection_struct *conn, files_struct *fsp,
+ const char *fname, size_t *pea_total_len)
+{
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t i, num_names;
+ char **names;
+ struct ea_list *ea_list_head = NULL;
+ NTSTATUS status;
+
+ *pea_total_len = 0;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return NULL;
+ }
+
+ status = get_ea_names_from_file(talloc_tos(), conn, fsp, fname,
+ &names, &num_names);
+
+ if (!NT_STATUS_IS_OK(status) || (num_names == 0)) {
+ return NULL;
+ }
+
+ for (i=0; i<num_names; i++) {
+ struct ea_list *listp;
+ fstring dos_ea_name;
+
+ if (strnequal(names[i], "system.", 7)
+ || samba_private_attr_name(names[i]))
+ continue;
+
+ listp = TALLOC_P(mem_ctx, struct ea_list);
+ if (listp == NULL) {
+ return NULL;
+ }
+
+ if (!NT_STATUS_IS_OK(get_ea_value(mem_ctx, conn, fsp,
+ fname, names[i],
+ &listp->ea))) {
+ return NULL;
+ }
+
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+
+ *pea_total_len +=
+ 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+
+ DEBUG(10,("get_ea_list_from_file: total_len = %u, %s, val len "
+ "= %u\n", (unsigned int)*pea_total_len, dos_ea_name,
+ (unsigned int)listp->ea.value.length));
+
+ DLIST_ADD_END(ea_list_head, listp, struct ea_list *);
+
+ }
+
+ /* Add on 4 for total length. */
+ if (*pea_total_len) {
+ *pea_total_len += 4;
+ }
+
+ DEBUG(10, ("get_ea_list_from_file: total_len = %u\n",
+ (unsigned int)*pea_total_len));
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Fill a qfilepathinfo buffer with EA's. Returns the length of the buffer
+ that was filled.
+****************************************************************************/
+
+static unsigned int fill_ea_buffer(TALLOC_CTX *mem_ctx, char *pdata, unsigned int total_data_size,
+ connection_struct *conn, struct ea_list *ea_list)
+{
+ unsigned int ret_data_size = 4;
+ char *p = pdata;
+
+ SMB_ASSERT(total_data_size >= 4);
+
+ if (!lp_ea_support(SNUM(conn))) {
+ SIVAL(pdata,4,0);
+ return 4;
+ }
+
+ for (p = pdata + 4; ea_list; ea_list = ea_list->next) {
+ size_t dos_namelen;
+ fstring dos_ea_name;
+ push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+ dos_namelen = strlen(dos_ea_name);
+ if (dos_namelen > 255 || dos_namelen == 0) {
+ break;
+ }
+ if (ea_list->ea.value.length > 65535) {
+ break;
+ }
+ if (4 + dos_namelen + 1 + ea_list->ea.value.length > total_data_size) {
+ break;
+ }
+
+ /* We know we have room. */
+ SCVAL(p,0,ea_list->ea.flags);
+ SCVAL(p,1,dos_namelen);
+ SSVAL(p,2,ea_list->ea.value.length);
+ fstrcpy(p+4, dos_ea_name);
+ memcpy( p + 4 + dos_namelen + 1, ea_list->ea.value.data, ea_list->ea.value.length);
+
+ total_data_size -= 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ p += 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ }
+
+ ret_data_size = PTR_DIFF(p, pdata);
+ DEBUG(10,("fill_ea_buffer: data_size = %u\n", ret_data_size ));
+ SIVAL(pdata,0,ret_data_size);
+ return ret_data_size;
+}
+
+static unsigned int estimate_ea_size(connection_struct *conn, files_struct *fsp, const char *fname)
+{
+ size_t total_ea_len = 0;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return 0;
+ }
+ mem_ctx = talloc_tos();
+ (void)get_ea_list_from_file(mem_ctx, conn, fsp, fname, &total_ea_len);
+ return total_ea_len;
+}
+
+/****************************************************************************
+ Ensure the EA name is case insensitive by matching any existing EA name.
+****************************************************************************/
+
+static void canonicalize_ea_name(connection_struct *conn, files_struct *fsp, const char *fname, fstring unix_ea_name)
+{
+ size_t total_ea_len;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct ea_list *ea_list = get_ea_list_from_file(mem_ctx, conn, fsp, fname, &total_ea_len);
+
+ for (; ea_list; ea_list = ea_list->next) {
+ if (strequal(&unix_ea_name[5], ea_list->ea.name)) {
+ DEBUG(10,("canonicalize_ea_name: %s -> %s\n",
+ &unix_ea_name[5], ea_list->ea.name));
+ safe_strcpy(&unix_ea_name[5], ea_list->ea.name, sizeof(fstring)-6);
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ Set or delete an extended attribute.
+****************************************************************************/
+
+NTSTATUS set_ea(connection_struct *conn, files_struct *fsp, const char *fname, struct ea_list *ea_list)
+{
+ if (!lp_ea_support(SNUM(conn))) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+
+ for (;ea_list; ea_list = ea_list->next) {
+ int ret;
+ fstring unix_ea_name;
+
+ fstrcpy(unix_ea_name, "user."); /* All EA's must start with user. */
+ fstrcat(unix_ea_name, ea_list->ea.name);
+
+ canonicalize_ea_name(conn, fsp, fname, unix_ea_name);
+
+ DEBUG(10,("set_ea: ea_name %s ealen = %u\n", unix_ea_name, (unsigned int)ea_list->ea.value.length));
+
+ if (samba_private_attr_name(unix_ea_name)) {
+ DEBUG(10,("set_ea: ea name %s is a private Samba name.\n", unix_ea_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (ea_list->ea.value.length == 0) {
+ /* Remove the attribute. */
+ if (fsp && (fsp->fh->fd != -1)) {
+ DEBUG(10,("set_ea: deleting ea name %s on file %s by file descriptor.\n",
+ unix_ea_name, fsp->fsp_name));
+ ret = SMB_VFS_FREMOVEXATTR(fsp, unix_ea_name);
+ } else {
+ DEBUG(10,("set_ea: deleting ea name %s on file %s.\n",
+ unix_ea_name, fname));
+ ret = SMB_VFS_REMOVEXATTR(conn, fname, unix_ea_name);
+ }
+#ifdef ENOATTR
+ /* Removing a non existent attribute always succeeds. */
+ if (ret == -1 && errno == ENOATTR) {
+ DEBUG(10,("set_ea: deleting ea name %s didn't exist - succeeding by default.\n",
+ unix_ea_name));
+ ret = 0;
+ }
+#endif
+ } else {
+ if (fsp && (fsp->fh->fd != -1)) {
+ DEBUG(10,("set_ea: setting ea name %s on file %s by file descriptor.\n",
+ unix_ea_name, fsp->fsp_name));
+ ret = SMB_VFS_FSETXATTR(fsp, unix_ea_name,
+ ea_list->ea.value.data, ea_list->ea.value.length, 0);
+ } else {
+ DEBUG(10,("set_ea: setting ea name %s on file %s.\n",
+ unix_ea_name, fname));
+ ret = SMB_VFS_SETXATTR(conn, fname, unix_ea_name,
+ ea_list->ea.value.data, ea_list->ea.value.length, 0);
+ }
+ }
+
+ if (ret == -1) {
+#ifdef ENOTSUP
+ if (errno == ENOTSUP) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+#endif
+ return map_nt_error_from_unix(errno);
+ }
+
+ }
+ return NT_STATUS_OK;
+}
+/****************************************************************************
+ Read a list of EA names from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+static struct ea_list *read_ea_name_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t converted_size, offset = 0;
+
+ while (offset + 2 < data_size) {
+ struct ea_list *eal = TALLOC_ZERO_P(ctx, struct ea_list);
+ unsigned int namelen = CVAL(pdata,offset);
+
+ offset++; /* Go past the namelen byte. */
+
+ /* integer wrap paranioa. */
+ if ((offset + namelen < offset) || (offset + namelen < namelen) ||
+ (offset > data_size) || (namelen > data_size) ||
+ (offset + namelen >= data_size)) {
+ break;
+ }
+ /* Ensure the name is null terminated. */
+ if (pdata[offset + namelen] != '\0') {
+ return NULL;
+ }
+ if (!pull_ascii_talloc(ctx, &eal->ea.name, &pdata[offset],
+ &converted_size)) {
+ DEBUG(0,("read_ea_name_list: pull_ascii_talloc "
+ "failed: %s", strerror(errno)));
+ }
+ if (!eal->ea.name) {
+ return NULL;
+ }
+
+ offset += (namelen + 1); /* Go past the name + terminating zero. */
+ DLIST_ADD_END(ea_list_head, eal, struct ea_list *);
+ DEBUG(10,("read_ea_name_list: read ea name %s\n", eal->ea.name));
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Read one EA list entry from the buffer.
+****************************************************************************/
+
+struct ea_list *read_ea_list_entry(TALLOC_CTX *ctx, const char *pdata, size_t data_size, size_t *pbytes_used)
+{
+ struct ea_list *eal = TALLOC_ZERO_P(ctx, struct ea_list);
+ uint16 val_len;
+ unsigned int namelen;
+ size_t converted_size;
+
+ if (!eal) {
+ return NULL;
+ }
+
+ if (data_size < 6) {
+ return NULL;
+ }
+
+ eal->ea.flags = CVAL(pdata,0);
+ namelen = CVAL(pdata,1);
+ val_len = SVAL(pdata,2);
+
+ if (4 + namelen + 1 + val_len > data_size) {
+ return NULL;
+ }
+
+ /* Ensure the name is null terminated. */
+ if (pdata[namelen + 4] != '\0') {
+ return NULL;
+ }
+ if (!pull_ascii_talloc(ctx, &eal->ea.name, pdata + 4, &converted_size)) {
+ DEBUG(0,("read_ea_list_entry: pull_ascii_talloc failed: %s",
+ strerror(errno)));
+ }
+ if (!eal->ea.name) {
+ return NULL;
+ }
+
+ eal->ea.value = data_blob_talloc(eal, NULL, (size_t)val_len + 1);
+ if (!eal->ea.value.data) {
+ return NULL;
+ }
+
+ memcpy(eal->ea.value.data, pdata + 4 + namelen + 1, val_len);
+
+ /* Ensure we're null terminated just in case we print the value. */
+ eal->ea.value.data[val_len] = '\0';
+ /* But don't count the null. */
+ eal->ea.value.length--;
+
+ if (pbytes_used) {
+ *pbytes_used = 4 + namelen + 1 + val_len;
+ }
+
+ DEBUG(10,("read_ea_list_entry: read ea name %s\n", eal->ea.name));
+ dump_data(10, eal->ea.value.data, eal->ea.value.length);
+
+ return eal;
+}
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+static struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t offset = 0;
+ size_t bytes_used = 0;
+
+ while (offset < data_size) {
+ struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset, data_size - offset, &bytes_used);
+
+ if (!eal) {
+ return NULL;
+ }
+
+ DLIST_ADD_END(ea_list_head, eal, struct ea_list *);
+ offset += bytes_used;
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Count the total EA size needed.
+****************************************************************************/
+
+static size_t ea_list_size(struct ea_list *ealist)
+{
+ fstring dos_ea_name;
+ struct ea_list *listp;
+ size_t ret = 0;
+
+ for (listp = ealist; listp; listp = listp->next) {
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+ ret += 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+ }
+ /* Add on 4 for total length. */
+ if (ret) {
+ ret += 4;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Return a union of EA's from a file list and a list of names.
+ The TALLOC context for the two lists *MUST* be identical as we steal
+ memory from one list to add to another. JRA.
+****************************************************************************/
+
+static struct ea_list *ea_list_union(struct ea_list *name_list, struct ea_list *file_list, size_t *total_ea_len)
+{
+ struct ea_list *nlistp, *flistp;
+
+ for (nlistp = name_list; nlistp; nlistp = nlistp->next) {
+ for (flistp = file_list; flistp; flistp = flistp->next) {
+ if (strequal(nlistp->ea.name, flistp->ea.name)) {
+ break;
+ }
+ }
+
+ if (flistp) {
+ /* Copy the data from this entry. */
+ nlistp->ea.flags = flistp->ea.flags;
+ nlistp->ea.value = flistp->ea.value;
+ } else {
+ /* Null entry. */
+ nlistp->ea.flags = 0;
+ ZERO_STRUCT(nlistp->ea.value);
+ }
+ }
+
+ *total_ea_len = ea_list_size(name_list);
+ return name_list;
+}
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+void send_trans2_replies(connection_struct *conn,
+ struct smb_request *req,
+ const char *params,
+ int paramsize,
+ const char *pdata,
+ int datasize,
+ int max_data_bytes)
+{
+ /* As we are using a protocol > LANMAN1 then the max_send
+ variable must have been set in the sessetupX call.
+ This takes precedence over the max_xmit field in the
+ global struct. These different max_xmit variables should
+ be merged as this is now too confusing */
+
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ const char *pp = params;
+ const char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 1; /* JRA. This used to be 3. Set to 1 to make netmon parse ok. */
+ int data_alignment_offset = 0;
+ bool overflow = False;
+
+ /* Modify the data_to_send and datasize and set the error if
+ we're trying to send more than max_data_bytes. We still send
+ the part of the packet(s) that fit. Strange, but needed
+ for OS/2. */
+
+ if (max_data_bytes > 0 && datasize > max_data_bytes) {
+ DEBUG(5,("send_trans2_replies: max_data_bytes %d exceeded by data %d\n",
+ max_data_bytes, datasize ));
+ datasize = data_to_send = max_data_bytes;
+ overflow = True;
+ }
+
+ /* If there genuinely are no parameters or data to send just send the empty packet */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_outbuf(req, 10, 0);
+ show_msg((char *)req->outbuf);
+ return;
+ }
+
+ /* When sending params and data ensure that both are nicely aligned */
+ /* Only do this alignment when there is also data to send - else
+ can cause NT redirector problems. */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0))
+ data_alignment_offset = 4 - (params_to_send % 4);
+
+ /* Space is bufsize minus Netbios over TCP header minus SMB header */
+ /* The alignment_offset is to align the param bytes on an even byte
+ boundary. NT 4.0 Beta needs this to work correctly. */
+
+ useable_space = max_send - (smb_size
+ + 2 * 10 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ DEBUG(0, ("send_trans2_replies failed sanity useable_space "
+ "= %d!!!", useable_space));
+ exit_server_cleanly("send_trans2_replies: Not enough space");
+ }
+
+ while (params_to_send || data_to_send) {
+ /* Calculate whether we will totally or partially fill this packet */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /* We can never send more than useable_space */
+ /*
+ * Note that 'useable_space' does not include the alignment offsets,
+ * but we must include the alignment offsets in the calculation of
+ * the length of the data we send over the wire, as the alignment offsets
+ * are sent here. Fix from Marc_Jacobsen@hp.com.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_outbuf(req, 10, total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /* Set total params and data to be sent */
+ SSVAL(req->outbuf,smb_tprcnt,paramsize);
+ SSVAL(req->outbuf,smb_tdrcnt,datasize);
+
+ /* Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SSVAL(req->outbuf,smb_prcnt, params_sent_thistime);
+
+ /* smb_proff is the offset from the start of the SMB header to the
+ parameter bytes, however the first 4 bytes of outbuf are
+ the Netbios over TCP header. Thus use smb_base() to subtract
+ them from the calculation */
+
+ SSVAL(req->outbuf,smb_proff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+
+ if(params_sent_thistime == 0)
+ SSVAL(req->outbuf,smb_prdisp,0);
+ else
+ /* Absolute displacement of param bytes sent in this packet */
+ SSVAL(req->outbuf,smb_prdisp,pp - params);
+
+ SSVAL(req->outbuf,smb_drcnt, data_sent_thistime);
+ if(data_sent_thistime == 0) {
+ SSVAL(req->outbuf,smb_droff,0);
+ SSVAL(req->outbuf,smb_drdisp, 0);
+ } else {
+ /* The offset of the data bytes is the offset of the
+ parameter bytes plus the number of parameters being sent this time */
+ SSVAL(req->outbuf, smb_droff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SSVAL(req->outbuf,smb_drdisp, pd - pdata);
+ }
+
+ /* Initialize the padding for alignment */
+
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0, alignment_offset);
+ }
+
+ /* Copy the param bytes into the packet */
+
+ if(params_sent_thistime) {
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /* Copy in the data bytes */
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("t2_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("t2_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (overflow) {
+ error_packet_set((char *)req->outbuf,
+ ERRDOS,ERRbufferoverflow,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!srv_send_smb(smbd_server_fd(),
+ (char *)req->outbuf,
+ IS_CONN_ENCRYPTED(conn)))
+ exit_server_cleanly("send_trans2_replies: srv_send_smb failed.");
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /* Sanity check */
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_trans2_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ return;
+ }
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_OPEN.
+****************************************************************************/
+
+static void call_trans2open(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ int deny_mode;
+ int32 open_attr;
+ bool oplock_request;
+#if 0
+ bool return_additional_info;
+ int16 open_sattr;
+ time_t open_time;
+#endif
+ int open_ofun;
+ uint32 open_size;
+ char *pname;
+ char *fname = NULL;
+ SMB_OFF_T size=0;
+ int fattr=0,mtime=0;
+ SMB_INO_T inode = 0;
+ SMB_STRUCT_STAT sbuf;
+ int smb_action = 0;
+ files_struct *fsp;
+ struct ea_list *ea_list = NULL;
+ uint16 flags = 0;
+ NTSTATUS status;
+ uint32 access_mask;
+ uint32 share_mode;
+ uint32 create_disposition;
+ uint32 create_options = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure we have enough parameters to perform the operation.
+ */
+
+ if (total_params < 29) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ flags = SVAL(params, 0);
+ deny_mode = SVAL(params, 2);
+ open_attr = SVAL(params,6);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+#if 0
+ return_additional_info = BITSETW(params,0);
+ open_sattr = SVAL(params, 4);
+ open_time = make_unix_date3(params+8);
+#endif
+ open_ofun = SVAL(params,12);
+ open_size = IVAL(params,14);
+ pname = &params[28];
+
+ if (IS_IPC(conn)) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+
+ srvstr_get_path(ctx, params, req->flags2, &fname, pname,
+ total_params - 28, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DEBUG(3,("call_trans2open %s deny_mode=0x%x attr=%d ofun=0x%x size=%d\n",
+ fname, (unsigned int)deny_mode, (unsigned int)open_attr,
+ (unsigned int)open_ofun, open_size));
+
+ if (open_ofun == 0) {
+ reply_nterror(req, NT_STATUS_OBJECT_NAME_COLLISION);
+ return;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, deny_mode, open_ofun,
+ &access_mask,
+ &share_mode,
+ &create_disposition,
+ &create_options)) {
+ reply_doserror(req, ERRDOS, ERRbadaccess);
+ return;
+ }
+
+ /* Any data in this call is an EA list. */
+ if (total_data && (total_data != 4) && !lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ if (total_data != 4) {
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2open: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ } else if (IVAL(pdata,0) != 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ status = create_file(conn, /* conn */
+ req, /* req */
+ 0, /* root_dir_fid */
+ fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ open_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ open_size, /* allocation_size */
+ NULL, /* sd */
+ ea_list, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* pinfo */
+ &sbuf); /* psbuf */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ reply_openerror(req, status);
+ return;
+ }
+
+ size = get_file_size(sbuf);
+ fattr = dos_mode(conn,fsp->fsp_name,&sbuf);
+ mtime = sbuf.st_mtime;
+ inode = sbuf.st_ino;
+ if (fattr & aDIR) {
+ close_file(fsp,ERROR_CLOSE);
+ reply_doserror(req, ERRDOS,ERRnoaccess);
+ return;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ *pparams = (char *)SMB_REALLOC(*pparams, 30);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fsp->fnum);
+ SSVAL(params,2,fattr);
+ srv_put_dos_date2(params,4, mtime);
+ SIVAL(params,8, (uint32)size);
+ SSVAL(params,12,deny_mode);
+ SSVAL(params,14,0); /* open_type - file or directory. */
+ SSVAL(params,16,0); /* open_state - only valid for IPC device. */
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ SSVAL(params,18,smb_action);
+
+ /*
+ * WARNING - this may need to be changed if SMB_INO_T <> 4 bytes.
+ */
+ SIVAL(params,20,inode);
+ SSVAL(params,24,0); /* Padding. */
+ if (flags & 8) {
+ uint32 ea_size = estimate_ea_size(conn, fsp, fsp->fsp_name);
+ SIVAL(params, 26, ea_size);
+ } else {
+ SIVAL(params, 26, 0);
+ }
+
+ /* Send the required number of replies */
+ send_trans2_replies(conn, req, params, 30, *ppdata, 0, max_data_bytes);
+}
+
+/*********************************************************
+ Routine to check if a given string matches exactly.
+ as a special case a mask of "." does NOT match. That
+ is required for correct wildcard semantics
+ Case can be significant or not.
+**********************************************************/
+
+static bool exact_match(connection_struct *conn,
+ const char *str,
+ const char *mask)
+{
+ if (mask[0] == '.' && mask[1] == 0)
+ return False;
+ if (conn->case_sensitive)
+ return strcmp(str,mask)==0;
+ if (StrCaseCmp(str,mask) != 0) {
+ return False;
+ }
+ if (dptr_has_wild(conn->dirptr)) {
+ return False;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Return the filetype for UNIX extensions.
+****************************************************************************/
+
+static uint32 unix_filetype(mode_t mode)
+{
+ if(S_ISREG(mode))
+ return UNIX_TYPE_FILE;
+ else if(S_ISDIR(mode))
+ return UNIX_TYPE_DIR;
+#ifdef S_ISLNK
+ else if(S_ISLNK(mode))
+ return UNIX_TYPE_SYMLINK;
+#endif
+#ifdef S_ISCHR
+ else if(S_ISCHR(mode))
+ return UNIX_TYPE_CHARDEV;
+#endif
+#ifdef S_ISBLK
+ else if(S_ISBLK(mode))
+ return UNIX_TYPE_BLKDEV;
+#endif
+#ifdef S_ISFIFO
+ else if(S_ISFIFO(mode))
+ return UNIX_TYPE_FIFO;
+#endif
+#ifdef S_ISSOCK
+ else if(S_ISSOCK(mode))
+ return UNIX_TYPE_SOCKET;
+#endif
+
+ DEBUG(0,("unix_filetype: unknown filetype %u", (unsigned)mode));
+ return UNIX_TYPE_UNKNOWN;
+}
+
+/****************************************************************************
+ Map wire perms onto standard UNIX permissions. Obey share restrictions.
+****************************************************************************/
+
+enum perm_type { PERM_NEW_FILE, PERM_NEW_DIR, PERM_EXISTING_FILE, PERM_EXISTING_DIR};
+
+static NTSTATUS unix_perms_from_wire( connection_struct *conn,
+ SMB_STRUCT_STAT *psbuf,
+ uint32 perms,
+ enum perm_type ptype,
+ mode_t *ret_perms)
+{
+ mode_t ret = 0;
+
+ if (perms == SMB_MODE_NO_CHANGE) {
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ } else {
+ *ret_perms = psbuf->st_mode;
+ return NT_STATUS_OK;
+ }
+ }
+
+ ret |= ((perms & UNIX_X_OTH ) ? S_IXOTH : 0);
+ ret |= ((perms & UNIX_W_OTH ) ? S_IWOTH : 0);
+ ret |= ((perms & UNIX_R_OTH ) ? S_IROTH : 0);
+ ret |= ((perms & UNIX_X_GRP ) ? S_IXGRP : 0);
+ ret |= ((perms & UNIX_W_GRP ) ? S_IWGRP : 0);
+ ret |= ((perms & UNIX_R_GRP ) ? S_IRGRP : 0);
+ ret |= ((perms & UNIX_X_USR ) ? S_IXUSR : 0);
+ ret |= ((perms & UNIX_W_USR ) ? S_IWUSR : 0);
+ ret |= ((perms & UNIX_R_USR ) ? S_IRUSR : 0);
+#ifdef S_ISVTX
+ ret |= ((perms & UNIX_STICKY ) ? S_ISVTX : 0);
+#endif
+#ifdef S_ISGID
+ ret |= ((perms & UNIX_SET_GID ) ? S_ISGID : 0);
+#endif
+#ifdef S_ISUID
+ ret |= ((perms & UNIX_SET_UID ) ? S_ISUID : 0);
+#endif
+
+ switch (ptype) {
+ case PERM_NEW_FILE:
+ /* Apply mode mask */
+ ret &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_create_mode(SNUM(conn));
+ break;
+ case PERM_NEW_DIR:
+ ret &= lp_dir_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_dir_mode(SNUM(conn));
+ break;
+ case PERM_EXISTING_FILE:
+ /* Apply mode mask */
+ ret &= lp_security_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_security_mode(SNUM(conn));
+ break;
+ case PERM_EXISTING_DIR:
+ /* Apply mode mask */
+ ret &= lp_dir_security_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_dir_security_mode(SNUM(conn));
+ break;
+ }
+
+ *ret_perms = ret;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Needed to show the msdfs symlinks as directories. Modifies psbuf
+ to be a directory if it's a msdfs link.
+****************************************************************************/
+
+static bool check_msdfs_link(connection_struct *conn,
+ const char *pathname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ int saved_errno = errno;
+ if(lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn)) &&
+ is_msdfs_link(conn, pathname, psbuf)) {
+
+ DEBUG(5,("check_msdfs_link: Masquerading msdfs link %s "
+ "as a directory\n",
+ pathname));
+ psbuf->st_mode = (psbuf->st_mode & 0xFFF) | S_IFDIR;
+ errno = saved_errno;
+ return true;
+ }
+ errno = saved_errno;
+ return false;
+}
+
+
+/****************************************************************************
+ Get a level dependent lanman2 dir entry.
+****************************************************************************/
+
+static bool get_lanman2_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ uint16 flags2,
+ const char *path_mask,
+ uint32 dirtype,
+ int info_level,
+ int requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ bool *out_of_space,
+ bool *got_exact_match,
+ int *last_entry_off,
+ struct ea_list *name_list)
+{
+ const char *dname;
+ bool found = False;
+ SMB_STRUCT_STAT sbuf;
+ const char *mask = NULL;
+ char *pathreal = NULL;
+ const char *fname = NULL;
+ char *p, *q, *pdata = *ppdata;
+ uint32 reskey=0;
+ long prev_dirpos=0;
+ uint32 mode=0;
+ SMB_OFF_T file_size = 0;
+ SMB_BIG_UINT allocation_size = 0;
+ uint32 len;
+ struct timespec mdate_ts, adate_ts, create_date_ts;
+ time_t mdate = (time_t)0, adate = (time_t)0, create_date = (time_t)0;
+ char *nameptr;
+ char *last_entry_ptr;
+ bool was_8_3;
+ uint32 nt_extmode; /* Used for NT connections instead of mode */
+ bool needslash = ( conn->dirpath[strlen(conn->dirpath) -1] != '/');
+ bool check_mangled_names = lp_manglednames(conn->params);
+ char mangled_name[13]; /* mangled 8.3 name. */
+
+ *out_of_space = False;
+ *got_exact_match = False;
+
+ ZERO_STRUCT(mdate_ts);
+ ZERO_STRUCT(adate_ts);
+ ZERO_STRUCT(create_date_ts);
+
+ if (!conn->dirptr) {
+ return(False);
+ }
+
+ p = strrchr_m(path_mask,'/');
+ if(p != NULL) {
+ if(p[1] == '\0') {
+ mask = talloc_strdup(ctx,"*.*");
+ } else {
+ mask = p+1;
+ }
+ } else {
+ mask = path_mask;
+ }
+
+ while (!found) {
+ bool got_match;
+ bool ms_dfs_link = False;
+
+ /* Needed if we run out of space */
+ long curr_dirpos = prev_dirpos = dptr_TellDir(conn->dirptr);
+ dname = dptr_ReadDirName(ctx,conn->dirptr,&curr_dirpos,&sbuf);
+
+ /*
+ * Due to bugs in NT client redirectors we are not using
+ * resume keys any more - set them to zero.
+ * Check out the related comments in findfirst/findnext.
+ * JRA.
+ */
+
+ reskey = 0;
+
+ DEBUG(8,("get_lanman2_dir_entry:readdir on dirptr 0x%lx now at offset %ld\n",
+ (long)conn->dirptr,curr_dirpos));
+
+ if (!dname) {
+ return(False);
+ }
+
+ /*
+ * fname may get mangled, dname is never mangled.
+ * Whenever we're accessing the filesystem we use
+ * pathreal which is composed from dname.
+ */
+
+ pathreal = NULL;
+ fname = dname;
+
+ /* Mangle fname if it's an illegal name. */
+ if (mangle_must_mangle(dname,conn->params)) {
+ if (!name_to_8_3(dname,mangled_name,True,conn->params)) {
+ continue; /* Error - couldn't mangle. */
+ }
+ fname = mangled_name;
+ }
+
+ if(!(got_match = *got_exact_match = exact_match(conn, fname, mask))) {
+ got_match = mask_match(fname, mask, conn->case_sensitive);
+ }
+
+ if(!got_match && check_mangled_names &&
+ !mangle_is_8_3(fname, False, conn->params)) {
+ /*
+ * It turns out that NT matches wildcards against
+ * both long *and* short names. This may explain some
+ * of the wildcard wierdness from old DOS clients
+ * that some people have been seeing.... JRA.
+ */
+ /* Force the mangling into 8.3. */
+ if (!name_to_8_3( fname, mangled_name, False, conn->params)) {
+ continue; /* Error - couldn't mangle. */
+ }
+
+ if(!(got_match = *got_exact_match = exact_match(conn, mangled_name, mask))) {
+ got_match = mask_match(mangled_name, mask, conn->case_sensitive);
+ }
+ }
+
+ if (got_match) {
+ bool isdots = (ISDOT(dname) || ISDOTDOT(dname));
+
+ if (dont_descend && !isdots) {
+ continue;
+ }
+
+ if (needslash) {
+ pathreal = NULL;
+ pathreal = talloc_asprintf(ctx,
+ "%s/%s",
+ conn->dirpath,
+ dname);
+ } else {
+ pathreal = talloc_asprintf(ctx,
+ "%s%s",
+ conn->dirpath,
+ dname);
+ }
+
+ if (!pathreal) {
+ return False;
+ }
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (SMB_VFS_LSTAT(conn,pathreal,&sbuf) != 0) {
+ DEBUG(5,("get_lanman2_dir_entry:Couldn't lstat [%s] (%s)\n",
+ pathreal,strerror(errno)));
+ TALLOC_FREE(pathreal);
+ continue;
+ }
+ } else if (!VALID_STAT(sbuf) && SMB_VFS_STAT(conn,pathreal,&sbuf) != 0) {
+ /* Needed to show the msdfs symlinks as
+ * directories */
+
+ ms_dfs_link = check_msdfs_link(conn, pathreal, &sbuf);
+ if (!ms_dfs_link) {
+ DEBUG(5,("get_lanman2_dir_entry:Couldn't stat [%s] (%s)\n",
+ pathreal,strerror(errno)));
+ TALLOC_FREE(pathreal);
+ continue;
+ }
+ }
+
+ if (ms_dfs_link) {
+ mode = dos_mode_msdfs(conn,pathreal,&sbuf);
+ } else {
+ mode = dos_mode(conn,pathreal,&sbuf);
+ }
+
+ if (!dir_check_ftype(conn,mode,dirtype)) {
+ DEBUG(5,("get_lanman2_dir_entry: [%s] attribs didn't match %x\n",fname,dirtype));
+ TALLOC_FREE(pathreal);
+ continue;
+ }
+
+ if (!(mode & aDIR)) {
+ file_size = get_file_size(sbuf);
+ }
+ allocation_size = get_allocation_size(conn,NULL,&sbuf);
+
+ mdate_ts = get_mtimespec(&sbuf);
+ adate_ts = get_atimespec(&sbuf);
+ create_date_ts = get_create_timespec(&sbuf,lp_fake_dir_create_times(SNUM(conn)));
+
+ if (ask_sharemode) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ get_file_infos(fileid, NULL, &write_time_ts);
+ if (!null_timespec(write_time_ts)) {
+ mdate_ts = write_time_ts;
+ }
+ }
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_date_ts);
+ dos_filetime_timespec(&mdate_ts);
+ dos_filetime_timespec(&adate_ts);
+ }
+
+ create_date = convert_timespec_to_time_t(create_date_ts);
+ mdate = convert_timespec_to_time_t(mdate_ts);
+ adate = convert_timespec_to_time_t(adate_ts);
+
+ DEBUG(5,("get_lanman2_dir_entry: found %s fname=%s\n",pathreal,fname));
+
+ found = True;
+
+ dptr_DirCacheAdd(conn->dirptr, dname, curr_dirpos);
+ }
+ }
+
+ p = pdata;
+ last_entry_ptr = p;
+
+ nt_extmode = mode ? mode : FILE_ATTRIBUTE_NORMAL;
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_INFO_STANDARD\n"));
+ if(requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2(p,0,create_date);
+ srv_put_dos_date2(p,4,adate);
+ srv_put_dos_date2(p,8,mdate);
+ SIVAL(p,12,(uint32)file_size);
+ SIVAL(p,16,(uint32)allocation_size);
+ SSVAL(p,20,mode);
+ p += 23;
+ nameptr = p;
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ p += ucs2_align(base_data, p, 0);
+ }
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE);
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ SCVAL(nameptr, -1, len - 2);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ } else {
+ if (len > 1) {
+ SCVAL(nameptr, -1, len - 1);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ }
+ p += len;
+ break;
+
+ case SMB_FIND_EA_SIZE:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_EA_SIZE\n"));
+ if(requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2(p,0,create_date);
+ srv_put_dos_date2(p,4,adate);
+ srv_put_dos_date2(p,8,mdate);
+ SIVAL(p,12,(uint32)file_size);
+ SIVAL(p,16,(uint32)allocation_size);
+ SSVAL(p,20,mode);
+ {
+ unsigned int ea_size = estimate_ea_size(conn, NULL, pathreal);
+ SIVAL(p,22,ea_size); /* Extended attributes */
+ }
+ p += 27;
+ nameptr = p - 1;
+ len = srvstr_push(base_data, flags2,
+ p, fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE | STR_NOALIGN);
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+
+ case SMB_FIND_EA_LIST:
+ {
+ struct ea_list *file_list = NULL;
+ size_t ea_len = 0;
+
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_EA_LIST\n"));
+ if (!name_list) {
+ return False;
+ }
+ if(requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2(p,0,create_date);
+ srv_put_dos_date2(p,4,adate);
+ srv_put_dos_date2(p,8,mdate);
+ SIVAL(p,12,(uint32)file_size);
+ SIVAL(p,16,(uint32)allocation_size);
+ SSVAL(p,20,mode);
+ p += 22; /* p now points to the EA area. */
+
+ file_list = get_ea_list_from_file(ctx, conn, NULL, pathreal, &ea_len);
+ name_list = ea_list_union(name_list, file_list, &ea_len);
+
+ /* We need to determine if this entry will fit in the space available. */
+ /* Max string size is 255 bytes. */
+ if (PTR_DIFF(p + 255 + ea_len,pdata) > space_remaining) {
+ /* Move the dirptr back to prev_dirpos */
+ dptr_SeekDir(conn->dirptr, prev_dirpos);
+ *out_of_space = True;
+ DEBUG(9,("get_lanman2_dir_entry: out of space\n"));
+ return False; /* Not finished - just out of space */
+ }
+
+ /* Push the ea_data followed by the name. */
+ p += fill_ea_buffer(ctx, p, space_remaining, conn, name_list);
+ nameptr = p;
+ len = srvstr_push(base_data, flags2,
+ p + 1, fname, PTR_DIFF(end_data, p+1),
+ STR_TERMINATE | STR_NOALIGN);
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len + 1;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+ }
+
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_timespec(p,create_date_ts); p += 8;
+ put_long_date_timespec(p,adate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,nt_extmode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ {
+ unsigned int ea_size = estimate_ea_size(conn, NULL, pathreal);
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p += 4;
+ }
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ if (!was_8_3 && check_mangled_names) {
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ len = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE);
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ memset(p,'\0',26);
+ }
+ p += 2 + 24;
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII);
+ SIVAL(q,0,len);
+ p += len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_timespec(p,create_date_ts); p += 8;
+ put_long_date_timespec(p,adate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,nt_extmode); p += 4;
+ len = srvstr_push(base_data, flags2,
+ p + 4, fname, PTR_DIFF(end_data, p+4),
+ STR_TERMINATE_ASCII);
+ SIVAL(p,0,len);
+ p += 4 + len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_timespec(p,create_date_ts); p += 8;
+ put_long_date_timespec(p,adate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,nt_extmode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ {
+ unsigned int ea_size = estimate_ea_size(conn, NULL, pathreal);
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p +=4;
+ }
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII);
+ SIVAL(q, 0, len);
+ p += len;
+
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ case SMB_FIND_FILE_NAMES_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_NAMES_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ p += 4;
+ /* this must *not* be null terminated or w2k gets in a loop trying to set an
+ acl on a dir (tridge) */
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII);
+ SIVAL(p, -4, len);
+ p += len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_ID_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_timespec(p,create_date_ts); p += 8;
+ put_long_date_timespec(p,adate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,nt_extmode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ {
+ unsigned int ea_size = estimate_ea_size(conn, NULL, pathreal);
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p +=4;
+ }
+ SIVAL(p,0,0); p += 4; /* Unknown - reserved ? */
+ SIVAL(p,0,sbuf.st_ino); p += 4; /* FileIndexLow */
+ SIVAL(p,0,sbuf.st_dev); p += 4; /* FileIndexHigh */
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII);
+ SIVAL(q, 0, len);
+ p += len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_ID_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_timespec(p,create_date_ts); p += 8;
+ put_long_date_timespec(p,adate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ put_long_date_timespec(p,mdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,nt_extmode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length */
+ {
+ unsigned int ea_size = estimate_ea_size(conn, NULL, pathreal);
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p +=4;
+ }
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ if (!was_8_3 && check_mangled_names) {
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ len = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE);
+ SSVAL(p, 0, len);
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ memset(p,'\0',26);
+ }
+ p += 26;
+ SSVAL(p,0,0); p += 2; /* Reserved ? */
+ SIVAL(p,0,sbuf.st_ino); p += 4; /* FileIndexLow */
+ SIVAL(p,0,sbuf.st_dev); p += 4; /* FileIndexHigh */
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII);
+ SIVAL(q,0,len);
+ p += len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len);
+ p = pdata + len;
+ break;
+
+ /* CIFS UNIX Extension. */
+
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ p+= 4;
+ SIVAL(p,0,reskey); p+= 4; /* Used for continuing search. */
+
+ /* Begin of SMB_QUERY_FILE_UNIX_BASIC */
+
+ if (info_level == SMB_FIND_FILE_UNIX) {
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_UNIX\n"));
+ p = store_file_unix_basic(conn, p,
+ NULL, &sbuf);
+ len = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE);
+ } else {
+ DEBUG(10,("get_lanman2_dir_entry: SMB_FIND_FILE_UNIX_INFO2\n"));
+ p = store_file_unix_basic_info2(conn, p,
+ NULL, &sbuf);
+ nameptr = p;
+ p += 4;
+ len = srvstr_push(base_data, flags2, p, fname,
+ PTR_DIFF(end_data, p), 0);
+ SIVAL(nameptr, 0, len);
+ }
+
+ p += len;
+ SIVAL(p,0,0); /* Ensure any padding is null. */
+
+ len = PTR_DIFF(p, pdata);
+ len = (len + 3) & ~3;
+ SIVAL(pdata,0,len); /* Offset from this structure to the beginning of the next one */
+ p = pdata + len;
+ /* End of SMB_QUERY_FILE_UNIX_BASIC */
+
+ break;
+
+ default:
+ return(False);
+ }
+
+
+ if (PTR_DIFF(p,pdata) > space_remaining) {
+ /* Move the dirptr back to prev_dirpos */
+ dptr_SeekDir(conn->dirptr, prev_dirpos);
+ *out_of_space = True;
+ DEBUG(9,("get_lanman2_dir_entry: out of space\n"));
+ return False; /* Not finished - just out of space */
+ }
+
+ /* Setup the last entry pointer, as an offset from base_data */
+ *last_entry_off = PTR_DIFF(last_entry_ptr,base_data);
+ /* Advance the data pointer to the next slot */
+ *ppdata = p;
+
+ return(found);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDFIRST.
+****************************************************************************/
+
+static void call_trans2findfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ uint32 dirtype;
+ int maxentries;
+ uint16 findfirst_flags;
+ bool close_after_first;
+ bool close_if_end;
+ bool requires_resume_key;
+ int info_level;
+ char *directory = NULL;
+ const char *mask = NULL;
+ char *p;
+ int last_entry_off=0;
+ int dptr_num = -1;
+ int numentries = 0;
+ int i;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ bool mask_contains_wcard = False;
+ SMB_STRUCT_STAT sbuf;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode = lp_parm_bool(SNUM(conn), "smbd", "search ask sharemode", true);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ dirtype = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ findfirst_flags = SVAL(params,4);
+ close_after_first = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findfirst_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ info_level = SVAL(params,6);
+
+ DEBUG(3,("call_trans2findfirst: dirtype = %x, maxentries = %d, close_after_first=%d, \
+close_if_end = %d requires_resume_key = %d level = 0x%x, max_data_bytes = %d\n",
+ (unsigned int)dirtype, maxentries, close_after_first, close_if_end, requires_resume_key,
+ info_level, max_data_bytes));
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ /* Always use filesystem for UNIX mtime query. */
+ ask_sharemode = false;
+ if (!lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ srvstr_get_path_wcard(ctx, params, req->flags2, &directory,
+ params+12, total_params - 12,
+ STR_TERMINATE, &ntstatus, &mask_contains_wcard);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+
+ ntstatus = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ directory,
+ &directory,
+ &mask_contains_wcard);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ if (NT_STATUS_EQUAL(ntstatus,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, ntstatus);
+ return;
+ }
+
+ ntstatus = unix_convert(ctx, conn, directory, True, &directory, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+
+ ntstatus = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+
+ p = strrchr_m(directory,'/');
+ if(p == NULL) {
+ /* Windows and OS/2 systems treat search on the root '\' as if it were '\*' */
+ if((directory[0] == '.') && (directory[1] == '\0')) {
+ mask = "*";
+ mask_contains_wcard = True;
+ } else {
+ mask = directory;
+ }
+ directory = talloc_strdup(talloc_tos(), "./");
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ } else {
+ mask = p+1;
+ *p = 0;
+ }
+
+ DEBUG(5,("dir=%s, mask = %s\n",directory, mask));
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32 ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DEBUG(4,("call_trans2findfirst: Rejecting EA request with incorrect \
+total_data=%u (should be %u)\n", (unsigned int)total_data, (unsigned int)IVAL(pdata,0) ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_doserror(req, ERRDOS, ERReasnotsupported);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(ctx, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 10);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ /* Save the wildcard match and attribs we are using on this directory -
+ needed as lanman2 assumes these are being saved between calls */
+
+ ntstatus = dptr_create(conn,
+ directory,
+ False,
+ True,
+ req->smbpid,
+ mask,
+ mask_contains_wcard,
+ dirtype,
+ &conn->dirptr);
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+
+ dptr_num = dptr_dnum(conn->dirptr);
+ DEBUG(4,("dptr_num is %d, wcard = %s, attr = %d\n", dptr_num, mask, dirtype));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n", conn->dirpath,lp_dontdescend(SNUM(conn))));
+ if (in_list(conn->dirpath,lp_dontdescend(SNUM(conn)),conn->case_sensitive))
+ dont_descend = True;
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ for (i=0;(i<maxentries) && !finished && !out_of_space;i++) {
+ bool got_exact_match = False;
+
+ /* this is a heuristic to avoid seeking the dirptr except when
+ absolutely necessary. It allows for a filename of about 40 chars */
+ if (space_remaining < DIRLEN_GUESS && numentries > 0) {
+ out_of_space = True;
+ finished = False;
+ } else {
+ finished = !get_lanman2_dir_entry(ctx,
+ conn,
+ req->flags2,
+ mask,dirtype,info_level,
+ requires_resume_key,dont_descend,
+ ask_sharemode,
+ &p,pdata,data_end,
+ space_remaining, &out_of_space,
+ &got_exact_match,
+ &last_entry_off, ea_list);
+ }
+
+ if (finished && out_of_space)
+ finished = False;
+
+ if (!finished && !out_of_space)
+ numentries++;
+
+ /*
+ * As an optimisation if we know we aren't looking
+ * for a wildcard name (ie. the name matches the wildcard exactly)
+ * then we can finish on any (first) match.
+ * This speeds up large directory searches. JRA.
+ */
+
+ if(got_exact_match)
+ finished = True;
+
+ /* Ensure space_remaining never goes -ve. */
+ if (PTR_DIFF(p,pdata) > max_data_bytes) {
+ space_remaining = 0;
+ out_of_space = true;
+ } else {
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+ }
+
+ /* Check if we can close the dirptr */
+ if(close_after_first || (finished && close_if_end)) {
+ DEBUG(5,("call_trans2findfirst - (2) closing dptr_num %d\n", dptr_num));
+ dptr_close(&dptr_num);
+ }
+
+ /*
+ * If there are no matching entries we must return ERRDOS/ERRbadfile -
+ * from observation of NT. NB. This changes to ERRDOS,ERRnofiles if
+ * the protocol level is less than NT1. Tested with smbclient. JRA.
+ * This should fix the OS/2 client bug #2335.
+ */
+
+ if(numentries == 0) {
+ dptr_close(&dptr_num);
+ if (Protocol < PROTOCOL_NT1) {
+ reply_doserror(req, ERRDOS, ERRnofiles);
+ return;
+ } else {
+ reply_botherror(req, NT_STATUS_NO_SUCH_FILE,
+ ERRDOS, ERRbadfile);
+ return;
+ }
+ }
+
+ /* At this point pdata points to numentries directory entries. */
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,dptr_num);
+ SSVAL(params,2,numentries);
+ SSVAL(params,4,finished);
+ SSVAL(params,6,0); /* Never an EA error */
+ SSVAL(params,8,last_entry_off);
+
+ send_trans2_replies(conn, req, params, 10, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ if ((! *directory) && dptr_path(dptr_num)) {
+ directory = talloc_strdup(talloc_tos(),dptr_path(dptr_num));
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ DEBUG( 4, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(CVAL(req->inbuf,smb_com)),
+ mask, directory, dirtype, numentries ) );
+
+ /*
+ * Force a name mangle here to ensure that the
+ * mask as an 8.3 name is top of the mangled cache.
+ * The reasons for this are subtle. Don't remove
+ * this code unless you know what you are doing
+ * (see PR#13758). JRA.
+ */
+
+ if(!mangle_is_8_3_wildcards( mask, False, conn->params)) {
+ char mangled_name[13];
+ name_to_8_3(mask, mangled_name, True, conn->params);
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNEXT.
+****************************************************************************/
+
+static void call_trans2findnext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ int dptr_num;
+ int maxentries;
+ uint16 info_level;
+ uint32 resume_key;
+ uint16 findnext_flags;
+ bool close_after_request;
+ bool close_if_end;
+ bool requires_resume_key;
+ bool continue_bit;
+ bool mask_contains_wcard = False;
+ char *resume_name = NULL;
+ const char *mask = NULL;
+ const char *directory = NULL;
+ char *p = NULL;
+ uint16 dirtype;
+ int numentries = 0;
+ int i, last_entry_off=0;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode = lp_parm_bool(SNUM(conn), "smbd", "search ask sharemode", true);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ dptr_num = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ info_level = SVAL(params,4);
+ resume_key = IVAL(params,6);
+ findnext_flags = SVAL(params,10);
+ close_after_request = (findnext_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findnext_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findnext_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ continue_bit = (findnext_flags & FLAG_TRANS2_FIND_CONTINUE);
+
+ srvstr_get_path_wcard(ctx, params, req->flags2, &resume_name,
+ params+12,
+ total_params - 12, STR_TERMINATE, &ntstatus,
+ &mask_contains_wcard);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ /* Win9x or OS/2 can send a resume name of ".." or ".". This will cause the parser to
+ complain (it thinks we're asking for the directory above the shared
+ path or an invalid name). Catch this as the resume name is only compared, never used in
+ a file access. JRA. */
+ srvstr_pull_talloc(ctx, params, req->flags2,
+ &resume_name, params+12,
+ total_params - 12,
+ STR_TERMINATE);
+
+ if (!resume_name || !(ISDOT(resume_name) || ISDOTDOT(resume_name))) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+ }
+
+ DEBUG(3,("call_trans2findnext: dirhandle = %d, max_data_bytes = %d, maxentries = %d, \
+close_after_request=%d, close_if_end = %d requires_resume_key = %d \
+resume_key = %d resume name = %s continue=%d level = %d\n",
+ dptr_num, max_data_bytes, maxentries, close_after_request, close_if_end,
+ requires_resume_key, resume_key, resume_name, continue_bit, info_level));
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ /* Always use filesystem for UNIX mtime query. */
+ ask_sharemode = false;
+ if (!lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32 ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DEBUG(4,("call_trans2findnext: Rejecting EA request with incorrect \
+total_data=%u (should be %u)\n", (unsigned int)total_data, (unsigned int)IVAL(pdata,0) ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_doserror(req, ERRDOS, ERReasnotsupported);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(ctx, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 6*SIZEOFWORD);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ params = *pparams;
+
+ /* Check that the dptr is valid */
+ if(!(conn->dirptr = dptr_fetch_lanman2(dptr_num))) {
+ reply_doserror(req, ERRDOS, ERRnofiles);
+ return;
+ }
+
+ string_set(&conn->dirpath,dptr_path(dptr_num));
+
+ /* Get the wildcard mask from the dptr */
+ if((p = dptr_wcard(dptr_num))== NULL) {
+ DEBUG(2,("dptr_num %d has no wildcard\n", dptr_num));
+ reply_doserror(req, ERRDOS, ERRnofiles);
+ return;
+ }
+
+ mask = p;
+ directory = conn->dirpath;
+
+ /* Get the attr mask from the dptr */
+ dirtype = dptr_attr(dptr_num);
+
+ DEBUG(3,("dptr_num is %d, mask = %s, attr = %x, dirptr=(0x%lX,%ld)\n",
+ dptr_num, mask, dirtype,
+ (long)conn->dirptr,
+ dptr_TellDir(conn->dirptr)));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",conn->dirpath,lp_dontdescend(SNUM(conn))));
+ if (in_list(conn->dirpath,lp_dontdescend(SNUM(conn)),conn->case_sensitive))
+ dont_descend = True;
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ /*
+ * Seek to the correct position. We no longer use the resume key but
+ * depend on the last file name instead.
+ */
+
+ if(*resume_name && !continue_bit) {
+ SMB_STRUCT_STAT st;
+
+ long current_pos = 0;
+ /*
+ * Remember, name_to_8_3 is called by
+ * get_lanman2_dir_entry(), so the resume name
+ * could be mangled. Ensure we check the unmangled name.
+ */
+
+ if (mangle_is_mangled(resume_name, conn->params)) {
+ char *new_resume_name = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ resume_name,
+ &new_resume_name,
+ conn->params);
+ if (new_resume_name) {
+ resume_name = new_resume_name;
+ }
+ }
+
+ /*
+ * Fix for NT redirector problem triggered by resume key indexes
+ * changing between directory scans. We now return a resume key of 0
+ * and instead look for the filename to continue from (also given
+ * to us by NT/95/smbfs/smbclient). If no other scans have been done between the
+ * findfirst/findnext (as is usual) then the directory pointer
+ * should already be at the correct place.
+ */
+
+ finished = !dptr_SearchDir(conn->dirptr, resume_name, &current_pos, &st);
+ } /* end if resume_name && !continue_bit */
+
+ for (i=0;(i<(int)maxentries) && !finished && !out_of_space ;i++) {
+ bool got_exact_match = False;
+
+ /* this is a heuristic to avoid seeking the dirptr except when
+ absolutely necessary. It allows for a filename of about 40 chars */
+ if (space_remaining < DIRLEN_GUESS && numentries > 0) {
+ out_of_space = True;
+ finished = False;
+ } else {
+ finished = !get_lanman2_dir_entry(ctx,
+ conn,
+ req->flags2,
+ mask,dirtype,info_level,
+ requires_resume_key,dont_descend,
+ ask_sharemode,
+ &p,pdata,data_end,
+ space_remaining, &out_of_space,
+ &got_exact_match,
+ &last_entry_off, ea_list);
+ }
+
+ if (finished && out_of_space)
+ finished = False;
+
+ if (!finished && !out_of_space)
+ numentries++;
+
+ /*
+ * As an optimisation if we know we aren't looking
+ * for a wildcard name (ie. the name matches the wildcard exactly)
+ * then we can finish on any (first) match.
+ * This speeds up large directory searches. JRA.
+ */
+
+ if(got_exact_match)
+ finished = True;
+
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+
+ DEBUG( 3, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(CVAL(req->inbuf,smb_com)),
+ mask, directory, dirtype, numentries ) );
+
+ /* Check if we can close the dirptr */
+ if(close_after_request || (finished && close_if_end)) {
+ DEBUG(5,("call_trans2findnext: closing dptr_num = %d\n", dptr_num));
+ dptr_close(&dptr_num); /* This frees up the saved mask */
+ }
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,numentries);
+ SSVAL(params,2,finished);
+ SSVAL(params,4,0); /* Never an EA error */
+ SSVAL(params,6,last_entry_off);
+
+ send_trans2_replies(conn, req, params, 8, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ return;
+}
+
+unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16])
+{
+ E_md4hash(lp_servicename(SNUM(conn)),objid);
+ return objid;
+}
+
+static void samba_extended_info_version(struct smb_extended_info *extended_info)
+{
+ SMB_ASSERT(extended_info != NULL);
+
+ extended_info->samba_magic = SAMBA_EXTENDED_INFO_MAGIC;
+ extended_info->samba_version = ((SAMBA_VERSION_MAJOR & 0xff) << 24)
+ | ((SAMBA_VERSION_MINOR & 0xff) << 16)
+ | ((SAMBA_VERSION_RELEASE & 0xff) << 8);
+#ifdef SAMBA_VERSION_REVISION
+ extended_info->samba_version |= (tolower(*SAMBA_VERSION_REVISION) - 'a' + 1) & 0xff;
+#endif
+ extended_info->samba_subversion = 0;
+#ifdef SAMBA_VERSION_RC_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_RC_RELEASE & 0xff) << 24;
+#else
+#ifdef SAMBA_VERSION_PRE_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_PRE_RELEASE & 0xff) << 16;
+#endif
+#endif
+#ifdef SAMBA_VERSION_VENDOR_PATCH
+ extended_info->samba_subversion |= (SAMBA_VERSION_VENDOR_PATCH & 0xffff);
+#endif
+ extended_info->samba_gitcommitdate = 0;
+#ifdef SAMBA_VERSION_GIT_COMMIT_TIME
+ unix_to_nt_time(&extended_info->samba_gitcommitdate, SAMBA_VERSION_GIT_COMMIT_TIME);
+#endif
+
+ memset(extended_info->samba_version_string, 0,
+ sizeof(extended_info->samba_version_string));
+
+ snprintf (extended_info->samba_version_string,
+ sizeof(extended_info->samba_version_string),
+ "%s", samba_version_string());
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFSINFO (query filesystem info).
+****************************************************************************/
+
+static void call_trans2qfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *pdata, *end_data;
+ char *params = *pparams;
+ uint16 info_level;
+ int data_len, len;
+ SMB_STRUCT_STAT st;
+ const char *vname = volume_label(SNUM(conn));
+ int snum = SNUM(conn);
+ char *fstype = lp_fstype(SNUM(conn));
+ uint32 additional_flags = 0;
+
+ if (total_params < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2qfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2qfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ exit_server_cleanly("encryption required "
+ "on connection");
+ return;
+ }
+ }
+
+ DEBUG(3,("call_trans2qfsinfo: level = %d\n", info_level));
+
+ if(SMB_VFS_STAT(conn,".",&st)!=0) {
+ DEBUG(2,("call_trans2qfsinfo: stat of . failed (%s)\n", strerror(errno)));
+ reply_doserror(req, ERRSRV, ERRinvdevice);
+ return;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if (*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ pdata = *ppdata;
+ memset((char *)pdata,'\0',max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ end_data = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ switch (info_level) {
+ case SMB_INFO_ALLOCATION:
+ {
+ SMB_BIG_UINT dfree,dsize,bsize,block_size,sectors_per_unit,bytes_per_sector;
+ data_len = 18;
+ if (get_dfree_info(conn,".",False,&bsize,&dfree,&dsize) == (SMB_BIG_UINT)-1) {
+ reply_unixerror(req, ERRHRD, ERRgeneral);
+ return;
+ }
+
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ SMB_BIG_UINT factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ SMB_BIG_UINT factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ bytes_per_sector = 512;
+ sectors_per_unit = bsize/bytes_per_sector;
+
+ DEBUG(5,("call_trans2qfsinfo : SMB_INFO_ALLOCATION id=%x, bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)st.st_dev, (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+
+ SIVAL(pdata,l1_idFileSystem,st.st_dev);
+ SIVAL(pdata,l1_cSectorUnit,sectors_per_unit);
+ SIVAL(pdata,l1_cUnit,dsize);
+ SIVAL(pdata,l1_cUnitAvail,dfree);
+ SSVAL(pdata,l1_cbSector,bytes_per_sector);
+ break;
+ }
+
+ case SMB_INFO_VOLUME:
+ /* Return volume name */
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ SIVAL(pdata,0,str_checksum(lp_servicename(snum)) ^ (str_checksum(get_local_machine_name())<<16) );
+ /*
+ * Win2k3 and previous mess this up by sending a name length
+ * one byte short. I believe only older clients (OS/2 Win9x) use
+ * this call so try fixing this by adding a terminating null to
+ * the pushed string. The change here was adding the STR_TERMINATE. JRA.
+ */
+ len = srvstr_push(
+ pdata, req->flags2,
+ pdata+l2_vol_szVolLabel, vname,
+ PTR_DIFF(end_data, pdata+l2_vol_szVolLabel),
+ STR_NOALIGN|STR_TERMINATE);
+ SCVAL(pdata,l2_vol_cch,len);
+ data_len = l2_vol_szVolLabel + len;
+ DEBUG(5,("call_trans2qfsinfo : time = %x, namelen = %d, name = %s\n",
+ (unsigned)st.st_ctime, len, vname));
+ break;
+
+ case SMB_QUERY_FS_ATTRIBUTE_INFO:
+ case SMB_FS_ATTRIBUTE_INFORMATION:
+
+ additional_flags = 0;
+#if defined(HAVE_SYS_QUOTAS)
+ additional_flags |= FILE_VOLUME_QUOTAS;
+#endif
+
+ if(lp_nt_acl_support(SNUM(conn))) {
+ additional_flags |= FILE_PERSISTENT_ACLS;
+ }
+
+ /* Capabilities are filled in at connection time through STATVFS call */
+ additional_flags |= conn->fs_capabilities;
+
+ SIVAL(pdata,0,FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH|
+ FILE_SUPPORTS_OBJECT_IDS|FILE_UNICODE_ON_DISK|
+ additional_flags); /* FS ATTRIBUTES */
+
+ SIVAL(pdata,4,255); /* Max filename component length */
+ /* NOTE! the fstype must *not* be null terminated or win98 won't recognise it
+ and will think we can't do long filenames */
+ len = srvstr_push(pdata, req->flags2, pdata+12, fstype,
+ PTR_DIFF(end_data, pdata+12),
+ STR_UNICODE);
+ SIVAL(pdata,8,len);
+ data_len = 12 + len;
+ break;
+
+ case SMB_QUERY_FS_LABEL_INFO:
+ case SMB_FS_LABEL_INFORMATION:
+ len = srvstr_push(pdata, req->flags2, pdata+4, vname,
+ PTR_DIFF(end_data, pdata+4), 0);
+ data_len = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+
+ case SMB_QUERY_FS_VOLUME_INFO:
+ case SMB_FS_VOLUME_INFORMATION:
+
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ SIVAL(pdata,8,str_checksum(lp_servicename(snum)) ^
+ (str_checksum(get_local_machine_name())<<16));
+
+ /* Max label len is 32 characters. */
+ len = srvstr_push(pdata, req->flags2, pdata+18, vname,
+ PTR_DIFF(end_data, pdata+18),
+ STR_UNICODE);
+ SIVAL(pdata,12,len);
+ data_len = 18+len;
+
+ DEBUG(5,("call_trans2qfsinfo : SMB_QUERY_FS_VOLUME_INFO namelen = %d, vol=%s serv=%s\n",
+ (int)strlen(vname),vname, lp_servicename(snum)));
+ break;
+
+ case SMB_QUERY_FS_SIZE_INFO:
+ case SMB_FS_SIZE_INFORMATION:
+ {
+ SMB_BIG_UINT dfree,dsize,bsize,block_size,sectors_per_unit,bytes_per_sector;
+ data_len = 24;
+ if (get_dfree_info(conn,".",False,&bsize,&dfree,&dsize) == (SMB_BIG_UINT)-1) {
+ reply_unixerror(req, ERRHRD, ERRgeneral);
+ return;
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ SMB_BIG_UINT factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ SMB_BIG_UINT factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ bytes_per_sector = 512;
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("call_trans2qfsinfo : SMB_QUERY_FS_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize);
+ SBIG_UINT(pdata,8,dfree);
+ SIVAL(pdata,16,sectors_per_unit);
+ SIVAL(pdata,20,bytes_per_sector);
+ break;
+ }
+
+ case SMB_FS_FULL_SIZE_INFORMATION:
+ {
+ SMB_BIG_UINT dfree,dsize,bsize,block_size,sectors_per_unit,bytes_per_sector;
+ data_len = 32;
+ if (get_dfree_info(conn,".",False,&bsize,&dfree,&dsize) == (SMB_BIG_UINT)-1) {
+ reply_unixerror(req, ERRHRD, ERRgeneral);
+ return;
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ SMB_BIG_UINT factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ SMB_BIG_UINT factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ bytes_per_sector = 512;
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("call_trans2qfsinfo : SMB_QUERY_FS_FULL_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize); /* Total Allocation units. */
+ SBIG_UINT(pdata,8,dfree); /* Caller available allocation units. */
+ SBIG_UINT(pdata,16,dfree); /* Actual available allocation units. */
+ SIVAL(pdata,24,sectors_per_unit); /* Sectors per allocation unit. */
+ SIVAL(pdata,28,bytes_per_sector); /* Bytes per sector. */
+ break;
+ }
+
+ case SMB_QUERY_FS_DEVICE_INFO:
+ case SMB_FS_DEVICE_INFORMATION:
+ data_len = 8;
+ SIVAL(pdata,0,0); /* dev type */
+ SIVAL(pdata,4,0); /* characteristics */
+ break;
+
+#ifdef HAVE_SYS_QUOTAS
+ case SMB_FS_QUOTA_INFORMATION:
+ /*
+ * what we have to send --metze:
+ *
+ * Unknown1: 24 NULL bytes
+ * Soft Quota Treshold: 8 bytes seems like SMB_BIG_UINT or so
+ * Hard Quota Limit: 8 bytes seems like SMB_BIG_UINT or so
+ * Quota Flags: 2 byte :
+ * Unknown3: 6 NULL bytes
+ *
+ * 48 bytes total
+ *
+ * details for Quota Flags:
+ *
+ * 0x0020 Log Limit: log if the user exceeds his Hard Quota
+ * 0x0010 Log Warn: log if the user exceeds his Soft Quota
+ * 0x0002 Deny Disk: deny disk access when the user exceeds his Hard Quota
+ * 0x0001 Enable Quotas: enable quota for this fs
+ *
+ */
+ {
+ /* we need to fake up a fsp here,
+ * because its not send in this call
+ */
+ files_struct fsp;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(fsp);
+ ZERO_STRUCT(quotas);
+
+ fsp.conn = conn;
+ fsp.fnum = -1;
+
+ /* access check */
+ if (conn->server_info->utok.uid != 0) {
+ DEBUG(0,("set_user_quota: access_denied "
+ "service [%s] user [%s]\n",
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name));
+ reply_doserror(req, ERRDOS, ERRnoaccess);
+ return;
+ }
+
+ if (vfs_get_ntquota(&fsp, SMB_USER_FS_QUOTA_TYPE, NULL, &quotas)!=0) {
+ DEBUG(0,("vfs_get_ntquota() failed for service [%s]\n",lp_servicename(SNUM(conn))));
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+
+ data_len = 48;
+
+ DEBUG(10,("SMB_FS_QUOTA_INFORMATION: for service [%s]\n",lp_servicename(SNUM(conn))));
+
+ /* Unknown1 24 NULL bytes*/
+ SBIG_UINT(pdata,0,(SMB_BIG_UINT)0);
+ SBIG_UINT(pdata,8,(SMB_BIG_UINT)0);
+ SBIG_UINT(pdata,16,(SMB_BIG_UINT)0);
+
+ /* Default Soft Quota 8 bytes */
+ SBIG_UINT(pdata,24,quotas.softlim);
+
+ /* Default Hard Quota 8 bytes */
+ SBIG_UINT(pdata,32,quotas.hardlim);
+
+ /* Quota flag 2 bytes */
+ SSVAL(pdata,40,quotas.qflags);
+
+ /* Unknown3 6 NULL bytes */
+ SSVAL(pdata,42,0);
+ SIVAL(pdata,44,0);
+
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+ case SMB_FS_OBJECTID_INFORMATION:
+ {
+ unsigned char objid[16];
+ struct smb_extended_info extended_info;
+ memcpy(pdata,create_volume_objectid(conn, objid),16);
+ samba_extended_info_version (&extended_info);
+ SIVAL(pdata,16,extended_info.samba_magic);
+ SIVAL(pdata,20,extended_info.samba_version);
+ SIVAL(pdata,24,extended_info.samba_subversion);
+ SBIG_UINT(pdata,28,extended_info.samba_gitcommitdate);
+ memcpy(pdata+36,extended_info.samba_version_string,28);
+ data_len = 64;
+ break;
+ }
+
+ /*
+ * Query the version and capabilities of the CIFS UNIX extensions
+ * in use.
+ */
+
+ case SMB_QUERY_CIFS_UNIX_INFO:
+ {
+ bool large_write = lp_min_receive_file_size() &&
+ !srv_is_signing_active();
+ bool large_read = !srv_is_signing_active();
+ int encrypt_caps = 0;
+
+ if (!lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ switch (conn->encrypt_level) {
+ case 0:
+ encrypt_caps = 0;
+ break;
+ case 1:
+ case Auto:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP;
+ break;
+ case Required:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP|
+ CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP;
+ large_write = false;
+ large_read = false;
+ break;
+ }
+
+ data_len = 12;
+ SSVAL(pdata,0,CIFS_UNIX_MAJOR_VERSION);
+ SSVAL(pdata,2,CIFS_UNIX_MINOR_VERSION);
+
+ /* We have POSIX ACLs, pathname, encryption,
+ * large read/write, and locking capability. */
+
+ SBIG_UINT(pdata,4,((SMB_BIG_UINT)(
+ CIFS_UNIX_POSIX_ACLS_CAP|
+ CIFS_UNIX_POSIX_PATHNAMES_CAP|
+ CIFS_UNIX_FCNTL_LOCKS_CAP|
+ CIFS_UNIX_EXTATTR_CAP|
+ CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP|
+ encrypt_caps|
+ (large_read ? CIFS_UNIX_LARGE_READ_CAP : 0) |
+ (large_write ?
+ CIFS_UNIX_LARGE_WRITE_CAP : 0))));
+ break;
+ }
+
+ case SMB_QUERY_POSIX_FS_INFO:
+ {
+ int rc;
+ vfs_statvfs_struct svfs;
+
+ if (!lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ rc = SMB_VFS_STATVFS(conn, ".", &svfs);
+
+ if (!rc) {
+ data_len = 56;
+ SIVAL(pdata,0,svfs.OptimalTransferSize);
+ SIVAL(pdata,4,svfs.BlockSize);
+ SBIG_UINT(pdata,8,svfs.TotalBlocks);
+ SBIG_UINT(pdata,16,svfs.BlocksAvail);
+ SBIG_UINT(pdata,24,svfs.UserBlocksAvail);
+ SBIG_UINT(pdata,32,svfs.TotalFileNodes);
+ SBIG_UINT(pdata,40,svfs.FreeFileNodes);
+ SBIG_UINT(pdata,48,svfs.FsIdentifier);
+ DEBUG(5,("call_trans2qfsinfo : SMB_QUERY_POSIX_FS_INFO succsessful\n"));
+#ifdef EOPNOTSUPP
+ } else if (rc == EOPNOTSUPP) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+#endif /* EOPNOTSUPP */
+ } else {
+ DEBUG(0,("vfs_statvfs() failed for service [%s]\n",lp_servicename(SNUM(conn))));
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+ break;
+ }
+
+ case SMB_QUERY_POSIX_WHOAMI:
+ {
+ uint32_t flags = 0;
+ uint32_t sid_bytes;
+ int i;
+
+ if (!lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (max_data_bytes < 40) {
+ reply_nterror(req, NT_STATUS_BUFFER_TOO_SMALL);
+ return;
+ }
+
+ /* We ARE guest if global_sid_Builtin_Guests is
+ * in our list of SIDs.
+ */
+ if (nt_token_check_sid(&global_sid_Builtin_Guests,
+ conn->server_info->ptok)) {
+ flags |= SMB_WHOAMI_GUEST;
+ }
+
+ /* We are NOT guest if global_sid_Authenticated_Users
+ * is in our list of SIDs.
+ */
+ if (nt_token_check_sid(&global_sid_Authenticated_Users,
+ conn->server_info->ptok)) {
+ flags &= ~SMB_WHOAMI_GUEST;
+ }
+
+ /* NOTE: 8 bytes for UID/GID, irrespective of native
+ * platform size. This matches
+ * SMB_QUERY_FILE_UNIX_BASIC and friends.
+ */
+ data_len = 4 /* flags */
+ + 4 /* flag mask */
+ + 8 /* uid */
+ + 8 /* gid */
+ + 4 /* ngroups */
+ + 4 /* num_sids */
+ + 4 /* SID bytes */
+ + 4 /* pad/reserved */
+ + (conn->server_info->utok.ngroups * 8)
+ /* groups list */
+ + (conn->server_info->ptok->num_sids *
+ SID_MAX_SIZE)
+ /* SID list */;
+
+ SIVAL(pdata, 0, flags);
+ SIVAL(pdata, 4, SMB_WHOAMI_MASK);
+ SBIG_UINT(pdata, 8,
+ (SMB_BIG_UINT)conn->server_info->utok.uid);
+ SBIG_UINT(pdata, 16,
+ (SMB_BIG_UINT)conn->server_info->utok.gid);
+
+
+ if (data_len >= max_data_bytes) {
+ /* Potential overflow, skip the GIDs and SIDs. */
+
+ SIVAL(pdata, 24, 0); /* num_groups */
+ SIVAL(pdata, 28, 0); /* num_sids */
+ SIVAL(pdata, 32, 0); /* num_sid_bytes */
+ SIVAL(pdata, 36, 0); /* reserved */
+
+ data_len = 40;
+ break;
+ }
+
+ SIVAL(pdata, 24, conn->server_info->utok.ngroups);
+ SIVAL(pdata, 28, conn->server_info->num_sids);
+
+ /* We walk the SID list twice, but this call is fairly
+ * infrequent, and I don't expect that it's performance
+ * sensitive -- jpeach
+ */
+ for (i = 0, sid_bytes = 0;
+ i < conn->server_info->ptok->num_sids; ++i) {
+ sid_bytes += ndr_size_dom_sid(
+ &conn->server_info->ptok->user_sids[i],
+ 0);
+ }
+
+ /* SID list byte count */
+ SIVAL(pdata, 32, sid_bytes);
+
+ /* 4 bytes pad/reserved - must be zero */
+ SIVAL(pdata, 36, 0);
+ data_len = 40;
+
+ /* GID list */
+ for (i = 0; i < conn->server_info->utok.ngroups; ++i) {
+ SBIG_UINT(pdata, data_len,
+ (SMB_BIG_UINT)conn->server_info->utok.groups[i]);
+ data_len += 8;
+ }
+
+ /* SID list */
+ for (i = 0;
+ i < conn->server_info->ptok->num_sids; ++i) {
+ int sid_len = ndr_size_dom_sid(
+ &conn->server_info->ptok->user_sids[i],
+ 0);
+
+ sid_linearize(pdata + data_len, sid_len,
+ &conn->server_info->ptok->user_sids[i]);
+ data_len += sid_len;
+ }
+
+ break;
+ }
+
+ case SMB_MAC_QUERY_FS_INFO:
+ /*
+ * Thursby MAC extension... ONLY on NTFS filesystems
+ * once we do streams then we don't need this
+ */
+ if (strequal(lp_fstype(SNUM(conn)),"NTFS")) {
+ data_len = 88;
+ SIVAL(pdata,84,0x100); /* Don't support mac... */
+ break;
+ }
+ /* drop through */
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+
+ send_trans2_replies(conn, req, params, 0, pdata, data_len,
+ max_data_bytes);
+
+ DEBUG( 4, ( "%s info_level = %d\n",
+ smb_fn_name(CVAL(req->inbuf,smb_com)), info_level) );
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_SETFSINFO (set filesystem info).
+****************************************************************************/
+
+static void call_trans2setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *pdata = *ppdata;
+ char *params = *pparams;
+ uint16 info_level;
+
+ DEBUG(10,("call_trans2setfsinfo: for service [%s]\n",lp_servicename(SNUM(conn))));
+
+ /* */
+ if (total_params < 4) {
+ DEBUG(0,("call_trans2setfsinfo: requires total_params(%d) >= 4 bytes!\n",
+ total_params));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,2);
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION &&
+ info_level != SMB_SET_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2setfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION) {
+ DEBUG(0,("call_trans2setfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ exit_server_cleanly("encryption required "
+ "on connection");
+ return;
+ }
+ }
+
+ switch(info_level) {
+ case SMB_SET_CIFS_UNIX_INFO:
+ {
+ uint16 client_unix_major;
+ uint16 client_unix_minor;
+ uint32 client_unix_cap_low;
+ uint32 client_unix_cap_high;
+
+ if (!lp_unix_extensions()) {
+ reply_nterror(req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* There should be 12 bytes of capabilities set. */
+ if (total_data < 8) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ client_unix_major = SVAL(pdata,0);
+ client_unix_minor = SVAL(pdata,2);
+ client_unix_cap_low = IVAL(pdata,4);
+ client_unix_cap_high = IVAL(pdata,8);
+ /* Just print these values for now. */
+ DEBUG(10,("call_trans2setfsinfo: set unix info. major = %u, minor = %u \
+cap_low = 0x%x, cap_high = 0x%x\n",
+ (unsigned int)client_unix_major,
+ (unsigned int)client_unix_minor,
+ (unsigned int)client_unix_cap_low,
+ (unsigned int)client_unix_cap_high ));
+
+ /* Here is where we must switch to posix pathname processing... */
+ if (client_unix_cap_low & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ lp_set_posix_pathnames();
+ mangle_change_to_posix();
+ }
+
+ if ((client_unix_cap_low & CIFS_UNIX_FCNTL_LOCKS_CAP) &&
+ !(client_unix_cap_low & CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP)) {
+ /* Client that knows how to do posix locks,
+ * but not posix open/mkdir operations. Set a
+ * default type for read/write checks. */
+
+ lp_set_posix_default_cifsx_readwrite_locktype(POSIX_LOCK);
+
+ }
+ break;
+ }
+
+ case SMB_REQUEST_TRANSPORT_ENCRYPTION:
+ {
+ NTSTATUS status;
+ size_t param_len = 0;
+ size_t data_len = total_data;
+
+ if (!lp_unix_extensions()) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (lp_smb_encrypt(SNUM(conn)) == false) {
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ DEBUG( 4,("call_trans2setfsinfo: "
+ "request transport encryption.\n"));
+
+ status = srv_request_encryption_setup(conn,
+ (unsigned char **)ppdata,
+ &data_len,
+ (unsigned char **)pparams,
+ &param_len);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ send_trans2_replies(conn, req,
+ *pparams,
+ param_len,
+ *ppdata,
+ data_len,
+ max_data_bytes);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Server-side transport
+ * encryption is now *on*. */
+ status = srv_encryption_start(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly(
+ "Failure in setting "
+ "up encrypted transport");
+ }
+ }
+ return;
+ }
+
+ case SMB_FS_QUOTA_INFORMATION:
+ {
+ files_struct *fsp = NULL;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(quotas);
+
+ /* access check */
+ if ((conn->server_info->utok.uid != 0)
+ ||!CAN_WRITE(conn)) {
+ DEBUG(0,("set_user_quota: access_denied service [%s] user [%s]\n",
+ lp_servicename(SNUM(conn)),
+ conn->server_info->unix_name));
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+
+ /* note: normaly there're 48 bytes,
+ * but we didn't use the last 6 bytes for now
+ * --metze
+ */
+ fsp = file_fsp(SVAL(params,0));
+
+ if (!check_fsp_ntquota_handle(conn, req,
+ fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ reply_nterror(
+ req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (total_data < 42) {
+ DEBUG(0,("call_trans2setfsinfo: SET_FS_QUOTA: requires total_data(%d) >= 42 bytes!\n",
+ total_data));
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* unknown_1 24 NULL bytes in pdata*/
+
+ /* the soft quotas 8 bytes (SMB_BIG_UINT)*/
+ quotas.softlim = (SMB_BIG_UINT)IVAL(pdata,24);
+#ifdef LARGE_SMB_OFF_T
+ quotas.softlim |= (((SMB_BIG_UINT)IVAL(pdata,28)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if ((IVAL(pdata,28) != 0)&&
+ ((quotas.softlim != 0xFFFFFFFF)||
+ (IVAL(pdata,28)!=0xFFFFFFFF))) {
+ /* more than 32 bits? */
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ /* the hard quotas 8 bytes (SMB_BIG_UINT)*/
+ quotas.hardlim = (SMB_BIG_UINT)IVAL(pdata,32);
+#ifdef LARGE_SMB_OFF_T
+ quotas.hardlim |= (((SMB_BIG_UINT)IVAL(pdata,36)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if ((IVAL(pdata,36) != 0)&&
+ ((quotas.hardlim != 0xFFFFFFFF)||
+ (IVAL(pdata,36)!=0xFFFFFFFF))) {
+ /* more than 32 bits? */
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ /* quota_flags 2 bytes **/
+ quotas.qflags = SVAL(pdata,40);
+
+ /* unknown_2 6 NULL bytes follow*/
+
+ /* now set the quotas */
+ if (vfs_set_ntquota(fsp, SMB_USER_FS_QUOTA_TYPE, NULL, &quotas)!=0) {
+ DEBUG(0,("vfs_set_ntquota() failed for service [%s]\n",lp_servicename(SNUM(conn))));
+ reply_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+
+ break;
+ }
+ default:
+ DEBUG(3,("call_trans2setfsinfo: unknown level (0x%X) not implemented yet.\n",
+ info_level));
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ break;
+ }
+
+ /*
+ * sending this reply works fine,
+ * but I'm not sure it's the same
+ * like windows do...
+ * --metze
+ */
+ reply_outbuf(req, 10, 0);
+}
+
+#if defined(HAVE_POSIX_ACLS)
+/****************************************************************************
+ Utility function to count the number of entries in a POSIX acl.
+****************************************************************************/
+
+static unsigned int count_acl_entries(connection_struct *conn, SMB_ACL_T posix_acl)
+{
+ unsigned int ace_count = 0;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (SMB_VFS_SYS_ACL_GET_ENTRY(conn, posix_acl, entry_id, &entry) == 1)) {
+ /* get_next... */
+ if (entry_id == SMB_ACL_FIRST_ENTRY) {
+ entry_id = SMB_ACL_NEXT_ENTRY;
+ }
+ ace_count++;
+ }
+ return ace_count;
+}
+
+/****************************************************************************
+ Utility function to marshall a POSIX acl into wire format.
+****************************************************************************/
+
+static bool marshall_posix_acl(connection_struct *conn, char *pdata, SMB_STRUCT_STAT *pst, SMB_ACL_T posix_acl)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (SMB_VFS_SYS_ACL_GET_ENTRY(conn, posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ unsigned char perms = 0;
+ unsigned int own_grp;
+
+ /* get_next... */
+ if (entry_id == SMB_ACL_FIRST_ENTRY) {
+ entry_id = SMB_ACL_NEXT_ENTRY;
+ }
+
+ if (SMB_VFS_SYS_ACL_GET_TAG_TYPE(conn, entry, &tagtype) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_TAG_TYPE failed.\n"));
+ return False;
+ }
+
+ if (SMB_VFS_SYS_ACL_GET_PERMSET(conn, entry, &permset) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_PERMSET failed.\n"));
+ return False;
+ }
+
+ perms |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_READ) ? SMB_POSIX_ACL_READ : 0);
+ perms |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_WRITE) ? SMB_POSIX_ACL_WRITE : 0);
+ perms |= (SMB_VFS_SYS_ACL_GET_PERM(conn, permset, SMB_ACL_EXECUTE) ? SMB_POSIX_ACL_EXECUTE : 0);
+
+ SCVAL(pdata,1,perms);
+
+ switch (tagtype) {
+ case SMB_ACL_USER_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER_OBJ);
+ own_grp = (unsigned int)pst->st_uid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)SMB_VFS_SYS_ACL_GET_QUALIFIER(conn, entry);
+ if (!puid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*puid;
+ SMB_VFS_SYS_ACL_FREE_QUALIFIER(conn, (void *)puid,tagtype);
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP_OBJ);
+ own_grp = (unsigned int)pst->st_gid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid= (gid_t *)SMB_VFS_SYS_ACL_GET_QUALIFIER(conn, entry);
+ if (!pgid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*pgid;
+ SMB_VFS_SYS_ACL_FREE_QUALIFIER(conn, (void *)pgid,tagtype);
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_MASK:
+ SCVAL(pdata,0,SMB_POSIX_ACL_MASK);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ case SMB_ACL_OTHER:
+ SCVAL(pdata,0,SMB_POSIX_ACL_OTHER);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ default:
+ DEBUG(0,("marshall_posix_acl: unknown tagtype.\n"));
+ return False;
+ }
+ pdata += SMB_POSIX_ACL_ENTRY_SIZE;
+ }
+
+ return True;
+}
+#endif
+
+/****************************************************************************
+ Store the FILE_UNIX_BASIC info.
+****************************************************************************/
+
+static char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ DEBUG(10,("store_file_unix_basic: SMB_QUERY_FILE_UNIX_BASIC\n"));
+ DEBUG(4,("store_file_unix_basic: st_mode=%o\n",(int)psbuf->st_mode));
+
+ SOFF_T(pdata,0,get_file_size(*psbuf)); /* File size 64 Bit */
+ pdata += 8;
+
+ SOFF_T(pdata,0,get_allocation_size(conn,fsp,psbuf)); /* Number of bytes used on disk - 64 Bit */
+ pdata += 8;
+
+ put_long_date_timespec(pdata,get_ctimespec(psbuf)); /* Change Time 64 Bit */
+ put_long_date_timespec(pdata+8,get_atimespec(psbuf)); /* Last access time 64 Bit */
+ put_long_date_timespec(pdata+16,get_mtimespec(psbuf)); /* Last modification time 64 Bit */
+ pdata += 24;
+
+ SIVAL(pdata,0,psbuf->st_uid); /* user id for the owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_gid); /* group id of owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_filetype(psbuf->st_mode));
+ pdata += 4;
+
+ SIVAL(pdata,0,unix_dev_major(psbuf->st_rdev)); /* Major device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_dev_minor(psbuf->st_rdev)); /* Minor device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SINO_T_VAL(pdata,0,(SMB_INO_T)psbuf->st_ino); /* inode number */
+ pdata += 8;
+
+ SIVAL(pdata,0, unix_perms_to_wire(psbuf->st_mode)); /* Standard UNIX file permissions */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_nlink); /* number of hard links */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ return pdata;
+}
+
+/* Forward and reverse mappings from the UNIX_INFO2 file flags field and
+ * the chflags(2) (or equivalent) flags.
+ *
+ * XXX: this really should be behind the VFS interface. To do this, we would
+ * need to alter SMB_STRUCT_STAT so that it included a flags and a mask field.
+ * Each VFS module could then implement its own mapping as appropriate for the
+ * platform. We would then pass the SMB flags into SMB_VFS_CHFLAGS.
+ */
+static const struct {unsigned stat_fflag; unsigned smb_fflag;}
+ info2_flags_map[] =
+{
+#ifdef UF_NODUMP
+ { UF_NODUMP, EXT_DO_NOT_BACKUP },
+#endif
+
+#ifdef UF_IMMUTABLE
+ { UF_IMMUTABLE, EXT_IMMUTABLE },
+#endif
+
+#ifdef UF_APPEND
+ { UF_APPEND, EXT_OPEN_APPEND_ONLY },
+#endif
+
+#ifdef UF_HIDDEN
+ { UF_HIDDEN, EXT_HIDDEN },
+#endif
+
+ /* Do not remove. We need to guarantee that this array has at least one
+ * entry to build on HP-UX.
+ */
+ { 0, 0 }
+
+};
+
+static void map_info2_flags_from_sbuf(const SMB_STRUCT_STAT *psbuf,
+ uint32 *smb_fflags, uint32 *smb_fmask)
+{
+#ifdef HAVE_STAT_ST_FLAGS
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ *smb_fmask |= info2_flags_map[i].smb_fflag;
+ if (psbuf->st_flags & info2_flags_map[i].stat_fflag) {
+ *smb_fflags |= info2_flags_map[i].smb_fflag;
+ }
+ }
+#endif /* HAVE_STAT_ST_FLAGS */
+}
+
+static bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf,
+ const uint32 smb_fflags,
+ const uint32 smb_fmask,
+ int *stat_fflags)
+{
+#ifdef HAVE_STAT_ST_FLAGS
+ uint32 max_fmask = 0;
+ int i;
+
+ *stat_fflags = psbuf->st_flags;
+
+ /* For each flags requested in smb_fmask, check the state of the
+ * corresponding flag in smb_fflags and set or clear the matching
+ * stat flag.
+ */
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ max_fmask |= info2_flags_map[i].smb_fflag;
+ if (smb_fmask & info2_flags_map[i].smb_fflag) {
+ if (smb_fflags & info2_flags_map[i].smb_fflag) {
+ *stat_fflags |= info2_flags_map[i].stat_fflag;
+ } else {
+ *stat_fflags &= ~info2_flags_map[i].stat_fflag;
+ }
+ }
+ }
+
+ /* If smb_fmask is asking to set any bits that are not supported by
+ * our flag mappings, we should fail.
+ */
+ if ((smb_fmask & max_fmask) != smb_fmask) {
+ return False;
+ }
+
+ return True;
+#else
+ return False;
+#endif /* HAVE_STAT_ST_FLAGS */
+}
+
+
+/* Just like SMB_QUERY_FILE_UNIX_BASIC, but with the addition
+ * of file flags and birth (create) time.
+ */
+static char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ uint32 file_flags = 0;
+ uint32 flags_mask = 0;
+
+ pdata = store_file_unix_basic(conn, pdata, fsp, psbuf);
+
+ /* Create (birth) time 64 bit */
+ put_long_date_timespec(pdata, get_create_timespec(psbuf, False));
+ pdata += 8;
+
+ map_info2_flags_from_sbuf(psbuf, &file_flags, &flags_mask);
+ SIVAL(pdata, 0, file_flags); /* flags */
+ SIVAL(pdata, 4, flags_mask); /* mask */
+ pdata += 8;
+
+ return pdata;
+}
+
+static NTSTATUS marshall_stream_info(unsigned int num_streams,
+ const struct stream_struct *streams,
+ char *data,
+ unsigned int max_data_bytes,
+ unsigned int *data_size)
+{
+ unsigned int i;
+ unsigned int ofs = 0;
+
+ for (i=0; i<num_streams; i++) {
+ unsigned int next_offset;
+ size_t namelen;
+ smb_ucs2_t *namebuf;
+
+ if (!push_ucs2_talloc(talloc_tos(), &namebuf,
+ streams[i].name, &namelen) ||
+ namelen <= 2)
+ {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * name_buf is now null-terminated, we need to marshall as not
+ * terminated
+ */
+
+ namelen -= 2;
+
+ if (ofs + 24 + namelen > max_data_bytes) {
+ TALLOC_FREE(namebuf);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ SIVAL(data, ofs+4, namelen);
+ SOFF_T(data, ofs+8, streams[i].size);
+ SOFF_T(data, ofs+16, streams[i].alloc_size);
+ memcpy(data+ofs+24, namebuf, namelen);
+ TALLOC_FREE(namebuf);
+
+ next_offset = ofs + 24 + namelen;
+
+ if (i == num_streams-1) {
+ SIVAL(data, ofs, 0);
+ }
+ else {
+ unsigned int align = ndr_align_size(next_offset, 8);
+
+ if (next_offset + align > max_data_bytes) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ memset(data+next_offset, 0, align);
+ next_offset += align;
+
+ SIVAL(data, ofs, next_offset - ofs);
+ ofs = next_offset;
+ }
+
+ ofs = next_offset;
+ }
+
+ *data_size = ofs;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_QFILEINFO on a PIPE !
+****************************************************************************/
+
+static void call_trans2qpipeinfo(connection_struct *conn,
+ struct smb_request *req,
+ unsigned int tran_call,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ unsigned int data_size = 0;
+ unsigned int param_size = 2;
+ uint16 info_level;
+ smb_np_struct *p_pipe = NULL;
+
+ if (!params) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ p_pipe = get_rpc_pipe_p(SVAL(params,0));
+ if (p_pipe == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ info_level = SVAL(params,2);
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ switch (info_level) {
+ case SMB_FILE_STANDARD_INFORMATION:
+ memset(pdata,0,24);
+ SOFF_T(pdata,0,4096LL);
+ SIVAL(pdata,16,1);
+ SIVAL(pdata,20,1);
+ data_size = 24;
+ break;
+
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ send_trans2_replies(conn, req, params, param_size, *ppdata, data_size,
+ max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFILEPATHINFO or TRANSACT2_QFILEINFO (query file info by
+ file name or file id).
+****************************************************************************/
+
+static void call_trans2qfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ unsigned int tran_call,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *dstart, *dend;
+ uint16 info_level;
+ int mode=0;
+ int nlink;
+ SMB_OFF_T file_size=0;
+ SMB_BIG_UINT allocation_size=0;
+ unsigned int data_size = 0;
+ unsigned int param_size = 2;
+ SMB_STRUCT_STAT sbuf;
+ char *dos_fname = NULL;
+ char *fname = NULL;
+ char *fullpathname;
+ char *base_name;
+ char *p;
+ SMB_OFF_T pos = 0;
+ bool delete_pending = False;
+ int len;
+ time_t create_time, mtime, atime;
+ struct timespec create_time_ts, mtime_ts, atime_ts;
+ struct timespec write_time_ts;
+ files_struct *fsp = NULL;
+ struct file_id fileid;
+ struct ea_list *ea_list = NULL;
+ uint32 access_mask = 0x12019F; /* Default - GENERIC_EXECUTE mapping from Windows */
+ char *lock_data = NULL;
+ bool ms_dfs_link = false;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!params) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ZERO_STRUCT(sbuf);
+ ZERO_STRUCT(write_time_ts);
+
+ if (tran_call == TRANSACT2_QFILEINFO) {
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (IS_IPC(conn)) {
+ call_trans2qpipeinfo(conn, req, tran_call,
+ pparams, total_params,
+ ppdata, total_data,
+ max_data_bytes);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(params,0));
+ info_level = SVAL(params,2);
+
+ DEBUG(3,("call_trans2qfilepathinfo: TRANSACT2_QFILEINFO: level = %d\n", info_level));
+
+ if (INFO_LEVEL_IS_UNIX(info_level) && !lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* Initial check for valid fsp ptr. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+
+ fname = talloc_strdup(talloc_tos(),fsp->fsp_name);
+ if (!fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if(fsp->fake_file_handle) {
+ /*
+ * This is actually for the QUOTA_FAKE_FILE --metze
+ */
+
+ /* We know this name is ok, it's already passed the checks. */
+
+ } else if(fsp && (fsp->is_directory || fsp->fh->fd == -1)) {
+ /*
+ * This is actually a QFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ /* Always do lstat for UNIX calls. */
+ if (SMB_VFS_LSTAT(conn,fname,&sbuf)) {
+ DEBUG(3,("call_trans2qfilepathinfo: SMB_VFS_LSTAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req,ERRDOS,ERRbadpath);
+ return;
+ }
+ } else if (SMB_VFS_STAT(conn,fname,&sbuf)) {
+ DEBUG(3,("call_trans2qfilepathinfo: SMB_VFS_STAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadpath);
+ return;
+ }
+
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ get_file_infos(fileid, &delete_pending, &write_time_ts);
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ if (SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ DEBUG(3,("fstat of fnum %d failed (%s)\n", fsp->fnum, strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+ pos = fsp->fh->position_information;
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ get_file_infos(fileid, &delete_pending, &write_time_ts);
+ access_mask = fsp->access_mask;
+ }
+
+ } else {
+ NTSTATUS status = NT_STATUS_OK;
+
+ /* qpathinfo */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ DEBUG(3,("call_trans2qfilepathinfo: TRANSACT2_QPATHINFO: level = %d\n", info_level));
+
+ if (INFO_LEVEL_IS_UNIX(info_level) && !lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ srvstr_get_path(ctx, params, req->flags2, &fname, &params[6],
+ total_params - 6,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = resolve_dfspath(ctx,
+ conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ fname,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, fname, False, &fname, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("call_trans2qfilepathinfo: fileinfo of %s failed (%s)\n",fname,nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ /* Always do lstat for UNIX calls. */
+ if (SMB_VFS_LSTAT(conn,fname,&sbuf)) {
+ DEBUG(3,("call_trans2qfilepathinfo: SMB_VFS_LSTAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadpath);
+ return;
+ }
+
+ } else if (!VALID_STAT(sbuf) && SMB_VFS_STAT(conn,fname,&sbuf) && (info_level != SMB_INFO_IS_NAME_VALID)) {
+ ms_dfs_link = check_msdfs_link(conn,fname,&sbuf);
+
+ if (!ms_dfs_link) {
+ DEBUG(3,("call_trans2qfilepathinfo: SMB_VFS_STAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadpath);
+ return;
+ }
+ }
+
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ get_file_infos(fileid, &delete_pending, &write_time_ts);
+ if (delete_pending) {
+ reply_nterror(req, NT_STATUS_DELETE_PENDING);
+ return;
+ }
+ }
+
+ if (INFO_LEVEL_IS_UNIX(info_level) && !lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ DEBUG(3,("call_trans2qfilepathinfo %s (fnum = %d) level=%d call=%d total_data=%d\n",
+ fname,fsp ? fsp->fnum : -1, info_level,tran_call,total_data));
+
+ p = strrchr_m(fname,'/');
+ if (!p)
+ base_name = fname;
+ else
+ base_name = p+1;
+
+ if (ms_dfs_link) {
+ mode = dos_mode_msdfs(conn,fname,&sbuf);
+ } else {
+ mode = dos_mode(conn,fname,&sbuf);
+ }
+ if (!mode)
+ mode = FILE_ATTRIBUTE_NORMAL;
+
+ nlink = sbuf.st_nlink;
+
+ if (nlink && (mode&aDIR)) {
+ nlink = 1;
+ }
+
+ if ((nlink > 0) && delete_pending) {
+ nlink -= 1;
+ }
+
+ fullpathname = fname;
+ if (!(mode & aDIR))
+ file_size = get_file_size(sbuf);
+
+ /* Pull out any data sent here before we realloc. */
+ switch (info_level) {
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ /* Pull any EA list from the data portion. */
+ uint32 ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ ea_size = IVAL(pdata,0);
+
+ if (total_data > 0 && ea_size != total_data) {
+ DEBUG(4,("call_trans2qfilepathinfo: Rejecting EA request with incorrect \
+total_data=%u (should be %u)\n", (unsigned int)total_data, (unsigned int)IVAL(pdata,0) ));
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_doserror(req, ERRDOS,
+ ERReasnotsupported);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(ctx, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ break;
+ }
+
+ case SMB_QUERY_POSIX_LOCK:
+ {
+ if (fsp == NULL || fsp->fh->fd == -1) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* Copy the lock range data. */
+ lock_data = (char *)TALLOC_MEMDUP(
+ ctx, pdata, total_data);
+ if (!lock_data) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+ default:
+ break;
+ }
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+ dstart = pdata;
+ dend = dstart + data_size - 1;
+
+ create_time_ts = get_create_timespec(&sbuf,lp_fake_dir_create_times(SNUM(conn)));
+ mtime_ts = get_mtimespec(&sbuf);
+ atime_ts = get_atimespec(&sbuf);
+
+ allocation_size = get_allocation_size(conn,fsp,&sbuf);
+
+ if (!fsp) {
+ /* Do we have this path open ? */
+ files_struct *fsp1;
+ fileid = vfs_file_id_from_sbuf(conn, &sbuf);
+ fsp1 = file_find_di_first(fileid);
+ if (fsp1 && fsp1->initial_allocation_size) {
+ allocation_size = get_allocation_size(conn, fsp1, &sbuf);
+ }
+ }
+
+ if (!null_timespec(write_time_ts) && !INFO_LEVEL_IS_UNIX(info_level)) {
+ mtime_ts = write_time_ts;
+ }
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_time_ts);
+ dos_filetime_timespec(&mtime_ts);
+ dos_filetime_timespec(&atime_ts);
+ }
+
+ create_time = convert_timespec_to_time_t(create_time_ts);
+ mtime = convert_timespec_to_time_t(mtime_ts);
+ atime = convert_timespec_to_time_t(atime_ts);
+
+ /* NT expects the name to be in an exact form of the *full*
+ filename. See the trans2 torture test */
+ if (ISDOT(base_name)) {
+ dos_fname = talloc_strdup(ctx, "\\");
+ if (!dos_fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ } else {
+ dos_fname = talloc_asprintf(ctx,
+ "\\%s",
+ fname);
+ if (!dos_fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ string_replace(dos_fname, '/', '\\');
+ }
+
+ switch (info_level) {
+ case SMB_INFO_STANDARD:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_INFO_STANDARD\n"));
+ data_size = 22;
+ srv_put_dos_date2(pdata,l1_fdateCreation,create_time);
+ srv_put_dos_date2(pdata,l1_fdateLastAccess,atime);
+ srv_put_dos_date2(pdata,l1_fdateLastWrite,mtime); /* write time */
+ SIVAL(pdata,l1_cbFile,(uint32)file_size);
+ SIVAL(pdata,l1_cbFileAlloc,(uint32)allocation_size);
+ SSVAL(pdata,l1_attrFile,mode);
+ break;
+
+ case SMB_INFO_QUERY_EA_SIZE:
+ {
+ unsigned int ea_size = estimate_ea_size(conn, fsp, fname);
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_INFO_QUERY_EA_SIZE\n"));
+ data_size = 26;
+ srv_put_dos_date2(pdata,0,create_time);
+ srv_put_dos_date2(pdata,4,atime);
+ srv_put_dos_date2(pdata,8,mtime); /* write time */
+ SIVAL(pdata,12,(uint32)file_size);
+ SIVAL(pdata,16,(uint32)allocation_size);
+ SSVAL(pdata,20,mode);
+ SIVAL(pdata,22,ea_size);
+ break;
+ }
+
+ case SMB_INFO_IS_NAME_VALID:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_INFO_IS_NAME_VALID\n"));
+ if (tran_call == TRANSACT2_QFILEINFO) {
+ /* os/2 needs this ? really ?*/
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+ data_size = 0;
+ param_size = 0;
+ break;
+
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ size_t total_ea_len = 0;
+ struct ea_list *ea_file_list = NULL;
+
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_INFO_QUERY_EAS_FROM_LIST\n"));
+
+ ea_file_list = get_ea_list_from_file(ctx, conn, fsp, fname, &total_ea_len);
+ ea_list = ea_list_union(ea_list, ea_file_list, &total_ea_len);
+
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB_INFO_QUERY_ALL_EAS:
+ {
+ /* We have data_size bytes to put EA's into. */
+ size_t total_ea_len = 0;
+
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_INFO_QUERY_ALL_EAS\n"));
+
+ ea_list = get_ea_list_from_file(ctx, conn, fsp, fname, &total_ea_len);
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB_FILE_BASIC_INFORMATION:
+ case SMB_QUERY_FILE_BASIC_INFO:
+
+ if (info_level == SMB_QUERY_FILE_BASIC_INFO) {
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_QUERY_FILE_BASIC_INFO\n"));
+ data_size = 36; /* w95 returns 40 bytes not 36 - why ?. */
+ } else {
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_BASIC_INFORMATION\n"));
+ data_size = 40;
+ SIVAL(pdata,36,0);
+ }
+ put_long_date_timespec(pdata,create_time_ts);
+ put_long_date_timespec(pdata+8,atime_ts);
+ put_long_date_timespec(pdata+16,mtime_ts); /* write time */
+ put_long_date_timespec(pdata+24,mtime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+
+ DEBUG(5,("SMB_QFBI - "));
+ DEBUG(5,("create: %s ", ctime(&create_time)));
+ DEBUG(5,("access: %s ", ctime(&atime)));
+ DEBUG(5,("write: %s ", ctime(&mtime)));
+ DEBUG(5,("change: %s ", ctime(&mtime)));
+ DEBUG(5,("mode: %x\n", mode));
+ break;
+
+ case SMB_FILE_STANDARD_INFORMATION:
+ case SMB_QUERY_FILE_STANDARD_INFO:
+
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_STANDARD_INFORMATION\n"));
+ data_size = 24;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending?1:0);
+ SCVAL(pdata,21,(mode&aDIR)?1:0);
+ SSVAL(pdata,22,0); /* Padding. */
+ break;
+
+ case SMB_FILE_EA_INFORMATION:
+ case SMB_QUERY_FILE_EA_INFO:
+ {
+ unsigned int ea_size = estimate_ea_size(conn, fsp, fname);
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_EA_INFORMATION\n"));
+ data_size = 4;
+ SIVAL(pdata,0,ea_size);
+ break;
+ }
+
+ /* Get the 8.3 name - used if NT SMB was negotiated. */
+ case SMB_QUERY_FILE_ALT_NAME_INFO:
+ case SMB_FILE_ALTERNATE_NAME_INFORMATION:
+ {
+ char mangled_name[13];
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ALTERNATE_NAME_INFORMATION\n"));
+ if (!name_to_8_3(base_name,mangled_name,
+ True,conn->params)) {
+ reply_nterror(
+ req,
+ NT_STATUS_NO_MEMORY);
+ }
+ len = srvstr_push(dstart, req->flags2,
+ pdata+4, mangled_name,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE);
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+ }
+
+ case SMB_QUERY_FILE_NAME_INFO:
+ /*
+ this must be *exactly* right for ACLs on mapped drives to work
+ */
+ len = srvstr_push(dstart, req->flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE);
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_QUERY_FILE_NAME_INFO\n"));
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_QUERY_FILE_ALLOCATION_INFO:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ALLOCATION_INFORMATION\n"));
+ data_size = 8;
+ SOFF_T(pdata,0,allocation_size);
+ break;
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_QUERY_FILE_END_OF_FILEINFO:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_END_OF_FILE_INFORMATION\n"));
+ data_size = 8;
+ SOFF_T(pdata,0,file_size);
+ break;
+
+ case SMB_QUERY_FILE_ALL_INFO:
+ case SMB_FILE_ALL_INFORMATION:
+ {
+ unsigned int ea_size = estimate_ea_size(conn, fsp, fname);
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ALL_INFORMATION\n"));
+ put_long_date_timespec(pdata,create_time_ts);
+ put_long_date_timespec(pdata+8,atime_ts);
+ put_long_date_timespec(pdata+16,mtime_ts); /* write time */
+ put_long_date_timespec(pdata+24,mtime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+ SIVAL(pdata,36,0); /* padding. */
+ pdata += 40;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending);
+ SCVAL(pdata,21,(mode&aDIR)?1:0);
+ SSVAL(pdata,22,0);
+ pdata += 24;
+ SIVAL(pdata,0,ea_size);
+ pdata += 4; /* EA info */
+ len = srvstr_push(dstart, req->flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE);
+ SIVAL(pdata,0,len);
+ pdata += 4 + len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+ break;
+ }
+ case SMB_FILE_INTERNAL_INFORMATION:
+ /* This should be an index number - looks like
+ dev/ino to me :-)
+
+ I think this causes us to fail the IFSKIT
+ BasicFileInformationTest. -tpot */
+
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_INTERNAL_INFORMATION\n"));
+ SIVAL(pdata,0,sbuf.st_ino); /* FileIndexLow */
+ SIVAL(pdata,4,sbuf.st_dev); /* FileIndexHigh */
+ data_size = 8;
+ break;
+
+ case SMB_FILE_ACCESS_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ACCESS_INFORMATION\n"));
+ SIVAL(pdata,0,access_mask);
+ data_size = 4;
+ break;
+
+ case SMB_FILE_NAME_INFORMATION:
+ /* Pathname with leading '\'. */
+ {
+ size_t byte_len;
+ byte_len = dos_PutUniCode(pdata+4,dos_fname,(size_t)max_data_bytes,False);
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_NAME_INFORMATION\n"));
+ SIVAL(pdata,0,byte_len);
+ data_size = 4 + byte_len;
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_DISPOSITION_INFORMATION\n"));
+ data_size = 1;
+ SCVAL(pdata,0,delete_pending);
+ break;
+
+ case SMB_FILE_POSITION_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_POSITION_INFORMATION\n"));
+ data_size = 8;
+ SOFF_T(pdata,0,pos);
+ break;
+
+ case SMB_FILE_MODE_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_MODE_INFORMATION\n"));
+ SIVAL(pdata,0,mode);
+ data_size = 4;
+ break;
+
+ case SMB_FILE_ALIGNMENT_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ALIGNMENT_INFORMATION\n"));
+ SIVAL(pdata,0,0); /* No alignment needed. */
+ data_size = 4;
+ break;
+
+ /*
+ * NT4 server just returns "invalid query" to this - if we try
+ * to answer it then NTws gets a BSOD! (tridge). W2K seems to
+ * want this. JRA.
+ */
+ /* The first statement above is false - verified using Thursby
+ * client against NT4 -- gcolley.
+ */
+ case SMB_QUERY_FILE_STREAM_INFO:
+ case SMB_FILE_STREAM_INFORMATION: {
+ unsigned int num_streams;
+ struct stream_struct *streams;
+ NTSTATUS status;
+
+ DEBUG(10,("call_trans2qfilepathinfo: "
+ "SMB_FILE_STREAM_INFORMATION\n"));
+
+ status = SMB_VFS_STREAMINFO(
+ conn, fsp, fname, talloc_tos(),
+ &num_streams, &streams);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("could not get stream info: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = marshall_stream_info(num_streams, streams,
+ pdata, max_data_bytes,
+ &data_size);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("marshall_stream_info failed: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+
+ TALLOC_FREE(streams);
+
+ break;
+ }
+ case SMB_QUERY_COMPRESSION_INFO:
+ case SMB_FILE_COMPRESSION_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_COMPRESSION_INFORMATION\n"));
+ SOFF_T(pdata,0,file_size);
+ SIVAL(pdata,8,0); /* ??? */
+ SIVAL(pdata,12,0); /* ??? */
+ data_size = 16;
+ break;
+
+ case SMB_FILE_NETWORK_OPEN_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_NETWORK_OPEN_INFORMATION\n"));
+ put_long_date_timespec(pdata,create_time_ts);
+ put_long_date_timespec(pdata+8,atime_ts);
+ put_long_date_timespec(pdata+16,mtime_ts); /* write time */
+ put_long_date_timespec(pdata+24,mtime_ts); /* change time */
+ SOFF_T(pdata,32,allocation_size);
+ SOFF_T(pdata,40,file_size);
+ SIVAL(pdata,48,mode);
+ SIVAL(pdata,52,0); /* ??? */
+ data_size = 56;
+ break;
+
+ case SMB_FILE_ATTRIBUTE_TAG_INFORMATION:
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_FILE_ATTRIBUTE_TAG_INFORMATION\n"));
+ SIVAL(pdata,0,mode);
+ SIVAL(pdata,4,0);
+ data_size = 8;
+ break;
+
+ /*
+ * CIFS UNIX Extensions.
+ */
+
+ case SMB_QUERY_FILE_UNIX_BASIC:
+
+ pdata = store_file_unix_basic(conn, pdata, fsp, &sbuf);
+ data_size = PTR_DIFF(pdata,(*ppdata));
+
+ {
+ int i;
+ DEBUG(4,("call_trans2qfilepathinfo: SMB_QUERY_FILE_UNIX_BASIC "));
+
+ for (i=0; i<100; i++)
+ DEBUG(4,("%d=%x, ",i, (*ppdata)[i]));
+ DEBUG(4,("\n"));
+ }
+
+ break;
+
+ case SMB_QUERY_FILE_UNIX_INFO2:
+
+ pdata = store_file_unix_basic_info2(conn, pdata, fsp, &sbuf);
+ data_size = PTR_DIFF(pdata,(*ppdata));
+
+ {
+ int i;
+ DEBUG(4,("call_trans2qfilepathinfo: SMB_QUERY_FILE_UNIX_INFO2 "));
+
+ for (i=0; i<100; i++)
+ DEBUG(4,("%d=%x, ",i, (*ppdata)[i]));
+ DEBUG(4,("\n"));
+ }
+
+ break;
+
+ case SMB_QUERY_FILE_UNIX_LINK:
+ {
+ char *buffer = TALLOC_ARRAY(ctx, char, PATH_MAX+1);
+
+ if (!buffer) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ DEBUG(10,("call_trans2qfilepathinfo: SMB_QUERY_FILE_UNIX_LINK\n"));
+#ifdef S_ISLNK
+ if(!S_ISLNK(sbuf.st_mode)) {
+ reply_unixerror(req, ERRSRV,
+ ERRbadlink);
+ return;
+ }
+#else
+ reply_unixerror(req, ERRDOS, ERRbadlink);
+ return;
+#endif
+ len = SMB_VFS_READLINK(conn,fullpathname,
+ buffer, PATH_MAX);
+ if (len == -1) {
+ reply_unixerror(req, ERRDOS,
+ ERRnoaccess);
+ return;
+ }
+ buffer[len] = 0;
+ len = srvstr_push(dstart, req->flags2,
+ pdata, buffer,
+ PTR_DIFF(dend, pdata),
+ STR_TERMINATE);
+ pdata += len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+
+ break;
+ }
+
+#if defined(HAVE_POSIX_ACLS)
+ case SMB_QUERY_POSIX_ACL:
+ {
+ SMB_ACL_T file_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ uint16 num_file_acls = 0;
+ uint16 num_def_acls = 0;
+
+ if (fsp && !fsp->is_directory && (fsp->fh->fd != -1)) {
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp);
+ } else {
+ file_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, fname, SMB_ACL_TYPE_ACCESS);
+ }
+
+ if (file_acl == NULL && no_acl_syscall_error(errno)) {
+ DEBUG(5,("call_trans2qfilepathinfo: ACLs not implemented on filesystem containing %s\n",
+ fname ));
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_IMPLEMENTED);
+ return;
+ }
+
+ if (S_ISDIR(sbuf.st_mode)) {
+ if (fsp && fsp->is_directory) {
+ def_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, fsp->fsp_name, SMB_ACL_TYPE_DEFAULT);
+ } else {
+ def_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, fname, SMB_ACL_TYPE_DEFAULT);
+ }
+ def_acl = free_empty_sys_acl(conn, def_acl);
+ }
+
+ num_file_acls = count_acl_entries(conn, file_acl);
+ num_def_acls = count_acl_entries(conn, def_acl);
+
+ if ( data_size < (num_file_acls + num_def_acls)*SMB_POSIX_ACL_ENTRY_SIZE + SMB_POSIX_ACL_HEADER_SIZE) {
+ DEBUG(5,("call_trans2qfilepathinfo: data_size too small (%u) need %u\n",
+ data_size,
+ (unsigned int)((num_file_acls + num_def_acls)*SMB_POSIX_ACL_ENTRY_SIZE +
+ SMB_POSIX_ACL_HEADER_SIZE) ));
+ if (file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ }
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ reply_nterror(
+ req,
+ NT_STATUS_BUFFER_TOO_SMALL);
+ return;
+ }
+
+ SSVAL(pdata,0,SMB_POSIX_ACL_VERSION);
+ SSVAL(pdata,2,num_file_acls);
+ SSVAL(pdata,4,num_def_acls);
+ if (!marshall_posix_acl(conn, pdata + SMB_POSIX_ACL_HEADER_SIZE, &sbuf, file_acl)) {
+ if (file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ }
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ reply_nterror(
+ req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ if (!marshall_posix_acl(conn, pdata + SMB_POSIX_ACL_HEADER_SIZE + (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE), &sbuf, def_acl)) {
+ if (file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ }
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ reply_nterror(
+ req,
+ NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (file_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, file_acl);
+ }
+ if (def_acl) {
+ SMB_VFS_SYS_ACL_FREE_ACL(conn, def_acl);
+ }
+ data_size = (num_file_acls + num_def_acls)*SMB_POSIX_ACL_ENTRY_SIZE + SMB_POSIX_ACL_HEADER_SIZE;
+ break;
+ }
+#endif
+
+
+ case SMB_QUERY_POSIX_LOCK:
+ {
+ NTSTATUS status = NT_STATUS_INVALID_LEVEL;
+ SMB_BIG_UINT count;
+ SMB_BIG_UINT offset;
+ uint32 lock_pid;
+ enum brl_type lock_type;
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ default:
+ /* There's no point in asking for an unlock... */
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ lock_pid = IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+#if defined(HAVE_LONGLONG)
+ offset = (((SMB_BIG_UINT) IVAL(pdata,(POSIX_LOCK_START_OFFSET+4))) << 32) |
+ ((SMB_BIG_UINT) IVAL(pdata,POSIX_LOCK_START_OFFSET));
+ count = (((SMB_BIG_UINT) IVAL(pdata,(POSIX_LOCK_LEN_OFFSET+4))) << 32) |
+ ((SMB_BIG_UINT) IVAL(pdata,POSIX_LOCK_LEN_OFFSET));
+#else /* HAVE_LONGLONG */
+ offset = (SMB_BIG_UINT)IVAL(pdata,POSIX_LOCK_START_OFFSET);
+ count = (SMB_BIG_UINT)IVAL(pdata,POSIX_LOCK_LEN_OFFSET);
+#endif /* HAVE_LONGLONG */
+
+ status = query_lock(fsp,
+ &lock_pid,
+ &count,
+ &offset,
+ &lock_type,
+ POSIX_LOCK);
+
+ if (ERROR_WAS_LOCK_DENIED(status)) {
+ /* Here we need to report who has it locked... */
+ data_size = POSIX_LOCK_DATA_SIZE;
+
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, lock_type);
+ SSVAL(pdata, POSIX_LOCK_FLAGS_OFFSET, 0);
+ SIVAL(pdata, POSIX_LOCK_PID_OFFSET, lock_pid);
+#if defined(HAVE_LONGLONG)
+ SIVAL(pdata, POSIX_LOCK_START_OFFSET, (uint32)(offset & 0xFFFFFFFF));
+ SIVAL(pdata, POSIX_LOCK_START_OFFSET + 4, (uint32)((offset >> 32) & 0xFFFFFFFF));
+ SIVAL(pdata, POSIX_LOCK_LEN_OFFSET, (uint32)(count & 0xFFFFFFFF));
+ SIVAL(pdata, POSIX_LOCK_LEN_OFFSET + 4, (uint32)((count >> 32) & 0xFFFFFFFF));
+#else /* HAVE_LONGLONG */
+ SIVAL(pdata, POSIX_LOCK_START_OFFSET, offset);
+ SIVAL(pdata, POSIX_LOCK_LEN_OFFSET, count);
+#endif /* HAVE_LONGLONG */
+
+ } else if (NT_STATUS_IS_OK(status)) {
+ /* For success we just return a copy of what we sent
+ with the lock type set to POSIX_LOCK_TYPE_UNLOCK. */
+ data_size = POSIX_LOCK_DATA_SIZE;
+ memcpy(pdata, lock_data, POSIX_LOCK_DATA_SIZE);
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_UNLOCK);
+ } else {
+ reply_nterror(req, status);
+ return;
+ }
+ break;
+ }
+
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ send_trans2_replies(conn, req, params, param_size, *ppdata, data_size,
+ max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Set a hard link (called by UNIX extensions and by NT rename with HARD link
+ code.
+****************************************************************************/
+
+NTSTATUS hardlink_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *oldname_in,
+ const char *newname_in)
+{
+ SMB_STRUCT_STAT sbuf1, sbuf2;
+ char *last_component_oldname = NULL;
+ char *last_component_newname = NULL;
+ char *oldname = NULL;
+ char *newname = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ ZERO_STRUCT(sbuf1);
+ ZERO_STRUCT(sbuf2);
+
+ status = unix_convert(ctx, conn, oldname_in, False, &oldname,
+ &last_component_oldname, &sbuf1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_name(conn, oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* source must already exist. */
+ if (!VALID_STAT(sbuf1)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = unix_convert(ctx, conn, newname_in, False, &newname,
+ &last_component_newname, &sbuf2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_name(conn, newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Disallow if newname already exists. */
+ if (VALID_STAT(sbuf2)) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(sbuf1.st_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ /* Ensure this is within the share. */
+ status = check_reduced_name(conn, oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("hardlink_internals: doing hard link %s -> %s\n", newname, oldname ));
+
+ if (SMB_VFS_LINK(conn,oldname,newname) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3,("hardlink_internals: Error %s hard link %s -> %s\n",
+ nt_errstr(status), newname, oldname));
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with setting the time from any of the setfilepathinfo functions.
+****************************************************************************/
+
+NTSTATUS smb_set_file_time(connection_struct *conn,
+ files_struct *fsp,
+ const char *fname,
+ const SMB_STRUCT_STAT *psbuf,
+ struct timespec ts[2],
+ bool setting_write_time)
+{
+ uint32 action =
+ FILE_NOTIFY_CHANGE_LAST_ACCESS
+ |FILE_NOTIFY_CHANGE_LAST_WRITE;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* get some defaults (no modifications) if any info is zero or -1. */
+ if (null_timespec(ts[0])) {
+ ts[0] = get_atimespec(psbuf);
+ action &= ~FILE_NOTIFY_CHANGE_LAST_ACCESS;
+ }
+
+ if (null_timespec(ts[1])) {
+ ts[1] = get_mtimespec(psbuf);
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ if (!setting_write_time) {
+ /* ts[1] comes from change time, not write time. */
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ DEBUG(6,("smb_set_file_time: actime: %s " , time_to_asc(convert_timespec_to_time_t(ts[0])) ));
+ DEBUG(6,("smb_set_file_time: modtime: %s ", time_to_asc(convert_timespec_to_time_t(ts[1])) ));
+
+ /*
+ * Try and set the times of this file if
+ * they are different from the current values.
+ */
+
+ {
+ struct timespec mts = get_mtimespec(psbuf);
+ struct timespec ats = get_atimespec(psbuf);
+ if ((timespec_compare(&ts[0], &ats) == 0) && (timespec_compare(&ts[1], &mts) == 0)) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (setting_write_time) {
+ /*
+ * This was a setfileinfo on an open file.
+ * NT does this a lot. We also need to
+ * set the time here, as it can be read by
+ * FindFirst/FindNext and with the patch for bug #2045
+ * in smbd/fileio.c it ensures that this timestamp is
+ * kept sticky even after a write. We save the request
+ * away and will set it on file close and after a write. JRA.
+ */
+
+ DEBUG(10,("smb_set_file_time: setting pending modtime to %s\n",
+ time_to_asc(convert_timespec_to_time_t(ts[1])) ));
+
+ if (fsp != NULL) {
+ set_sticky_write_time_fsp(fsp, ts[1]);
+ } else {
+ set_sticky_write_time_path(conn, fname,
+ vfs_file_id_from_sbuf(conn, psbuf),
+ ts[1]);
+ }
+ }
+
+ DEBUG(10,("smb_set_file_time: setting utimes to modified values.\n"));
+
+ if(file_ntimes(conn, fname, ts)!=0) {
+ return map_nt_error_from_unix(errno);
+ }
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED, action, fname);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the dosmode from any of the setfilepathinfo functions.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_dosmode(connection_struct *conn,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ uint32 dosmode)
+{
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (dosmode) {
+ if (S_ISDIR(psbuf->st_mode)) {
+ dosmode |= aDIR;
+ } else {
+ dosmode &= ~aDIR;
+ }
+ }
+
+ DEBUG(6,("smb_set_file_dosmode: dosmode: 0x%x\n", (unsigned int)dosmode));
+
+ /* check the mode isn't different, before changing it */
+ if ((dosmode != 0) && (dosmode != dos_mode(conn, fname, psbuf))) {
+
+ DEBUG(10,("smb_set_file_dosmode: file %s : setting dos mode 0x%x\n",
+ fname, (unsigned int)dosmode ));
+
+ if(file_set_dosmode(conn, fname, dosmode, psbuf, NULL, false)) {
+ DEBUG(2,("smb_set_file_dosmode: file_set_dosmode of %s failed (%s)\n",
+ fname, strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the size from any of the setfilepathinfo functions.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_size(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ SMB_OFF_T size)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(6,("smb_set_file_size: size: %.0f ", (double)size));
+
+ if (size == get_file_size(*psbuf)) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10,("smb_set_file_size: file %s : setting new size to %.0f\n",
+ fname, (double)size ));
+
+ if (fsp && fsp->fh->fd != -1) {
+ /* Handle based call. */
+ if (vfs_set_filelen(fsp, size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ status = open_file_ntcreate(conn, req, fname, psbuf,
+ FILE_WRITE_ATTRIBUTES,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ FILE_ATTRIBUTE_NORMAL,
+ FORCE_OPLOCK_BREAK_TO_NONE,
+ NULL, &new_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ if (vfs_set_filelen(new_fsp, size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file(new_fsp,NORMAL_CLOSE);
+ return status;
+ }
+
+ trigger_write_time_update_immediate(new_fsp);
+ close_file(new_fsp,NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_SET_EA.
+****************************************************************************/
+
+static NTSTATUS smb_info_set_ea(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname)
+{
+ struct ea_list *ea_list = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (total_data < 10) {
+
+ /* OS/2 workplace shell seems to send SET_EA requests of "null"
+ length. They seem to have no effect. Bug #3212. JRA */
+
+ if ((total_data == 4) && (IVAL(pdata,0) == 4)) {
+ /* We're done. We only get EA info in this call. */
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("smb_info_set_ea: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_tos();
+ ea_list = read_ea_list(ctx, pdata + 4, total_data - 4);
+ if (!ea_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = set_ea(conn, fsp, fname, ea_list);
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_DISPOSITION_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_disposition_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ bool delete_on_close;
+ uint32 dosmode = 0;
+
+ if (total_data < 1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ delete_on_close = (CVAL(pdata,0) ? True : False);
+ dosmode = dos_mode(conn, fname, psbuf);
+
+ DEBUG(10,("smb_set_file_disposition_info: file %s, dosmode = %u, "
+ "delete_on_close = %u\n",
+ fsp->fsp_name,
+ (unsigned int)dosmode,
+ (unsigned int)delete_on_close ));
+
+ status = can_set_delete_on_close(fsp, delete_on_close, dosmode);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, delete_on_close,
+ &conn->server_info->utok)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_POSITION_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_position_information(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ SMB_BIG_UINT position_information;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ /* Ignore on pathname based set. */
+ return NT_STATUS_OK;
+ }
+
+ position_information = (SMB_BIG_UINT)IVAL(pdata,0);
+#ifdef LARGE_SMB_OFF_T
+ position_information |= (((SMB_BIG_UINT)IVAL(pdata,4)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if (IVAL(pdata,4) != 0) {
+ /* more than 32 bits? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ DEBUG(10,("smb_file_position_information: Set file position information for file %s to %.0f\n",
+ fsp->fsp_name, (double)position_information ));
+ fsp->fh->position_information = position_information;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_MODE_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_mode_information(connection_struct *conn,
+ const char *pdata,
+ int total_data)
+{
+ uint32 mode;
+
+ if (total_data < 4) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ mode = IVAL(pdata,0);
+ if (mode != 0 && mode != 2 && mode != 4 && mode != 6) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_LINK (create a UNIX symlink).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_link(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ const char *fname)
+{
+ char *link_target = NULL;
+ const char *newname = fname;
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /* Set a symbolic link. */
+ /* Don't allow this if follow links is false. */
+
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_symlinks(SNUM(conn))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ srvstr_pull_talloc(ctx, pdata, req->flags2, &link_target, pdata,
+ total_data, STR_TERMINATE);
+
+ if (!link_target) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* !widelinks forces the target path to be within the share. */
+ /* This means we can interpret the target as a pathname. */
+ if (!lp_widelinks(SNUM(conn))) {
+ char *rel_name = NULL;
+ char *last_dirp = NULL;
+
+ if (*link_target == '/') {
+ /* No absolute paths allowed. */
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ rel_name = talloc_strdup(ctx,newname);
+ if (!rel_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ last_dirp = strrchr_m(rel_name, '/');
+ if (last_dirp) {
+ last_dirp[1] = '\0';
+ } else {
+ rel_name = talloc_strdup(ctx,"./");
+ if (!rel_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ rel_name = talloc_asprintf_append(rel_name,
+ "%s",
+ link_target);
+ if (!rel_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = check_name(conn, rel_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ DEBUG(10,("smb_set_file_unix_link: SMB_SET_FILE_UNIX_LINK doing symlink %s -> %s\n",
+ newname, link_target ));
+
+ if (SMB_VFS_SYMLINK(conn,link_target,newname) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_HLINK (create a UNIX hard link).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_hlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata, int total_data,
+ const char *fname)
+{
+ char *oldname = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_OK;
+
+ /* Set a hard link. */
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ srvstr_get_path(ctx, pdata, req->flags2, &oldname, pdata,
+ total_data, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ oldname,
+ &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_hlink: SMB_SET_FILE_UNIX_LINK doing hard link %s -> %s\n",
+ fname, oldname));
+
+ return hardlink_internals(ctx, conn, oldname, fname);
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_RENAME_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_rename_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname)
+{
+ bool overwrite;
+ uint32 root_fid;
+ uint32 len;
+ char *newname = NULL;
+ char *base_name = NULL;
+ bool dest_has_wcard = False;
+ NTSTATUS status = NT_STATUS_OK;
+ char *p;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (total_data < 13) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? True : False);
+ root_fid = IVAL(pdata,4);
+ len = IVAL(pdata,8);
+
+ if (len > (total_data - 12) || (len == 0) || (root_fid != 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ srvstr_get_path_wcard(ctx, pdata, req->flags2, &newname, &pdata[12],
+ len, 0, &status,
+ &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_rename_information: got name |%s|\n",
+ newname));
+
+ status = resolve_dfspath_wcard(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ newname,
+ &newname,
+ &dest_has_wcard);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Check the new name has no '/' characters. */
+ if (strchr_m(newname, '/')) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Create the base directory. */
+ base_name = talloc_strdup(ctx, fname);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(base_name, '/');
+ if (p) {
+ p[1] = '\0';
+ } else {
+ base_name = talloc_strdup(ctx, "./");
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ /* Append the new name. */
+ base_name = talloc_asprintf_append(base_name,
+ "%s",
+ newname);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (fsp) {
+ SMB_STRUCT_STAT sbuf;
+ char *newname_last_component = NULL;
+
+ ZERO_STRUCT(sbuf);
+
+ status = unix_convert(ctx, conn, newname, False,
+ &newname,
+ &newname_last_component,
+ &sbuf);
+
+ /* If an error we expect this to be
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND */
+
+ if (!NT_STATUS_IS_OK(status)
+ && !NT_STATUS_EQUAL(NT_STATUS_OBJECT_PATH_NOT_FOUND,
+ status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_rename_information: SMB_FILE_RENAME_INFORMATION (fnum %d) %s -> %s\n",
+ fsp->fnum, fsp->fsp_name, base_name ));
+ status = rename_internals_fsp(conn, fsp, base_name,
+ newname_last_component, 0,
+ overwrite);
+ } else {
+ DEBUG(10,("smb_file_rename_information: SMB_FILE_RENAME_INFORMATION %s -> %s\n",
+ fname, base_name ));
+ status = rename_internals(ctx, conn, req, fname, base_name, 0,
+ overwrite, False, dest_has_wcard,
+ FILE_WRITE_ATTRIBUTES);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_ACL.
+****************************************************************************/
+
+#if defined(HAVE_POSIX_ACLS)
+static NTSTATUS smb_set_posix_acl(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ uint16 posix_acl_version;
+ uint16 num_file_acls;
+ uint16 num_def_acls;
+ bool valid_file_acls = True;
+ bool valid_def_acls = True;
+
+ if (total_data < SMB_POSIX_ACL_HEADER_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ posix_acl_version = SVAL(pdata,0);
+ num_file_acls = SVAL(pdata,2);
+ num_def_acls = SVAL(pdata,4);
+
+ if (num_file_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_file_acls = False;
+ num_file_acls = 0;
+ }
+
+ if (num_def_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_def_acls = False;
+ num_def_acls = 0;
+ }
+
+ if (posix_acl_version != SMB_POSIX_ACL_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (total_data < SMB_POSIX_ACL_HEADER_SIZE +
+ (num_file_acls+num_def_acls)*SMB_POSIX_ACL_ENTRY_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("smb_set_posix_acl: file %s num_file_acls = %u, num_def_acls = %u\n",
+ fname ? fname : fsp->fsp_name,
+ (unsigned int)num_file_acls,
+ (unsigned int)num_def_acls));
+
+ if (valid_file_acls && !set_unix_posix_acl(conn, fsp, fname, num_file_acls,
+ pdata + SMB_POSIX_ACL_HEADER_SIZE)) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (valid_def_acls && !set_unix_posix_default_acl(conn, fname, psbuf, num_def_acls,
+ pdata + SMB_POSIX_ACL_HEADER_SIZE +
+ (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE))) {
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+}
+#endif
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_LOCK.
+****************************************************************************/
+
+static NTSTATUS smb_set_posix_lock(connection_struct *conn,
+ const struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ SMB_BIG_UINT count;
+ SMB_BIG_UINT offset;
+ uint32 lock_pid;
+ bool blocking_lock = False;
+ enum brl_type lock_type;
+
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (fsp == NULL || fsp->fh->fd == -1) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ /* Return the right POSIX-mappable error code for files opened read-only. */
+ if (!fsp->can_write) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ lock_type = UNLOCK_LOCK;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (SVAL(pdata,POSIX_LOCK_FLAGS_OFFSET) == POSIX_LOCK_FLAG_NOWAIT) {
+ blocking_lock = False;
+ } else if (SVAL(pdata,POSIX_LOCK_FLAGS_OFFSET) == POSIX_LOCK_FLAG_WAIT) {
+ blocking_lock = True;
+ } else {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_blocking_locks(SNUM(conn))) {
+ blocking_lock = False;
+ }
+
+ lock_pid = IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+#if defined(HAVE_LONGLONG)
+ offset = (((SMB_BIG_UINT) IVAL(pdata,(POSIX_LOCK_START_OFFSET+4))) << 32) |
+ ((SMB_BIG_UINT) IVAL(pdata,POSIX_LOCK_START_OFFSET));
+ count = (((SMB_BIG_UINT) IVAL(pdata,(POSIX_LOCK_LEN_OFFSET+4))) << 32) |
+ ((SMB_BIG_UINT) IVAL(pdata,POSIX_LOCK_LEN_OFFSET));
+#else /* HAVE_LONGLONG */
+ offset = (SMB_BIG_UINT)IVAL(pdata,POSIX_LOCK_START_OFFSET);
+ count = (SMB_BIG_UINT)IVAL(pdata,POSIX_LOCK_LEN_OFFSET);
+#endif /* HAVE_LONGLONG */
+
+ DEBUG(10,("smb_set_posix_lock: file %s, lock_type = %u,"
+ "lock_pid = %u, count = %.0f, offset = %.0f\n",
+ fsp->fsp_name,
+ (unsigned int)lock_type,
+ (unsigned int)lock_pid,
+ (double)count,
+ (double)offset ));
+
+ if (lock_type == UNLOCK_LOCK) {
+ status = do_unlock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ POSIX_LOCK);
+ } else {
+ uint32 block_smbpid;
+
+ struct byte_range_lock *br_lck = do_lock(smbd_messaging_context(),
+ fsp,
+ lock_pid,
+ count,
+ offset,
+ lock_type,
+ POSIX_LOCK,
+ blocking_lock,
+ &status,
+ &block_smbpid);
+
+ if (br_lck && blocking_lock && ERROR_WAS_LOCK_DENIED(status)) {
+ /*
+ * A blocking lock was requested. Package up
+ * this smb into a queued request and push it
+ * onto the blocking lock queue.
+ */
+ if(push_blocking_lock_request(br_lck,
+ req,
+ fsp,
+ -1, /* infinite timeout. */
+ 0,
+ lock_pid,
+ lock_type,
+ POSIX_LOCK,
+ offset,
+ count,
+ block_smbpid)) {
+ TALLOC_FREE(br_lck);
+ return status;
+ }
+ }
+ TALLOC_FREE(br_lck);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_STANDARD.
+****************************************************************************/
+
+static NTSTATUS smb_set_info_standard(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ struct timespec ts[2];
+
+ if (total_data < 12) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* access time */
+ ts[0] = convert_time_t_to_timespec(srv_make_unix_date2(pdata+l1_fdateLastAccess));
+ /* write time */
+ ts[1] = convert_time_t_to_timespec(srv_make_unix_date2(pdata+l1_fdateLastWrite));
+
+ DEBUG(10,("smb_set_info_standard: file %s\n",
+ fname ? fname : fsp->fsp_name ));
+
+ return smb_set_file_time(conn,
+ fsp,
+ fname,
+ psbuf,
+ ts,
+ true);
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_BASIC_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_basic_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ /* Patch to do this correctly from Paul Eggert <eggert@twinsun.com>. */
+ struct timespec write_time;
+ struct timespec changed_time;
+ uint32 dosmode = 0;
+ struct timespec ts[2];
+ NTSTATUS status = NT_STATUS_OK;
+ bool setting_write_time = true;
+
+ if (total_data < 36) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Set the attributes */
+ dosmode = IVAL(pdata,32);
+ status = smb_set_file_dosmode(conn,
+ fname,
+ psbuf,
+ dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ignore create time at offset pdata. */
+
+ /* access time */
+ ts[0] = interpret_long_date(pdata+8);
+
+ write_time = interpret_long_date(pdata+16);
+ changed_time = interpret_long_date(pdata+24);
+
+ /* mtime */
+ ts[1] = timespec_min(&write_time, &changed_time);
+
+ if ((timespec_compare(&write_time, &ts[1]) == 1) && !null_timespec(write_time)) {
+ ts[1] = write_time;
+ }
+
+ /* Prefer a defined time to an undefined one. */
+ if (null_timespec(ts[1])) {
+ if (null_timespec(write_time)) {
+ ts[1] = changed_time;
+ setting_write_time = false;
+ } else {
+ ts[1] = write_time;
+ }
+ }
+
+ DEBUG(10,("smb_set_file_basic_info: file %s\n",
+ fname ? fname : fsp->fsp_name ));
+
+ return smb_set_file_time(conn,
+ fsp,
+ fname,
+ psbuf,
+ ts,
+ setting_write_time);
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_ALLOCATION_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_allocation_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ SMB_BIG_UINT allocation_size = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ allocation_size = (SMB_BIG_UINT)IVAL(pdata,0);
+#ifdef LARGE_SMB_OFF_T
+ allocation_size |= (((SMB_BIG_UINT)IVAL(pdata,4)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if (IVAL(pdata,4) != 0) {
+ /* more than 32 bits? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+#endif /* LARGE_SMB_OFF_T */
+
+ DEBUG(10,("smb_set_file_allocation_info: Set file allocation info for file %s to %.0f\n",
+ fname, (double)allocation_size ));
+
+ if (allocation_size) {
+ allocation_size = smb_roundup(conn, allocation_size);
+ }
+
+ DEBUG(10,("smb_set_file_allocation_info: file %s : setting new allocation size to %.0f\n",
+ fname, (double)allocation_size ));
+
+ if (fsp && fsp->fh->fd != -1) {
+ /* Open file handle. */
+ /* Only change if needed. */
+ if (allocation_size != get_file_size(*psbuf)) {
+ if (vfs_allocate_file_space(fsp, allocation_size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ /* But always update the time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ /* Pathname or stat or directory file. */
+
+ status = open_file_ntcreate(conn, req, fname, psbuf,
+ FILE_WRITE_DATA,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ FILE_ATTRIBUTE_NORMAL,
+ FORCE_OPLOCK_BREAK_TO_NONE,
+ NULL, &new_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ /* Only change if needed. */
+ if (allocation_size != get_file_size(*psbuf)) {
+ if (vfs_allocate_file_space(new_fsp, allocation_size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file(new_fsp,NORMAL_CLOSE);
+ return status;
+ }
+ }
+
+ /* Changing the allocation size should set the last mod time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(new_fsp);
+
+ close_file(new_fsp,NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_END_OF_FILE_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_end_of_file_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ SMB_OFF_T size;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ size = IVAL(pdata,0);
+#ifdef LARGE_SMB_OFF_T
+ size |= (((SMB_OFF_T)IVAL(pdata,4)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if (IVAL(pdata,4) != 0) {
+ /* more than 32 bits? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+#endif /* LARGE_SMB_OFF_T */
+ DEBUG(10,("smb_set_file_end_of_file_info: Set end of file info for "
+ "file %s to %.0f\n", fname, (double)size ));
+
+ return smb_set_file_size(conn, req,
+ fsp,
+ fname,
+ psbuf,
+ size);
+}
+
+/****************************************************************************
+ Allow a UNIX info mknod.
+****************************************************************************/
+
+static NTSTATUS smb_unix_mknod(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ uint32 file_type = IVAL(pdata,56);
+#if defined(HAVE_MAKEDEV)
+ uint32 dev_major = IVAL(pdata,60);
+ uint32 dev_minor = IVAL(pdata,68);
+#endif
+ SMB_DEV_T dev = (SMB_DEV_T)0;
+ uint32 raw_unixmode = IVAL(pdata,84);
+ NTSTATUS status;
+ mode_t unixmode;
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = unix_perms_from_wire(conn, psbuf, raw_unixmode, PERM_NEW_FILE, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#if defined(HAVE_MAKEDEV)
+ dev = makedev(dev_major, dev_minor);
+#endif
+
+ switch (file_type) {
+#if defined(S_IFIFO)
+ case UNIX_TYPE_FIFO:
+ unixmode |= S_IFIFO;
+ break;
+#endif
+#if defined(S_IFSOCK)
+ case UNIX_TYPE_SOCKET:
+ unixmode |= S_IFSOCK;
+ break;
+#endif
+#if defined(S_IFCHR)
+ case UNIX_TYPE_CHARDEV:
+ unixmode |= S_IFCHR;
+ break;
+#endif
+#if defined(S_IFBLK)
+ case UNIX_TYPE_BLKDEV:
+ unixmode |= S_IFBLK;
+ break;
+#endif
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("smb_unix_mknod: SMB_SET_FILE_UNIX_BASIC doing mknod dev %.0f mode \
+0%o for file %s\n", (double)dev, (unsigned int)unixmode, fname ));
+
+ /* Ok - do the mknod. */
+ if (SMB_VFS_MKNOD(conn, fname, unixmode, dev) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* If any of the other "set" calls fail we
+ * don't want to end up with a half-constructed mknod.
+ */
+
+ if (lp_inherit_perms(SNUM(conn))) {
+ inherit_access_posix_acl(
+ conn, parent_dirname(fname),
+ fname, unixmode);
+ }
+
+ if (SMB_VFS_STAT(conn, fname, psbuf) != 0) {
+ status = map_nt_error_from_unix(errno);
+ SMB_VFS_UNLINK(conn,fname);
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_BASIC.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_basic(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ struct timespec ts[2];
+ uint32 raw_unixmode;
+ mode_t unixmode;
+ SMB_OFF_T size = 0;
+ uid_t set_owner = (uid_t)SMB_UID_NO_CHANGE;
+ gid_t set_grp = (uid_t)SMB_GID_NO_CHANGE;
+ NTSTATUS status = NT_STATUS_OK;
+ bool delete_on_fail = False;
+ enum perm_type ptype;
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(IVAL(pdata, 0) != SMB_SIZE_NO_CHANGE_LO &&
+ IVAL(pdata, 4) != SMB_SIZE_NO_CHANGE_HI) {
+ size=IVAL(pdata,0); /* first 8 Bytes are size */
+#ifdef LARGE_SMB_OFF_T
+ size |= (((SMB_OFF_T)IVAL(pdata,4)) << 32);
+#else /* LARGE_SMB_OFF_T */
+ if (IVAL(pdata,4) != 0) {
+ /* more than 32 bits? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+#endif /* LARGE_SMB_OFF_T */
+ }
+
+ ts[0] = interpret_long_date(pdata+24); /* access_time */
+ ts[1] = interpret_long_date(pdata+32); /* modification_time */
+ set_owner = (uid_t)IVAL(pdata,40);
+ set_grp = (gid_t)IVAL(pdata,48);
+ raw_unixmode = IVAL(pdata,84);
+
+ if (VALID_STAT(*psbuf)) {
+ if (S_ISDIR(psbuf->st_mode)) {
+ ptype = PERM_EXISTING_DIR;
+ } else {
+ ptype = PERM_EXISTING_FILE;
+ }
+ } else {
+ ptype = PERM_NEW_FILE;
+ }
+
+ status = unix_perms_from_wire(conn, psbuf, raw_unixmode, ptype, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC: name = %s \
+size = %.0f, uid = %u, gid = %u, raw perms = 0%o\n",
+ fname, (double)size, (unsigned int)set_owner, (unsigned int)set_grp, (int)raw_unixmode));
+
+ if (!VALID_STAT(*psbuf)) {
+ /*
+ * The only valid use of this is to create character and block
+ * devices, and named pipes. This is deprecated (IMHO) and
+ * a new info level should be used for mknod. JRA.
+ */
+
+ status = smb_unix_mknod(conn,
+ pdata,
+ total_data,
+ fname,
+ psbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ensure we don't try and change anything else. */
+ raw_unixmode = SMB_MODE_NO_CHANGE;
+ size = get_file_size(*psbuf);
+ ts[0] = get_atimespec(psbuf);
+ ts[1] = get_mtimespec(psbuf);
+ /*
+ * We continue here as we might want to change the
+ * owner uid/gid.
+ */
+ delete_on_fail = True;
+ }
+
+#if 1
+ /* Horrible backwards compatibility hack as an old server bug
+ * allowed a CIFS client bug to remain unnoticed :-(. JRA.
+ * */
+
+ if (!size) {
+ size = get_file_size(*psbuf);
+ }
+#endif
+
+ /*
+ * Deal with the UNIX specific mode set.
+ */
+
+ if (raw_unixmode != SMB_MODE_NO_CHANGE) {
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC setting mode 0%o for file %s\n",
+ (unsigned int)unixmode, fname ));
+ if (SMB_VFS_CHMOD(conn, fname, unixmode) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific uid set.
+ */
+
+ if ((set_owner != (uid_t)SMB_UID_NO_CHANGE) && (psbuf->st_uid != set_owner)) {
+ int ret;
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC changing owner %u for path %s\n",
+ (unsigned int)set_owner, fname ));
+
+ if (S_ISLNK(psbuf->st_mode)) {
+ ret = SMB_VFS_LCHOWN(conn, fname, set_owner, (gid_t)-1);
+ } else {
+ ret = SMB_VFS_CHOWN(conn, fname, set_owner, (gid_t)-1);
+ }
+
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ if (delete_on_fail) {
+ SMB_VFS_UNLINK(conn,fname);
+ }
+ return status;
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific gid set.
+ */
+
+ if ((set_grp != (uid_t)SMB_GID_NO_CHANGE) && (psbuf->st_gid != set_grp)) {
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC changing group %u for file %s\n",
+ (unsigned int)set_owner, fname ));
+ if (SMB_VFS_CHOWN(conn, fname, (uid_t)-1, set_grp) != 0) {
+ status = map_nt_error_from_unix(errno);
+ if (delete_on_fail) {
+ SMB_VFS_UNLINK(conn,fname);
+ }
+ return status;
+ }
+ }
+
+ /* Deal with any size changes. */
+
+ status = smb_set_file_size(conn, req,
+ fsp,
+ fname,
+ psbuf,
+ size);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Deal with any time changes. */
+
+ return smb_set_file_time(conn,
+ fsp,
+ fname,
+ psbuf,
+ ts,
+ true);
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_INFO2.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_info2(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ NTSTATUS status;
+ uint32 smb_fflags;
+ uint32 smb_fmask;
+
+ if (total_data < 116) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Start by setting all the fields that are common between UNIX_BASIC
+ * and UNIX_INFO2.
+ */
+ status = smb_set_file_unix_basic(conn, req, pdata, total_data,
+ fsp, fname, psbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ smb_fflags = IVAL(pdata, 108);
+ smb_fmask = IVAL(pdata, 112);
+
+ /* NB: We should only attempt to alter the file flags if the client
+ * sends a non-zero mask.
+ */
+ if (smb_fmask != 0) {
+ int stat_fflags = 0;
+
+ if (!map_info2_flags_to_sbuf(psbuf, smb_fflags, smb_fmask,
+ &stat_fflags)) {
+ /* Client asked to alter a flag we don't understand. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp && fsp->fh->fd != -1) {
+ /* XXX: we should be using SMB_VFS_FCHFLAGS here. */
+ return NT_STATUS_NOT_SUPPORTED;
+ } else {
+ if (SMB_VFS_CHFLAGS(conn, fname, stat_fflags) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+
+ /* XXX: need to add support for changing the create_time here. You
+ * can do this for paths on Darwin with setattrlist(2). The right way
+ * to hook this up is probably by extending the VFS utimes interface.
+ */
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Create a directory with POSIX semantics.
+****************************************************************************/
+
+static NTSTATUS smb_posix_mkdir(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ int *pdata_return_size)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uint32 raw_unixmode = 0;
+ uint32 mod_unixmode = 0;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ uint16 info_level_return = 0;
+ int info;
+ char *pdata = *ppdata;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn, psbuf, raw_unixmode, PERM_NEW_DIR, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ mod_unixmode = (uint32)unixmode | FILE_FLAG_POSIX_SEMANTICS;
+
+ DEBUG(10,("smb_posix_mkdir: file %s, mode 0%o\n",
+ fname, (unsigned int)unixmode ));
+
+ status = open_directory(conn, req,
+ fname,
+ psbuf,
+ FILE_READ_ATTRIBUTES, /* Just a stat open */
+ FILE_SHARE_NONE, /* Ignored for stat opens */
+ FILE_CREATE,
+ 0,
+ mod_unixmode,
+ &info,
+ &fsp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file(fsp, NORMAL_CLOSE);
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ SSVAL(pdata,2,0); /* No fnum. */
+ SIVAL(pdata,4,info); /* Was directory created. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp, psbuf);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp, psbuf);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* Padding. */
+ break;
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Open/Create a file with POSIX semantics.
+****************************************************************************/
+
+static NTSTATUS smb_posix_open(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf,
+ int *pdata_return_size)
+{
+ bool extended_oplock_granted = False;
+ char *pdata = *ppdata;
+ uint32 flags = 0;
+ uint32 wire_open_mode = 0;
+ uint32 raw_unixmode = 0;
+ uint32 mod_unixmode = 0;
+ uint32 create_disp = 0;
+ uint32 access_mask = 0;
+ uint32 create_options = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ int oplock_request = 0;
+ int info = 0;
+ uint16 info_level_return = 0;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = IVAL(pdata,0);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+ wire_open_mode = IVAL(pdata,4);
+
+ if (wire_open_mode == (SMB_O_CREAT|SMB_O_DIRECTORY)) {
+ return smb_posix_mkdir(conn, req,
+ ppdata,
+ total_data,
+ fname,
+ psbuf,
+ pdata_return_size);
+ }
+
+ switch (wire_open_mode & SMB_ACCMODE) {
+ case SMB_O_RDONLY:
+ access_mask = FILE_READ_DATA;
+ break;
+ case SMB_O_WRONLY:
+ access_mask = FILE_WRITE_DATA;
+ break;
+ case SMB_O_RDWR:
+ access_mask = FILE_READ_DATA|FILE_WRITE_DATA;
+ break;
+ default:
+ DEBUG(5,("smb_posix_open: invalid open mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ wire_open_mode &= ~SMB_ACCMODE;
+
+ if((wire_open_mode & (SMB_O_CREAT | SMB_O_EXCL)) == (SMB_O_CREAT | SMB_O_EXCL)) {
+ create_disp = FILE_CREATE;
+ } else if((wire_open_mode & (SMB_O_CREAT | SMB_O_TRUNC)) == (SMB_O_CREAT | SMB_O_TRUNC)) {
+ create_disp = FILE_OVERWRITE_IF;
+ } else if((wire_open_mode & SMB_O_CREAT) == SMB_O_CREAT) {
+ create_disp = FILE_OPEN_IF;
+ } else {
+ DEBUG(5,("smb_posix_open: invalid create mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn,
+ psbuf,
+ raw_unixmode,
+ VALID_STAT(*psbuf) ? PERM_EXISTING_FILE : PERM_NEW_FILE,
+ &unixmode);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ mod_unixmode = (uint32)unixmode | FILE_FLAG_POSIX_SEMANTICS;
+
+ if (wire_open_mode & SMB_O_SYNC) {
+ create_options |= FILE_WRITE_THROUGH;
+ }
+ if (wire_open_mode & SMB_O_APPEND) {
+ access_mask |= FILE_APPEND_DATA;
+ }
+ if (wire_open_mode & SMB_O_DIRECT) {
+ mod_unixmode |= FILE_FLAG_NO_BUFFERING;
+ }
+
+ DEBUG(10,("smb_posix_open: file %s, smb_posix_flags = %u, mode 0%o\n",
+ fname,
+ (unsigned int)wire_open_mode,
+ (unsigned int)unixmode ));
+
+ status = open_file_ntcreate(conn, req,
+ fname,
+ psbuf,
+ access_mask,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ create_disp,
+ 0, /* no create options yet. */
+ mod_unixmode,
+ oplock_request,
+ &info,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ extended_oplock_granted = True;
+ }
+
+ if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ extended_oplock_granted = True;
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ /* Allocate the correct return size. */
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ close_file(fsp,ERROR_CLOSE);
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ if (extended_oplock_granted) {
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ SSVAL(pdata,0, BATCH_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0, EXCLUSIVE_OPLOCK_RETURN);
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ SSVAL(pdata,0, LEVEL_II_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ }
+
+ SSVAL(pdata,2,fsp->fnum);
+ SIVAL(pdata,4,info); /* Was file created etc. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp, psbuf);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp, psbuf);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* padding. */
+ break;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Delete a file with POSIX semantics.
+****************************************************************************/
+
+static NTSTATUS smb_posix_unlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ const char *fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ uint16 flags = 0;
+ char del = 1;
+ int info = 0;
+ int i;
+ struct share_mode_lock *lck = NULL;
+
+ if (total_data < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = SVAL(pdata,0);
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) &&
+ !VALID_STAT_OF_DIR(*psbuf)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ DEBUG(10,("smb_posix_unlink: %s %s\n",
+ (flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) ? "directory" : "file",
+ fname));
+
+ if (VALID_STAT_OF_DIR(*psbuf)) {
+ status = open_directory(conn, req,
+ fname,
+ psbuf,
+ DELETE_ACCESS,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ FILE_FLAG_POSIX_SEMANTICS|0777,
+ &info,
+ &fsp);
+ } else {
+
+ status = open_file_ntcreate(conn, req,
+ fname,
+ psbuf,
+ DELETE_ACCESS,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ FILE_FLAG_POSIX_SEMANTICS|0777,
+ 0, /* No oplock, but break existing ones. */
+ &info,
+ &fsp);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Don't lie to client. If we can't really delete due to
+ * non-POSIX opens return SHARING_VIOLATION.
+ */
+
+ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL,
+ NULL);
+ if (lck == NULL) {
+ DEBUG(0, ("smb_posix_unlink: Could not get share mode "
+ "lock for file %s\n", fsp->fsp_name));
+ close_file(fsp, NORMAL_CLOSE);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * See if others still have the file open. If this is the case, then
+ * don't delete. If all opens are POSIX delete we can set the delete
+ * on close disposition.
+ */
+ for (i=0; i<lck->num_share_modes; i++) {
+ struct share_mode_entry *e = &lck->share_modes[i];
+ if (is_valid_share_mode_entry(e)) {
+ if (e->flags & SHARE_MODE_FLAG_POSIX_OPEN) {
+ continue;
+ }
+ /* Fail with sharing violation. */
+ close_file(fsp, NORMAL_CLOSE);
+ TALLOC_FREE(lck);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ /*
+ * Set the delete on close.
+ */
+ status = smb_set_file_disposition_info(conn,
+ &del,
+ 1,
+ fsp,
+ fname,
+ psbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file(fsp, NORMAL_CLOSE);
+ TALLOC_FREE(lck);
+ return status;
+ }
+ TALLOC_FREE(lck);
+ return close_file(fsp, NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_SETFILEINFO (set file info by fileid or pathname).
+****************************************************************************/
+
+static void call_trans2setfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ unsigned int tran_call,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ uint16 info_level;
+ SMB_STRUCT_STAT sbuf;
+ char *fname = NULL;
+ files_struct *fsp = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ int data_return_size = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!params) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ZERO_STRUCT(sbuf);
+
+ if (tran_call == TRANSACT2_SETFILEINFO) {
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(SVAL(params,0));
+ /* Basic check for non-null fsp. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+ info_level = SVAL(params,2);
+
+ fname = talloc_strdup(talloc_tos(),fsp->fsp_name);
+ if (!fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if(fsp->is_directory || fsp->fh->fd == -1) {
+ /*
+ * This is actually a SETFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ /* Always do lstat for UNIX calls. */
+ if (SMB_VFS_LSTAT(conn,fname,&sbuf)) {
+ DEBUG(3,("call_trans2setfilepathinfo: SMB_VFS_LSTAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req,ERRDOS,ERRbadpath);
+ return;
+ }
+ } else {
+ if (SMB_VFS_STAT(conn,fname,&sbuf) != 0) {
+ DEBUG(3,("call_trans2setfilepathinfo: fileinfo of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req,ERRDOS,ERRbadpath);
+ return;
+ }
+ }
+ } else if (fsp->print_file) {
+ /*
+ * Doing a DELETE_ON_CLOSE should cancel a print job.
+ */
+ if ((info_level == SMB_SET_FILE_DISPOSITION_INFO) && CVAL(pdata,0)) {
+ fsp->fh->private_options |= FILE_DELETE_ON_CLOSE;
+
+ DEBUG(3,("call_trans2setfilepathinfo: Cancelling print job (%s)\n", fsp->fsp_name ));
+
+ SSVAL(params,0,0);
+ send_trans2_replies(conn, req, params, 2,
+ *ppdata, 0,
+ max_data_bytes);
+ return;
+ } else {
+ reply_unixerror(req, ERRDOS, ERRbadpath);
+ return;
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ if (SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ DEBUG(3,("call_trans2setfilepathinfo: fstat of fnum %d failed (%s)\n",fsp->fnum, strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+ }
+ } else {
+ /* set path info */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+ srvstr_get_path(ctx, params, req->flags2, &fname, &params[6],
+ total_params - 6, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = resolve_dfspath(ctx, conn,
+ req->flags2 & FLAGS2_DFS_PATHNAMES,
+ fname,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = unix_convert(ctx, conn, fname, False,
+ &fname, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = check_name(conn, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ /*
+ * For CIFS UNIX extensions the target name may not exist.
+ */
+
+ /* Always do lstat for UNIX calls. */
+ SMB_VFS_LSTAT(conn,fname,&sbuf);
+
+ } else if (!VALID_STAT(sbuf) && SMB_VFS_STAT(conn,fname,&sbuf)) {
+ DEBUG(3,("call_trans2setfilepathinfo: SMB_VFS_STAT of %s failed (%s)\n",fname,strerror(errno)));
+ reply_unixerror(req, ERRDOS, ERRbadpath);
+ return;
+ }
+ }
+
+ if (!CAN_WRITE(conn)) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+
+ if (INFO_LEVEL_IS_UNIX(info_level) && !lp_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ DEBUG(3,("call_trans2setfilepathinfo(%d) %s (fnum %d) info_level=%d totdata=%d\n",
+ tran_call,fname, fsp ? fsp->fnum : -1, info_level,total_data));
+
+ /* Realloc the parameter size */
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0);
+
+ switch (info_level) {
+
+ case SMB_INFO_STANDARD:
+ {
+ status = smb_set_info_standard(conn,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_INFO_SET_EA:
+ {
+ status = smb_info_set_ea(conn,
+ pdata,
+ total_data,
+ fsp,
+ fname);
+ break;
+ }
+
+ case SMB_SET_FILE_BASIC_INFO:
+ case SMB_FILE_BASIC_INFORMATION:
+ {
+ status = smb_set_file_basic_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_SET_FILE_ALLOCATION_INFO:
+ {
+ status = smb_set_file_allocation_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_SET_FILE_END_OF_FILE_INFO:
+ {
+ status = smb_set_file_end_of_file_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ case SMB_SET_FILE_DISPOSITION_INFO: /* Set delete on close for open file. */
+ {
+#if 0
+ /* JRA - We used to just ignore this on a path ?
+ * Shouldn't this be invalid level on a pathname
+ * based call ?
+ */
+ if (tran_call != TRANSACT2_SETFILEINFO) {
+ return ERROR_NT(NT_STATUS_INVALID_LEVEL);
+ }
+#endif
+ status = smb_set_file_disposition_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_FILE_POSITION_INFORMATION:
+ {
+ status = smb_file_position_information(conn,
+ pdata,
+ total_data,
+ fsp);
+ break;
+ }
+
+ /* From tridge Samba4 :
+ * MODE_INFORMATION in setfileinfo (I have no
+ * idea what "mode information" on a file is - it takes a value of 0,
+ * 2, 4 or 6. What could it be?).
+ */
+
+ case SMB_FILE_MODE_INFORMATION:
+ {
+ status = smb_file_mode_information(conn,
+ pdata,
+ total_data);
+ break;
+ }
+
+ /*
+ * CIFS UNIX extensions.
+ */
+
+ case SMB_SET_FILE_UNIX_BASIC:
+ {
+ status = smb_set_file_unix_basic(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_SET_FILE_UNIX_INFO2:
+ {
+ status = smb_set_file_unix_info2(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ case SMB_SET_FILE_UNIX_LINK:
+ {
+ if (tran_call != TRANSACT2_SETPATHINFO) {
+ /* We must have a pathname for this. */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ status = smb_set_file_unix_link(conn, req, pdata,
+ total_data, fname);
+ break;
+ }
+
+ case SMB_SET_FILE_UNIX_HLINK:
+ {
+ if (tran_call != TRANSACT2_SETPATHINFO) {
+ /* We must have a pathname for this. */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ status = smb_set_file_unix_hlink(conn, req,
+ pdata, total_data,
+ fname);
+ break;
+ }
+
+ case SMB_FILE_RENAME_INFORMATION:
+ {
+ status = smb_file_rename_information(conn, req,
+ pdata, total_data,
+ fsp, fname);
+ break;
+ }
+
+#if defined(HAVE_POSIX_ACLS)
+ case SMB_SET_POSIX_ACL:
+ {
+ status = smb_set_posix_acl(conn,
+ pdata,
+ total_data,
+ fsp,
+ fname,
+ &sbuf);
+ break;
+ }
+#endif
+
+ case SMB_SET_POSIX_LOCK:
+ {
+ if (tran_call != TRANSACT2_SETFILEINFO) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ status = smb_set_posix_lock(conn, req,
+ pdata, total_data, fsp);
+ break;
+ }
+
+ case SMB_POSIX_PATH_OPEN:
+ {
+ if (tran_call != TRANSACT2_SETPATHINFO) {
+ /* We must have a pathname for this. */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ status = smb_posix_open(conn, req,
+ ppdata,
+ total_data,
+ fname,
+ &sbuf,
+ &data_return_size);
+ break;
+ }
+
+ case SMB_POSIX_PATH_UNLINK:
+ {
+ if (tran_call != TRANSACT2_SETPATHINFO) {
+ /* We must have a pathname for this. */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ status = smb_posix_unlink(conn, req,
+ pdata,
+ total_data,
+ fname,
+ &sbuf);
+ break;
+ }
+
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ if (blocking_lock_was_deferred(req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ if (info_level == SMB_POSIX_PATH_OPEN) {
+ reply_openerror(req, status);
+ return;
+ }
+
+ reply_nterror(req, status);
+ return;
+ }
+
+ SSVAL(params,0,0);
+ send_trans2_replies(conn, req, params, 2, *ppdata, data_return_size,
+ max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_MKDIR (make directory with extended attributes).
+****************************************************************************/
+
+static void call_trans2mkdir(connection_struct *conn, struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *directory = NULL;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status = NT_STATUS_OK;
+ struct ea_list *ea_list = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!CAN_WRITE(conn)) {
+ reply_doserror(req, ERRSRV, ERRaccess);
+ return;
+ }
+
+ if (total_params < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ srvstr_get_path(ctx, params, req->flags2, &directory, &params[4],
+ total_params - 4, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DEBUG(3,("call_trans2mkdir : name = %s\n", directory));
+
+ status = unix_convert(ctx, conn, directory, False, &directory, NULL, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ status = check_name(conn, directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("call_trans2mkdir error (%s)\n", nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+
+ /* Any data in this call is an EA list. */
+ if (total_data && (total_data != 4) && !lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ /*
+ * OS/2 workplace shell seems to send SET_EA requests of "null"
+ * length (4 bytes containing IVAL 4).
+ * They seem to have no effect. Bug #3212. JRA.
+ */
+
+ if (total_data != 4) {
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2mkdir: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ } else if (IVAL(pdata,0) != 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ status = create_directory(conn, req, directory);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ /* Try and set any given EA. */
+ if (ea_list) {
+ status = set_ea(conn, NULL, directory, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if(*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0);
+
+ send_trans2_replies(conn, req, params, 2, *ppdata, 0, max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYFIRST (start monitoring a directory for changes).
+ We don't actually do this - we just send a null response.
+****************************************************************************/
+
+static void call_trans2findnotifyfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ static uint16 fnf_handle = 257;
+ char *params = *pparams;
+ uint16 info_level;
+
+ if (total_params < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,4);
+ DEBUG(3,("call_trans2findnotifyfirst - info_level %d\n", info_level));
+
+ switch (info_level) {
+ case 1:
+ case 2:
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,6);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fnf_handle);
+ SSVAL(params,2,0); /* No changes */
+ SSVAL(params,4,0); /* No EA errors */
+
+ fnf_handle++;
+
+ if(fnf_handle == 0)
+ fnf_handle = 257;
+
+ send_trans2_replies(conn, req, params, 6, *ppdata, 0, max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYNEXT (continue monitoring a directory for
+ changes). Currently this does nothing.
+****************************************************************************/
+
+static void call_trans2findnotifynext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+
+ DEBUG(3,("call_trans2findnotifynext\n"));
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,4);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0); /* No changes */
+ SSVAL(params,2,0); /* No EA errors */
+
+ send_trans2_replies(conn, req, params, 4, *ppdata, 0, max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_GET_DFS_REFERRAL - Shirish Kalele <kalele@veritas.com>.
+****************************************************************************/
+
+static void call_trans2getdfsreferral(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pathname = NULL;
+ int reply_size = 0;
+ int max_referral_level;
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(10,("call_trans2getdfsreferral\n"));
+
+ if (total_params < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ max_referral_level = SVAL(params,0);
+
+ if(!lp_host_msdfs()) {
+ reply_doserror(req, ERRDOS, ERRbadfunc);
+ return;
+ }
+
+ srvstr_pull_talloc(ctx, params, req->flags2, &pathname, &params[2],
+ total_params - 2, STR_TERMINATE);
+ if (!pathname) {
+ reply_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+ if((reply_size = setup_dfs_referral(conn, pathname, max_referral_level,
+ ppdata,&status)) < 0) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SSVAL(req->inbuf, smb_flg2,
+ SVAL(req->inbuf,smb_flg2) | FLAGS2_DFS_PATHNAMES);
+ send_trans2_replies(conn, req,0,0,*ppdata,reply_size, max_data_bytes);
+
+ return;
+}
+
+#define LMCAT_SPL 0x53
+#define LMFUNC_GETJOBID 0x60
+
+/****************************************************************************
+ Reply to a TRANS2_IOCTL - used for OS/2 printing.
+****************************************************************************/
+
+static void call_trans2ioctl(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *pdata = *ppdata;
+ files_struct *fsp = file_fsp(SVAL(req->inbuf,smb_vwv15));
+
+ /* check for an invalid fid before proceeding */
+
+ if (!fsp) {
+ reply_doserror(req, ERRDOS, ERRbadfid);
+ return;
+ }
+
+ if ((SVAL(req->inbuf,(smb_setup+4)) == LMCAT_SPL)
+ && (SVAL(req->inbuf,(smb_setup+6)) == LMFUNC_GETJOBID)) {
+ *ppdata = (char *)SMB_REALLOC(*ppdata, 32);
+ if (*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ /* NOTE - THIS IS ASCII ONLY AT THE MOMENT - NOT SURE IF OS/2
+ CAN ACCEPT THIS IN UNICODE. JRA. */
+
+ SSVAL(pdata,0,fsp->rap_print_jobid); /* Job number */
+ srvstr_push(pdata, req->flags2, pdata + 2,
+ global_myname(), 15,
+ STR_ASCII|STR_TERMINATE); /* Our NetBIOS name */
+ srvstr_push(pdata, req->flags2, pdata+18,
+ lp_servicename(SNUM(conn)), 13,
+ STR_ASCII|STR_TERMINATE); /* Service name */
+ send_trans2_replies(conn, req, *pparams, 0, *ppdata, 32,
+ max_data_bytes);
+ return;
+ }
+
+ DEBUG(2,("Unknown TRANS2_IOCTL\n"));
+ reply_doserror(req, ERRSRV, ERRerror);
+}
+
+/****************************************************************************
+ Reply to a SMBfindclose (stop trans2 directory search).
+****************************************************************************/
+
+void reply_findclose(struct smb_request *req)
+{
+ int dptr_num;
+
+ START_PROFILE(SMBfindclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindclose);
+ return;
+ }
+
+ dptr_num = SVALS(req->inbuf,smb_vwv0);
+
+ DEBUG(3,("reply_findclose, dptr_num = %d\n", dptr_num));
+
+ dptr_close(&dptr_num);
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMBfindclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBfindnclose (stop FINDNOTIFYFIRST directory search).
+****************************************************************************/
+
+void reply_findnclose(struct smb_request *req)
+{
+ int dptr_num;
+
+ START_PROFILE(SMBfindnclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindnclose);
+ return;
+ }
+
+ dptr_num = SVAL(req->inbuf,smb_vwv0);
+
+ DEBUG(3,("reply_findnclose, dptr_num = %d\n", dptr_num));
+
+ /* We never give out valid handles for a
+ findnotifyfirst - so any dptr_num is ok here.
+ Just ignore it. */
+
+ reply_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMB_findnclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindnclose);
+ return;
+}
+
+static void handle_trans2(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ if (Protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL(req->inbuf,smb_flg2,req->flags2);
+ }
+
+ if (conn->encrypt_level == Required && !req->encrypted) {
+ if (state->call != TRANSACT2_QFSINFO &&
+ state->call != TRANSACT2_SETFSINFO) {
+ DEBUG(0,("handle_trans2: encryption required "
+ "with call 0x%x\n",
+ (unsigned int)state->call));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ /* Now we must call the relevant TRANS2 function */
+ switch(state->call) {
+ case TRANSACT2_OPEN:
+ {
+ START_PROFILE(Trans2_open);
+ call_trans2open(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_open);
+ break;
+ }
+
+ case TRANSACT2_FINDFIRST:
+ {
+ START_PROFILE(Trans2_findfirst);
+ call_trans2findfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNEXT:
+ {
+ START_PROFILE(Trans2_findnext);
+ call_trans2findnext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnext);
+ break;
+ }
+
+ case TRANSACT2_QFSINFO:
+ {
+ START_PROFILE(Trans2_qfsinfo);
+ call_trans2qfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qfsinfo);
+ break;
+ }
+
+ case TRANSACT2_SETFSINFO:
+ {
+ START_PROFILE(Trans2_setfsinfo);
+ call_trans2setfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setfsinfo);
+ break;
+ }
+
+ case TRANSACT2_QPATHINFO:
+ case TRANSACT2_QFILEINFO:
+ {
+ START_PROFILE(Trans2_qpathinfo);
+ call_trans2qfilepathinfo(conn, req, state->call,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qpathinfo);
+ break;
+ }
+
+ case TRANSACT2_SETPATHINFO:
+ case TRANSACT2_SETFILEINFO:
+ {
+ START_PROFILE(Trans2_setpathinfo);
+ call_trans2setfilepathinfo(conn, req, state->call,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setpathinfo);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYFIRST:
+ {
+ START_PROFILE(Trans2_findnotifyfirst);
+ call_trans2findnotifyfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifyfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYNEXT:
+ {
+ START_PROFILE(Trans2_findnotifynext);
+ call_trans2findnotifynext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifynext);
+ break;
+ }
+
+ case TRANSACT2_MKDIR:
+ {
+ START_PROFILE(Trans2_mkdir);
+ call_trans2mkdir(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_mkdir);
+ break;
+ }
+
+ case TRANSACT2_GET_DFS_REFERRAL:
+ {
+ START_PROFILE(Trans2_get_dfs_referral);
+ call_trans2getdfsreferral(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_get_dfs_referral);
+ break;
+ }
+
+ case TRANSACT2_IOCTL:
+ {
+ START_PROFILE(Trans2_ioctl);
+ call_trans2ioctl(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_ioctl);
+ break;
+ }
+
+ default:
+ /* Error in request */
+ DEBUG(2,("Unknown request %d in trans2 call\n", state->call));
+ reply_doserror(req, ERRSRV,ERRerror);
+ }
+}
+
+/****************************************************************************
+ Reply to a SMBtrans2.
+ ****************************************************************************/
+
+void reply_trans2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ unsigned int tran_call;
+ unsigned int size;
+ unsigned int av_size;
+ struct trans_state *state;
+ NTSTATUS result;
+
+ START_PROFILE(SMBtrans2);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ dsoff = SVAL(req->inbuf, smb_dsoff);
+ dscnt = SVAL(req->inbuf, smb_dscnt);
+ psoff = SVAL(req->inbuf, smb_psoff);
+ pscnt = SVAL(req->inbuf, smb_pscnt);
+ tran_call = SVAL(req->inbuf, smb_setup0);
+ size = smb_len(req->inbuf) + 4;
+ av_size = smb_len(req->inbuf);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans2 request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ if (IS_IPC(conn)) {
+ switch (tran_call) {
+ /* List the allowed trans2 calls on IPC$ */
+ case TRANSACT2_OPEN:
+ case TRANSACT2_GET_DFS_REFERRAL:
+ case TRANSACT2_QFILEINFO:
+ case TRANSACT2_QFSINFO:
+ case TRANSACT2_SETFSINFO:
+ break;
+ default:
+ reply_doserror(req, ERRSRV, ERRaccess);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((state = TALLOC_P(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ state->cmd = SMBtrans2;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = SVAL(req->inbuf, smb_suwcnt);
+ state->setup = NULL;
+ state->total_param = SVAL(req->inbuf, smb_tpscnt);
+ state->param = NULL;
+ state->total_data = SVAL(req->inbuf, smb_tdscnt);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->inbuf, smb_mprcnt);
+ state->max_data_return = SVAL(req->inbuf, smb_mdrcnt);
+ state->max_setup_return = SVAL(req->inbuf, smb_msrcnt);
+ state->close_on_completion = BITSETW(req->inbuf+smb_vwv5,0);
+ state->one_way = BITSETW(req->inbuf+smb_vwv5,1);
+
+ state->call = tran_call;
+
+ /* All trans2 messages we handle have smb_sucnt == 1 - ensure this
+ is so as a sanity check */
+ if (state->setup_count != 1) {
+ /*
+ * Need to have rc=0 for ioctl to get job id for OS/2.
+ * Network printing will fail if function is not successful.
+ * Similar function in reply.c will be used if protocol
+ * is LANMAN1.0 instead of LM1.2X002.
+ * Until DosPrintSetJobInfo with PRJINFO3 is supported,
+ * outbuf doesn't have to be set(only job id is used).
+ */
+ if ( (state->setup_count == 4)
+ && (tran_call == TRANSACT2_IOCTL)
+ && (SVAL(req->inbuf,(smb_setup+4)) == LMCAT_SPL)
+ && (SVAL(req->inbuf,(smb_setup+6)) == LMFUNC_GETJOBID)) {
+ DEBUG(2,("Got Trans2 DevIOctl jobid\n"));
+ } else {
+ DEBUG(2,("Invalid smb_sucnt in trans2 call(%u)\n",state->setup_count));
+ DEBUG(2,("Transaction is %d\n",tran_call));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->data = (char *)SMB_MALLOC(state->total_data);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans2: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ if (dscnt > state->total_data ||
+ dsoff+dscnt < dsoff) {
+ goto bad_param;
+ }
+
+ if (dsoff > av_size ||
+ dscnt > av_size ||
+ dsoff+dscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->param = (char *)SMB_MALLOC(state->total_param);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ if (pscnt > state->total_param ||
+ psoff+pscnt < psoff) {
+ goto bad_param;
+ }
+
+ if (psoff > av_size ||
+ pscnt > av_size ||
+ psoff+pscnt > av_size) {
+ goto bad_param;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if ((state->received_param == state->total_param) &&
+ (state->received_data == state->total_data)) {
+
+ handle_trans2(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans2: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+}
+
+
+/****************************************************************************
+ Reply to a SMBtranss2
+ ****************************************************************************/
+
+void reply_transs2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+ unsigned int size;
+ unsigned int av_size;
+
+ START_PROFILE(SMBtranss2);
+
+ show_msg((char *)req->inbuf);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ size = smb_len(req->inbuf)+4;
+ av_size = smb_len(req->inbuf);
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans2)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+
+ if (SVAL(req->inbuf, smb_tpscnt) < state->total_param)
+ state->total_param = SVAL(req->inbuf, smb_tpscnt);
+ if (SVAL(req->inbuf, smb_tdscnt) < state->total_data)
+ state->total_data = SVAL(req->inbuf, smb_tdscnt);
+
+ pcnt = SVAL(req->inbuf, smb_spscnt);
+ poff = SVAL(req->inbuf, smb_spsoff);
+ pdisp = SVAL(req->inbuf, smb_spsdisp);
+
+ dcnt = SVAL(req->inbuf, smb_sdscnt);
+ doff = SVAL(req->inbuf, smb_sdsoff);
+ ddisp = SVAL(req->inbuf, smb_sdsdisp);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (pdisp > state->total_param ||
+ pcnt > state->total_param ||
+ pdisp+pcnt > state->total_param ||
+ pdisp+pcnt < pdisp) {
+ goto bad_param;
+ }
+
+ if (poff > av_size ||
+ pcnt > av_size ||
+ poff+pcnt > av_size ||
+ poff+pcnt < poff) {
+ goto bad_param;
+ }
+
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,
+ pcnt);
+ }
+
+ if (dcnt) {
+ if (ddisp > state->total_data ||
+ dcnt > state->total_data ||
+ ddisp+dcnt > state->total_data ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ if (ddisp > av_size ||
+ dcnt > av_size ||
+ ddisp+dcnt > av_size ||
+ ddisp+dcnt < ddisp) {
+ goto bad_param;
+ }
+
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,
+ dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ /*
+ * construct_reply_common will copy smb_com from inbuf to
+ * outbuf. SMBtranss2 is wrong here.
+ */
+ SCVAL(req->inbuf,smb_com,SMBtrans2);
+
+ handle_trans2(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs2: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+}
diff --git a/source3/smbd/uid.c b/source3/smbd/uid.c
new file mode 100644
index 0000000000..8998f6a371
--- /dev/null
+++ b/source3/smbd/uid.c
@@ -0,0 +1,449 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ 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"
+
+/* what user is current? */
+extern struct current_user current_user;
+
+/****************************************************************************
+ Become the guest user without changing the security context stack.
+****************************************************************************/
+
+bool change_to_guest(void)
+{
+ static struct passwd *pass=NULL;
+
+ if (!pass) {
+ /* Don't need to free() this as its stored in a static */
+ pass = getpwnam_alloc(NULL, lp_guestaccount());
+ if (!pass)
+ return(False);
+ }
+
+#ifdef AIX
+ /* MWW: From AIX FAQ patch to WU-ftpd: call initgroups before
+ setting IDs */
+ initgroups(pass->pw_name, pass->pw_gid);
+#endif
+
+ set_sec_ctx(pass->pw_uid, pass->pw_gid, 0, NULL, NULL);
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ TALLOC_FREE(pass);
+ pass = NULL;
+
+ return True;
+}
+
+/*******************************************************************
+ Check if a username is OK.
+
+ This sets up conn->server_info with a copy related to this vuser that
+ later code can then mess with.
+********************************************************************/
+
+static bool check_user_ok(connection_struct *conn, uint16_t vuid,
+ struct auth_serversupplied_info *server_info,
+ int snum)
+{
+ unsigned int i;
+ struct vuid_cache_entry *ent = NULL;
+ bool readonly_share;
+ bool admin_user;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ ent = &conn->vuid_cache.array[i];
+ if (ent->vuid == vuid) {
+ conn->server_info = ent->server_info;
+ conn->read_only = ent->read_only;
+ conn->admin_user = ent->admin_user;
+ return(True);
+ }
+ }
+
+ if (!user_ok_token(server_info->unix_name,
+ pdb_get_domain(server_info->sam_account),
+ server_info->ptok, snum))
+ return(False);
+
+ readonly_share = is_share_read_only_for_token(
+ server_info->unix_name,
+ pdb_get_domain(server_info->sam_account),
+ server_info->ptok, snum);
+
+ if (!readonly_share &&
+ !share_access_check(server_info->ptok, lp_servicename(snum),
+ FILE_WRITE_DATA)) {
+ /* smb.conf allows r/w, but the security descriptor denies
+ * write. Fall back to looking at readonly. */
+ readonly_share = True;
+ DEBUG(5,("falling back to read-only access-evaluation due to "
+ "security descriptor\n"));
+ }
+
+ if (!share_access_check(server_info->ptok, lp_servicename(snum),
+ readonly_share ?
+ FILE_READ_DATA : FILE_WRITE_DATA)) {
+ return False;
+ }
+
+ admin_user = token_contains_name_in_list(
+ server_info->unix_name,
+ pdb_get_domain(server_info->sam_account),
+ NULL, server_info->ptok, lp_admin_users(snum));
+
+ ent = &conn->vuid_cache.array[conn->vuid_cache.next_entry];
+
+ conn->vuid_cache.next_entry =
+ (conn->vuid_cache.next_entry + 1) % VUID_CACHE_SIZE;
+
+ TALLOC_FREE(ent->server_info);
+
+ /*
+ * If force_user was set, all server_info's are based on the same
+ * username-based faked one.
+ */
+
+ ent->server_info = copy_serverinfo(
+ conn, conn->force_user ? conn->server_info : server_info);
+
+ if (ent->server_info == NULL) {
+ ent->vuid = UID_FIELD_INVALID;
+ return false;
+ }
+
+ ent->vuid = vuid;
+ ent->read_only = readonly_share;
+ ent->admin_user = admin_user;
+
+ conn->read_only = ent->read_only;
+ conn->admin_user = ent->admin_user;
+ conn->server_info = ent->server_info;
+
+ return(True);
+}
+
+/****************************************************************************
+ Clear a vuid out of the connection's vuid cache
+****************************************************************************/
+
+void conn_clear_vuid_cache(connection_struct *conn, uint16_t vuid)
+{
+ int i;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+
+ ent = &conn->vuid_cache.array[i];
+
+ if (ent->vuid == vuid) {
+ ent->vuid = UID_FIELD_INVALID;
+ TALLOC_FREE(ent->server_info);
+ ent->read_only = False;
+ ent->admin_user = False;
+ }
+ }
+}
+
+/****************************************************************************
+ Become the user of a connection number without changing the security context
+ stack, but modify the current_user entries.
+****************************************************************************/
+
+bool change_to_user(connection_struct *conn, uint16 vuid)
+{
+ user_struct *vuser = get_valid_user_struct(vuid);
+ int snum;
+ gid_t gid;
+ uid_t uid;
+ char group_c;
+ int num_groups = 0;
+ gid_t *group_list = NULL;
+
+ if (!conn) {
+ DEBUG(2,("change_to_user: Connection not open\n"));
+ return(False);
+ }
+
+ /*
+ * We need a separate check in security=share mode due to vuid
+ * always being UID_FIELD_INVALID. If we don't do this then
+ * in share mode security we are *always* changing uid's between
+ * SMB's - this hurts performance - Badly.
+ */
+
+ if((lp_security() == SEC_SHARE) && (current_user.conn == conn) &&
+ (current_user.ut.uid == conn->server_info->utok.uid)) {
+ DEBUG(4,("change_to_user: Skipping user change - already "
+ "user\n"));
+ return(True);
+ } else if ((current_user.conn == conn) &&
+ (vuser != NULL) && (current_user.vuid == vuid) &&
+ (current_user.ut.uid == vuser->server_info->utok.uid)) {
+ DEBUG(4,("change_to_user: Skipping user change - already "
+ "user\n"));
+ return(True);
+ }
+
+ snum = SNUM(conn);
+
+ if ((vuser) && !check_user_ok(conn, vuid, vuser->server_info, snum)) {
+ DEBUG(2,("change_to_user: SMB user %s (unix user %s, vuid %d) "
+ "not permitted access to share %s.\n",
+ vuser->server_info->sanitized_username,
+ vuser->server_info->unix_name, vuid,
+ lp_servicename(snum)));
+ return False;
+ }
+
+ /*
+ * conn->server_info is now correctly set up with a copy we can mess
+ * with for force_group etc.
+ */
+
+ if (conn->force_user) /* security = share sets this too */ {
+ uid = conn->server_info->utok.uid;
+ gid = conn->server_info->utok.gid;
+ group_list = conn->server_info->utok.groups;
+ num_groups = conn->server_info->utok.ngroups;
+ } else if (vuser) {
+ uid = conn->admin_user ? 0 : vuser->server_info->utok.uid;
+ gid = conn->server_info->utok.gid;
+ num_groups = conn->server_info->utok.ngroups;
+ group_list = conn->server_info->utok.groups;
+ } else {
+ DEBUG(2,("change_to_user: Invalid vuid used %d in accessing "
+ "share %s.\n",vuid, lp_servicename(snum) ));
+ return False;
+ }
+
+ /*
+ * See if we should force group for this service.
+ * If so this overrides any group set in the force
+ * user code.
+ */
+
+ if((group_c = *lp_force_group(snum))) {
+
+ if(group_c == '+') {
+
+ /*
+ * Only force group if the user is a member of
+ * the service group. Check the group memberships for
+ * this user (we already have this) to
+ * see if we should force the group.
+ */
+
+ int i;
+ for (i = 0; i < num_groups; i++) {
+ if (group_list[i]
+ == conn->server_info->utok.gid) {
+ gid = conn->server_info->utok.gid;
+ gid_to_sid(&conn->server_info->ptok
+ ->user_sids[1], gid);
+ break;
+ }
+ }
+ } else {
+ gid = conn->server_info->utok.gid;
+ gid_to_sid(&conn->server_info->ptok->user_sids[1],
+ gid);
+ }
+ }
+
+ /* Now set current_user since we will immediately also call
+ set_sec_ctx() */
+
+ current_user.ut.ngroups = num_groups;
+ current_user.ut.groups = group_list;
+
+ set_sec_ctx(uid, gid, current_user.ut.ngroups, current_user.ut.groups,
+ conn->server_info->ptok);
+
+ current_user.conn = conn;
+ current_user.vuid = vuid;
+
+ DEBUG(5,("change_to_user uid=(%d,%d) gid=(%d,%d)\n",
+ (int)getuid(),(int)geteuid(),(int)getgid(),(int)getegid()));
+
+ return(True);
+}
+
+/****************************************************************************
+ Go back to being root without changing the security context stack,
+ but modify the current_user entries.
+****************************************************************************/
+
+bool change_to_root_user(void)
+{
+ set_root_sec_ctx();
+
+ DEBUG(5,("change_to_root_user: now uid=(%d,%d) gid=(%d,%d)\n",
+ (int)getuid(),(int)geteuid(),(int)getgid(),(int)getegid()));
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ return(True);
+}
+
+/****************************************************************************
+ Become the user of an authenticated connected named pipe.
+ When this is called we are currently running as the connection
+ user. Doesn't modify current_user.
+****************************************************************************/
+
+bool become_authenticated_pipe_user(pipes_struct *p)
+{
+ if (!push_sec_ctx())
+ return False;
+
+ set_sec_ctx(p->pipe_user.ut.uid, p->pipe_user.ut.gid,
+ p->pipe_user.ut.ngroups, p->pipe_user.ut.groups,
+ p->pipe_user.nt_user_token);
+
+ return True;
+}
+
+/****************************************************************************
+ Unbecome the user of an authenticated connected named pipe.
+ When this is called we are running as the authenticated pipe
+ user and need to go back to being the connection user. Doesn't modify
+ current_user.
+****************************************************************************/
+
+bool unbecome_authenticated_pipe_user(void)
+{
+ return pop_sec_ctx();
+}
+
+/****************************************************************************
+ Utility functions used by become_xxx/unbecome_xxx.
+****************************************************************************/
+
+struct conn_ctx {
+ connection_struct *conn;
+ uint16 vuid;
+};
+
+/* A stack of current_user connection contexts. */
+
+static struct conn_ctx conn_ctx_stack[MAX_SEC_CTX_DEPTH];
+static int conn_ctx_stack_ndx;
+
+static void push_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+
+ /* Check we don't overflow our stack */
+
+ if (conn_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Connection context stack overflow!\n"));
+ smb_panic("Connection context stack overflow!\n");
+ }
+
+ /* Store previous user context */
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ ctx_p->conn = current_user.conn;
+ ctx_p->vuid = current_user.vuid;
+
+ DEBUG(3, ("push_conn_ctx(%u) : conn_ctx_stack_ndx = %d\n",
+ (unsigned int)ctx_p->vuid, conn_ctx_stack_ndx ));
+
+ conn_ctx_stack_ndx++;
+}
+
+static void pop_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+
+ /* Check for stack underflow. */
+
+ if (conn_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Connection context stack underflow!\n"));
+ smb_panic("Connection context stack underflow!\n");
+ }
+
+ conn_ctx_stack_ndx--;
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ current_user.conn = ctx_p->conn;
+ current_user.vuid = ctx_p->vuid;
+
+ ctx_p->conn = NULL;
+ ctx_p->vuid = UID_FIELD_INVALID;
+}
+
+/****************************************************************************
+ Temporarily become a root user. Must match with unbecome_root(). Saves and
+ restores the connection context.
+****************************************************************************/
+
+void become_root(void)
+{
+ /*
+ * no good way to handle push_sec_ctx() failing without changing
+ * the prototype of become_root()
+ */
+ if (!push_sec_ctx()) {
+ smb_panic("become_root: push_sec_ctx failed");
+ }
+ push_conn_ctx();
+ set_root_sec_ctx();
+}
+
+/* Unbecome the root user */
+
+void unbecome_root(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+}
+
+/****************************************************************************
+ Push the current security context then force a change via change_to_user().
+ Saves and restores the connection context.
+****************************************************************************/
+
+bool become_user(connection_struct *conn, uint16 vuid)
+{
+ if (!push_sec_ctx())
+ return False;
+
+ push_conn_ctx();
+
+ if (!change_to_user(conn, vuid)) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return False;
+ }
+
+ return True;
+}
+
+bool unbecome_user(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return True;
+}
diff --git a/source3/smbd/utmp.c b/source3/smbd/utmp.c
new file mode 100644
index 0000000000..af947ef462
--- /dev/null
+++ b/source3/smbd/utmp.c
@@ -0,0 +1,630 @@
+/*
+ Unix SMB/CIFS implementation.
+ utmp routines
+ Copyright (C) T.D.Lee@durham.ac.uk 1999
+ Heavily modified by Andrew Bartlett and Tridge, April 2001
+
+ 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"
+
+/****************************************************************************
+Reflect connection status in utmp/wtmp files.
+ T.D.Lee@durham.ac.uk September 1999
+
+ With grateful thanks since then to many who have helped port it to
+ different operating systems. The variety of OS quirks thereby
+ uncovered is amazing...
+
+Hints for porting:
+ o Always attempt to use programmatic interface (pututline() etc.)
+ Indeed, at present only programmatic use is supported.
+ o The only currently supported programmatic interface to "wtmp{,x}"
+ is through "updwtmp*()" routines.
+ o The "x" (utmpx/wtmpx; HAVE_UTMPX_H) seems preferable.
+ o The HAVE_* items should identify supported features.
+ o If at all possible, avoid "if defined(MY-OS)" constructions.
+
+OS observations and status:
+ Almost every OS seems to have its own quirks.
+
+ Solaris 2.x:
+ Tested on 2.6 and 2.7; should be OK on other flavours.
+ AIX:
+ Apparently has utmpx.h but doesn't implement.
+ OSF:
+ Has utmpx.h, but (e.g.) no "getutmpx()". (Is this like AIX ?)
+ Redhat 6:
+ utmpx.h seems not to set default filenames. non-x better.
+ IRIX 6.5:
+ Not tested. Appears to have "x".
+ HP-UX 9.x:
+ Not tested. Appears to lack "x".
+ HP-UX 10.x:
+ Not tested.
+ "updwtmp*()" routines seem absent, so no current wtmp* support.
+ Has "ut_addr": probably trivial to implement (although remember
+ that IPv6 is coming...).
+
+ FreeBSD:
+ No "putut*()" type of interface.
+ No "ut_type" and associated defines.
+ Write files directly. Alternatively use its login(3)/logout(3).
+ SunOS 4:
+ Not tested. Resembles FreeBSD, but no login()/logout().
+
+lastlog:
+ Should "lastlog" files, if any, be updated?
+ BSD systems (SunOS 4, FreeBSD):
+ o Prominent mention on man pages.
+ System-V (e.g. Solaris 2):
+ o No mention on man pages, even under "man -k".
+ o Has a "/var/adm/lastlog" file, but pututxline() etc. seem
+ not to touch it.
+ o Despite downplaying (above), nevertheless has <lastlog.h>.
+ So perhaps UN*X "lastlog" facility is intended for tty/terminal only?
+
+Notes:
+ Each connection requires a small number (starting at 0, working up)
+ to represent the line. This must be unique within and across all
+ smbd processes. It is the 'id_num' from Samba's session.c code.
+
+ The 4 byte 'ut_id' component is vital to distinguish connections,
+ of which there could be several hundred or even thousand.
+ Entries seem to be printable characters, with optional NULL pads.
+
+ We need to be distinct from other entries in utmp/wtmp.
+
+ Observed things: therefore avoid them. Add to this list please.
+ From Solaris 2.x (because that's what I have):
+ 'sN' : run-levels; N: [0-9]
+ 'co' : console
+ 'CC' : arbitrary things; C: [a-z]
+ 'rXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ 'tXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ '/NNN' : Solaris CDE
+ 'ftpZ' : ftp (Z is the number 255, aka 0377, aka 0xff)
+ Mostly a record uses the same 'ut_id' in both "utmp" and "wtmp",
+ but differences have been seen.
+
+ Arbitrarily I have chosen to use a distinctive 'SM' for the
+ first two bytes.
+
+ The remaining two bytes encode the session 'id_num' (see above).
+ Our caller (session.c) should note our 16-bit limitation.
+
+****************************************************************************/
+
+#ifndef WITH_UTMP
+/*
+ * Not WITH_UTMP? Simply supply dummy routines.
+ */
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *ip_addr_str,
+ const char *id_str, int id_num)
+{}
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *ip_addr_str,
+ const char *id_str, int id_num)
+{}
+
+#else /* WITH_UTMP */
+
+#include <utmp.h>
+
+#ifdef HAVE_UTMPX_H
+#include <utmpx.h>
+#endif
+
+/* BSD systems: some may need lastlog.h (SunOS 4), some may not (FreeBSD) */
+/* Some System-V systems (e.g. Solaris 2) declare this too. */
+#ifdef HAVE_LASTLOG_H
+#include <lastlog.h>
+#endif
+
+/****************************************************************************
+ Default paths to various {u,w}tmp{,x} files.
+****************************************************************************/
+
+#ifdef HAVE_UTMPX_H
+
+static const char *ux_pathname =
+# if defined (UTMPX_FILE)
+ UTMPX_FILE ;
+# elif defined (_UTMPX_FILE)
+ _UTMPX_FILE ;
+# elif defined (_PATH_UTMPX)
+ _PATH_UTMPX ;
+# else
+ "" ;
+# endif
+
+static const char *wx_pathname =
+# if defined (WTMPX_FILE)
+ WTMPX_FILE ;
+# elif defined (_WTMPX_FILE)
+ _WTMPX_FILE ;
+# elif defined (_PATH_WTMPX)
+ _PATH_WTMPX ;
+# else
+ "" ;
+# endif
+
+#endif /* HAVE_UTMPX_H */
+
+static const char *ut_pathname =
+# if defined (UTMP_FILE)
+ UTMP_FILE ;
+# elif defined (_UTMP_FILE)
+ _UTMP_FILE ;
+# elif defined (_PATH_UTMP)
+ _PATH_UTMP ;
+# else
+ "" ;
+# endif
+
+static const char *wt_pathname =
+# if defined (WTMP_FILE)
+ WTMP_FILE ;
+# elif defined (_WTMP_FILE)
+ _WTMP_FILE ;
+# elif defined (_PATH_WTMP)
+ _PATH_WTMP ;
+# else
+ "" ;
+# endif
+
+/* BSD-like systems might want "lastlog" support. */
+#if 0 /* *** Not yet implemented */
+#ifndef HAVE_PUTUTLINE /* see "pututline_my()" */
+static const char *ll_pathname =
+# if defined (_PATH_LASTLOG) /* what other names (if any?) */
+ _PATH_LASTLOG ;
+# else
+ "" ;
+# endif /* _PATH_LASTLOG */
+#endif /* HAVE_PUTUTLINE */
+#endif
+
+/*
+ * Get name of {u,w}tmp{,x} file.
+ * return: fname contains filename
+ * Possibly empty if this code not yet ported to this system.
+ *
+ * utmp{,x}: try "utmp dir", then default (a define)
+ * wtmp{,x}: try "wtmp dir", then "utmp dir", then default (a define)
+ */
+static char *uw_pathname(TALLOC_CTX *ctx,
+ const char *uw_name,
+ const char *uw_default)
+{
+ char *dirname = NULL;
+
+ /* For w-files, first look for explicit "wtmp dir" */
+ if (uw_name[0] == 'w') {
+ dirname = talloc_strdup(ctx, lp_wtmpdir());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* For u-files and non-explicit w-dir, look for "utmp dir" */
+ if ((dirname == NULL) || (strlen(dirname) == 0)) {
+ dirname = talloc_strdup(ctx, lp_utmpdir());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* If explicit directory above, use it */
+ if (dirname && strlen(dirname) != 0) {
+ return talloc_asprintf(ctx,
+ "%s/%s",
+ dirname,
+ uw_name);
+ }
+
+ /* No explicit directory: attempt to use default paths */
+ if (strlen(uw_default) == 0) {
+ /* No explicit setting, no known default.
+ * Has it yet been ported to this OS?
+ */
+ DEBUG(2,("uw_pathname: unable to determine pathname\n"));
+ }
+ return talloc_strdup(ctx, uw_default);
+}
+
+#ifndef HAVE_PUTUTLINE
+/****************************************************************************
+ Update utmp file directly. No subroutine interface: probably a BSD system.
+****************************************************************************/
+
+static void pututline_my(const char *uname, struct utmp *u, bool claim)
+{
+ DEBUG(1,("pututline_my: not yet implemented\n"));
+ /* BSD implementor: may want to consider (or not) adjusting "lastlog" */
+}
+#endif /* HAVE_PUTUTLINE */
+
+#ifndef HAVE_UPDWTMP
+
+/****************************************************************************
+ Update wtmp file directly. No subroutine interface: probably a BSD system.
+ Credit: Michail Vidiassov <master@iaas.msu.ru>
+****************************************************************************/
+
+static void updwtmp_my(const char *wname, struct utmp *u, bool claim)
+{
+ int fd;
+ struct stat buf;
+
+ if (! claim) {
+ /*
+ * BSD-like systems:
+ * may use empty ut_name to distinguish a logout record.
+ *
+ * May need "if defined(SUNOS4)" etc. around some of these,
+ * but try to avoid if possible.
+ *
+ * SunOS 4:
+ * man page indicates ut_name and ut_host both NULL
+ * FreeBSD 4.0:
+ * man page appears not to specify (hints non-NULL)
+ * A correspondent suggest at least ut_name should be NULL
+ */
+#if defined(HAVE_UT_UT_NAME)
+ memset((char *)&u->ut_name, '\0', sizeof(u->ut_name));
+#endif
+#if defined(HAVE_UT_UT_HOST)
+ memset((char *)&u->ut_host, '\0', sizeof(u->ut_host));
+#endif
+ }
+ /* Stolen from logwtmp function in libutil.
+ * May be more locking/blocking is needed?
+ */
+ if ((fd = open(wname, O_WRONLY|O_APPEND, 0)) < 0)
+ return;
+ if (fstat(fd, &buf) == 0) {
+ if (write(fd, (char *)u, sizeof(struct utmp)) != sizeof(struct utmp))
+ (void) ftruncate(fd, buf.st_size);
+ }
+ (void) close(fd);
+}
+#endif /* HAVE_UPDWTMP */
+
+/****************************************************************************
+ Update via utmp/wtmp (not utmpx/wtmpx).
+****************************************************************************/
+
+static void utmp_nox_update(struct utmp *u, bool claim)
+{
+ char *uname = NULL;
+ char *wname = NULL;
+#if defined(PUTUTLINE_RETURNS_UTMP)
+ struct utmp *urc;
+#endif /* PUTUTLINE_RETURNS_UTMP */
+
+ uname = uw_pathname(talloc_tos(), "utmp", ut_pathname);
+ if (!uname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: uname:%s\n", uname));
+
+#ifdef HAVE_PUTUTLINE
+ if (strlen(uname) != 0) {
+ utmpname(uname);
+ }
+
+# if defined(PUTUTLINE_RETURNS_UTMP)
+ setutent();
+ urc = pututline(u);
+ endutent();
+ if (urc == NULL) {
+ DEBUG(2,("utmp_nox_update: pututline() failed\n"));
+ return;
+ }
+# else /* PUTUTLINE_RETURNS_UTMP */
+ setutent();
+ pututline(u);
+ endutent();
+# endif /* PUTUTLINE_RETURNS_UTMP */
+
+#else /* HAVE_PUTUTLINE */
+ if (strlen(uname) != 0) {
+ pututline_my(uname, u, claim);
+ }
+#endif /* HAVE_PUTUTLINE */
+
+ wname = uw_pathname(talloc_tos(), "wtmp", wt_pathname);
+ if (!wname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: wname:%s\n", wname));
+ if (strlen(wname) != 0) {
+#ifdef HAVE_UPDWTMP
+ updwtmp(wname, u);
+ /*
+ * updwtmp() and the newer updwtmpx() may be unsymmetrical.
+ * At least one OS, Solaris 2.x declares the former in the
+ * "utmpx" (latter) file and context.
+ * In the Solaris case this is irrelevant: it has both and
+ * we always prefer the "x" case, so doesn't come here.
+ * But are there other systems, with no "x", which lack
+ * updwtmp() perhaps?
+ */
+#else
+ updwtmp_my(wname, u, claim);
+#endif /* HAVE_UPDWTMP */
+ }
+}
+
+/****************************************************************************
+ Copy a string in the utmp structure.
+****************************************************************************/
+
+static void utmp_strcpy(char *dest, const char *src, size_t n)
+{
+ size_t len = 0;
+
+ memset(dest, '\0', n);
+ if (src)
+ len = strlen(src);
+ if (len >= n) {
+ memcpy(dest, src, n);
+ } else {
+ if (len)
+ memcpy(dest, src, len);
+ }
+}
+
+/****************************************************************************
+ Update via utmpx/wtmpx (preferred) or via utmp/wtmp.
+****************************************************************************/
+
+static void sys_utmp_update(struct utmp *u, const char *hostname, bool claim)
+{
+#if !defined(HAVE_UTMPX_H)
+ /* No utmpx stuff. Drop to non-x stuff */
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_PUTUTXLINE)
+ /* Odd. Have utmpx.h but no "pututxline()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no pututxline() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_GETUTMPX)
+ /* Odd. Have utmpx.h but no "getutmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no getutmpx() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_UPDWTMPX)
+ /* Have utmpx.h but no "updwtmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no updwtmpx() function\n"));
+ utmp_nox_update(u, claim);
+#else
+ char *uname = NULL;
+ char *wname = NULL;
+ struct utmpx ux, *uxrc;
+
+ getutmpx(u, &ux);
+
+#if defined(HAVE_UX_UT_SYSLEN)
+ if (hostname)
+ ux.ut_syslen = strlen(hostname) + 1; /* include end NULL */
+ else
+ ux.ut_syslen = 0;
+#endif
+#if defined(HAVE_UT_UT_HOST)
+ utmp_strcpy(ux.ut_host, hostname, sizeof(ux.ut_host));
+#endif
+
+ uname = uw_pathname(talloc_tos(), "utmpx", ux_pathname);
+ wname = uw_pathname(talloc_tos(), "wtmpx", wx_pathname);
+ if (uname && wname) {
+ DEBUG(2,("utmp_update: uname:%s wname:%s\n", uname, wname));
+ }
+
+ /*
+ * Check for either uname or wname being empty.
+ * Some systems, such as Redhat 6, have a "utmpx.h" which doesn't
+ * define default filenames.
+ * Also, our local installation has not provided an override.
+ * Drop to non-x method. (E.g. RH6 has good defaults in "utmp.h".)
+ */
+ if (!uname || !wname || (strlen(uname) == 0) || (strlen(wname) == 0)) {
+ utmp_nox_update(u, claim);
+ } else {
+ utmpxname(uname);
+ setutxent();
+ uxrc = pututxline(&ux);
+ endutxent();
+ if (uxrc == NULL) {
+ DEBUG(2,("utmp_update: pututxline() failed\n"));
+ return;
+ }
+ updwtmpx(wname, &ux);
+ }
+#endif /* HAVE_UTMPX_H */
+}
+
+#if defined(HAVE_UT_UT_ID)
+/****************************************************************************
+ Encode the unique connection number into "ut_id".
+****************************************************************************/
+
+static int ut_id_encode(int i, char *fourbyte)
+{
+ int nbase;
+ const char *ut_id_encstr = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ fourbyte[0] = 'S';
+ fourbyte[1] = 'M';
+
+/*
+ * Encode remaining 2 bytes from 'i'.
+ * 'ut_id_encstr' is the character set on which modulo arithmetic is done.
+ * Example: digits would produce the base-10 numbers from '001'.
+ */
+ nbase = strlen(ut_id_encstr);
+
+ fourbyte[3] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[2] = ut_id_encstr[i % nbase];
+ i /= nbase;
+
+ return(i); /* 0: good; else overflow */
+}
+#endif /* defined(HAVE_UT_UT_ID) */
+
+
+/*
+ fill a system utmp structure given all the info we can gather
+*/
+static bool sys_utmp_fill(struct utmp *u,
+ const char *username, const char *hostname,
+ const char *ip_addr_str,
+ const char *id_str, int id_num)
+{
+ struct timeval timeval;
+
+ /*
+ * ut_name, ut_user:
+ * Several (all?) systems seems to define one as the other.
+ * It is easier and clearer simply to let the following take its course,
+ * rather than to try to detect and optimise.
+ */
+#if defined(HAVE_UT_UT_USER)
+ utmp_strcpy(u->ut_user, username, sizeof(u->ut_user));
+#elif defined(HAVE_UT_UT_NAME)
+ utmp_strcpy(u->ut_name, username, sizeof(u->ut_name));
+#endif
+
+ /*
+ * ut_line:
+ * If size limit proves troublesome, then perhaps use "ut_id_encode()".
+ */
+ if (strlen(id_str) > sizeof(u->ut_line)) {
+ DEBUG(1,("id_str [%s] is too long for %lu char utmp field\n",
+ id_str, (unsigned long)sizeof(u->ut_line)));
+ return False;
+ }
+ utmp_strcpy(u->ut_line, id_str, sizeof(u->ut_line));
+
+#if defined(HAVE_UT_UT_PID)
+ u->ut_pid = sys_getpid();
+#endif
+
+/*
+ * ut_time, ut_tv:
+ * Some have one, some the other. Many have both, but defined (aliased).
+ * It is easier and clearer simply to let the following take its course.
+ * But note that we do the more precise ut_tv as the final assignment.
+ */
+#if defined(HAVE_UT_UT_TIME)
+ GetTimeOfDay(&timeval);
+ u->ut_time = timeval.tv_sec;
+#elif defined(HAVE_UT_UT_TV)
+ GetTimeOfDay(&timeval);
+ u->ut_tv = timeval;
+#else
+#error "with-utmp must have UT_TIME or UT_TV"
+#endif
+
+#if defined(HAVE_UT_UT_HOST)
+ utmp_strcpy(u->ut_host, hostname, sizeof(u->ut_host));
+#endif
+#if defined(HAVE_IPV6) && defined(HAVE_UT_UT_ADDR_V6)
+ memset(&u->ut_addr_v6, '\0', sizeof(u->ut_addr_v6));
+ if (ip_addr_str) {
+ struct in6_addr addr;
+ if (inet_pton(AF_INET6, ip_addr_str, &addr) > 0) {
+ memcpy(&u->ut_addr_v6, &addr, sizeof(addr));
+ }
+ }
+#elif defined(HAVE_UT_UT_ADDR)
+ memset(&u->ut_addr, '\0', sizeof(u->ut_addr));
+ if (ip_addr_str) {
+ struct in_addr addr;
+ if (inet_pton(AF_INET, ip_addr_str, &addr) > 0) {
+ memcpy(&u->ut_addr, &addr, sizeof(addr));
+ }
+ }
+ /*
+ * "(unsigned long) ut_addr" apparently exists on at least HP-UX 10.20.
+ * Volunteer to implement, please ...
+ */
+#endif
+
+#if defined(HAVE_UT_UT_ID)
+ if (ut_id_encode(id_num, u->ut_id) != 0) {
+ DEBUG(1,("utmp_fill: cannot encode id %d\n", id_num));
+ return False;
+ }
+#endif
+
+ return True;
+}
+
+/****************************************************************************
+ Close a connection.
+****************************************************************************/
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *ip_addr_str,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_EXIT)
+ u.ut_exit.e_termination = 0;
+ u.ut_exit.e_exit = 0;
+#endif
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = DEAD_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, ip_addr_str, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, NULL, False);
+}
+
+/****************************************************************************
+ Claim a entry in whatever utmp system the OS uses.
+****************************************************************************/
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *ip_addr_str,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = USER_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, ip_addr_str, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, hostname, True);
+}
+
+#endif /* WITH_UTMP */
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c
new file mode 100644
index 0000000000..1e137dd908
--- /dev/null
+++ b/source3/smbd/vfs.c
@@ -0,0 +1,968 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ VFS initialisation and support functions
+ Copyright (C) Tim Potter 1999
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) James Peach 2006
+
+ 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/>.
+
+ This work was sponsored by Optifacio Software Services, Inc.
+*/
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static_decl_vfs;
+
+struct vfs_init_function_entry {
+ char *name;
+ const vfs_op_tuple *vfs_op_tuples;
+ struct vfs_init_function_entry *prev, *next;
+};
+
+static struct vfs_init_function_entry *backends = NULL;
+
+/****************************************************************************
+ maintain the list of available backends
+****************************************************************************/
+
+static struct vfs_init_function_entry *vfs_find_backend_entry(const char *name)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ DEBUG(10, ("vfs_find_backend_entry called for %s\n", name));
+
+ while(entry) {
+ if (strcmp(entry->name, name)==0) return entry;
+ entry = entry->next;
+ }
+
+ return NULL;
+}
+
+NTSTATUS smb_register_vfs(int version, const char *name, const vfs_op_tuple *vfs_op_tuples)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ if ((version != SMB_VFS_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register vfs module.\n"
+ "The module was compiled against SMB_VFS_INTERFACE_VERSION %d,\n"
+ "current SMB_VFS_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current Samba Version!\n",
+ version, SMB_VFS_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !vfs_op_tuples) {
+ DEBUG(0,("smb_register_vfs() called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (vfs_find_backend_entry(name)) {
+ DEBUG(0,("VFS module %s already loaded!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct vfs_init_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->vfs_op_tuples = vfs_op_tuples;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("Successfully added vfs backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ initialise default vfs hooks
+****************************************************************************/
+
+static void vfs_init_default(connection_struct *conn)
+{
+ DEBUG(3, ("Initialising default vfs hooks\n"));
+ vfs_init_custom(conn, DEFAULT_VFS_MODULE_NAME);
+}
+
+/****************************************************************************
+ initialise custom vfs hooks
+ ****************************************************************************/
+
+static inline void vfs_set_operation(struct vfs_ops * vfs, vfs_op_type which,
+ struct vfs_handle_struct * handle, void * op)
+{
+ ((struct vfs_handle_struct **)&vfs->handles)[which] = handle;
+ ((void **)(void *)&vfs->ops)[which] = op;
+}
+
+bool vfs_init_custom(connection_struct *conn, const char *vfs_object)
+{
+ const vfs_op_tuple *ops;
+ char *module_path = NULL;
+ char *module_name = NULL;
+ char *module_param = NULL, *p;
+ int i;
+ vfs_handle_struct *handle;
+ const struct vfs_init_function_entry *entry;
+
+ if (!conn||!vfs_object||!vfs_object[0]) {
+ DEBUG(0,("vfs_init_custon() called with NULL pointer or emtpy vfs_object!\n"));
+ return False;
+ }
+
+ if(!backends) {
+ static_init_vfs;
+ }
+
+ DEBUG(3, ("Initialising custom vfs hooks from [%s]\n", vfs_object));
+
+ module_path = smb_xstrdup(vfs_object);
+
+ p = strchr_m(module_path, ':');
+
+ if (p) {
+ *p = 0;
+ module_param = p+1;
+ trim_char(module_param, ' ', ' ');
+ }
+
+ trim_char(module_path, ' ', ' ');
+
+ module_name = smb_xstrdup(module_path);
+
+ if ((module_name[0] == '/') &&
+ (strcmp(module_path, DEFAULT_VFS_MODULE_NAME) != 0)) {
+
+ /*
+ * Extract the module name from the path. Just use the base
+ * name of the last path component.
+ */
+
+ SAFE_FREE(module_name);
+ module_name = smb_xstrdup(strrchr_m(module_path, '/')+1);
+
+ p = strchr_m(module_name, '.');
+
+ if (p != NULL) {
+ *p = '\0';
+ }
+ }
+
+ /* First, try to load the module with the new module system */
+ if((entry = vfs_find_backend_entry(module_name)) ||
+ (NT_STATUS_IS_OK(smb_probe_module("vfs", module_path)) &&
+ (entry = vfs_find_backend_entry(module_name)))) {
+
+ DEBUGADD(5,("Successfully loaded vfs module [%s] with the new modules system\n", vfs_object));
+
+ if ((ops = entry->vfs_op_tuples) == NULL) {
+ DEBUG(0, ("entry->vfs_op_tuples==NULL for [%s] failed\n", vfs_object));
+ goto fail;
+ }
+ } else {
+ DEBUG(0,("Can't find a vfs module [%s]\n",vfs_object));
+ goto fail;
+ }
+
+ handle = TALLOC_ZERO_P(conn, vfs_handle_struct);
+ if (!handle) {
+ DEBUG(0,("TALLOC_ZERO() failed!\n"));
+ goto fail;
+ }
+ memcpy(&handle->vfs_next, &conn->vfs, sizeof(struct vfs_ops));
+ handle->conn = conn;
+ if (module_param) {
+ handle->param = talloc_strdup(conn, module_param);
+ }
+ DLIST_ADD(conn->vfs_handles, handle);
+
+ for(i=0; ops[i].op != NULL; i++) {
+ DEBUG(5, ("Checking operation #%d (type %d, layer %d)\n", i, ops[i].type, ops[i].layer));
+ if(ops[i].layer == SMB_VFS_LAYER_OPAQUE) {
+ /* If this operation was already made opaque by different module, it
+ * will be overridden here.
+ */
+ DEBUGADD(5, ("Making operation type %d opaque [module %s]\n", ops[i].type, vfs_object));
+ vfs_set_operation(&conn->vfs_opaque, ops[i].type, handle, ops[i].op);
+ }
+ /* Change current VFS disposition*/
+ DEBUGADD(5, ("Accepting operation type %d from module %s\n", ops[i].type, vfs_object));
+ vfs_set_operation(&conn->vfs, ops[i].type, handle, ops[i].op);
+ }
+
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return True;
+
+ fail:
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return False;
+}
+
+/*****************************************************************
+ Allow VFS modules to extend files_struct with VFS-specific state.
+ This will be ok for small numbers of extensions, but might need to
+ be refactored if it becomes more widely used.
+******************************************************************/
+
+#define EXT_DATA_AREA(e) ((uint8 *)(e) + sizeof(struct vfs_fsp_data))
+
+void *vfs_add_fsp_extension_notype(vfs_handle_struct *handle, files_struct *fsp, size_t ext_size)
+{
+ struct vfs_fsp_data *ext;
+ void * ext_data;
+
+ /* Prevent VFS modules adding multiple extensions. */
+ if ((ext_data = vfs_fetch_fsp_extension(handle, fsp))) {
+ return ext_data;
+ }
+
+ ext = (struct vfs_fsp_data *)TALLOC_ZERO(
+ handle->conn, sizeof(struct vfs_fsp_data) + ext_size);
+ if (ext == NULL) {
+ return NULL;
+ }
+
+ ext->owner = handle;
+ ext->next = fsp->vfs_extension;
+ fsp->vfs_extension = ext;
+ return EXT_DATA_AREA(ext);
+}
+
+void vfs_remove_fsp_extension(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct vfs_fsp_data *curr;
+ struct vfs_fsp_data *prev;
+
+ for (curr = fsp->vfs_extension, prev = NULL;
+ curr;
+ prev = curr, curr = curr->next) {
+ if (curr->owner == handle) {
+ if (prev) {
+ prev->next = curr->next;
+ } else {
+ fsp->vfs_extension = curr->next;
+ }
+ TALLOC_FREE(curr);
+ return;
+ }
+ }
+}
+
+void *vfs_memctx_fsp_extension(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ for (head = fsp->vfs_extension; head; head = head->next) {
+ if (head->owner == handle) {
+ return head;
+ }
+ }
+
+ return NULL;
+}
+
+void *vfs_fetch_fsp_extension(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ head = (struct vfs_fsp_data *)vfs_memctx_fsp_extension(handle, fsp);
+ if (head != NULL) {
+ return EXT_DATA_AREA(head);
+ }
+
+ return NULL;
+}
+
+#undef EXT_DATA_AREA
+
+/*****************************************************************
+ Generic VFS init.
+******************************************************************/
+
+bool smbd_vfs_init(connection_struct *conn)
+{
+ const char **vfs_objects;
+ unsigned int i = 0;
+ int j = 0;
+
+ /* Normal share - initialise with disk access functions */
+ vfs_init_default(conn);
+ vfs_objects = lp_vfs_objects(SNUM(conn));
+
+ /* Override VFS functions if 'vfs object' was not specified*/
+ if (!vfs_objects || !vfs_objects[0])
+ return True;
+
+ for (i=0; vfs_objects[i] ;) {
+ i++;
+ }
+
+ for (j=i-1; j >= 0; j--) {
+ if (!vfs_init_custom(conn, vfs_objects[j])) {
+ DEBUG(0, ("smbd_vfs_init: vfs_init_custom failed for %s\n", vfs_objects[j]));
+ return False;
+ }
+ }
+ return True;
+}
+
+/*******************************************************************
+ Check if directory exists.
+********************************************************************/
+
+bool vfs_directory_exist(connection_struct *conn, const char *dname, SMB_STRUCT_STAT *st)
+{
+ SMB_STRUCT_STAT st2;
+ bool ret;
+
+ if (!st)
+ st = &st2;
+
+ if (SMB_VFS_STAT(conn,dname,st) != 0)
+ return(False);
+
+ ret = S_ISDIR(st->st_mode);
+ if(!ret)
+ errno = ENOTDIR;
+
+ return ret;
+}
+
+/*******************************************************************
+ Check if an object exists in the vfs.
+********************************************************************/
+
+bool vfs_object_exist(connection_struct *conn,const char *fname,SMB_STRUCT_STAT *sbuf)
+{
+ SMB_STRUCT_STAT st;
+
+ if (!sbuf)
+ sbuf = &st;
+
+ ZERO_STRUCTP(sbuf);
+
+ if (SMB_VFS_STAT(conn,fname,sbuf) == -1)
+ return(False);
+ return True;
+}
+
+/*******************************************************************
+ Check if a file exists in the vfs.
+********************************************************************/
+
+bool vfs_file_exist(connection_struct *conn, const char *fname,SMB_STRUCT_STAT *sbuf)
+{
+ SMB_STRUCT_STAT st;
+
+ if (!sbuf)
+ sbuf = &st;
+
+ ZERO_STRUCTP(sbuf);
+
+ if (SMB_VFS_STAT(conn,fname,sbuf) == -1)
+ return False;
+ return(S_ISREG(sbuf->st_mode));
+}
+
+/****************************************************************************
+ Read data from fsp on the vfs. (note: EINTR re-read differs from vfs_write_data)
+****************************************************************************/
+
+ssize_t vfs_read_data(files_struct *fsp, char *buf, size_t byte_count)
+{
+ size_t total=0;
+
+ while (total < byte_count)
+ {
+ ssize_t ret = SMB_VFS_READ(fsp, buf + total,
+ byte_count - total);
+
+ if (ret == 0) return total;
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ else
+ return -1;
+ }
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+
+ssize_t vfs_pread_data(files_struct *fsp, char *buf,
+ size_t byte_count, SMB_OFF_T offset)
+{
+ size_t total=0;
+
+ while (total < byte_count)
+ {
+ ssize_t ret = SMB_VFS_PREAD(fsp, buf + total,
+ byte_count - total, offset + total);
+
+ if (ret == 0) return total;
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ else
+ return -1;
+ }
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+
+/****************************************************************************
+ Write data to a fd on the vfs.
+****************************************************************************/
+
+ssize_t vfs_write_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N)
+{
+ size_t total=0;
+ ssize_t ret;
+
+ if (req && req->unread_bytes) {
+ SMB_ASSERT(req->unread_bytes == N);
+ /* VFS_RECVFILE must drain the socket
+ * before returning. */
+ req->unread_bytes = 0;
+ return SMB_VFS_RECVFILE(smbd_server_fd(),
+ fsp,
+ (SMB_OFF_T)-1,
+ N);
+ }
+
+ while (total < N) {
+ ret = SMB_VFS_WRITE(fsp, buffer + total, N - total);
+
+ if (ret == -1)
+ return -1;
+ if (ret == 0)
+ return total;
+
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+
+ssize_t vfs_pwrite_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N,
+ SMB_OFF_T offset)
+{
+ size_t total=0;
+ ssize_t ret;
+
+ if (req && req->unread_bytes) {
+ SMB_ASSERT(req->unread_bytes == N);
+ /* VFS_RECVFILE must drain the socket
+ * before returning. */
+ req->unread_bytes = 0;
+ return SMB_VFS_RECVFILE(smbd_server_fd(),
+ fsp,
+ offset,
+ N);
+ }
+
+ while (total < N) {
+ ret = SMB_VFS_PWRITE(fsp, buffer + total, N - total,
+ offset + total);
+
+ if (ret == -1)
+ return -1;
+ if (ret == 0)
+ return total;
+
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+/****************************************************************************
+ An allocate file space call using the vfs interface.
+ Allocates space for a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_allocate_file_space(files_struct *fsp, SMB_BIG_UINT len)
+{
+ int ret;
+ SMB_STRUCT_STAT st;
+ connection_struct *conn = fsp->conn;
+ SMB_BIG_UINT space_avail;
+ SMB_BIG_UINT bsize,dfree,dsize;
+
+ release_level_2_oplocks_on_change(fsp);
+
+ /*
+ * Actually try and commit the space on disk....
+ */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n", fsp->fsp_name, (double)len ));
+
+ if (((SMB_OFF_T)len) < 0) {
+ DEBUG(0,("vfs_allocate_file_space: %s negative len requested.\n", fsp->fsp_name ));
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = SMB_VFS_FSTAT(fsp, &st);
+ if (ret == -1)
+ return ret;
+
+ if (len == (SMB_BIG_UINT)st.st_size)
+ return 0;
+
+ if (len < (SMB_BIG_UINT)st.st_size) {
+ /* Shrink - use ftruncate. */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current size %.0f\n",
+ fsp->fsp_name, (double)st.st_size ));
+
+ flush_write_cache(fsp, SIZECHANGE_FLUSH);
+ if ((ret = SMB_VFS_FTRUNCATE(fsp, (SMB_OFF_T)len)) != -1) {
+ set_filelen_write_cache(fsp, len);
+ }
+ return ret;
+ }
+
+ /* Grow - we need to test if we have enough space. */
+
+ if (!lp_strict_allocate(SNUM(fsp->conn)))
+ return 0;
+
+ len -= st.st_size;
+ len /= 1024; /* Len is now number of 1k blocks needed. */
+ space_avail = get_dfree_info(conn,fsp->fsp_name,False,&bsize,&dfree,&dsize);
+ if (space_avail == (SMB_BIG_UINT)-1) {
+ return -1;
+ }
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f, needed blocks = %.0f, space avail = %.0f\n",
+ fsp->fsp_name, (double)st.st_size, (double)len, (double)space_avail ));
+
+ if (len > space_avail) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ A vfs set_filelen call.
+ set the length of a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_set_filelen(files_struct *fsp, SMB_OFF_T len)
+{
+ int ret;
+
+ release_level_2_oplocks_on_change(fsp);
+ DEBUG(10,("vfs_set_filelen: ftruncate %s to len %.0f\n", fsp->fsp_name, (double)len));
+ flush_write_cache(fsp, SIZECHANGE_FLUSH);
+ if ((ret = SMB_VFS_FTRUNCATE(fsp, len)) != -1) {
+ set_filelen_write_cache(fsp, len);
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SIZE
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ A vfs fill sparse call.
+ Writes zeros from the end of file to len, if len is greater than EOF.
+ Used only by strict_sync.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+static char *sparse_buf;
+#define SPARSE_BUF_WRITE_SIZE (32*1024)
+
+int vfs_fill_sparse(files_struct *fsp, SMB_OFF_T len)
+{
+ int ret;
+ SMB_STRUCT_STAT st;
+ SMB_OFF_T offset;
+ size_t total;
+ size_t num_to_write;
+ ssize_t pwrite_ret;
+
+ release_level_2_oplocks_on_change(fsp);
+ ret = SMB_VFS_FSTAT(fsp, &st);
+ if (ret == -1) {
+ return ret;
+ }
+
+ if (len <= st.st_size) {
+ return 0;
+ }
+
+ DEBUG(10,("vfs_fill_sparse: write zeros in file %s from len %.0f to len %.0f (%.0f bytes)\n",
+ fsp->fsp_name, (double)st.st_size, (double)len, (double)(len - st.st_size)));
+
+ flush_write_cache(fsp, SIZECHANGE_FLUSH);
+
+ if (!sparse_buf) {
+ sparse_buf = SMB_CALLOC_ARRAY(char, SPARSE_BUF_WRITE_SIZE);
+ if (!sparse_buf) {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ offset = st.st_size;
+ num_to_write = len - st.st_size;
+ total = 0;
+
+ while (total < num_to_write) {
+ size_t curr_write_size = MIN(SPARSE_BUF_WRITE_SIZE, (num_to_write - total));
+
+ pwrite_ret = SMB_VFS_PWRITE(fsp, sparse_buf, curr_write_size, offset + total);
+ if (pwrite_ret == -1) {
+ DEBUG(10,("vfs_fill_sparse: SMB_VFS_PWRITE for file %s failed with error %s\n",
+ fsp->fsp_name, strerror(errno) ));
+ return -1;
+ }
+ if (pwrite_ret == 0) {
+ return 0;
+ }
+
+ total += pwrite_ret;
+ }
+
+ set_filelen_write_cache(fsp, len);
+ return 0;
+}
+
+/****************************************************************************
+ Transfer some data (n bytes) between two file_struct's.
+****************************************************************************/
+
+static ssize_t vfs_read_fn(void *file, void *buf, size_t len)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_READ(fsp, buf, len);
+}
+
+static ssize_t vfs_write_fn(void *file, const void *buf, size_t len)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_WRITE(fsp, buf, len);
+}
+
+SMB_OFF_T vfs_transfer_file(files_struct *in, files_struct *out, SMB_OFF_T n)
+{
+ return transfer_file_internal((void *)in, (void *)out, n,
+ vfs_read_fn, vfs_write_fn);
+}
+
+/*******************************************************************
+ A vfs_readdir wrapper which just returns the file name.
+********************************************************************/
+
+char *vfs_readdirname(connection_struct *conn, void *p)
+{
+ SMB_STRUCT_DIRENT *ptr= NULL;
+ char *dname;
+
+ if (!p)
+ return(NULL);
+
+ ptr = SMB_VFS_READDIR(conn, (DIR *)p);
+ if (!ptr)
+ return(NULL);
+
+ dname = ptr->d_name;
+
+#ifdef NEXT2
+ if (telldir(p) < 0)
+ return(NULL);
+#endif
+
+#ifdef HAVE_BROKEN_READDIR_NAME
+ /* using /usr/ucb/cc is BAD */
+ dname = dname - 2;
+#endif
+
+ return(dname);
+}
+
+/*******************************************************************
+ A wrapper for vfs_chdir().
+********************************************************************/
+
+int vfs_ChDir(connection_struct *conn, const char *path)
+{
+ int res;
+ static char *LastDir = NULL;
+
+ if (!LastDir) {
+ LastDir = SMB_STRDUP("");
+ }
+
+ if (strcsequal(path,"."))
+ return(0);
+
+ if (*path == '/' && strcsequal(LastDir,path))
+ return(0);
+
+ DEBUG(4,("vfs_ChDir to %s\n",path));
+
+ res = SMB_VFS_CHDIR(conn,path);
+ if (!res) {
+ SAFE_FREE(LastDir);
+ LastDir = SMB_STRDUP(path);
+ }
+ return(res);
+}
+
+/*******************************************************************
+ Return the absolute current directory path - given a UNIX pathname.
+ Note that this path is returned in DOS format, not UNIX
+ format. Note this can be called with conn == NULL.
+********************************************************************/
+
+struct getwd_cache_key {
+ SMB_DEV_T dev;
+ SMB_INO_T ino;
+};
+
+char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
+{
+ char s[PATH_MAX+1];
+ SMB_STRUCT_STAT st, st2;
+ char *result;
+ DATA_BLOB cache_value;
+ struct getwd_cache_key key;
+
+ *s = 0;
+
+ if (!lp_getwd_cache()) {
+ goto nocache;
+ }
+
+ SET_STAT_INVALID(st);
+
+ if (SMB_VFS_STAT(conn, ".",&st) == -1) {
+ /*
+ * Known to fail for root: the directory may be NFS-mounted
+ * and exported with root_squash (so has no root access).
+ */
+ DEBUG(1,("vfs_GetWd: couldn't stat \".\" error %s "
+ "(NFS problem ?)\n", strerror(errno) ));
+ goto nocache;
+ }
+
+ ZERO_STRUCT(key); /* unlikely, but possible padding */
+ key.dev = st.st_dev;
+ key.ino = st.st_ino;
+
+ if (!memcache_lookup(smbd_memcache(), GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)),
+ &cache_value)) {
+ goto nocache;
+ }
+
+ SMB_ASSERT((cache_value.length > 0)
+ && (cache_value.data[cache_value.length-1] == '\0'));
+
+ if ((SMB_VFS_STAT(conn, (char *)cache_value.data, &st2) == 0)
+ && (st.st_dev == st2.st_dev) && (st.st_ino == st2.st_ino)
+ && (S_ISDIR(st.st_mode))) {
+ /*
+ * Ok, we're done
+ */
+ result = talloc_strdup(ctx, (char *)cache_value.data);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+ return result;
+ }
+
+ nocache:
+
+ /*
+ * We don't have the information to hand so rely on traditional
+ * methods. The very slow getcwd, which spawns a process on some
+ * systems, or the not quite so bad getwd.
+ */
+
+ if (!SMB_VFS_GETWD(conn,s)) {
+ DEBUG(0, ("vfs_GetWd: SMB_VFS_GETWD call failed: %s\n",
+ strerror(errno)));
+ return NULL;
+ }
+
+ if (lp_getwd_cache() && VALID_STAT(st)) {
+ ZERO_STRUCT(key); /* unlikely, but possible padding */
+ key.dev = st.st_dev;
+ key.ino = st.st_ino;
+
+ memcache_add(smbd_memcache(), GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)),
+ data_blob_const(s, strlen(s)+1));
+ }
+
+ result = talloc_strdup(ctx, s);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+ return result;
+}
+
+/*******************************************************************
+ Reduce a file name, removing .. elements and checking that
+ it is below dir in the heirachy. This uses realpath.
+********************************************************************/
+
+NTSTATUS check_reduced_name(connection_struct *conn, const char *fname)
+{
+#ifdef REALPATH_TAKES_NULL
+ bool free_resolved_name = True;
+#else
+ char resolved_name_buf[PATH_MAX+1];
+ bool free_resolved_name = False;
+#endif
+ char *resolved_name = NULL;
+ size_t con_path_len = strlen(conn->connectpath);
+ char *p = NULL;
+
+ DEBUG(3,("reduce_name [%s] [%s]\n", fname, conn->connectpath));
+
+#ifdef REALPATH_TAKES_NULL
+ resolved_name = SMB_VFS_REALPATH(conn,fname,NULL);
+#else
+ resolved_name = SMB_VFS_REALPATH(conn,fname,resolved_name_buf);
+#endif
+
+ if (!resolved_name) {
+ switch (errno) {
+ case ENOTDIR:
+ DEBUG(3,("reduce_name: Component not a directory in getting realpath for %s\n", fname));
+ return map_nt_error_from_unix(errno);
+ case ENOENT:
+ {
+ TALLOC_CTX *ctx = talloc_tos();
+ char *tmp_fname = NULL;
+ char *last_component = NULL;
+ /* Last component didn't exist. Remove it and try and canonicalise the directory. */
+
+ tmp_fname = talloc_strdup(ctx, fname);
+ if (!tmp_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(tmp_fname, '/');
+ if (p) {
+ *p++ = '\0';
+ last_component = p;
+ } else {
+ last_component = tmp_fname;
+ tmp_fname = talloc_strdup(ctx,
+ ".");
+ if (!tmp_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+#ifdef REALPATH_TAKES_NULL
+ resolved_name = SMB_VFS_REALPATH(conn,tmp_fname,NULL);
+#else
+ resolved_name = SMB_VFS_REALPATH(conn,tmp_fname,resolved_name_buf);
+#endif
+ if (!resolved_name) {
+ DEBUG(3,("reduce_name: couldn't get realpath for %s\n", fname));
+ return map_nt_error_from_unix(errno);
+ }
+ tmp_fname = talloc_asprintf(ctx,
+ "%s/%s",
+ resolved_name,
+ last_component);
+ if (!tmp_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+#ifdef REALPATH_TAKES_NULL
+ SAFE_FREE(resolved_name);
+ resolved_name = SMB_STRDUP(tmp_fname);
+ if (!resolved_name) {
+ DEBUG(0,("reduce_name: malloc fail for %s\n", tmp_fname));
+ return NT_STATUS_NO_MEMORY;
+ }
+#else
+ safe_strcpy(resolved_name_buf, tmp_fname, PATH_MAX);
+ resolved_name = resolved_name_buf;
+#endif
+ break;
+ }
+ default:
+ DEBUG(1,("reduce_name: couldn't get realpath for %s\n", fname));
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ DEBUG(10,("reduce_name realpath [%s] -> [%s]\n", fname, resolved_name));
+
+ if (*resolved_name != '/') {
+ DEBUG(0,("reduce_name: realpath doesn't return absolute paths !\n"));
+ if (free_resolved_name) {
+ SAFE_FREE(resolved_name);
+ }
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ /* Check for widelinks allowed. */
+ if (!lp_widelinks(SNUM(conn)) && (strncmp(conn->connectpath, resolved_name, con_path_len) != 0)) {
+ DEBUG(2, ("reduce_name: Bad access attempt: %s is a symlink outside the share path", fname));
+ if (free_resolved_name) {
+ SAFE_FREE(resolved_name);
+ }
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Check if we are allowing users to follow symlinks */
+ /* Patch from David Clerc <David.Clerc@cui.unige.ch>
+ University of Geneva */
+
+#ifdef S_ISLNK
+ if (!lp_symlinks(SNUM(conn))) {
+ SMB_STRUCT_STAT statbuf;
+ if ( (SMB_VFS_LSTAT(conn,fname,&statbuf) != -1) &&
+ (S_ISLNK(statbuf.st_mode)) ) {
+ if (free_resolved_name) {
+ SAFE_FREE(resolved_name);
+ }
+ DEBUG(3,("reduce_name: denied: file path name %s is a symlink\n",resolved_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+#endif
+
+ DEBUG(3,("reduce_name: %s reduced to %s\n", fname, resolved_name));
+ if (free_resolved_name) {
+ SAFE_FREE(resolved_name);
+ }
+ return NT_STATUS_OK;
+}