X-Git-Url: http://git.samba.org/samba.git/?p=kai%2Fsamba.git;a=blobdiff_plain;f=source3%2Fsmbd%2Fopen.c;h=d736f4f795bb05d935afff6060f326e9e7c5761d;hp=a94a02eb463da53b3a7e500411a01c7613ecd631;hb=b3eb78c4f7123ccad6af50379c29d0939590d1ff;hpb=a93cdacd1679d968862b6c82ea266f986d4c621e diff --git a/source3/smbd/open.c b/source3/smbd/open.c index a94a02eb463..d736f4f795b 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -29,19 +29,21 @@ #include "../librpc/gen_ndr/ndr_security.h" #include "../librpc/gen_ndr/open_files.h" #include "auth.h" +#include "serverid.h" #include "messages.h" extern const struct generic_mapping file_generic_mapping; struct deferred_open_record { bool delayed_for_oplocks; + bool async_open; struct file_id id; }; /**************************************************************************** If the requester wanted DELETE_ACCESS and was rejected because the file ACL didn't include DELETE_ACCESS, see if the parent ACL - ovverrides this. + overrides this. ****************************************************************************/ static bool parent_override_delete(connection_struct *conn, @@ -63,6 +65,7 @@ static bool parent_override_delete(connection_struct *conn, NTSTATUS smbd_check_access_rights(struct connection_struct *conn, const struct smb_filename *smb_fname, + bool use_privs, uint32_t access_mask) { /* Check if we have rights to open. */ @@ -82,7 +85,7 @@ NTSTATUS smbd_check_access_rights(struct connection_struct *conn, return NT_STATUS_ACCESS_DENIED; } - if (get_current_uid(conn) == (uid_t)0) { + if (!use_privs && get_current_uid(conn) == (uid_t)0) { /* I'm sorry sir, I didn't know you were root... */ DEBUG(10,("smbd_check_access_rights: root override " "on %s. Granting 0x%x\n", @@ -112,7 +115,7 @@ NTSTATUS smbd_check_access_rights(struct connection_struct *conn, status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name, (SECINFO_OWNER | SECINFO_GROUP | - SECINFO_DACL),&sd); + SECINFO_DACL), talloc_tos(), &sd); if (!NT_STATUS_IS_OK(status)) { DEBUG(10, ("smbd_check_access_rights: Could not get acl " @@ -128,11 +131,18 @@ NTSTATUS smbd_check_access_rights(struct connection_struct *conn, } /* - * Never test FILE_READ_ATTRIBUTES. se_access_check() also takes care of + * If we can access the path to this file, by + * default we have FILE_READ_ATTRIBUTES from the + * containing directory. See the section: + * "Algorithm to Check Access to an Existing File" + * in MS-FSA.pdf. + * + * se_file_access_check() also takes care of * owner WRITE_DAC and READ_CONTROL. */ - status = se_access_check(sd, + status = se_file_access_check(sd, get_current_nttok(conn), + use_privs, (access_mask & ~FILE_READ_ATTRIBUTES), &rejected_mask); @@ -201,15 +211,13 @@ NTSTATUS smbd_check_access_rights(struct connection_struct *conn, if (rejected_mask != 0) { return NT_STATUS_ACCESS_DENIED; - } else { - return NT_STATUS_OK; } + return NT_STATUS_OK; } static NTSTATUS check_parent_access(struct connection_struct *conn, struct smb_filename *smb_fname, - uint32_t access_mask, - char **pp_parent_dir) + uint32_t access_mask) { NTSTATUS status; char *parent_dir = NULL; @@ -223,10 +231,6 @@ static NTSTATUS check_parent_access(struct connection_struct *conn, return NT_STATUS_NO_MEMORY; } - if (pp_parent_dir) { - *pp_parent_dir = parent_dir; - } - if (get_current_uid(conn) == (uid_t)0) { /* I'm sorry sir, I didn't know you were root... */ DEBUG(10,("check_parent_access: root override " @@ -239,6 +243,7 @@ static NTSTATUS check_parent_access(struct connection_struct *conn, status = SMB_VFS_GET_NT_ACL(conn, parent_dir, SECINFO_DACL, + talloc_tos(), &parent_sd); if (!NT_STATUS_IS_OK(status)) { @@ -250,11 +255,18 @@ static NTSTATUS check_parent_access(struct connection_struct *conn, } /* - * Never test FILE_READ_ATTRIBUTES. se_access_check() also takes care of + * If we can access the path to this file, by + * default we have FILE_READ_ATTRIBUTES from the + * containing directory. See the section: + * "Algorithm to Check Access to an Existing File" + * in MS-FSA.pdf. + * + * se_file_access_check() also takes care of * owner WRITE_DAC and READ_CONTROL. */ - status = se_access_check(parent_sd, + status = se_file_access_check(parent_sd, get_current_nttok(conn), + false, (access_mask & ~FILE_READ_ATTRIBUTES), &access_granted); if(!NT_STATUS_IS_OK(status)) { @@ -276,10 +288,10 @@ static NTSTATUS check_parent_access(struct connection_struct *conn, fd support routines - attempt to do a dos_open. ****************************************************************************/ -static NTSTATUS fd_open(struct connection_struct *conn, - files_struct *fsp, - int flags, - mode_t mode) +NTSTATUS fd_open(struct connection_struct *conn, + files_struct *fsp, + int flags, + mode_t mode) { struct smb_filename *smb_fname = fsp->fsp_name; NTSTATUS status = NT_STATUS_OK; @@ -297,7 +309,26 @@ static NTSTATUS fd_open(struct connection_struct *conn, fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, mode); if (fsp->fh->fd == -1) { - status = map_nt_error_from_unix(errno); + int posix_errno = errno; +#ifdef O_NOFOLLOW +#if defined(ENOTSUP) && defined(OSF1) + /* handle special Tru64 errno */ + if (errno == ENOTSUP) { + posix_errno = ELOOP; + } +#endif /* ENOTSUP */ +#ifdef EFTYPE + /* fix broken NetBSD errno */ + if (errno == EFTYPE) { + posix_errno = ELOOP; + } +#endif /* EFTYPE */ + /* fix broken FreeBSD errno */ + if (errno == EMLINK) { + posix_errno = ELOOP; + } +#endif /* O_NOFOLLOW */ + status = map_nt_error_from_unix(posix_errno); if (errno == EMFILE) { static time_t last_warned = 0L; @@ -519,6 +550,106 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, return status; } +/**************************************************************************** + Open a file - returning a guaranteed ATOMIC indication of if the + file was created or not. +****************************************************************************/ + +static NTSTATUS fd_open_atomic(struct connection_struct *conn, + files_struct *fsp, + int flags, + mode_t mode, + bool *file_created) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + bool file_existed = VALID_STAT(fsp->fsp_name->st); + + *file_created = false; + + if (!(flags & O_CREAT)) { + /* + * We're not creating the file, just pass through. + */ + return fd_open(conn, fsp, flags, mode); + } + + if (flags & O_EXCL) { + /* + * Fail if already exists, just pass through. + */ + status = fd_open(conn, fsp, flags, mode); + + /* + * Here we've opened with O_CREAT|O_EXCL. If that went + * NT_STATUS_OK, we *know* we created this file. + */ + *file_created = NT_STATUS_IS_OK(status); + + return status; + } + + /* + * Now it gets tricky. We have O_CREAT, but not O_EXCL. + * To know absolutely if we created the file or not, + * we can never call O_CREAT without O_EXCL. So if + * we think the file existed, try without O_CREAT|O_EXCL. + * If we think the file didn't exist, try with + * O_CREAT|O_EXCL. Keep bouncing between these two + * requests until either the file is created, or + * opened. Either way, we keep going until we get + * a returnable result (error, or open/create). + */ + + while(1) { + int curr_flags = flags; + + if (file_existed) { + /* Just try open, do not create. */ + curr_flags &= ~(O_CREAT); + status = fd_open(conn, fsp, curr_flags, mode); + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* + * Someone deleted it in the meantime. + * Retry with O_EXCL. + */ + file_existed = false; + DEBUG(10,("fd_open_atomic: file %s existed. " + "Retry.\n", + smb_fname_str_dbg(fsp->fsp_name))); + continue; + } + } else { + /* Try create exclusively, fail if it exists. */ + curr_flags |= O_EXCL; + status = fd_open(conn, fsp, curr_flags, mode); + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_COLLISION)) { + /* + * Someone created it in the meantime. + * Retry without O_CREAT. + */ + file_existed = true; + DEBUG(10,("fd_open_atomic: file %s " + "did not exist. Retry.\n", + smb_fname_str_dbg(fsp->fsp_name))); + continue; + } + if (NT_STATUS_IS_OK(status)) { + /* + * Here we've opened with O_CREAT|O_EXCL + * and got success. We *know* we created + * this file. + */ + *file_created = true; + } + } + /* Create is done, or failed. */ + break; + } + return status; +} + /**************************************************************************** Open a file. ****************************************************************************/ @@ -530,14 +661,14 @@ static NTSTATUS open_file(files_struct *fsp, 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. */ + uint32 open_access_mask, /* what we're actually using in the open. */ + bool *p_file_created) { struct smb_filename *smb_fname = fsp->fsp_name; NTSTATUS status = NT_STATUS_OK; int accmode = (flags & O_ACCMODE); int local_flags = flags; bool file_existed = VALID_STAT(fsp->fsp_name->st); - bool file_created = false; fsp->fh->fd = -1; errno = EPERM; @@ -560,7 +691,8 @@ static NTSTATUS open_file(files_struct *fsp, DEBUG(3,("Permission denied opening %s\n", smb_fname_str_dbg(smb_fname))); return NT_STATUS_ACCESS_DENIED; - } else if(flags & O_CREAT) { + } + 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. @@ -592,13 +724,7 @@ static NTSTATUS open_file(files_struct *fsp, (!file_existed && (local_flags & O_CREAT)) || ((local_flags & O_TRUNC) == O_TRUNC) ) { const char *wild; - - /* - * 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; + int ret; #if defined(O_NONBLOCK) && defined(S_ISFIFO) /* @@ -608,6 +734,7 @@ static NTSTATUS open_file(files_struct *fsp, */ if (file_existed && S_ISFIFO(smb_fname->st.st_ex_mode)) { + local_flags &= ~O_TRUNC; /* Can't truncate a FIFO. */ local_flags |= O_NONBLOCK; } #endif @@ -633,12 +760,12 @@ static NTSTATUS open_file(files_struct *fsp, if (file_existed) { status = smbd_check_access_rights(conn, smb_fname, + false, access_mask); } else if (local_flags & O_CREAT){ status = check_parent_access(conn, smb_fname, - SEC_DIR_ADD_FILE, - NULL); + SEC_DIR_ADD_FILE); } else { /* File didn't exist and no O_CREAT. */ return NT_STATUS_OBJECT_NAME_NOT_FOUND; @@ -657,7 +784,8 @@ static NTSTATUS open_file(files_struct *fsp, } /* Actually do the open */ - status = fd_open(conn, fsp, local_flags, unx_mode); + status = fd_open_atomic(conn, fsp, local_flags, + unx_mode, p_file_created); if (!NT_STATUS_IS_OK(status)) { DEBUG(3,("Error opening file %s (%s) (local_flags=%d) " "(flags=%d)\n", smb_fname_str_dbg(smb_fname), @@ -665,70 +793,24 @@ static NTSTATUS open_file(files_struct *fsp, return status; } - if ((local_flags & O_CREAT) && !file_existed) { - file_created = true; - } - - } else { - fsp->fh->fd = -1; /* What we used to call a stat open. */ - if (!file_existed) { - /* File must exist for a stat open. */ - return NT_STATUS_OBJECT_NAME_NOT_FOUND; - } - - status = smbd_check_access_rights(conn, - smb_fname, - access_mask); - - if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) && - fsp->posix_open && - S_ISLNK(smb_fname->st.st_ex_mode)) { - /* This is a POSIX stat open for delete - * or rename on a symlink that points - * nowhere. Allow. */ - DEBUG(10,("open_file: allowing POSIX " - "open on bad symlink %s\n", - smb_fname_str_dbg(smb_fname))); - status = NT_STATUS_OK; - } - - if (!NT_STATUS_IS_OK(status)) { - DEBUG(10,("open_file: " - "smbd_check_access_rights on file " - "%s returned %s\n", - smb_fname_str_dbg(smb_fname), - nt_errstr(status) )); - return status; - } - } - - if (!file_existed) { - int ret; - - if (fsp->fh->fd == -1) { - ret = SMB_VFS_STAT(conn, smb_fname); - } else { - ret = SMB_VFS_FSTAT(fsp, &smb_fname->st); - /* If we have an fd, this stat should succeed. */ - if (ret == -1) { - DEBUG(0,("Error doing fstat on open file %s " - "(%s)\n", - smb_fname_str_dbg(smb_fname), - strerror(errno) )); - } - } - - /* For a non-io open, this stat failing means file not found. JRA */ + ret = SMB_VFS_FSTAT(fsp, &smb_fname->st); if (ret == -1) { + /* If we have an fd, this stat should succeed. */ + DEBUG(0,("Error doing fstat on open file %s " + "(%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno) )); status = map_nt_error_from_unix(errno); fd_close(fsp); return status; } - if (file_created) { + if (*p_file_created) { + /* We created this file. */ + bool need_re_stat = false; /* Do all inheritance work after we've - done a successful stat call and filled + done a successful fstat call and filled in the stat struct in fsp->fsp_name. */ /* Inherit the ACL if required */ @@ -747,17 +829,13 @@ static NTSTATUS open_file(files_struct *fsp, } if (need_re_stat) { - if (fsp->fh->fd == -1) { - ret = SMB_VFS_STAT(conn, smb_fname); - } else { - ret = SMB_VFS_FSTAT(fsp, &smb_fname->st); - /* If we have an fd, this stat should succeed. */ - if (ret == -1) { - DEBUG(0,("Error doing fstat on open file %s " - "(%s)\n", - smb_fname_str_dbg(smb_fname), - strerror(errno) )); - } + ret = SMB_VFS_FSTAT(fsp, &smb_fname->st); + /* If we have an fd, this stat should succeed. */ + if (ret == -1) { + DEBUG(0,("Error doing fstat on open file %s " + "(%s)\n", + smb_fname_str_dbg(smb_fname), + strerror(errno) )); } } @@ -765,6 +843,38 @@ static NTSTATUS open_file(files_struct *fsp, FILE_NOTIFY_CHANGE_FILE_NAME, smb_fname->base_name); } + } else { + fsp->fh->fd = -1; /* What we used to call a stat open. */ + if (!file_existed) { + /* File must exist for a stat open. */ + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + status = smbd_check_access_rights(conn, + smb_fname, + false, + access_mask); + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) && + fsp->posix_open && + S_ISLNK(smb_fname->st.st_ex_mode)) { + /* This is a POSIX stat open for delete + * or rename on a symlink that points + * nowhere. Allow. */ + DEBUG(10,("open_file: allowing POSIX " + "open on bad symlink %s\n", + smb_fname_str_dbg(smb_fname))); + status = NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("open_file: " + "smbd_check_access_rights on file " + "%s returned %s\n", + smb_fname_str_dbg(smb_fname), + nt_errstr(status) )); + return status; + } } /* @@ -779,18 +889,14 @@ static NTSTATUS open_file(files_struct *fsp, return NT_STATUS_FILE_IS_A_DIRECTORY; } - fsp->mode = smb_fname->st.st_ex_mode; fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st); 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->can_read = ((access_mask & FILE_READ_DATA) != 0); + fsp->can_write = + CAN_WRITE(conn) && + ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0); fsp->print_file = NULL; fsp->modified = False; fsp->sent_oplock_break = NO_BREAK_SENT; @@ -829,6 +935,14 @@ static bool share_conflict(struct share_mode_entry *entry, (unsigned int)entry->share_access, (unsigned int)entry->private_options)); + if (server_id_is_disconnected(&entry->pid)) { + /* + * note: cleanup should have been done by + * delay_for_batch_oplocks() + */ + return false; + } + DEBUG(10,("share_conflict: access_mask = 0x%x, share_access = 0x%x\n", (unsigned int)access_mask, (unsigned int)share_access)); @@ -902,14 +1016,16 @@ static void validate_my_share_entries(struct smbd_server_connection *sconn, int num, struct share_mode_entry *share_entry) { + struct server_id self = messaging_server_id(sconn->msg_ctx); files_struct *fsp; - if (!procid_is_me(&share_entry->pid)) { + if (!serverid_equal(&self, &share_entry->pid)) { return; } if (is_deferred_open_entry(share_entry) && - !open_was_deferred(sconn, share_entry->op_mid)) { + !open_was_deferred(sconn, share_entry->op_mid)) + { char *str = talloc_asprintf(talloc_tos(), "Got a deferred entry without a request: " "PANIC: %s\n", @@ -935,7 +1051,8 @@ static void validate_my_share_entries(struct smbd_server_connection *sconn, } if ((share_entry->op_type == NO_OPLOCK) && - (fsp->oplock_type == FAKE_LEVEL_II_OPLOCK)) { + (fsp->oplock_type == FAKE_LEVEL_II_OPLOCK)) + { /* Someone has already written to it, but I haven't yet * noticed */ return; @@ -965,11 +1082,13 @@ static void validate_my_share_entries(struct smbd_server_connection *sconn, 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)); + const uint32_t stat_open_bits = + (SYNCHRONIZE_ACCESS| + FILE_READ_ATTRIBUTES| + FILE_WRITE_ATTRIBUTES); + + return (((access_mask & stat_open_bits) != 0) && + ((access_mask & ~stat_open_bits) == 0)); } /**************************************************************************** @@ -992,12 +1111,23 @@ static NTSTATUS open_mode_check(connection_struct *conn, return NT_STATUS_OK; } - *file_existed = True; - /* A delete on close prohibits everything */ if (is_delete_on_close_set(lck, name_hash)) { - return NT_STATUS_DELETE_PENDING; + /* + * Check the delete on close token + * is valid. It could have been left + * after a server crash. + */ + for(i = 0; i < lck->data->num_share_modes; i++) { + if (!share_mode_stale_pid(lck->data, i)) { + + *file_existed = true; + + return NT_STATUS_DELETE_PENDING; + } + } + return NT_STATUS_OK; } if (is_stat_open(access_mask)) { @@ -1017,10 +1147,6 @@ static NTSTATUS open_mode_check(connection_struct *conn, } #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->data->num_share_modes; i++) { @@ -1032,16 +1158,22 @@ static NTSTATUS open_mode_check(connection_struct *conn, * too */ if (share_conflict(&lck->data->share_modes[i], access_mask, share_access)) { + + if (share_mode_stale_pid(lck->data, i)) { + continue; + } + + *file_existed = true; + return NT_STATUS_SHARING_VIOLATION; } } - return NT_STATUS_OK; -} + if (lck->data->num_share_modes != 0) { + *file_existed = true; + } -static bool is_delete_request(files_struct *fsp) { - return ((fsp->access_mask == DELETE_ACCESS) && - (fsp->oplock_type == NO_OPLOCK)); + return NT_STATUS_OK; } /* @@ -1131,6 +1263,10 @@ static void find_oplock_types(files_struct *fsp, if (BATCH_OPLOCK_TYPE(lck->data->share_modes[i].op_type)) { /* batch - can only be one. */ + if (share_mode_stale_pid(lck->data, i)) { + DEBUG(10, ("Found stale batch oplock\n")); + continue; + } if (*pp_ex_or_batch || *pp_batch || *got_level2 || *got_no_oplock) { smb_panic("Bad batch oplock entry."); } @@ -1138,6 +1274,10 @@ static void find_oplock_types(files_struct *fsp, } if (EXCLUSIVE_OPLOCK_TYPE(lck->data->share_modes[i].op_type)) { + if (share_mode_stale_pid(lck->data, i)) { + DEBUG(10, ("Found stale duplicate oplock\n")); + continue; + } /* Exclusive or batch - can only be one. */ if (*pp_ex_or_batch || *got_level2 || *got_no_oplock) { smb_panic("Bad exclusive or batch oplock entry."); @@ -1147,6 +1287,11 @@ static void find_oplock_types(files_struct *fsp, if (LEVEL_II_OPLOCK_TYPE(lck->data->share_modes[i].op_type)) { if (*pp_batch || *pp_ex_or_batch) { + if (share_mode_stale_pid(lck->data, i)) { + DEBUG(10, ("Found stale LevelII " + "oplock\n")); + continue; + } smb_panic("Bad levelII oplock entry."); } *got_level2 = true; @@ -1154,6 +1299,11 @@ static void find_oplock_types(files_struct *fsp, if (lck->data->share_modes[i].op_type == NO_OPLOCK) { if (*pp_batch || *pp_ex_or_batch) { + if (share_mode_stale_pid(lck->data, i)) { + DEBUG(10, ("Found stale NO_OPLOCK " + "entry\n")); + continue; + } smb_panic("Bad no oplock entry."); } *got_no_oplock = true; @@ -1173,6 +1323,19 @@ static bool delay_for_batch_oplocks(files_struct *fsp, return false; } + if (server_id_is_disconnected(&batch_entry->pid)) { + /* + * TODO: clean up. + * This could be achieved by sending a break message + * to ourselves. Special considerations for files + * with delete_on_close flag set! + * + * For now we keep it simple and do not + * allow delete on close for durable handles. + */ + return false; + } + /* Found a batch oplock */ send_break_message(fsp, batch_entry, mid, oplock_request); return true; @@ -1183,8 +1346,6 @@ static bool delay_for_exclusive_oplocks(files_struct *fsp, int oplock_request, struct share_mode_entry *ex_entry) { - bool delay_it; - if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) { return false; } @@ -1192,12 +1353,12 @@ static bool delay_for_exclusive_oplocks(files_struct *fsp, return false; } - /* Found an exclusive or batch oplock */ - - delay_it = is_delete_request(fsp) ? - BATCH_OPLOCK_TYPE(ex_entry->op_type) : true; - - if (!delay_it) { + if (server_id_is_disconnected(&ex_entry->pid)) { + /* + * since only durable handles can get disconnected, + * and we can only get durable handles with batch oplocks, + * this should actually never be reached... + */ return false; } @@ -1234,7 +1395,9 @@ static void grant_fsp_oplock_type(files_struct *fsp, DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n", fsp->oplock_type, fsp_str_dbg(fsp))); return; - } else if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) { + } + + if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) { DEBUG(10,("grant_fsp_oplock_type: file %s has byte range locks\n", fsp_str_dbg(fsp))); fsp->oplock_type = NO_OPLOCK; @@ -1284,8 +1447,8 @@ static void grant_fsp_oplock_type(files_struct *fsp, fsp->oplock_type, fsp_str_dbg(fsp))); } -bool request_timed_out(struct timeval request_time, - struct timeval timeout) +static bool request_timed_out(struct timeval request_time, + struct timeval timeout) { struct timeval now, end_time; GetTimeOfDay(&now); @@ -1303,20 +1466,25 @@ static void defer_open(struct share_mode_lock *lck, struct smb_request *req, struct deferred_open_record *state) { - int i; + struct server_id self = messaging_server_id(req->sconn->msg_ctx); /* Paranoia check */ - for (i=0; idata->num_share_modes; i++) { - struct share_mode_entry *e = &lck->data->share_modes[i]; + if (lck) { + int i; - if (is_deferred_open_entry(e) && - procid_is_me(&e->pid) && - (e->op_mid == req->mid)) { - DEBUG(0, ("Trying to defer an already deferred " - "request: mid=%llu, exiting\n", - (unsigned long long)req->mid)); - exit_server("attempt to defer a deferred request"); + for (i=0; idata->num_share_modes; i++) { + struct share_mode_entry *e = &lck->data->share_modes[i]; + + if (is_deferred_open_entry(e) && + serverid_equal(&self, &e->pid) && + (e->op_mid == req->mid)) { + DEBUG(0, ("Trying to defer an already deferred " + "request: mid=%llu, exiting\n", + (unsigned long long)req->mid)); + TALLOC_FREE(lck); + exit_server("attempt to defer a deferred request"); + } } } @@ -1330,10 +1498,12 @@ static void defer_open(struct share_mode_lock *lck, if (!push_deferred_open_message_smb(req, request_time, timeout, state->id, (char *)state, sizeof(*state))) { + TALLOC_FREE(lck); exit_server("push_deferred_open_message_smb failed"); } - add_deferred_open(lck, req->mid, request_time, - messaging_server_id(req->sconn->msg_ctx), state->id); + if (lck) { + add_deferred_open(lck, req->mid, request_time, self, state->id); + } } @@ -1341,12 +1511,12 @@ static void defer_open(struct share_mode_lock *lck, On overwrite open ensure that the attributes match. ****************************************************************************/ -bool open_match_attributes(connection_struct *conn, - uint32 old_dos_attr, - uint32 new_dos_attr, - mode_t existing_unx_mode, - mode_t new_unx_mode, - mode_t *returned_unx_mode) +static bool open_match_attributes(connection_struct *conn, + 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; @@ -1389,16 +1559,16 @@ bool open_match_attributes(connection_struct *conn, Try and find a duplicated file handle. ****************************************************************************/ -NTSTATUS fcb_or_dos_open(struct smb_request *req, - connection_struct *conn, - files_struct *fsp_to_dup_into, - const struct smb_filename *smb_fname, - struct file_id id, - uint16 file_pid, - uint16 vuid, - uint32 access_mask, - uint32 share_access, - uint32 create_options) +static NTSTATUS fcb_or_dos_open(struct smb_request *req, + connection_struct *conn, + files_struct *fsp_to_dup_into, + const struct smb_filename *smb_fname, + struct file_id id, + uint16 file_pid, + uint64_t vuid, + uint32 access_mask, + uint32 share_access, + uint32 create_options) { files_struct *fsp; @@ -1409,14 +1579,15 @@ NTSTATUS fcb_or_dos_open(struct smb_request *req, 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 " + "vuid = %llu, file_pid = %u, private_options = 0x%x " "access_mask = 0x%x\n", fsp_str_dbg(fsp), - fsp->fh->fd, (unsigned int)fsp->vuid, + fsp->fh->fd, (unsigned long long)fsp->vuid, (unsigned int)fsp->file_pid, (unsigned int)fsp->fh->private_options, (unsigned int)fsp->access_mask )); - if (fsp->fh->fd != -1 && + if (fsp != fsp_to_dup_into && + fsp->fh->fd != -1 && fsp->vuid == vuid && fsp->file_pid == file_pid && (fsp->fh->private_options & (NTCREATEX_OPTIONS_PRIVATE_DENY_DOS | @@ -1476,6 +1647,7 @@ static void schedule_defer_open(struct share_mode_lock *lck, a 1 second delay for share mode conflicts. */ state.delayed_for_oplocks = True; + state.async_open = false; state.id = lck->data->id; if (!request_timed_out(request_time, timeout)) { @@ -1483,6 +1655,27 @@ static void schedule_defer_open(struct share_mode_lock *lck, } } +/**************************************************************************** + Reschedule an open call that went asynchronous. +****************************************************************************/ + +static void schedule_async_open(struct timeval request_time, + struct smb_request *req) +{ + struct deferred_open_record state; + struct timeval timeout; + + timeout = timeval_set(20, 0); + + ZERO_STRUCT(state); + state.delayed_for_oplocks = false; + state.async_open = true; + + if (!request_timed_out(request_time, timeout)) { + defer_open(NULL, request_time, timeout, req, &state); + } +} + /**************************************************************************** Work out what access_mask to use from what the client sent us. ****************************************************************************/ @@ -1490,13 +1683,14 @@ static void schedule_defer_open(struct share_mode_lock *lck, static NTSTATUS smbd_calculate_maximum_allowed_access( connection_struct *conn, const struct smb_filename *smb_fname, + bool use_privs, uint32_t *p_access_mask) { struct security_descriptor *sd; uint32_t access_granted; NTSTATUS status; - if (get_current_uid(conn) == (uid_t)0) { + if (!use_privs && (get_current_uid(conn) == (uid_t)0)) { *p_access_mask |= FILE_GENERIC_ALL; return NT_STATUS_OK; } @@ -1504,7 +1698,8 @@ static NTSTATUS smbd_calculate_maximum_allowed_access( status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name, (SECINFO_OWNER | SECINFO_GROUP | - SECINFO_DACL),&sd); + SECINFO_DACL), + talloc_tos(), &sd); if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { /* @@ -1514,37 +1709,50 @@ static NTSTATUS smbd_calculate_maximum_allowed_access( return NT_STATUS_OK; } if (!NT_STATUS_IS_OK(status)) { - DEBUG(10,("smbd_calculate_access_mask: " - "Could not get acl on file %s: %s\n", + DEBUG(10,("Could not get acl on file %s: %s\n", smb_fname_str_dbg(smb_fname), nt_errstr(status))); return NT_STATUS_ACCESS_DENIED; } /* - * Never test FILE_READ_ATTRIBUTES. se_access_check() + * If we can access the path to this file, by + * default we have FILE_READ_ATTRIBUTES from the + * containing directory. See the section: + * "Algorithm to Check Access to an Existing File" + * in MS-FSA.pdf. + * + * se_file_access_check() * also takes care of owner WRITE_DAC and READ_CONTROL. */ - status = se_access_check(sd, + status = se_file_access_check(sd, get_current_nttok(conn), + use_privs, (*p_access_mask & ~FILE_READ_ATTRIBUTES), &access_granted); TALLOC_FREE(sd); if (!NT_STATUS_IS_OK(status)) { - DEBUG(10, ("smbd_calculate_access_mask: " - "Access denied on file %s: " + DEBUG(10, ("Access denied on file %s: " "when calculating maximum access\n", smb_fname_str_dbg(smb_fname))); return NT_STATUS_ACCESS_DENIED; } *p_access_mask = (access_granted | FILE_READ_ATTRIBUTES); + + if (!(access_granted & DELETE_ACCESS)) { + if (can_delete_file_in_directory(conn, smb_fname)) { + *p_access_mask |= DELETE_ACCESS; + } + } + return NT_STATUS_OK; } NTSTATUS smbd_calculate_access_mask(connection_struct *conn, const struct smb_filename *smb_fname, + bool use_privs, uint32_t access_mask, uint32_t *access_mask_out) { @@ -1562,7 +1770,7 @@ NTSTATUS smbd_calculate_access_mask(connection_struct *conn, if (access_mask & MAXIMUM_ALLOWED_ACCESS) { status = smbd_calculate_maximum_allowed_access( - conn, smb_fname, &access_mask); + conn, smb_fname, use_privs, &access_mask); if (!NT_STATUS_IS_OK(status)) { return status; @@ -1595,7 +1803,8 @@ NTSTATUS smbd_calculate_access_mask(connection_struct *conn, void remove_deferred_open_entry(struct file_id id, uint64_t mid, struct server_id pid) { - struct share_mode_lock *lck = get_share_mode_lock(talloc_tos(), id); + struct share_mode_lock *lck = get_existing_share_mode_lock( + talloc_tos(), id); if (lck == NULL) { DEBUG(0, ("could not get share mode lock\n")); return; @@ -1604,6 +1813,120 @@ void remove_deferred_open_entry(struct file_id id, uint64_t mid, TALLOC_FREE(lck); } +/**************************************************************************** + Return true if this is a state pointer to an asynchronous create. +****************************************************************************/ + +bool is_deferred_open_async(const void *ptr) +{ + const struct deferred_open_record *state = (const struct deferred_open_record *)ptr; + + return state->async_open; +} + +static bool clear_ads(uint32_t create_disposition) +{ + bool ret = false; + + switch (create_disposition) { + case FILE_SUPERSEDE: + case FILE_OVERWRITE_IF: + case FILE_OVERWRITE: + ret = true; + break; + default: + break; + } + return ret; +} + +static int disposition_to_open_flags(uint32_t create_disposition) +{ + int ret = 0; + + /* + * 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. + */ + + switch (create_disposition) { + case FILE_SUPERSEDE: + case FILE_OVERWRITE_IF: + /* + * If file exists replace/overwrite. If file doesn't + * exist create. + */ + ret = O_CREAT|O_TRUNC; + break; + + case FILE_OPEN: + /* + * If file exists open. If file doesn't exist error. + */ + ret = 0; + break; + + case FILE_OVERWRITE: + /* + * If file exists overwrite. If file doesn't exist + * error. + */ + ret = O_TRUNC; + break; + + case FILE_CREATE: + /* + * If file exists error. If file doesn't exist create. + */ + ret = O_CREAT|O_EXCL; + break; + + case FILE_OPEN_IF: + /* + * If file exists open. If file doesn't exist create. + */ + ret = O_CREAT; + break; + } + return ret; +} + +static int calculate_open_access_flags(uint32_t access_mask, + int oplock_request, + uint32_t private_flags) +{ + bool need_write, need_read; + + /* + * Note that we ignore the append flag as append does not + * mean the same thing under DOS and Unix. + */ + + need_write = + ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) || + (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE)); + + if (!need_write) { + return O_RDONLY; + } + + /* DENY_DOS opens are always underlying read-write on the + file handle, no matter what the requested access mask + says. */ + + need_read = + ((private_flags & NTCREATEX_OPTIONS_PRIVATE_DENY_DOS) || + access_mask & (FILE_READ_ATTRIBUTES|FILE_READ_DATA| + FILE_READ_EA|FILE_EXECUTE)); + + if (!need_read) { + return O_WRONLY; + } + return O_RDWR; +} + /**************************************************************************** Open a file with a share mode. Passed in an already created files_struct *. ****************************************************************************/ @@ -1628,7 +1951,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, bool def_acl = False; bool posix_open = False; bool new_file_created = False; - bool clear_ads = false; + bool first_open_attempt = true; NTSTATUS fsp_open = NT_STATUS_ACCESS_DENIED; mode_t new_unx_mode = (mode_t)0; mode_t unx_mode = (mode_t)0; @@ -1639,6 +1962,13 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, uint32 open_access_mask = access_mask; NTSTATUS status; char *parent_dir; + SMB_STRUCT_STAT saved_stat = smb_fname->st; + struct share_mode_entry *batch_entry = NULL; + struct share_mode_entry *exclusive_entry = NULL; + bool got_level2_oplock = false; + bool got_a_none_oplock = false; + struct timespec old_write_time; + struct file_id id; if (conn->printer) { /* @@ -1708,19 +2038,28 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, if (get_deferred_open_message_state(req, &request_time, &ptr)) { - - struct deferred_open_record *state = (struct deferred_open_record *)ptr; /* Remember the absolute time of the original request with this mid. We'll use it later to see if this has timed out. */ - /* Remove the deferred open entry under lock. */ - remove_deferred_open_entry( - state->id, req->mid, - messaging_server_id(req->sconn->msg_ctx)); + /* If it was an async create retry, the file + didn't exist. */ + + if (is_deferred_open_async(ptr)) { + SET_STAT_INVALID(smb_fname->st); + file_existed = false; + } else { + struct deferred_open_record *state = (struct deferred_open_record *)ptr; + /* Remove the deferred open entry under lock. */ + remove_deferred_open_entry( + state->id, req->mid, + messaging_server_id(req->sconn->msg_ctx)); + } /* Ensure we don't reprocess this message. */ remove_deferred_open_message_smb(req->sconn, req->mid); + + first_open_attempt = false; } } @@ -1751,26 +2090,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } 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); - clear_ads = true; - break; - - case FILE_OVERWRITE_IF: - /* If file exists replace/overwrite. If file doesn't - * exist create. */ - flags2 |= (O_CREAT | O_TRUNC); - clear_ads = true; - break; - case FILE_OPEN: /* If file exists open. If file doesn't exist error. */ if (!file_existed) { @@ -1794,8 +2113,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, errno = ENOENT; return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - flags2 |= O_TRUNC; - clear_ads = true; break; case FILE_CREATE: @@ -1813,24 +2130,24 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } return map_nt_error_from_unix(errno); } - flags2 |= (O_CREAT|O_EXCL); break; + case FILE_SUPERSEDE: + case FILE_OVERWRITE_IF: case FILE_OPEN_IF: - /* If file exists open. If file doesn't exist - * create. */ - flags2 |= O_CREAT; break; - default: return NT_STATUS_INVALID_PARAMETER; } + flags2 = disposition_to_open_flags(create_disposition); + /* 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 (!posix_open && file_existed && + ((create_disposition == FILE_OVERWRITE) || + (create_disposition == FILE_OVERWRITE_IF))) { if (!open_match_attributes(conn, existing_dos_attributes, new_dos_attributes, smb_fname->st.st_ex_mode, @@ -1848,6 +2165,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } status = smbd_calculate_access_mask(conn, smb_fname, + false, access_mask, &access_mask); if (!NT_STATUS_IS_OK(status)) { @@ -1872,20 +2190,8 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * 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 ((private_flags & 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; - } + flags = calculate_open_access_flags(access_mask, oplock_request, + private_flags); /* * Currently we only look at FILE_WRITE_THROUGH for create options. @@ -1910,6 +2216,24 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, flags2 &= ~(O_CREAT|O_TRUNC); } + if (first_open_attempt && lp_kernel_oplocks(SNUM(conn))) { + /* + * With kernel oplocks the open breaking an oplock + * blocks until the oplock holder has given up the + * oplock or closed the file. We prevent this by first + * trying to open the file with O_NONBLOCK (see "man + * fcntl" on Linux). For the second try, triggered by + * an oplock break response, we do not need this + * anymore. + * + * This is true under the assumption that only Samba + * requests kernel oplocks. Once someone else like + * NFSv4 starts to use that API, we will have to + * modify this by communicating with the NFSv4 server. + */ + flags2 |= O_NONBLOCK; + } + /* * Ensure we can't write on a read-only share or file. */ @@ -1939,206 +2263,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, request_time = fsp->open_time; } - if (file_existed) { - struct share_mode_entry *batch_entry = NULL; - struct share_mode_entry *exclusive_entry = NULL; - bool got_level2_oplock = false; - bool got_a_none_oplock = false; - struct file_id id; - - struct timespec old_write_time = smb_fname->st.st_ex_mtime; - id = vfs_file_id_from_sbuf(conn, &smb_fname->st); - - lck = get_share_mode_lock_fresh(talloc_tos(), id, - conn->connectpath, - smb_fname, &old_write_time); - if (lck == NULL) { - DEBUG(0, ("Could not get share mode lock\n")); - return NT_STATUS_SHARING_VIOLATION; - } - - /* Get the types we need to examine. */ - find_oplock_types(fsp, - oplock_request, - lck, - &batch_entry, - &exclusive_entry, - &got_level2_oplock, - &got_a_none_oplock); - - /* First pass - send break only on batch oplocks. */ - if ((req != NULL) && - delay_for_batch_oplocks(fsp, - req->mid, - oplock_request, - batch_entry)) { - schedule_defer_open(lck, request_time, req); - TALLOC_FREE(lck); - return NT_STATUS_SHARING_VIOLATION; - } - - /* Use the client requested access mask here, not the one we - * open with. */ - status = open_mode_check(conn, lck, fsp->name_hash, - 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_exclusive_oplocks( - fsp, - req->mid, - oplock_request, - exclusive_entry)) { - schedule_defer_open(lck, request_time, req); - TALLOC_FREE(lck); - 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); - return status; - } - - grant_fsp_oplock_type(fsp, - oplock_request, - got_level2_oplock, - got_a_none_oplock); - - 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 (private_flags & - (NTCREATEX_OPTIONS_PRIVATE_DENY_DOS| - NTCREATEX_OPTIONS_PRIVATE_DENY_FCB)) { - if (req == NULL) { - DEBUG(0, ("DOS open without an SMB " - "request!\n")); - TALLOC_FREE(lck); - return NT_STATUS_INTERNAL_ERROR; - } - - /* Use the client requested access mask here, - * not the one we open with. */ - status = fcb_or_dos_open(req, - conn, - fsp, - smb_fname, - id, - req->smbpid, - req->vuid, - access_mask, - share_access, - create_options); - - if (NT_STATUS_IS_OK(status)) { - TALLOC_FREE(lck); - if (pinfo) { - *pinfo = FILE_WAS_OPENED; - } - 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)) || - !NT_STATUS_IS_OK(smbd_check_access_rights(conn, - smb_fname, 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; - } - 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. */ @@ -2154,135 +2278,318 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, (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, flags|flags2, unx_mode, access_mask, - open_access_mask); + open_access_mask, &new_file_created); - if (!NT_STATUS_IS_OK(fsp_open)) { - TALLOC_FREE(lck); - return fsp_open; - } + if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) { + struct deferred_open_record state; - if (!file_existed) { - struct share_mode_entry *batch_entry = NULL; - struct share_mode_entry *exclusive_entry = NULL; - bool got_level2_oplock = false; - bool got_a_none_oplock = false; - struct timespec old_write_time = smb_fname->st.st_ex_mtime; - struct file_id id; /* - * 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. + * EWOULDBLOCK/EAGAIN maps to NETWORK_BUSY. */ + if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) { + DEBUG(10, ("FIFO busy\n")); + return NT_STATUS_NETWORK_BUSY; + } + if (req == NULL) { + DEBUG(10, ("Internal open busy\n")); + return NT_STATUS_NETWORK_BUSY; + } /* - * 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 . JRA. + * From here on we assume this is an oplock break triggered */ - id = fsp->file_id; + lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); + if (lck == NULL) { + state.delayed_for_oplocks = false; + state.async_open = false; + state.id = fsp->file_id; + defer_open(NULL, request_time, timeval_set(0, 0), + req, &state); + DEBUG(10, ("No share mode lock found after " + "EWOULDBLOCK, retrying sync\n")); + return NT_STATUS_SHARING_VIOLATION; + } - lck = get_share_mode_lock_fresh(talloc_tos(), id, - conn->connectpath, - smb_fname, &old_write_time); + find_oplock_types(fsp, 0, lck, &batch_entry, &exclusive_entry, + &got_level2_oplock, &got_a_none_oplock); - if (lck == NULL) { - DEBUG(0, ("open_file_ntcreate: Could not get share " - "mode lock for %s\n", - smb_fname_str_dbg(smb_fname))); - fd_close(fsp); + if (delay_for_batch_oplocks(fsp, req->mid, 0, batch_entry) || + delay_for_exclusive_oplocks(fsp, req->mid, 0, + exclusive_entry)) { + schedule_defer_open(lck, request_time, req); + TALLOC_FREE(lck); + DEBUG(10, ("Sent oplock break request to kernel " + "oplock holder\n")); return NT_STATUS_SHARING_VIOLATION; } - /* Get the types we need to examine. */ - find_oplock_types(fsp, - oplock_request, - lck, - &batch_entry, - &exclusive_entry, - &got_level2_oplock, - &got_a_none_oplock); + /* + * No oplock from Samba around. Immediately retry with + * a blocking open. + */ + state.delayed_for_oplocks = false; + state.async_open = false; + state.id = lck->data->id; + defer_open(lck, request_time, timeval_set(0, 0), req, &state); + TALLOC_FREE(lck); + DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. " + "Retrying sync\n")); + return NT_STATUS_SHARING_VIOLATION; + } + + if (!NT_STATUS_IS_OK(fsp_open)) { + if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_RETRY)) { + schedule_async_open(request_time, req); + } + TALLOC_FREE(lck); + return fsp_open; + } + + if (file_existed && !check_same_dev_ino(&saved_stat, &smb_fname->st)) { + /* + * The file did exist, but some other (local or NFS) + * process either renamed/unlinked and re-created the + * file with different dev/ino after we walked the path, + * but before we did the open. We could retry the + * open but it's a rare enough case it's easier to + * just fail the open to prevent creating any problems + * in the open file db having the wrong dev/ino key. + */ + TALLOC_FREE(lck); + fd_close(fsp); + DEBUG(1,("open_file_ntcreate: file %s - dev/ino mismatch. " + "Old (dev=0x%llu, ino =0x%llu). " + "New (dev=0x%llu, ino=0x%llu). Failing open " + " with NT_STATUS_ACCESS_DENIED.\n", + smb_fname_str_dbg(smb_fname), + (unsigned long long)saved_stat.st_ex_dev, + (unsigned long long)saved_stat.st_ex_ino, + (unsigned long long)smb_fname->st.st_ex_dev, + (unsigned long long)smb_fname->st.st_ex_ino)); + return NT_STATUS_ACCESS_DENIED; + } + + old_write_time = smb_fname->st.st_ex_mtime; + + /* + * 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 . JRA. + */ + + id = fsp->file_id; - /* First pass - send break only on batch oplocks. */ + lck = get_share_mode_lock(talloc_tos(), id, + conn->connectpath, + smb_fname, &old_write_time); + + if (lck == NULL) { + DEBUG(0, ("open_file_ntcreate: Could not get share " + "mode lock for %s\n", + smb_fname_str_dbg(smb_fname))); + fd_close(fsp); + return NT_STATUS_SHARING_VIOLATION; + } + + /* Get the types we need to examine. */ + find_oplock_types(fsp, + oplock_request, + lck, + &batch_entry, + &exclusive_entry, + &got_level2_oplock, + &got_a_none_oplock); + + /* First pass - send break only on batch oplocks. */ + if ((req != NULL) && + delay_for_batch_oplocks(fsp, + req->mid, + oplock_request, + batch_entry)) { + schedule_defer_open(lck, request_time, req); + TALLOC_FREE(lck); + fd_close(fsp); + return NT_STATUS_SHARING_VIOLATION; + } + + status = open_mode_check(conn, lck, fsp->name_hash, + 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_batch_oplocks(fsp, - req->mid, - oplock_request, - batch_entry)) { + delay_for_exclusive_oplocks( + fsp, + req->mid, + oplock_request, + exclusive_entry)) { schedule_defer_open(lck, request_time, req); TALLOC_FREE(lck); fd_close(fsp); return NT_STATUS_SHARING_VIOLATION; } + } - status = open_mode_check(conn, lck, fsp->name_hash, - access_mask, share_access, - create_options, &file_existed); + if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) { + /* DELETE_PENDING is not deferred for a second */ + TALLOC_FREE(lck); + fd_close(fsp); + return status; + } - 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_exclusive_oplocks( - fsp, - req->mid, - oplock_request, - exclusive_entry)) { - schedule_defer_open(lck, request_time, req); + 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 (private_flags & + (NTCREATEX_OPTIONS_PRIVATE_DENY_DOS| + NTCREATEX_OPTIONS_PRIVATE_DENY_FCB)) { + if (req == NULL) { + DEBUG(0, ("DOS open without an SMB " + "request!\n")); TALLOC_FREE(lck); fd_close(fsp); - return NT_STATUS_SHARING_VIOLATION; + return NT_STATUS_INTERNAL_ERROR; + } + + /* Use the client requested access mask here, + * not the one we open with. */ + status = fcb_or_dos_open(req, + conn, + fsp, + smb_fname, + id, + req->smbpid, + req->vuid, + access_mask, + share_access, + create_options); + + if (NT_STATUS_IS_OK(status)) { + TALLOC_FREE(lck); + if (pinfo) { + *pinfo = FILE_WAS_OPENED; + } + return NT_STATUS_OK; } } - if (!NT_STATUS_IS_OK(status)) { + /* + * 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)) || + !NT_STATUS_IS_OK(smbd_check_access_rights(conn, + smb_fname, + false, + 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.async_open = 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(), + if ((req != NULL) + && !request_timed_out(request_time, + timeout)) { + defer_open(lck, request_time, timeout, req, &state); } - TALLOC_FREE(lck); - fd_close(fsp); - return status; } - grant_fsp_oplock_type(fsp, - oplock_request, - got_level2_oplock, - got_a_none_oplock); - - /* - * We exit this block with the share entry *locked*..... - */ - + TALLOC_FREE(lck); + fd_close(fsp); + 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; + } + return status; } - SMB_ASSERT(lck != NULL); + grant_fsp_oplock_type(fsp, + oplock_request, + got_level2_oplock, + got_a_none_oplock); + + /* + * We have the share entry *locked*..... + */ /* Delete streams if create_disposition requires it */ - if (file_existed && clear_ads && + if (!new_file_created && clear_ads(create_disposition) && !is_ntfs_stream_smb_fname(smb_fname)) { status = delete_all_streams(conn, smb_fname->base_name); if (!NT_STATUS_IS_OK(status)) { @@ -2300,7 +2607,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, the kernel refuses the operations then the kernel is wrong. note that GPFS supports it as well - jmcd */ - if (fsp->fh->fd != -1) { + if (fsp->fh->fd != -1 && lp_kernel_share_modes(SNUM(conn))) { int ret_flock; ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, share_access, access_mask); if(ret_flock == -1 ){ @@ -2313,29 +2620,11 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } /* - * At this point onwards, we can guarentee that the share entry + * At this point onwards, we can guarantee 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 (file_existed && (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, &smb_fname->st)==-1)) { - status = map_nt_error_from_unix(errno); - TALLOC_FREE(lck); - fd_close(fsp); - return status; - } - } - /* * According to Samba4, SEC_FILE_READ_ATTRIBUTE is always granted, * but we don't have to store this - just ignore it on access check. @@ -2358,14 +2647,16 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, if (is_stat_open(open_access_mask)) { fsp->oplock_type = NO_OPLOCK; } + } + if (new_file_created) { + info = FILE_WAS_CREATED; + } else { if (flags2 & O_TRUNC) { info = FILE_WAS_OVERWRITTEN; } else { info = FILE_WAS_OPENED; } - } else { - info = FILE_WAS_CREATED; } if (pinfo) { @@ -2377,7 +2668,8 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * file structs. */ - if (!set_file_oplock(fsp, fsp->oplock_type)) { + status = set_file_oplock(fsp, fsp->oplock_type); + if (!NT_STATUS_IS_OK(status)) { /* * Could not get the kernel oplock or there are byte-range * locks on the file. @@ -2406,13 +2698,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, fsp->initial_delete_on_close = True; } - if (info == FILE_WAS_OVERWRITTEN - || info == FILE_WAS_CREATED - || info == FILE_WAS_SUPERSEDED) { - new_file_created = True; - } - - if (new_file_created) { + if (info != FILE_WAS_OPENED) { /* Files should be initially set as archive */ if (lp_map_archive(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) { @@ -2440,7 +2726,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * selected. */ - if (!posix_open && !file_existed && !def_acl) { + if (!posix_open && new_file_created && !def_acl) { int saved_errno = errno; /* We might get ENOSYS in the next * call.. */ @@ -2535,9 +2821,9 @@ static NTSTATUS mkdir_internal(connection_struct *conn, bool need_re_stat = false; uint32_t access_mask = SEC_DIR_ADD_SUBDIR; - if(access_mask & ~(conn->share_access)) { + if (!CAN_WRITE(conn) || (access_mask & ~(conn->share_access))) { DEBUG(5,("mkdir_internal: failing share access " - "%s\n", lp_servicename(SNUM(conn)))); + "%s\n", lp_servicename(talloc_tos(), SNUM(conn)))); return NT_STATUS_ACCESS_DENIED; } @@ -2555,8 +2841,7 @@ static NTSTATUS mkdir_internal(connection_struct *conn, status = check_parent_access(conn, smb_dname, - access_mask, - &parent_dir); + access_mask); if(!NT_STATUS_IS_OK(status)) { DEBUG(5,("mkdir_internal: check_parent_access " "on directory %s for path %s returned %s\n", @@ -2637,22 +2922,6 @@ static NTSTATUS mkdir_internal(connection_struct *conn, return NT_STATUS_OK; } -/**************************************************************************** - Ensure we didn't get symlink raced on opening a directory. -****************************************************************************/ - -bool check_same_stat(const SMB_STRUCT_STAT *sbuf1, - const SMB_STRUCT_STAT *sbuf2) -{ - if (sbuf1->st_ex_uid != sbuf2->st_ex_uid || - sbuf1->st_ex_gid != sbuf2->st_ex_gid || - sbuf1->st_ex_dev != sbuf2->st_ex_dev || - sbuf1->st_ex_ino != sbuf2->st_ex_ino) { - return false; - } - return true; -} - /**************************************************************************** Open a directory from an NT SMB call. ****************************************************************************/ @@ -2681,8 +2950,10 @@ static NTSTATUS open_directory(connection_struct *conn, return NT_STATUS_NOT_A_DIRECTORY; } - /* Ensure we have a directory attribute. */ - file_attributes |= FILE_ATTRIBUTE_DIRECTORY; + if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS)) { + /* Ensure we have a directory attribute. */ + file_attributes |= FILE_ATTRIBUTE_DIRECTORY; + } DEBUG(5,("open_directory: opening directory %s, access_mask = 0x%x, " "share_access = 0x%x create_options = 0x%x, " @@ -2694,7 +2965,7 @@ static NTSTATUS open_directory(connection_struct *conn, (unsigned int)create_disposition, (unsigned int)file_attributes)); - status = smbd_calculate_access_mask(conn, smb_dname, + status = smbd_calculate_access_mask(conn, smb_dname, false, access_mask, &access_mask); if (!NT_STATUS_IS_OK(status)) { DEBUG(10, ("open_directory: smbd_calculate_access_mask " @@ -2800,7 +3071,10 @@ static NTSTATUS open_directory(connection_struct *conn, } if (info == FILE_WAS_OPENED) { - status = smbd_check_access_rights(conn, smb_dname, access_mask); + status = smbd_check_access_rights(conn, + smb_dname, + false, + access_mask); if (!NT_STATUS_IS_OK(status)) { DEBUG(10, ("open_directory: smbd_check_access_rights on " "file %s failed with %s\n", @@ -2819,7 +3093,6 @@ static NTSTATUS open_directory(connection_struct *conn, * Setup the files_struct for it. */ - fsp->mode = smb_dname->st.st_ex_mode; fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_dname->st); fsp->vuid = req ? req->vuid : UID_FIELD_INVALID; fsp->file_pid = req ? req->smbpid : 0; @@ -2879,9 +3152,9 @@ static NTSTATUS open_directory(connection_struct *conn, return NT_STATUS_ACCESS_DENIED; } - lck = get_share_mode_lock_fresh(talloc_tos(), fsp->file_id, - conn->connectpath, smb_dname, - &mtimespec); + lck = get_share_mode_lock(talloc_tos(), fsp->file_id, + conn->connectpath, smb_dname, + &mtimespec); if (lck == NULL) { DEBUG(0, ("open_directory: Could not get share mode lock for " @@ -3023,8 +3296,8 @@ void msg_file_was_renamed(struct messaging_context *msg, 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_str_dbg(fsp), + DEBUG(10,("msg_file_was_renamed: renaming file %s from %s -> %s\n", + fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), smb_fname_str_dbg(smb_fname))); status = fsp_set_smb_fname(fsp, smb_fname); if (!NT_STATUS_IS_OK(status)) { @@ -3036,10 +3309,10 @@ void msg_file_was_renamed(struct messaging_context *msg, 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", + "%s from %s -> %s\n", fsp->conn->connectpath, sharepath, - fsp->fnum, + fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), smb_fname_str_dbg(smb_fname))); } @@ -3174,7 +3447,7 @@ NTSTATUS open_streams_for_delete(connection_struct *conn, static NTSTATUS inherit_new_acl(files_struct *fsp) { - TALLOC_CTX *ctx = talloc_tos(); + TALLOC_CTX *frame = talloc_stackframe(); char *parent_name = NULL; struct security_descriptor *parent_desc = NULL; NTSTATUS status = NT_STATUS_OK; @@ -3186,15 +3459,18 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) bool inheritable_components = false; size_t size = 0; - if (!parent_dirname(ctx, fsp->fsp_name->base_name, &parent_name, NULL)) { + if (!parent_dirname(frame, fsp->fsp_name->base_name, &parent_name, NULL)) { + TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } status = SMB_VFS_GET_NT_ACL(fsp->conn, - parent_name, - (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL), - &parent_desc); + parent_name, + (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL), + frame, + &parent_desc); if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); return status; } @@ -3202,6 +3478,7 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) fsp->is_directory); if (!inheritable_components && !inherit_owner) { + TALLOC_FREE(frame); /* Nothing to inherit and not setting owner. */ return NT_STATUS_OK; } @@ -3227,7 +3504,7 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) group_sid = &fsp->conn->session_info->security_token->sids[PRIMARY_GROUP_SID_INDEX]; } - status = se_create_child_secdesc(ctx, + status = se_create_child_secdesc(frame, &psd, &size, parent_desc, @@ -3235,6 +3512,7 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) group_sid, fsp->is_directory); if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); return status; } @@ -3265,6 +3543,7 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) if (inherit_owner) { unbecome_root(); } + TALLOC_FREE(frame); return status; } @@ -3446,14 +3725,20 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, goto fail; } - /* - * We're opening the stream element of a base_fsp - * we already opened. Set up the base_fsp pointer. - */ if (base_fsp) { + /* + * We're opening the stream element of a + * base_fsp we already opened. Set up the + * base_fsp pointer. + */ fsp->base_fsp = base_fsp; } + if (allocation_size) { + fsp->initial_allocation_size = smb_roundup(fsp->conn, + allocation_size); + } + status = open_file_ntcreate(conn, req, access_mask, @@ -3538,6 +3823,8 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, fsp->initial_allocation_size = smb_roundup( fsp->conn, (uint64_t)fsp->fsp_name->st.st_ex_size); } + } else { + fsp->initial_allocation_size = 0; } if ((info == FILE_WAS_CREATED) && lp_nt_acl_support(SNUM(conn)) && @@ -3561,15 +3848,11 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, fsp->access_mask = FILE_GENERIC_ALL; - /* Convert all the generic bits. */ - security_acl_map_generic(sd->dacl, &file_generic_mapping); - security_acl_map_generic(sd->sacl, &file_generic_mapping); - if (sec_info_sent & (SECINFO_OWNER| SECINFO_GROUP| SECINFO_DACL| SECINFO_SACL)) { - status = SMB_VFS_FSET_NT_ACL(fsp, sec_info_sent, sd); + status = set_sd(fsp, sd, sec_info_sent); } fsp->access_mask = saved_access_mask;