mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
c0c057c0df
nttrans.c: Moved common code into function in server.c
Removed left over debug level 0.
server.c: Moved common code into function in server.c
Jeremy.
(This used to be commit 34ef55b722
)
5466 lines
154 KiB
C
5466 lines
154 KiB
C
/*
|
|
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;
|
|
|
|
connection_struct Connections[MAX_CONNECTIONS];
|
|
files_struct Files[MAX_FNUMS];
|
|
|
|
/*
|
|
* Indirection for file fd's. Needed as POSIX locking
|
|
* is based on file/process, not fd/process.
|
|
*/
|
|
file_fd_struct FileFd[MAX_OPEN_FILES];
|
|
int max_file_fd_used = 0;
|
|
|
|
extern int Protocol;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* a fnum to use when chaining */
|
|
int chain_fnum = -1;
|
|
|
|
/* number of open connections */
|
|
static int num_connections_open = 0;
|
|
|
|
/* 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;
|
|
|
|
static int find_free_connection(int hash);
|
|
|
|
/* for readability... */
|
|
#define IS_DOS_READONLY(test_mode) (((test_mode) & aRONLY) != 0)
|
|
#define IS_DOS_DIR(test_mode) (((test_mode) & aDIR) != 0)
|
|
#define IS_DOS_ARCHIVE(test_mode) (((test_mode) & aARCH) != 0)
|
|
#define IS_DOS_SYSTEM(test_mode) (((test_mode) & aSYSTEM) != 0)
|
|
#define IS_DOS_HIDDEN(test_mode) (((test_mode) & aHIDDEN) != 0)
|
|
|
|
/****************************************************************************
|
|
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(int cnum,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(cnum));
|
|
/* Add in force bits */
|
|
result |= lp_force_dir_mode(SNUM(cnum));
|
|
} else {
|
|
if (MAP_ARCHIVE(cnum) && IS_DOS_ARCHIVE(dosmode))
|
|
result |= S_IXUSR;
|
|
|
|
if (MAP_SYSTEM(cnum) && IS_DOS_SYSTEM(dosmode))
|
|
result |= S_IXGRP;
|
|
|
|
if (MAP_HIDDEN(cnum) && IS_DOS_HIDDEN(dosmode))
|
|
result |= S_IXOTH;
|
|
|
|
/* Apply mode mask */
|
|
result &= lp_create_mode(SNUM(cnum));
|
|
/* Add in force bits */
|
|
result |= lp_force_create_mode(SNUM(cnum));
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
change a unix mode to a dos mode
|
|
****************************************************************************/
|
|
int dos_mode(int cnum,char *path,struct stat *sbuf)
|
|
{
|
|
int result = 0;
|
|
extern struct current_user current_user;
|
|
|
|
DEBUG(8,("dos_mode: %d %s\n", cnum, path));
|
|
|
|
if (CAN_WRITE(cnum) && !lp_alternate_permissions(SNUM(cnum))) {
|
|
if (!((sbuf->st_mode & S_IWOTH) ||
|
|
Connections[cnum].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(cnum) && ((sbuf->st_mode & S_IXUSR) != 0))
|
|
result |= aARCH;
|
|
|
|
if (MAP_SYSTEM(cnum) && ((sbuf->st_mode & S_IXGRP) != 0))
|
|
result |= aSYSTEM;
|
|
|
|
if (MAP_HIDDEN(cnum) && ((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(cnum)))
|
|
{
|
|
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(cnum,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(int cnum,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(cnum,fname,st) == dosmode) return(0);
|
|
|
|
unixmode = unix_mode(cnum,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(cnum)) mask |= S_IXUSR;
|
|
if (!MAP_SYSTEM(cnum)) mask |= S_IXGRP;
|
|
if (!MAP_HIDDEN(cnum)) 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(int cnum, 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(cnum)))
|
|
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(cnum)) {
|
|
if (((sb.st_mode & S_IWOTH) ||
|
|
Connections[cnum].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(int cnum, char *fname, time_t mtime)
|
|
{
|
|
struct utimbuf times;
|
|
|
|
if (null_mtime(mtime)) return(True);
|
|
|
|
times.modtime = times.actime = mtime;
|
|
|
|
if (file_utime(cnum, 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,int cnum,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(cnum)))) {
|
|
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(cnum, 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(cnum))) 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(cnum));
|
|
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,int cnum,pstring 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 (Connections[cnum].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,%d)\n",name,cnum));
|
|
|
|
/* 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, cnum, 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,int cnum)
|
|
{
|
|
BOOL ret;
|
|
|
|
errno = 0;
|
|
|
|
if( IS_VETO_PATH(cnum, name))
|
|
{
|
|
DEBUG(5,("file path name %s vetoed\n",name));
|
|
return(0);
|
|
}
|
|
|
|
ret = reduce_name(name,Connections[cnum].connectpath,lp_widelinks(SNUM(cnum)));
|
|
|
|
/* Check if we are allowing users to follow symlinks */
|
|
/* Patch from David Clerc <David.Clerc@cui.unige.ch>
|
|
University of Geneva */
|
|
|
|
#ifdef S_ISLNK
|
|
if (!lp_symlinks(SNUM(cnum)))
|
|
{
|
|
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.
|
|
****************************************************************************/
|
|
static 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 find an already open file by dev
|
|
and inode - increments the ref_count of the returned file_fd_struct *.
|
|
****************************************************************************/
|
|
static file_fd_struct *fd_get_already_open(struct stat *sbuf)
|
|
{
|
|
int i;
|
|
file_fd_struct *fd_ptr;
|
|
|
|
if(sbuf == 0)
|
|
return 0;
|
|
|
|
for(i = 0; i <= max_file_fd_used; i++) {
|
|
fd_ptr = &FileFd[i];
|
|
if((fd_ptr->ref_count > 0) &&
|
|
(((uint32)sbuf->st_dev) == fd_ptr->dev) &&
|
|
(((uint32)sbuf->st_ino) == fd_ptr->inode)) {
|
|
fd_ptr->ref_count++;
|
|
DEBUG(3,
|
|
("Re-used file_fd_struct %d, dev = %x, inode = %x, ref_count = %d\n",
|
|
i, fd_ptr->dev, fd_ptr->inode, fd_ptr->ref_count));
|
|
return fd_ptr;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
fd support routines - attempt to find a empty slot in the FileFd array.
|
|
Increments the ref_count of the returned entry.
|
|
****************************************************************************/
|
|
static file_fd_struct *fd_get_new(void)
|
|
{
|
|
extern struct current_user current_user;
|
|
int i;
|
|
file_fd_struct *fd_ptr;
|
|
|
|
for(i = 0; i < MAX_OPEN_FILES; i++) {
|
|
fd_ptr = &FileFd[i];
|
|
if(fd_ptr->ref_count == 0) {
|
|
fd_ptr->dev = (uint32)-1;
|
|
fd_ptr->inode = (uint32)-1;
|
|
fd_ptr->fd = -1;
|
|
fd_ptr->fd_readonly = -1;
|
|
fd_ptr->fd_writeonly = -1;
|
|
fd_ptr->real_open_flags = -1;
|
|
fd_ptr->uid_cache_count = 0;
|
|
fd_add_to_uid_cache(fd_ptr, (uid_t)current_user.uid);
|
|
fd_ptr->ref_count++;
|
|
/* Increment max used counter if neccessary, cuts down
|
|
on search time when re-using */
|
|
if(i > max_file_fd_used)
|
|
max_file_fd_used = i;
|
|
DEBUG(3,("Allocated new file_fd_struct %d, dev = %x, inode = %x\n",
|
|
i, fd_ptr->dev, fd_ptr->inode));
|
|
return fd_ptr;
|
|
}
|
|
}
|
|
DEBUG(1,("ERROR! Out of file_fd structures - perhaps increase MAX_OPEN_FILES?\n"));
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
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 on file_fd_struct %d, fd = %d, dev = %x, inode = %x, open_flags = %d, ref_count = %d.\n",
|
|
fd_ptr - &FileFd[0],
|
|
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(int fnum,int cnum,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;
|
|
files_struct *fsp = &Files[fnum];
|
|
int accmode = (flags & (O_RDONLY | O_WRONLY | O_RDWR));
|
|
|
|
fsp->open = False;
|
|
fsp->f_u.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 (!CAN_WRITE(cnum) && !Connections[cnum].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 (Connections[cnum].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) &&
|
|
Connections[cnum].printer && lp_minprintspace(SNUM(cnum))) {
|
|
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(cnum))) {
|
|
fd_attempt_close(fd_ptr);
|
|
fsp->f_u.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->f_u.fd_ptr = fd_ptr;
|
|
Connections[cnum].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 = Connections[cnum].printer;
|
|
fsp->modified = False;
|
|
fsp->granted_oplock = False;
|
|
fsp->sent_oplock_break = False;
|
|
fsp->is_directory = False;
|
|
fsp->cnum = cnum;
|
|
/*
|
|
* 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->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 && POSTSCRIPT(cnum) && fsp->can_write)
|
|
{
|
|
DEBUG(3,("Writing postscript line\n"));
|
|
write_file(fnum,"%!\n",3);
|
|
}
|
|
|
|
DEBUG( 2, ( "%s opened file %s read=%s write=%s (numopen=%d fnum=%d)\n",
|
|
*sesssetup_user ? sesssetup_user : Connections[cnum].user,fname,
|
|
BOOLSTR(fsp->can_read), BOOLSTR(fsp->can_write),
|
|
Connections[cnum].num_files_open,fnum ) );
|
|
|
|
}
|
|
|
|
#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->f_u.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(int cnum, int fnum)
|
|
{
|
|
#ifdef HAVE_FSYNC
|
|
if(lp_strict_sync(SNUM(cnum)))
|
|
fsync(Files[fnum].f_u.fd_ptr->fd);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
run a file if it is a magic script
|
|
****************************************************************************/
|
|
static void check_magic(int fnum,int cnum)
|
|
{
|
|
if (!*lp_magicscript(SNUM(cnum)))
|
|
return;
|
|
|
|
DEBUG(5,("checking magic for %s\n",Files[fnum].name));
|
|
|
|
{
|
|
char *p;
|
|
if (!(p = strrchr(Files[fnum].name,'/')))
|
|
p = Files[fnum].name;
|
|
else
|
|
p++;
|
|
|
|
if (!strequal(lp_magicscript(SNUM(cnum)),p))
|
|
return;
|
|
}
|
|
|
|
{
|
|
int ret;
|
|
pstring magic_output;
|
|
pstring fname;
|
|
pstrcpy(fname,Files[fnum].name);
|
|
|
|
if (*lp_magicoutput(SNUM(cnum)))
|
|
pstrcpy(magic_output,lp_magicoutput(SNUM(cnum)));
|
|
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)
|
|
{
|
|
int cnum = fsp->cnum;
|
|
|
|
fsp->reserved = False;
|
|
fsp->open = False;
|
|
fsp->is_directory = False;
|
|
|
|
Connections[cnum].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(int fnum, BOOL normal_close)
|
|
{
|
|
files_struct *fs_p = &Files[fnum];
|
|
int cnum = fs_p->cnum;
|
|
uint32 dev = fs_p->f_u.fd_ptr->dev;
|
|
uint32 inode = fs_p->f_u.fd_ptr->inode;
|
|
int token;
|
|
|
|
close_filestruct(fs_p);
|
|
|
|
#if USE_READ_PREDICTION
|
|
invalidate_read_prediction(fs_p->f_u.fd_ptr->fd);
|
|
#endif
|
|
|
|
if (lp_share_modes(SNUM(cnum)))
|
|
{
|
|
lock_share_entry( cnum, dev, inode, &token);
|
|
del_share_mode(token, fnum);
|
|
}
|
|
|
|
fd_attempt_close(fs_p->f_u.fd_ptr);
|
|
|
|
if (lp_share_modes(SNUM(cnum)))
|
|
unlock_share_entry( cnum, dev, inode, token);
|
|
|
|
/* NT uses smbclose to start a print - weird */
|
|
if (normal_close && fs_p->print_file)
|
|
print_file(fnum);
|
|
|
|
/* check for magic scripts */
|
|
if (normal_close)
|
|
check_magic(fnum,cnum);
|
|
|
|
if(fs_p->granted_oplock == True)
|
|
global_oplocks_open--;
|
|
|
|
fs_p->sent_oplock_break = False;
|
|
|
|
DEBUG( 2, ( "%s closed file %s (numopen=%d)\n",
|
|
Connections[cnum].user,fs_p->name,
|
|
Connections[cnum].num_files_open ) );
|
|
|
|
if (fs_p->name) {
|
|
string_free(&fs_p->name);
|
|
}
|
|
|
|
/* we will catch bugs faster by zeroing this structure */
|
|
memset(fs_p, 0, sizeof(*fs_p));
|
|
}
|
|
|
|
/****************************************************************************
|
|
Close a directory opened by an NT SMB call.
|
|
****************************************************************************/
|
|
|
|
void close_directory(int fnum)
|
|
{
|
|
files_struct *fsp = &Files[fnum];
|
|
|
|
/* TODO - walk the list of pending
|
|
change notify requests and free
|
|
any pertaining to this fnum. */
|
|
|
|
remove_pending_change_notify_requests_by_fid(fnum);
|
|
|
|
/*
|
|
* Do the code common to files and directories.
|
|
*/
|
|
close_filestruct(fsp);
|
|
|
|
if (fsp->name)
|
|
string_free(&fsp->name);
|
|
|
|
if (fsp->f_u.dir_ptr)
|
|
free((char *)fsp->f_u.dir_ptr);
|
|
|
|
/* we will catch bugs faster by zeroing this structure */
|
|
memset(fsp, 0, sizeof(*fsp));
|
|
}
|
|
|
|
/****************************************************************************
|
|
Open a directory from an NT SMB call.
|
|
****************************************************************************/
|
|
|
|
int open_directory(int fnum,int cnum,char *fname, int smb_ofun, int unixmode, int *action)
|
|
{
|
|
extern struct current_user current_user;
|
|
files_struct *fsp = &Files[fnum];
|
|
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, fnum = %d\n",
|
|
fname, fnum ));
|
|
|
|
/*
|
|
* Setup the files_struct for it.
|
|
*/
|
|
|
|
fsp->f_u.dir_ptr = NULL;
|
|
Connections[cnum].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->cnum = cnum;
|
|
/*
|
|
* 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->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(int cnum,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(cnum)))
|
|
return True;
|
|
|
|
if (sys_stat(fname,&sbuf) == -1) return(True);
|
|
|
|
dev = (uint32)sbuf.st_dev;
|
|
inode = (uint32)sbuf.st_ino;
|
|
|
|
lock_share_entry(cnum, dev, inode, &token);
|
|
num_share_modes = get_share_modes(cnum, 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(cnum, 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(cnum, 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(cnum, 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(cnum, 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(int fnum, int cnum, int token,
|
|
BOOL *share_locked)
|
|
{
|
|
if (Files[fnum].can_write){
|
|
if (is_locked(fnum,cnum,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(cnum)))
|
|
unlock_share_entry( cnum, Files[fnum].f_u.fd_ptr->dev,
|
|
Files[fnum].f_u.fd_ptr->inode, token);
|
|
close_file(fnum,False);
|
|
/* Share mode no longer locked. */
|
|
*share_locked = False;
|
|
errno = EACCES;
|
|
unix_ERR_class = ERRDOS;
|
|
unix_ERR_code = ERRlock;
|
|
}
|
|
else
|
|
ftruncate(Files[fnum].f_u.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(int fnum,int cnum,char *fname,int share_mode,int ofun,
|
|
int mode,int oplock_request, int *Access,int *action)
|
|
{
|
|
files_struct *fs_p = &Files[fnum];
|
|
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;
|
|
|
|
fs_p->open = False;
|
|
fs_p->f_u.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(cnum) || IS_DOS_READONLY(dos_mode(cnum,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(cnum)))
|
|
{
|
|
int i;
|
|
share_mode_entry *old_shares = 0;
|
|
|
|
if (file_existed)
|
|
{
|
|
dev = (uint32)sbuf.st_dev;
|
|
inode = (uint32)sbuf.st_ino;
|
|
lock_share_entry(cnum, dev, inode, &token);
|
|
share_locked = True;
|
|
num_share_modes = get_share_modes(cnum, 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(cnum, 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(cnum, 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(cnum, 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(cnum, 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(fnum,cnum,fname,flags|(flags2&~(O_TRUNC)),mode,file_existed ? &sbuf : 0);
|
|
if (!fs_p->open && flags==O_RDWR && errno!=ENOENT && fcbopen)
|
|
{
|
|
flags = O_RDONLY;
|
|
open_file(fnum,cnum,fname,flags,mode,file_existed ? &sbuf : 0 );
|
|
}
|
|
|
|
if (fs_p->open)
|
|
{
|
|
int open_mode=0;
|
|
|
|
if((share_locked == False) && lp_share_modes(SNUM(cnum)))
|
|
{
|
|
/* We created the file - thus we must now lock the share entry before creating it. */
|
|
dev = fs_p->f_u.fd_ptr->dev;
|
|
inode = fs_p->f_u.fd_ptr->inode;
|
|
lock_share_entry(cnum, 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;
|
|
}
|
|
|
|
fs_p->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(cnum)))
|
|
{
|
|
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(cnum)) &&
|
|
!IS_VETO_OPLOCK_PATH(cnum,fname))
|
|
{
|
|
fs_p->granted_oplock = True;
|
|
fs_p->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, fnum, port, oplock_request);
|
|
}
|
|
|
|
if ((flags2&O_TRUNC) && file_existed)
|
|
truncate_unless_locked(fnum,cnum,token,&share_locked);
|
|
}
|
|
|
|
if (share_locked && lp_share_modes(SNUM(cnum)))
|
|
unlock_share_entry( cnum, dev, inode, token);
|
|
}
|
|
|
|
/****************************************************************************
|
|
seek a file. Try to avoid the seek if possible
|
|
****************************************************************************/
|
|
int seek_file(int fnum,uint32 pos)
|
|
{
|
|
uint32 offset = 0;
|
|
files_struct *fsp = &Files[fnum];
|
|
|
|
if (fsp->print_file && POSTSCRIPT(fsp->cnum))
|
|
offset = 3;
|
|
|
|
fsp->pos = (int)(lseek(fsp->f_u.fd_ptr->fd,pos+offset,SEEK_SET) - offset);
|
|
return(fsp->pos);
|
|
}
|
|
|
|
/****************************************************************************
|
|
read from a file
|
|
****************************************************************************/
|
|
int read_file(int fnum,char *data,uint32 pos,int n)
|
|
{
|
|
int ret=0,readret;
|
|
files_struct *fsp = &Files[fnum];
|
|
|
|
#if USE_READ_PREDICTION
|
|
if (!fsp->can_write)
|
|
{
|
|
ret = read_predict(fsp->f_u.fd_ptr->fd,pos,data,NULL,n);
|
|
|
|
data += ret;
|
|
n -= ret;
|
|
pos += ret;
|
|
}
|
|
#endif
|
|
|
|
#if WITH_MMAP
|
|
if (fsp->mmap_ptr)
|
|
{
|
|
int num = (fsp->mmap_size > pos) ? (fsp->mmap_size - pos) : -1;
|
|
num = MIN(n,num);
|
|
if (num > 0)
|
|
{
|
|
memcpy(data,fsp->mmap_ptr+pos,num);
|
|
data += num;
|
|
pos += num;
|
|
n -= num;
|
|
ret += num;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (n <= 0)
|
|
return(ret);
|
|
|
|
if (seek_file(fnum,pos) != pos)
|
|
{
|
|
DEBUG(3,("Failed to seek to %d\n",pos));
|
|
return(ret);
|
|
}
|
|
|
|
if (n > 0) {
|
|
readret = read(fsp->f_u.fd_ptr->fd,data,n);
|
|
if (readret > 0) ret += readret;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
write to a file
|
|
****************************************************************************/
|
|
int write_file(int fnum,char *data,int n)
|
|
{
|
|
files_struct *fsp = &Files[fnum];
|
|
|
|
if (!fsp->can_write) {
|
|
errno = EPERM;
|
|
return(0);
|
|
}
|
|
|
|
if (!fsp->modified) {
|
|
struct stat st;
|
|
fsp->modified = True;
|
|
if (fstat(fsp->f_u.fd_ptr->fd,&st) == 0) {
|
|
int dosmode = dos_mode(fsp->cnum,fsp->name,&st);
|
|
if (MAP_ARCHIVE(fsp->cnum) && !IS_DOS_ARCHIVE(dosmode)) {
|
|
dos_chmod(fsp->cnum,fsp->name,dosmode | aARCH,&st);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(write_data(fsp->f_u.fd_ptr->fd,data,n));
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
load parameters specific to a connection/service
|
|
****************************************************************************/
|
|
BOOL become_service(int cnum,BOOL do_chdir)
|
|
{
|
|
extern char magic_char;
|
|
static int last_cnum = -1;
|
|
int snum;
|
|
|
|
if (!OPEN_CNUM(cnum))
|
|
{
|
|
last_cnum = -1;
|
|
return(False);
|
|
}
|
|
|
|
Connections[cnum].lastused = smb_last_time;
|
|
|
|
snum = SNUM(cnum);
|
|
|
|
if (do_chdir &&
|
|
ChDir(Connections[cnum].connectpath) != 0 &&
|
|
ChDir(Connections[cnum].origpath) != 0)
|
|
{
|
|
DEBUG( 0, ( "chdir (%s) failed cnum=%d\n",
|
|
Connections[cnum].connectpath, cnum ) );
|
|
return(False);
|
|
}
|
|
|
|
if (cnum == last_cnum)
|
|
return(True);
|
|
|
|
last_cnum = cnum;
|
|
|
|
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 <joehtg@joehtg.co.at>.
|
|
*/
|
|
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,int fnum,int line)
|
|
{
|
|
write_bmpx_struct *wbmpx = Files[fnum].wbmpx_ptr;
|
|
|
|
int32 eclass = wbmpx->wr_errclass;
|
|
int32 err = wbmpx->wr_error;
|
|
|
|
/* We can now delete the auxiliary struct */
|
|
free((char *)wbmpx);
|
|
Files[fnum].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 int sig_pipe(void)
|
|
{
|
|
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 0;
|
|
}
|
|
|
|
exit_server("Got sigpipe\n");
|
|
return(0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
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(-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",
|
|
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;
|
|
int fnum;
|
|
time_t start_time;
|
|
BOOL shutdown_server = False;
|
|
int saved_cnum;
|
|
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. */
|
|
for( fnum = 0; fnum < MAX_FNUMS; fnum++)
|
|
{
|
|
if(OPEN_FNUM(fnum))
|
|
{
|
|
if((Files[fnum].f_u.fd_ptr->dev == dev) && (Files[fnum].f_u.fd_ptr->inode == inode) &&
|
|
(Files[fnum].open_time.tv_sec == tval->tv_sec) &&
|
|
(Files[fnum].open_time.tv_usec == tval->tv_usec)) {
|
|
fsp = &Files[fnum];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (fnum = %d) ", dev, inode, fnum );
|
|
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 (fnum = %d, ", fsp->name, fnum );
|
|
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 (fnum = %d, ", fsp->name, fnum );
|
|
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->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,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_cnum = fsp->cnum;
|
|
saved_vuid = current_user.vuid;
|
|
GetWd(saved_dir);
|
|
unbecome_user();
|
|
|
|
while(OPEN_FNUM(fnum) && 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->name ) );
|
|
DEBUGADD( 0, ( "(fnum = %d, dev = %x, inode = %x).\n", fnum, 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->name );
|
|
dbgtext( "(fnum = %d, dev = %x, inode = %x).\n", fnum, dev, inode );
|
|
}
|
|
shutdown_server = True;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Go back to being the user who requested the oplock
|
|
* break.
|
|
*/
|
|
if(!become_user(&Connections[saved_cnum], saved_cnum, 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_FNUM(fnum))
|
|
{
|
|
/* 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( "fnum = %d, dev = %x, inode = %x.\n", fnum, 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, ( "%s 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;
|
|
}
|
|
|
|
/****************************************************************************
|
|
check if a snum is in use
|
|
****************************************************************************/
|
|
BOOL snum_used(int snum)
|
|
{
|
|
int i;
|
|
for (i=0;i<MAX_CONNECTIONS;i++)
|
|
if (OPEN_CNUM(i) && (SNUM(i) == snum))
|
|
return(True);
|
|
return(False);
|
|
}
|
|
|
|
/****************************************************************************
|
|
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(snum_used);
|
|
|
|
ret = lp_load(servicesf,False,False,True);
|
|
|
|
/* 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(-1,True);
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
this prevents zombie child processes
|
|
****************************************************************************/
|
|
static BOOL reload_after_sighup = False;
|
|
|
|
static int sig_hup(void)
|
|
{
|
|
BlockSignals(True,SIGHUP);
|
|
DEBUG(0,("Got SIGHUP\n"));
|
|
|
|
/*
|
|
* Fix from <branko.cibej@hermes.si> here.
|
|
* We used to reload in the signal handler - this
|
|
* is a *BIG* no-no.
|
|
*/
|
|
|
|
reload_after_sighup = True;
|
|
BlockSignals(False,SIGHUP);
|
|
return(0);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
make a connection to a service
|
|
****************************************************************************/
|
|
int make_connection(char *service,char *user,char *password, int pwlen, char *dev,uint16 vuid)
|
|
{
|
|
int cnum;
|
|
int snum;
|
|
struct passwd *pass = NULL;
|
|
connection_struct *pcon;
|
|
BOOL guest = False;
|
|
BOOL force = False;
|
|
|
|
strlower(service);
|
|
|
|
snum = find_service(service);
|
|
if (snum < 0)
|
|
{
|
|
extern int Client;
|
|
if (strequal(service,"IPC$"))
|
|
{
|
|
DEBUG( 3, ( "refusing IPC connection\n" ) );
|
|
return( -3 );
|
|
}
|
|
|
|
DEBUG( 0, ( "%s (%s) couldn't find service %s\n",
|
|
remote_machine, client_addr(Client), service ) );
|
|
return(-2);
|
|
}
|
|
|
|
if (strequal(service,HOMES_NAME))
|
|
{
|
|
if (*user && Get_Pwnam(user,True))
|
|
return(make_connection(user,user,password,pwlen,dev,vuid));
|
|
|
|
if(lp_security() != SEC_SHARE)
|
|
{
|
|
if (validated_username(vuid))
|
|
{
|
|
pstrcpy(user,validated_username(vuid));
|
|
return(make_connection(user,user,password,pwlen,dev,vuid));
|
|
}
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!lp_snum_ok(snum) || !check_access(snum)) {
|
|
return(-4);
|
|
}
|
|
|
|
/* 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"));
|
|
return(-6);
|
|
}
|
|
|
|
/* 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 ) );
|
|
return(-1);
|
|
}
|
|
|
|
cnum = find_free_connection( str_checksum(service) + str_checksum(user) );
|
|
if (cnum < 0)
|
|
{
|
|
DEBUG( 0, ( "Couldn't find free connection.\n" ) );
|
|
return(-1);
|
|
}
|
|
|
|
pcon = &Connections[cnum];
|
|
bzero((char *)pcon,sizeof(*pcon));
|
|
|
|
/* find out some info about the user */
|
|
pass = Get_Pwnam(user,True);
|
|
|
|
if (pass == NULL)
|
|
{
|
|
DEBUG( 0, ( "Couldn't find account %s\n", user ) );
|
|
return(-7);
|
|
}
|
|
|
|
pcon->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))
|
|
pcon->read_only = True;
|
|
|
|
StrnCpy(list,lp_writelist(snum),sizeof(pstring)-1);
|
|
string_sub(list,"%S",service);
|
|
|
|
if (user_in_list(user,list))
|
|
pcon->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
|
|
&& !pcon->read_only)
|
|
#else
|
|
)
|
|
#endif
|
|
{
|
|
pcon->admin_user = True;
|
|
DEBUG(0,("%s logged in as admin user (root privileges)\n",user));
|
|
}
|
|
else
|
|
pcon->admin_user = False;
|
|
|
|
pcon->force_user = force;
|
|
pcon->vuid = vuid;
|
|
pcon->uid = pass->pw_uid;
|
|
pcon->gid = pass->pw_gid;
|
|
pcon->num_files_open = 0;
|
|
pcon->lastused = time(NULL);
|
|
pcon->service = snum;
|
|
pcon->used = True;
|
|
pcon->printer = (strncmp(dev,"LPT",3) == 0);
|
|
pcon->ipc = (strncmp(dev,"IPC",3) == 0);
|
|
pcon->dirptr = NULL;
|
|
pcon->veto_list = NULL;
|
|
pcon->hide_list = NULL;
|
|
pcon->veto_oplock_list = NULL;
|
|
string_set(&pcon->dirpath,"");
|
|
string_set(&pcon->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)
|
|
{
|
|
pcon->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)
|
|
{
|
|
pcon->uid = pass2->pw_uid;
|
|
string_set(&pcon->user,fuser);
|
|
fstrcpy(user,fuser);
|
|
pcon->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(cnum,s);
|
|
string_set(&pcon->connectpath,s);
|
|
DEBUG(3,("Connect path is %s\n",s));
|
|
}
|
|
|
|
/* groups stuff added by ih */
|
|
pcon->ngroups = 0;
|
|
pcon->groups = NULL;
|
|
|
|
if (!IS_IPC(cnum))
|
|
{
|
|
/* Find all the groups this uid is in and store them. Used by become_user() */
|
|
setup_groups(pcon->user,pcon->uid,pcon->gid,
|
|
&pcon->ngroups,&pcon->groups);
|
|
|
|
/* check number of connections */
|
|
if (!claim_connection(cnum,
|
|
lp_servicename(SNUM(cnum)),
|
|
lp_max_connections(SNUM(cnum)),False))
|
|
{
|
|
DEBUG(1,("too many connections - rejected\n"));
|
|
return(-8);
|
|
}
|
|
|
|
if (lp_status(SNUM(cnum)))
|
|
claim_connection(cnum,"STATUS.",MAXSTATUS,False);
|
|
} /* IS_IPC */
|
|
|
|
pcon->open = True;
|
|
|
|
/* execute any "root preexec = " line */
|
|
if (*lp_rootpreexec(SNUM(cnum)))
|
|
{
|
|
pstring cmd;
|
|
pstrcpy(cmd,lp_rootpreexec(SNUM(cnum)));
|
|
standard_sub(cnum,cmd);
|
|
DEBUG(5,("cmd=%s\n",cmd));
|
|
smbrun(cmd,NULL,False);
|
|
}
|
|
|
|
if (!become_user(&Connections[cnum], cnum,pcon->vuid))
|
|
{
|
|
DEBUG(0,("Can't become connected user!\n"));
|
|
pcon->open = False;
|
|
if (!IS_IPC(cnum)) {
|
|
yield_connection(cnum,
|
|
lp_servicename(SNUM(cnum)),
|
|
lp_max_connections(SNUM(cnum)));
|
|
if (lp_status(SNUM(cnum))) yield_connection(cnum,"STATUS.",MAXSTATUS);
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
if (ChDir(pcon->connectpath) != 0)
|
|
{
|
|
DEBUG(0,("Can't change directory to %s (%s)\n",
|
|
pcon->connectpath,strerror(errno)));
|
|
pcon->open = False;
|
|
unbecome_user();
|
|
if (!IS_IPC(cnum)) {
|
|
yield_connection(cnum,
|
|
lp_servicename(SNUM(cnum)),
|
|
lp_max_connections(SNUM(cnum)));
|
|
if (lp_status(SNUM(cnum))) yield_connection(cnum,"STATUS.",MAXSTATUS);
|
|
}
|
|
return(-5);
|
|
}
|
|
|
|
string_set(&pcon->origpath,pcon->connectpath);
|
|
|
|
#if SOFTLINK_OPTIMISATION
|
|
/* resolve any soft links early */
|
|
{
|
|
pstring s;
|
|
pstrcpy(s,pcon->connectpath);
|
|
GetWd(s);
|
|
string_set(&pcon->connectpath,s);
|
|
ChDir(pcon->connectpath);
|
|
}
|
|
#endif
|
|
|
|
num_connections_open++;
|
|
add_session_user(user);
|
|
|
|
/* execute any "preexec = " line */
|
|
if (*lp_preexec(SNUM(cnum)))
|
|
{
|
|
pstring cmd;
|
|
pstrcpy(cmd,lp_preexec(SNUM(cnum)));
|
|
standard_sub(cnum,cmd);
|
|
smbrun(cmd,NULL,False);
|
|
}
|
|
|
|
/* we've finished with the sensitive stuff */
|
|
unbecome_user();
|
|
|
|
/* Add veto/hide lists */
|
|
if (!IS_IPC(cnum) && !IS_PRINT(cnum))
|
|
{
|
|
set_namearray( &pcon->veto_list, lp_veto_files(SNUM(cnum)));
|
|
set_namearray( &pcon->hide_list, lp_hide_files(SNUM(cnum)));
|
|
set_namearray( &pcon->veto_oplock_list, lp_veto_oplocks(SNUM(cnum)));
|
|
}
|
|
|
|
if( DEBUGLVL( IS_IPC(cnum) ? 3 : 1 ) )
|
|
{
|
|
extern int Client;
|
|
|
|
dbgtext( "%s (%s) ", remote_machine, client_addr(Client) );
|
|
dbgtext( "connect to service %s ", lp_servicename(SNUM(cnum)) );
|
|
dbgtext( "as user %s ", user );
|
|
dbgtext( "(uid=%d, gid=%d) ", pcon->uid, pcon->gid );
|
|
dbgtext( "(pid %d)\n", (int)getpid() );
|
|
}
|
|
|
|
return(cnum);
|
|
}
|
|
|
|
/****************************************************************************
|
|
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.
|
|
****************************************************************************/
|
|
|
|
static BOOL attempt_close_oplocked_file(files_struct *fsp)
|
|
{
|
|
|
|
DEBUG(5,("attempt_close_oplocked_file: checking file %s.\n", fsp->name));
|
|
|
|
if (fsp->open && fsp->granted_oplock && !fsp->sent_oplock_break) {
|
|
|
|
/* Try and break the oplock. */
|
|
file_fd_struct *fd_ptr = fsp->f_u.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;
|
|
}
|
|
|
|
/****************************************************************************
|
|
find first available file slot
|
|
****************************************************************************/
|
|
int find_free_file(void )
|
|
{
|
|
int i;
|
|
static int first_file;
|
|
|
|
/* we want to give out file handles differently on each new
|
|
connection because of a common bug in MS clients where they try to
|
|
reuse a file descriptor from an earlier smb connection. This code
|
|
increases the chance that the errant client will get an error rather
|
|
than causing corruption */
|
|
if (first_file == 0) {
|
|
first_file = (getpid() ^ (int)time(NULL)) % MAX_FNUMS;
|
|
if (first_file == 0) first_file = 1;
|
|
}
|
|
|
|
if (first_file >= MAX_FNUMS)
|
|
first_file = 1;
|
|
|
|
for (i=first_file;i<MAX_FNUMS;i++)
|
|
if (!Files[i].open && !Files[i].reserved) {
|
|
memset(&Files[i], 0, sizeof(Files[i]));
|
|
first_file = i+1;
|
|
Files[i].reserved = True;
|
|
return(i);
|
|
}
|
|
|
|
/* returning a file handle of 0 is a bad idea - so we start at 1 */
|
|
for (i=1;i<first_file;i++)
|
|
if (!Files[i].open && !Files[i].reserved) {
|
|
memset(&Files[i], 0, sizeof(Files[i]));
|
|
first_file = i+1;
|
|
Files[i].reserved = True;
|
|
return(i);
|
|
}
|
|
|
|
/*
|
|
* Before we give up, go through the open files
|
|
* and see if there are any files opened with a
|
|
* batch oplock. If so break the oplock and then
|
|
* re-use that entry (if it becomes closed).
|
|
* This may help as NT/95 clients tend to keep
|
|
* files batch oplocked for quite a long time
|
|
* after they have finished with them.
|
|
*/
|
|
for (i=first_file;i<MAX_FNUMS;i++) {
|
|
if(attempt_close_oplocked_file( &Files[i])) {
|
|
memset(&Files[i], 0, sizeof(Files[i]));
|
|
first_file = i+1;
|
|
Files[i].reserved = True;
|
|
return(i);
|
|
}
|
|
}
|
|
|
|
for (i=1;i<MAX_FNUMS;i++) {
|
|
if(attempt_close_oplocked_file( &Files[i])) {
|
|
memset(&Files[i], 0, sizeof(Files[i]));
|
|
first_file = i+1;
|
|
Files[i].reserved = True;
|
|
return(i);
|
|
}
|
|
}
|
|
|
|
DEBUG(1,("ERROR! Out of file structures - perhaps increase MAX_OPEN_FILES?\n"));
|
|
return(-1);
|
|
}
|
|
|
|
/****************************************************************************
|
|
find first available connection slot, starting from a random position.
|
|
The randomisation stops problems with the server dieing and clients
|
|
thinking the server is still available.
|
|
****************************************************************************/
|
|
static int find_free_connection(int hash )
|
|
{
|
|
int i;
|
|
BOOL used=False;
|
|
hash = (hash % (MAX_CONNECTIONS-2))+1;
|
|
|
|
again:
|
|
|
|
for (i=hash+1;i!=hash;)
|
|
{
|
|
if (!Connections[i].open && Connections[i].used == used)
|
|
{
|
|
DEBUG(3,("found free connection number %d\n",i));
|
|
return(i);
|
|
}
|
|
i++;
|
|
if (i == MAX_CONNECTIONS)
|
|
i = 1;
|
|
}
|
|
|
|
if (!used)
|
|
{
|
|
used = !used;
|
|
goto again;
|
|
}
|
|
|
|
DEBUG(1,("ERROR! Out of connection structures\n"));
|
|
return(-1);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
reply for the core protocol
|
|
****************************************************************************/
|
|
int reply_corep(char *outbuf)
|
|
{
|
|
int outsize = set_message(outbuf,1,0,True);
|
|
|
|
Protocol = PROTOCOL_CORE;
|
|
|
|
return outsize;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
reply for the coreplus protocol
|
|
****************************************************************************/
|
|
int reply_coreplus(char *outbuf)
|
|
{
|
|
int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
|
|
int outsize = set_message(outbuf,13,0,True);
|
|
SSVAL(outbuf,smb_vwv5,raw); /* tell redirector we support
|
|
readbraw and writebraw (possibly) */
|
|
CVAL(outbuf,smb_flg) = 0x81; /* Reply, SMBlockread, SMBwritelock supported */
|
|
SSVAL(outbuf,smb_vwv1,0x1); /* user level security, don't encrypt */
|
|
|
|
Protocol = PROTOCOL_COREPLUS;
|
|
|
|
return outsize;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
reply for the lanman 1.0 protocol
|
|
****************************************************************************/
|
|
int reply_lanman1(char *outbuf)
|
|
{
|
|
int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
|
|
int secword=0;
|
|
BOOL doencrypt = SMBENCRYPT();
|
|
time_t t = time(NULL);
|
|
|
|
if (lp_security()>=SEC_USER) secword |= 1;
|
|
if (doencrypt) secword |= 2;
|
|
|
|
set_message(outbuf,13,doencrypt?8:0,True);
|
|
SSVAL(outbuf,smb_vwv1,secword);
|
|
/* Create a token value and add it to the outgoing packet. */
|
|
if (doencrypt)
|
|
generate_next_challenge(smb_buf(outbuf));
|
|
|
|
Protocol = PROTOCOL_LANMAN1;
|
|
|
|
CVAL(outbuf,smb_flg) = 0x81; /* Reply, SMBlockread, SMBwritelock supported */
|
|
SSVAL(outbuf,smb_vwv2,max_recv);
|
|
SSVAL(outbuf,smb_vwv3,lp_maxmux()); /* maxmux */
|
|
SSVAL(outbuf,smb_vwv4,1);
|
|
SSVAL(outbuf,smb_vwv5,raw); /* tell redirector we support
|
|
readbraw writebraw (possibly) */
|
|
SIVAL(outbuf,smb_vwv6,getpid());
|
|
SSVAL(outbuf,smb_vwv10, TimeDiff(t)/60);
|
|
|
|
put_dos_date(outbuf,smb_vwv8,t);
|
|
|
|
return (smb_len(outbuf)+4);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
reply for the lanman 2.0 protocol
|
|
****************************************************************************/
|
|
int reply_lanman2(char *outbuf)
|
|
{
|
|
int raw = (lp_readraw()?1:0) | (lp_writeraw()?2:0);
|
|
int secword=0;
|
|
BOOL doencrypt = SMBENCRYPT();
|
|
time_t t = time(NULL);
|
|
struct cli_state *cli = NULL;
|
|
char cryptkey[8];
|
|
char crypt_len = 0;
|
|
|
|
if (lp_security() == SEC_SERVER) {
|
|
cli = server_cryptkey();
|
|
}
|
|
|
|
if (cli) {
|
|
DEBUG(3,("using password server validation\n"));
|
|
doencrypt = ((cli->sec_mode & 2) != 0);
|
|
}
|
|
|
|
if (lp_security()>=SEC_USER) secword |= 1;
|
|
if (doencrypt) secword |= 2;
|
|
|
|
if (doencrypt) {
|
|
crypt_len = 8;
|
|
if (!cli) {
|
|
generate_next_challenge(cryptkey);
|
|
} else {
|
|
memcpy(cryptkey, cli->cryptkey, 8);
|
|
set_challenge(cli->cryptkey);
|
|
}
|
|
}
|
|
|
|
set_message(outbuf,13,crypt_len,True);
|
|
SSVAL(outbuf,smb_vwv1,secword);
|
|
SIVAL(outbuf,smb_vwv6,getpid());
|
|
if (doencrypt)
|
|
memcpy(smb_buf(outbuf), cryptkey, 8);
|
|
|
|
Protocol = PROTOCOL_LANMAN2;
|
|
|
|
CVAL(outbuf,smb_flg) = 0x81; /* Reply, SMBlockread, SMBwritelock supported */
|
|
SSVAL(outbuf,smb_vwv2,max_recv);
|
|
SSVAL(outbuf,smb_vwv3,lp_maxmux());
|
|
SSVAL(outbuf,smb_vwv4,1);
|
|
SSVAL(outbuf,smb_vwv5,raw); /* readbraw and/or writebraw */
|
|
SSVAL(outbuf,smb_vwv10, TimeDiff(t)/60);
|
|
put_dos_date(outbuf,smb_vwv8,t);
|
|
|
|
return (smb_len(outbuf)+4);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
reply for the nt protocol
|
|
****************************************************************************/
|
|
int reply_nt1(char *outbuf)
|
|
{
|
|
/* dual names + lock_and_read + nt SMBs + remote API calls */
|
|
int capabilities = CAP_NT_FIND|CAP_LOCK_AND_READ|CAP_RPC_REMOTE_APIS |CAP_NT_SMBS;
|
|
|
|
/*
|
|
other valid capabilities which we may support at some time...
|
|
CAP_LARGE_FILES|
|
|
CAP_LARGE_READX|CAP_STATUS32|CAP_LEVEL_II_OPLOCKS;
|
|
*/
|
|
|
|
int secword=0;
|
|
BOOL doencrypt = SMBENCRYPT();
|
|
time_t t = time(NULL);
|
|
int data_len;
|
|
struct cli_state *cli = NULL;
|
|
char cryptkey[8];
|
|
char crypt_len = 0;
|
|
|
|
if (lp_security() == SEC_SERVER) {
|
|
cli = server_cryptkey();
|
|
}
|
|
|
|
if (cli) {
|
|
DEBUG(3,("using password server validation\n"));
|
|
doencrypt = ((cli->sec_mode & 2) != 0);
|
|
}
|
|
|
|
if (doencrypt) {
|
|
crypt_len = 8;
|
|
if (!cli) {
|
|
generate_next_challenge(cryptkey);
|
|
} else {
|
|
memcpy(cryptkey, cli->cryptkey, 8);
|
|
set_challenge(cli->cryptkey);
|
|
}
|
|
}
|
|
|
|
if (lp_readraw() && lp_writeraw()) {
|
|
capabilities |= CAP_RAW_MODE;
|
|
}
|
|
|
|
if (lp_security() >= SEC_USER) secword |= 1;
|
|
if (doencrypt) secword |= 2;
|
|
|
|
/* decide where (if) to put the encryption challenge, and
|
|
follow it with the OEM'd domain name
|
|
*/
|
|
data_len = crypt_len + strlen(global_myworkgroup) + 1;
|
|
|
|
set_message(outbuf,17,data_len,True);
|
|
pstrcpy(smb_buf(outbuf)+crypt_len, global_myworkgroup);
|
|
|
|
CVAL(outbuf,smb_vwv1) = secword;
|
|
SSVALS(outbuf,smb_vwv16+1,crypt_len);
|
|
if (doencrypt)
|
|
memcpy(smb_buf(outbuf), cryptkey, 8);
|
|
|
|
Protocol = PROTOCOL_NT1;
|
|
|
|
SSVAL(outbuf,smb_vwv1+1,lp_maxmux()); /* maxmpx */
|
|
SSVAL(outbuf,smb_vwv2+1,1); /* num vcs */
|
|
SIVAL(outbuf,smb_vwv3+1,0xffff); /* max buffer. LOTS! */
|
|
SIVAL(outbuf,smb_vwv5+1,0x10000); /* raw size. full 64k */
|
|
SIVAL(outbuf,smb_vwv7+1,getpid()); /* session key */
|
|
SIVAL(outbuf,smb_vwv9+1,capabilities); /* capabilities */
|
|
put_long_date(outbuf+smb_vwv11+1,t);
|
|
SSVALS(outbuf,smb_vwv15+1,TimeDiff(t)/60);
|
|
SSVAL(outbuf,smb_vwv17,data_len); /* length of challenge+domain strings */
|
|
|
|
return (smb_len(outbuf)+4);
|
|
}
|
|
|
|
/* these are the protocol lists used for auto architecture detection:
|
|
|
|
WinNT 3.51:
|
|
protocol [PC NETWORK PROGRAM 1.0]
|
|
protocol [XENIX CORE]
|
|
protocol [MICROSOFT NETWORKS 1.03]
|
|
protocol [LANMAN1.0]
|
|
protocol [Windows for Workgroups 3.1a]
|
|
protocol [LM1.2X002]
|
|
protocol [LANMAN2.1]
|
|
protocol [NT LM 0.12]
|
|
|
|
Win95:
|
|
protocol [PC NETWORK PROGRAM 1.0]
|
|
protocol [XENIX CORE]
|
|
protocol [MICROSOFT NETWORKS 1.03]
|
|
protocol [LANMAN1.0]
|
|
protocol [Windows for Workgroups 3.1a]
|
|
protocol [LM1.2X002]
|
|
protocol [LANMAN2.1]
|
|
protocol [NT LM 0.12]
|
|
|
|
OS/2:
|
|
protocol [PC NETWORK PROGRAM 1.0]
|
|
protocol [XENIX CORE]
|
|
protocol [LANMAN1.0]
|
|
protocol [LM1.2X002]
|
|
protocol [LANMAN2.1]
|
|
*/
|
|
|
|
/*
|
|
* Modified to recognize the architecture of the remote machine better.
|
|
*
|
|
* This appears to be the matrix of which protocol is used by which
|
|
* MS product.
|
|
Protocol WfWg Win95 WinNT OS/2
|
|
PC NETWORK PROGRAM 1.0 1 1 1 1
|
|
XENIX CORE 2 2
|
|
MICROSOFT NETWORKS 3.0 2 2
|
|
DOS LM1.2X002 3 3
|
|
MICROSOFT NETWORKS 1.03 3
|
|
DOS LANMAN2.1 4 4
|
|
LANMAN1.0 4 3
|
|
Windows for Workgroups 3.1a 5 5 5
|
|
LM1.2X002 6 4
|
|
LANMAN2.1 7 5
|
|
NT LM 0.12 6 8
|
|
*
|
|
* tim@fsg.com 09/29/95
|
|
*/
|
|
|
|
#define ARCH_WFWG 0x3 /* This is a fudge because WfWg is like Win95 */
|
|
#define ARCH_WIN95 0x2
|
|
#define ARCH_OS2 0xC /* Again OS/2 is like NT */
|
|
#define ARCH_WINNT 0x8
|
|
#define ARCH_SAMBA 0x10
|
|
|
|
#define ARCH_ALL 0x1F
|
|
|
|
/* List of supported protocols, most desired first */
|
|
struct {
|
|
char *proto_name;
|
|
char *short_name;
|
|
int (*proto_reply_fn)(char *);
|
|
int protocol_level;
|
|
} supported_protocols[] = {
|
|
{"NT LANMAN 1.0", "NT1", reply_nt1, PROTOCOL_NT1},
|
|
{"NT LM 0.12", "NT1", reply_nt1, PROTOCOL_NT1},
|
|
{"LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
|
|
{"Samba", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
|
|
{"DOS LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
|
|
{"LANMAN1.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
|
|
{"MICROSOFT NETWORKS 3.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
|
|
{"MICROSOFT NETWORKS 1.03", "COREPLUS", reply_coreplus, PROTOCOL_COREPLUS},
|
|
{"PC NETWORK PROGRAM 1.0", "CORE", reply_corep, PROTOCOL_CORE},
|
|
{NULL,NULL},
|
|
};
|
|
|
|
|
|
/****************************************************************************
|
|
reply to a negprot
|
|
****************************************************************************/
|
|
static int reply_negprot(char *inbuf,char *outbuf, int dum_size, int dum_buffsize)
|
|
{
|
|
int outsize = set_message(outbuf,1,0,True);
|
|
int Index=0;
|
|
int choice= -1;
|
|
int protocol;
|
|
char *p;
|
|
int bcc = SVAL(smb_buf(inbuf),-2);
|
|
int arch = ARCH_ALL;
|
|
|
|
p = smb_buf(inbuf)+1;
|
|
while (p < (smb_buf(inbuf) + bcc))
|
|
{
|
|
Index++;
|
|
DEBUG(3,("Requested protocol [%s]\n",p));
|
|
if (strcsequal(p,"Windows for Workgroups 3.1a"))
|
|
arch &= ( ARCH_WFWG | ARCH_WIN95 | ARCH_WINNT );
|
|
else if (strcsequal(p,"DOS LM1.2X002"))
|
|
arch &= ( ARCH_WFWG | ARCH_WIN95 );
|
|
else if (strcsequal(p,"DOS LANMAN2.1"))
|
|
arch &= ( ARCH_WFWG | ARCH_WIN95 );
|
|
else if (strcsequal(p,"NT LM 0.12"))
|
|
arch &= ( ARCH_WIN95 | ARCH_WINNT );
|
|
else if (strcsequal(p,"LANMAN2.1"))
|
|
arch &= ( ARCH_WINNT | ARCH_OS2 );
|
|
else if (strcsequal(p,"LM1.2X002"))
|
|
arch &= ( ARCH_WINNT | ARCH_OS2 );
|
|
else if (strcsequal(p,"MICROSOFT NETWORKS 1.03"))
|
|
arch &= ARCH_WINNT;
|
|
else if (strcsequal(p,"XENIX CORE"))
|
|
arch &= ( ARCH_WINNT | ARCH_OS2 );
|
|
else if (strcsequal(p,"Samba")) {
|
|
arch = ARCH_SAMBA;
|
|
break;
|
|
}
|
|
|
|
p += strlen(p) + 2;
|
|
}
|
|
|
|
switch ( arch ) {
|
|
case ARCH_SAMBA:
|
|
set_remote_arch(RA_SAMBA);
|
|
break;
|
|
case ARCH_WFWG:
|
|
set_remote_arch(RA_WFWG);
|
|
break;
|
|
case ARCH_WIN95:
|
|
set_remote_arch(RA_WIN95);
|
|
break;
|
|
case ARCH_WINNT:
|
|
set_remote_arch(RA_WINNT);
|
|
break;
|
|
case ARCH_OS2:
|
|
set_remote_arch(RA_OS2);
|
|
break;
|
|
default:
|
|
set_remote_arch(RA_UNKNOWN);
|
|
break;
|
|
}
|
|
|
|
/* possibly reload - change of architecture */
|
|
reload_services(True);
|
|
|
|
/* a special case to stop password server loops */
|
|
if (Index == 1 && strequal(remote_machine,myhostname) &&
|
|
(lp_security()==SEC_SERVER || lp_security()==SEC_DOMAIN))
|
|
exit_server("Password server loop!");
|
|
|
|
/* Check for protocols, most desirable first */
|
|
for (protocol = 0; supported_protocols[protocol].proto_name; protocol++)
|
|
{
|
|
p = smb_buf(inbuf)+1;
|
|
Index = 0;
|
|
if (lp_maxprotocol() >= supported_protocols[protocol].protocol_level)
|
|
while (p < (smb_buf(inbuf) + bcc))
|
|
{
|
|
if (strequal(p,supported_protocols[protocol].proto_name))
|
|
choice = Index;
|
|
Index++;
|
|
p += strlen(p) + 2;
|
|
}
|
|
if(choice != -1)
|
|
break;
|
|
}
|
|
|
|
SSVAL(outbuf,smb_vwv0,choice);
|
|
if(choice != -1) {
|
|
extern fstring remote_proto;
|
|
fstrcpy(remote_proto,supported_protocols[protocol].short_name);
|
|
reload_services(True);
|
|
outsize = supported_protocols[protocol].proto_reply_fn(outbuf);
|
|
DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
|
|
}
|
|
else {
|
|
DEBUG(0,("No protocol supported !\n"));
|
|
}
|
|
SSVAL(outbuf,smb_vwv0,choice);
|
|
|
|
DEBUG( 5, ( "negprot index=%d\n", choice ) );
|
|
|
|
return(outsize);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
close all open files for a connection
|
|
****************************************************************************/
|
|
static void close_open_files(int cnum)
|
|
{
|
|
int i;
|
|
for (i=0;i<MAX_FNUMS;i++)
|
|
if( Files[i].cnum == cnum && Files[i].open) {
|
|
if(Files[i].is_directory)
|
|
close_directory(i);
|
|
else
|
|
close_file(i,False);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
close a cnum
|
|
****************************************************************************/
|
|
void close_cnum(int cnum, uint16 vuid)
|
|
{
|
|
extern int Client;
|
|
DirCacheFlush(SNUM(cnum));
|
|
|
|
unbecome_user();
|
|
|
|
if (!OPEN_CNUM(cnum))
|
|
{
|
|
DEBUG(0,("Can't close cnum %d\n",cnum));
|
|
return;
|
|
}
|
|
|
|
DEBUG( IS_IPC(cnum)?3:1, ( "%s (%s) closed connection to service %s\n",
|
|
remote_machine,client_addr(Client),
|
|
lp_servicename(SNUM(cnum)) ) );
|
|
|
|
yield_connection(cnum,
|
|
lp_servicename(SNUM(cnum)),
|
|
lp_max_connections(SNUM(cnum)));
|
|
|
|
if (lp_status(SNUM(cnum)))
|
|
yield_connection(cnum,"STATUS.",MAXSTATUS);
|
|
|
|
close_open_files(cnum);
|
|
dptr_closecnum(cnum);
|
|
|
|
/* execute any "postexec = " line */
|
|
if (*lp_postexec(SNUM(cnum)) && become_user(&Connections[cnum], cnum,vuid))
|
|
{
|
|
pstring cmd;
|
|
pstrcpy(cmd,lp_postexec(SNUM(cnum)));
|
|
standard_sub(cnum,cmd);
|
|
smbrun(cmd,NULL,False);
|
|
unbecome_user();
|
|
}
|
|
|
|
unbecome_user();
|
|
/* execute any "root postexec = " line */
|
|
if (*lp_rootpostexec(SNUM(cnum)))
|
|
{
|
|
pstring cmd;
|
|
pstrcpy(cmd,lp_rootpostexec(SNUM(cnum)));
|
|
standard_sub(cnum,cmd);
|
|
smbrun(cmd,NULL,False);
|
|
}
|
|
|
|
Connections[cnum].open = False;
|
|
num_connections_open--;
|
|
if (Connections[cnum].ngroups && Connections[cnum].groups)
|
|
{
|
|
free(Connections[cnum].groups);
|
|
Connections[cnum].groups = NULL;
|
|
Connections[cnum].ngroups = 0;
|
|
}
|
|
|
|
free_namearray(Connections[cnum].veto_list);
|
|
free_namearray(Connections[cnum].hide_list);
|
|
free_namearray(Connections[cnum].veto_oplock_list);
|
|
|
|
string_set(&Connections[cnum].user,"");
|
|
string_set(&Connections[cnum].dirpath,"");
|
|
string_set(&Connections[cnum].connectpath,"");
|
|
}
|
|
|
|
|
|
|
|
#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",rlp.rlim_cur,rlp.rlim_max));
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
DEBUG(0,("Dumping core in %s\n",dname));
|
|
return(True);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
exit the server
|
|
****************************************************************************/
|
|
void exit_server(char *reason)
|
|
{
|
|
static int firsttime=1;
|
|
int i;
|
|
|
|
if (!firsttime) exit(0);
|
|
firsttime = 0;
|
|
|
|
unbecome_user();
|
|
DEBUG(2,("Closing connections\n"));
|
|
for (i=0;i<MAX_CONNECTIONS;i++)
|
|
if (Connections[i].open)
|
|
close_cnum(i,(uint16)-1);
|
|
#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);
|
|
}
|
|
|
|
/****************************************************************************
|
|
do some standard substitutions in a string
|
|
****************************************************************************/
|
|
void standard_sub(int cnum,char *str)
|
|
{
|
|
if (VALID_CNUM(cnum)) {
|
|
char *p, *s, *home;
|
|
|
|
for ( s=str ; (p=strchr(s, '%')) != NULL ; s=p ) {
|
|
switch (*(p+1)) {
|
|
case 'H' : if ((home = get_home_dir(Connections[cnum].user))!=NULL)
|
|
string_sub(p,"%H",home);
|
|
else
|
|
p += 2;
|
|
break;
|
|
case 'P' : string_sub(p,"%P",Connections[cnum].connectpath); break;
|
|
case 'S' : string_sub(p,"%S",lp_servicename(Connections[cnum].service)); break;
|
|
case 'g' : string_sub(p,"%g",gidtoname(Connections[cnum].gid)); break;
|
|
case 'u' : string_sub(p,"%u",Connections[cnum].user); break;
|
|
/*
|
|
* Patch from jkf@soton.ac.uk
|
|
* Left the %N (NIS server name) in standard_sub_basic as it
|
|
* is a feature for logon servers, hence uses the username.
|
|
* The %p (NIS server path) code is here as it is used
|
|
* instead of the default "path =" string in [homes] and so
|
|
* needs the service name, not the username.
|
|
*/
|
|
case 'p' : string_sub(p,"%p",automount_path(lp_servicename(Connections[cnum].service))); break;
|
|
case '\0' : p++; break; /* don't run off the end of the string */
|
|
default : p+=2; break;
|
|
}
|
|
}
|
|
}
|
|
standard_sub_basic(str);
|
|
}
|
|
|
|
/*
|
|
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)(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|AS_GUEST},
|
|
{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;match<num_smb_messages;match++)
|
|
if (smb_messages[match].code == type)
|
|
break;
|
|
|
|
if (match == num_smb_messages)
|
|
return(unknown_name);
|
|
|
|
return(smb_messages[match].name);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
do a switch on the message type, and return the response size
|
|
****************************************************************************/
|
|
static int switch_message(int type,char *inbuf,char *outbuf,int size,int bufsize)
|
|
{
|
|
static int pid= -1;
|
|
int outsize = 0;
|
|
static int num_smb_messages =
|
|
sizeof(smb_messages) / sizeof(struct smb_message_struct);
|
|
int match;
|
|
|
|
#if PROFILING
|
|
struct timeval msg_start_time;
|
|
struct timeval msg_end_time;
|
|
static unsigned long total_time = 0;
|
|
|
|
GetTimeOfDay(&msg_start_time);
|
|
#endif
|
|
|
|
if (pid == -1)
|
|
pid = getpid();
|
|
|
|
errno = 0;
|
|
last_message = type;
|
|
|
|
/* make sure this is an SMB packet */
|
|
if (strncmp(smb_base(inbuf),"\377SMB",4) != 0)
|
|
{
|
|
DEBUG(2,("Non-SMB packet of length %d\n",smb_len(inbuf)));
|
|
return(-1);
|
|
}
|
|
|
|
for (match=0;match<num_smb_messages;match++)
|
|
if (smb_messages[match].code == type)
|
|
break;
|
|
|
|
if (match == num_smb_messages)
|
|
{
|
|
DEBUG(0,("Unknown message type %d!\n",type));
|
|
outsize = reply_unknown(inbuf,outbuf);
|
|
}
|
|
else
|
|
{
|
|
DEBUG(3,("switch message %s (pid %d)\n",smb_messages[match].name,pid));
|
|
|
|
if(global_oplock_break && (smb_messages[match].flags & QUEUE_IN_OPLOCK))
|
|
{
|
|
/*
|
|
* Queue this message as we are the process of an oplock break.
|
|
*/
|
|
|
|
DEBUG( 2, ( "switch_message: queueing message due to being in " ) );
|
|
DEBUGADD( 2, ( "oplock break state.\n" ) );
|
|
|
|
push_oplock_pending_smb_message( inbuf, size );
|
|
return -1;
|
|
}
|
|
|
|
if (smb_messages[match].fn)
|
|
{
|
|
int cnum = SVAL(inbuf,smb_tid);
|
|
int flags = smb_messages[match].flags;
|
|
static uint16 last_session_tag = UID_FIELD_INVALID;
|
|
/* In share mode security we must ignore the vuid. */
|
|
uint16 session_tag = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : SVAL(inbuf,smb_uid);
|
|
/* Ensure this value is replaced in the incoming packet. */
|
|
SSVAL(inbuf,smb_uid,session_tag);
|
|
|
|
/*
|
|
* Ensure the correct username is in sesssetup_user.
|
|
* This is a really ugly bugfix for problems with
|
|
* multiple session_setup_and_X's being done and
|
|
* allowing %U and %G substitutions to work correctly.
|
|
* There is a reason this code is done here, don't
|
|
* move it unless you know what you're doing... :-).
|
|
* JRA.
|
|
*/
|
|
if(session_tag != last_session_tag ) {
|
|
user_struct *vuser = NULL;
|
|
|
|
last_session_tag = session_tag;
|
|
if(session_tag != UID_FIELD_INVALID)
|
|
vuser = get_valid_user_struct(session_tag);
|
|
if(vuser != NULL)
|
|
pstrcpy( sesssetup_user, vuser->requested_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(&Connections[cnum], cnum,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(cnum))
|
|
return(ERROR(ERRSRV,ERRaccess));
|
|
|
|
/* ipc services are limited */
|
|
if (IS_IPC(cnum) && (flags & AS_USER) && !(flags & CAN_IPC))
|
|
return(ERROR(ERRSRV,ERRaccess));
|
|
|
|
/* load service specific parameters */
|
|
if (OPEN_CNUM(cnum) && !become_service(cnum,(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(-1)))
|
|
return(ERROR(ERRSRV,ERRaccess));
|
|
|
|
last_inbuf = inbuf;
|
|
|
|
outsize = smb_messages[match].fn(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;
|
|
chain_fnum = -1;
|
|
reset_chain_pnum();
|
|
|
|
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)
|
|
{
|
|
int i;
|
|
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 <branko.cibej@hermes.si>
|
|
*/
|
|
|
|
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 (num_connections_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 */
|
|
for (i=0;i<MAX_CONNECTIONS;i++)
|
|
if (Connections[i].open)
|
|
{
|
|
/* close dirptrs on connections that are idle */
|
|
if ((t-Connections[i].lastused)>DPTR_IDLE_TIMEOUT)
|
|
dptr_idlecnum(i);
|
|
|
|
if (Connections[i].num_files_open > 0 ||
|
|
(t-Connections[i].lastused)<deadtime)
|
|
allidle = False;
|
|
}
|
|
|
|
if (allidle && num_connections_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 )
|
|
{
|
|
int i;
|
|
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 );
|
|
|
|
for (i=0;i<MAX_CONNECTIONS;i++)
|
|
{
|
|
Connections[i].open = False;
|
|
Connections[i].num_files_open=0;
|
|
Connections[i].lastused=0;
|
|
Connections[i].used=False;
|
|
string_init(&Connections[i].user,"");
|
|
string_init(&Connections[i].dirpath,"");
|
|
string_init(&Connections[i].connectpath,"");
|
|
string_init(&Connections[i].origpath,"");
|
|
}
|
|
|
|
for (i=0;i<MAX_FNUMS;i++)
|
|
{
|
|
Files[i].open = False;
|
|
string_init(&Files[i].name,"");
|
|
}
|
|
|
|
for (i=0;i<MAX_OPEN_FILES;i++)
|
|
{
|
|
file_fd_struct *fd_ptr = &FileFd[i];
|
|
fd_ptr->ref_count = 0;
|
|
fd_ptr->dev = (int32)-1;
|
|
fd_ptr->inode = (int32)-1;
|
|
fd_ptr->fd = -1;
|
|
fd_ptr->fd_readonly = -1;
|
|
fd_ptr->fd_writeonly = -1;
|
|
fd_ptr->real_open_flags = -1;
|
|
}
|
|
|
|
/* 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" ) );
|
|
|
|
#ifdef HAVE_GETRLIMIT
|
|
#ifdef RLIMIT_NOFILE
|
|
{
|
|
struct rlimit rlp;
|
|
getrlimit(RLIMIT_NOFILE, &rlp);
|
|
/*
|
|
* Set the fd limit to be MAX_OPEN_FILES + 10 to account for the
|
|
* extra fd we need to read directories, as well as the log files
|
|
* and standard handles etc.
|
|
*/
|
|
rlp.rlim_cur = (MAX_OPEN_FILES+10>rlp.rlim_max)? rlp.rlim_max:MAX_OPEN_FILES+10;
|
|
setrlimit(RLIMIT_NOFILE, &rlp);
|
|
getrlimit(RLIMIT_NOFILE, &rlp);
|
|
DEBUG(3,("Maximum number of open files per session is %d\n",rlp.rlim_cur));
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
DEBUG(2,("uid=%d gid=%d euid=%d egid=%d\n",
|
|
getuid(),getgid(),geteuid(),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);
|
|
}
|