/* Unix SMB/Netbios implementation. Version 1.9. Main SMB server routines Copyright (C) Andrew Tridgell 1992-1998 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "includes.h" #include "trans2.h" pstring servicesf = CONFIGFILE; extern pstring debugf; extern pstring sesssetup_user; extern fstring global_myworkgroup; extern pstring global_myname; char *InBuffer = NULL; char *OutBuffer = NULL; char *last_inbuf = NULL; int am_parent = 1; /* the last message the was processed */ int last_message = -1; /* a useful macro to debug the last message processed */ #define LAST_MESSAGE() smb_fn_name(last_message) extern pstring scope; extern int DEBUGLEVEL; extern int case_default; extern BOOL case_sensitive; extern BOOL case_preserve; extern BOOL use_mangled_map; extern BOOL short_case_preserve; extern BOOL case_mangle; time_t smb_last_time=(time_t)0; extern BOOL global_machine_pasword_needs_changing; extern int smb_read_error; extern pstring user_socket_options; #ifdef WITH_DFS extern int dcelogin_atmost_once; #endif /* WITH_DFS */ /* * This is set on startup - it defines the SID for this * machine. */ extern DOM_SID global_machine_sid; /* * Size of data we can send to client. Set * by the client for all protocols above CORE. * Set by us for CORE protocol. */ int max_send = BUFFER_SIZE; /* * Size of the data we can receive. Set by us. * Can be modified by the max xmit parameter. */ int max_recv = BUFFER_SIZE; /* Oplock ipc UDP socket. */ int oplock_sock = -1; uint16 oplock_port = 0; /* Current number of oplocks we have outstanding. */ int32 global_oplocks_open = 0; BOOL global_oplock_break = False; extern fstring remote_machine; extern pstring OriginalDir; /* these can be set by some functions to override the error codes */ int unix_ERR_class=SMB_SUCCESS; int unix_ERR_code=0; extern int extra_time_offset; extern pstring myhostname; /**************************************************************************** when exiting, take the whole family ****************************************************************************/ void *dflt_sig(void) { exit_server("caught signal"); return 0; /* Keep -Wall happy :-) */ } /**************************************************************************** Send a SIGTERM to our process group. *****************************************************************************/ void killkids(void) { if(am_parent) kill(0,SIGTERM); } /**************************************************************************** change a dos mode to a unix mode base permission for files: everybody gets read bit set dos readonly is represented in unix by removing everyone's write bit dos archive is represented in unix by the user's execute bit dos system is represented in unix by the group's execute bit dos hidden is represented in unix by the other's execute bit Then apply create mask, then add force bits. base permission for directories: dos directory is represented in unix by unix's dir bit and the exec bit Then apply create mask, then add force bits. ****************************************************************************/ mode_t unix_mode(connection_struct *conn,int dosmode) { mode_t result = (S_IRUSR | S_IRGRP | S_IROTH); if ( !IS_DOS_READONLY(dosmode) ) result |= (S_IWUSR | S_IWGRP | S_IWOTH); if (IS_DOS_DIR(dosmode)) { /* We never make directories read only for the owner as under DOS a user can always create a file in a read-only directory. */ result |= (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH | S_IWUSR); /* Apply directory mask */ result &= lp_dir_mode(SNUM(conn)); /* Add in force bits */ result |= lp_force_dir_mode(SNUM(conn)); } else { if (lp_map_archive(SNUM(conn)) && IS_DOS_ARCHIVE(dosmode)) result |= S_IXUSR; if (lp_map_system(SNUM(conn)) && IS_DOS_SYSTEM(dosmode)) result |= S_IXGRP; if (lp_map_hidden(SNUM(conn)) && IS_DOS_HIDDEN(dosmode)) result |= S_IXOTH; /* Apply mode mask */ result &= lp_create_mode(SNUM(conn)); /* Add in force bits */ result |= lp_force_create_mode(SNUM(conn)); } return(result); } /**************************************************************************** change a unix mode to a dos mode ****************************************************************************/ int dos_mode(connection_struct *conn,char *path,struct stat *sbuf) { int result = 0; extern struct current_user current_user; DEBUG(8,("dos_mode: %s\n", path)); if (CAN_WRITE(conn) && !lp_alternate_permissions(SNUM(conn))) { if (!((sbuf->st_mode & S_IWOTH) || conn->admin_user || ((sbuf->st_mode & S_IWUSR) && current_user.uid==sbuf->st_uid) || ((sbuf->st_mode & S_IWGRP) && in_group(sbuf->st_gid,current_user.gid, current_user.ngroups,current_user.groups)))) result |= aRONLY; } else { if ((sbuf->st_mode & S_IWUSR) == 0) result |= aRONLY; } if (MAP_ARCHIVE(conn) && ((sbuf->st_mode & S_IXUSR) != 0)) result |= aARCH; if (MAP_SYSTEM(conn) && ((sbuf->st_mode & S_IXGRP) != 0)) result |= aSYSTEM; if (MAP_HIDDEN(conn) && ((sbuf->st_mode & S_IXOTH) != 0)) result |= aHIDDEN; if (S_ISDIR(sbuf->st_mode)) result = aDIR | (result & aRONLY); #ifdef S_ISLNK #if LINKS_READ_ONLY if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode)) result |= aRONLY; #endif #endif /* hide files with a name starting with a . */ if (lp_hide_dot_files(SNUM(conn))) { char *p = strrchr(path,'/'); if (p) p++; else p = path; if (p[0] == '.' && p[1] != '.' && p[1] != 0) result |= aHIDDEN; } /* Optimization : Only call is_hidden_path if it's not already hidden. */ if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) { result |= aHIDDEN; } DEBUG(8,("dos_mode returning ")); if (result & aHIDDEN) DEBUG(8, ("h")); if (result & aRONLY ) DEBUG(8, ("r")); if (result & aSYSTEM) DEBUG(8, ("s")); if (result & aDIR ) DEBUG(8, ("d")); if (result & aARCH ) DEBUG(8, ("a")); DEBUG(8,("\n")); return(result); } /******************************************************************* chmod a file - but preserve some bits ********************************************************************/ int dos_chmod(connection_struct *conn,char *fname,int dosmode,struct stat *st) { struct stat st1; int mask=0; int tmp; int unixmode; if (!st) { st = &st1; if (sys_stat(fname,st)) return(-1); } if (S_ISDIR(st->st_mode)) dosmode |= aDIR; if (dos_mode(conn,fname,st) == dosmode) return(0); unixmode = unix_mode(conn,dosmode); /* preserve the s bits */ mask |= (S_ISUID | S_ISGID); /* preserve the t bit */ #ifdef S_ISVTX mask |= S_ISVTX; #endif /* possibly preserve the x bits */ if (!MAP_ARCHIVE(conn)) mask |= S_IXUSR; if (!MAP_SYSTEM(conn)) mask |= S_IXGRP; if (!MAP_HIDDEN(conn)) mask |= S_IXOTH; unixmode |= (st->st_mode & mask); /* if we previously had any r bits set then leave them alone */ if ((tmp = st->st_mode & (S_IRUSR|S_IRGRP|S_IROTH))) { unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH); unixmode |= tmp; } /* if we previously had any w bits set then leave them alone if the new mode is not rdonly */ if (!IS_DOS_READONLY(dosmode) && (tmp = st->st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))) { unixmode &= ~(S_IWUSR|S_IWGRP|S_IWOTH); unixmode |= tmp; } return(sys_chmod(fname,unixmode)); } /******************************************************************* Wrapper around sys_utime that possibly allows DOS semantics rather than POSIX. *******************************************************************/ int file_utime(connection_struct *conn, char *fname, struct utimbuf *times) { extern struct current_user current_user; struct stat sb; int ret = -1; errno = 0; if(sys_utime(fname, times) == 0) return 0; if((errno != EPERM) && (errno != EACCES)) return -1; if(!lp_dos_filetimes(SNUM(conn))) return -1; /* We have permission (given by the Samba admin) to break POSIX semantics and allow a user to change the time on a file they don't own but can write to (as DOS does). */ if(sys_stat(fname,&sb) != 0) return -1; /* Check if we have write access. */ if (CAN_WRITE(conn)) { if (((sb.st_mode & S_IWOTH) || conn->admin_user || ((sb.st_mode & S_IWUSR) && current_user.uid==sb.st_uid) || ((sb.st_mode & S_IWGRP) && in_group(sb.st_gid,current_user.gid, current_user.ngroups,current_user.groups)))) { /* We are allowed to become root and change the filetime. */ become_root(False); ret = sys_utime(fname, times); unbecome_root(False); } } return ret; } /******************************************************************* Change a filetime - possibly allowing DOS semantics. *******************************************************************/ BOOL set_filetime(connection_struct *conn, char *fname, time_t mtime) { struct utimbuf times; if (null_mtime(mtime)) return(True); times.modtime = times.actime = mtime; if (file_utime(conn, fname, ×)) { DEBUG(4,("set_filetime(%s) failed: %s\n",fname,strerror(errno))); } return(True); } /**************************************************************************** check if two filenames are equal this needs to be careful about whether we are case sensitive ****************************************************************************/ static BOOL fname_equal(char *name1, char *name2) { int l1 = strlen(name1); int l2 = strlen(name2); /* handle filenames ending in a single dot */ if (l1-l2 == 1 && name1[l1-1] == '.' && lp_strip_dot()) { BOOL ret; name1[l1-1] = 0; ret = fname_equal(name1,name2); name1[l1-1] = '.'; return(ret); } if (l2-l1 == 1 && name2[l2-1] == '.' && lp_strip_dot()) { BOOL ret; name2[l2-1] = 0; ret = fname_equal(name1,name2); name2[l2-1] = '.'; return(ret); } /* now normal filename handling */ if (case_sensitive) return(strcmp(name1,name2) == 0); return(strequal(name1,name2)); } /**************************************************************************** mangle the 2nd name and check if it is then equal to the first name ****************************************************************************/ static BOOL mangled_equal(char *name1, char *name2) { pstring tmpname; if (is_8_3(name2, True)) return(False); pstrcpy(tmpname,name2); mangle_name_83(tmpname,sizeof(tmpname)); return(strequal(name1,tmpname)); } /**************************************************************************** scan a directory to find a filename, matching without case sensitivity If the name looks like a mangled name then try via the mangling functions ****************************************************************************/ static BOOL scan_directory(char *path, char *name,connection_struct *conn,BOOL docache) { void *cur_dir; char *dname; BOOL mangled; pstring name2; mangled = is_mangled(name); /* handle null paths */ if (*path == 0) path = "."; if (docache && (dname = DirCacheCheck(path,name,SNUM(conn)))) { pstrcpy(name, dname); return(True); } /* * The incoming name can be mangled, and if we de-mangle it * here it will not compare correctly against the filename (name2) * read from the directory and then mangled by the name_map_mangle() * call. We need to mangle both names or neither. * (JRA). */ if (mangled) mangled = !check_mangled_cache( name ); /* open the directory */ if (!(cur_dir = OpenDir(conn, path, True))) { DEBUG(3,("scan dir didn't open dir [%s]\n",path)); return(False); } /* now scan for matching names */ while ((dname = ReadDirName(cur_dir))) { if (*dname == '.' && (strequal(dname,".") || strequal(dname,".."))) continue; pstrcpy(name2,dname); if (!name_map_mangle(name2,False,SNUM(conn))) continue; if ((mangled && mangled_equal(name,name2)) || fname_equal(name, name2)) { /* we've found the file, change it's name and return */ if (docache) DirCacheAdd(path,name,dname,SNUM(conn)); pstrcpy(name, dname); CloseDir(cur_dir); return(True); } } CloseDir(cur_dir); return(False); } /**************************************************************************** This routine is called to convert names from the dos namespace to unix namespace. It needs to handle any case conversions, mangling, format changes etc. We assume that we have already done a chdir() to the right "root" directory for this service. The function will return False if some part of the name except for the last part cannot be resolved If the saved_last_component != 0, then the unmodified last component of the pathname is returned there. This is used in an exceptional case in reply_mv (so far). If saved_last_component == 0 then nothing is returned there. The bad_path arg is set to True if the filename walk failed. This is used to pick the correct error code to return between ENOENT and ENOTDIR as Windows applications depend on ERRbadpath being returned if a component of a pathname does not exist. ****************************************************************************/ BOOL unix_convert(char *name,connection_struct *conn,char *saved_last_component, BOOL *bad_path) { struct stat st; char *start, *end; pstring dirpath; int saved_errno; *dirpath = 0; *bad_path = False; if(saved_last_component) *saved_last_component = 0; /* convert to basic unix format - removing \ chars and cleaning it up */ unix_format(name); unix_clean_name(name); /* names must be relative to the root of the service - trim any leading /. also trim trailing /'s */ trim_string(name,"/","/"); /* * Ensure saved_last_component is valid even if file exists. */ if(saved_last_component) { end = strrchr(name, '/'); if(end) pstrcpy(saved_last_component, end + 1); else pstrcpy(saved_last_component, name); } if (!case_sensitive && (!case_preserve || (is_8_3(name, False) && !short_case_preserve))) strnorm(name); /* check if it's a printer file */ if (conn->printer) { if ((! *name) || strchr(name,'/') || !is_8_3(name, True)) { char *s; fstring name2; slprintf(name2,sizeof(name2)-1,"%.6s.XXXXXX",remote_machine); /* sanitise the name */ for (s=name2 ; *s ; s++) if (!issafe(*s)) *s = '_'; pstrcpy(name,(char *)mktemp(name2)); } return(True); } /* stat the name - if it exists then we are all done! */ if (sys_stat(name,&st) == 0) return(True); saved_errno = errno; DEBUG(5,("unix_convert(%s)\n",name)); /* a special case - if we don't have any mangling chars and are case sensitive then searching won't help */ if (case_sensitive && !is_mangled(name) && !lp_strip_dot() && !use_mangled_map && (saved_errno != ENOENT)) return(False); /* now we need to recursively match the name against the real directory structure */ start = name; while (strncmp(start,"./",2) == 0) start += 2; /* now match each part of the path name separately, trying the names as is first, then trying to scan the directory for matching names */ for (;start;start = (end?end+1:(char *)NULL)) { /* pinpoint the end of this section of the filename */ end = strchr(start, '/'); /* chop the name at this point */ if (end) *end = 0; if(saved_last_component != 0) pstrcpy(saved_last_component, end ? end + 1 : start); /* check if the name exists up to this point */ if (sys_stat(name, &st) == 0) { /* it exists. it must either be a directory or this must be the last part of the path for it to be OK */ if (end && !(st.st_mode & S_IFDIR)) { /* an intermediate part of the name isn't a directory */ DEBUG(5,("Not a dir %s\n",start)); *end = '/'; return(False); } } else { pstring rest; *rest = 0; /* remember the rest of the pathname so it can be restored later */ if (end) pstrcpy(rest,end+1); /* try to find this part of the path in the directory */ if (strchr(start,'?') || strchr(start,'*') || !scan_directory(dirpath, start, conn, end?True:False)) { if (end) { /* an intermediate part of the name can't be found */ DEBUG(5,("Intermediate not found %s\n",start)); *end = '/'; /* We need to return the fact that the intermediate name resolution failed. This is used to return an error of ERRbadpath rather than ERRbadfile. Some Windows applications depend on the difference between these two errors. */ *bad_path = True; return(False); } /* just the last part of the name doesn't exist */ /* we may need to strupper() or strlower() it in case this conversion is being used for file creation purposes */ /* if the filename is of mixed case then don't normalise it */ if (!case_preserve && (!strhasupper(start) || !strhaslower(start))) strnorm(start); /* check on the mangled stack to see if we can recover the base of the filename */ if (is_mangled(start)) check_mangled_cache( start ); DEBUG(5,("New file %s\n",start)); return(True); } /* restore the rest of the string */ if (end) { pstrcpy(start+strlen(start)+1,rest); end = start + strlen(start); } } /* add to the dirpath that we have resolved so far */ if (*dirpath) pstrcat(dirpath,"/"); pstrcat(dirpath,start); /* restore the / that we wiped out earlier */ if (end) *end = '/'; } /* the name has been resolved */ DEBUG(5,("conversion finished %s\n",name)); return(True); } /**************************************************************************** check a filename - possibly caling reducename This is called by every routine before it allows an operation on a filename. It does any final confirmation necessary to ensure that the filename is a valid one for the user to access. ****************************************************************************/ BOOL check_name(char *name,connection_struct *conn) { BOOL ret; errno = 0; if (IS_VETO_PATH(conn, name)) { DEBUG(5,("file path name %s vetoed\n",name)); return(0); } ret = reduce_name(name,conn->connectpath,lp_widelinks(SNUM(conn))); /* Check if we are allowing users to follow symlinks */ /* Patch from David Clerc University of Geneva */ #ifdef S_ISLNK if (!lp_symlinks(SNUM(conn))) { struct stat statbuf; if ( (sys_lstat(name,&statbuf) != -1) && (S_ISLNK(statbuf.st_mode)) ) { DEBUG(3,("check_name: denied: file path name %s is a symlink\n",name)); ret=0; } } #endif if (!ret) DEBUG(5,("check_name on %s failed\n",name)); return(ret); } /**************************************************************************** check a filename - possibly caling reducename ****************************************************************************/ static void check_for_pipe(char *fname) { /* special case of pipe opens */ char s[10]; StrnCpy(s,fname,9); strlower(s); if (strstr(s,"pipe/")) { DEBUG(3,("Rejecting named pipe open for %s\n",fname)); unix_ERR_class = ERRSRV; unix_ERR_code = ERRaccess; } } /**************************************************************************** fd support routines - attempt to do a sys_open ****************************************************************************/ static int fd_attempt_open(char *fname, int flags, int mode) { int fd = sys_open(fname,flags,mode); /* Fix for files ending in '.' */ if((fd == -1) && (errno == ENOENT) && (strchr(fname,'.')==NULL)) { pstrcat(fname,"."); fd = sys_open(fname,flags,mode); } #if (defined(ENAMETOOLONG) && defined(HAVE_PATHCONF)) if ((fd == -1) && (errno == ENAMETOOLONG)) { int max_len; char *p = strrchr(fname, '/'); if (p == fname) /* name is "/xxx" */ { max_len = pathconf("/", _PC_NAME_MAX); p++; } else if ((p == NULL) || (p == fname)) { p = fname; max_len = pathconf(".", _PC_NAME_MAX); } else { *p = '\0'; max_len = pathconf(fname, _PC_NAME_MAX); *p = '/'; p++; } if (strlen(p) > max_len) { char tmp = p[max_len]; p[max_len] = '\0'; if ((fd = sys_open(fname,flags,mode)) == -1) p[max_len] = tmp; } } #endif return fd; } /**************************************************************************** Cache a uid_t currently with this file open. This is an optimization only used when multiple sessionsetup's have been done to one smbd. ****************************************************************************/ void fd_add_to_uid_cache(file_fd_struct *fd_ptr, uid_t u) { if(fd_ptr->uid_cache_count >= sizeof(fd_ptr->uid_users_cache)/sizeof(uid_t)) return; fd_ptr->uid_users_cache[fd_ptr->uid_cache_count++] = u; } /**************************************************************************** Remove a uid_t that currently has this file open. This is an optimization only used when multiple sessionsetup's have been done to one smbd. ****************************************************************************/ static void fd_remove_from_uid_cache(file_fd_struct *fd_ptr, uid_t u) { int i; for(i = 0; i < fd_ptr->uid_cache_count; i++) if(fd_ptr->uid_users_cache[i] == u) { if(i < (fd_ptr->uid_cache_count-1)) memmove((char *)&fd_ptr->uid_users_cache[i], (char *)&fd_ptr->uid_users_cache[i+1], sizeof(uid_t)*(fd_ptr->uid_cache_count-1-i) ); fd_ptr->uid_cache_count--; } return; } /**************************************************************************** Check if a uid_t that currently has this file open is present. This is an optimization only used when multiple sessionsetup's have been done to one smbd. ****************************************************************************/ static BOOL fd_is_in_uid_cache(file_fd_struct *fd_ptr, uid_t u) { int i; for(i = 0; i < fd_ptr->uid_cache_count; i++) if(fd_ptr->uid_users_cache[i] == u) return True; return False; } /**************************************************************************** fd support routines - attempt to re-open an already open fd as O_RDWR. Save the already open fd (we cannot close due to POSIX file locking braindamage. ****************************************************************************/ static void fd_attempt_reopen(char *fname, int mode, file_fd_struct *fd_ptr) { int fd = sys_open( fname, O_RDWR, mode); if(fd == -1) return; if(fd_ptr->real_open_flags == O_RDONLY) fd_ptr->fd_readonly = fd_ptr->fd; if(fd_ptr->real_open_flags == O_WRONLY) fd_ptr->fd_writeonly = fd_ptr->fd; fd_ptr->fd = fd; fd_ptr->real_open_flags = O_RDWR; } /**************************************************************************** fd support routines - attempt to close the file referenced by this fd. Decrements the ref_count and returns it. ****************************************************************************/ static int fd_attempt_close(file_fd_struct *fd_ptr) { extern struct current_user current_user; DEBUG(3,("fd_attempt_close fd = %d, dev = %x, inode = %x, open_flags = %d, ref_count = %d.\n", fd_ptr->fd, fd_ptr->dev, fd_ptr->inode, fd_ptr->real_open_flags, fd_ptr->ref_count)); if(fd_ptr->ref_count > 0) { fd_ptr->ref_count--; if(fd_ptr->ref_count == 0) { if(fd_ptr->fd != -1) close(fd_ptr->fd); if(fd_ptr->fd_readonly != -1) close(fd_ptr->fd_readonly); if(fd_ptr->fd_writeonly != -1) close(fd_ptr->fd_writeonly); fd_ptr->fd = -1; fd_ptr->fd_readonly = -1; fd_ptr->fd_writeonly = -1; fd_ptr->real_open_flags = -1; fd_ptr->dev = (uint32)-1; fd_ptr->inode = (uint32)-1; fd_ptr->uid_cache_count = 0; } else { fd_remove_from_uid_cache(fd_ptr, (uid_t)current_user.uid); } } return fd_ptr->ref_count; } /**************************************************************************** fd support routines - check that current user has permissions to open this file. Used when uid not found in optimization cache. This is really ugly code, as due to POSIX locking braindamage we must fork and then attempt to open the file, and return success or failure via an exit code. ****************************************************************************/ static BOOL check_access_allowed_for_current_user( char *fname, int accmode ) { pid_t child_pid; if((child_pid = fork()) < 0) { DEBUG(0,("check_access_allowed_for_current_user: fork failed.\n")); return False; } if(child_pid) { /* * Parent. */ pid_t wpid; int status_code; if ((wpid = sys_waitpid(child_pid, &status_code, 0)) < 0) { DEBUG(0,("check_access_allowed_for_current_user: The process is no longer waiting!\n")); return(False); } if (child_pid != wpid) { DEBUG(0,("check_access_allowed_for_current_user: We were waiting for the wrong process ID\n")); return(False); } #if defined(WIFEXITED) && defined(WEXITSTATUS) if (WIFEXITED(status_code) == 0) { DEBUG(0,("check_access_allowed_for_current_user: The process exited while we were waiting\n")); return(False); } if (WEXITSTATUS(status_code) != 0) { DEBUG(9,("check_access_allowed_for_current_user: The status of the process exiting was %d. Returning access denied.\n", status_code)); return(False); } #else /* defined(WIFEXITED) && defined(WEXITSTATUS) */ if(status_code != 0) { DEBUG(9,("check_access_allowed_for_current_user: The status of the process exiting was %d. Returning access denied.\n", status_code)); return(False); } #endif /* defined(WIFEXITED) && defined(WEXITSTATUS) */ /* * Success - the child could open the file. */ DEBUG(9,("check_access_allowed_for_current_user: The status of the process exiting was %d. Returning access allowed.\n", status_code)); return True; } else { /* * Child. */ int fd; DEBUG(9,("check_access_allowed_for_current_user: Child - attempting to open %s with mode %d.\n", fname, accmode )); if((fd = fd_attempt_open( fname, accmode, 0)) < 0) { /* Access denied. */ _exit(EACCES); } close(fd); DEBUG(9,("check_access_allowed_for_current_user: Child - returning ok.\n")); _exit(0); } return False; } /**************************************************************************** open a file ****************************************************************************/ static void open_file(files_struct *fsp,connection_struct *conn, char *fname1,int flags,int mode, struct stat *sbuf) { extern struct current_user current_user; pstring fname; struct stat statbuf; file_fd_struct *fd_ptr; int accmode = (flags & (O_RDONLY | O_WRONLY | O_RDWR)); fsp->open = False; fsp->fd_ptr = 0; fsp->granted_oplock = False; errno = EPERM; pstrcpy(fname,fname1); /* check permissions */ /* * This code was changed after seeing a client open request * containing the open mode of (DENY_WRITE/read-only) with * the 'create if not exist' bit set. The previous code * would fail to open the file read only on a read-only share * as it was checking the flags parameter directly against O_RDONLY, * this was failing as the flags parameter was set to O_RDONLY|O_CREAT. * JRA. */ if (conn->read_only && !conn->printer) { /* It's a read-only share - fail if we wanted to write. */ if(accmode != O_RDONLY) { DEBUG(3,("Permission denied opening %s\n",fname)); check_for_pipe(fname); return; } else if(flags & O_CREAT) { /* We don't want to write - but we must make sure that O_CREAT doesn't create the file if we have write access into the directory. */ flags &= ~O_CREAT; } } /* this handles a bug in Win95 - it doesn't say to create the file when it should */ if (conn->printer) { flags |= O_CREAT; } /* if (flags == O_WRONLY) DEBUG(3,("Bug in client? Set O_WRONLY without O_CREAT\n")); */ /* * Ensure we have a valid struct stat so we can search the * open fd table. */ if(sbuf == 0) { if(sys_stat(fname, &statbuf) < 0) { if(errno != ENOENT) { DEBUG(3,("Error doing stat on file %s (%s)\n", fname,strerror(errno))); check_for_pipe(fname); return; } sbuf = 0; } else { sbuf = &statbuf; } } /* * Check to see if we have this file already * open. If we do, just use the already open fd and increment the * reference count (fd_get_already_open increments the ref_count). */ if((fd_ptr = fd_get_already_open(sbuf))!= 0) { /* * File was already open. */ /* * Check it wasn't open for exclusive use. */ if((flags & O_CREAT) && (flags & O_EXCL)) { fd_ptr->ref_count--; errno = EEXIST; return; } /* * Ensure that the user attempting to open * this file has permissions to do so, if * the user who originally opened the file wasn't * the same as the current user. */ if(!fd_is_in_uid_cache(fd_ptr, (uid_t)current_user.uid)) { if(!check_access_allowed_for_current_user( fname, accmode )) { /* Error - permission denied. */ DEBUG(3,("Permission denied opening file %s (flags=%d, accmode = %d)\n", fname, flags, accmode)); /* Ensure the ref_count is decremented. */ fd_ptr->ref_count--; fd_remove_from_uid_cache(fd_ptr, (uid_t)current_user.uid); errno = EACCES; return; } } fd_add_to_uid_cache(fd_ptr, (uid_t)current_user.uid); /* * If not opened O_RDWR try * and do that here - a chmod may have been done * between the last open and now. */ if(fd_ptr->real_open_flags != O_RDWR) fd_attempt_reopen(fname, mode, fd_ptr); /* * Ensure that if we wanted write access * it has been opened for write, and if we wanted read it * was open for read. */ if(((accmode == O_WRONLY) && (fd_ptr->real_open_flags == O_RDONLY)) || ((accmode == O_RDONLY) && (fd_ptr->real_open_flags == O_WRONLY)) || ((accmode == O_RDWR) && (fd_ptr->real_open_flags != O_RDWR))) { DEBUG(3,("Error opening (already open for flags=%d) file %s (%s) (flags=%d)\n", fd_ptr->real_open_flags, fname,strerror(EACCES),flags)); check_for_pipe(fname); fd_remove_from_uid_cache(fd_ptr, (uid_t)current_user.uid); fd_ptr->ref_count--; return; } } else { int open_flags; /* We need to allocate a new file_fd_struct (this increments the ref_count). */ if((fd_ptr = fd_get_new()) == 0) return; /* * Whatever the requested flags, attempt read/write access, * as we don't know what flags future file opens may require. * If this fails, try again with the required flags. * Even if we open read/write when only read access was * requested the setting of the can_write flag in * the file_struct will protect us from errant * write requests. We never need to worry about O_APPEND * as this is not set anywhere in Samba. */ fd_ptr->real_open_flags = O_RDWR; /* Set the flags as needed without the read/write modes. */ open_flags = flags & ~(O_RDWR|O_WRONLY|O_RDONLY); fd_ptr->fd = fd_attempt_open(fname, open_flags|O_RDWR, mode); /* * On some systems opening a file for R/W access on a read only * filesystems sets errno to EROFS. */ #ifdef EROFS if((fd_ptr->fd == -1) && ((errno == EACCES) || (errno == EROFS))) { #else /* No EROFS */ if((fd_ptr->fd == -1) && (errno == EACCES)) { #endif /* EROFS */ if(accmode != O_RDWR) { fd_ptr->fd = fd_attempt_open(fname, open_flags|accmode, mode); fd_ptr->real_open_flags = accmode; } } } if ((fd_ptr->fd >=0) && conn->printer && lp_minprintspace(SNUM(conn))) { pstring dname; int dum1,dum2,dum3; char *p; pstrcpy(dname,fname); p = strrchr(dname,'/'); if (p) *p = 0; if (sys_disk_free(dname,&dum1,&dum2,&dum3) < lp_minprintspace(SNUM(conn))) { fd_attempt_close(fd_ptr); fsp->fd_ptr = 0; if(fd_ptr->ref_count == 0) sys_unlink(fname); errno = ENOSPC; return; } } if (fd_ptr->fd < 0) { DEBUG(3,("Error opening file %s (%s) (flags=%d)\n", fname,strerror(errno),flags)); /* Ensure the ref_count is decremented. */ fd_attempt_close(fd_ptr); check_for_pipe(fname); return; } if (fd_ptr->fd >= 0) { if(sbuf == 0) { /* Do the fstat */ if(fstat(fd_ptr->fd, &statbuf) == -1) { /* Error - backout !! */ DEBUG(3,("Error doing fstat on fd %d, file %s (%s)\n", fd_ptr->fd, fname,strerror(errno))); /* Ensure the ref_count is decremented. */ fd_attempt_close(fd_ptr); return; } sbuf = &statbuf; } /* Set the correct entries in fd_ptr. */ fd_ptr->dev = (uint32)sbuf->st_dev; fd_ptr->inode = (uint32)sbuf->st_ino; fsp->fd_ptr = fd_ptr; conn->num_files_open++; fsp->mode = sbuf->st_mode; GetTimeOfDay(&fsp->open_time); fsp->vuid = current_user.vuid; fsp->size = 0; fsp->pos = -1; fsp->open = True; fsp->mmap_ptr = NULL; fsp->mmap_size = 0; fsp->can_lock = True; fsp->can_read = ((flags & O_WRONLY)==0); fsp->can_write = ((flags & (O_WRONLY|O_RDWR))!=0); fsp->share_mode = 0; fsp->print_file = conn->printer; fsp->modified = False; fsp->granted_oplock = False; fsp->sent_oplock_break = False; fsp->is_directory = False; fsp->conn = conn; /* * Note that the file name here is the *untranslated* name * ie. it is still in the DOS codepage sent from the client. * All use of this filename will pass though the sys_xxxx * functions which will do the dos_to_unix translation before * mapping into a UNIX filename. JRA. */ string_set(&fsp->fsp_name,fname); fsp->wbmpx_ptr = NULL; /* * If the printer is marked as postscript output a leading * file identifier to ensure the file is treated as a raw * postscript file. * This has a similar effect as CtrlD=0 in WIN.INI file. * tim@fsg.com 09/06/94 */ if (fsp->print_file && lp_postscript(SNUM(conn)) && fsp->can_write) { DEBUG(3,("Writing postscript line\n")); write_file(fsp,"%!\n",3); } DEBUG(2,("%s opened file %s read=%s write=%s (numopen=%d)\n", *sesssetup_user ? sesssetup_user : conn->user,fname, BOOLSTR(fsp->can_read), BOOLSTR(fsp->can_write), conn->num_files_open)); } #if WITH_MMAP /* mmap it if read-only */ if (!fsp->can_write) { fsp->mmap_size = file_size(fname); fsp->mmap_ptr = (char *)mmap(NULL,fsp->mmap_size, PROT_READ,MAP_SHARED,fsp->fd_ptr->fd,0); if (fsp->mmap_ptr == (char *)-1 || !fsp->mmap_ptr) { DEBUG(3,("Failed to mmap() %s - %s\n", fname,strerror(errno))); fsp->mmap_ptr = NULL; } } #endif } /******************************************************************* sync a file ********************************************************************/ void sync_file(connection_struct *conn, files_struct *fsp) { #ifdef HAVE_FSYNC if(lp_strict_sync(SNUM(conn))) fsync(fsp->fd_ptr->fd); #endif } /**************************************************************************** run a file if it is a magic script ****************************************************************************/ static void check_magic(files_struct *fsp,connection_struct *conn) { if (!*lp_magicscript(SNUM(conn))) return; DEBUG(5,("checking magic for %s\n",fsp->fsp_name)); { char *p; if (!(p = strrchr(fsp->fsp_name,'/'))) p = fsp->fsp_name; else p++; if (!strequal(lp_magicscript(SNUM(conn)),p)) return; } { int ret; pstring magic_output; pstring fname; pstrcpy(fname,fsp->fsp_name); if (*lp_magicoutput(SNUM(conn))) pstrcpy(magic_output,lp_magicoutput(SNUM(conn))); else slprintf(magic_output,sizeof(fname)-1, "%s.out",fname); chmod(fname,0755); ret = smbrun(fname,magic_output,False); DEBUG(3,("Invoking magic command %s gave %d\n",fname,ret)); unlink(fname); } } /**************************************************************************** Common code to close a file or a directory. ****************************************************************************/ static void close_filestruct(files_struct *fsp) { connection_struct *conn = fsp->conn; fsp->open = False; fsp->is_directory = False; conn->num_files_open--; if(fsp->wbmpx_ptr) { free((char *)fsp->wbmpx_ptr); fsp->wbmpx_ptr = NULL; } #if WITH_MMAP if(fsp->mmap_ptr) { munmap(fsp->mmap_ptr,fsp->mmap_size); fsp->mmap_ptr = NULL; } #endif } /**************************************************************************** Close a file - possibly invalidating the read prediction. If normal_close is 1 then this came from a normal SMBclose (or equivalent) operation otherwise it came as the result of some other operation such as the closing of the connection. In the latter case printing and magic scripts are not run. ****************************************************************************/ void close_file(files_struct *fsp, BOOL normal_close) { uint32 dev = fsp->fd_ptr->dev; uint32 inode = fsp->fd_ptr->inode; int token; connection_struct *conn = fsp->conn; close_filestruct(fsp); #if USE_READ_PREDICTION invalidate_read_prediction(fsp->fd_ptr->fd); #endif if (lp_share_modes(SNUM(conn))) { lock_share_entry(conn, dev, inode, &token); del_share_mode(token, fsp); } fd_attempt_close(fsp->fd_ptr); if (lp_share_modes(SNUM(conn))) unlock_share_entry(conn, dev, inode, token); /* NT uses smbclose to start a print - weird */ if (normal_close && fsp->print_file) print_file(conn, fsp); /* check for magic scripts */ if (normal_close) { check_magic(fsp,conn); } if(fsp->granted_oplock == True) global_oplocks_open--; fsp->sent_oplock_break = False; DEBUG(2,("%s closed file %s (numopen=%d)\n", conn->user,fsp->fsp_name, conn->num_files_open)); if (fsp->fsp_name) { string_free(&fsp->fsp_name); } file_free(fsp); } /**************************************************************************** Close a directory opened by an NT SMB call. ****************************************************************************/ void close_directory(files_struct *fsp) { /* TODO - walk the list of pending change notify requests and free any pertaining to this fsp. */ remove_pending_change_notify_requests_by_fid(fsp); /* * Do the code common to files and directories. */ close_filestruct(fsp); if (fsp->fsp_name) string_free(&fsp->fsp_name); file_free(fsp); } /**************************************************************************** Open a directory from an NT SMB call. ****************************************************************************/ int open_directory(files_struct *fsp,connection_struct *conn, char *fname, int smb_ofun, int unixmode, int *action) { extern struct current_user current_user; struct stat st; if (smb_ofun & 0x10) { /* * Create the directory. */ if(sys_mkdir(fname, unixmode) < 0) { DEBUG(0,("open_directory: unable to create %s. Error was %s\n", fname, strerror(errno) )); return -1; } *action = FILE_WAS_CREATED; } else { /* * Check that it *was* a directory. */ if(sys_stat(fname, &st) < 0) { DEBUG(0,("open_directory: unable to stat name = %s. Error was %s\n", fname, strerror(errno) )); return -1; } if(!S_ISDIR(st.st_mode)) { DEBUG(0,("open_directory: %s is not a directory !\n", fname )); return -1; } *action = FILE_WAS_OPENED; } DEBUG(5,("open_directory: opening directory %s\n", fname)); /* * Setup the files_struct for it. */ fsp->fd_ptr = NULL; conn->num_files_open++; fsp->mode = 0; GetTimeOfDay(&fsp->open_time); fsp->vuid = current_user.vuid; fsp->size = 0; fsp->pos = -1; fsp->open = True; fsp->mmap_ptr = NULL; fsp->mmap_size = 0; fsp->can_lock = True; fsp->can_read = False; fsp->can_write = False; fsp->share_mode = 0; fsp->print_file = False; fsp->modified = False; fsp->granted_oplock = False; fsp->sent_oplock_break = False; fsp->is_directory = True; fsp->conn = conn; /* * Note that the file name here is the *untranslated* name * ie. it is still in the DOS codepage sent from the client. * All use of this filename will pass though the sys_xxxx * functions which will do the dos_to_unix translation before * mapping into a UNIX filename. JRA. */ string_set(&fsp->fsp_name,fname); fsp->wbmpx_ptr = NULL; return 0; } enum {AFAIL,AREAD,AWRITE,AALL}; /******************************************************************* reproduce the share mode access table ********************************************************************/ static int access_table(int new_deny,int old_deny,int old_mode, int share_pid,char *fname) { if (new_deny == DENY_ALL || old_deny == DENY_ALL) return(AFAIL); if (new_deny == DENY_DOS || old_deny == DENY_DOS) { int pid = getpid(); if (old_deny == new_deny && share_pid == pid) return(AALL); if (old_mode == 0) return(AREAD); /* the new smbpub.zip spec says that if the file extension is .com, .dll, .exe or .sym then allow the open. I will force it to read-only as this seems sensible although the spec is a little unclear on this. */ if ((fname = strrchr(fname,'.'))) { if (strequal(fname,".com") || strequal(fname,".dll") || strequal(fname,".exe") || strequal(fname,".sym")) return(AREAD); } return(AFAIL); } switch (new_deny) { case DENY_WRITE: if (old_deny==DENY_WRITE && old_mode==0) return(AREAD); if (old_deny==DENY_READ && old_mode==0) return(AWRITE); if (old_deny==DENY_NONE && old_mode==0) return(AALL); return(AFAIL); case DENY_READ: if (old_deny==DENY_WRITE && old_mode==1) return(AREAD); if (old_deny==DENY_READ && old_mode==1) return(AWRITE); if (old_deny==DENY_NONE && old_mode==1) return(AALL); return(AFAIL); case DENY_NONE: if (old_deny==DENY_WRITE) return(AREAD); if (old_deny==DENY_READ) return(AWRITE); if (old_deny==DENY_NONE) return(AALL); return(AFAIL); } return(AFAIL); } /******************************************************************* check if the share mode on a file allows it to be deleted or unlinked return True if sharing doesn't prevent the operation ********************************************************************/ BOOL check_file_sharing(connection_struct *conn,char *fname, BOOL rename_op) { int i; int ret = False; share_mode_entry *old_shares = 0; int num_share_modes; struct stat sbuf; int token; int pid = getpid(); uint32 dev, inode; if(!lp_share_modes(SNUM(conn))) return True; if (sys_stat(fname,&sbuf) == -1) return(True); dev = (uint32)sbuf.st_dev; inode = (uint32)sbuf.st_ino; lock_share_entry(conn, dev, inode, &token); num_share_modes = get_share_modes(conn, token, dev, inode, &old_shares); /* * Check if the share modes will give us access. */ if(num_share_modes != 0) { BOOL broke_oplock; do { broke_oplock = False; for(i = 0; i < num_share_modes; i++) { share_mode_entry *share_entry = &old_shares[i]; /* * Break oplocks before checking share modes. See comment in * open_file_shared for details. * Check if someone has an oplock on this file. If so we must * break it before continuing. */ if(share_entry->op_type & BATCH_OPLOCK) { /* * It appears that the NT redirector may have a bug, in that * it tries to do an SMBmv on a file that it has open with a * batch oplock, and then fails to respond to the oplock break * request. This only seems to occur when the client is doing an * SMBmv to the smbd it is using - thus we try and detect this * condition by checking if the file being moved is open and oplocked by * this smbd process, and then not sending the oplock break in this * special case. If the file was open with a deny mode that * prevents the move the SMBmv will fail anyway with a share * violation error. JRA. */ if(rename_op && (share_entry->pid == pid)) { DEBUG(0,("check_file_sharing: NT redirector workaround - rename attempted on \ batch oplocked file %s, dev = %x, inode = %x\n", fname, dev, inode)); /* * This next line is a test that allows the deny-mode * processing to be skipped. This seems to be needed as * NT insists on the rename succeeding (in Office 9x no less !). * This should be removed as soon as (a) MS fix the redirector * bug or (b) NT SMB support in Samba makes NT not issue the * call (as is my fervent hope). JRA. */ continue; } else { DEBUG(5,("check_file_sharing: breaking oplock (%x) on file %s, \ dev = %x, inode = %x\n", share_entry->op_type, fname, dev, inode)); /* Oplock break.... */ unlock_share_entry(conn, dev, inode, token); if(request_oplock_break(share_entry, dev, inode) == False) { free((char *)old_shares); DEBUG(0,("check_file_sharing: FAILED when breaking oplock (%x) on file %s, \ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode)); return False; } lock_share_entry(conn, dev, inode, &token); broke_oplock = True; break; } } /* someone else has a share lock on it, check to see if we can too */ if ((share_entry->share_mode != DENY_DOS) || (share_entry->pid != pid)) goto free_and_exit; } /* end for */ if(broke_oplock) { free((char *)old_shares); num_share_modes = get_share_modes(conn, token, dev, inode, &old_shares); } } while(broke_oplock); } /* XXXX exactly what share mode combinations should be allowed for deleting/renaming? */ /* If we got here then either there were no share modes or all share modes were DENY_DOS and the pid == getpid() */ ret = True; free_and_exit: unlock_share_entry(conn, dev, inode, token); if(old_shares != NULL) free((char *)old_shares); return(ret); } /**************************************************************************** C. Hoch 11/22/95 Helper for open_file_shared. Truncate a file after checking locking; close file if locked. **************************************************************************/ static void truncate_unless_locked(files_struct *fsp, connection_struct *conn, int token, BOOL *share_locked) { if (fsp->can_write){ if (is_locked(fsp,conn,0x3FFFFFFF,0,F_WRLCK)){ /* If share modes are in force for this connection we have the share entry locked. Unlock it before closing. */ if (*share_locked && lp_share_modes(SNUM(conn))) unlock_share_entry( conn, fsp->fd_ptr->dev, fsp->fd_ptr->inode, token); close_file(fsp,False); /* Share mode no longer locked. */ *share_locked = False; errno = EACCES; unix_ERR_class = ERRDOS; unix_ERR_code = ERRlock; } else ftruncate(fsp->fd_ptr->fd,0); } } /**************************************************************************** check if we can open a file with a share mode ****************************************************************************/ int check_share_mode( share_mode_entry *share, int deny_mode, char *fname, BOOL fcbopen, int *flags) { int old_open_mode = share->share_mode &0xF; int old_deny_mode = (share->share_mode >>4)&7; if (old_deny_mode > 4 || old_open_mode > 2) { DEBUG(0,("Invalid share mode found (%d,%d,%d) on file %s\n", deny_mode,old_deny_mode,old_open_mode,fname)); return False; } { int access_allowed = access_table(deny_mode,old_deny_mode,old_open_mode, share->pid,fname); if ((access_allowed == AFAIL) || (!fcbopen && (access_allowed == AREAD && *flags == O_RDWR)) || (access_allowed == AREAD && *flags == O_WRONLY) || (access_allowed == AWRITE && *flags == O_RDONLY)) { DEBUG(2,("Share violation on file (%d,%d,%d,%d,%s,fcbopen = %d, flags = %d) = %d\n", deny_mode,old_deny_mode,old_open_mode, share->pid,fname, fcbopen, *flags, access_allowed)); return False; } if (access_allowed == AREAD) *flags = O_RDONLY; if (access_allowed == AWRITE) *flags = O_WRONLY; } return True; } /**************************************************************************** open a file with a share mode ****************************************************************************/ void open_file_shared(files_struct *fsp,connection_struct *conn,char *fname,int share_mode,int ofun, int mode,int oplock_request, int *Access,int *action) { int flags=0; int flags2=0; int deny_mode = (share_mode>>4)&7; struct stat sbuf; BOOL file_existed = file_exist(fname,&sbuf); BOOL share_locked = False; BOOL fcbopen = False; int token; uint32 dev = 0; uint32 inode = 0; int num_share_modes = 0; fsp->open = False; fsp->fd_ptr = 0; /* this is for OS/2 EAs - try and say we don't support them */ if (strstr(fname,".+,;=[].")) { unix_ERR_class = ERRDOS; /* OS/2 Workplace shell fix may be main code stream in a later release. */ #if 1 /* OS2_WPS_FIX - Recent versions of OS/2 need this. */ unix_ERR_code = ERRcannotopen; #else /* OS2_WPS_FIX */ unix_ERR_code = ERROR_EAS_NOT_SUPPORTED; #endif /* OS2_WPS_FIX */ return; } if ((ofun & 0x3) == 0 && file_existed) { errno = EEXIST; return; } if (ofun & 0x10) flags2 |= O_CREAT; if ((ofun & 0x3) == 2) flags2 |= O_TRUNC; /* note that we ignore the append flag as append does not mean the same thing under dos and unix */ switch (share_mode&0xF) { case 1: flags = O_WRONLY; break; case 0xF: fcbopen = True; flags = O_RDWR; break; case 2: flags = O_RDWR; break; default: flags = O_RDONLY; break; } #if defined(O_SYNC) if (share_mode&(1<<14)) { flags2 |= O_SYNC; } #endif /* O_SYNC */ if (flags != O_RDONLY && file_existed && (!CAN_WRITE(conn) || IS_DOS_READONLY(dos_mode(conn,fname,&sbuf)))) { if (!fcbopen) { errno = EACCES; return; } flags = O_RDONLY; } if (deny_mode > DENY_NONE && deny_mode!=DENY_FCB) { DEBUG(2,("Invalid deny mode %d on file %s\n",deny_mode,fname)); errno = EINVAL; return; } if (deny_mode == DENY_FCB) deny_mode = DENY_DOS; if (lp_share_modes(SNUM(conn))) { int i; share_mode_entry *old_shares = 0; if (file_existed) { dev = (uint32)sbuf.st_dev; inode = (uint32)sbuf.st_ino; lock_share_entry(conn, dev, inode, &token); share_locked = True; num_share_modes = get_share_modes(conn, token, dev, inode, &old_shares); } /* * Check if the share modes will give us access. */ if(share_locked && (num_share_modes != 0)) { BOOL broke_oplock; do { broke_oplock = False; for(i = 0; i < num_share_modes; i++) { share_mode_entry *share_entry = &old_shares[i]; /* * By observation of NetBench, oplocks are broken *before* share * modes are checked. This allows a file to be closed by the client * if the share mode would deny access and the client has an oplock. * Check if someone has an oplock on this file. If so we must break * it before continuing. */ if(share_entry->op_type & (EXCLUSIVE_OPLOCK|BATCH_OPLOCK)) { DEBUG(5,("open_file_shared: breaking oplock (%x) on file %s, \ dev = %x, inode = %x\n", share_entry->op_type, fname, dev, inode)); /* Oplock break.... */ unlock_share_entry(conn, dev, inode, token); if(request_oplock_break(share_entry, dev, inode) == False) { free((char *)old_shares); DEBUG(0,("open_file_shared: FAILED when breaking oplock (%x) on file %s, \ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode)); errno = EACCES; unix_ERR_class = ERRDOS; unix_ERR_code = ERRbadshare; return; } lock_share_entry(conn, dev, inode, &token); broke_oplock = True; break; } /* someone else has a share lock on it, check to see if we can too */ if(check_share_mode(share_entry, deny_mode, fname, fcbopen, &flags) == False) { free((char *)old_shares); unlock_share_entry(conn, dev, inode, token); errno = EACCES; unix_ERR_class = ERRDOS; unix_ERR_code = ERRbadshare; return; } } /* end for */ if(broke_oplock) { free((char *)old_shares); num_share_modes = get_share_modes(conn, token, dev, inode, &old_shares); } } while(broke_oplock); } if(old_shares != 0) free((char *)old_shares); } DEBUG(4,("calling open_file with flags=0x%X flags2=0x%X mode=0%o\n", flags,flags2,mode)); open_file(fsp,conn,fname,flags|(flags2&~(O_TRUNC)),mode,file_existed ? &sbuf : 0); if (!fsp->open && flags==O_RDWR && errno!=ENOENT && fcbopen) { flags = O_RDONLY; open_file(fsp,conn,fname,flags,mode,file_existed ? &sbuf : 0 ); } if (fsp->open) { int open_mode=0; if((share_locked == False) && lp_share_modes(SNUM(conn))) { /* We created the file - thus we must now lock the share entry before creating it. */ dev = fsp->fd_ptr->dev; inode = fsp->fd_ptr->inode; lock_share_entry(conn, dev, inode, &token); share_locked = True; } switch (flags) { case O_RDONLY: open_mode = 0; break; case O_RDWR: open_mode = 2; break; case O_WRONLY: open_mode = 1; break; } fsp->share_mode = (deny_mode<<4) | open_mode; if (Access) (*Access) = open_mode; if (action) { if (file_existed && !(flags2 & O_TRUNC)) *action = FILE_WAS_OPENED; if (!file_existed) *action = FILE_WAS_CREATED; if (file_existed && (flags2 & O_TRUNC)) *action = FILE_WAS_OVERWRITTEN; } /* We must create the share mode entry before truncate as truncate can fail due to locking and have to close the file (which expects the share_mode_entry to be there). */ if (lp_share_modes(SNUM(conn))) { uint16 port = 0; /* JRA. Currently this only services Exlcusive and batch oplocks (no other opens on this file). This needs to be extended to level II oplocks (multiple reader oplocks). */ if(oplock_request && (num_share_modes == 0) && lp_oplocks(SNUM(conn)) && !IS_VETO_OPLOCK_PATH(conn,fname)) { fsp->granted_oplock = True; fsp->sent_oplock_break = False; global_oplocks_open++; port = oplock_port; DEBUG(5,("open_file_shared: granted oplock (%x) on file %s, \ dev = %x, inode = %x\n", oplock_request, fname, dev, inode)); } else { port = 0; oplock_request = 0; } set_share_mode(token, fsp, port, oplock_request); } if ((flags2&O_TRUNC) && file_existed) truncate_unless_locked(fsp,conn,token,&share_locked); } if (share_locked && lp_share_modes(SNUM(conn))) unlock_share_entry( conn, dev, inode, token); } /**************************************************************************** load parameters specific to a connection/service ****************************************************************************/ BOOL become_service(connection_struct *conn,BOOL do_chdir) { extern char magic_char; static connection_struct *last_conn; int snum; if (!conn) { last_conn = NULL; return(False); } conn->lastused = smb_last_time; snum = SNUM(conn); if (do_chdir && ChDir(conn->connectpath) != 0 && ChDir(conn->origpath) != 0) { DEBUG(0,("chdir (%s) failed\n", conn->connectpath)); return(False); } if (conn == last_conn) return(True); last_conn = conn; case_default = lp_defaultcase(snum); case_preserve = lp_preservecase(snum); short_case_preserve = lp_shortpreservecase(snum); case_mangle = lp_casemangle(snum); case_sensitive = lp_casesensitive(snum); magic_char = lp_magicchar(snum); use_mangled_map = (*lp_mangled_map(snum) ? True:False); return(True); } /**************************************************************************** find a service entry ****************************************************************************/ int find_service(char *service) { int iService; string_sub(service,"\\","/"); iService = lp_servicenumber(service); /* now handle the special case of a home directory */ if (iService < 0) { char *phome_dir = get_home_dir(service); if(!phome_dir) { /* * Try mapping the servicename, it may * be a Windows to unix mapped user name. */ if(map_username(service)) phome_dir = get_home_dir(service); } DEBUG(3,("checking for home directory %s gave %s\n",service, phome_dir?phome_dir:"(NULL)")); if (phome_dir) { int iHomeService; if ((iHomeService = lp_servicenumber(HOMES_NAME)) >= 0) { lp_add_home(service,iHomeService,phome_dir); iService = lp_servicenumber(service); } } } /* If we still don't have a service, attempt to add it as a printer. */ if (iService < 0) { int iPrinterService; if ((iPrinterService = lp_servicenumber(PRINTERS_NAME)) >= 0) { char *pszTemp; DEBUG(3,("checking whether %s is a valid printer name...\n", service)); pszTemp = PRINTCAP; if ((pszTemp != NULL) && pcap_printername_ok(service, pszTemp)) { DEBUG(3,("%s is a valid printer name\n", service)); DEBUG(3,("adding %s as a printer service\n", service)); lp_add_printer(service,iPrinterService); iService = lp_servicenumber(service); if (iService < 0) DEBUG(0,("failed to add %s as a printer service!\n", service)); } else DEBUG(3,("%s is not a valid printer name\n", service)); } } /* just possibly it's a default service? */ if (iService < 0) { char *pdefservice = lp_defaultservice(); if (pdefservice && *pdefservice && !strequal(pdefservice,service)) { /* * We need to do a local copy here as lp_defaultservice() * returns one of the rotating lp_string buffers that * could get overwritten by the recursive find_service() call * below. Fix from Josef Hinteregger . */ pstring defservice; pstrcpy(defservice, pdefservice); iService = find_service(defservice); if (iService >= 0) { string_sub(service,"_","/"); iService = lp_add_service(service,iService); } } } if (iService >= 0) if (!VALID_SNUM(iService)) { DEBUG(0,("Invalid snum %d for %s\n",iService,service)); iService = -1; } if (iService < 0) DEBUG(3,("find_service() failed to find service %s\n", service)); return (iService); } /**************************************************************************** create an error packet from a cached error. ****************************************************************************/ int cached_error_packet(char *inbuf,char *outbuf,files_struct *fsp,int line) { write_bmpx_struct *wbmpx = fsp->wbmpx_ptr; int32 eclass = wbmpx->wr_errclass; int32 err = wbmpx->wr_error; /* We can now delete the auxiliary struct */ free((char *)wbmpx); fsp->wbmpx_ptr = NULL; return error_packet(inbuf,outbuf,eclass,err,line); } struct { int unixerror; int smbclass; int smbcode; } unix_smb_errmap[] = { {EPERM,ERRDOS,ERRnoaccess}, {EACCES,ERRDOS,ERRnoaccess}, {ENOENT,ERRDOS,ERRbadfile}, {ENOTDIR,ERRDOS,ERRbadpath}, {EIO,ERRHRD,ERRgeneral}, {EBADF,ERRSRV,ERRsrverror}, {EINVAL,ERRSRV,ERRsrverror}, {EEXIST,ERRDOS,ERRfilexists}, {ENFILE,ERRDOS,ERRnofids}, {EMFILE,ERRDOS,ERRnofids}, {ENOSPC,ERRHRD,ERRdiskfull}, #ifdef EDQUOT {EDQUOT,ERRHRD,ERRdiskfull}, #endif #ifdef ENOTEMPTY {ENOTEMPTY,ERRDOS,ERRnoaccess}, #endif #ifdef EXDEV {EXDEV,ERRDOS,ERRdiffdevice}, #endif {EROFS,ERRHRD,ERRnowrite}, {0,0,0} }; /**************************************************************************** create an error packet from errno ****************************************************************************/ int unix_error_packet(char *inbuf,char *outbuf,int def_class,uint32 def_code,int line) { int eclass=def_class; int ecode=def_code; int i=0; if (unix_ERR_class != SMB_SUCCESS) { eclass = unix_ERR_class; ecode = unix_ERR_code; unix_ERR_class = SMB_SUCCESS; unix_ERR_code = 0; } else { while (unix_smb_errmap[i].smbclass != 0) { if (unix_smb_errmap[i].unixerror == errno) { eclass = unix_smb_errmap[i].smbclass; ecode = unix_smb_errmap[i].smbcode; break; } i++; } } return(error_packet(inbuf,outbuf,eclass,ecode,line)); } /**************************************************************************** create an error packet. Normally called using the ERROR() macro ****************************************************************************/ int error_packet(char *inbuf,char *outbuf,int error_class,uint32 error_code,int line) { int outsize = set_message(outbuf,0,0,True); int cmd = CVAL(inbuf,smb_com); int flgs2 = SVAL(outbuf,smb_flg2); if ((flgs2 & FLAGS2_32_BIT_ERROR_CODES) == FLAGS2_32_BIT_ERROR_CODES) { SIVAL(outbuf,smb_rcls,error_code); DEBUG( 3, ( "32 bit error packet at line %d cmd=%d (%s) eclass=%08x [%s]\n", line, cmd, smb_fn_name(cmd), error_code, smb_errstr(outbuf) ) ); } else { CVAL(outbuf,smb_rcls) = error_class; SSVAL(outbuf,smb_err,error_code); DEBUG( 3, ( "error packet at line %d cmd=%d (%s) eclass=%d ecode=%d\n", line, (int)CVAL(inbuf,smb_com), smb_fn_name(CVAL(inbuf,smb_com)), error_class, error_code ) ); } if (errno != 0) DEBUG(3,("error string = %s\n",strerror(errno))); return(outsize); } /**************************************************************************** this is called when the client exits abruptly **************************************************************************/ static void sig_pipe(int sig) { struct cli_state *cli; BlockSignals(True,SIGPIPE); if ((cli = server_client()) && cli->initialised) { DEBUG(3,("lost connection to password server\n")); cli_shutdown(cli); BlockSignals(False,SIGPIPE); return; } exit_server("Got sigpipe\n"); } /**************************************************************************** open the socket communication ****************************************************************************/ static BOOL open_sockets(BOOL is_daemon,int port) { extern int Client; if (is_daemon) { int num_interfaces = iface_count(); int fd_listenset[FD_SETSIZE]; fd_set listen_set; int s; int i; #ifdef HAVE_ATEXIT static int atexit_set; if(atexit_set == 0) { atexit_set=1; atexit(killkids); } #endif /* Stop zombies */ CatchChild(); FD_ZERO(&listen_set); if(lp_interfaces() && lp_bind_interfaces_only()) { /* We have been given an interfaces line, and been told to only bind to those interfaces. Create a socket per interface and bind to only these. */ if(num_interfaces > FD_SETSIZE) { DEBUG(0,("open_sockets: Too many interfaces specified to bind to. Number was %d \ max can be %d\n", num_interfaces, FD_SETSIZE)); return False; } /* Now open a listen socket for each of the interfaces. */ for(i = 0; i < num_interfaces; i++) { struct in_addr *ifip = iface_n_ip(i); if(ifip == NULL) { DEBUG(0,("open_sockets: interface %d has NULL IP address !\n", i)); continue; } s = fd_listenset[i] = open_socket_in(SOCK_STREAM, port, 0, ifip->s_addr); if(s == -1) return False; /* ready to listen */ if (listen(s, 5) == -1) { DEBUG(0,("listen: %s\n",strerror(errno))); close(s); return False; } FD_SET(s,&listen_set); } } else { /* Just bind to 0.0.0.0 - accept connections from anywhere. */ num_interfaces = 1; /* open an incoming socket */ s = open_socket_in(SOCK_STREAM, port, 0,interpret_addr(lp_socket_address())); if (s == -1) return(False); /* ready to listen */ if (listen(s, 5) == -1) { DEBUG(0,("open_sockets: listen: %s\n",strerror(errno))); close(s); return False; } fd_listenset[0] = s; FD_SET(s,&listen_set); } /* now accept incoming connections - forking a new process for each incoming connection */ DEBUG(2,("waiting for a connection\n")); while (1) { fd_set lfds; int num; memcpy((char *)&lfds, (char *)&listen_set, sizeof(listen_set)); num = sys_select(&lfds,NULL); if (num == -1 && errno == EINTR) continue; /* Find the sockets that are read-ready - accept on these. */ for( ; num > 0; num--) { struct sockaddr addr; int in_addrlen = sizeof(addr); s = -1; for(i = 0; i < num_interfaces; i++) { if(FD_ISSET(fd_listenset[i],&lfds)) { s = fd_listenset[i]; /* Clear this so we don't look at it again. */ FD_CLR(fd_listenset[i],&lfds); break; } } Client = accept(s,&addr,&in_addrlen); if (Client == -1 && errno == EINTR) continue; if (Client == -1) { DEBUG(0,("open_sockets: accept: %s\n",strerror(errno))); continue; } if (Client != -1 && fork()==0) { /* Child code ... */ CatchSignal(SIGPIPE, SIGNAL_CAST sig_pipe); /* close the listening socket(s) */ for(i = 0; i < num_interfaces; i++) close(fd_listenset[i]); /* close our standard file descriptors */ close_low_fds(); am_parent = 0; set_socket_options(Client,"SO_KEEPALIVE"); set_socket_options(Client,user_socket_options); /* Reset global variables in util.c so that client substitutions will be done correctly in the process. */ reset_globals_after_fork(); return True; } close(Client); /* The parent doesn't need this socket */ /* * Force parent to check log size after spawning child. * Fix from klausr@ITAP.Physik.Uni-Stuttgart.De. * The parent smbd will log to logserver.smb. * It writes only two messages for each child * started/finished. But each child writes, say, 50 messages also in * logserver.smb, begining with the debug_count of the parent, before the * child opens its own log file logserver.client. In a worst case * scenario the size of logserver.smb would be checked after about * 50*50=2500 messages (ca. 100kb). */ force_check_log_size(); } /* end for num */ } /* end while 1 */ } /* end if is_daemon */ else { /* Started from inetd. fd 0 is the socket. */ /* We will abort gracefully when the client or remote system goes away */ CatchSignal(SIGPIPE, SIGNAL_CAST sig_pipe); Client = dup(0); /* close our standard file descriptors */ close_low_fds(); set_socket_options(Client,"SO_KEEPALIVE"); set_socket_options(Client,user_socket_options); } return True; } /**************************************************************************** process an smb from the client - split out from the process() code so it can be used by the oplock break code. ****************************************************************************/ static void process_smb(char *inbuf, char *outbuf) { extern int Client; #ifdef WITH_SSL extern BOOL sslEnabled; /* don't use function for performance reasons */ static int sslConnected = 0; #endif /* WITH_SSL */ static int trans_num; int msg_type = CVAL(inbuf,0); int32 len = smb_len(inbuf); int nread = len + 4; if (trans_num == 0) { /* on the first packet, check the global hosts allow/ hosts deny parameters before doing any parsing of the packet passed to us by the client. This prevents attacks on our parsing code from hosts not in the hosts allow list */ if (!check_access(Client, lp_hostsallow(-1), lp_hostsdeny(-1))) { /* send a negative session response "not listining on calling name" */ static unsigned char buf[5] = {0x83, 0, 0, 1, 0x81}; DEBUG( 1, ( "Connection denied from %s\n", client_addr(Client) ) ); send_smb(Client,(char *)buf); exit_server("connection denied"); } } DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type, len ) ); DEBUG( 3, ( "Transaction %d of length %d\n", trans_num, nread ) ); #ifdef WITH_SSL if(sslEnabled && !sslConnected){ sslConnected = sslutil_negotiate_ssl(Client, msg_type); if(sslConnected < 0){ /* an error occured */ exit_server("SSL negotiation failed"); }else if(sslConnected){ trans_num++; return; } } #endif /* WITH_SSL */ #ifdef WITH_VTP if(trans_num == 1 && VT_Check(inbuf)) { VT_Process(); return; } #endif if (msg_type == 0) show_msg(inbuf); else if(msg_type == 0x85) return; /* Keepalive packet. */ nread = construct_reply(inbuf,outbuf,nread,max_send); if(nread > 0) { if (CVAL(outbuf,0) == 0) show_msg(outbuf); if (nread != smb_len(outbuf) + 4) { DEBUG(0,("ERROR: Invalid message response size! %d %d\n", nread, smb_len(outbuf))); } else send_smb(Client,outbuf); } trans_num++; } /**************************************************************************** open the oplock IPC socket communication ****************************************************************************/ static BOOL open_oplock_ipc(void) { struct sockaddr_in sock_name; int len = sizeof(sock_name); DEBUG(3,("open_oplock_ipc: opening loopback UDP socket.\n")); /* Open a lookback UDP socket on a random port. */ oplock_sock = open_socket_in(SOCK_DGRAM, 0, 0, htonl(INADDR_LOOPBACK)); if (oplock_sock == -1) { DEBUG(0,("open_oplock_ipc: Failed to get local UDP socket for \ address %x. Error was %s\n", htonl(INADDR_LOOPBACK), strerror(errno))); oplock_port = 0; return(False); } /* Find out the transient UDP port we have been allocated. */ if(getsockname(oplock_sock, (struct sockaddr *)&sock_name, &len)<0) { DEBUG(0,("open_oplock_ipc: Failed to get local UDP port. Error was %s\n", strerror(errno))); close(oplock_sock); oplock_sock = -1; oplock_port = 0; return False; } oplock_port = ntohs(sock_name.sin_port); DEBUG(3,("open_oplock ipc: pid = %d, oplock_port = %u\n", (int)getpid(), oplock_port)); return True; } /**************************************************************************** process an oplock break message. ****************************************************************************/ static BOOL process_local_message(int sock, char *buffer, int buf_size) { int32 msg_len; uint16 from_port; char *msg_start; msg_len = IVAL(buffer,UDP_CMD_LEN_OFFSET); from_port = SVAL(buffer,UDP_CMD_PORT_OFFSET); msg_start = &buffer[UDP_CMD_HEADER_LEN]; DEBUG(5,("process_local_message: Got a message of length %d from port (%d)\n", msg_len, from_port)); /* Switch on message command - currently OPLOCK_BREAK_CMD is the only valid request. */ switch(SVAL(msg_start,UDP_MESSAGE_CMD_OFFSET)) { case OPLOCK_BREAK_CMD: /* Ensure that the msg length is correct. */ if(msg_len != OPLOCK_BREAK_MSG_LEN) { DEBUG(0,("process_local_message: incorrect length for OPLOCK_BREAK_CMD (was %d, \ should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN)); return False; } { uint32 remotepid = IVAL(msg_start,OPLOCK_BREAK_PID_OFFSET); uint32 dev = IVAL(msg_start,OPLOCK_BREAK_DEV_OFFSET); uint32 inode = IVAL(msg_start, OPLOCK_BREAK_INODE_OFFSET); struct timeval tval; struct sockaddr_in toaddr; tval.tv_sec = IVAL(msg_start, OPLOCK_BREAK_SEC_OFFSET); tval.tv_usec = IVAL(msg_start, OPLOCK_BREAK_USEC_OFFSET); DEBUG(5,("process_local_message: oplock break request from \ pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode)); /* * If we have no record of any currently open oplocks, * it's not an error, as a close command may have * just been issued on the file that was oplocked. * Just return success in this case. */ if(global_oplocks_open != 0) { if(oplock_break(dev, inode, &tval) == False) { DEBUG(0,("process_local_message: oplock break failed - \ not returning udp message.\n")); return False; } } else { DEBUG(3,("process_local_message: oplock break requested with no outstanding \ oplocks. Returning success.\n")); } /* Send the message back after OR'ing in the 'REPLY' bit. */ SSVAL(msg_start,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD | CMD_REPLY); bzero((char *)&toaddr,sizeof(toaddr)); toaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); toaddr.sin_port = htons(from_port); toaddr.sin_family = AF_INET; if(sendto( sock, msg_start, OPLOCK_BREAK_MSG_LEN, 0, (struct sockaddr *)&toaddr, sizeof(toaddr)) < 0) { DEBUG(0,("process_local_message: sendto process %d failed. Errno was %s\n", remotepid, strerror(errno))); return False; } DEBUG(5,("process_local_message: oplock break reply sent to \ pid %d, port %d, for file dev = %x, inode = %x\n", remotepid, from_port, dev, inode)); } break; /* * Keep this as a debug case - eventually we can remove it. */ case 0x8001: DEBUG(0,("process_local_message: Received unsolicited break \ reply - dumping info.\n")); if(msg_len != OPLOCK_BREAK_MSG_LEN) { DEBUG(0,("process_local_message: ubr: incorrect length for reply \ (was %d, should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN)); return False; } { uint32 remotepid = IVAL(msg_start,OPLOCK_BREAK_PID_OFFSET); uint32 dev = IVAL(msg_start,OPLOCK_BREAK_DEV_OFFSET); uint32 inode = IVAL(msg_start, OPLOCK_BREAK_INODE_OFFSET); DEBUG(0,("process_local_message: unsolicited oplock break reply from \ pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode)); } return False; default: DEBUG(0,("process_local_message: unknown UDP message command code (%x) - ignoring.\n", (unsigned int)SVAL(msg_start,0))); return False; } return True; } /**************************************************************************** Process an oplock break directly. ****************************************************************************/ BOOL oplock_break(uint32 dev, uint32 inode, struct timeval *tval) { extern struct current_user current_user; extern int Client; char *inbuf = NULL; char *outbuf = NULL; files_struct *fsp = NULL; time_t start_time; BOOL shutdown_server = False; connection_struct *saved_conn; int saved_vuid; pstring saved_dir; if( DEBUGLVL( 3 ) ) { dbgtext( "oplock_break: called for dev = %x, inode = %x.\n", dev, inode ); dbgtext( "Current global_oplocks_open = %d\n", global_oplocks_open ); } /* We need to search the file open table for the entry containing this dev and inode, and ensure we have an oplock on it. */ fsp = file_find_dit(dev, inode, tval); if(fsp == NULL) { /* The file could have been closed in the meantime - return success. */ if( DEBUGLVL( 0 ) ) { dbgtext( "oplock_break: cannot find open file with " ); dbgtext( "dev = %x, inode = %x ", dev, inode); dbgtext( "allowing break to succeed.\n" ); } return True; } /* Ensure we have an oplock on the file */ /* There is a potential race condition in that an oplock could have been broken due to another udp request, and yet there are still oplock break messages being sent in the udp message queue for this file. So return true if we don't have an oplock, as we may have just freed it. */ if(!fsp->granted_oplock) { if( DEBUGLVL( 0 ) ) { dbgtext( "oplock_break: file %s ", fsp->fsp_name ); dbgtext( "(dev = %x, inode = %x) has no oplock.\n", dev, inode ); dbgtext( "Allowing break to succeed regardless.\n" ); } return True; } /* mark the oplock break as sent - we don't want to send twice! */ if (fsp->sent_oplock_break) { if( DEBUGLVL( 0 ) ) { dbgtext( "oplock_break: ERROR: oplock_break already sent for " ); dbgtext( "file %s ", fsp->fsp_name); dbgtext( "(dev = %x, inode = %x)\n", dev, inode ); } /* We have to fail the open here as we cannot send another oplock break on this file whilst we are awaiting a response from the client - neither can we allow another open to succeed while we are waiting for the client. */ return False; } /* Now comes the horrid part. We must send an oplock break to the client, and then process incoming messages until we get a close or oplock release. At this point we know we need a new inbuf/outbuf buffer pair. We cannot use these staticaly as we may recurse into here due to messages crossing on the wire. */ if((inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN))==NULL) { DEBUG(0,("oplock_break: malloc fail for input buffer.\n")); return False; } if((outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN))==NULL) { DEBUG(0,("oplock_break: malloc fail for output buffer.\n")); free(inbuf); inbuf = NULL; return False; } /* Prepare the SMBlockingX message. */ bzero(outbuf,smb_size); set_message(outbuf,8,0,True); SCVAL(outbuf,smb_com,SMBlockingX); SSVAL(outbuf,smb_tid,fsp->conn->cnum); SSVAL(outbuf,smb_pid,0xFFFF); SSVAL(outbuf,smb_uid,0); SSVAL(outbuf,smb_mid,0xFFFF); SCVAL(outbuf,smb_vwv0,0xFF); SSVAL(outbuf,smb_vwv2,fsp->fnum); SCVAL(outbuf,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE); /* Change this when we have level II oplocks. */ SCVAL(outbuf,smb_vwv3+1,OPLOCKLEVEL_NONE); send_smb(Client, outbuf); /* Remember we just sent an oplock break on this file. */ fsp->sent_oplock_break = True; /* We need this in case a readraw crosses on the wire. */ global_oplock_break = True; /* Process incoming messages. */ /* JRA - If we don't get a break from the client in OPLOCK_BREAK_TIMEOUT seconds we should just die.... */ start_time = time(NULL); /* * Save the information we need to re-become the * user, then unbecome the user whilst we're doing this. */ saved_conn = fsp->conn; saved_vuid = current_user.vuid; GetWd(saved_dir); unbecome_user(); while(OPEN_FSP(fsp) && fsp->granted_oplock) { if(receive_smb(Client,inbuf,OPLOCK_BREAK_TIMEOUT * 1000) == False) { /* * Die if we got an error. */ if (smb_read_error == READ_EOF) DEBUG( 0, ( "oplock_break: end of file from client\n" ) ); if (smb_read_error == READ_ERROR) DEBUG( 0, ("oplock_break: receive_smb error (%s)\n", strerror(errno)) ); if (smb_read_error == READ_TIMEOUT) DEBUG( 0, ( "oplock_break: receive_smb timed out after %d seconds.\n", OPLOCK_BREAK_TIMEOUT ) ); DEBUGADD( 0, ( "oplock_break failed for file %s ", fsp->fsp_name ) ); DEBUGADD( 0, ( "(dev = %x, inode = %x).\n", dev, inode)); shutdown_server = True; break; } /* * There are certain SMB requests that we shouldn't allow * to recurse. opens, renames and deletes are the obvious * ones. This is handled in the switch_message() function. * If global_oplock_break is set they will push the packet onto * the pending smb queue and return -1 (no reply). * JRA. */ process_smb(inbuf, outbuf); /* * Die if we go over the time limit. */ if((time(NULL) - start_time) > OPLOCK_BREAK_TIMEOUT) { if( DEBUGLVL( 0 ) ) { dbgtext( "oplock_break: no break received from client " ); dbgtext( "within %d seconds.\n", OPLOCK_BREAK_TIMEOUT ); dbgtext( "oplock_break failed for file %s ", fsp->fsp_name ); dbgtext( "(dev = %x, inode = %x).\n", dev, inode ); } shutdown_server = True; break; } } /* * Go back to being the user who requested the oplock * break. */ if(!become_user(saved_conn, saved_vuid)) { DEBUG( 0, ( "oplock_break: unable to re-become user!" ) ); DEBUGADD( 0, ( "Shutting down server\n" ) ); close_sockets(); close(oplock_sock); exit_server("unable to re-become user"); } /* Including the directory. */ ChDir(saved_dir); /* Free the buffers we've been using to recurse. */ free(inbuf); free(outbuf); /* We need this in case a readraw crossed on the wire. */ if(global_oplock_break) global_oplock_break = False; /* * If the client did not respond we must die. */ if(shutdown_server) { DEBUG( 0, ( "oplock_break: client failure in break - " ) ); DEBUGADD( 0, ( "shutting down this smbd.\n" ) ); close_sockets(); close(oplock_sock); exit_server("oplock break failure"); } if(OPEN_FSP(fsp)) { /* The lockingX reply will have removed the oplock flag from the sharemode. */ /* Paranoia.... */ fsp->granted_oplock = False; fsp->sent_oplock_break = False; global_oplocks_open--; } /* Santity check - remove this later. JRA */ if(global_oplocks_open < 0) { DEBUG(0,("oplock_break: global_oplocks_open < 0 (%d). PANIC ERROR\n", global_oplocks_open)); exit_server("oplock_break: global_oplocks_open < 0"); } if( DEBUGLVL( 3 ) ) { dbgtext( "oplock_break: returning success for " ); dbgtext( "dev = %x, inode = %x.\n", dev, inode ); dbgtext( "Current global_oplocks_open = %d\n", global_oplocks_open ); } return True; } /**************************************************************************** Send an oplock break message to another smbd process. If the oplock is held by the local smbd then call the oplock break function directly. ****************************************************************************/ BOOL request_oplock_break(share_mode_entry *share_entry, uint32 dev, uint32 inode) { char op_break_msg[OPLOCK_BREAK_MSG_LEN]; struct sockaddr_in addr_out; int pid = getpid(); time_t start_time; int time_left; if(pid == share_entry->pid) { /* We are breaking our own oplock, make sure it's us. */ if(share_entry->op_port != oplock_port) { DEBUG(0,("request_oplock_break: corrupt share mode entry - pid = %d, port = %d \ should be %d\n", pid, share_entry->op_port, oplock_port)); return False; } DEBUG(5,("request_oplock_break: breaking our own oplock\n")); /* Call oplock break direct. */ return oplock_break(dev, inode, &share_entry->time); } /* We need to send a OPLOCK_BREAK_CMD message to the port in the share mode entry. */ SSVAL(op_break_msg,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD); SIVAL(op_break_msg,OPLOCK_BREAK_PID_OFFSET,pid); SIVAL(op_break_msg,OPLOCK_BREAK_DEV_OFFSET,dev); SIVAL(op_break_msg,OPLOCK_BREAK_INODE_OFFSET,inode); SIVAL(op_break_msg,OPLOCK_BREAK_SEC_OFFSET,(uint32)share_entry->time.tv_sec); SIVAL(op_break_msg,OPLOCK_BREAK_USEC_OFFSET,(uint32)share_entry->time.tv_usec); /* set the address and port */ bzero((char *)&addr_out,sizeof(addr_out)); addr_out.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr_out.sin_port = htons( share_entry->op_port ); addr_out.sin_family = AF_INET; if( DEBUGLVL( 3 ) ) { dbgtext( "request_oplock_break: sending a oplock break message to " ); dbgtext( "pid %d on port %d ", share_entry->pid, share_entry->op_port ); dbgtext( "for dev = %x, inode = %x\n", dev, inode ); } if(sendto(oplock_sock,op_break_msg,OPLOCK_BREAK_MSG_LEN,0, (struct sockaddr *)&addr_out,sizeof(addr_out)) < 0) { if( DEBUGLVL( 0 ) ) { dbgtext( "request_oplock_break: failed when sending a oplock " ); dbgtext( "break message to pid %d ", share_entry->pid ); dbgtext( "on port %d ", share_entry->op_port ); dbgtext( "for dev = %x, inode = %x.\n", dev, inode ); dbgtext( "Error was %s\n", strerror(errno) ); } return False; } /* * Now we must await the oplock broken message coming back * from the target smbd process. Timeout if it fails to * return in (OPLOCK_BREAK_TIMEOUT + OPLOCK_BREAK_TIMEOUT_FUDGEFACTOR) seconds. * While we get messages that aren't ours, loop. */ start_time = time(NULL); time_left = OPLOCK_BREAK_TIMEOUT+OPLOCK_BREAK_TIMEOUT_FUDGEFACTOR; while(time_left >= 0) { char op_break_reply[UDP_CMD_HEADER_LEN+OPLOCK_BREAK_MSG_LEN]; int32 reply_msg_len; uint16 reply_from_port; char *reply_msg_start; if(receive_local_message(oplock_sock, op_break_reply, sizeof(op_break_reply), time_left ? time_left * 1000 : 1) == False) { if(smb_read_error == READ_TIMEOUT) { if( DEBUGLVL( 0 ) ) { dbgtext( "request_oplock_break: no response received to oplock " ); dbgtext( "break request to pid %d ", share_entry->pid ); dbgtext( "on port %d ", share_entry->op_port ); dbgtext( "for dev = %x, inode = %x\n", dev, inode ); } /* * This is a hack to make handling of failing clients more robust. * If a oplock break response message is not received in the timeout * period we may assume that the smbd servicing that client holding * the oplock has died and the client changes were lost anyway, so * we should continue to try and open the file. */ break; } else if( DEBUGLVL( 0 ) ) { dbgtext( "request_oplock_break: error in response received " ); dbgtext( "to oplock break request to pid %d ", share_entry->pid ); dbgtext( "on port %d ", share_entry->op_port ); dbgtext( "for dev = %x, inode = %x.\n", dev, inode ); dbgtext( "Error was (%s).\n", strerror(errno) ); } return False; } reply_msg_len = IVAL(op_break_reply,UDP_CMD_LEN_OFFSET); reply_from_port = SVAL(op_break_reply,UDP_CMD_PORT_OFFSET); reply_msg_start = &op_break_reply[UDP_CMD_HEADER_LEN]; if(reply_msg_len != OPLOCK_BREAK_MSG_LEN) { /* Ignore it. */ DEBUG( 0, ( "request_oplock_break: invalid message length received." ) ); DEBUGADD( 0, ( " Ignoring.\n" ) ); continue; } /* * Test to see if this is the reply we are awaiting. */ if((SVAL(reply_msg_start,UDP_MESSAGE_CMD_OFFSET) & CMD_REPLY) && (reply_from_port == share_entry->op_port) && (memcmp(&reply_msg_start[OPLOCK_BREAK_PID_OFFSET], &op_break_msg[OPLOCK_BREAK_PID_OFFSET], OPLOCK_BREAK_MSG_LEN - OPLOCK_BREAK_PID_OFFSET) == 0)) { /* * This is the reply we've been waiting for. */ break; } else { /* * This is another message - probably a break request. * Process it to prevent potential deadlock. * Note that the code in switch_message() prevents * us from recursing into here as any SMB requests * we might process that would cause another oplock * break request to be made will be queued. * JRA. */ process_local_message(oplock_sock, op_break_reply, sizeof(op_break_reply)); } time_left -= (time(NULL) - start_time); } DEBUG(3,("request_oplock_break: broke oplock.\n")); return True; } /**************************************************************************** Get the next SMB packet, doing the local message processing automatically. ****************************************************************************/ BOOL receive_next_smb(int smbfd, int oplockfd, char *inbuf, int bufsize, int timeout) { BOOL got_smb = False; BOOL ret; do { ret = receive_message_or_smb(smbfd,oplockfd,inbuf,bufsize, timeout,&got_smb); if(ret && !got_smb) { /* Deal with oplock break requests from other smbd's. */ process_local_message(oplock_sock, inbuf, bufsize); continue; } if(ret && (CVAL(inbuf,0) == 0x85)) { /* Keepalive packet. */ got_smb = False; } } while(ret && !got_smb); return ret; } /**************************************************************************** reload the services file **************************************************************************/ BOOL reload_services(BOOL test) { BOOL ret; if (lp_loaded()) { pstring fname; pstrcpy(fname,lp_configfile()); if (file_exist(fname,NULL) && !strcsequal(fname,servicesf)) { pstrcpy(servicesf,fname); test = False; } } reopen_logs(); if (test && !lp_file_list_changed()) return(True); lp_killunused(conn_snum_used); ret = lp_load(servicesf,False,False,True); load_printers(); /* perhaps the config filename is now set */ if (!test) reload_services(True); reopen_logs(); load_interfaces(); { extern int Client; if (Client != -1) { set_socket_options(Client,"SO_KEEPALIVE"); set_socket_options(Client,user_socket_options); } } reset_mangled_cache(); /* this forces service parameters to be flushed */ become_service(NULL,True); return(ret); } /**************************************************************************** this prevents zombie child processes ****************************************************************************/ static BOOL reload_after_sighup = False; static void sig_hup(int sig) { BlockSignals(True,SIGHUP); DEBUG(0,("Got SIGHUP\n")); /* * Fix from here. * We used to reload in the signal handler - this * is a *BIG* no-no. */ reload_after_sighup = True; BlockSignals(False,SIGHUP); } /**************************************************************************** make a connection to a service ****************************************************************************/ connection_struct *make_connection(char *service,char *user,char *password, int pwlen, char *dev,uint16 vuid, int *ecode) { int snum; struct passwd *pass = NULL; BOOL guest = False; BOOL force = False; extern int Client; connection_struct *conn; strlower(service); snum = find_service(service); if (snum < 0) { extern int Client; if (strequal(service,"IPC$")) { DEBUG(3,("refusing IPC connection\n")); *ecode = ERRnoipc; return NULL; } DEBUG(0,("%s (%s) couldn't find service %s\n", remote_machine, client_addr(Client), service)); *ecode = ERRinvnetname; return NULL; } if (strequal(service,HOMES_NAME)) { if (*user && Get_Pwnam(user,True)) return(make_connection(user,user,password, pwlen,dev,vuid,ecode)); if(lp_security() != SEC_SHARE) { if (validated_username(vuid)) { pstrcpy(user,validated_username(vuid)); return(make_connection(user,user,password,pwlen,dev,vuid,ecode)); } } else { /* Security = share. Try with sesssetup_user * as the username. */ if(*sesssetup_user) { pstrcpy(user,sesssetup_user); return(make_connection(user,user,password,pwlen,dev,vuid,ecode)); } } } if (!lp_snum_ok(snum) || !check_access(Client, lp_hostsallow(snum), lp_hostsdeny(snum))) { *ecode = ERRaccess; return NULL; } /* you can only connect to the IPC$ service as an ipc device */ if (strequal(service,"IPC$")) pstrcpy(dev,"IPC"); if (*dev == '?' || !*dev) { if (lp_print_ok(snum)) { pstrcpy(dev,"LPT1:"); } else { pstrcpy(dev,"A:"); } } /* if the request is as a printer and you can't print then refuse */ strupper(dev); if (!lp_print_ok(snum) && (strncmp(dev,"LPT",3) == 0)) { DEBUG(1,("Attempt to connect to non-printer as a printer\n")); *ecode = ERRinvdevice; return NULL; } /* lowercase the user name */ strlower(user); /* add it as a possible user name */ add_session_user(service); /* shall we let them in? */ if (!authorise_login(snum,user,password,pwlen,&guest,&force,vuid)) { DEBUG( 2, ( "Invalid username/password for %s\n", service ) ); *ecode = ERRbadpw; return NULL; } conn = conn_new(); if (!conn) { DEBUG(0,("Couldn't find free connection.\n")); *ecode = ERRnoresource; conn_free(conn); return NULL; } /* find out some info about the user */ pass = Get_Pwnam(user,True); if (pass == NULL) { DEBUG(0,( "Couldn't find account %s\n",user)); *ecode = ERRbaduid; conn_free(conn); return NULL; } conn->read_only = lp_readonly(snum); { pstring list; StrnCpy(list,lp_readlist(snum),sizeof(pstring)-1); string_sub(list,"%S",service); if (user_in_list(user,list)) conn->read_only = True; StrnCpy(list,lp_writelist(snum),sizeof(pstring)-1); string_sub(list,"%S",service); if (user_in_list(user,list)) conn->read_only = False; } /* admin user check */ /* JRA - original code denied admin user if the share was marked read_only. Changed as I don't think this is needed, but old code left in case there is a problem here. */ if (user_in_list(user,lp_admin_users(snum)) #if 0 && !conn->read_only #endif ) { conn->admin_user = True; DEBUG(0,("%s logged in as admin user (root privileges)\n",user)); } else { conn->admin_user = False; } conn->force_user = force; conn->vuid = vuid; conn->uid = pass->pw_uid; conn->gid = pass->pw_gid; conn->num_files_open = 0; conn->lastused = time(NULL); conn->service = snum; conn->used = True; conn->printer = (strncmp(dev,"LPT",3) == 0); conn->ipc = (strncmp(dev,"IPC",3) == 0); conn->dirptr = NULL; conn->veto_list = NULL; conn->hide_list = NULL; conn->veto_oplock_list = NULL; string_set(&conn->dirpath,""); string_set(&conn->user,user); #ifdef HAVE_GETGRNAM if (*lp_force_group(snum)) { struct group *gptr; pstring gname; StrnCpy(gname,lp_force_group(snum),sizeof(pstring)-1); /* default service may be a group name */ string_sub(gname,"%S",service); gptr = (struct group *)getgrnam(gname); if (gptr) { conn->gid = gptr->gr_gid; DEBUG(3,("Forced group %s\n",gname)); } else { DEBUG(1,("Couldn't find group %s\n",gname)); } } #endif if (*lp_force_user(snum)) { struct passwd *pass2; fstring fuser; fstrcpy(fuser,lp_force_user(snum)); pass2 = (struct passwd *)Get_Pwnam(fuser,True); if (pass2) { conn->uid = pass2->pw_uid; string_set(&conn->user,fuser); fstrcpy(user,fuser); conn->force_user = True; DEBUG(3,("Forced user %s\n",fuser)); } else { DEBUG(1,("Couldn't find user %s\n",fuser)); } } { pstring s; pstrcpy(s,lp_pathname(snum)); standard_sub(conn,s); string_set(&conn->connectpath,s); DEBUG(3,("Connect path is %s\n",s)); } /* groups stuff added by ih */ conn->ngroups = 0; conn->groups = NULL; if (!IS_IPC(conn)) { /* Find all the groups this uid is in and store them. Used by become_user() */ setup_groups(conn->user,conn->uid,conn->gid, &conn->ngroups,&conn->groups); /* check number of connections */ if (!claim_connection(conn, lp_servicename(SNUM(conn)), lp_max_connections(SNUM(conn)), False)) { DEBUG(1,("too many connections - rejected\n")); *ecode = ERRnoresource; conn_free(conn); return NULL; } if (lp_status(SNUM(conn))) claim_connection(conn,"STATUS.", MAXSTATUS,False); } /* IS_IPC */ /* execute any "root preexec = " line */ if (*lp_rootpreexec(SNUM(conn))) { pstring cmd; pstrcpy(cmd,lp_rootpreexec(SNUM(conn))); standard_sub(conn,cmd); DEBUG(5,("cmd=%s\n",cmd)); smbrun(cmd,NULL,False); } if (!become_user(conn, conn->vuid)) { DEBUG(0,("Can't become connected user!\n")); if (!IS_IPC(conn)) { yield_connection(conn, lp_servicename(SNUM(conn)), lp_max_connections(SNUM(conn))); if (lp_status(SNUM(conn))) { yield_connection(conn,"STATUS.",MAXSTATUS); } } conn_free(conn); *ecode = ERRbadpw; return NULL; } if (ChDir(conn->connectpath) != 0) { DEBUG(0,("Can't change directory to %s (%s)\n", conn->connectpath,strerror(errno))); unbecome_user(); if (!IS_IPC(conn)) { yield_connection(conn, lp_servicename(SNUM(conn)), lp_max_connections(SNUM(conn))); if (lp_status(SNUM(conn))) yield_connection(conn,"STATUS.",MAXSTATUS); } conn_free(conn); *ecode = ERRinvnetname; return NULL; } string_set(&conn->origpath,conn->connectpath); #if SOFTLINK_OPTIMISATION /* resolve any soft links early */ { pstring s; pstrcpy(s,conn->connectpath); GetWd(s); string_set(&conn->connectpath,s); ChDir(conn->connectpath); } #endif add_session_user(user); /* execute any "preexec = " line */ if (*lp_preexec(SNUM(conn))) { pstring cmd; pstrcpy(cmd,lp_preexec(SNUM(conn))); standard_sub(conn,cmd); smbrun(cmd,NULL,False); } /* we've finished with the sensitive stuff */ unbecome_user(); /* Add veto/hide lists */ if (!IS_IPC(conn) && !IS_PRINT(conn)) { set_namearray( &conn->veto_list, lp_veto_files(SNUM(conn))); set_namearray( &conn->hide_list, lp_hide_files(SNUM(conn))); set_namearray( &conn->veto_oplock_list, lp_veto_oplocks(SNUM(conn))); } if( DEBUGLVL( IS_IPC(conn) ? 3 : 1 ) ) { extern int Client; dbgtext( "%s (%s) ", remote_machine, client_addr(Client) ); dbgtext( "connect to service %s ", lp_servicename(SNUM(conn)) ); dbgtext( "as user %s ", user ); dbgtext( "(uid=%d, gid=%d) ", conn->uid, conn->gid ); dbgtext( "(pid %d)\n", (int)getpid() ); } return(conn); } /**************************************************************************** Attempt to break an oplock on a file (if oplocked). Returns True if the file was closed as a result of the oplock break, False otherwise. Used as a last ditch attempt to free a space in the file table when we have run out. ****************************************************************************/ BOOL attempt_close_oplocked_file(files_struct *fsp) { DEBUG(5,("attempt_close_oplocked_file: checking file %s.\n", fsp->fsp_name)); if (fsp->open && fsp->granted_oplock && !fsp->sent_oplock_break) { /* Try and break the oplock. */ file_fd_struct *fd_ptr = fsp->fd_ptr; if(oplock_break( fd_ptr->dev, fd_ptr->inode, &fsp->open_time)) { if(!fsp->open) /* Did the oplock break close the file ? */ return True; } } return False; } /**************************************************************************** close a cnum ****************************************************************************/ void close_cnum(connection_struct *conn, uint16 vuid) { extern int Client; DirCacheFlush(SNUM(conn)); unbecome_user(); DEBUG(IS_IPC(conn)?3:1, ("%s (%s) closed connection to service %s\n", remote_machine,client_addr(Client), lp_servicename(SNUM(conn)))); yield_connection(conn, lp_servicename(SNUM(conn)), lp_max_connections(SNUM(conn))); if (lp_status(SNUM(conn))) yield_connection(conn,"STATUS.",MAXSTATUS); file_close_conn(conn); dptr_closecnum(conn); /* execute any "postexec = " line */ if (*lp_postexec(SNUM(conn)) && become_user(conn, vuid)) { pstring cmd; pstrcpy(cmd,lp_postexec(SNUM(conn))); standard_sub(conn,cmd); smbrun(cmd,NULL,False); unbecome_user(); } unbecome_user(); /* execute any "root postexec = " line */ if (*lp_rootpostexec(SNUM(conn))) { pstring cmd; pstrcpy(cmd,lp_rootpostexec(SNUM(conn))); standard_sub(conn,cmd); smbrun(cmd,NULL,False); } conn_free(conn); } #if DUMP_CORE /******************************************************************* prepare to dump a core file - carefully! ********************************************************************/ static BOOL dump_core(void) { char *p; pstring dname; pstrcpy(dname,debugf); if ((p=strrchr(dname,'/'))) *p=0; pstrcat(dname,"/corefiles"); mkdir(dname,0700); sys_chown(dname,getuid(),getgid()); chmod(dname,0700); if (chdir(dname)) return(False); umask(~(0700)); #ifdef HAVE_GETRLIMIT #ifdef RLIMIT_CORE { struct rlimit rlp; getrlimit(RLIMIT_CORE, &rlp); rlp.rlim_cur = MAX(4*1024*1024,rlp.rlim_cur); setrlimit(RLIMIT_CORE, &rlp); getrlimit(RLIMIT_CORE, &rlp); DEBUG(3,("Core limits now %d %d\n",(int)rlp.rlim_cur,(int)rlp.rlim_max)); } #endif #endif DEBUG(0,("Dumping core in %s\n",dname)); abort(); return(True); } #endif /**************************************************************************** exit the server ****************************************************************************/ void exit_server(char *reason) { static int firsttime=1; if (!firsttime) exit(0); firsttime = 0; unbecome_user(); DEBUG(2,("Closing connections\n")); conn_close_all(); #ifdef WITH_DFS if (dcelogin_atmost_once) { dfs_unlogin(); } #endif if (!reason) { int oldlevel = DEBUGLEVEL; DEBUGLEVEL = 10; DEBUG(0,("Last message was %s\n",smb_fn_name(last_message))); if (last_inbuf) show_msg(last_inbuf); DEBUGLEVEL = oldlevel; DEBUG(0,("===============================================================\n")); #if DUMP_CORE if (dump_core()) return; #endif } locking_end(); DEBUG( 3, ( "Server exit (%s)\n", (reason ? reason : "") ) ); exit(0); } /* These flags determine some of the permissions required to do an operation Note that I don't set NEED_WRITE on some write operations because they are used by some brain-dead clients when printing, and I don't want to force write permissions on print services. */ #define AS_USER (1<<0) #define NEED_WRITE (1<<1) #define TIME_INIT (1<<2) #define CAN_IPC (1<<3) #define AS_GUEST (1<<5) #define QUEUE_IN_OPLOCK (1<<6) /* define a list of possible SMB messages and their corresponding functions. Any message that has a NULL function is unimplemented - please feel free to contribute implementations! */ struct smb_message_struct { int code; char *name; int (*fn)(connection_struct *conn, char *, char *, int, int); int flags; #if PROFILING unsigned long time; #endif } smb_messages[] = { /* CORE PROTOCOL */ {SMBnegprot,"SMBnegprot",reply_negprot,0}, {SMBtcon,"SMBtcon",reply_tcon,0}, {SMBtdis,"SMBtdis",reply_tdis,0}, {SMBexit,"SMBexit",reply_exit,0}, {SMBioctl,"SMBioctl",reply_ioctl,0}, {SMBecho,"SMBecho",reply_echo,0}, {SMBsesssetupX,"SMBsesssetupX",reply_sesssetup_and_X,0}, {SMBtconX,"SMBtconX",reply_tcon_and_X,0}, {SMBulogoffX, "SMBulogoffX", reply_ulogoffX, 0}, /* ulogoff doesn't give a valid TID */ {SMBgetatr,"SMBgetatr",reply_getatr,AS_USER}, {SMBsetatr,"SMBsetatr",reply_setatr,AS_USER | NEED_WRITE}, {SMBchkpth,"SMBchkpth",reply_chkpth,AS_USER}, {SMBsearch,"SMBsearch",reply_search,AS_USER}, {SMBopen,"SMBopen",reply_open,AS_USER | QUEUE_IN_OPLOCK }, /* note that SMBmknew and SMBcreate are deliberately overloaded */ {SMBcreate,"SMBcreate",reply_mknew,AS_USER}, {SMBmknew,"SMBmknew",reply_mknew,AS_USER}, {SMBunlink,"SMBunlink",reply_unlink,AS_USER | NEED_WRITE | QUEUE_IN_OPLOCK}, {SMBread,"SMBread",reply_read,AS_USER}, {SMBwrite,"SMBwrite",reply_write,AS_USER}, {SMBclose,"SMBclose",reply_close,AS_USER | CAN_IPC}, {SMBmkdir,"SMBmkdir",reply_mkdir,AS_USER | NEED_WRITE}, {SMBrmdir,"SMBrmdir",reply_rmdir,AS_USER | NEED_WRITE}, {SMBdskattr,"SMBdskattr",reply_dskattr,AS_USER}, {SMBmv,"SMBmv",reply_mv,AS_USER | NEED_WRITE | QUEUE_IN_OPLOCK}, /* this is a Pathworks specific call, allowing the changing of the root path */ {pSETDIR,"pSETDIR",reply_setdir,AS_USER}, {SMBlseek,"SMBlseek",reply_lseek,AS_USER}, {SMBflush,"SMBflush",reply_flush,AS_USER}, {SMBctemp,"SMBctemp",reply_ctemp,AS_USER | QUEUE_IN_OPLOCK }, {SMBsplopen,"SMBsplopen",reply_printopen,AS_USER | QUEUE_IN_OPLOCK }, {SMBsplclose,"SMBsplclose",reply_printclose,AS_USER}, {SMBsplretq,"SMBsplretq",reply_printqueue,AS_USER}, {SMBsplwr,"SMBsplwr",reply_printwrite,AS_USER}, {SMBlock,"SMBlock",reply_lock,AS_USER}, {SMBunlock,"SMBunlock",reply_unlock,AS_USER}, /* CORE+ PROTOCOL FOLLOWS */ {SMBreadbraw,"SMBreadbraw",reply_readbraw,AS_USER}, {SMBwritebraw,"SMBwritebraw",reply_writebraw,AS_USER}, {SMBwriteclose,"SMBwriteclose",reply_writeclose,AS_USER}, {SMBlockread,"SMBlockread",reply_lockread,AS_USER}, {SMBwriteunlock,"SMBwriteunlock",reply_writeunlock,AS_USER}, /* LANMAN1.0 PROTOCOL FOLLOWS */ {SMBreadBmpx,"SMBreadBmpx",reply_readbmpx,AS_USER}, {SMBreadBs,"SMBreadBs",NULL,AS_USER}, {SMBwriteBmpx,"SMBwriteBmpx",reply_writebmpx,AS_USER}, {SMBwriteBs,"SMBwriteBs",reply_writebs,AS_USER}, {SMBwritec,"SMBwritec",NULL,AS_USER}, {SMBsetattrE,"SMBsetattrE",reply_setattrE,AS_USER | NEED_WRITE}, {SMBgetattrE,"SMBgetattrE",reply_getattrE,AS_USER}, {SMBtrans,"SMBtrans",reply_trans,AS_USER | CAN_IPC}, {SMBtranss,"SMBtranss",NULL,AS_USER | CAN_IPC}, {SMBioctls,"SMBioctls",NULL,AS_USER}, {SMBcopy,"SMBcopy",reply_copy,AS_USER | NEED_WRITE | QUEUE_IN_OPLOCK }, {SMBmove,"SMBmove",NULL,AS_USER | NEED_WRITE | QUEUE_IN_OPLOCK }, {SMBopenX,"SMBopenX",reply_open_and_X,AS_USER | CAN_IPC | QUEUE_IN_OPLOCK }, {SMBreadX,"SMBreadX",reply_read_and_X,AS_USER | CAN_IPC }, {SMBwriteX,"SMBwriteX",reply_write_and_X,AS_USER}, {SMBlockingX,"SMBlockingX",reply_lockingX,AS_USER}, {SMBffirst,"SMBffirst",reply_search,AS_USER}, {SMBfunique,"SMBfunique",reply_search,AS_USER}, {SMBfclose,"SMBfclose",reply_fclose,AS_USER}, /* LANMAN2.0 PROTOCOL FOLLOWS */ {SMBfindnclose, "SMBfindnclose", reply_findnclose, AS_USER}, {SMBfindclose, "SMBfindclose", reply_findclose,AS_USER}, {SMBtrans2, "SMBtrans2", reply_trans2, AS_USER }, {SMBtranss2, "SMBtranss2", reply_transs2, AS_USER}, /* NT PROTOCOL FOLLOWS */ {SMBntcreateX, "SMBntcreateX", reply_ntcreate_and_X, AS_USER | CAN_IPC | QUEUE_IN_OPLOCK }, {SMBnttrans, "SMBnttrans", reply_nttrans, AS_USER | CAN_IPC }, {SMBnttranss, "SMBnttranss", reply_nttranss, AS_USER | CAN_IPC }, {SMBntcancel, "SMBntcancel", reply_ntcancel, AS_USER }, /* messaging routines */ {SMBsends,"SMBsends",reply_sends,AS_GUEST}, {SMBsendstrt,"SMBsendstrt",reply_sendstrt,AS_GUEST}, {SMBsendend,"SMBsendend",reply_sendend,AS_GUEST}, {SMBsendtxt,"SMBsendtxt",reply_sendtxt,AS_GUEST}, /* NON-IMPLEMENTED PARTS OF THE CORE PROTOCOL */ {SMBsendb,"SMBsendb",NULL,AS_GUEST}, {SMBfwdname,"SMBfwdname",NULL,AS_GUEST}, {SMBcancelf,"SMBcancelf",NULL,AS_GUEST}, {SMBgetmac,"SMBgetmac",NULL,AS_GUEST} }; /**************************************************************************** return a string containing the function name of a SMB command ****************************************************************************/ char *smb_fn_name(int type) { static char *unknown_name = "SMBunknown"; static int num_smb_messages = sizeof(smb_messages) / sizeof(struct smb_message_struct); int match; for (match=0;matchrequested_name); } /* does this protocol need to be run as root? */ if (!(flags & AS_USER)) unbecome_user(); /* does this protocol need to be run as the connected user? */ if ((flags & AS_USER) && !become_user(conn,session_tag)) { if (flags & AS_GUEST) flags &= ~AS_USER; else return(ERROR(ERRSRV,ERRinvnid)); } /* this code is to work around a bug is MS client 3 without introducing a security hole - it needs to be able to do print queue checks as guest if it isn't logged in properly */ if (flags & AS_USER) flags &= ~AS_GUEST; /* does it need write permission? */ if ((flags & NEED_WRITE) && !CAN_WRITE(conn)) return(ERROR(ERRSRV,ERRaccess)); /* ipc services are limited */ if (IS_IPC(conn) && (flags & AS_USER) && !(flags & CAN_IPC)) { return(ERROR(ERRSRV,ERRaccess)); } /* load service specific parameters */ if (conn && !become_service(conn,(flags & AS_USER)?True:False)) { return(ERROR(ERRSRV,ERRaccess)); } /* does this protocol need to be run as guest? */ if ((flags & AS_GUEST) && (!become_guest() || !check_access(Client, lp_hostsallow(-1), lp_hostsdeny(-1)))) { return(ERROR(ERRSRV,ERRaccess)); } last_inbuf = inbuf; outsize = smb_messages[match].fn(conn, inbuf,outbuf,size,bufsize); } else { outsize = reply_unknown(inbuf,outbuf); } } #if PROFILING GetTimeOfDay(&msg_end_time); if (!(smb_messages[match].flags & TIME_INIT)) { smb_messages[match].time = 0; smb_messages[match].flags |= TIME_INIT; } { unsigned long this_time = (msg_end_time.tv_sec - msg_start_time.tv_sec)*1e6 + (msg_end_time.tv_usec - msg_start_time.tv_usec); smb_messages[match].time += this_time; total_time += this_time; } DEBUG(2,("TIME %s %d usecs %g pct\n", smb_fn_name(type),smb_messages[match].time, (100.0*smb_messages[match].time) / total_time)); #endif return(outsize); } /**************************************************************************** construct a chained reply and add it to the already made reply **************************************************************************/ int chain_reply(char *inbuf,char *outbuf,int size,int bufsize) { static char *orig_inbuf; static char *orig_outbuf; int smb_com1, smb_com2 = CVAL(inbuf,smb_vwv0); unsigned smb_off2 = SVAL(inbuf,smb_vwv1); char *inbuf2, *outbuf2; int outsize2; char inbuf_saved[smb_wct]; char outbuf_saved[smb_wct]; extern int chain_size; int wct = CVAL(outbuf,smb_wct); int outsize = smb_size + 2*wct + SVAL(outbuf,smb_vwv0+2*wct); /* maybe its not chained */ if (smb_com2 == 0xFF) { CVAL(outbuf,smb_vwv0) = 0xFF; return outsize; } if (chain_size == 0) { /* this is the first part of the chain */ orig_inbuf = inbuf; orig_outbuf = outbuf; } /* we need to tell the client where the next part of the reply will be */ SSVAL(outbuf,smb_vwv1,smb_offset(outbuf+outsize,outbuf)); CVAL(outbuf,smb_vwv0) = smb_com2; /* remember how much the caller added to the chain, only counting stuff after the parameter words */ chain_size += outsize - smb_wct; /* work out pointers into the original packets. The headers on these need to be filled in */ inbuf2 = orig_inbuf + smb_off2 + 4 - smb_wct; outbuf2 = orig_outbuf + SVAL(outbuf,smb_vwv1) + 4 - smb_wct; /* remember the original command type */ smb_com1 = CVAL(orig_inbuf,smb_com); /* save the data which will be overwritten by the new headers */ memcpy(inbuf_saved,inbuf2,smb_wct); memcpy(outbuf_saved,outbuf2,smb_wct); /* give the new packet the same header as the last part of the SMB */ memmove(inbuf2,inbuf,smb_wct); /* create the in buffer */ CVAL(inbuf2,smb_com) = smb_com2; /* create the out buffer */ bzero(outbuf2,smb_size); set_message(outbuf2,0,0,True); CVAL(outbuf2,smb_com) = CVAL(inbuf2,smb_com); memcpy(outbuf2+4,inbuf2+4,4); CVAL(outbuf2,smb_rcls) = SMB_SUCCESS; CVAL(outbuf2,smb_reh) = 0; CVAL(outbuf2,smb_flg) = 0x80 | (CVAL(inbuf2,smb_flg) & 0x8); /* bit 7 set means a reply */ SSVAL(outbuf2,smb_flg2,1); /* say we support long filenames */ SSVAL(outbuf2,smb_err,SMB_SUCCESS); SSVAL(outbuf2,smb_tid,SVAL(inbuf2,smb_tid)); SSVAL(outbuf2,smb_pid,SVAL(inbuf2,smb_pid)); SSVAL(outbuf2,smb_uid,SVAL(inbuf2,smb_uid)); SSVAL(outbuf2,smb_mid,SVAL(inbuf2,smb_mid)); DEBUG(3,("Chained message\n")); show_msg(inbuf2); /* process the request */ outsize2 = switch_message(smb_com2,inbuf2,outbuf2,size-chain_size, bufsize-chain_size); /* copy the new reply and request headers over the old ones, but preserve the smb_com field */ memmove(orig_outbuf,outbuf2,smb_wct); CVAL(orig_outbuf,smb_com) = smb_com1; /* restore the saved data, being careful not to overwrite any data from the reply header */ memcpy(inbuf2,inbuf_saved,smb_wct); { int ofs = smb_wct - PTR_DIFF(outbuf2,orig_outbuf); if (ofs < 0) ofs = 0; memmove(outbuf2+ofs,outbuf_saved+ofs,smb_wct-ofs); } return outsize2; } /**************************************************************************** Helper function for contruct_reply. ****************************************************************************/ void construct_reply_common(char *inbuf,char *outbuf) { bzero(outbuf,smb_size); CVAL(outbuf,smb_com) = CVAL(inbuf,smb_com); set_message(outbuf,0,0,True); memcpy(outbuf+4,inbuf+4,4); CVAL(outbuf,smb_rcls) = SMB_SUCCESS; CVAL(outbuf,smb_reh) = 0; CVAL(outbuf,smb_flg) = 0x80 | (CVAL(inbuf,smb_flg) & 0x8); /* bit 7 set means a reply */ SSVAL(outbuf,smb_flg2,1); /* say we support long filenames */ SSVAL(outbuf,smb_err,SMB_SUCCESS); SSVAL(outbuf,smb_tid,SVAL(inbuf,smb_tid)); SSVAL(outbuf,smb_pid,SVAL(inbuf,smb_pid)); SSVAL(outbuf,smb_uid,SVAL(inbuf,smb_uid)); SSVAL(outbuf,smb_mid,SVAL(inbuf,smb_mid)); } /**************************************************************************** construct a reply to the incoming packet ****************************************************************************/ int construct_reply(char *inbuf,char *outbuf,int size,int bufsize) { int type = CVAL(inbuf,smb_com); int outsize = 0; int msg_type = CVAL(inbuf,0); extern int chain_size; smb_last_time = time(NULL); chain_size = 0; file_chain_reset(); reset_chain_p(); if (msg_type != 0) return(reply_special(inbuf,outbuf)); construct_reply_common(inbuf, outbuf); outsize = switch_message(type,inbuf,outbuf,size,bufsize); outsize += chain_size; if(outsize > 4) smb_setlen(outbuf,outsize - 4); return(outsize); } /**************************************************************************** process commands from the client ****************************************************************************/ static void process(void) { extern int Client; InBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); OutBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); if ((InBuffer == NULL) || (OutBuffer == NULL)) return; InBuffer += SMB_ALIGNMENT; OutBuffer += SMB_ALIGNMENT; #if PRIME_NMBD DEBUG(3,("priming nmbd\n")); { struct in_addr ip; ip = *interpret_addr2("localhost"); if (zero_ip(ip)) ip = *interpret_addr2("127.0.0.1"); *OutBuffer = 0; send_one_packet(OutBuffer,1,ip,NMB_PORT,SOCK_DGRAM); } #endif /* re-initialise the timezone */ TimeInit(); while (True) { int deadtime = lp_deadtime()*60; int counter; int last_keepalive=0; int service_load_counter = 0; BOOL got_smb = False; if (deadtime <= 0) deadtime = DEFAULT_SMBD_TIMEOUT; #if USE_READ_PREDICTION if (lp_readprediction()) do_read_prediction(); #endif errno = 0; for (counter=SMBD_SELECT_LOOP; !receive_message_or_smb(Client,oplock_sock, InBuffer,BUFFER_SIZE,SMBD_SELECT_LOOP*1000,&got_smb); counter += SMBD_SELECT_LOOP) { time_t t; BOOL allidle = True; extern int keepalive; if (counter > 365 * 3600) /* big number of seconds. */ { counter = 0; service_load_counter = 0; } if (smb_read_error == READ_EOF) { DEBUG(3,("end of file from client\n")); return; } if (smb_read_error == READ_ERROR) { DEBUG(3,("receive_smb error (%s) exiting\n", strerror(errno))); return; } t = time(NULL); /* become root again if waiting */ unbecome_user(); /* check for smb.conf reload */ if (counter >= service_load_counter + SMBD_RELOAD_CHECK) { service_load_counter = counter; /* reload services, if files have changed. */ reload_services(True); } /* * If reload_after_sighup == True then we got a SIGHUP * and are being asked to reload. Fix from */ if (reload_after_sighup) { DEBUG(0,("Reloading services after SIGHUP\n")); reload_services(False); reload_after_sighup = False; } /* automatic timeout if all connections are closed */ if (conn_num_open()==0 && counter >= IDLE_CLOSED_TIMEOUT) { DEBUG( 2, ( "Closing idle connection\n" ) ); return; } if (keepalive && (counter-last_keepalive)>keepalive) { struct cli_state *cli = server_client(); if (!send_keepalive(Client)) { DEBUG( 2, ( "Keepalive failed - exiting.\n" ) ); return; } /* also send a keepalive to the password server if its still connected */ if (cli && cli->initialised) send_keepalive(cli->fd); last_keepalive = counter; } /* check for connection timeouts */ allidle = conn_idle_all(t, deadtime); if (allidle && conn_num_open()>0) { DEBUG(2,("Closing idle connection 2.\n")); return; } if(global_machine_pasword_needs_changing) { unsigned char trust_passwd_hash[16]; time_t lct; pstring remote_machine_list; /* * We're in domain level security, and the code that * read the machine password flagged that the machine * password needs changing. */ /* * First, open the machine password file with an exclusive lock. */ if(!trust_password_lock( global_myworkgroup, global_myname, True)) { DEBUG(0,("process: unable to open the machine account password file for \ machine %s in domain %s.\n", global_myname, global_myworkgroup )); continue; } if(!get_trust_account_password( trust_passwd_hash, &lct)) { DEBUG(0,("process: unable to read the machine account password for \ machine %s in domain %s.\n", global_myname, global_myworkgroup )); trust_password_unlock(); continue; } /* * Make sure someone else hasn't already done this. */ if(t < lct + lp_machine_password_timeout()) { trust_password_unlock(); global_machine_pasword_needs_changing = False; continue; } pstrcpy(remote_machine_list, lp_passwordserver()); change_trust_account_password( global_myworkgroup, remote_machine_list); trust_password_unlock(); global_machine_pasword_needs_changing = False; } /* * Check to see if we have any change notifies * outstanding on the queue. */ process_pending_change_notify_queue(t); } if(got_smb) process_smb(InBuffer, OutBuffer); else process_local_message(oplock_sock, InBuffer, BUFFER_SIZE); } } /**************************************************************************** initialise connect, service and file structs ****************************************************************************/ static void init_structs(void ) { get_myname(myhostname,NULL); /* * Set the machine NETBIOS name if not already * set from the config file. */ if (!*global_myname) { char *p; fstrcpy( global_myname, myhostname ); p = strchr( global_myname, '.' ); if (p) *p = 0; } strupper( global_myname ); conn_init(); file_init(); /* for RPC pipes */ init_rpc_pipe_hnd(); /* for LSA handles */ init_lsa_policy_hnd(); init_dptrs(); } /**************************************************************************** usage on the program ****************************************************************************/ static void usage(char *pname) { DEBUG(0,("Incorrect program usage - are you sure the command line is correct?\n")); printf("Usage: %s [-D] [-p port] [-d debuglevel] [-l log basename] [-s services file]\n",pname); printf("Version %s\n",VERSION); printf("\t-D become a daemon\n"); printf("\t-p port listen on the specified port\n"); printf("\t-d debuglevel set the debuglevel\n"); printf("\t-l log basename. Basename for log/debug files\n"); printf("\t-s services file. Filename of services file\n"); printf("\t-P passive only\n"); printf("\t-a overwrite log file, don't append\n"); printf("\n"); } /**************************************************************************** main program ****************************************************************************/ int main(int argc,char *argv[]) { extern BOOL append_log; /* shall I run as a daemon */ BOOL is_daemon = False; int port = SMB_PORT; int opt; extern char *optarg; #ifdef HAVE_SET_AUTH_PARAMETERS set_auth_parameters(argc,argv); #endif #ifdef HAVE_SETLUID /* needed for SecureWare on SCO */ setluid(0); #endif append_log = True; TimeInit(); pstrcpy(debugf,SMBLOGFILE); pstrcpy(remote_machine, "smb"); setup_logging(argv[0],False); charset_initialise(); /* make absolutely sure we run as root - to handle cases where people are crazy enough to have it setuid */ #ifdef HAVE_SETRESUID setresuid(0,0,0); #else setuid(0); seteuid(0); setuid(0); seteuid(0); #endif fault_setup((void (*)(void *))exit_server); CatchSignal(SIGTERM , SIGNAL_CAST dflt_sig); /* we want total control over the permissions on created files, so set our umask to 0 */ umask(0); GetWd(OriginalDir); init_uid(); /* this is for people who can't start the program correctly */ while (argc > 1 && (*argv[1] != '-')) { argv++; argc--; } while ((opt = getopt(argc, argv, "O:i:l:s:d:Dp:hPaf:")) != EOF) switch (opt) { case 'O': pstrcpy(user_socket_options,optarg); break; case 'i': pstrcpy(scope,optarg); break; case 'P': { extern BOOL passive; passive = True; } break; case 's': pstrcpy(servicesf,optarg); break; case 'l': pstrcpy(debugf,optarg); break; case 'a': { extern BOOL append_log; append_log = !append_log; } break; case 'D': is_daemon = True; break; case 'd': if (*optarg == 'A') DEBUGLEVEL = 10000; else DEBUGLEVEL = atoi(optarg); break; case 'p': port = atoi(optarg); break; case 'h': usage(argv[0]); exit(0); break; default: usage(argv[0]); exit(1); } reopen_logs(); DEBUG( 1, ( "smbd version %s started.\n", VERSION ) ); DEBUGADD( 1, ( "Copyright Andrew Tridgell 1992-1997\n" ) ); DEBUG(2,("uid=%d gid=%d euid=%d egid=%d\n", (int)getuid(),(int)getgid(),(int)geteuid(),(int)getegid())); if (sizeof(uint16) < 2 || sizeof(uint32) < 4) { DEBUG(0,("ERROR: Samba is not configured correctly for the word size on your machine\n")); exit(1); } init_structs(); if (!reload_services(False)) return(-1); #ifdef WITH_SSL { extern BOOL sslEnabled; sslEnabled = lp_ssl_enabled(); if(sslEnabled) sslutil_init(True); } #endif /* WITH_SSL */ codepage_initialise(lp_client_code_page()); pstrcpy(global_myworkgroup, lp_workgroup()); if(!pdb_generate_machine_sid()) { DEBUG(0,("ERROR: Samba cannot get a machine SID.\n")); exit(1); } CatchSignal(SIGHUP,SIGNAL_CAST sig_hup); /* Setup the signals that allow the debug log level to by dynamically changed. */ /* If we are using the malloc debug code we can't use SIGUSR1 and SIGUSR2 to do debug level changes. */ #ifndef MEM_MAN #if defined(SIGUSR1) CatchSignal( SIGUSR1, SIGNAL_CAST sig_usr1 ); #endif /* SIGUSR1 */ #if defined(SIGUSR2) CatchSignal( SIGUSR2, SIGNAL_CAST sig_usr2 ); #endif /* SIGUSR2 */ #endif /* MEM_MAN */ DEBUG( 3, ( "loaded services\n" ) ); if (!is_daemon && !is_a_socket(0)) { DEBUG(0,("standard input is not a socket, assuming -D option\n")); is_daemon = True; } if (is_daemon) { DEBUG( 3, ( "Becoming a daemon.\n" ) ); become_daemon(); } if (!directory_exist(lp_lockdir(), NULL)) { mkdir(lp_lockdir(), 0755); } if (is_daemon) { pidfile_create("smbd"); } if (!open_sockets(is_daemon,port)) exit(1); if (!locking_init(0)) exit(1); if(!initialize_password_db()) exit(1); /* possibly reload the services file. */ reload_services(True); max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); if (*lp_rootdir()) { if (sys_chroot(lp_rootdir()) == 0) DEBUG( 2, ( "Changed root to %s\n", lp_rootdir() ) ); } /* Setup the oplock IPC socket. */ if( !open_oplock_ipc() ) exit(1); process(); close_sockets(); exit_server("normal exit"); return(0); }