diff options
Diffstat (limited to 'source3/smbd/server.c')
-rw-r--r-- | source3/smbd/server.c | 4300 |
1 files changed, 4300 insertions, 0 deletions
diff --git a/source3/smbd/server.c b/source3/smbd/server.c new file mode 100644 index 00000000000..5d8facef33f --- /dev/null +++ b/source3/smbd/server.c @@ -0,0 +1,4300 @@ +/* + Unix SMB/Netbios implementation. + Version 1.9. + Main SMB server routines + Copyright (C) Andrew Tridgell 1992-1995 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 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 "loadparm.h" +#include "pcap.h" +#include "trans2.h" +#include "reply.h" + +pstring servicesf = CONFIGFILE; +pstring OriginalDir ="/"; +extern pstring debugf; +extern pstring sesssetup_user; + +char *InBuffer = NULL; +char *OutBuffer = NULL; +char *last_inbuf = NULL; + +int initial_uid = 0; +int initial_gid = 0; + +BOOL share_mode_pending = False; + +/* have I done a become_user? */ +static struct { + int cnum, uid; +} last_user; + +/* 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; +extern time_t smb_last_time; + +extern pstring user_socket_options; + +connection_struct Connections[MAX_CONNECTIONS]; +files_struct Files[MAX_OPEN_FILES]; + +extern int Protocol; + +int maxxmit = BUFFER_SIZE; + +int chain_size = 0; + +/* a fnum to use when chaining */ +int chain_fnum = -1; + +/* number of open connections */ +static int num_connections_open = 0; + +extern fstring remote_machine; + + +/* these can be set by some functions to override the error codes */ +int unix_ERR_class=SUCCESS; +int unix_ERR_code=0; + + +extern int extra_time_offset; + +extern pstring myhostname; +extern struct in_addr myip; + + +static int find_free_connection(int hash); + +#ifdef SMB_PASSWD +extern void generate_next_challenge(char *challenge); +extern void set_challenge(char *challenge); +#endif + +/* 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) + + + +/**************************************************************************** + 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 + base permission for directories: + dos directory is represented in unix by unix's dir bit and the exec bit +****************************************************************************/ +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)) + result |= (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH | S_IWUSR); + + 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; + + result &= CREATE_MODE(cnum); + return(result); +} + + +/**************************************************************************** + change a unix mode to a dos mode +****************************************************************************/ +int dos_mode(int cnum,char *path,struct stat *sbuf) +{ + int result = 0; + +#if OLD_DOS_MODE + if (!CAN_WRITE(cnum) || !((sbuf->st_mode & S_IWOTH) || + Connections[cnum].admin_user || + ((sbuf->st_mode & S_IWUSR) && + Connections[cnum].uid==sbuf->st_uid) || + ((sbuf->st_mode & S_IWGRP) && + in_group(sbuf->st_gid,Connections[cnum].gid, + Connections[cnum].ngroups, + Connections[cnum].igroups)))) + result |= aRONLY; +#else + if (CAN_WRITE(cnum) && !lp_alternate_permissions(SNUM(cnum))) { + if (!((sbuf->st_mode & S_IWOTH) || + Connections[cnum].admin_user || + ((sbuf->st_mode & S_IWUSR) && Connections[cnum].uid==sbuf->st_uid) || + ((sbuf->st_mode & S_IWGRP) && + in_group(sbuf->st_gid,Connections[cnum].gid, + Connections[cnum].ngroups,Connections[cnum].igroups)))) + result |= aRONLY; + } else { + if ((sbuf->st_mode & S_IWUSR) == 0) + result |= aRONLY; + } +#endif + + if ((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); + +#if LINKS_READ_ONLY + if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode)) + result |= aRONLY; +#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; + } + + 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(chmod(fname,unixmode)); +} + + +/**************************************************************************** +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)) + return(False); + + strcpy(tmpname,name2); + mangle_name_83(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 snum,BOOL docache) +{ + void *cur_dir; + char *dname; + BOOL mangled; + fstring name2; + + mangled = is_mangled(name); + + /* handle null paths */ + if (*path == 0) + path = "."; + + if (docache && (dname = DirCacheCheck(path,name,snum))) { + strcpy(name, dname); + return(True); + } + + if (mangled) + check_mangled_stack(name); + + /* open the directory */ + if (!(cur_dir = OpenDir(path))) + { + 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; + + strcpy(name2,dname); + if (!name_map_mangle(name2,False,snum)) 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); + strcpy(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 +****************************************************************************/ +BOOL unix_convert(char *name,int cnum) +{ + struct stat st; + char *start, *end; + pstring dirpath; + + *dirpath = 0; + + /* convert to basic unix format - removing \ chars and cleaning it up */ + unix_format(name); + unix_clean_name(name); + + if (!case_sensitive && + (!case_preserve || (is_8_3(name) && !short_case_preserve))) + strnorm(name); + + /* names must be relative to the root of the service - trim any leading /. + also trim trailing /'s */ + trim_string(name,"/","/"); + + /* check if it's a printer file */ + if (Connections[cnum].printer) + { + if ((! *name) || strchr(name,'/') || !is_8_3(name)) + { + fstring name2; + sprintf(name2,"%.6s.XXXXXX",remote_machine); + strcpy(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); + + 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) + 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; + + /* 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) strcpy(rest,end+1); + + + /* try to find this part of the path in the directory */ + if (strchr(start,'?') || strchr(start,'*') || + !scan_directory(dirpath, start, SNUM(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 = '/'; + 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_stack(start); + + DEBUG(5,("New file %s\n",start)); + return(True); + } + + /* restore the rest of the string */ + if (end) + { + strcpy(start+strlen(start)+1,rest); + end = start + strlen(start); + } + } + + /* add to the dirpath that we have resolved so far */ + if (*dirpath) strcat(dirpath,"/"); + strcat(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); +} + + + + +#ifdef QUOTAS +#ifdef LINUX +/**************************************************************************** +try to get the disk space from disk quotas (LINUX version) +****************************************************************************/ +/* +If you didn't make the symlink to the quota package, too bad :( +*/ +#include "quota/quotactl.c" +#include "quota/hasquota.c" +static BOOL disk_quotas(char *path, int *bsize, int *dfree, int *dsize) +{ + uid_t euser_id; + struct dqblk D; + struct stat S; + dev_t devno ; + struct mntent *mnt; + FILE *fp; + int found ; + int qcmd, fd ; + char *qfpathname; + + /* find the block device file */ + + if ( stat(path, &S) == -1 ) + return(False) ; + + devno = S.st_dev ; + + fp = setmntent(MOUNTED,"r"); + found = False ; + + while ((mnt = getmntent(fp)) != (struct mntent *) 0) { + if ( stat(mnt->mnt_dir,&S) == -1 ) + continue ; + if (S.st_dev == devno) { + found = True ; + break ; + } + } + endmntent(fp) ; + + if ( ! found ) + return(False) ; + + qcmd = QCMD(Q_GETQUOTA, USRQUOTA); + + if (hasmntopt(mnt, MNTOPT_NOAUTO) || hasmntopt(mnt, MNTOPT_NOQUOTA)) + return(False) ; + + if (!hasquota(mnt, USRQUOTA, &qfpathname)) + return(False) ; + + euser_id = geteuid(); + seteuid(0); + + if (quotactl(qcmd, mnt->mnt_fsname, euser_id, (caddr_t)&D) != 0) { + if ((fd = open(qfpathname, O_RDONLY)) < 0) { + seteuid(euser_id); + return(False); + } + lseek(fd, (long) dqoff(euser_id), L_SET); + switch (read(fd, &D, sizeof(struct dqblk))) { + case 0:/* EOF */ + memset((caddr_t)&D, 0, sizeof(struct dqblk)); + break; + case sizeof(struct dqblk): /* OK */ + break; + default: /* ERROR */ + close(fd); + seteuid(euser_id); + return(False); + } + } + seteuid(euser_id); + *bsize=1024; + + if (D.dqb_bsoftlimit==0) + return(False); + if ((D.dqb_curblocks>D.dqb_bsoftlimit)||(D.dqb_curinodes>D.dqb_isoftlimit)) + { + *dfree = 0; + *dsize = D.dqb_curblocks; + } + else { + *dfree = D.dqb_bsoftlimit - D.dqb_curblocks; + *dsize = D.dqb_bsoftlimit; + } + return (True); +} +#else +#ifndef CRAY +/**************************************************************************** +try to get the disk space from disk quotas +****************************************************************************/ +static BOOL disk_quotas(char *path, int *bsize, int *dfree, int *dsize) +{ + uid_t user_id, euser_id; + int r; + char dev_disk[256]; + struct dqblk D; + struct stat S; + /* find the block device file */ + if ((stat(path, &S)<0) || + (devnm(S_IFBLK, S.st_dev, dev_disk, 256, 0)<0)) return (False); + + euser_id = geteuid(); + +#ifdef USE_SETRES + /* for HPUX, real uid must be same as euid to execute quotactl for euid */ + user_id = getuid(); + setresuid(euser_id,-1,-1); +#endif + r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D); + #ifdef USE_SETRES + if (setresuid(user_id,-1,-1)) + DEBUG(5,("Unable to reset uid to %d\n", user_id)); + #endif + /* Use softlimit to determine disk space, except when it has been exceeded */ + *bsize = 1024; + if (r) + { + if (errno == EDQUOT) + { + *dfree =0; + *dsize =D.dqb_curblocks; + return (True); + } + else return(False); + } + /* Use softlimit to determine disk space, except when it has been exceeded */ + if ((D.dqb_curblocks>D.dqb_bsoftlimit)||(D.dqb_curfiles>D.dqb_fsoftlimit)) + { + *dfree = 0; + *dsize = D.dqb_curblocks; + } + else { + *dfree = D.dqb_bsoftlimit - D.dqb_curblocks; + *dsize = D.dqb_bsoftlimit; + } + return (True); +} +#else +/**************************************************************************** +try to get the disk space from disk quotas (CRAY VERSION) +****************************************************************************/ +static BOOL disk_quotas(char *path, int *bsize, int *dfree, int *dsize) +{ + struct mntent *mnt; + FILE *fd; + struct stat sbuf; + dev_t devno ; + static dev_t devno_cached = 0 ; + static char name[MNTMAXSTR] ; + struct q_request request ; + struct qf_header header ; + static int quota_default = 0 ; + int found ; + + if ( stat(path,&sbuf) == -1 ) + return(False) ; + + devno = sbuf.st_dev ; + + if ( devno != devno_cached ) { + + devno_cached = devno ; + + if ((fd = setmntent(KMTAB)) == NULL) + return(False) ; + + found = False ; + + while ((mnt = getmntent(fd)) != NULL) { + + if ( stat(mnt->mnt_dir,&sbuf) == -1 ) + continue ; + + if (sbuf.st_dev == devno) { + + found = True ; + break ; + + } + + } + + strcpy(name,mnt->mnt_dir) ; + endmntent(fd) ; + + if ( ! found ) + return(False) ; + } + + request.qf_magic = QF_MAGIC ; + request.qf_entry.id = geteuid() ; + + if (quotactl(name, Q_GETQUOTA, &request) == -1) + return(False) ; + + if ( ! request.user ) + return(False) ; + + if ( request.qf_entry.user_q.f_quota == QFV_DEFAULT ) { + + if ( ! quota_default ) { + + if ( quotactl(name, Q_GETHEADER, &header) == -1 ) + return(False) ; + else + quota_default = header.user_h.def_fq ; + } + + *dfree = quota_default ; + + }else if ( request.qf_entry.user_q.f_quota == QFV_PREVENT ) { + + *dfree = 0 ; + + }else{ + + *dfree = request.qf_entry.user_q.f_quota ; + + } + + *dsize = request.qf_entry.user_q.f_use ; + + if ( *dfree ) + *dfree -= *dsize ; + + if ( *dfree < 0 ) + *dfree = 0 ; + + *bsize = 4096 ; /* Cray blocksize */ + + return(True) ; + +} +#endif /* CRAY */ +#endif /* LINUX */ +#endif /* QUOTAS */ + + +/**************************************************************************** +normalise for DOS usage +****************************************************************************/ +static void disk_norm(int *bsize,int *dfree,int *dsize) +{ + /* check if the disk is beyond the max disk size */ + int maxdisksize = lp_maxdisksize(); + if (maxdisksize) { + /* convert to blocks - and don't overflow */ + maxdisksize = ((maxdisksize*1024)/(*bsize))*1024; + if (*dsize > maxdisksize) *dsize = maxdisksize; + if (*dfree > maxdisksize) *dfree = maxdisksize-1; /* the -1 should stop + applications getting + div by 0 errors */ + } + + while (*dfree > WORDMAX || *dsize > WORDMAX || *bsize < 512) + { + *dfree /= 2; + *dsize /= 2; + *bsize *= 2; + if (*bsize > WORDMAX ) + { + *bsize = WORDMAX; + if (*dsize > WORDMAX) + *dsize = WORDMAX; + if (*dfree > WORDMAX) + *dfree = WORDMAX; + break; + } + } +} + +/**************************************************************************** + return number of 1K blocks available on a path and total number +****************************************************************************/ +int disk_free(char *path,int *bsize,int *dfree,int *dsize) +{ + char *df_command = lp_dfree_command(); +#ifndef NO_STATFS +#ifdef USE_STATVFS + struct statvfs fs; +#else +#ifdef ULTRIX + struct fs_data fs; +#else + struct statfs fs; +#endif +#endif +#endif + +#ifdef QUOTAS + if (disk_quotas(path, bsize, dfree, dsize)) + { + disk_norm(bsize,dfree,dsize); + return(((*bsize)/1024)*(*dfree)); + } +#endif + + + /* possibly use system() to get the result */ + if (df_command && *df_command) + { + int ret; + pstring syscmd; + pstring outfile; + + sprintf(outfile,"/tmp/dfree.smb.%d",(int)getpid()); + sprintf(syscmd,"%s %s",df_command,path); + standard_sub_basic(syscmd); + + ret = smbrun(syscmd,outfile); + DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret)); + + { + FILE *f = fopen(outfile,"r"); + *dsize = 0; + *dfree = 0; + *bsize = 1024; + if (f) + { + fscanf(f,"%d %d %d",dsize,dfree,bsize); + fclose(f); + } + else + DEBUG(0,("Can't open %s\n",outfile)); + } + + unlink(outfile); + disk_norm(bsize,dfree,dsize); + return(((*bsize)/1024)*(*dfree)); + } + +#ifdef NO_STATFS + DEBUG(1,("Warning - no statfs function\n")); + return(1); +#else +#ifdef STATFS4 + if (statfs(path,&fs,sizeof(fs),0) != 0) +#else +#ifdef USE_STATVFS + if (statvfs(path, &fs)) +#else +#ifdef STATFS3 + if (statfs(path,&fs,sizeof(fs)) == -1) +#else + if (statfs(path,&fs) == -1) +#endif /* STATFS3 */ +#endif /* USE_STATVFS */ +#endif /* STATFS4 */ + { + DEBUG(3,("dfree call failed code errno=%d\n",errno)); + *bsize = 1024; + *dfree = 1; + *dsize = 1; + return(((*bsize)/1024)*(*dfree)); + } + +#ifdef ULTRIX + *bsize = 1024; + *dfree = fs.fd_req.bfree; + *dsize = fs.fd_req.btot; +#else +#ifdef USE_STATVFS + *bsize = fs.f_frsize; +#else +#ifdef USE_F_FSIZE + /* eg: osf1 has f_fsize = fundamental filesystem block size, + f_bsize = optimal transfer block size (MX: 94-04-19) */ + *bsize = fs.f_fsize; +#else + *bsize = fs.f_bsize; +#endif /* STATFS3 */ +#endif /* USE_STATVFS */ + +#ifdef STATFS4 + *dfree = fs.f_bfree; +#else + *dfree = fs.f_bavail; +#endif /* STATFS4 */ + *dsize = fs.f_blocks; +#endif /* ULTRIX */ + +#if defined(SCO) || defined(ISC) || defined(MIPS) + *bsize = 512; +#endif + +/* handle rediculous bsize values - some OSes are broken */ +if ((*bsize) < 512 || (*bsize)>0xFFFF) *bsize = 1024; + + disk_norm(bsize,dfree,dsize); + + if (*bsize < 256) + *bsize = 512; + if ((*dsize)<1) + { + DEBUG(0,("dfree seems to be broken on your system\n")); + *dsize = 20*1024*1024/(*bsize); + *dfree = MAX(1,*dfree); + } + return(((*bsize)/1024)*(*dfree)); +#endif +} + + +/**************************************************************************** +wrap it to get filenames right +****************************************************************************/ +int sys_disk_free(char *path,int *bsize,int *dfree,int *dsize) +{ + return(disk_free(dos_to_unix(path,False),bsize,dfree,dsize)); +} + + + +/**************************************************************************** +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; + + ret = reduce_name(name,Connections[cnum].connectpath,lp_widelinks(SNUM(cnum))); + 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; + } +} + + +/**************************************************************************** +open a file +****************************************************************************/ +void open_file(int fnum,int cnum,char *fname1,int flags,int mode) +{ + pstring fname; + + Files[fnum].open = False; + Files[fnum].fd = -1; + errno = EPERM; + + strcpy(fname,fname1); + + /* check permissions */ + if ((flags != O_RDONLY) && !CAN_WRITE(cnum) && !Connections[cnum].printer) + { + DEBUG(3,("Permission denied opening %s\n",fname)); + check_for_pipe(fname); + return; + } + + /* 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")); +*/ + +#if UTIME_WORKAROUND + /* XXXX - is this OK?? */ + /* this works around a utime bug but can cause other problems */ + if ((flags & (O_WRONLY|O_RDWR)) && (flags & O_CREAT) && !(flags & O_APPEND)) + sys_unlink(fname); +#endif + + + Files[fnum].fd = sys_open(fname,flags,mode); + + if ((Files[fnum].fd>=0) && + Connections[cnum].printer && lp_minprintspace(SNUM(cnum))) { + pstring dname; + int dum1,dum2,dum3; + char *p; + strcpy(dname,fname); + p = strrchr(dname,'/'); + if (p) *p = 0; + if (sys_disk_free(dname,&dum1,&dum2,&dum3) < + lp_minprintspace(SNUM(cnum))) { + close(Files[fnum].fd); + Files[fnum].fd = -1; + sys_unlink(fname); + errno = ENOSPC; + return; + } + } + + + /* Fix for files ending in '.' */ + if((Files[fnum].fd == -1) && (errno == ENOENT) && + (strchr(fname,'.')==NULL)) + { + strcat(fname,"."); + Files[fnum].fd = sys_open(fname,flags,mode); + } + +#if (defined(ENAMETOOLONG) && defined(HAVE_PATHCONF)) + if ((Files[fnum].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 ((Files[fnum].fd = sys_open(fname,flags,mode)) == -1) + p[max_len] = tmp; + } + } +#endif + + if (Files[fnum].fd < 0) + { + DEBUG(3,("Error opening file %s (%s) (flags=%d)\n", + fname,strerror(errno),flags)); + check_for_pipe(fname); + return; + } + + if (Files[fnum].fd >= 0) + { + struct stat st; + Connections[cnum].num_files_open++; + fstat(Files[fnum].fd,&st); + Files[fnum].mode = st.st_mode; + Files[fnum].open_time = time(NULL); + Files[fnum].size = 0; + Files[fnum].pos = -1; + Files[fnum].open = True; + Files[fnum].mmap_ptr = NULL; + Files[fnum].mmap_size = 0; + Files[fnum].can_lock = True; + Files[fnum].can_read = ((flags & O_WRONLY)==0); + Files[fnum].can_write = ((flags & (O_WRONLY|O_RDWR))!=0); + Files[fnum].share_mode = 0; + Files[fnum].share_pending = False; + Files[fnum].print_file = Connections[cnum].printer; + Files[fnum].modified = False; + Files[fnum].cnum = cnum; + string_set(&Files[fnum].name,fname); + Files[fnum].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 (Files[fnum].print_file && POSTSCRIPT(cnum) && + Files[fnum].can_write) + { + DEBUG(3,("Writing postscript line\n")); + write_file(fnum,"%!\n",3); + } + + DEBUG(2,("%s %s opened file %s read=%s write=%s (numopen=%d fnum=%d)\n", + timestring(),Connections[cnum].user,fname, + BOOLSTR(Files[fnum].can_read),BOOLSTR(Files[fnum].can_write), + Connections[cnum].num_files_open,fnum)); + + } + +#if USE_MMAP + /* mmap it if read-only */ + if (!Files[fnum].can_write) + { + Files[fnum].mmap_size = file_size(fname); + Files[fnum].mmap_ptr = (char *)mmap(NULL,Files[fnum].mmap_size, + PROT_READ,MAP_SHARED,Files[fnum].fd,0); + + if (Files[fnum].mmap_ptr == (char *)-1 || !Files[fnum].mmap_ptr) + { + DEBUG(3,("Failed to mmap() %s - %s\n",fname,strerror(errno))); + Files[fnum].mmap_ptr = NULL; + } + } +#endif +} + +/******************************************************************* +sync a file +********************************************************************/ +void sync_file(int fnum) +{ +#ifndef NO_FSYNC + fsync(Files[fnum].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; + strcpy(fname,Files[fnum].name); + + if (*lp_magicoutput(SNUM(cnum))) + strcpy(magic_output,lp_magicoutput(SNUM(cnum))); + else + sprintf(magic_output,"%s.out",fname); + + chmod(fname,0755); + ret = smbrun(fname,magic_output); + DEBUG(3,("Invoking magic command %s gave %d\n",fname,ret)); + unlink(fname); + } +} + + +/**************************************************************************** +close a file - possibly invalidating the read prediction +****************************************************************************/ +void close_file(int fnum) +{ + int cnum = Files[fnum].cnum; + invalidate_read_prediction(Files[fnum].fd); + Files[fnum].open = False; + Connections[cnum].num_files_open--; + if(Files[fnum].wbmpx_ptr) + { + free((char *)Files[fnum].wbmpx_ptr); + Files[fnum].wbmpx_ptr = NULL; + } + +#if USE_MMAP + if(Files[fnum].mmap_ptr) + { + munmap(Files[fnum].mmap_ptr,Files[fnum].mmap_size); + Files[fnum].mmap_ptr = NULL; + } +#endif + + if (lp_share_modes(SNUM(cnum))) + del_share_mode(fnum); + + if (Files[fnum].modified) { + struct stat st; + if (fstat(Files[fnum].fd,&st) == 0) { + int dosmode = dos_mode(cnum,Files[fnum].name,&st); + if (!IS_DOS_ARCHIVE(dosmode)) { + dos_chmod(cnum,Files[fnum].name,dosmode | aARCH,&st); + } + } + } + + close(Files[fnum].fd); + + /* NT uses smbclose to start a print - weird */ + if (Files[fnum].print_file) + print_file(fnum); + + /* check for magic scripts */ + check_magic(fnum,cnum); + + DEBUG(2,("%s %s closed file %s (numopen=%d)\n", + timestring(),Connections[cnum].user,Files[fnum].name, + Connections[cnum].num_files_open)); +} + +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) { + if (old_deny == new_deny && share_pid == getpid()) + 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) +{ + int pid=0; + int share_mode = get_share_mode_byname(cnum,fname,&pid); + + if (!pid || !share_mode) return(True); + + if (share_mode == DENY_DOS) + return(pid == getpid()); + + /* XXXX exactly what share mode combinations should be allowed for + deleting/renaming? */ + return(False); +} + +/**************************************************************************** + 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) +{ + if (Files[fnum].can_write){ + if (is_locked(fnum,cnum,0x3FFFFFFF,0)){ + close_file(fnum); + errno = EACCES; + unix_ERR_class = ERRDOS; + unix_ERR_code = ERRlock; + } + else + ftruncate(Files[fnum].fd,0); + } +} + + +/**************************************************************************** +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 *Access,int *action) +{ + int flags=0; + int flags2=0; + int deny_mode = (share_mode>>4)&7; + struct stat sbuf; + BOOL file_existed = file_exist(fname,&sbuf); + BOOL fcbopen = False; + int share_pid=0; + + Files[fnum].open = False; + Files[fnum].fd = -1; + + /* this is for OS/2 EAs - try and say we don't support them */ + if (strstr(fname,".+,;=[].")) { + unix_ERR_class = ERRDOS; + unix_ERR_code = ERROR_EAS_NOT_SUPPORTED; + 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 (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 old_share=0; + + if (file_existed) + old_share = get_share_mode(cnum,&sbuf,&share_pid); + + if (share_pid) { + /* someone else has a share lock on it, check to see + if we can too */ + int old_open_mode = old_share&0xF; + int old_deny_mode = (old_share>>4)&7; + + if (deny_mode > 4 || old_deny_mode > 4 || old_open_mode > 2) { + DEBUG(2,("Invalid share mode (%d,%d,%d) on file %s\n", + deny_mode,old_deny_mode,old_open_mode,fname)); + errno = EACCES; + unix_ERR_class = ERRDOS; + unix_ERR_code = ERRbadshare; + return; + } + + { + int access_allowed = access_table(deny_mode,old_deny_mode,old_open_mode, + share_pid,fname); + + if ((access_allowed == AFAIL) || + (access_allowed == AREAD && flags == O_WRONLY) || + (access_allowed == AWRITE && flags == O_RDONLY)) { + DEBUG(2,("Share violation on file (%d,%d,%d,%d,%s) = %d\n", + deny_mode,old_deny_mode,old_open_mode, + share_pid,fname, + access_allowed)); + errno = EACCES; + unix_ERR_class = ERRDOS; + unix_ERR_code = ERRbadshare; + return; + } + + if (access_allowed == AREAD) + flags = O_RDONLY; + + if (access_allowed == AWRITE) + flags = O_WRONLY; + } + } + } + + 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); + if (!Files[fnum].open && flags==O_RDWR && errno!=ENOENT && fcbopen) { + flags = O_RDONLY; + open_file(fnum,cnum,fname,flags,mode); + } + + if (Files[fnum].open) { + int open_mode=0; + switch (flags) { + case O_RDONLY: + open_mode = 0; + break; + case O_RDWR: + open_mode = 2; + break; + case O_WRONLY: + open_mode = 1; + break; + } + + Files[fnum].share_mode = (deny_mode<<4) | open_mode; + Files[fnum].share_pending = True; + + if (Access) { + (*Access) = open_mode; + } + + if (action) { + if (file_existed && !(flags2 & O_TRUNC)) *action = 1; + if (!file_existed) *action = 2; + if (file_existed && (flags2 & O_TRUNC)) *action = 3; + } + + if (!share_pid) + share_mode_pending = True; + + if ((flags2&O_TRUNC) && file_existed) + truncate_unless_locked(fnum,cnum); + } +} + + + +/******************************************************************* +check for files that we should now set our share modes on +********************************************************************/ +static void check_share_modes(void) +{ + int i; + for (i=0;i<MAX_OPEN_FILES;i++) + if(Files[i].open && Files[i].share_pending) { + if (lp_share_modes(SNUM(Files[i].cnum))) { + int pid=0; + get_share_mode_by_fnum(Files[i].cnum,i,&pid); + if (!pid) { + set_share_mode(i,Files[i].share_mode); + Files[i].share_pending = False; + } + } else { + Files[i].share_pending = False; + } + } +} + + +/**************************************************************************** +seek a file. Try to avoid the seek if possible +****************************************************************************/ +int seek_file(int fnum,int pos) +{ + int offset = 0; + if (Files[fnum].print_file && POSTSCRIPT(Files[fnum].cnum)) + offset = 3; + + Files[fnum].pos = lseek(Files[fnum].fd,pos+offset,SEEK_SET) - offset; + return(Files[fnum].pos); +} + +/**************************************************************************** +read from a file +****************************************************************************/ +int read_file(int fnum,char *data,int pos,int mincnt,int maxcnt,int timeout,BOOL exact) +{ + int ret=0; + + if (!Files[fnum].can_write) + { + ret = read_predict(Files[fnum].fd, + pos, + data, + NULL, + maxcnt); + + data += ret; + maxcnt -= ret; + mincnt = MAX(mincnt-ret,0); + pos += ret; + } + +#if USE_MMAP + if (Files[fnum].mmap_ptr) + { + int num = MIN(maxcnt,Files[fnum].mmap_size-pos); + if (num > 0) + { + memcpy(data,Files[fnum].mmap_ptr+pos,num); + data += num; + pos += num; + maxcnt -= num; + mincnt = MAX(mincnt-num,0); + ret += num; + } + } +#endif + + if (maxcnt <= 0) + return(ret); + + if (seek_file(fnum,pos) != pos) + { + DEBUG(3,("Failed to seek to %d\n",pos)); + return(ret); + } + + if (maxcnt > 0) + ret += read_with_timeout(Files[fnum].fd, + data, + mincnt, + maxcnt, + timeout, + exact); + + return(ret); +} + + +/**************************************************************************** +write to a file +****************************************************************************/ +int write_file(int fnum,char *data,int n) +{ + if (!Files[fnum].can_write) { + errno = EPERM; + return(0); + } + + Files[fnum].modified = True; + + return(write_data(Files[fnum].fd,data,n)); +} + + +static int old_umask = 022; + +/**************************************************************************** +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,("%s chdir (%s) failed cnum=%d\n",timestring(), + 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); +} + + +/**************************************************************************** + become the specified uid +****************************************************************************/ +static BOOL become_uid(int uid) +{ + if (initial_uid != 0) + return(True); + +#ifdef AIX + { + /* AIX 3 stuff - inspired by a code fragment in wu-ftpd */ + priv_t priv; + + priv.pv_priv[0] = 0; + priv.pv_priv[1] = 0; + if (setpriv(PRIV_SET|PRIV_INHERITED|PRIV_EFFECTIVE|PRIV_BEQUEATH, + &priv, sizeof(priv_t)) < 0 || + setuidx(ID_REAL|ID_EFFECTIVE, (uid_t)uid) < 0 || + seteuid((uid_t)uid) < 0) + DEBUG(1,("Can't set uid (AIX3)")); + } +#endif + +#ifdef USE_SETRES + if (setresuid(-1,uid,-1) != 0) +#else + if ((seteuid(uid) != 0) && + (setuid(uid) != 0)) +#endif + { + DEBUG(0,("Couldn't set uid %d currently set to (%d,%d)\n", + uid,getuid(), geteuid())); + if (uid > 32000) + DEBUG(0,("Looks like your OS doesn't like high uid values - try using a different account\n")); + return(False); + } + + if (((uid == -1) || (uid == 65535)) && geteuid() != uid) + { + DEBUG(0,("Invalid uid -1. perhaps you have a account with uid 65535?\n")); + return(False); + } + + return(True); +} + + +/**************************************************************************** + become the specified gid +****************************************************************************/ +static BOOL become_gid(int gid) +{ + if (initial_uid != 0) + return(True); + +#ifdef USE_SETRES + if (setresgid(-1,gid,-1) != 0) +#else + if (setgid(gid) != 0) +#endif + { + DEBUG(0,("Couldn't set gid %d currently set to (%d,%d)\n", + gid,getgid(),getegid())); + if (gid > 32000) + DEBUG(0,("Looks like your OS doesn't like high gid values - try using a different account\n")); + return(False); + } + + return(True); +} + + +/**************************************************************************** + become the specified uid and gid +****************************************************************************/ +static BOOL become_id(int uid,int gid) +{ + return(become_gid(gid) && become_uid(uid)); +} + +/**************************************************************************** +become the guest user +****************************************************************************/ +static BOOL become_guest(void) +{ + BOOL ret; + static struct passwd *pass=NULL; + + if (initial_uid != 0) + return(True); + + if (!pass) + pass = Get_Pwnam(lp_guestaccount(-1),True); + if (!pass) return(False); + + ret = become_id(pass->pw_uid,pass->pw_gid); + + if (!ret) + DEBUG(1,("Failed to become guest. Invalid guest account?\n")); + + last_user.cnum = -2; + + return(ret); +} + +/******************************************************************* +check if a username is OK +********************************************************************/ +static BOOL check_user_ok(int cnum,user_struct *vuser,int snum) +{ + int i; + for (i=0;i<Connections[cnum].uid_cache.entries;i++) + if (Connections[cnum].uid_cache.list[i] == vuser->uid) return(True); + + if (!user_ok(vuser->name,snum)) return(False); + + i = Connections[cnum].uid_cache.entries % UID_CACHE_SIZE; + Connections[cnum].uid_cache.list[i] = vuser->uid; + + if (Connections[cnum].uid_cache.entries < UID_CACHE_SIZE) + Connections[cnum].uid_cache.entries++; + + return(True); +} + + +/**************************************************************************** + become the user of a connection number +****************************************************************************/ +BOOL become_user(int cnum, int uid) +{ + int new_umask; + user_struct *vuser; + int snum,gid; + int ngroups; + gid_t *groups; + + if (last_user.cnum == cnum && last_user.uid == uid) { + DEBUG(4,("Skipping become_user - already user\n")); + return(True); + } + + unbecome_user(); + + if (!OPEN_CNUM(cnum)) { + DEBUG(2,("Connection %d not open\n",cnum)); + return(False); + } + + snum = Connections[cnum].service; + + if (Connections[cnum].force_user || + lp_security() == SEC_SHARE || + !(vuser = get_valid_user_struct(uid)) || + !check_user_ok(cnum,vuser,snum)) { + uid = Connections[cnum].uid; + gid = Connections[cnum].gid; + groups = Connections[cnum].groups; + ngroups = Connections[cnum].ngroups; + } else { + if (!vuser) { + DEBUG(2,("Invalid vuid used %d\n",uid)); + return(False); + } + uid = vuser->uid; + if(!*lp_force_group(snum)) + gid = vuser->gid; + else + gid = Connections[cnum].gid; + groups = vuser->user_groups; + ngroups = vuser->user_ngroups; + } + + if (initial_uid == 0) + { + if (!become_gid(gid)) return(False); + +#ifndef NO_SETGROUPS + if (!IS_IPC(cnum)) { + /* groups stuff added by ih/wreu */ + if (ngroups > 0) + if (setgroups(ngroups,groups)<0) + DEBUG(0,("setgroups call failed!\n")); + } +#endif + + if (!Connections[cnum].admin_user && !become_uid(uid)) + return(False); + } + + new_umask = 0777 & ~CREATE_MODE(cnum); + old_umask = umask(new_umask); + + last_user.cnum = cnum; + last_user.uid = uid; + + DEBUG(5,("become_user uid=(%d,%d) gid=(%d,%d) new_umask=0%o\n", + getuid(),geteuid(),getgid(),getegid(),new_umask)); + + return(True); +} + +/**************************************************************************** + unbecome the user of a connection number +****************************************************************************/ +BOOL unbecome_user(void ) +{ + if (last_user.cnum == -1) + return(False); + + ChDir(OriginalDir); + + umask(old_umask); + + if (initial_uid == 0) + { +#ifdef USE_SETRES + setresuid(-1,getuid(),-1); + setresgid(-1,getgid(),-1); +#else + if (seteuid(initial_uid) != 0) + setuid(initial_uid); + setgid(initial_gid); +#endif + } +#ifdef NO_EID + if (initial_uid == 0) + DEBUG(2,("Running with no EID\n")); + initial_uid = getuid(); + initial_gid = getgid(); +#else + if (geteuid() != initial_uid) + { + DEBUG(0,("Warning: You appear to have a trapdoor uid system\n")); + initial_uid = geteuid(); + } + if (getegid() != initial_gid) + { + DEBUG(0,("Warning: You appear to have a trapdoor gid system\n")); + initial_gid = getegid(); + } +#endif + + if (ChDir(OriginalDir) != 0) + DEBUG(0,("%s chdir(%s) failed in unbecome_user\n", + timestring(),OriginalDir)); + + DEBUG(5,("unbecome_user now uid=(%d,%d) gid=(%d,%d)\n", + getuid(),geteuid(),getgid(),getegid())); + + last_user.cnum = -1; + + 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); + 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 *defservice = lp_defaultservice(); + if (defservice && *defservice && !strequal(defservice,service)) { + 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}, + {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 != SUCCESS) + { + eclass = unix_ERR_class; + ecode = unix_ERR_code; + unix_ERR_class = 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; + cmd = CVAL(inbuf,smb_com); + + CVAL(outbuf,smb_rcls) = error_class; + SSVAL(outbuf,smb_err,error_code); + + DEBUG(3,("%s error packet at line %d cmd=%d (%s) eclass=%d ecode=%d\n", + timestring(), + 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); +} + + +#ifndef SIGCLD_IGNORE +/**************************************************************************** +this prevents zombie child processes +****************************************************************************/ +static int sig_cld() +{ + static int depth = 0; + if (depth != 0) + { + DEBUG(0,("ERROR: Recursion in sig_cld? Perhaps you need `#define USE_WAITPID'?\n")); + depth=0; + return(0); + } + depth++; + + BlockSignals(True); + DEBUG(5,("got SIGCLD\n")); + +#ifdef USE_WAITPID + while (waitpid((pid_t)-1,(int *)NULL, WNOHANG) > 0); +#endif + + /* Stop zombies */ + /* Stevens, Adv. Unix Prog. says that on system V you must call + wait before reinstalling the signal handler, because the kernel + calls the handler from within the signal-call when there is a + child that has exited. This would lead to an infinite recursion + if done vice versa. */ + +#ifndef DONT_REINSTALL_SIG +#ifdef SIGCLD_IGNORE + signal(SIGCLD, SIG_IGN); +#else + signal(SIGCLD, SIGNAL_CAST sig_cld); +#endif +#endif + +#ifndef USE_WAITPID + while (wait3(WAIT3_CAST1 NULL, WNOHANG, WAIT3_CAST2 NULL) > 0); +#endif + depth--; + BlockSignals(False); + return 0; +} +#endif + +/**************************************************************************** + this is called when the client exits abruptly + **************************************************************************/ +static int sig_pipe() +{ + 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 s; + struct sockaddr addr; + int in_addrlen = sizeof(addr); + + /* Stop zombies */ +#ifdef SIGCLD_IGNORE + signal(SIGCLD, SIG_IGN); +#else + signal(SIGCLD, SIGNAL_CAST sig_cld); +#endif + + /* open an incoming socket */ + s = open_socket_in(SOCK_STREAM, port, 0); + if (s == -1) + return(False); + + /* ready to listen */ + if (listen(s, 5) == -1) + { + DEBUG(0,("listen: %s",strerror(errno))); + close(s); + return False; + } + + /* now accept incoming connections - forking a new process + for each incoming connection */ + DEBUG(2,("waiting for a connection\n")); + while (1) + { + Client = accept(s,&addr,&in_addrlen); + + if (Client == -1 && errno == EINTR) + continue; + + if (Client == -1) + { + DEBUG(0,("accept: %s",strerror(errno))); + return False; + } + +#ifdef NO_FORK_DEBUG +#ifndef NO_SIGNAL_TEST + signal(SIGPIPE, SIGNAL_CAST sig_pipe); + signal(SIGCLD, SIGNAL_CAST SIG_DFL); +#endif + return True; +#else + if (Client != -1 && fork()==0) + { +#ifndef NO_SIGNAL_TEST + signal(SIGPIPE, SIGNAL_CAST sig_pipe); + signal(SIGCLD, SIGNAL_CAST SIG_DFL); +#endif + /* close our standard file descriptors */ + close_low_fds(); + + set_socket_options(Client,"SO_KEEPALIVE"); + set_socket_options(Client,user_socket_options); + + return True; + } + close(Client); /* The parent doesn't need this socket */ +#endif + } + } + else + { + /* We will abort gracefully when the client or remote system + goes away */ +#ifndef NO_SIGNAL_TEST + signal(SIGPIPE, SIGNAL_CAST sig_pipe); +#endif + 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; +} + + +/**************************************************************************** +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; + strcpy(fname,lp_configfile()); + if (file_exist(fname,NULL) && !strcsequal(fname,servicesf)) + { + strcpy(servicesf,fname); + test = False; + } + } + + reopen_logs(); + + if (test && !lp_file_list_changed()) + return(True); + + lp_killunused(snum_used); + + ret = lp_load(servicesf,False); + + /* perhaps the config filename is now set */ + if (!test) + reload_services(True); + + reopen_logs(); + + { + extern int Client; + if (Client != -1) { + set_socket_options(Client,"SO_KEEPALIVE"); + set_socket_options(Client,user_socket_options); + } + } + + create_mangled_stack(lp_mangledstack()); + + /* this forces service parameters to be flushed */ + become_service(-1,True); + + return(ret); +} + + + +/**************************************************************************** +this prevents zombie child processes +****************************************************************************/ +static int sig_hup() +{ + BlockSignals(True); + DEBUG(0,("Got SIGHUP\n")); + reload_services(False); +#ifndef DONT_REINSTALL_SIG + signal(SIGHUP,SIGNAL_CAST sig_hup); +#endif + BlockSignals(False); + return(0); +} + +/**************************************************************************** +Setup the groups a user belongs to. +****************************************************************************/ +int setup_groups(char *user, int uid, int gid, int *p_ngroups, + int **p_igroups, gid_t **p_groups) +{ + if (-1 == initgroups(user,gid)) + { + if (getuid() == 0) + { + DEBUG(0,("Unable to initgroups!\n")); + if (gid < 0 || gid > 16000 || uid < 0 || uid > 16000) + DEBUG(0,("This is probably a problem with the account %s\n",user)); + } + } + else + { + int i,ngroups; + int *igroups; + gid_t grp = 0; + ngroups = getgroups(0,&grp); + if (ngroups <= 0) + ngroups = 32; + igroups = (int *)malloc(sizeof(int)*ngroups); + for (i=0;i<ngroups;i++) + igroups[i] = 0x42424242; + ngroups = getgroups(ngroups,(gid_t *)igroups); + + if (igroups[0] == 0x42424242) + ngroups = 0; + + *p_ngroups = ngroups; + + /* The following bit of code is very strange. It is due to the + fact that some OSes use int* and some use gid_t* for + getgroups, and some (like SunOS) use both, one in prototypes, + and one in man pages and the actual code. Thus we detect it + dynamically using some very ugly code */ + if (ngroups > 0) + { + /* does getgroups return ints or gid_t ?? */ + static BOOL groups_use_ints = True; + + if (groups_use_ints && + ngroups == 1 && + SVAL(igroups,2) == 0x4242) + groups_use_ints = False; + + for (i=0;groups_use_ints && i<ngroups;i++) + if (igroups[i] == 0x42424242) + groups_use_ints = False; + + if (groups_use_ints) + { + *p_igroups = igroups; + *p_groups = (gid_t *)igroups; + } + else + { + gid_t *groups = (gid_t *)igroups; + igroups = (int *)malloc(sizeof(int)*ngroups); + for (i=0;i<ngroups;i++) + igroups[i] = groups[i]; + *p_igroups = igroups; + *p_groups = (gid_t *)groups; + } + } + DEBUG(3,("%s is in %d groups\n",user,ngroups)); + for (i=0;i<ngroups;i++) + DEBUG(3,("%d ",igroups[i])); + DEBUG(3,("\n")); + } + return 0; +} + +/**************************************************************************** + make a connection to a service +****************************************************************************/ +int make_connection(char *service,char *user,char *password, int pwlen, char *dev,int vuid) +{ + int cnum; + int snum; + struct passwd *pass = NULL; + connection_struct *pcon; + BOOL guest = False; + BOOL force = False; + static BOOL first_connection = True; + + strlower(service); + + snum = find_service(service); + if (snum < 0) + { + if (strequal(service,"IPC$")) + { + DEBUG(3,("%s refusing IPC connection\n",timestring())); + return(-3); + } + + DEBUG(0,("%s couldn't find service %s\n",timestring(),service)); + return(-2); + } + + if (strequal(service,HOMES_NAME)) + { + if (*user && Get_Pwnam(user,True)) + return(make_connection(user,user,password,pwlen,dev,vuid)); + + if (validated_username(vuid)) + { + strcpy(user,validated_username(vuid)); + 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$")) + strcpy(dev,"IPC"); + + if (*dev == '?' || !*dev) + { + if (lp_print_ok(snum)) + strcpy(dev,"LPT1:"); + else + strcpy(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,("%s invalid username/password for %s\n",timestring(),service)); + return(-1); + } + + cnum = find_free_connection(str_checksum(service) + str_checksum(user)); + if (cnum < 0) + { + DEBUG(0,("%s couldn't find free connection\n",timestring())); + 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,("%s couldn't find account %s\n",timestring(),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 */ + if (user_in_list(user,lp_admin_users(snum)) && + !pcon->read_only) + { + 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->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; + string_set(&pcon->dirpath,""); + string_set(&pcon->user,user); + +#if HAVE_GETGRNAM + if (*lp_force_group(snum)) + { + struct group *gptr = (struct group *)getgrnam(lp_force_group(snum)); + if (gptr) + { + pcon->gid = gptr->gr_gid; + DEBUG(3,("Forced group %s\n",lp_force_group(snum))); + } + else + DEBUG(1,("Couldn't find group %s\n",lp_force_group(snum))); + } +#endif + + if (*lp_force_user(snum)) + { + struct passwd *pass2; + fstring fuser; + strcpy(fuser,lp_force_user(snum)); + pass2 = (struct passwd *)Get_Pwnam(fuser,True); + if (pass2) + { + pcon->uid = pass2->pw_uid; + string_set(&pcon->user,fuser); + strcpy(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; + strcpy(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->igroups,&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,first_connection); + + first_connection = False; + } /* IS_IPC */ + + pcon->open = True; + + /* execute any "root preexec = " line */ + if (*lp_rootpreexec(SNUM(cnum))) + { + pstring cmd; + strcpy(cmd,lp_rootpreexec(SNUM(cnum))); + standard_sub(cnum,cmd); + DEBUG(5,("cmd=%s\n",cmd)); + smbrun(cmd,NULL); + } + + if (!become_user(cnum,pcon->uid)) + { + 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\n",pcon->connectpath)); + 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; + strcpy(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; + strcpy(cmd,lp_preexec(SNUM(cnum))); + standard_sub(cnum,cmd); + smbrun(cmd,NULL); + } + + /* we've finished with the sensitive stuff */ + unbecome_user(); + + { + extern struct from_host Client_info; + DEBUG(IS_IPC(cnum)?3:1,("%s %s (%s) connect to service %s as user %s (uid=%d,gid=%d) (pid %d)\n", + timestring(), + Client_info.name,Client_info.addr, + lp_servicename(SNUM(cnum)),user, + pcon->uid, + pcon->gid, + (int)getpid())); + } + + return(cnum); +} + + +/**************************************************************************** + find first available file slot +****************************************************************************/ +int find_free_file(void ) +{ + int i; + for (i=1;i<MAX_OPEN_FILES;i++) + if (!Files[i].open) + 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); +#ifdef SMB_PASSWD + /* Create a token value and add it to the outgoing packet. */ + if (doencrypt) + generate_next_challenge(smb_buf(outbuf)); +#endif + + Protocol = PROTOCOL_LANMAN1; + + if (lp_security() == SEC_SERVER && server_cryptkey(outbuf)) { + DEBUG(3,("using password server validation\n")); +#ifdef SMB_PASSWD + if (doencrypt) set_challenge(smb_buf(outbuf)); +#endif + } + + CVAL(outbuf,smb_flg) = 0x81; /* Reply, SMBlockread, SMBwritelock supported */ + SSVAL(outbuf,smb_vwv2,maxxmit); + 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); + + if (lp_security()>=SEC_USER) secword |= 1; + if (doencrypt) secword |= 2; + + set_message(outbuf,13,doencrypt?8:0,True); + SSVAL(outbuf,smb_vwv1,secword); +#ifdef SMB_PASSWD + /* Create a token value and add it to the outgoing packet. */ + if (doencrypt) + generate_next_challenge(smb_buf(outbuf)); +#endif + + SIVAL(outbuf,smb_vwv6,getpid()); + + Protocol = PROTOCOL_LANMAN2; + + if (lp_security() == SEC_SERVER && server_cryptkey(outbuf)) { + DEBUG(3,("using password server validation\n")); +#ifdef SMB_PASSWD + if (doencrypt) set_challenge(smb_buf(outbuf)); +#endif + } + + CVAL(outbuf,smb_flg) = 0x81; /* Reply, SMBlockread, SMBwritelock supported */ + SSVAL(outbuf,smb_vwv2,maxxmit); + 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) +{ + int capabilities=0x300; /* has dual names + lock_and_read */ + int secword=0; + BOOL doencrypt = SMBENCRYPT(); + + if (lp_security()>=SEC_USER) secword |= 1; + if (doencrypt) secword |= 2; + + set_message(outbuf,17,doencrypt?8:0,True); + CVAL(outbuf,smb_vwv1) = secword; +#ifdef SMB_PASSWD + /* Create a token value and add it to the outgoing packet. */ + if (doencrypt) { + generate_next_challenge(smb_buf(outbuf)); + /* Tell the nt machine how long the challenge is. */ + SSVALS(outbuf,smb_vwv16+1,8); + } +#endif + + SIVAL(outbuf,smb_vwv7+1,getpid()); /* session key */ + + Protocol = PROTOCOL_NT1; + + if (lp_security() == SEC_SERVER && server_cryptkey(outbuf)) { + DEBUG(3,("using password server validation\n")); +#ifdef SMB_PASSWD + if (doencrypt) set_challenge(smb_buf(outbuf)); +#endif + } + + if (lp_readraw() && lp_writeraw()) + capabilities |= 1; + + SSVAL(outbuf,smb_vwv1+1,lp_maxmux()); /* maxmpx */ + SSVAL(outbuf,smb_vwv2+1,1); /* num vcs */ + SIVAL(outbuf,smb_vwv3+1,0xFFFF); /* max buffer */ + SIVAL(outbuf,smb_vwv5+1,0xFFFF); /* raw size */ + SIVAL(outbuf,smb_vwv9+1,capabilities); /* capabilities */ + put_long_date(outbuf+smb_vwv11+1,time(NULL)); + SSVALS(outbuf,smb_vwv15+1,TimeDiff(time(NULL))/60); + + 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) +{ + extern fstring remote_arch; + 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: + strcpy(remote_arch,"Samba"); + break; + case ARCH_WFWG: + strcpy(remote_arch,"WfWg"); + break; + case ARCH_WIN95: + strcpy(remote_arch,"Win95"); + break; + case ARCH_WINNT: + strcpy(remote_arch,"WinNT"); + break; + case ARCH_OS2: + strcpy(remote_arch,"OS2"); + break; + default: + strcpy(remote_arch,"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) + 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; + strcpy(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,("%s negprot index=%d\n",timestring(),choice)); + + return(outsize); +} + + +/**************************************************************************** + parse a connect packet +****************************************************************************/ +void parse_connect(char *buf,char *service,char *user,char *password,int *pwlen,char *dev) +{ + char *p = smb_buf(buf) + 1; + char *p2; + + DEBUG(4,("parsing connect string %s\n",p)); + + p2 = strrchr(p,'\\'); + if (p2 == NULL) + strcpy(service,p); + else + strcpy(service,p2+1); + + p += strlen(p) + 2; + + strcpy(password,p); + *pwlen = strlen(password); + + p += strlen(p) + 2; + + strcpy(dev,p); + + *user = 0; + p = strchr(service,'%'); + if (p != NULL) + { + *p = 0; + strcpy(user,p+1); + } +} + + +/**************************************************************************** +close all open files for a connection +****************************************************************************/ +static void close_open_files(int cnum) +{ + int i; + for (i=0;i<MAX_OPEN_FILES;i++) + if( Files[i].cnum == cnum && Files[i].open) { + close_file(i); + } +} + + + +/**************************************************************************** +close a cnum +****************************************************************************/ +void close_cnum(int cnum, int uid) +{ + extern struct from_host Client_info; + + 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 (%s) closed connection to service %s\n", + timestring(), + Client_info.name,Client_info.addr, + 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(cnum,uid)) + { + pstring cmd; + strcpy(cmd,lp_postexec(SNUM(cnum))); + standard_sub(cnum,cmd); + smbrun(cmd,NULL); + unbecome_user(); + } + + unbecome_user(); + /* execute any "root postexec = " line */ + if (*lp_rootpostexec(SNUM(cnum))) + { + pstring cmd; + strcpy(cmd,lp_rootpostexec(SNUM(cnum))); + standard_sub(cnum,cmd); + smbrun(cmd,NULL); + } + + Connections[cnum].open = False; + num_connections_open--; + if (Connections[cnum].ngroups && Connections[cnum].groups) + { + if (Connections[cnum].igroups != (int *)Connections[cnum].groups) + free(Connections[cnum].groups); + free(Connections[cnum].igroups); + Connections[cnum].groups = NULL; + Connections[cnum].igroups = NULL; + Connections[cnum].ngroups = 0; + } + + string_set(&Connections[cnum].user,""); + string_set(&Connections[cnum].dirpath,""); + string_set(&Connections[cnum].connectpath,""); +} + + +/**************************************************************************** +simple routines to do connection counting +****************************************************************************/ +BOOL yield_connection(int cnum,char *name,int max_connections) +{ + struct connect_record crec; + pstring fname; + FILE *f; + int mypid = getpid(); + int i; + + DEBUG(3,("Yielding connection to %d %s\n",cnum,name)); + + if (max_connections <= 0) + return(True); + + bzero(&crec,sizeof(crec)); + + strcpy(fname,lp_lockdir()); + standard_sub(cnum,fname); + trim_string(fname,"","/"); + + strcat(fname,"/"); + strcat(fname,name); + strcat(fname,".LCK"); + + f = fopen(fname,"r+"); + if (!f) + { + DEBUG(2,("Coudn't open lock file %s (%s)\n",fname,strerror(errno))); + return(False); + } + + fseek(f,0,SEEK_SET); + + /* find a free spot */ + for (i=0;i<max_connections;i++) + { + if (fread(&crec,sizeof(crec),1,f) != 1) + { + DEBUG(2,("Entry not found in lock file %s\n",fname)); + fclose(f); + return(False); + } + if (crec.pid == mypid && crec.cnum == cnum) + break; + } + + if (crec.pid != mypid || crec.cnum != cnum) + { + fclose(f); + DEBUG(2,("Entry not found in lock file %s\n",fname)); + return(False); + } + + bzero((void *)&crec,sizeof(crec)); + + /* remove our mark */ + if (fseek(f,i*sizeof(crec),SEEK_SET) != 0 || + fwrite(&crec,sizeof(crec),1,f) != 1) + { + DEBUG(2,("Couldn't update lock file %s (%s)\n",fname,strerror(errno))); + fclose(f); + return(False); + } + + DEBUG(3,("Yield successful\n")); + + fclose(f); + return(True); +} + + +/**************************************************************************** +simple routines to do connection counting +****************************************************************************/ +BOOL claim_connection(int cnum,char *name,int max_connections,BOOL Clear) +{ + struct connect_record crec; + pstring fname; + FILE *f; + int snum = SNUM(cnum); + int i,foundi= -1; + int total_recs; + + if (max_connections <= 0) + return(True); + + DEBUG(5,("trying claim %s %s %d\n",lp_lockdir(),name,max_connections)); + + strcpy(fname,lp_lockdir()); + standard_sub(cnum,fname); + trim_string(fname,"","/"); + + if (!directory_exist(fname,NULL)) + mkdir(fname,0755); + + strcat(fname,"/"); + strcat(fname,name); + strcat(fname,".LCK"); + + if (!file_exist(fname,NULL)) + { + f = fopen(fname,"w"); + if (f) fclose(f); + } + + total_recs = file_size(fname) / sizeof(crec); + + f = fopen(fname,"r+"); + + if (!f) + { + DEBUG(1,("couldn't open lock file %s\n",fname)); + return(False); + } + + /* find a free spot */ + for (i=0;i<max_connections;i++) + { + + if (i>=total_recs || + fseek(f,i*sizeof(crec),SEEK_SET) != 0 || + fread(&crec,sizeof(crec),1,f) != 1) + { + if (foundi < 0) foundi = i; + break; + } + + if (Clear && crec.pid && !process_exists(crec.pid)) + { + fseek(f,i*sizeof(crec),SEEK_SET); + bzero((void *)&crec,sizeof(crec)); + fwrite(&crec,sizeof(crec),1,f); + if (foundi < 0) foundi = i; + continue; + } + if (foundi < 0 && (!crec.pid || !process_exists(crec.pid))) + { + foundi=i; + if (!Clear) break; + } + } + + if (foundi < 0) + { + DEBUG(3,("no free locks in %s\n",fname)); + fclose(f); + return(False); + } + + /* fill in the crec */ + bzero((void *)&crec,sizeof(crec)); + crec.magic = 0x280267; + crec.pid = getpid(); + crec.cnum = cnum; + crec.uid = Connections[cnum].uid; + crec.gid = Connections[cnum].gid; + StrnCpy(crec.name,lp_servicename(snum),sizeof(crec.name)-1); + crec.start = time(NULL); + + { + extern struct from_host Client_info; + StrnCpy(crec.machine,Client_info.name,sizeof(crec.machine)-1); + StrnCpy(crec.addr,Client_info.addr,sizeof(crec.addr)-1); + } + + /* make our mark */ + if (fseek(f,foundi*sizeof(crec),SEEK_SET) != 0 || + fwrite(&crec,sizeof(crec),1,f) != 1) + { + fclose(f); + return(False); + } + + fclose(f); + return(True); +} + +#if DUMP_CORE +/******************************************************************* +prepare to dump a core file - carefully! +********************************************************************/ +static BOOL dump_core(void) +{ + char *p; + pstring dname; + strcpy(dname,debugf); + if ((p=strrchr(dname,'/'))) *p=0; + strcat(dname,"/corefiles"); + mkdir(dname,0700); + sys_chown(dname,getuid(),getgid()); + chmod(dname,0700); + if (chdir(dname)) return(False); + umask(~(0700)); + +#ifndef NO_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,-1); +#ifdef DFS_AUTH + 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 + } + DEBUG(3,("%s Server exit (%s)\n",timestring(),reason?reason:"")); + exit(0); +} + +/**************************************************************************** +do some standard substitutions in a string +****************************************************************************/ +void standard_sub(int cnum,char *s) +{ + if (!strchr(s,'%')) return; + + if (VALID_CNUM(cnum)) + { + string_sub(s,"%S",lp_servicename(Connections[cnum].service)); + string_sub(s,"%P",Connections[cnum].connectpath); + string_sub(s,"%u",Connections[cnum].user); + if (strstr(s,"%H")) { + char *home = get_home_dir(Connections[cnum].user); + if (home) string_sub(s,"%H",home); + } + string_sub(s,"%g",gidtoname(Connections[cnum].gid)); + } + standard_sub_basic(s); +} + +/* +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 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)(); + 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}, + {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}, + + /* 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}, + {SMBread,"SMBread",reply_read,AS_USER}, + {SMBwrite,"SMBwrite",reply_write,AS_USER}, + {SMBclose,"SMBclose",reply_close,AS_USER}, + {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}, + + /* 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}, + {SMBsplopen,"SMBsplopen",reply_printopen,AS_USER}, + {SMBsplclose,"SMBsplclose",reply_printclose,AS_USER}, + {SMBsplretq,"SMBsplretq",reply_printqueue,AS_USER}, + {SMBsplwr,"SMBsplwr",reply_printwrite,AS_USER}, + {SMBlock,"SMBlock",reply_lock,AS_USER}, + {SMBunlock,"SMBunlock",reply_unlock,AS_USER}, + + /* CORE+ PROTOCOL FOLLOWS */ + + {SMBreadbraw,"SMBreadbraw",reply_readbraw,AS_USER}, + {SMBwritebraw,"SMBwritebraw",reply_writebraw,AS_USER}, + {SMBwriteclose,"SMBwriteclose",reply_writeclose,AS_USER}, + {SMBlockread,"SMBlockread",reply_lockread,AS_USER}, + {SMBwriteunlock,"SMBwriteunlock",reply_writeunlock,AS_USER}, + + /* LANMAN1.0 PROTOCOL FOLLOWS */ + + {SMBreadBmpx,"SMBreadBmpx",reply_readbmpx,AS_USER}, + {SMBreadBs,"SMBreadBs",NULL,AS_USER}, + {SMBwriteBmpx,"SMBwriteBmpx",reply_writebmpx,AS_USER}, + {SMBwriteBs,"SMBwriteBs",reply_writebs,AS_USER}, + {SMBwritec,"SMBwritec",NULL,AS_USER}, + {SMBsetattrE,"SMBsetattrE",reply_setattrE,AS_USER | NEED_WRITE}, + {SMBgetattrE,"SMBgetattrE",reply_getattrE,AS_USER}, + {SMBtrans,"SMBtrans",reply_trans,AS_USER | CAN_IPC}, + {SMBtranss,"SMBtranss",NULL,AS_USER | CAN_IPC}, + {SMBioctls,"SMBioctls",NULL,AS_USER}, + {SMBcopy,"SMBcopy",reply_copy,AS_USER | NEED_WRITE}, + {SMBmove,"SMBmove",NULL,AS_USER | NEED_WRITE}, + + {SMBopenX,"SMBopenX",reply_open_and_X,AS_USER}, + {SMBreadX,"SMBreadX",reply_read_and_X,AS_USER}, + {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}, + + /* 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 (smb_messages[match].fn) + { + int cnum = SVAL(inbuf,smb_tid); + int flags = smb_messages[match].flags; + int uid = SVAL(inbuf,smb_uid); + + /* 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(cnum,uid)) + return(ERROR(ERRSRV,ERRinvnid)); + + /* 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 + +inbuf points to the original message start. +inbuf2 points to the smb_wct part of the secondary message +type is the type of the secondary message +outbuf points to the original outbuffer +outbuf2 points to the smb_wct field of the new outbuffer +size is the total length of the incoming message (from inbuf1) +bufsize is the total buffer size + +return how many bytes were added to the response +****************************************************************************/ +int chain_reply(int type,char *inbuf,char *inbuf2,char *outbuf,char *outbuf2,int size,int bufsize) +{ + int outsize = 0; + char *ibuf,*obuf; + static BOOL in_chain = False; + static char *last_outbuf=NULL; + BOOL was_inchain = in_chain; + int insize_remaining; + static int insize_deleted; + + + chain_size += PTR_DIFF(outbuf2,outbuf) - smb_wct; + if (was_inchain) + outbuf = last_outbuf; + else + insize_deleted = 0; + + + insize_deleted = 0; + inbuf2 -= insize_deleted; + insize_remaining = size - PTR_DIFF(inbuf2,inbuf); + insize_deleted += size - (insize_remaining + smb_wct); + + in_chain = True; + last_outbuf = outbuf; + + + /* allocate some space for the in and out buffers of the chained message */ + ibuf = (char *)malloc(size + SAFETY_MARGIN); + obuf = (char *)malloc(bufsize + SAFETY_MARGIN); + + if (!ibuf || !obuf) + { + DEBUG(0,("Out of memory in chain reply\n")); + return(ERROR(ERRSRV,ERRnoresource)); + } + + ibuf += SMB_ALIGNMENT; + obuf += SMB_ALIGNMENT; + + /* create the in buffer */ + memcpy(ibuf,inbuf,smb_wct); + memcpy(ibuf+smb_wct,inbuf2,insize_remaining); + CVAL(ibuf,smb_com) = type; + + /* create the out buffer */ + bzero(obuf,smb_size); + + set_message(obuf,0,0,True); + CVAL(obuf,smb_com) = CVAL(ibuf,smb_com); + + memcpy(obuf+4,ibuf+4,4); + CVAL(obuf,smb_rcls) = SUCCESS; + CVAL(obuf,smb_reh) = 0; + CVAL(obuf,smb_flg) = 0x80 | (CVAL(ibuf,smb_flg) & 0x8); /* bit 7 set + means a reply */ + SSVAL(obuf,smb_flg2,1); /* say we support long filenames */ + SSVAL(obuf,smb_err,SUCCESS); + SSVAL(obuf,smb_tid,SVAL(inbuf,smb_tid)); + SSVAL(obuf,smb_pid,SVAL(inbuf,smb_pid)); + SSVAL(obuf,smb_uid,SVAL(inbuf,smb_uid)); + SSVAL(obuf,smb_mid,SVAL(inbuf,smb_mid)); + + DEBUG(3,("Chained message\n")); + show_msg(ibuf); + + /* process the request */ + outsize = switch_message(type,ibuf,obuf,smb_wct+insize_remaining, + bufsize-chain_size); + + /* copy the new reply header over the old one, but preserve + the smb_com field */ + memcpy(outbuf+smb_com+1,obuf+smb_com+1,smb_wct-(smb_com+1)); + + /* and copy the data from the reply to the right spot */ + memcpy(outbuf2,obuf+smb_wct,outsize - smb_wct); + + /* free the allocated buffers */ + if (ibuf) free(ibuf-SMB_ALIGNMENT); + if (obuf) free(obuf-SMB_ALIGNMENT); + + in_chain = was_inchain; + + /* return how much extra has been added to the packet */ + return(outsize - smb_wct); +} + + + +/**************************************************************************** + 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); + + smb_last_time = time(NULL); + + chain_size = 0; + + bzero(outbuf,smb_size); + + if (msg_type != 0) + return(reply_special(inbuf,outbuf)); + + CVAL(outbuf,smb_com) = CVAL(inbuf,smb_com); + set_message(outbuf,0,0,True); + + memcpy(outbuf+4,inbuf+4,4); + CVAL(outbuf,smb_rcls) = 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,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)); + + outsize = switch_message(type,inbuf,outbuf,size,bufsize); + + if(outsize > 4) + smb_setlen(outbuf,outsize - 4); + return(outsize); +} + + +/**************************************************************************** + process commands from the client +****************************************************************************/ +void process(void ) +{ + static int trans_num = 0; + int nread; + extern struct from_host Client_info; + extern int Client; + + fromhost(Client,&Client_info); + + 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,137,SOCK_DGRAM); + } +#endif + + last_user.cnum = -1; + + while (True) + { + int32 len; + int msg_type; + int msg_flags; + int type; + int deadtime = lp_deadtime()*60; + int counter; + int last_keepalive=0; + + if (deadtime <= 0) + deadtime = DEFAULT_SMBD_TIMEOUT; + + if (lp_readprediction()) + do_read_prediction(); + + { + extern pstring share_del_pending; + if (*share_del_pending) { + unbecome_user(); + if (!unlink(share_del_pending)) + DEBUG(3,("Share file deleted %s\n",share_del_pending)); + else + DEBUG(2,("Share del failed of %s\n",share_del_pending)); + share_del_pending[0] = 0; + } + } + + if (share_mode_pending) { + unbecome_user(); + check_share_modes(); + share_mode_pending=False; + } + + errno = 0; + + for (counter=SMBD_SELECT_LOOP; + !receive_smb(Client,InBuffer,SMBD_SELECT_LOOP*1000); + counter += SMBD_SELECT_LOOP) + { + int i; + time_t t; + BOOL allidle = True; + extern int keepalive; + + /* check for socket failure */ + if (errno == EBADF) { + DEBUG(3,("%s Bad file descriptor - exiting\n",timestring())); + return; + } + + t = time(NULL); + + /* become root again if waiting */ + unbecome_user(); + + /* check for smb.conf reload */ + if (!(counter%SMBD_RELOAD_CHECK)) + reload_services(True); + + /* check the share modes every 10 secs */ + if (!(counter%SHARE_MODES_CHECK)) + check_share_modes(); + + /* clean the share modes every 5 minutes */ + if (!(counter%SHARE_MODES_CLEAN)) + clean_share_files(); + + /* automatic timeout if all connections are closed */ + if (num_connections_open==0 && counter >= IDLE_CLOSED_TIMEOUT) { + DEBUG(2,("%s Closing idle connection\n",timestring())); + return; + } + + if (keepalive && (counter-last_keepalive)>keepalive) { + if (!send_keepalive(Client)) { + DEBUG(2,("%s Keepalive failed - exiting\n",timestring())); + return; + } + 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,("%s Closing idle connection 2\n",timestring())); + return; + } + } + + msg_type = CVAL(InBuffer,0); + msg_flags = CVAL(InBuffer,1); + type = CVAL(InBuffer,smb_com); + + len = smb_len(InBuffer); + + DEBUG(6,("got message type 0x%x of len 0x%x\n",msg_type,len)); + + nread = len + 4; + + DEBUG(3,("%s Transaction %d of length %d\n",timestring(),trans_num,nread)); + +#ifdef WITH_VTP + if(trans_num == 1 && VT_Check(InBuffer)) { + VT_Process(); + return; + } +#endif + + + if (msg_type == 0) + show_msg(InBuffer); + + nread = construct_reply(InBuffer,OutBuffer,nread,maxxmit); + + if(nread > 0) { + if (CVAL(OutBuffer,0) == 0) + show_msg(OutBuffer); + + if (nread != smb_len(OutBuffer) + 4) + { + DEBUG(0,("ERROR: Invalid message response size! %d %d\n", + nread, + smb_len(OutBuffer))); + } + else + send_smb(Client,OutBuffer); + } + trans_num++; + } +} + + +/**************************************************************************** + initialise connect, service and file structs +****************************************************************************/ +static void init_structs(void ) +{ + int i; + get_myname(myhostname,&myip); + + 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_OPEN_FILES;i++) + { + Files[i].open = False; + string_init(&Files[i].name,""); + } + + init_dptrs(); +} + +/**************************************************************************** +usage on the program +****************************************************************************/ +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 = 139; + int opt; + extern char *optarg; + +#ifdef NEED_AUTH_PARAMETERS + set_auth_parameters(argc,argv); +#endif + +#ifdef SecureWare + setluid(0); +#endif + + append_log = True; + + TimeInit(); + + strcpy(debugf,SMBLOGFILE); + + setup_logging(argv[0],False); + + charset_initialise(); + + /* make absolutely sure we run as root - to handle cases whre people + are crazy enough to have it setuid */ +#ifdef USE_SETRES + setresuid(0,0,0); +#else + setuid(0); + seteuid(0); + setuid(0); + seteuid(0); +#endif + + fault_setup(exit_server); + + umask(0777 & ~DEF_CREATE_MASK); + + initial_uid = geteuid(); + initial_gid = getegid(); + + if (initial_gid != 0 && initial_uid == 0) + { +#ifdef HPUX + setresgid(0,0,0); +#else + setgid(0); + setegid(0); +#endif + } + + initial_uid = geteuid(); + initial_gid = getegid(); + + + /* 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:hPa")) != EOF) + switch (opt) + { + case 'O': + strcpy(user_socket_options,optarg); + break; + case 'i': + strcpy(scope,optarg); + break; + case 'P': + { + extern BOOL passive; + passive = True; + } + break; + case 's': + strcpy(servicesf,optarg); + break; + case 'l': + strcpy(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(2,("%s smbd version %s started\n",timestring(),VERSION)); + DEBUG(2,("Copyright Andrew Tridgell 1992-1995\n")); + + GetWd(OriginalDir); + +#ifndef NO_GETRLIMIT +#ifdef RLIMIT_NOFILE + { + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + rlp.rlim_cur = (MAX_OPEN_FILES>rlp.rlim_max)? rlp.rlim_max:MAX_OPEN_FILES; + 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); + +#ifndef NO_SIGNAL_TEST + signal(SIGHUP,SIGNAL_CAST sig_hup); +#endif + + DEBUG(3,("%s loaded services\n",timestring())); + + 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,("%s becoming a daemon\n",timestring())); + become_daemon(); + } + + if (open_sockets(is_daemon,port)) + { + /* possibly reload the services file. */ + reload_services(True); + + maxxmit = MIN(lp_maxxmit(),BUFFER_SIZE); + + if (*lp_rootdir()) + { + if (sys_chroot(lp_rootdir()) == 0) + DEBUG(2,("%s changed root to %s\n",timestring(),lp_rootdir())); + } + + process(); + close_sockets(); + } + exit_server("normal exit"); + return(0); +} + + |