diff options
Diffstat (limited to 'source3/smbd')
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, ¤t_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, ¤t_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, ¤t_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, ¬ify->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 = ¬ify->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 = ¬ify->array->depth[depth]; + + for (i=0;i<d->num_entries;i++) { + if (private_data == d->entries[i].private_data && + cluster_id_equal(¬ify->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 = ¬ify->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 = ¬ify->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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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( ¤t_ace->trustee, ¤t_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( ¤t_ace->trustee, ¤t_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(¤t_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, "astat)) { + 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, "astat)) { + 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 *) "abuf; + + 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 = ¶ms[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, ¤t_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, "as)!=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, + ¶m_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, "as)!=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, ¶ms[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, ¶ms[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, ¶ms[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, ¶ms[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; +} |