diff options
Diffstat (limited to 'source3/smbd/nttrans.c')
-rw-r--r-- | source3/smbd/nttrans.c | 1217 |
1 files changed, 1056 insertions, 161 deletions
diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c index 70023e2407..9615c5ada2 100644 --- a/source3/smbd/nttrans.c +++ b/source3/smbd/nttrans.c @@ -37,17 +37,14 @@ static void remove_pending_change_notify_requests_by_mid(int mid); static char *known_nt_pipes[] = { "\\LANMAN", "\\srvsvc", - "\\svcctl", "\\samr", "\\wkssvc", - "\\browser", "\\NETLOGON", "\\ntlsa", "\\ntsvcs", "\\lsass", "\\lsarpc", "\\winreg", - "\\spoolss", NULL }; @@ -58,7 +55,7 @@ static char *known_nt_pipes[] = { HACK ! Always assumes smb_setup field is zero. ****************************************************************************/ -static int send_nt_replies(char *outbuf, int bufsize, char *params, +static int send_nt_replies(char *inbuf, char *outbuf, int bufsize, uint32 nt_error, char *params, int paramsize, char *pdata, int datasize) { extern int max_send; @@ -78,6 +75,13 @@ static int send_nt_replies(char *outbuf, int bufsize, char *params, set_message(outbuf,18,0,True); + if(nt_error != 0) { + /* NT Error. */ + SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2) | FLAGS2_32_BIT_ERROR_CODES); + + ERROR(0,nt_error); + } + /* * If there genuinely are no parameters or data to send just send * the empty packet. @@ -355,18 +359,25 @@ static int map_create_disposition( uint32 create_disposition) Utility function to map share modes. ****************************************************************************/ -static int map_share_mode( char *fname, uint32 desired_access, uint32 share_access, uint32 file_attributes) +static int map_share_mode( BOOL *pstat_open_only, char *fname, + uint32 desired_access, uint32 share_access, uint32 file_attributes) { int smb_open_mode = -1; - switch( desired_access & (FILE_READ_DATA|FILE_WRITE_DATA) ) { + *pstat_open_only = False; + + switch( desired_access & (FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA) ) { case FILE_READ_DATA: smb_open_mode = DOS_OPEN_RDONLY; break; case FILE_WRITE_DATA: + case FILE_APPEND_DATA: + case FILE_WRITE_DATA|FILE_APPEND_DATA: smb_open_mode = DOS_OPEN_WRONLY; break; case FILE_READ_DATA|FILE_WRITE_DATA: + case FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA: + case FILE_READ_DATA|FILE_APPEND_DATA: smb_open_mode = DOS_OPEN_RDWR; break; } @@ -386,8 +397,12 @@ static int map_share_mode( char *fname, uint32 desired_access, uint32 share_acce */ if (smb_open_mode == -1) { + if(desired_access == WRITE_DAC_ACCESS || desired_access == READ_CONTROL_ACCESS) + *pstat_open_only = True; + if(desired_access & (DELETE_ACCESS|WRITE_DAC_ACCESS|WRITE_OWNER_ACCESS| FILE_EXECUTE|FILE_READ_ATTRIBUTES| + FILE_READ_EA|FILE_WRITE_EA|SYSTEM_SECURITY_ACCESS| FILE_WRITE_ATTRIBUTES|READ_CONTROL_ACCESS)) smb_open_mode = DOS_OPEN_RDONLY; else { @@ -464,11 +479,40 @@ static time_t fail_time; void fail_next_srvsvc_open(void) { + /* Check client is WinNT proper; Win2K doesn't like Jeremy's hack - matty */ + if (get_remote_arch() != RA_WINNT) + return; + fail_next_srvsvc = True; fail_time = time(NULL); DEBUG(10,("fail_next_srvsvc_open: setting up timeout close of \\srvsvc pipe for print fix.\n")); } +/* + * HACK alert.... see above - JRA. + */ + +BOOL should_fail_next_srvsvc_open(const char *pipename) +{ + + DEBUG(10,("should_fail_next_srvsvc_open: fail = %d, pipe = %s\n", + (int)fail_next_srvsvc, pipename)); + + if(fail_next_srvsvc && (time(NULL) > fail_time + HACK_FAIL_TIME)) { + fail_next_srvsvc = False; + fail_time = (time_t)0; + DEBUG(10,("should_fail_next_srvsvc_open: End of timeout close of \\srvsvc pipe for print fix.\n")); + } + + if(fail_next_srvsvc && strequal(pipename, "srvsvc")) { + fail_next_srvsvc = False; + DEBUG(10,("should_fail_next_srvsvc_open: Deliberately failing open of \\srvsvc pipe for print fix.\n")); + return True; + } + return False; +} + + /**************************************************************************** Reply to an NT create and X call on a pipe. ****************************************************************************/ @@ -487,32 +531,15 @@ static int nt_open_pipe(char *fname, connection_struct *conn, if( strequal(fname,known_nt_pipes[i])) break; - /* - * HACK alert.... see above - JRA. - */ - - if(fail_next_srvsvc && (time(NULL) > fail_time + HACK_FAIL_TIME)) { - fail_next_srvsvc = False; - fail_time = (time_t)0; - DEBUG(10,("nt_open_pipe: End of timeout close of \\srvsvc pipe for print fix.\n")); - } - - if(fail_next_srvsvc && strequal(fname, "\\srvsvc")) { - fail_next_srvsvc = False; - DEBUG(10,("nt_open_pipe: Deliberately failing open of \\srvsvc pipe for print fix.\n")); - return(ERROR(ERRSRV,ERRaccess)); - } - - /* - * End hack alert.... see above - JRA. - */ - if ( known_nt_pipes[i] == NULL ) return(ERROR(ERRSRV,ERRaccess)); /* Strip \\ off the name. */ fname++; + if(should_fail_next_srvsvc_open(fname)) + return (ERROR(ERRSRV,ERRaccess)); + DEBUG(3,("nt_open_pipe: Known pipe %s opening.\n", fname)); p = open_rpc_pipe_p(fname, conn, vuid); @@ -547,7 +574,7 @@ int reply_ntcreate_and_X(connection_struct *conn, /* Breakout the oplock request bits so we can set the reply bits separately. */ int oplock_request = 0; - mode_t unixmode; + mode_t unixmode; int pnum = -1; int fmode=0,rmode=0; SMB_OFF_T file_len = 0; @@ -556,6 +583,7 @@ int reply_ntcreate_and_X(connection_struct *conn, BOOL bad_path = False; files_struct *fsp=NULL; char *p = NULL; + BOOL stat_open_only = False; /* * We need to construct the open_and_X ofun value from the @@ -576,8 +604,23 @@ int reply_ntcreate_and_X(connection_struct *conn, files_struct *dir_fsp = file_fsp(inbuf,smb_ntcreate_RootDirectoryFid); size_t dir_name_len; - if(!dir_fsp || !dir_fsp->is_directory) + if(!dir_fsp) + return(ERROR(ERRDOS,ERRbadfid)); + + if(!dir_fsp->is_directory) { + /* + * Check to see if this is a mac fork of some kind. + */ + + get_filename(&fname[0], inbuf, smb_buf(inbuf)-inbuf, + smb_buflen(inbuf),fname_len); + + if( fname[0] == ':') { + SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); + return(ERROR(0, 0xc0000000|NT_STATUS_OBJECT_PATH_NOT_FOUND)); + } return(ERROR(ERRDOS,ERRbadfid)); + } /* * Copy in the base directory name. @@ -600,26 +643,17 @@ int reply_ntcreate_and_X(connection_struct *conn, get_filename(&fname[dir_name_len], inbuf, smb_buf(inbuf)-inbuf, smb_buflen(inbuf),fname_len); -#if 0 - StrnCpy(&fname[dir_name_len], smb_buf(inbuf),fname_len); - fname[dir_name_len+fname_len] = '\0'; -#endif } else { get_filename(fname, inbuf, smb_buf(inbuf)-inbuf, smb_buflen(inbuf),fname_len); - -#if 0 - StrnCpy(fname,smb_buf(inbuf),fname_len); - fname[fname_len] = '\0'; -#endif } /* If it's an IPC, use the pipe handler. */ - if (IS_IPC(conn) && lp_nt_pipe_support() && lp_security() != SEC_SHARE) - { + if (IS_IPC(conn) && lp_nt_pipe_support()) { + int ret = nt_open_pipe(fname, conn, inbuf, outbuf, &pnum); if(ret != 0) return ret; @@ -654,11 +688,13 @@ int reply_ntcreate_and_X(connection_struct *conn, * desired access and the share access. */ - if((smb_open_mode = map_share_mode(fname, desired_access, + if((smb_open_mode = map_share_mode(&stat_open_only, fname, desired_access, share_access, - file_attributes)) == -1) { + file_attributes)) == -1) return(ERROR(ERRDOS,ERRbadaccess)); - } + + oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0; + oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0; /* * Ordinary file or directory. @@ -670,11 +706,7 @@ int reply_ntcreate_and_X(connection_struct *conn, set_posix_case_semantics(file_attributes); - if (!unix_dfs_convert(fname,conn,0,&bad_path,NULL)) - { - SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); - return(ERROR(0, 0xc0000000|NT_STATUS_PATH_NOT_COVERED)); - } + unix_convert(fname,conn,0,&bad_path,NULL); fsp = file_new(); if (!fsp) { @@ -696,9 +728,6 @@ int reply_ntcreate_and_X(connection_struct *conn, unixmode = unix_mode(conn,smb_attr | aARCH); - oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0; - oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0; - /* * If it's a request for a directory open, deal with it separately. */ @@ -706,8 +735,8 @@ int reply_ntcreate_and_X(connection_struct *conn, if(create_options & FILE_DIRECTORY_FILE) { oplock_request = 0; - open_directory(fsp, conn, fname, smb_ofun, unixmode, - &smb_action); + open_directory(fsp, conn, fname, smb_ofun, + unixmode, &smb_action); restore_case_semantics(file_attributes); @@ -733,13 +762,12 @@ int reply_ntcreate_and_X(connection_struct *conn, * before issuing an oplock break request to * our client. JRA. */ - open_file_shared(fsp,conn,fname,smb_open_mode, - smb_ofun,unixmode, - oplock_request,&rmode,&smb_action); + open_file_shared(fsp,conn,fname,smb_open_mode, + smb_ofun,unixmode, oplock_request,&rmode,&smb_action); if (!fsp->open) { - /* We cheat here. The only case we - * care about is a directory rename, + /* We cheat here. There are two cases we + * care about. One is a directory rename, * where the NT client will attempt to * open the source directory for * DELETE access. Note that when the @@ -752,21 +780,54 @@ int reply_ntcreate_and_X(connection_struct *conn, * will generate an EISDIR error, so * we can catch this here and open a * pseudo handle that is flagged as a - * directory. JRA. */ + * directory. The second is an open + * for a permissions read only, which + * we handle in the open_file_stat case. JRA. + */ if(errno == EISDIR) { + + /* + * Fail the open if it was explicitly a non-directory file. + */ + + if (create_options & FILE_NON_DIRECTORY_FILE) { + file_free(fsp); + restore_case_semantics(file_attributes); + SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); + return(ERROR(0, 0xc0000000|NT_STATUS_FILE_IS_A_DIRECTORY)); + } + oplock_request = 0; - - open_directory(fsp, conn, fname, - smb_ofun, unixmode, - &smb_action); + open_directory(fsp, conn, fname, smb_ofun, unixmode, &smb_action); if(!fsp->open) { file_free(fsp); restore_case_semantics(file_attributes); return(UNIXERROR(ERRDOS,ERRnoaccess)); } +#ifdef EROFS + } else if (((errno == EACCES) || (errno == EROFS)) && stat_open_only) { +#else /* !EROFS */ + } else if (errno == EACCES && stat_open_only) { +#endif + /* + * We couldn't open normally and all we want + * are the permissions. Try and do a stat open. + */ + + oplock_request = 0; + + open_file_stat(fsp,conn,fname,smb_open_mode,&sbuf,&smb_action); + + if(!fsp->open) { + file_free(fsp); + restore_case_semantics(file_attributes); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } else { + if((errno == ENOENT) && bad_path) { unix_ERR_class = ERRDOS; unix_ERR_code = ERRbadpath; @@ -782,15 +843,13 @@ int reply_ntcreate_and_X(connection_struct *conn, } if(fsp->is_directory) { - if(fsp->conn->vfs_ops.stat(dos_to_unix(fsp->fsp_name, False), - &sbuf) != 0) { - close_directory(fsp); + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + close_file(fsp,True); restore_case_semantics(file_attributes); return(ERROR(ERRDOS,ERRnoaccess)); } } else { - if (fsp->conn->vfs_ops.fstat(fsp->fd_ptr->fd,&sbuf) - != 0) { + if (!fsp->stat_open && sys_fstat(fsp->fd_ptr->fd,&sbuf) != 0) { close_file(fsp,False); restore_case_semantics(file_attributes); return(ERROR(ERRDOS,ERRnoaccess)); @@ -817,9 +876,9 @@ int reply_ntcreate_and_X(connection_struct *conn, if (oplock_request && lp_fake_oplocks(SNUM(conn))) smb_action |= EXTENDED_OPLOCK_GRANTED; - if(oplock_request && fsp->granted_oplock) + if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) smb_action |= EXTENDED_OPLOCK_GRANTED; - + set_message(outbuf,34,0,True); p = outbuf + smb_vwv2; @@ -828,8 +887,14 @@ int reply_ntcreate_and_X(connection_struct *conn, * Currently as we don't support level II oplocks we just report * exclusive & batch here. */ + + if (smb_action & EXTENDED_OPLOCK_GRANTED) + SCVAL(p,0, BATCH_OPLOCK_RETURN); + else if (LEVEL_II_OPLOCK_TYPE(fsp->oplock_type)) + SCVAL(p,0, LEVEL_II_OPLOCK_RETURN); + else + SCVAL(p,0,NO_OPLOCK_RETURN); - SCVAL(p,0, (smb_action & EXTENDED_OPLOCK_GRANTED ? 1 : 0)); p++; SSVAL(p,0,fsp->fnum); p += 2; @@ -861,6 +926,7 @@ int reply_ntcreate_and_X(connection_struct *conn, /**************************************************************************** Reply to a NT_TRANSACT_CREATE call (needs to process SD's). ****************************************************************************/ + static int call_nt_transact_create(connection_struct *conn, char *inbuf, char *outbuf, int length, int bufsize, @@ -893,6 +959,7 @@ static int call_nt_transact_create(connection_struct *conn, BOOL bad_path = False; files_struct *fsp = NULL; char *p = NULL; + BOOL stat_open_only = False; /* * We need to construct the open_and_X ofun value from the @@ -914,8 +981,24 @@ static int call_nt_transact_create(connection_struct *conn, files_struct *dir_fsp = file_fsp(params,4); size_t dir_name_len; - if(!dir_fsp || !dir_fsp->is_directory) + if(!dir_fsp) + return(ERROR(ERRDOS,ERRbadfid)); + + if(!dir_fsp->is_directory) { + /* + * Check to see if this is a mac fork of some kind. + */ + + StrnCpy(fname,params+53,fname_len); + fname[fname_len] = '\0'; + + if( fname[0] == ':') { + SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); + return(ERROR(0, 0xc0000000|NT_STATUS_OBJECT_PATH_NOT_FOUND)); + } + return(ERROR(ERRDOS,ERRbadfid)); + } /* * Copy in the base directory name. @@ -951,17 +1034,26 @@ static int call_nt_transact_create(connection_struct *conn, return ret; smb_action = FILE_WAS_OPENED; } else { + + /* + * Now contruct the smb_open_mode value from the desired access + * and the share access. + */ + + if((smb_open_mode = map_share_mode( &stat_open_only, fname, desired_access, + share_access, file_attributes)) == -1) + return(ERROR(ERRDOS,ERRbadaccess)); + + oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0; + oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0; + /* * Check if POSIX semantics are wanted. */ set_posix_case_semantics(file_attributes); - if (!unix_dfs_convert(fname,conn,0,&bad_path,NULL)) - { - SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); - return(ERROR(0, 0xc0000000|NT_STATUS_PATH_NOT_COVERED)); - } + unix_convert(fname,conn,0,&bad_path,NULL); fsp = file_new(); if (!fsp) { @@ -983,17 +1075,6 @@ static int call_nt_transact_create(connection_struct *conn, unixmode = unix_mode(conn,smb_attr | aARCH); - oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0; - oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0; - - /* - * Now contruct the smb_open_mode value from the desired access - * and the share access. - */ - - if((smb_open_mode = map_share_mode( fname, desired_access, share_access, file_attributes)) == -1) - return(ERROR(ERRDOS,ERRbadaccess)); - /* * If it's a request for a directory open, deal with it separately. */ @@ -1012,37 +1093,96 @@ static int call_nt_transact_create(connection_struct *conn, if(!fsp->open) { file_free(fsp); + restore_case_semantics(file_attributes); return(UNIXERROR(ERRDOS,ERRnoaccess)); } + + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + close_file(fsp,True); + restore_case_semantics(file_attributes); + return(ERROR(ERRDOS,ERRnoaccess)); + } + } else { /* * Ordinary file case. */ - open_file_shared(fsp,conn,fname,smb_open_mode,smb_ofun, - unixmode,oplock_request,&rmode,&smb_action); + open_file_shared(fsp,conn,fname,smb_open_mode,smb_ofun,unixmode, + oplock_request,&rmode,&smb_action); if (!fsp->open) { - if((errno == ENOENT) && bad_path) { - unix_ERR_class = ERRDOS; - unix_ERR_code = ERRbadpath; - } - file_free(fsp); - restore_case_semantics(file_attributes); + if(errno == EISDIR) { - return(UNIXERROR(ERRDOS,ERRnoaccess)); - } - - if (fsp->conn->vfs_ops.fstat(fsp->fd_ptr->fd,&sbuf) != 0) { - close_file(fsp,False); + /* + * Fail the open if it was explicitly a non-directory file. + */ - restore_case_semantics(file_attributes); + if (create_options & FILE_NON_DIRECTORY_FILE) { + file_free(fsp); + restore_case_semantics(file_attributes); + SSVAL(outbuf, smb_flg2, FLAGS2_32_BIT_ERROR_CODES); + return(ERROR(0, 0xc0000000|NT_STATUS_FILE_IS_A_DIRECTORY)); + } + + oplock_request = 0; + open_directory(fsp, conn, fname, smb_ofun, unixmode, &smb_action); + + if(!fsp->open) { + file_free(fsp); + restore_case_semantics(file_attributes); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } +#ifdef EROFS + } else if (((errno == EACCES) || (errno == EROFS)) && stat_open_only) { +#else /* !EROFS */ + } else if (errno == EACCES && stat_open_only) { +#endif - return(ERROR(ERRDOS,ERRnoaccess)); + /* + * We couldn't open normally and all we want + * are the permissions. Try and do a stat open. + */ + + oplock_request = 0; + + open_file_stat(fsp,conn,fname,smb_open_mode,&sbuf,&smb_action); + + if(!fsp->open) { + file_free(fsp); + restore_case_semantics(file_attributes); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } else { + + if((errno == ENOENT) && bad_path) { + unix_ERR_class = ERRDOS; + unix_ERR_code = ERRbadpath; + } + file_free(fsp); + + restore_case_semantics(file_attributes); + + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } } + if(fsp->is_directory) { + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + close_file(fsp,True); + restore_case_semantics(file_attributes); + return(ERROR(ERRDOS,ERRnoaccess)); + } + } else { + if (!fsp->stat_open && sys_fstat(fsp->fd_ptr->fd,&sbuf) != 0) { + close_file(fsp,False); + restore_case_semantics(file_attributes); + return(ERROR(ERRDOS,ERRnoaccess)); + } + } + file_len = sbuf.st_size; fmode = dos_mode(conn,fname,&sbuf); if(fmode == 0) @@ -1063,7 +1203,7 @@ static int call_nt_transact_create(connection_struct *conn, if (oplock_request && lp_fake_oplocks(SNUM(conn))) smb_action |= EXTENDED_OPLOCK_GRANTED; - if(oplock_request && fsp->granted_oplock) + if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) smb_action |= EXTENDED_OPLOCK_GRANTED; } } @@ -1075,8 +1215,16 @@ static int call_nt_transact_create(connection_struct *conn, if(params == NULL) return(ERROR(ERRDOS,ERRnomem)); + memset((char *)params,'\0',69); + p = params; - SCVAL(p,0, (smb_action & EXTENDED_OPLOCK_GRANTED ? 1 : 0)); + if (smb_action & EXTENDED_OPLOCK_GRANTED) + SCVAL(p,0, BATCH_OPLOCK_RETURN); + else if (LEVEL_II_OPLOCK_TYPE(fsp->oplock_type)) + SCVAL(p,0, LEVEL_II_OPLOCK_RETURN); + else + SCVAL(p,0,NO_OPLOCK_RETURN); + p += 2; if (IS_IPC(conn)) { SSVAL(p,0,pnum); @@ -1119,7 +1267,7 @@ static int call_nt_transact_create(connection_struct *conn, } /* Send the required number of replies */ - send_nt_replies(outbuf, bufsize, params, 69, *ppdata, 0); + send_nt_replies(inbuf, outbuf, bufsize, 0, params, 69, *ppdata, 0); return -1; } @@ -1156,6 +1304,7 @@ int reply_nttranss(connection_struct *conn, /**************************************************************************** Reply to an NT transact rename command. ****************************************************************************/ + static int call_nt_transact_rename(connection_struct *conn, char *inbuf, char *outbuf, int length, int bufsize, @@ -1179,7 +1328,7 @@ static int call_nt_transact_rename(connection_struct *conn, /* * Rename was successful. */ - send_nt_replies(outbuf, bufsize, NULL, 0, NULL, 0); + send_nt_replies(inbuf, outbuf, bufsize, 0, NULL, 0, NULL, 0); DEBUG(3,("nt transact rename from = %s, to = %s succeeded.\n", fsp->fsp_name, new_name)); @@ -1191,6 +1340,18 @@ static int call_nt_transact_rename(connection_struct *conn, } /**************************************************************************** + This is the structure to keep the information needed to + determine if a directory has changed. +*****************************************************************************/ + +typedef struct { + time_t modify_time; /* Info from the directory we're monitoring. */ + time_t status_time; /* Info from the directory we're monitoring. */ + time_t total_time; /* Total time of all directory entries - don't care if it wraps. */ + unsigned int num_entries; /* Zero or the number of files in the directory. */ +} change_hash_data; + +/**************************************************************************** This is the structure to queue to implement NT change notify. It consists of smb_size bytes stored from the transact command (to keep the mid, tid etc around). @@ -1201,9 +1362,9 @@ typedef struct { ubi_slNode msg_next; files_struct *fsp; connection_struct *conn; + uint32 flags; time_t next_check_time; - time_t modify_time; /* Info from the directory we're monitoring. */ - time_t status_time; /* Info from the directory we're monitoring. */ + change_hash_data change_data; char request_buf[smb_size]; } change_notify_buf; @@ -1244,8 +1405,92 @@ static void change_notify_reply_packet(char *inbuf, int error_class, uint32 erro } /**************************************************************************** + Create the hash we will use to determine if the contents changed. +*****************************************************************************/ + +static BOOL create_directory_notify_hash( change_notify_buf *cnbp, change_hash_data *change_data) +{ + SMB_STRUCT_STAT st; + files_struct *fsp = cnbp->fsp; + + memset((char *)change_data, '\0', sizeof(change_data)); + + /* + * Store the current timestamp on the directory we are monitoring. + */ + + if(dos_stat(fsp->fsp_name, &st) < 0) { + DEBUG(0,("create_directory_notify_hash: Unable to stat name = %s. \ +Error was %s\n", fsp->fsp_name, strerror(errno) )); + return False; + } + + change_data->modify_time = st.st_mtime; + change_data->status_time = st.st_ctime; + + /* + * If we are to watch for changes that are only stored + * in inodes of files, not in the directory inode, we must + * scan the directory and produce a unique identifier with + * which we can determine if anything changed. We use the + * modify and change times from all the files in the + * directory, added together (ignoring wrapping if it's + * larger than the max time_t value). + */ + + if(cnbp->flags & (FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE)) { + pstring full_name; + char *p; + char *fname; + size_t remaining_len; + size_t fullname_len; + void *dp = OpenDir(cnbp->conn, fsp->fsp_name, True); + + if(dp == NULL) { + DEBUG(0,("create_directory_notify_hash: Unable to open directory = %s. \ +Error was %s\n", fsp->fsp_name, strerror(errno) )); + return False; + } + + change_data->num_entries = 0; + + pstrcpy(full_name, fsp->fsp_name); + pstrcat(full_name, "/"); + + fullname_len = strlen(full_name); + remaining_len = sizeof(full_name) - fullname_len - 1; + p = &full_name[fullname_len]; + + while ((fname = ReadDirName(dp))) { + if(strequal(fname, ".") || strequal(fname, "..")) + continue; + + change_data->num_entries++; + safe_strcpy( p, fname, remaining_len); + + memset(&st, '\0', sizeof(st)); + + /* + * Do the stat - but ignore errors. + */ + + if(dos_stat(full_name, &st) < 0) { + DEBUG(5,("create_directory_notify_hash: Unable to stat content file = %s. \ +Error was %s\n", fsp->fsp_name, strerror(errno) )); + } + change_data->total_time += (st.st_mtime + st.st_ctime); + } + + CloseDir(dp); + } + + return True; +} + +/**************************************************************************** Delete entries by fnum from the change notify pending queue. *****************************************************************************/ + void remove_pending_change_notify_requests_by_fid(files_struct *fsp) { change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); @@ -1274,7 +1519,34 @@ static void remove_pending_change_notify_requests_by_mid(int mid) while(cnbp != NULL) { if(SVAL(cnbp->request_buf,smb_mid) == mid) { - change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_CANCELLED); + change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED); + free((char *)ubi_slRemNext( &change_notify_queue, prev)); + cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); + continue; + } + + prev = cnbp; + cnbp = (change_notify_buf *)ubi_slNext(cnbp); + } +} + +/**************************************************************************** + Delete entries by filename and cnum from the change notify pending queue. + Always send reply. +*****************************************************************************/ + +void remove_pending_change_notify_requests_by_filename(files_struct *fsp) +{ + change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); + change_notify_buf *prev = NULL; + + while(cnbp != NULL) { + /* + * We know it refers to the same directory if the connection number and + * the filename are identical. + */ + if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) { + change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED); free((char *)ubi_slRemNext( &change_notify_queue, prev)); cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); continue; @@ -1287,18 +1559,20 @@ static void remove_pending_change_notify_requests_by_mid(int mid) /**************************************************************************** Process the change notify queue. Note that this is only called as root. + Returns True if there are still outstanding change notify requests on the + queue. *****************************************************************************/ -void process_pending_change_notify_queue(time_t t) +BOOL process_pending_change_notify_queue(time_t t) { change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); change_notify_buf *prev = NULL; if(cnbp == NULL) - return; + return False; if(cnbp->next_check_time >= t) - return; + return True; /* * It's time to check. Go through the queue and see if @@ -1306,8 +1580,7 @@ void process_pending_change_notify_queue(time_t t) */ while((cnbp != NULL) && (cnbp->next_check_time <= t)) { - SMB_STRUCT_STAT st; - files_struct *fsp = cnbp->fsp; + change_hash_data change_data; connection_struct *conn = cnbp->conn; uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : SVAL(cnbp->request_buf,smb_uid); @@ -1343,9 +1616,9 @@ void process_pending_change_notify_queue(time_t t) continue; } - if(fsp->conn->vfs_ops.stat(dos_to_unix(fsp->fsp_name, False), &st) < 0) { - DEBUG(0,("process_pending_change_notify_queue: Unable to stat directory %s. \ -Error was %s.\n", fsp->fsp_name, strerror(errno) )); + if(!create_directory_notify_hash( cnbp, &change_data)) { + DEBUG(0,("process_pending_change_notify_queue: Unable to create change data for \ +directory %s\n", cnbp->fsp->fsp_name )); /* * Remove the entry and return an error to the client. */ @@ -1356,13 +1629,12 @@ Error was %s.\n", fsp->fsp_name, strerror(errno) )); continue; } - if(cnbp->modify_time != st.st_mtime || - cnbp->status_time != st.st_ctime) { + if(memcmp( (char *)&cnbp->change_data, (char *)&change_data, sizeof(change_data))) { /* * Remove the entry and return a change notify to the client. */ - DEBUG(5,("process_pending_change_notify_queue: directory name = %s changed\n", - fsp->fsp_name )); + DEBUG(5,("process_pending_change_notify_queue: directory name = %s changed.\n", + cnbp->fsp->fsp_name )); change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR); free((char *)ubi_slRemNext( &change_notify_queue, prev)); cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); @@ -1378,22 +1650,34 @@ Error was %s.\n", fsp->fsp_name, strerror(errno) )); prev = cnbp; cnbp = (change_notify_buf *)ubi_slNext(cnbp); } + + return (cnbp != NULL); +} + +/**************************************************************************** + Return true if there are pending change notifies. +****************************************************************************/ + +BOOL change_notifies_pending(void) +{ + change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); + return (cnbp != NULL); } /**************************************************************************** Reply to a notify change - queue the request and don't allow a directory to be opened. ****************************************************************************/ + static int call_nt_transact_notify_change(connection_struct *conn, - char *inbuf, char *outbuf, int length, + char *inbuf, char *outbuf, int length, int bufsize, char **ppsetup, - char **ppparams, char **ppdata) + char **ppparams, char **ppdata) { char *setup = *ppsetup; files_struct *fsp; change_notify_buf *cnbp; - SMB_STRUCT_STAT st; fsp = file_fsp(setup,4); @@ -1414,28 +1698,22 @@ static int call_nt_transact_notify_change(connection_struct *conn, */ if((cnbp = (change_notify_buf *)malloc(sizeof(change_notify_buf))) == NULL) { - DEBUG(0,("call_nt_transact_notify_change: Malloc fail (2) !\n" )); + DEBUG(0,("call_nt_transact_notify_change: malloc fail !\n" )); return -1; } - /* - * Store the current timestamp on the directory we are monitoring. - */ + memset((char *)cnbp, '\0', sizeof(change_notify_buf)); - if(fsp->conn->vfs_ops.stat(dos_to_unix(fsp->fsp_name, False), &st) < 0) { - DEBUG(0,("call_nt_transact_notify_change: Unable to stat name = %s. \ -Error was %s\n", fsp->fsp_name, strerror(errno) )); - free((char *)cnbp); - return(UNIXERROR(ERRDOS,ERRbadfid)); - } - memcpy(cnbp->request_buf, inbuf, smb_size); cnbp->fsp = fsp; cnbp->conn = conn; - cnbp->modify_time = st.st_mtime; - cnbp->status_time = st.st_ctime; - cnbp->next_check_time = time(NULL) + lp_change_notify_timeout(); + cnbp->flags = IVAL(setup, 0); + + if(!create_directory_notify_hash( cnbp, &cnbp->change_data )) { + free((char *)cnbp); + return(UNIXERROR(ERRDOS,ERRbadfid)); + } /* * Adding to the tail enables us to check only @@ -1452,43 +1730,661 @@ name = %s\n", fsp->fsp_name )); } /**************************************************************************** + Map unix perms to NT. +****************************************************************************/ + +static SEC_ACCESS map_unix_perms( int *pacl_type, mode_t perm, int r_mask, int w_mask, int x_mask, BOOL is_directory) +{ + SEC_ACCESS sa; + uint32 nt_mask = 0; + + *pacl_type = SEC_ACE_TYPE_ACCESS_ALLOWED; + + if((perm & (r_mask|w_mask|x_mask)) == (r_mask|w_mask|x_mask)) { + nt_mask = UNIX_ACCESS_RWX; + } else if((perm & (r_mask|w_mask|x_mask)) == 0) { + nt_mask = UNIX_ACCESS_NONE; + } else { + nt_mask |= (perm & r_mask) ? UNIX_ACCESS_R : 0; + if(is_directory) + nt_mask |= (perm & w_mask) ? UNIX_ACCESS_W : 0; + else + nt_mask |= (perm & w_mask) ? UNIX_ACCESS_W : 0; + nt_mask |= (perm & x_mask) ? UNIX_ACCESS_X : 0; + } + init_sec_access(&sa,nt_mask); + return sa; +} + +/**************************************************************************** + Reply to query a security descriptor from an fsp. If it succeeds it allocates + the space for the return elements and returns True. +****************************************************************************/ + +static size_t get_nt_acl(files_struct *fsp, SEC_DESC **ppdesc) +{ + extern DOM_SID global_sam_sid; + extern DOM_SID global_sid_World; + SMB_STRUCT_STAT sbuf; + SEC_ACE ace_list[6]; + DOM_SID owner_sid; + DOM_SID group_sid; + size_t sec_desc_size; + SEC_ACL *psa = NULL; + SEC_ACCESS owner_access; + int owner_acl_type; + SEC_ACCESS group_access; + int grp_acl_type; + SEC_ACCESS other_access; + int other_acl_type; + int num_acls = 0; + + *ppdesc = NULL; + + if(!lp_nt_acl_support()) { + sid_copy( &owner_sid, &global_sid_World); + sid_copy( &group_sid, &global_sid_World); + } else { + + if(fsp->is_directory || fsp->fd_ptr == NULL) { + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + return 0; + } + } else { + if(sys_fstat(fsp->fd_ptr->fd,&sbuf) != 0) { + return 0; + } + } + + /* + * Get the owner, group and world SIDs. + */ + + sid_copy(&owner_sid, &global_sam_sid); + sid_copy(&group_sid, &global_sam_sid); + sid_append_rid(&owner_sid, pdb_uid_to_user_rid(sbuf.st_uid)); + sid_append_rid(&group_sid, pdb_gid_to_group_rid(sbuf.st_gid)); + + /* + * Create the generic 3 element UNIX acl. + */ + + owner_access = map_unix_perms(&owner_acl_type, sbuf.st_mode, + S_IRUSR, S_IWUSR, S_IXUSR, fsp->is_directory); + group_access = map_unix_perms(&grp_acl_type, sbuf.st_mode, + S_IRGRP, S_IWGRP, S_IXGRP, fsp->is_directory); + other_access = map_unix_perms(&other_acl_type, sbuf.st_mode, + S_IROTH, S_IWOTH, S_IXOTH, fsp->is_directory); + + if(owner_access.mask) + init_sec_ace(&ace_list[num_acls++], &owner_sid, owner_acl_type, + owner_access, 0); + + if(group_access.mask) + init_sec_ace(&ace_list[num_acls++], &group_sid, grp_acl_type, + group_access, 0); + + if(other_access.mask) + init_sec_ace(&ace_list[num_acls++], &global_sid_World, other_acl_type, + other_access, 0); + + if(fsp->is_directory) { + /* + * For directory ACLs we also add in the inherited permissions + * ACE entries. These are the permissions a file would get when + * being created in the directory. + */ + mode_t mode = unix_mode( fsp->conn, FILE_ATTRIBUTE_ARCHIVE); + + owner_access = map_unix_perms(&owner_acl_type, mode, + S_IRUSR, S_IWUSR, S_IXUSR, fsp->is_directory); + group_access = map_unix_perms(&grp_acl_type, mode, + S_IRGRP, S_IWGRP, S_IXGRP, fsp->is_directory); + other_access = map_unix_perms(&other_acl_type, mode, + S_IROTH, S_IWOTH, S_IXOTH, fsp->is_directory); + + if(owner_access.mask) + init_sec_ace(&ace_list[num_acls++], &owner_sid, owner_acl_type, + owner_access, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + + if(group_access.mask) + init_sec_ace(&ace_list[num_acls++], &group_sid, grp_acl_type, + group_access, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + + if(other_access.mask) + init_sec_ace(&ace_list[num_acls++], &global_sid_World, other_acl_type, + other_access, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + } + + if(num_acls) + if((psa = make_sec_acl( 3, num_acls, ace_list)) == NULL) { + DEBUG(0,("get_nt_acl: Unable to malloc space for acl.\n")); + return 0; + } + } + + *ppdesc = make_standard_sec_desc( &owner_sid, &group_sid, psa, &sec_desc_size); + + if(!*ppdesc) { + DEBUG(0,("get_nt_acl: Unable to malloc space for security descriptor.\n")); + sec_desc_size = 0; + } + + free_sec_acl(&psa); + + return sec_desc_size; +} + +/**************************************************************************** Reply to query a security descriptor - currently this is not implemented (it - is planned to be though). + is planned to be though). Right now it just returns the same thing NT would + when queried on a FAT filesystem. JRA. ****************************************************************************/ + static int call_nt_transact_query_security_desc(connection_struct *conn, - char *inbuf, char *outbuf, - int length, - int bufsize, + char *inbuf, char *outbuf, + int length, int bufsize, char **ppsetup, char **ppparams, char **ppdata) { - static BOOL logged_message = False; + uint32 max_data_count = IVAL(inbuf,smb_nt_MaxDataCount); + char *params = *ppparams; + char *data = *ppdata; + prs_struct pd; + SEC_DESC *psd; + size_t sec_desc_size; - if(!logged_message) { - DEBUG(0,("call_nt_transact_query_security_desc: Currently not implemented.\n")); - logged_message = True; /* Only print this once... */ + files_struct *fsp = file_fsp(params,0); + + if(!fsp) + return(ERROR(ERRDOS,ERRbadfid)); + + DEBUG(3,("call_nt_transact_query_security_desc: file = %s\n", fsp->fsp_name )); + + params = *ppparams = Realloc(*ppparams, 4); + if(params == NULL) + return(ERROR(ERRDOS,ERRnomem)); + + /* + * Get the permissions to return. + */ + + if((sec_desc_size = get_nt_acl(fsp, &psd)) == 0) + return(UNIXERROR(ERRDOS,ERRnoaccess)); + + DEBUG(3,("call_nt_transact_query_security_desc: sec_desc_size = %d.\n",(int)sec_desc_size)); + + SIVAL(params,0,(uint32)sec_desc_size); + + if(max_data_count < sec_desc_size) { + + free_sec_desc(&psd); + + send_nt_replies(inbuf, outbuf, bufsize, 0xC0000000|NT_STATUS_BUFFER_TOO_SMALL, + params, 4, *ppdata, 0); + return -1; } - return(ERROR(ERRSRV,ERRnosupport)); + /* + * Allocate the data we will point this at. + */ + + data = *ppdata = Realloc(*ppdata, sec_desc_size); + if(data == NULL) { + free_sec_desc(&psd); + return(ERROR(ERRDOS,ERRnomem)); + } + + memset(data, '\0', sec_desc_size); + + /* + * Init the parse struct we will marshall into. + */ + + prs_init(&pd, 0, 4, MARSHALL); + + /* + * Setup the prs_struct to point at the memory we just + * allocated. + */ + + prs_give_memory( &pd, data, (uint32)sec_desc_size, False); + + /* + * Finally, linearize into the outgoing buffer. + */ + + if(!sec_io_desc( "sd data", &psd, &pd, 1)) { + free_sec_desc(&psd); + DEBUG(0,("call_nt_transact_query_security_desc: Error in marshalling \ +security descriptor.\n")); + /* + * Return access denied for want of a better error message.. + */ + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + + /* + * Now we can delete the security descriptor. + */ + + free_sec_desc(&psd); + + send_nt_replies(inbuf, outbuf, bufsize, 0, params, 4, data, (int)sec_desc_size); + return -1; } - + +/**************************************************************************** + Validate a SID. +****************************************************************************/ + +static BOOL validate_unix_sid( DOM_SID *psid, uint32 *prid, DOM_SID *sd_sid) +{ + extern DOM_SID global_sam_sid; + DOM_SID sid; + + if(!sd_sid) { + DEBUG(5,("validate_unix_sid: sid missing.\n")); + return False; + } + + sid_copy(psid, sd_sid); + sid_copy(&sid, sd_sid); + + if(!sid_split_rid(&sid, prid)) { + DEBUG(5,("validate_unix_sid: cannot get RID from sid.\n")); + return False; + } + + if(!sid_equal( &sid, &global_sam_sid)) { + DEBUG(5,("validate_unix_sid: sid is not ours.\n")); + return False; + } + + return True; +} + /**************************************************************************** - Reply to set a security descriptor - currently this is not implemented (it - is planned to be though). + Map NT perms to UNIX. ****************************************************************************/ + +#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( SEC_ACCESS sec_access, int type) +{ + mode_t mode = 0; + + switch(type) { + case S_IRUSR: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IRUSR|S_IWUSR|S_IXUSR; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRUSR : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWUSR : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXUSR : 0; + } + break; + case S_IRGRP: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IRGRP|S_IWGRP|S_IXGRP; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRGRP : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWGRP : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXGRP : 0; + } + break; + case S_IROTH: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IROTH|S_IWOTH|S_IXOTH; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IROTH : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWOTH : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXOTH : 0; + } + break; + } + + return mode; +} + +/**************************************************************************** + Unpack a SEC_DESC into a owner, group and set of UNIX permissions. +****************************************************************************/ + +static BOOL unpack_nt_permissions(uid_t *puser, gid_t *pgrp, mode_t *pmode, uint32 security_info_sent, + SEC_DESC *psd, BOOL is_directory) +{ + extern DOM_SID global_sid_World; + DOM_SID owner_sid; + DOM_SID grp_sid; + uint32 owner_rid; + uint32 grp_rid; + SEC_ACL *dacl = psd->dacl; + int i; + + *pmode = 0; + *puser = (uid_t)-1; + *pgrp = (gid_t)-1; + + if(security_info_sent == 0) { + DEBUG(0,("unpack_unix_permissions: no security info sent !\n")); + return False; + } + + /* + * Validate the owner and group SID's. + */ + + memset(&owner_sid, '\0', sizeof(owner_sid)); + memset(&grp_sid, '\0', sizeof(grp_sid)); + + DEBUG(5,("unpack_unix_permissions: validating owner_sid.\n")); + + /* + * Don't immediately fail if the owner sid cannot be validated. + * This may be a group chown only set. + */ + + if(!validate_unix_sid( &owner_sid, &owner_rid, psd->owner_sid)) + DEBUG(3,("unpack_unix_permissions: unable to validate owner sid.\n")); + else if(security_info_sent & OWNER_SECURITY_INFORMATION) + *puser = pdb_user_rid_to_uid(owner_rid); + + /* + * Don't immediately fail if the group sid cannot be validated. + * This may be an owner chown only set. + */ + + if(!validate_unix_sid( &grp_sid, &grp_rid, psd->grp_sid)) + DEBUG(3,("unpack_unix_permissions: unable to validate group sid.\n")); + else if(security_info_sent & GROUP_SECURITY_INFORMATION) + *pgrp = pdb_user_rid_to_gid(grp_rid); + + /* + * If no DACL then this is a chown only security descriptor. + */ + + if(!(security_info_sent & DACL_SECURITY_INFORMATION) || !dacl) { + *pmode = 0; + return True; + } + + /* + * Now go through the DACL and ensure that + * any owner/group sids match. + */ + + for(i = 0; i < dacl->num_aces; i++) { + DOM_SID ace_sid; + SEC_ACE *psa = &dacl->ace_list[i]; + + if((psa->type != SEC_ACE_TYPE_ACCESS_ALLOWED) && + (psa->type != SEC_ACE_TYPE_ACCESS_DENIED)) { + DEBUG(3,("unpack_unix_permissions: unable to set anything but an ALLOW or DENY ACE.\n")); + return False; + } + + /* + * Ignore or remove bits we don't care about on a directory ACE. + */ + + if(is_directory) { + if(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY) { + DEBUG(3,("unpack_unix_permissions: ignoring inherit only ACE.\n")); + continue; + } + + psa->flags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT); + } + + if(psa->flags != 0) { + DEBUG(1,("unpack_unix_permissions: unable to set ACE flags (%x).\n", + (unsigned int)psa->flags)); + return False; + } + + /* + * 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. + */ + + psa->info.mask &= (GENERIC_ALL_ACCESS|GENERIC_EXECUTE_ACCESS|GENERIC_WRITE_ACCESS| + GENERIC_READ_ACCESS|UNIX_ACCESS_NONE|FILE_ALL_ATTRIBUTES); + + if(psa->info.mask != UNIX_ACCESS_NONE) + psa->info.mask &= ~UNIX_ACCESS_NONE; + + sid_copy(&ace_sid, &psa->sid); + + if(sid_equal(&ace_sid, &owner_sid)) { + /* + * Map the desired permissions into owner perms. + */ + + if(psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) + *pmode |= map_nt_perms( psa->info, S_IRUSR); + else + *pmode &= ~(map_nt_perms( psa->info, S_IRUSR)); + + } else if( sid_equal(&ace_sid, &grp_sid)) { + /* + * Map the desired permissions into group perms. + */ + + if(psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) + *pmode |= map_nt_perms( psa->info, S_IRGRP); + else + *pmode &= ~(map_nt_perms( psa->info, S_IRGRP)); + + } else if( sid_equal(&ace_sid, &global_sid_World)) { + /* + * Map the desired permissions into other perms. + */ + + if(psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) + *pmode |= map_nt_perms( psa->info, S_IROTH); + else + *pmode &= ~(map_nt_perms( psa->info, S_IROTH)); + + } else { + DEBUG(0,("unpack_unix_permissions: unknown SID used in ACL.\n")); + return False; + } + } + + return True; +} + +/**************************************************************************** + Reply to set a security descriptor. Map to UNIX perms. +****************************************************************************/ + static int call_nt_transact_set_security_desc(connection_struct *conn, - char *inbuf, char *outbuf, - int length, - int bufsize, - char **ppsetup, - char **ppparams, char **ppdata) + char *inbuf, char *outbuf, int length, + int bufsize, char **ppsetup, + char **ppparams, char **ppdata) { - static BOOL logged_message = False; + uint32 total_parameter_count = IVAL(inbuf, smb_nts_TotalParameterCount); + char *params= *ppparams; + char *data = *ppdata; + prs_struct pd; + SEC_DESC *psd = NULL; + uint32 total_data_count = (uint32)IVAL(inbuf, smb_nts_TotalDataCount); + uid_t user = (uid_t)-1; + gid_t grp = (gid_t)-1; + mode_t perms = 0; + SMB_STRUCT_STAT sbuf; + files_struct *fsp = NULL; + uint32 security_info_sent = 0; + BOOL got_dacl = False; - if(!logged_message) { - DEBUG(0,("call_nt_transact_set_security_desc: Currently not implemented.\n")); - logged_message = True; /* Only print this once... */ + if(!lp_nt_acl_support()) + return(UNIXERROR(ERRDOS,ERRnoaccess)); + + if(total_parameter_count < 8) + return(ERROR(ERRDOS,ERRbadfunc)); + + if((fsp = file_fsp(params,0)) == NULL) + return(ERROR(ERRDOS,ERRbadfid)); + + 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 )); + + /* + * Init the parse struct we will unmarshall from. + */ + + prs_init(&pd, 0, 4, UNMARSHALL); + + /* + * Setup the prs_struct to point at the memory we just + * allocated. + */ + + prs_give_memory( &pd, data, total_data_count, False); + + /* + * Finally, unmarshall from the data buffer. + */ + + if(!sec_io_desc( "sd data", &psd, &pd, 1)) { + free_sec_desc(&psd); + DEBUG(0,("call_nt_transact_set_security_desc: Error in unmarshalling \ +security descriptor.\n")); + /* + * Return access denied for want of a better error message.. + */ + return(UNIXERROR(ERRDOS,ERRnoaccess)); } - return(ERROR(ERRSRV,ERRnosupport)); + + /* + * Unpack the user/group/world id's and permissions. + */ + + if(!unpack_nt_permissions( &user, &grp, &perms, security_info_sent, psd, fsp->is_directory)) { + free_sec_desc(&psd); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + + if (psd->dacl != NULL) + got_dacl = True; + + free_sec_desc(&psd); + + /* + * Get the current state of the file. + */ + + if(fsp->is_directory) { + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } else { + + int ret; + + if(fsp->fd_ptr == NULL) + ret = dos_stat(fsp->fsp_name, &sbuf); + else + ret = sys_fstat(fsp->fd_ptr->fd,&sbuf); + + if(ret != 0) { + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } + + /* + * Do we need to chown ? + */ + + if((user != (uid_t)-1 || grp != (uid_t)-1) && (sbuf.st_uid != user || sbuf.st_gid != grp)) { + + DEBUG(3,("call_nt_transact_set_security_desc: chown %s. uid = %u, gid = %u.\n", + fsp->fsp_name, (unsigned int)user, (unsigned int)grp )); + + if(dos_chown( fsp->fsp_name, user, grp) == -1) { + DEBUG(3,("call_nt_transact_set_security_desc: chown %s, %u, %u failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)user, (unsigned int)grp, strerror(errno) )); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + + /* + * Recheck the current state of the file, which may have changed. + * (suid/sgid bits, for instance) + */ + + if(fsp->is_directory) { + if(dos_stat(fsp->fsp_name, &sbuf) != 0) { + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } else { + + int ret; + + if(fsp->fd_ptr == NULL) + ret = dos_stat(fsp->fsp_name, &sbuf); + else + ret = sys_fstat(fsp->fd_ptr->fd,&sbuf); + + if(ret != 0) + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } + + /* + * Only change security if we got a DACL. + */ + + if((security_info_sent & DACL_SECURITY_INFORMATION) && got_dacl) { + + /* + * Check to see if we need to change anything. + * Enforce limits on modified bits *only*. Don't enforce masks + * on bits not changed by the user. + */ + + if(fsp->is_directory) { + + perms &= (lp_dir_security_mask(SNUM(conn)) | sbuf.st_mode); + perms |= (lp_force_dir_security_mode(SNUM(conn)) & ( perms ^ sbuf.st_mode )); + + } else { + + perms &= (lp_security_mask(SNUM(conn)) | sbuf.st_mode); + perms |= (lp_force_security_mode(SNUM(conn)) & ( perms ^ sbuf.st_mode )); + + } + + /* + * Preserve special bits. + */ + + perms |= (sbuf.st_mode & ~0777); + + /* + * Do we need to chmod ? + */ + + if(sbuf.st_mode != perms) { + + DEBUG(3,("call_nt_transact_set_security_desc: chmod %s. perms = 0%o.\n", + fsp->fsp_name, (unsigned int)perms )); + + if(dos_chmod( fsp->fsp_name, perms) == -1) { + DEBUG(3,("call_nt_transact_set_security_desc: chmod %s, 0%o failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)perms, strerror(errno) )); + return(UNIXERROR(ERRDOS,ERRnoaccess)); + } + } + } + + send_nt_replies(inbuf, outbuf, bufsize, 0, NULL, 0, NULL, 0); + return -1; } /**************************************************************************** @@ -1675,7 +2571,6 @@ due to being in oplock break state.\n" )); length, bufsize, &setup, ¶ms, &data); break; - default: /* Error in request */ DEBUG(0,("reply_nttrans: Unknown request %d in nttrans call\n", function_code)); |