1998-08-17 17:11:34 +04:00
/*
2002-01-30 09:08:46 +03:00
Unix SMB / CIFS implementation .
1998-08-17 17:11:34 +04:00
service ( connection ) opening and closing
Copyright ( C ) Andrew Tridgell 1992 - 1998
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include "includes.h"
1999-12-13 16:27:58 +03:00
extern struct timeval smb_last_time ;
1998-08-17 17:11:34 +04:00
extern int case_default ;
extern BOOL case_preserve ;
extern BOOL short_case_preserve ;
extern BOOL case_mangle ;
extern BOOL case_sensitive ;
extern BOOL use_mangled_map ;
2001-01-23 04:52:30 +03:00
extern userdom_struct current_user_info ;
1998-08-17 17:11:34 +04:00
/****************************************************************************
2001-10-19 00:15:12 +04:00
Load parameters specific to a connection / service .
1998-08-17 17:11:34 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2001-10-19 00:15:12 +04:00
BOOL set_current_service ( connection_struct * conn , BOOL do_chdir )
1998-08-17 17:11:34 +04:00
{
extern char magic_char ;
static connection_struct * last_conn ;
int snum ;
if ( ! conn ) {
last_conn = NULL ;
return ( False ) ;
}
1999-12-13 16:27:58 +03:00
conn - > lastused = smb_last_time . tv_sec ;
1998-08-17 17:11:34 +04:00
snum = SNUM ( conn ) ;
if ( do_chdir & &
2000-09-27 23:09:59 +04:00
vfs_ChDir ( conn , conn - > connectpath ) ! = 0 & &
vfs_ChDir ( conn , conn - > origpath ) ! = 0 ) {
1998-08-17 17:11:34 +04:00
DEBUG ( 0 , ( " chdir (%s) failed \n " ,
conn - > connectpath ) ) ;
return ( False ) ;
}
if ( conn = = last_conn )
return ( True ) ;
last_conn = conn ;
case_default = lp_defaultcase ( snum ) ;
case_preserve = lp_preservecase ( snum ) ;
short_case_preserve = lp_shortpreservecase ( snum ) ;
case_mangle = lp_casemangle ( snum ) ;
case_sensitive = lp_casesensitive ( snum ) ;
magic_char = lp_magicchar ( snum ) ;
use_mangled_map = ( * lp_mangled_map ( snum ) ? True : False ) ;
return ( True ) ;
}
2001-01-24 22:34:53 +03:00
/****************************************************************************
Add a home service . Returns the new service number or - 1 if fail .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2002-07-15 14:35:28 +04:00
int add_home_service ( const char * service , const char * username , const char * homedir )
2001-01-24 22:34:53 +03:00
{
int iHomeService ;
if ( ! service | | ! homedir )
return - 1 ;
if ( ( iHomeService = lp_servicenumber ( HOMES_NAME ) ) < 0 )
return - 1 ;
/*
* If this is a winbindd provided username , remove
* the domain component before adding the service .
* Log a warning if the " path= " parameter does not
* include any macros .
*/
2002-07-15 14:35:28 +04:00
{
const char * p = strchr ( service , * lp_winbind_separator ( ) ) ;
/* We only want the 'user' part of the string */
if ( p ) {
service = p + 1 ;
}
}
2002-08-17 19:27:10 +04:00
if ( ! lp_add_home ( service , iHomeService , username , homedir ) ) {
return - 1 ;
}
2002-07-15 14:35:28 +04:00
return lp_servicenumber ( service ) ;
2001-01-24 22:34:53 +03:00
}
1998-08-17 17:11:34 +04:00
2001-01-24 22:34:53 +03:00
2002-01-16 05:42:07 +03:00
/**
* Find a service entry . service is always in dos codepage .
*
* @ param service is modified ( to canonical form ? ? )
* */
int find_service ( fstring service )
1998-08-17 17:11:34 +04:00
{
int iService ;
1999-12-13 16:27:58 +03:00
all_string_sub ( service , " \\ " , " / " , 0 ) ;
1998-08-17 17:11:34 +04:00
iService = lp_servicenumber ( service ) ;
/* now handle the special case of a home directory */
if ( iService < 0 )
{
2002-07-15 14:35:28 +04:00
char * phome_dir = get_user_home_dir ( service ) ;
1998-08-17 17:11:34 +04:00
1999-12-13 16:27:58 +03:00
if ( ! phome_dir )
1998-08-17 17:11:34 +04:00
{
/*
* Try mapping the servicename , it may
* be a Windows to unix mapped user name .
*/
if ( map_username ( service ) )
2002-07-15 14:35:28 +04:00
phome_dir = get_user_home_dir ( service ) ;
1998-08-17 17:11:34 +04:00
}
DEBUG ( 3 , ( " checking for home directory %s gave %s \n " , service ,
phome_dir ? phome_dir : " (NULL) " ) ) ;
2002-07-15 14:35:28 +04:00
iService = add_home_service ( service , service /* 'username' */ , phome_dir ) ;
1998-08-17 17:11:34 +04:00
}
/* 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 ) ) ;
2003-01-15 21:57:41 +03:00
pszTemp = lp_printcapname ( ) ;
1998-08-17 17:11:34 +04:00
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 ) ) ;
2002-01-16 05:42:07 +03:00
lp_add_printer ( service , iPrinterService ) ;
1998-08-17 17:11:34 +04:00
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 ) ) ;
}
}
2000-02-03 08:17:25 +03:00
/* Check for default vfs service? Unsure whether to implement this */
if ( iService < 0 )
{
}
1998-08-17 17:11:34 +04:00
/* just possibly it's a default service? */
if ( iService < 0 )
{
char * pdefservice = lp_defaultservice ( ) ;
1998-11-29 09:23:16 +03:00
if ( pdefservice & & * pdefservice & &
! strequal ( pdefservice , service ) & &
! strstr ( service , " .. " ) )
1998-08-17 17:11:34 +04:00
{
/*
* We need to do a local copy here as lp_defaultservice ( )
* returns one of the rotating lp_string buffers that
* could get overwritten by the recursive find_service ( ) call
* below . Fix from Josef Hinteregger < joehtg @ joehtg . co . at > .
*/
pstring defservice ;
pstrcpy ( defservice , pdefservice ) ;
iService = find_service ( defservice ) ;
if ( iService > = 0 )
{
2002-01-16 05:42:07 +03:00
all_string_sub ( service , " _ " , " / " , 0 ) ;
iService = lp_add_service ( service , iService ) ;
1998-08-17 17:11:34 +04:00
}
}
}
if ( iService > = 0 )
if ( ! VALID_SNUM ( iService ) )
{
2002-01-16 05:42:07 +03:00
DEBUG ( 0 , ( " Invalid snum %d for %s \n " , iService , service ) ) ;
1998-08-17 17:11:34 +04:00
iService = - 1 ;
}
if ( iService < 0 )
DEBUG ( 3 , ( " find_service() failed to find service %s \n " , service ) ) ;
return ( iService ) ;
}
2001-08-17 11:03:27 +04:00
/****************************************************************************
do some basic sainity checks on the share .
This function modifies dev , ecode .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2003-03-19 02:49:03 +03:00
static NTSTATUS share_sanity_checks ( int snum , fstring dev )
2001-08-17 11:03:27 +04:00
{
if ( ! lp_snum_ok ( snum ) | |
! check_access ( smbd_server_fd ( ) ,
lp_hostsallow ( snum ) , lp_hostsdeny ( snum ) ) ) {
2001-08-27 23:46:22 +04:00
return NT_STATUS_ACCESS_DENIED ;
2001-08-17 11:03:27 +04:00
}
2002-01-16 05:42:07 +03:00
if ( dev [ 0 ] = = ' ? ' | | ! dev [ 0 ] ) {
2001-08-17 11:03:27 +04:00
if ( lp_print_ok ( snum ) ) {
2003-04-12 03:28:15 +04:00
fstrcpy ( dev , " LPT1: " ) ;
2003-03-30 20:37:10 +04:00
} else if ( strequal ( lp_fstype ( snum ) , " IPC " ) ) {
fstrcpy ( dev , " IPC " ) ;
2001-08-17 11:03:27 +04:00
} else {
2003-03-19 05:01:11 +03:00
fstrcpy ( dev , " A: " ) ;
2001-08-17 11:03:27 +04:00
}
}
2003-07-03 23:11:31 +04:00
strupper_m ( dev ) ;
2003-03-30 20:37:10 +04:00
if ( lp_print_ok ( snum ) ) {
2003-04-12 03:28:15 +04:00
if ( ! strequal ( dev , " LPT1: " ) ) {
2003-03-30 20:37:10 +04:00
return NT_STATUS_BAD_DEVICE_TYPE ;
}
} else if ( strequal ( lp_fstype ( snum ) , " IPC " ) ) {
if ( ! strequal ( dev , " IPC " ) ) {
return NT_STATUS_BAD_DEVICE_TYPE ;
}
} else if ( ! strequal ( dev , " A: " ) ) {
2001-08-27 23:46:22 +04:00
return NT_STATUS_BAD_DEVICE_TYPE ;
2001-08-17 11:03:27 +04:00
}
/* Behave as a printer if we are supposed to */
if ( lp_print_ok ( snum ) & & ( strcmp ( dev , " A: " ) = = 0 ) ) {
2003-04-12 03:28:15 +04:00
fstrcpy ( dev , " LPT1: " ) ;
2001-08-17 11:03:27 +04:00
}
2001-08-27 23:46:22 +04:00
return NT_STATUS_OK ;
2001-08-17 11:03:27 +04:00
}
2001-08-17 11:48:25 +04:00
/****************************************************************************
readonly share ?
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2003-02-24 05:35:54 +03:00
static void set_read_only ( connection_struct * conn , gid_t * groups , size_t n_groups )
2001-08-17 11:48:25 +04:00
{
2001-09-19 08:11:23 +04:00
char * * list ;
2001-08-17 11:48:25 +04:00
char * service = lp_servicename ( conn - > service ) ;
conn - > read_only = lp_readonly ( conn - > service ) ;
2001-09-19 08:11:23 +04:00
if ( ! service ) return ;
2001-08-17 11:48:25 +04:00
2002-07-15 14:35:28 +04:00
str_list_copy ( & list , lp_readlist ( conn - > service ) ) ;
2001-09-19 08:11:23 +04:00
if ( list ) {
2002-07-15 14:35:28 +04:00
if ( ! str_list_substitute ( list , " %S " , service ) ) {
2001-09-19 08:11:23 +04:00
DEBUG ( 0 , ( " ERROR: read list substitution failed \n " ) ) ;
2001-08-17 11:48:25 +04:00
}
2003-02-24 05:35:54 +03:00
if ( user_in_list ( conn - > user , ( const char * * ) list , groups , n_groups ) )
2001-09-19 08:11:23 +04:00
conn - > read_only = True ;
2002-07-15 14:35:28 +04:00
str_list_free ( & list ) ;
2001-09-19 08:11:23 +04:00
}
2002-07-15 14:35:28 +04:00
str_list_copy ( & list , lp_writelist ( conn - > service ) ) ;
2001-09-19 08:11:23 +04:00
if ( list ) {
2002-07-15 14:35:28 +04:00
if ( ! str_list_substitute ( list , " %S " , service ) ) {
2001-09-19 08:11:23 +04:00
DEBUG ( 0 , ( " ERROR: write list substitution failed \n " ) ) ;
2001-08-17 11:48:25 +04:00
}
2003-02-24 05:35:54 +03:00
if ( user_in_list ( conn - > user , ( const char * * ) list , groups , n_groups ) )
2001-09-19 08:11:23 +04:00
conn - > read_only = False ;
2002-07-15 14:35:28 +04:00
str_list_free ( & list ) ;
2001-08-17 11:48:25 +04:00
}
}
2001-08-17 12:12:33 +04:00
/****************************************************************************
admin user check
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2003-02-24 05:35:54 +03:00
static void set_admin_user ( connection_struct * conn , gid_t * groups , size_t n_groups )
2001-08-17 12:12:33 +04:00
{
/* admin user check */
/* JRA - original code denied admin user if the share was
marked read_only . Changed as I don ' t think this is needed ,
but old code left in case there is a problem here .
*/
2003-02-24 05:35:54 +03:00
if ( user_in_list ( conn - > user , lp_admin_users ( conn - > service ) , groups , n_groups )
2001-08-17 12:12:33 +04:00
#if 0
& & ! conn - > read_only
# endif
) {
conn - > admin_user = True ;
2002-09-25 19:19:00 +04:00
conn - > force_user = True ; /* Admin users are effectivly 'forced' */
2001-08-17 12:12:33 +04:00
DEBUG ( 0 , ( " %s logged in as admin user (root privileges) \n " , conn - > user ) ) ;
} else {
conn - > admin_user = False ;
}
#if 0 /* This done later, for now */
/* admin users always run as uid=0 */
if ( conn - > admin_user ) {
conn - > uid = 0 ;
}
# endif
}
1998-08-17 17:11:34 +04:00
/****************************************************************************
2002-07-15 14:35:28 +04:00
Make a connection , given the snum to connect to , and the vuser of the
connecting user if appropriate .
1998-08-17 17:11:34 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2001-10-19 00:15:12 +04:00
2002-07-15 14:35:28 +04:00
static connection_struct * make_connection_snum ( int snum , user_struct * vuser ,
DATA_BLOB password ,
2003-03-19 02:49:03 +03:00
const char * pdev , NTSTATUS * status )
1998-08-17 17:11:34 +04:00
{
1999-12-13 16:27:58 +03:00
struct passwd * pass = NULL ;
1998-08-17 17:11:34 +04:00
BOOL guest = False ;
connection_struct * conn ;
2002-07-15 14:35:28 +04:00
struct stat st ;
2001-09-15 16:55:59 +04:00
fstring user ;
2003-03-19 02:49:03 +03:00
fstring dev ;
2002-07-15 14:35:28 +04:00
* user = 0 ;
2003-03-19 02:49:03 +03:00
fstrcpy ( dev , pdev ) ;
1998-08-17 17:11:34 +04:00
2002-07-15 14:35:28 +04:00
if ( NT_STATUS_IS_ERR ( * status = share_sanity_checks ( snum , dev ) ) ) {
1998-08-17 17:11:34 +04:00
return NULL ;
2001-08-17 11:03:27 +04:00
}
2000-07-25 10:10:59 +04:00
1998-08-17 17:11:34 +04:00
conn = conn_new ( ) ;
if ( ! conn ) {
DEBUG ( 0 , ( " Couldn't find free connection. \n " ) ) ;
2001-08-27 23:46:22 +04:00
* status = NT_STATUS_INSUFFICIENT_RESOURCES ;
1998-08-17 17:11:34 +04:00
return NULL ;
}
2002-07-15 14:35:28 +04:00
if ( lp_guest_only ( snum ) ) {
2002-08-17 19:27:10 +04:00
const char * guestname = lp_guestaccount ( ) ;
2002-07-15 14:35:28 +04:00
guest = True ;
pass = getpwnam_alloc ( guestname ) ;
if ( ! pass ) {
2003-07-10 03:01:08 +04:00
DEBUG ( 0 , ( " make_connection_snum: Invalid guest account %s?? \n " , guestname ) ) ;
2002-07-15 14:35:28 +04:00
conn_free ( conn ) ;
* status = NT_STATUS_NO_SUCH_USER ;
return NULL ;
}
fstrcpy ( user , pass - > pw_name ) ;
conn - > force_user = True ;
2003-06-21 11:54:03 +04:00
conn - > uid = pass - > pw_uid ;
conn - > gid = pass - > pw_gid ;
2002-07-15 14:35:28 +04:00
string_set ( & conn - > user , pass - > pw_name ) ;
passwd_free ( & pass ) ;
DEBUG ( 3 , ( " Guest only user %s \n " , user ) ) ;
} else if ( vuser ) {
if ( vuser - > guest ) {
if ( ! lp_guest_ok ( snum ) ) {
2003-02-24 05:35:54 +03:00
DEBUG ( 2 , ( " guest user (from session setup) not permitted to access this share (%s) \n " , lp_servicename ( snum ) ) ) ;
2002-07-15 14:35:28 +04:00
conn_free ( conn ) ;
* status = NT_STATUS_ACCESS_DENIED ;
return NULL ;
}
} else {
2003-02-24 05:35:54 +03:00
if ( ! user_ok ( vuser - > user . unix_name , snum , vuser - > groups , vuser - > n_groups ) ) {
DEBUG ( 2 , ( " user '%s' (from session setup) not permitted to access this share (%s) \n " , vuser - > user . unix_name , lp_servicename ( snum ) ) ) ;
2002-07-15 14:35:28 +04:00
conn_free ( conn ) ;
* status = NT_STATUS_ACCESS_DENIED ;
return NULL ;
}
}
conn - > vuid = vuser - > vuid ;
conn - > uid = vuser - > uid ;
conn - > gid = vuser - > gid ;
string_set ( & conn - > user , vuser - > user . unix_name ) ;
fstrcpy ( user , vuser - > user . unix_name ) ;
guest = vuser - > guest ;
} else if ( lp_security ( ) = = SEC_SHARE ) {
/* add it as a possible user name if we
are in share mode security */
add_session_user ( lp_servicename ( snum ) ) ;
/* shall we let them in? */
if ( ! authorise_login ( snum , user , password , & guest ) ) {
DEBUG ( 2 , ( " Invalid username/password for [%s] \n " ,
lp_servicename ( snum ) ) ) ;
conn_free ( conn ) ;
* status = NT_STATUS_WRONG_PASSWORD ;
return NULL ;
}
pass = Get_Pwnam ( user ) ;
2002-09-25 19:19:00 +04:00
conn - > force_user = True ;
2002-07-15 14:35:28 +04:00
conn - > uid = pass - > pw_uid ;
conn - > gid = pass - > pw_gid ;
string_set ( & conn - > user , pass - > pw_name ) ;
fstrcpy ( user , pass - > pw_name ) ;
1998-08-17 17:11:34 +04:00
2002-07-15 14:35:28 +04:00
} else {
DEBUG ( 0 , ( " invalid VUID (vuser) but not in security=share \n " ) ) ;
1998-08-17 17:11:34 +04:00
conn_free ( conn ) ;
2002-07-15 14:35:28 +04:00
* status = NT_STATUS_ACCESS_DENIED ;
1998-08-17 17:11:34 +04:00
return NULL ;
}
2002-07-15 14:35:28 +04:00
add_session_user ( user ) ;
2001-08-17 12:12:33 +04:00
safe_strcpy ( conn - > client_address , client_addr ( ) ,
sizeof ( conn - > client_address ) - 1 ) ;
1998-08-17 17:11:34 +04:00
conn - > num_files_open = 0 ;
conn - > lastused = time ( NULL ) ;
conn - > service = snum ;
conn - > used = True ;
conn - > printer = ( strncmp ( dev , " LPT " , 3 ) = = 0 ) ;
2001-03-13 03:31:07 +03:00
conn - > ipc = ( ( strncmp ( dev , " IPC " , 3 ) = = 0 ) | | strequal ( dev , " ADMIN$ " ) ) ;
1998-08-17 17:11:34 +04:00
conn - > dirptr = NULL ;
conn - > veto_list = NULL ;
conn - > hide_list = NULL ;
conn - > veto_oplock_list = NULL ;
string_set ( & conn - > dirpath , " " ) ;
string_set ( & conn - > user , user ) ;
2000-08-04 02:38:43 +04:00
conn - > nt_user_token = NULL ;
1999-12-13 16:27:58 +03:00
2003-02-24 05:35:54 +03:00
set_read_only ( conn , vuser ? vuser - > groups : NULL , vuser ? vuser - > n_groups : 0 ) ;
2001-09-20 11:09:28 +04:00
2003-02-24 05:35:54 +03:00
set_admin_user ( conn , vuser ? vuser - > groups : NULL , vuser ? vuser - > n_groups : 0 ) ;
2001-08-17 12:12:33 +04:00
1999-12-13 16:27:58 +03:00
/*
* If force user is true , then store the
2002-09-25 19:19:00 +04:00
* given userid and also the groups
1999-12-13 16:27:58 +03:00
* of the user we ' re forcing .
*/
if ( * lp_force_user ( snum ) ) {
struct passwd * pass2 ;
pstring fuser ;
pstrcpy ( fuser , lp_force_user ( snum ) ) ;
1999-04-04 10:22:22 +04:00
1999-12-13 16:27:58 +03:00
/* Allow %S to be used by force user. */
2002-07-15 14:35:28 +04:00
pstring_sub ( fuser , " %S " , lp_servicename ( snum ) ) ;
1999-04-04 10:22:22 +04:00
2002-07-15 14:35:28 +04:00
pass2 = ( struct passwd * ) Get_Pwnam ( fuser ) ;
1999-12-13 16:27:58 +03:00
if ( pass2 ) {
conn - > uid = pass2 - > pw_uid ;
conn - > gid = pass2 - > pw_gid ;
2002-07-15 14:35:28 +04:00
string_set ( & conn - > user , pass2 - > pw_name ) ;
fstrcpy ( user , pass2 - > pw_name ) ;
1999-12-13 16:27:58 +03:00
conn - > force_user = True ;
2002-07-15 14:35:28 +04:00
DEBUG ( 3 , ( " Forced user %s \n " , user ) ) ;
1999-12-13 16:27:58 +03:00
} else {
DEBUG ( 1 , ( " Couldn't find user %s \n " , fuser ) ) ;
2002-07-15 14:35:28 +04:00
conn_free ( conn ) ;
* status = NT_STATUS_NO_SUCH_USER ;
return NULL ;
1999-12-13 16:27:58 +03:00
}
1999-04-04 10:22:22 +04:00
}
2000-08-31 11:11:45 +04:00
/* admin users always run as uid=0 */
if ( conn - > admin_user ) {
conn - > uid = 0 ;
}
1998-08-17 17:11:34 +04:00
# ifdef HAVE_GETGRNAM
1999-12-13 16:27:58 +03:00
/*
* If force group is true , then override
* any groupid stored for the connecting user .
*/
1998-08-17 17:11:34 +04:00
if ( * lp_force_group ( snum ) ) {
2000-10-13 05:59:14 +04:00
gid_t gid ;
1998-08-17 17:11:34 +04:00
pstring gname ;
1999-12-13 16:27:58 +03:00
pstring tmp_gname ;
BOOL user_must_be_member = False ;
1998-08-17 17:11:34 +04:00
2003-04-23 17:27:35 +04:00
pstrcpy ( tmp_gname , lp_force_group ( snum ) ) ;
2002-07-15 14:35:28 +04:00
1999-12-13 16:27:58 +03:00
if ( tmp_gname [ 0 ] = = ' + ' ) {
user_must_be_member = True ;
2003-04-23 17:27:35 +04:00
/* even now, tmp_gname is null terminated */
pstrcpy ( gname , & tmp_gname [ 1 ] ) ;
1999-12-13 16:27:58 +03:00
} else {
2003-04-23 17:27:35 +04:00
pstrcpy ( gname , tmp_gname ) ;
1999-12-13 16:27:58 +03:00
}
1998-08-17 17:11:34 +04:00
/* default service may be a group name */
2002-07-15 14:35:28 +04:00
pstring_sub ( gname , " %S " , lp_servicename ( snum ) ) ;
2000-10-13 05:59:14 +04:00
gid = nametogid ( gname ) ;
1998-08-17 17:11:34 +04:00
2000-10-13 05:59:14 +04:00
if ( gid ! = ( gid_t ) - 1 ) {
2002-09-25 19:19:00 +04:00
1999-12-13 16:27:58 +03:00
/*
* If the user has been forced and the forced group starts
* with a ' + ' , then we only set the group to be the forced
* group if the forced user is a member of that group .
* Otherwise , the meaning of the ' + ' would be ignored .
*/
if ( conn - > force_user & & user_must_be_member ) {
2003-02-24 05:35:54 +03:00
if ( user_in_group_list ( user , gname , NULL , 0 ) ) {
2000-10-13 05:59:14 +04:00
conn - > gid = gid ;
1999-12-13 16:27:58 +03:00
DEBUG ( 3 , ( " Forced group %s for member %s \n " , gname , user ) ) ;
}
} else {
2000-10-13 05:59:14 +04:00
conn - > gid = gid ;
1999-12-13 16:27:58 +03:00
DEBUG ( 3 , ( " Forced group %s \n " , gname ) ) ;
}
2002-09-25 19:19:00 +04:00
conn - > force_group = True ;
1998-08-17 17:11:34 +04:00
} else {
DEBUG ( 1 , ( " Couldn't find group %s \n " , gname ) ) ;
2002-07-15 14:35:28 +04:00
conn_free ( conn ) ;
* status = NT_STATUS_NO_SUCH_GROUP ;
return NULL ;
1998-08-17 17:11:34 +04:00
}
}
1999-12-13 16:27:58 +03:00
# endif /* HAVE_GETGRNAM */
1998-08-17 17:11:34 +04:00
{
pstring s ;
pstrcpy ( s , lp_pathname ( snum ) ) ;
2002-07-15 14:35:28 +04:00
standard_sub_conn ( conn , s , sizeof ( s ) ) ;
1998-08-17 17:11:34 +04:00
string_set ( & conn - > connectpath , s ) ;
2002-08-17 19:27:10 +04:00
DEBUG ( 3 , ( " Connect path is '%s' for service [%s] \n " , s , lp_servicename ( snum ) ) ) ;
1998-08-17 17:11:34 +04:00
}
2002-09-25 19:19:00 +04:00
if ( conn - > force_user | | conn - > force_group ) {
/* groups stuff added by ih */
conn - > ngroups = 0 ;
conn - > groups = NULL ;
/* Find all the groups this uid is in and
store them . Used by change_to_user ( ) */
initialise_groups ( conn - > user , conn - > uid , conn - > gid ) ;
get_current_groups ( conn - > gid , & conn - > ngroups , & conn - > groups ) ;
1998-08-17 17:11:34 +04:00
2002-09-25 19:19:00 +04:00
conn - > nt_user_token = create_nt_token ( conn - > uid , conn - > gid ,
conn - > ngroups , conn - > groups ,
guest ) ;
}
2000-08-04 02:38:43 +04:00
2001-04-12 01:19:25 +04:00
/*
* New code to check if there ' s a share security descripter
* added from NT server manager . This is done after the
* smb . conf checks are done as we need a uid and token . JRA .
2002-09-25 19:19:00 +04:00
*
2001-04-12 01:19:25 +04:00
*/
{
2002-07-15 14:35:28 +04:00
BOOL can_write = share_access_check ( conn , snum , vuser , FILE_WRITE_DATA ) ;
2001-04-12 01:19:25 +04:00
if ( ! can_write ) {
2002-07-15 14:35:28 +04:00
if ( ! share_access_check ( conn , snum , vuser , FILE_READ_DATA ) ) {
2001-04-12 01:19:25 +04:00
/* No access, read or write. */
DEBUG ( 0 , ( " make_connection: connection to %s denied due to security descriptor. \n " ,
2002-07-15 14:35:28 +04:00
lp_servicename ( snum ) ) ) ;
2001-08-17 12:57:58 +04:00
conn_free ( conn ) ;
2002-07-15 14:35:28 +04:00
* status = NT_STATUS_ACCESS_DENIED ;
2001-04-12 01:19:25 +04:00
return NULL ;
} else {
conn - > read_only = True ;
}
}
}
2000-08-04 02:38:43 +04:00
/* Initialise VFS function pointers */
2001-10-18 04:27:20 +04:00
if ( ! smbd_vfs_init ( conn ) ) {
2001-06-30 02:32:24 +04:00
DEBUG ( 0 , ( " vfs_init failed for service %s \n " , lp_servicename ( SNUM ( conn ) ) ) ) ;
2001-08-17 12:57:58 +04:00
conn_free ( conn ) ;
2003-01-06 10:40:39 +03:00
* status = NT_STATUS_BAD_NETWORK_NAME ;
2001-06-30 02:32:24 +04:00
return NULL ;
2000-08-04 02:38:43 +04:00
}
2001-09-20 11:09:28 +04:00
/* ROOT Activities: */
2001-08-17 12:44:04 +04:00
/* check number of connections */
if ( ! claim_connection ( conn ,
lp_servicename ( SNUM ( conn ) ) ,
lp_max_connections ( SNUM ( conn ) ) ,
2002-09-25 19:19:00 +04:00
False , 0 ) ) {
2001-08-17 12:44:04 +04:00
DEBUG ( 1 , ( " too many connections - rejected \n " ) ) ;
conn_free ( conn ) ;
2002-07-15 14:35:28 +04:00
* status = NT_STATUS_INSUFFICIENT_RESOURCES ;
2001-08-17 12:44:04 +04:00
return NULL ;
}
2001-09-20 11:09:28 +04:00
/* Preexecs are done here as they might make the dir we are to ChDir to below */
1998-08-17 17:11:34 +04:00
/* execute any "root preexec = " line */
if ( * lp_rootpreexec ( SNUM ( conn ) ) ) {
2001-09-15 16:55:59 +04:00
int ret ;
1998-08-17 17:11:34 +04:00
pstring cmd ;
pstrcpy ( cmd , lp_rootpreexec ( SNUM ( conn ) ) ) ;
2002-07-15 14:35:28 +04:00
standard_sub_conn ( conn , cmd , sizeof ( cmd ) ) ;
1998-08-17 17:11:34 +04:00
DEBUG ( 5 , ( " cmd=%s \n " , cmd ) ) ;
2001-04-13 23:12:06 +04:00
ret = smbrun ( cmd , NULL ) ;
1999-12-13 16:27:58 +03:00
if ( ret ! = 0 & & lp_rootpreexec_close ( SNUM ( conn ) ) ) {
2001-09-20 11:09:28 +04:00
DEBUG ( 1 , ( " root preexec gave %d - failing connection \n " , ret ) ) ;
2002-01-14 22:34:28 +03:00
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
1999-12-13 16:27:58 +03:00
conn_free ( conn ) ;
2003-01-06 10:40:39 +03:00
* status = NT_STATUS_ACCESS_DENIED ;
1999-12-13 16:27:58 +03:00
return NULL ;
}
1998-08-17 17:11:34 +04:00
}
2001-09-20 11:09:28 +04:00
/* USER Activites: */
2001-10-19 00:15:12 +04:00
if ( ! change_to_user ( conn , conn - > vuid ) ) {
/* No point continuing if they fail the basic checks */
DEBUG ( 0 , ( " Can't become connected user! \n " ) ) ;
conn_free ( conn ) ;
* status = NT_STATUS_LOGON_FAILURE ;
return NULL ;
}
2002-11-18 09:12:47 +03:00
2001-09-20 11:09:28 +04:00
/* Remember that a different vuid can connect later without these checks... */
2002-07-15 14:35:28 +04:00
2001-09-20 11:09:28 +04:00
/* Preexecs are done here as they might make the dir we are to ChDir to below */
/* execute any "preexec = " line */
if ( * lp_preexec ( SNUM ( conn ) ) ) {
int ret ;
pstring cmd ;
pstrcpy ( cmd , lp_preexec ( SNUM ( conn ) ) ) ;
2002-07-15 14:35:28 +04:00
standard_sub_conn ( conn , cmd , sizeof ( cmd ) ) ;
2001-09-20 11:09:28 +04:00
ret = smbrun ( cmd , NULL ) ;
if ( ret ! = 0 & & lp_preexec_close ( SNUM ( conn ) ) ) {
DEBUG ( 1 , ( " preexec gave %d - failing connection \n " , ret ) ) ;
2001-10-19 00:15:12 +04:00
change_to_root_user ( ) ;
2002-01-14 22:34:28 +03:00
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
2001-09-20 11:09:28 +04:00
conn_free ( conn ) ;
2003-01-06 10:40:39 +03:00
* status = NT_STATUS_ACCESS_DENIED ;
2001-09-20 11:09:28 +04:00
return NULL ;
}
1998-08-17 17:11:34 +04:00
}
2003-09-07 20:36:13 +04:00
# ifdef WITH_FAKE_KASERVER
afs_login ( user ) ;
# endif
2002-07-15 14:35:28 +04:00
# if CHECK_PATH_ON_TCONX
/* win2000 does not check the permissions on the directory
during the tree connect , instead relying on permission
check during individual operations . To match this behaviour
I have disabled this chdir check ( tridge ) */
2000-09-27 23:09:59 +04:00
if ( vfs_ChDir ( conn , conn - > connectpath ) ! = 0 ) {
2001-03-27 03:21:29 +04:00
DEBUG ( 0 , ( " %s (%s) Can't change directory to %s (%s) \n " ,
2002-08-17 19:27:10 +04:00
get_remote_machine_name ( ) , conn - > client_address ,
1998-08-17 17:11:34 +04:00
conn - > connectpath , strerror ( errno ) ) ) ;
2001-10-19 00:15:12 +04:00
change_to_root_user ( ) ;
2002-01-14 22:34:28 +03:00
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
1998-08-17 17:11:34 +04:00
conn_free ( conn ) ;
2001-08-27 23:46:22 +04:00
* status = NT_STATUS_BAD_NETWORK_NAME ;
1998-08-17 17:11:34 +04:00
return NULL ;
}
2002-07-15 14:35:28 +04:00
# else
/* the alternative is just to check the directory exists */
if ( stat ( conn - > connectpath , & st ) ! = 0 | | ! S_ISDIR ( st . st_mode ) ) {
2003-07-22 00:20:09 +04:00
DEBUG ( 0 , ( " '%s' does not exist or is not a directory, when connecting to [%s] \n " , conn - > connectpath , lp_servicename ( SNUM ( conn ) ) ) ) ;
2002-07-15 14:35:28 +04:00
change_to_root_user ( ) ;
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
conn_free ( conn ) ;
* status = NT_STATUS_BAD_NETWORK_NAME ;
return NULL ;
}
# endif
1998-08-17 17:11:34 +04:00
string_set ( & conn - > origpath , conn - > connectpath ) ;
# if SOFTLINK_OPTIMISATION
2002-07-15 14:35:28 +04:00
/* resolve any soft links early if possible */
if ( vfs_ChDir ( conn , conn - > connectpath ) = = 0 ) {
1998-08-17 17:11:34 +04:00
pstring s ;
pstrcpy ( s , conn - > connectpath ) ;
2000-09-27 23:09:59 +04:00
vfs_GetWd ( conn , s ) ;
1998-08-17 17:11:34 +04:00
string_set ( & conn - > connectpath , s ) ;
2000-09-27 23:09:59 +04:00
vfs_ChDir ( conn , conn - > connectpath ) ;
1998-08-17 17:11:34 +04:00
}
# endif
1999-12-13 16:27:58 +03:00
/*
* Print out the ' connected as ' stuff here as we need
2001-09-20 11:09:28 +04:00
* to know the effective uid and gid we will be using
* ( at least initially ) .
1999-12-13 16:27:58 +03:00
*/
if ( DEBUGLVL ( IS_IPC ( conn ) ? 3 : 1 ) ) {
2002-08-17 19:27:10 +04:00
dbgtext ( " %s (%s) " , get_remote_machine_name ( ) , conn - > client_address ) ;
2003-08-03 11:20:05 +04:00
dbgtext ( " %s " , srv_is_signing_active ( ) ? " signed " : " " ) ;
1999-12-13 16:27:58 +03:00
dbgtext ( " connect to service %s " , lp_servicename ( SNUM ( conn ) ) ) ;
2001-09-20 11:09:28 +04:00
dbgtext ( " initially as user %s " , user ) ;
1999-12-13 16:27:58 +03:00
dbgtext ( " (uid=%d, gid=%d) " , ( int ) geteuid ( ) , ( int ) getegid ( ) ) ;
2000-05-02 06:23:41 +04:00
dbgtext ( " (pid %d) \n " , ( int ) sys_getpid ( ) ) ;
1998-08-17 17:11:34 +04:00
}
/* Add veto/hide lists */
if ( ! IS_IPC ( conn ) & & ! IS_PRINT ( conn ) ) {
set_namearray ( & conn - > veto_list , lp_veto_files ( SNUM ( conn ) ) ) ;
set_namearray ( & conn - > hide_list , lp_hide_files ( SNUM ( conn ) ) ) ;
set_namearray ( & conn - > veto_oplock_list , lp_veto_oplocks ( SNUM ( conn ) ) ) ;
}
2000-02-03 08:17:25 +03:00
/* Invoke VFS make connection hook */
2003-05-14 14:59:01 +04:00
if ( SMB_VFS_CONNECT ( conn , lp_servicename ( snum ) , user ) < 0 ) {
2003-05-12 03:34:18 +04:00
DEBUG ( 0 , ( " make_connection: VFS make connection failed! \n " ) ) ;
change_to_root_user ( ) ;
conn_free ( conn ) ;
* status = NT_STATUS_UNSUCCESSFUL ;
return NULL ;
2000-08-04 02:38:43 +04:00
}
2001-09-20 11:09:28 +04:00
2001-10-19 00:15:12 +04:00
/* we've finished with the user stuff - go back to root */
change_to_root_user ( ) ;
2000-02-03 08:17:25 +03:00
2000-08-04 02:38:43 +04:00
return ( conn ) ;
1998-08-17 17:11:34 +04:00
}
2002-07-15 14:35:28 +04:00
/***************************************************************************************
Simple wrapper function for make_connection ( ) to include a call to
vfs_chdir ( )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
connection_struct * make_connection_with_chdir ( const char * service_in , DATA_BLOB password ,
2003-03-19 02:49:03 +03:00
const char * dev , uint16 vuid , NTSTATUS * status )
2002-07-15 14:35:28 +04:00
{
connection_struct * conn = NULL ;
conn = make_connection ( service_in , password , dev , vuid , status ) ;
/*
* make_connection ( ) does not change the directory for us any more
* so we have to do it as a separate step - - jerry
*/
if ( conn & & vfs_ChDir ( conn , conn - > connectpath ) ! = 0 ) {
DEBUG ( 0 , ( " move_driver_to_download_area: Can't change directory to %s for [print$] (%s) \n " ,
conn - > connectpath , strerror ( errno ) ) ) ;
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
conn_free ( conn ) ;
* status = NT_STATUS_UNSUCCESSFUL ;
return NULL ;
}
return conn ;
}
/****************************************************************************
Make a connection to a service .
*
* @ param service
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
connection_struct * make_connection ( const char * service_in , DATA_BLOB password ,
2003-03-19 02:49:03 +03:00
const char * pdev , uint16 vuid , NTSTATUS * status )
2002-07-15 14:35:28 +04:00
{
uid_t euid ;
user_struct * vuser = NULL ;
2003-01-15 21:57:41 +03:00
fstring service ;
2003-03-19 02:49:03 +03:00
fstring dev ;
2002-07-15 14:35:28 +04:00
int snum = - 1 ;
2003-04-14 07:48:26 +04:00
2003-03-19 02:49:03 +03:00
fstrcpy ( dev , pdev ) ;
2002-07-15 14:35:28 +04:00
/* This must ONLY BE CALLED AS ROOT. As it exits this function as root. */
if ( ! non_root_mode ( ) & & ( euid = geteuid ( ) ) ! = 0 ) {
DEBUG ( 0 , ( " make_connection: PANIC ERROR. Called as nonroot (%u) \n " , ( unsigned int ) euid ) ) ;
smb_panic ( " make_connection: PANIC ERROR. Called as nonroot \n " ) ;
}
if ( lp_security ( ) ! = SEC_SHARE ) {
vuser = get_valid_user_struct ( vuid ) ;
if ( ! vuser ) {
DEBUG ( 1 , ( " make_connection: refusing to connect with no session setup \n " ) ) ;
2002-08-17 19:27:10 +04:00
* status = NT_STATUS_ACCESS_DENIED ;
2002-07-15 14:35:28 +04:00
return NULL ;
}
}
/* Logic to try and connect to the correct [homes] share, preferably without too many
getpwnam ( ) lookups . This is particulary nasty for winbind usernames , where the
share name isn ' t the same as unix username .
The snum of the homes share is stored on the vuser at session setup time .
*/
if ( strequal ( service_in , HOMES_NAME ) ) {
if ( lp_security ( ) ! = SEC_SHARE ) {
DATA_BLOB no_pw = data_blob ( NULL , 0 ) ;
2002-08-17 19:27:10 +04:00
if ( vuser - > homes_snum = = - 1 ) {
2003-05-14 04:46:43 +04:00
DEBUG ( 2 , ( " [homes] share not available for this user because it was not found or created at session setup time \n " ) ) ;
2002-08-17 19:27:10 +04:00
* status = NT_STATUS_BAD_NETWORK_NAME ;
return NULL ;
2002-07-15 14:35:28 +04:00
}
2002-08-17 19:27:10 +04:00
DEBUG ( 5 , ( " making a connection to [homes] service created at session setup time \n " ) ) ;
return make_connection_snum ( vuser - > homes_snum ,
vuser , no_pw ,
dev , status ) ;
2002-07-15 14:35:28 +04:00
} else {
/* Security = share. Try with current_user_info.smb_name
* as the username . */
if ( * current_user_info . smb_name ) {
fstring unix_username ;
fstrcpy ( unix_username ,
current_user_info . smb_name ) ;
map_username ( unix_username ) ;
snum = find_service ( unix_username ) ;
}
if ( snum ! = - 1 ) {
DEBUG ( 5 , ( " making a connection to 'homes' service %s based on security=share \n " , service_in ) ) ;
return make_connection_snum ( snum , NULL ,
password ,
dev , status ) ;
}
}
} else if ( ( lp_security ( ) ! = SEC_SHARE ) & & ( vuser - > homes_snum ! = - 1 )
2002-08-17 19:27:10 +04:00
& & strequal ( service_in , lp_servicename ( vuser - > homes_snum ) ) ) {
2002-07-15 14:35:28 +04:00
DATA_BLOB no_pw = data_blob ( NULL , 0 ) ;
2002-09-25 19:19:00 +04:00
DEBUG ( 5 , ( " making a connection to 'homes' service [%s] created at session setup time \n " , service_in ) ) ;
2002-07-15 14:35:28 +04:00
return make_connection_snum ( vuser - > homes_snum ,
vuser , no_pw ,
dev , status ) ;
}
2003-01-15 21:57:41 +03:00
fstrcpy ( service , service_in ) ;
2002-07-15 14:35:28 +04:00
2003-07-03 23:11:31 +04:00
strlower_m ( service ) ;
2002-07-15 14:35:28 +04:00
snum = find_service ( service ) ;
if ( snum < 0 ) {
if ( strequal ( service , " IPC$ " ) | | strequal ( service , " ADMIN$ " ) ) {
DEBUG ( 3 , ( " refusing IPC connection to %s \n " , service ) ) ;
* status = NT_STATUS_ACCESS_DENIED ;
return NULL ;
}
DEBUG ( 0 , ( " %s (%s) couldn't find service %s \n " ,
2002-08-17 19:27:10 +04:00
get_remote_machine_name ( ) , client_addr ( ) , service ) ) ;
2002-07-15 14:35:28 +04:00
* status = NT_STATUS_BAD_NETWORK_NAME ;
return NULL ;
}
2002-12-28 02:03:22 +03:00
/* Handle non-Dfs clients attempting connections to msdfs proxy */
if ( lp_host_msdfs ( ) & & ( * lp_msdfs_proxy ( snum ) ! = ' \0 ' ) ) {
DEBUG ( 3 , ( " refusing connection to dfs proxy '%s' \n " , service ) ) ;
* status = NT_STATUS_BAD_NETWORK_NAME ;
return NULL ;
}
2002-07-15 14:35:28 +04:00
DEBUG ( 5 , ( " making a connection to 'normal' service %s \n " , service ) ) ;
return make_connection_snum ( snum , vuser ,
password ,
dev , status ) ;
}
1998-08-17 17:11:34 +04:00
/****************************************************************************
close a cnum
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void close_cnum ( connection_struct * conn , uint16 vuid )
{
DirCacheFlush ( SNUM ( conn ) ) ;
2001-10-19 00:15:12 +04:00
change_to_root_user ( ) ;
1998-08-17 17:11:34 +04:00
DEBUG ( IS_IPC ( conn ) ? 3 : 1 , ( " %s (%s) closed connection to service %s \n " ,
2002-08-17 19:27:10 +04:00
get_remote_machine_name ( ) , conn - > client_address ,
1998-08-17 17:11:34 +04:00
lp_servicename ( SNUM ( conn ) ) ) ) ;
2003-05-12 03:34:18 +04:00
/* Call VFS disconnect hook */
2003-05-14 14:59:01 +04:00
SMB_VFS_DISCONNECT ( conn ) ;
2000-02-03 08:17:25 +03:00
2002-01-14 22:34:28 +03:00
yield_connection ( conn , lp_servicename ( SNUM ( conn ) ) ) ;
1998-08-17 17:11:34 +04:00
file_close_conn ( conn ) ;
dptr_closecnum ( conn ) ;
/* execute any "postexec = " line */
if ( * lp_postexec ( SNUM ( conn ) ) & &
2001-10-19 00:15:12 +04:00
change_to_user ( conn , vuid ) ) {
1998-08-17 17:11:34 +04:00
pstring cmd ;
pstrcpy ( cmd , lp_postexec ( SNUM ( conn ) ) ) ;
2002-07-15 14:35:28 +04:00
standard_sub_conn ( conn , cmd , sizeof ( cmd ) ) ;
2001-04-13 23:12:06 +04:00
smbrun ( cmd , NULL ) ;
2001-10-19 00:15:12 +04:00
change_to_root_user ( ) ;
1998-08-17 17:11:34 +04:00
}
2001-10-19 00:15:12 +04:00
change_to_root_user ( ) ;
1998-08-17 17:11:34 +04:00
/* execute any "root postexec = " line */
if ( * lp_rootpostexec ( SNUM ( conn ) ) ) {
pstring cmd ;
pstrcpy ( cmd , lp_rootpostexec ( SNUM ( conn ) ) ) ;
2002-07-15 14:35:28 +04:00
standard_sub_conn ( conn , cmd , sizeof ( cmd ) ) ;
2001-04-13 23:12:06 +04:00
smbrun ( cmd , NULL ) ;
1998-08-17 17:11:34 +04:00
}
2002-07-15 14:35:28 +04:00
/* make sure we leave the directory available for unmount */
vfs_ChDir ( conn , " / " ) ;
1998-08-17 17:11:34 +04:00
conn_free ( conn ) ;
}