/* * :ts=4 * * Name: smb_abstraction.c * Description: Smb abstraction layer. * Author: Christian Starkjohann * Date: 1996-12-31 * Copyright: GNU-GPL * * Modified for use with AmigaOS by Olaf Barthel */ #include "smbfs.h" #include "errors.h" #include "quad_math.h" /*****************************************************************************/ #include #include #include /*****************************************************************************/ #define ATTR_CACHE_TIME 5 /* cache attributes for this time (seconds) */ #define DIR_CACHE_TIME 5 /* cache directories for this time (seconds) */ #define DIRCACHE_SIZE 170 #define DOS_PATHSEP '\\' /*****************************************************************************/ #include "smb_abstraction.h" /*****************************************************************************/ static void smba_cleanup_dircache(struct smba_server * server); static int smba_setup_dircache (struct smba_server * server,int cache_size, int * error_ptr); /*****************************************************************************/ #ifdef USE_SPLAY_TREE /* This is used by the splay tree functions to compare individual * key values. These key values are smba_file_t addresses. */ static int compare_files_by_address(const BYTE * a, const BYTE * b) { int result; if (a < b) result = -1; else if (a == b) result = 0; else result = 1; return(result); } #endif /* USE_SPLAY_TREE */ /*****************************************************************************/ static void init_open_file_list(smba_server_t *res) { NewList((struct List *)&res->open_files); #ifdef USE_SPLAY_TREE { splay_tree_init(&res->open_file_address_tree, (splay_key_compare_t)compare_files_by_address); splay_tree_init(&res->open_file_name_tree, (splay_key_compare_t)compare_names); res->open_file_name_tree.st_allow_duplicates = TRUE; } #endif /* USE_SPLAY_TREE */ } /*****************************************************************************/ static int smba_connect ( smba_connect_parameters_t * connect_parameters, struct sockaddr_in server_ip_addr, const char * tcp_service_name, int use_E, const char * workgroup_name, int cache_size, int max_transmit, int timeout, int opt_raw_smb, int opt_unicode, int opt_prefer_core_protocol, int opt_case_sensitive, int opt_session_setup_delay_unicode, int opt_write_behind, int opt_smb_request_write_threshold, int opt_smb_request_read_threshold, int opt_scatter_gather, int opt_tcp_no_delay, int opt_socket_receive_buffer_size, int opt_socket_send_buffer_size, int * error_ptr, int * smb_error_class_ptr, int * smb_error_ptr, smba_server_t ** smba_server_ptr) { smba_server_t *res; struct smb_mount_data data; char hostname[MAXHOSTNAMELEN], *s; struct servent * servent; int result = -1; (*smba_server_ptr) = NULL; res = malloc (sizeof(*res)); if(res == NULL) { report_error("Not enough memory."); (*error_ptr) = ENOMEM; goto error_occured; } memset (res, 0, sizeof(*res)); memset (&data, 0, sizeof (data)); memset (hostname, 0, sizeof (hostname)); /* Use raw SMB over TCP rather than NetBIOS. */ if(opt_raw_smb) res->server.raw_smb = TRUE; LOG(("use raw SMB = %s\n",opt_raw_smb ? "yes" : "no")); /* Timeout for send/receive operations in seconds. */ res->server.timeout = timeout; LOG(("send/receive/connect timeout = %ld seconds%s\n",timeout,timeout > 0 ? "" : " (= default timeout)")); /* Enable Unicode support if the server supports it, too. */ res->server.use_unicode = opt_unicode; LOG(("use Unicode = %s\n",opt_unicode ? "yes" : "no")); /* Prefer SMB core protocol commands to NT1 commands, if possible. */ res->server.prefer_core_protocol = opt_prefer_core_protocol; LOG(("prefer core protocol = %s\n",opt_prefer_core_protocol ? "yes" : "no")); /* Path names are case-sensitive. */ res->server.case_sensitive = opt_case_sensitive; LOG(("path names are case sensitive = %s\n",opt_case_sensitive ? "yes" : "no")); /* Delay the use of Unicode strings during session setup. */ res->server.session_setup_delay_unicode = opt_session_setup_delay_unicode; LOG(("delay use of unicode during session setup = %s\n",opt_session_setup_delay_unicode ? "yes" : "no")); /* Do not send SMB header and payload for write operations separately, * but in a single chunk if their combined size is smaller than * or equal to this threshold value. */ res->server.smb_write_threshold = opt_smb_request_write_threshold; LOG(("SMB write request threshold size = %ld bytes\n", res->server.smb_write_threshold)); /* Do not receive SMB header and payload for read operations separately, * but in a single chunk if their combined size is smaller than * or equal to this threshold value. */ res->server.smb_read_threshold = opt_smb_request_read_threshold; LOG(("SMB read request threshold size = %ld bytes\n", res->server.smb_read_threshold)); /* Use sendmsg() instead of send() where useful? */ res->server.scatter_gather = opt_scatter_gather; LOG(("use sendmsg() instead of send() where useful = %s\n",opt_scatter_gather ? "yes" : "no")); /* Disable the Nagle algorithm, causing send() to immediately * result in the data being transmitted? */ res->server.tcp_no_delay = opt_tcp_no_delay; /* Try to get the TCP/IP stack to use a specific * receive/transmit buffer size? */ res->server.socket_receive_buffer_size = opt_socket_receive_buffer_size; res->server.socket_send_buffer_size = opt_socket_send_buffer_size; /* Enable asynchronous SMB_COM_WRITE_RAW operations? */ res->server.write_behind = opt_write_behind; LOG(("use asynchronous SMB_COM_WRITE_RAW operations = %s\n",opt_write_behind ? "yes" : "no")); LOG(("cache size = %ld entries\n", cache_size)); if(smba_setup_dircache (res,cache_size,error_ptr) < 0) { report_error("Directory cache initialization failed (%ld, %s).",(*error_ptr),posix_strerror(*error_ptr)); goto error_occured; } strlcpy(data.workgroup_name,workgroup_name,sizeof(data.workgroup_name)); LOG(("workgroup name = '%s'\n",workgroup_name)); res->server.abstraction = res; if(gethostname (hostname, MAXHOSTNAMELEN) < 0) { report_error("Could not look up local host name (%ld, %s).",(*error_ptr),posix_strerror(*error_ptr)); goto error_occured; } /* Only retain the host name, drop any domain names following it. */ if ((s = strchr (hostname, '.')) != NULL) (*s) = '\0'; LOG(("local host name = '%s'\n",hostname)); LOG(("server ip address = %s\n",Inet_NtoA(server_ip_addr.sin_addr.s_addr))); data.addr = server_ip_addr; /* Override the default TCP service name or port * number which smbfs should connect to on the * server? */ if(tcp_service_name[0] != '\0') { /* Try to find a service whose name matches. */ servent = getservbyname((char *)tcp_service_name,"tcp"); if(servent != NULL) { data.addr.sin_port = servent->s_port; LOG(("using port number %ld\n",ntohs(data.addr.sin_port))); } else { long int n; char * str; /* Try again, this time converting what's * hopefully a number in the range * valid for a TCP service. */ n = strtol(tcp_service_name,&str,10); if(str != tcp_service_name && (*str) == '\0') { if(0 < n && n < 65536) { data.addr.sin_port = htons (n); LOG(("using port number %ld\n",n)); } else { report_error("Port number '%s' is out of range.", tcp_service_name); (*error_ptr) = EINVAL; goto error_occured; } } else { report_error("Invalid service '%s'.", tcp_service_name); (*error_ptr) = EINVAL; goto error_occured; } } } else if (res->server.raw_smb) { int port; servent = getservbyname("microsoft-ds","tcp"); if(servent != NULL) port = servent->s_port; else port = htons (445); LOG(("using port number %ld\n",ntohs(port))); data.addr.sin_port = port; } else { int port; servent = getservbyname("netbios-ssn","tcp"); if(servent != NULL) port = servent->s_port; else port = htons (139); LOG(("using port number %ld\n",ntohs(port))); data.addr.sin_port = port; } data.fd = socket (AF_INET, SOCK_STREAM, 0); if (data.fd < 0) { report_error("socket() call failed (%ld, %s).", errno, posix_strerror(errno)); (*error_ptr) = errno; goto error_occured; } strlcpy (data.service, connect_parameters->service, sizeof(data.service)); LOG(("service = '%s'\n",data.service)); string_toupper (data.service); strlcpy (data.username, connect_parameters->username, sizeof(data.username)); strlcpy (data.password, connect_parameters->password, sizeof(data.password)); LOG(("user name = '%s'\n",data.username)); data.given_max_xmit = max_transmit; LOG(("max transmit = %ld bytes\n",max_transmit)); strlcpy (data.server_name, connect_parameters->server_name, sizeof(data.server_name)); strlcpy (data.client_name, connect_parameters->client_name, sizeof(data.client_name)); LOG(("server name = '%s'\n",data.server_name)); LOG(("client name = '%s'\n",data.client_name)); if (data.server_name[0] == '\0') { if (!res->server.raw_smb && strlen (connect_parameters->server_ipname) > 16) { report_error("Server name '%s' is too long for NetBIOS (%ld characters are possible).",connect_parameters->server_ipname,16); (*error_ptr) = ENAMETOOLONG; goto error_occured; } strlcpy (data.server_name, connect_parameters->server_ipname, sizeof(data.server_name)); } string_toupper (data.server_name); if (data.client_name[0] == '\0') { if (!res->server.raw_smb && strlen (hostname) > 16) { report_error("Local host name '%s' is too long for NetBIOS (%ld characters are possible).", hostname, 16); (*error_ptr) = ENAMETOOLONG; goto error_occured; } strlcpy (data.client_name, hostname, sizeof(data.client_name)); string_toupper (data.client_name); } res->server.mount_data = data; init_open_file_list(res); if (smb_proc_connect (&res->server, error_ptr) < 0) goto error_occured; if (!use_E) res->supports_E_known = TRUE; (*smba_server_ptr) = res; res = NULL; result = 0; error_occured: if(res != NULL) { (*smb_error_class_ptr) = res->server.rcls; (*smb_error_ptr) = res->server.err; smba_cleanup_dircache (res); free (res); } return result; } /*****************************************************************************/ void smba_disconnect (smba_server_t * server) { if(server->server.mount_data.fd >= 0) CloseSocket (server->server.mount_data.fd); smba_cleanup_dircache(server); free (server); } /*****************************************************************************/ static int make_open (smba_file_t * f, int need_fid, int writable, int truncate_file, int * error_ptr) { smba_server_t *s; int result; if (!f->is_valid || (need_fid && !f->dirent.opened)) { ULONG now = get_current_time(); s = f->server; if (!f->is_valid || f->attr_time == 0 || (now > f->attr_time && now - f->attr_time > ATTR_CACHE_TIME)) { if (!f->server->server.prefer_core_protocol && f->server->server.protocol >= PROTOCOL_LANMAN2) { SHOWMSG("using the LAN Manager 2.0 getattr() variant"); result = smb_query_path_information (&s->server, f->dirent.complete_path, f->dirent.len, 0, &f->dirent, error_ptr); } else { SHOWMSG("using the legacy getattr() variant"); result = smb_proc_getattr_core (&s->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr); } if (result < 0) goto out; } if ((f->dirent.attr & SMB_FILE_ATTRIBUTE_DIRECTORY) == 0) /* a regular file */ { if (f->server->server.protocol >= PROTOCOL_LANMAN2) { if(need_fid) { if(!f->dirent.opened) { LOG (("opening file '%s'\n", escape_name(f->dirent.complete_path))); SHOWMSG("using the LAN Manager 2.0 open() variant"); result = smb_proc_open (&s->server, f->dirent.complete_path, f->dirent.len, writable, truncate_file, &f->dirent, error_ptr); if (result < 0) goto out; } else { LOG (("file '%s' is already open (fileid=0x%04lx)\n", escape_name(f->dirent.complete_path), f->dirent.fileid)); } } } else if (need_fid || !s->supports_E_known || s->supports_E) { if(!f->dirent.opened) { LOG (("opening file '%s'\n", escape_name(f->dirent.complete_path))); SHOWMSG("using the legacy open() variant"); result = smb_proc_open (&s->server, f->dirent.complete_path, f->dirent.len, writable, truncate_file, &f->dirent, error_ptr); if (result < 0) goto out; } else { LOG (("file '%s' is already open (fileid=0x%04lx)\n", escape_name(f->dirent.complete_path), f->dirent.fileid)); } if (s->supports_E || !s->supports_E_known) { if (smb_proc_getattrE (&s->server, &f->dirent, error_ptr) < 0) { if (!s->supports_E_known) { s->supports_E_known = TRUE; s->supports_E = FALSE; } /* ignore errors here */ } else { s->supports_E_known = TRUE; s->supports_E = TRUE; } } } } else { /* don't open directory, initialize directory cache */ if (f->dircache != NULL) { f->dircache->cache_for = NULL; f->dircache->len = 0; f->dircache = NULL; } } f->attr_time = get_current_time(); f->is_valid = TRUE; } result = 0; out: return result; } /*****************************************************************************/ static void add_smba_file(smba_server_t * s, smba_file_t *f) { AddTail ((struct List *)&s->open_files, (struct Node *)f); #ifdef USE_SPLAY_TREE { f->splay_address_node.sn_key = f; f->splay_address_node.sn_userdata = f; splay_tree_add(&s->open_file_address_tree, &f->splay_address_node); f->splay_name_node.sn_key = f->dirent.complete_path; f->splay_name_node.sn_userdata = f; splay_tree_add(&s->open_file_name_tree, &f->splay_name_node); } #endif /* USE_SPLAY_TREE */ } /*****************************************************************************/ static void remove_smba_file(smba_server_t * s, smba_file_t *f) { Remove((struct Node *)f); #ifdef USE_SPLAY_TREE { splay_tree_remove(&s->open_file_address_tree, NULL, (splay_key_t)f); splay_tree_remove(&s->open_file_name_tree, &f->splay_name_node, f->splay_name_node.sn_key); } #endif /* USE_SPLAY_TREE */ } /*****************************************************************************/ int smba_open ( smba_server_t * s, const char *name, int writable, int truncate_file, smba_file_t ** file, int * error_ptr) { smba_file_t *f; int result; (*file) = NULL; f = malloc (sizeof(*f)); if(f == NULL) { (*error_ptr) = ENOMEM; result = -1; goto out; } memset(f, 0, sizeof(*f)); f->dirent.complete_path = (char *)name; f->dirent.len = strlen (name); f->server = s; LOG(("open '%s' (writable=%s, truncate_file=%s)\n", escape_name(name), writable ? "yes" : "no", truncate_file ? "yes" : "no")); result = make_open (f, open_dont_need_fid, writable, truncate_file, error_ptr); if (result < 0) { LOG(("open failed\n")); goto out; } add_smba_file(s, f); s->num_open_files++; LOG(("file has been 'opened', number of open files = %ld\n",s->num_open_files)); (*file) = f; f = NULL; out: if (f != NULL) free (f); return result; } /*****************************************************************************/ static int write_attr (smba_file_t * f, int * error_ptr) { int result; LOG (("file '%s'\n", escape_name(f->dirent.complete_path))); LOG(("mtime = %lu\n",f->dirent.mtime)); if(!f->server->server.prefer_core_protocol && f->server->server.protocol >= PROTOCOL_LANMAN2) { /* Copy these, because make_open() may overwrite them. */ time_t mtime = f->dirent.mtime; time_t ctime = f->dirent.ctime; dword attr = f->dirent.attr; LOG(("mtime = %lu\n",f->dirent.mtime)); LOG(("ctime = %lu\n",f->dirent.ctime)); SHOWMSG("using the LAN Manager 2.0 open() variant"); result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr); if (result < 0) goto out; LOG(("mtime = %lu\n",f->dirent.mtime)); LOG(("ctime = %lu\n",f->dirent.ctime)); f->dirent.mtime = mtime; f->dirent.ctime = ctime; f->dirent.attr = attr; result = smb_set_file_information (&f->server->server, &f->dirent, NULL, error_ptr); if (result < 0) goto out; f->attr_dirty = FALSE; } else { /* Copy these, because make_open() may overwrite them. */ time_t mtime = f->dirent.mtime; time_t ctime = f->dirent.ctime; dword attr = f->dirent.attr; LOG(("mtime = %lu\n",f->dirent.mtime)); LOG(("ctime = %lu\n",f->dirent.ctime)); SHOWMSG("using the legacy open() variant"); result = make_open (f, open_dont_need_fid, open_writable, open_dont_truncate, error_ptr); if (result < 0) goto out; LOG(("mtime = %lu\n",f->dirent.mtime)); LOG(("ctime = %lu\n",f->dirent.ctime)); f->dirent.mtime = mtime; f->dirent.ctime = ctime; f->dirent.attr = attr; /* If the attributes need to be updated, we cannot use smb_proc_setattrE(), * because that only updates the "time of last write access", but not the * attributes. */ if(f->attr_dirty) { /* Update the attributes and the "time of last write access". */ result = smb_proc_setattr_core (&f->server->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr); if(result < 0) goto out; /* Now deal with the creation/access/modification times. */ if (f->dirent.opened && f->server->supports_E) { result = smb_proc_setattrE (&f->server->server, f->dirent.fileid, &f->dirent, error_ptr); if(result < 0) goto out; } f->attr_dirty = FALSE; } /* Update the times. */ else { if (f->dirent.opened && f->server->supports_E) result = smb_proc_setattrE (&f->server->server, f->dirent.fileid, &f->dirent, error_ptr); else result = smb_proc_setattr_core (&f->server->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr); } } out: if (result < 0) f->attr_time = 0; return result; } /*****************************************************************************/ static int file_is_valid(smba_server_t * s, const smba_file_t * f) { int is_valid; #ifndef USE_SPLAY_TREE { const smba_file_t * file; is_valid = FALSE; for (file = (smba_file_t *)s->open_files.mlh_Head ; file->node.mln_Succ != NULL ; file = (smba_file_t *)file->node.mln_Succ) { if (file == f) { is_valid = TRUE; break; } } } #else { is_valid = (splay_tree_find(&s->open_file_address_tree, (splay_key_t)f) != NULL); } #endif /* USE_SPLAY_TREE */ return(is_valid); } /*****************************************************************************/ void smba_close (smba_server_t * s, smba_file_t * f) { if (file_is_valid(s, f)) { int ignored_error; remove_smba_file(s, f); if(f->attr_dirty) write_attr(f, &ignored_error); if (f->dirent.opened) { LOG (("closing file '%s' (fileid=0x%04lx)\n", escape_name(f->dirent.complete_path), f->dirent.fileid)); /* Don't change the modification time. */ smb_proc_close (&f->server->server, f->dirent.fileid, -1, &ignored_error); } if (f->dircache != NULL) { f->dircache->cache_for = NULL; f->dircache->len = 0; f->dircache = NULL; } f->server->num_open_files--; LOG(("file closed, number of open files = %ld\n",f->server->num_open_files)); free (f); } else { LOG(("file seems to be invalid\n")); } } /*****************************************************************************/ int smba_read (smba_file_t * f, char *data, long len, const QUAD * const offset, int * error_ptr) { int max_receive = f->server->server.max_recv; int num_bytes_read = 0; int result; result = make_open (f, open_need_fid, open_read_only, open_dont_truncate, error_ptr); if (result < 0) goto out; LOG(("read %ld bytes from offset %ld\n",len,offset)); /* SMB_COM_READ_ANDX supported? */ if (f->server->server.protocol >= PROTOCOL_LANMAN1 && !f->server->server.prefer_core_protocol) { QUAD position_quad = (*offset); int max_readx_size; int count; /* Calculate the maximum number of bytes that could be transferred with * a single SMBreadX packet... * * The message header accounts for * 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+ * 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid) * = 32 bytes * * The parameters of a SMB_COM_READ_ANDX response account for * 1(wordcount)+1(andxcommand)+1(andxreserved)+2(andxoffset)+ * 2(available)+2(datacompactionmode)+2(reserved)+2(datalength)+ * 2(dataoffset)+10(reserved2) * = 25 bytes * * The data part of a SMB_COM_READ_ANDX response account for * 2(bytecount)+1(pad) = 3 bytes, * not including the actual payload * * This leaves 'max_receive' - 60 for the payload. */ max_readx_size = f->server->server.max_buffer_size - 60; if((f->server->server.capabilities & CAP_LARGE_READX) != 0) max_readx_size = 65535; /* ZZZ SMB_COM_READ_ANDX uses the packet buffer to receive * the data, which is why there is another limit to how * much data can be received. */ if(max_readx_size > f->server->server.transmit_buffer_size - 62) max_readx_size = f->server->server.transmit_buffer_size - 62; LOG (("len = %ld, max_readx_size = %ld\n", len, max_readx_size)); do { count = min(len, max_readx_size); result = smb_proc_readx (&f->server->server, &f->dirent, &position_quad, count, data, error_ptr); if (result < 0) goto out; num_bytes_read += result; len -= result; add_64_plus_32_to_64(&position_quad,result,&position_quad); data += result; if(result < count) { LOG(("read returned fewer characters than expected (%ld < %ld)\n",result,count)); break; } } while (len > 0); } /* SMB_COM_READ_RAW and SMB_COM_WRITE_RAW supported? */ else if ((f->server->server.capabilities & CAP_RAW_MODE) != 0) { int max_raw_size = f->server->server.max_raw_size; QUAD position_quad = (*offset); int n; do { /* SMB_COM_READ_RAW can only read up to 65535 bytes. */ n = min(len, 65535); /* The maximum number of bytes to be read in raw * mode may be limited, too. */ if(n > max_raw_size) n = max_raw_size; /* Limit how much data we are prepared to receive? */ if(n > max_receive) n = max_receive; result = smb_proc_read_raw (&f->server->server, &f->dirent, &position_quad, n, data, error_ptr); if(result <= 0) { LOG(("!!! wanted to read %ld bytes, got %ld\n",n,result)); break; } num_bytes_read += result; len -= result; add_64_plus_32_to_64(&position_quad,result,&position_quad); data += result; if(result < n) { LOG(("read returned fewer characters than expected (%ld < %ld)\n",result,n)); break; } } while(len > 0); } else { int max_size_smb_com_read; off_t position = offset->Low; int count; /* Calculate the maximum number of bytes that could be transferred with * a single SMBread packet... * * 'max_buffer_size' is the maximum size of a complete SMB message * including the message header, the parameter and data blocks. * * The message header accounts for * 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+ * 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid) * = 32 bytes * * The parameters of a SMB_COM_READ response account for * 1(wordcount)+2(countofbytesreturned)+8(reserved) * = 11 bytes * * The data part of a SMB_COM_READ response account for * 2(bytecount)+1(bufferformat)+2(countofbytesread) = 5 bytes, * not including the actual payload * * This leaves 'max_buffer_size' - 48 for the payload. */ /*max_size_smb_com_read = f->server->server.max_buffer_size - SMB_HEADER_LEN - 5 * 2 - 5;*/ max_size_smb_com_read = f->server->server.max_buffer_size - 48; /* ZZZ SMB_COM_READ uses the packet buffer to receive * the data, which is why there is another limit to how * much data can be received. */ if(max_size_smb_com_read > f->server->server.transmit_buffer_size - 48) max_size_smb_com_read = f->server->server.transmit_buffer_size - 48; do { /* SMB_COM_READ can read only up to 65535 bytes anyway. */ count = min(len, 65535); if(count == 0) break; if(count > max_size_smb_com_read) count = max_size_smb_com_read; /* Limit how much data we are prepared to receive? */ if(count > max_receive) count = max_receive; result = smb_proc_read (&f->server->server, &f->dirent, position, count, data, error_ptr); if (result < 0) goto out; num_bytes_read += result; len -= result; position += result; data += result; if(result < count) { LOG(("read returned fewer characters than expected (%ld < %ld)\n",result,count)); break; } } while (len > 0); } result = num_bytes_read; out: return result; } /*****************************************************************************/ int smba_write (smba_file_t * f, const char *data, long len, const QUAD * const offset, int * error_ptr) { int num_bytes_written = 0; int max_buffer_size; int result; result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr); if (result < 0) goto out; SHOWVALUE(f->server->server.max_buffer_size); max_buffer_size = f->server->server.max_buffer_size; /* SMB_COM_WRITE_ANDX supported? */ if (f->server->server.protocol >= PROTOCOL_LANMAN1 && !f->server->server.prefer_core_protocol) { QUAD position_quad = (*offset); int max_writex_size; int n; /* Calculate the maximum number of bytes that could be transferred with * a single SMB_COM_WRITE_ANDX packet... * * 'max_buffer_size' is the maximum size of a complete SMB message * including the message header, the parameter and data blocks. * * The message header accounts for * 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+ * 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid) * = 32 bytes * * The parameters of a SMB_COM_WRITE_ANDX command account for * 1(word_count)+1(and_x_command)+1(and_x_reserved)+2(and_x_offset)+ * 2(fid)+4(offset)+4(timeout)+2(write_mode)+2(remaining)+2(reserved)+ * 2(data_length)+2(data_offset)+4(offset_high) * = 29 bytes * * The data part of a SMB_COM_WRITE_ANDX command account for * 2(byte_count)+1(pad) = 3 bytes, not including * the actual payload * * This leaves 'max_buffer_size' - 64 for the payload. */ if(f->server->server.protocol >= PROTOCOL_NT1) max_writex_size = max_buffer_size - 64; else max_writex_size = max_buffer_size - 60; if((f->server->server.capabilities & CAP_LARGE_WRITEX) != 0) max_writex_size = 65535; LOG (("len = %ld, max_writex_size = %ld\n", len, max_writex_size)); do { n = min(len, max_writex_size); ASSERT( n > 0 ); LOG(("writing %ld bytes; offset=%lu, len=%ld\n", n, offset, len)); result = smb_proc_writex(&f->server->server, &f->dirent, &position_quad, n, data, error_ptr); if(result < 0) goto out; LOG(("number of bytes written = %ld\n", result)); data += result; add_64_plus_32_to_64(&position_quad,result,&position_quad); len -= result; num_bytes_written += result; } while(len > 0); } else if ((f->server->server.capabilities & CAP_RAW_MODE) != 0) { int max_raw_size = f->server->server.max_raw_size; int max_size_smb_com_write_raw; QUAD position_quad = (*offset); int n; /* Try to send the maximum number of bytes with the two SMBwritebraw packets. * This is how it breaks down: * * The message header accounts for * 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+ * 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid) * = 32 bytes * * The parameters of a SMB_COM_WRITE_RAW command account for * 1(wordcount)+2(fid)+2(countofbytes)+2(reserved1)+4(offset)+ * 4(timeout)+2(writemode)+4(reserved2)+2(datalength)+ * 2(dataoffset)+4(offset high) = 29 bytes * * The data part of a SMB_COM_WRITE_RAW command accounts for * 2(bytecount) = 2 bytes * * This leaves 'max_buffer_size' - 63 for the payload. */ max_size_smb_com_write_raw = max(max_buffer_size, max_raw_size) - 63; /* SMB_COM_WRITE_RAW cannot transmit more than 65535 bytes. */ if(max_size_smb_com_write_raw > 65535) max_size_smb_com_write_raw = 65535; LOG (("len = %ld, max_size_smb_com_write_raw = %ld\n", len, max_size_smb_com_write_raw)); do { n = min(len, max_size_smb_com_write_raw); ASSERT( n > 0 ); if(n > max_raw_size) n = max_raw_size; ASSERT( n <= 65535 ); result = smb_proc_write_raw (&f->server->server, &f->dirent, &position_quad, n, data, error_ptr); if(result < 0) goto out; data += result; add_64_plus_32_to_64(&position_quad,result,&position_quad); len -= result; num_bytes_written += result; } while(len > 0); } else { int max_size_smb_com_write, count; off_t position = offset->Low; /* Calculate the maximum number of bytes that could be transferred with * a single SMBwrite packet... * * 'max_buffer_size' is the maximum size of a complete SMB message * including the message header, the parameter and data blocks. * * The message header accounts for * 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+ * 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid) * = 32 bytes * * The parameters of a SMB_COM_WRITE command account for * 1(wordcount)+2(fid)+2(countofbytestowrite)+4(writeoffsetinbytes)+ * 2(estimateofremainingbytestobewritten) = 11 bytes * * The data part of a SMB_COM_WRITE command account for * 2(bytecount)+1(bufferformat)+2(datalength) = 5 bytes, not including * the actual payload * * This leaves 'max_buffer_size' - 48 for the payload. */ /*max_size_smb_com_write = f->server->server.max_buffer_size - (SMB_HEADER_LEN + 5 * sizeof (word) + 5) - 4;*/ max_size_smb_com_write = max_buffer_size - 48; /* SMB_COM_WRITE cannot transmit more than 65535 bytes. */ if(max_size_smb_com_write > 65535) max_size_smb_com_write = 65535; LOG (("len = %ld, max_size_smb_com_write = %ld\n", len, max_size_smb_com_write)); do { count = min(len, max_size_smb_com_write); if(count == 0) break; ASSERT( count <= 65535 ); result = smb_proc_write (&f->server->server, &f->dirent, position, count, data, error_ptr); if (result < 0) goto out; len -= result; position += result; data += result; num_bytes_written += result; } while (len > 0); } LOG(("num_bytes_written=%ld\n", num_bytes_written)); result = num_bytes_written; out: if (result < 0) f->attr_time = 0; /* Even if one write access failed, we may have succeeded * at writing some data. Hence we update the cached file * size here and take note of the change made. */ if(num_bytes_written > 0) { QUAD size_quad; QUAD new_position_quad; /* This file was modified. */ f->dirent.mtime = get_current_time(); size_quad.Low = f->dirent.size_low; size_quad.High = f->dirent.size_high; add_64_plus_32_to_64(offset,num_bytes_written,&new_position_quad); if(compare_64_to_64(&new_position_quad,&size_quad) > 0) { f->dirent.size_low = new_position_quad.Low; f->dirent.size_high = new_position_quad.High; } } return result; } /*****************************************************************************/ /* perform a single record-lock */ int smba_lockrec (smba_file_t *f, long offset, long len, long mode, int unlocked, long timeout, int * error_ptr) { struct smb_lkrng *rec_lock = NULL; int result; result = make_open (f, open_need_fid, open_read_only, open_dont_truncate, error_ptr); if(result < 0) goto out; if (unlocked) mode |= 2; rec_lock = malloc (sizeof (*rec_lock)); if (rec_lock == NULL) { (*error_ptr) = ENOMEM; result = -1; goto out; } memset(rec_lock, 0, sizeof(*rec_lock)); rec_lock->offset = offset, rec_lock->len = len; result = smb_proc_lockingX (&f->server->server, &f->dirent, rec_lock, 1, mode, timeout, error_ptr); out: if(rec_lock != NULL) free (rec_lock); return(result); } /*****************************************************************************/ int smba_getattr (smba_file_t * f, smba_stat_t * data, int * error_ptr) { const struct smb_dirent * dirent; int result; ULONG now; result = make_open (f, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr); if (result < 0) goto out; now = get_current_time(); if (f->attr_time == 0 || (now > f->attr_time && now - f->attr_time > ATTR_CACHE_TIME)) { LOG (("file '%s'\n", escape_name(f->dirent.complete_path))); if (!f->server->server.prefer_core_protocol && f->server->server.protocol >= PROTOCOL_LANMAN2) { SHOWMSG("using the LAN Manager 2.0 path query variant"); if (f->dirent.opened) result = smb_query_path_information (&f->server->server, NULL, 0, f->dirent.fileid, &f->dirent, error_ptr); else result = smb_query_path_information (&f->server->server, f->dirent.complete_path, f->dirent.len, 0, &f->dirent, error_ptr); } else { SHOWMSG("using the legacy path query variant"); if (f->dirent.opened && f->server->supports_E) { LOG(("using smb_proc_getattrE\n")); result = smb_proc_getattrE (&f->server->server, &f->dirent, error_ptr); } else { LOG(("using smb_proc_getattr_core\n")); result = smb_proc_getattr_core (&f->server->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr); } } if (result < 0) goto out; f->attr_time = now; } dirent = &f->dirent; data->is_dir = (dirent->attr & SMB_FILE_ATTRIBUTE_DIRECTORY) != 0; data->is_read_only = (dirent->attr & SMB_FILE_ATTRIBUTE_READONLY) != 0; data->is_hidden = (dirent->attr & SMB_FILE_ATTRIBUTE_HIDDEN) != 0; data->is_system = (dirent->attr & SMB_FILE_ATTRIBUTE_SYSTEM) != 0; data->was_changed_since_last_archive = (dirent->attr & SMB_FILE_ATTRIBUTE_ARCHIVE) != 0; data->size_low = dirent->size_low; data->size_high = dirent->size_high; data->atime = dirent->atime; data->ctime = dirent->ctime; data->mtime = dirent->mtime; out: return(result); } /*****************************************************************************/ int smba_setattr (smba_file_t * f, const smba_stat_t * st, const QUAD * const size, int * error_ptr) { BOOL times_changed = FALSE; int result = 0; dword attrs; if (st != NULL) { if (st->atime != 0 && st->atime != (time_t)-1 && f->dirent.atime != st->atime) { LOG(("atime changed to %lu\n",st->atime)); f->dirent.atime = st->atime; times_changed = TRUE; } if (st->ctime != 0 && st->ctime != (time_t)-1 && f->dirent.ctime != st->ctime) { LOG(("ctime changed to %lu\n",st->ctime)); f->dirent.ctime = st->ctime; times_changed = TRUE; } if (st->mtime != 0 && st->mtime != (time_t)-1 && f->dirent.mtime != st->mtime) { LOG(("mtime changed to %lu\n",st->mtime)); f->dirent.mtime = st->mtime; times_changed = TRUE; } attrs = f->dirent.attr; if (st->is_read_only) attrs |= SMB_FILE_ATTRIBUTE_READONLY; else attrs &= ~SMB_FILE_ATTRIBUTE_READONLY; if (st->was_changed_since_last_archive) attrs |= SMB_FILE_ATTRIBUTE_ARCHIVE; else attrs &= ~SMB_FILE_ATTRIBUTE_ARCHIVE; if(f->dirent.attr != attrs) { f->dirent.attr = attrs; f->attr_dirty = TRUE; } if(f->attr_dirty || times_changed) { result = write_attr (f, error_ptr); if (result < 0) goto out; } } if (size != NULL && (size->Low != f->dirent.size_low || size->High != f->dirent.size_high)) { result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr); if(result < 0) goto out; if(!f->server->server.prefer_core_protocol && f->server->server.protocol >= PROTOCOL_LANMAN2) { SHOWMSG("using the LAN Manager 2.0 trunc variant"); result = smb_set_file_information (&f->server->server, &f->dirent, size, error_ptr); } else { SHOWMSG("using the legacy trunc variant (which cannot truncate files)"); result = smb_proc_trunc (&f->server->server, &f->dirent, size->Low, error_ptr); } if(result < 0) goto out; f->dirent.size_low = size->Low; f->dirent.size_high = size->High; } out: return(result); } /*****************************************************************************/ int smba_readdir (smba_file_t * f, int offs, void *callback_data, smba_callback_t callback, int * error_ptr) { const struct smb_dirent * dirent; int cache_index, o, eof, count = 0; int num_entries; smba_stat_t data; int result; ULONG now; result = make_open (f, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr); if (result < 0) goto out; now = get_current_time(); if (f->dircache == NULL) /* get a cache */ { dircache_t * dircache = f->server->dircache; if (dircache->cache_for != NULL) dircache->cache_for->dircache = NULL; /* steal it */ dircache->eof = dircache->len = dircache->base = 0; dircache->cache_for = f; f->dircache = dircache; LOG (("stealing cache\n")); } else { if (now > f->dircache->created_at && now - f->dircache->created_at >= DIR_CACHE_TIME) { f->dircache->eof = f->dircache->len = f->dircache->base = 0; LOG (("cache outdated\n")); } } for (cache_index = offs ; ; cache_index++) { if (cache_index < f->dircache->base /* fill cache if necessary */ || cache_index >= f->dircache->base + f->dircache->len) { if (cache_index >= f->dircache->base + f->dircache->len && f->dircache->eof) break; /* nothing more to read */ LOG (("cachefill for '%s'\n", escape_name(f->dirent.complete_path))); LOG (("\tbase was: %ld, len was: %ld, newbase=%ld\n", f->dircache->base, f->dircache->len, cache_index)); f->dircache->len = 0; f->dircache->base = cache_index; num_entries = smb_proc_readdir (&f->server->server, f->dirent.complete_path, cache_index, f->dircache->cache_size, f->dircache->cache, error_ptr); /* We stop on error, or if the directory is empty. */ if (num_entries <= 0) { result = num_entries; goto out; } /* Avoid some hits if restart/retry occured. Should fix the real root * of this problem really, but I am not bored enough atm. -Piru */ if (f->dircache == NULL) { LOG (("lost dircache due to an error, bailing out!\n")); (*error_ptr) = ENOMEM; result = -1; goto out; } f->dircache->len = num_entries; f->dircache->eof = (num_entries < f->dircache->cache_size); f->dircache->created_at = now; LOG (("cachefill with %ld entries\n", num_entries)); } o = cache_index - f->dircache->base; eof = (o >= (f->dircache->len - 1) && f->dircache->eof); count++; dirent = &f->dircache->cache[o]; LOG (("delivering '%s', cache_index=%ld, eof=%ld\n", escape_name(dirent->complete_path), cache_index, eof)); data.is_dir = (dirent->attr & SMB_FILE_ATTRIBUTE_DIRECTORY) != 0; data.is_read_only = (dirent->attr & SMB_FILE_ATTRIBUTE_READONLY) != 0; data.is_hidden = (dirent->attr & SMB_FILE_ATTRIBUTE_HIDDEN) != 0; data.is_system = (dirent->attr & SMB_FILE_ATTRIBUTE_SYSTEM) != 0; data.was_changed_since_last_archive = (dirent->attr & SMB_FILE_ATTRIBUTE_ARCHIVE) != 0; data.size_low = dirent->size_low; data.size_high = dirent->size_high; data.atime = dirent->atime; data.ctime = dirent->ctime; data.mtime = dirent->mtime; if ((*callback) (callback_data, cache_index, cache_index + 1, dirent->complete_path, eof, &data)) break; } result = count; out: return result; } /*****************************************************************************/ static void invalidate_dircache (struct smba_server * server) { dircache_t * dircache = server->dircache; ENTER(); if(dircache->cache_for != NULL) LOG(("dircache->cache_for->dirent.complete_path = '%s'\n", escape_name(dircache->cache_for->dirent.complete_path))); else SHOWMSG("-- directory cache is empty --"); dircache->eof = dircache->len = dircache->base = 0; if(dircache->cache_for != NULL) { dircache->cache_for->dircache = NULL; dircache->cache_for = NULL; } LEAVE(); } /*****************************************************************************/ static char * allocate_path_name(const smba_file_t * dir, const char *name, size_t * path_name_len_ptr) { size_t dir_len = dir->dirent.len; char * path; size_t len; len = strlen(name); path = malloc (dir_len + 1 + len + 1); if(path != NULL) { memcpy (path, dir->dirent.complete_path, dir_len); path[dir_len++] = DOS_PATHSEP; memcpy(&path[dir_len], name, len+1); /* length includes terminating NUL character */ ASSERT( path_name_len_ptr != NULL ); (*path_name_len_ptr) = dir_len + len; } return(path); } /*****************************************************************************/ int smba_create (smba_file_t * dir, const char *name, int truncate, int * error_ptr) { struct smb_dirent entry; int ignored_error; char *path = NULL; size_t path_len = 0; int result; result = make_open (dir, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr); if (result < 0) goto out; memset (&entry, 0, sizeof (entry)); entry.atime = entry.mtime = entry.ctime = get_current_time(); path = allocate_path_name(dir, name, &path_len); if(path == NULL) { (*error_ptr) = ENOMEM; result = -1; goto out; } if (!dir->server->server.prefer_core_protocol && dir->server->server.protocol >= PROTOCOL_LANMAN2) { SHOWMSG("using the LAN Manager 2.0 creat variant"); result = smb_proc_open (&dir->server->server, path, path_len, open_writable, truncate, &entry, error_ptr); if(result < 0) goto out; } else { SHOWMSG("using the legacy creat variant"); result = smb_proc_create (&dir->server->server, path, path_len, &entry, error_ptr); if(result < 0) goto out; } /* Close the file again, we don't really need it right now. * Don't change the modification time. */ smb_proc_close(&dir->server->server,entry.fileid,-1,&ignored_error); out: if(path != NULL) free (path); return(result); } /*****************************************************************************/ int smba_mkdir (smba_file_t * dir, const char *name, int * error_ptr) { char *path = NULL; size_t path_len = 0; int result; result = make_open (dir, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr); if (result < 0) goto out; path = allocate_path_name(dir, name, &path_len); if(path == NULL) { (*error_ptr) = ENOMEM; result = -1; goto out; } result = smb_proc_mkdir (&dir->server->server, path, path_len, error_ptr); if(result < 0) goto out; out: if(path != NULL) free (path); return(result); } /*****************************************************************************/ /* Mark a file/directory as no longer valid, closing it if necessary. */ static int invalidate_smba_file(smba_server_t * s, smba_file_t *f, const char *path, int * error_ptr) { int result = 0; if (f->dirent.opened) { /* Don't change the modification time. */ result = smb_proc_close (&s->server, f->dirent.fileid, -1, error_ptr); if(result < 0) { LOG(("closing '%s' with file id %ld failed\n", escape_name(path), f->dirent.fileid)); goto out; } f->dirent.opened = FALSE; } f->is_valid = FALSE; out: return(result); } /*****************************************************************************/ static int close_path (smba_server_t * s, const char *path, int * error_ptr) { int result = 0; smba_file_t *p; #ifndef USE_SPLAY_TREE { for (p = (smba_file_t *)s->open_files.mlh_Head; p->node.mln_Succ != NULL; p = (smba_file_t *)p->node.mln_Succ) { if (p->is_valid && compare_names(p->dirent.complete_path, path) == SAME) { result = invalidate_smba_file(s, p, path, error_ptr); if(result < 0) break; } } } #else { struct splay_node * sn; /* Find all files which match the same path name. */ sn = splay_tree_find(&s->open_file_name_tree, (splay_key_t)path); if(sn != NULL) { /* Walk through all the files, marking them as no longer * valid and closing them, if necessary. Note that we * do not remove them from the list of open files, we just * mark them for reopening later, if needed. */ for((void)NULL ; sn != NULL ; sn = sn->sn_next) { p = sn->sn_userdata; if (p->is_valid) { result = invalidate_smba_file(s, p, path, error_ptr); if(result < 0) break; } } } } #endif /* USE_SPLAY_TREE */ return(result); } /*****************************************************************************/ int smba_remove (smba_server_t * s, const char *path, int * error_ptr) { int result; result = close_path (s, path, error_ptr); if(result < 0) goto out; result = smb_proc_unlink (&s->server, path, strlen (path), error_ptr); if(result < 0) goto out; out: return result; } /*****************************************************************************/ int smba_rmdir (smba_server_t * s, const char *path, int * error_ptr) { int result; result = close_path (s, path, error_ptr); if(result < 0) goto out; result = smb_proc_rmdir (&s->server, path, strlen (path), error_ptr); if(result < 0) goto out; out: return result; } /*****************************************************************************/ int smba_rename (smba_server_t * s, const char *from, const char *to, int * error_ptr) { int result; result = close_path (s, from, error_ptr); if(result < 0) goto out; result = smb_proc_mv (&s->server, from, strlen (from), to, strlen (to), error_ptr); if(result < 0) goto out; out: return(result); } /*****************************************************************************/ int smba_statfs (smba_server_t * s, long *bsize, long *blocks, long *bfree, int * error_ptr) { struct smb_dskattr dskattr; int result; result = smb_proc_dskattr (&s->server, &dskattr, error_ptr); if (result < 0) goto out; (*bsize) = dskattr.blocksize * dskattr.allocblocks; (*blocks) = dskattr.total; (*bfree) = dskattr.free; out: return(result); } /*****************************************************************************/ void smb_invalidate_all_inodes (struct smb_server *server) { smba_file_t *f; ENTER(); invalidate_dircache (server->abstraction); for (f = (smba_file_t *)server->abstraction->open_files.mlh_Head; f->node.mln_Succ != NULL; f = (smba_file_t *)f->node.mln_Succ) { f->dirent.opened = FALSE; f->is_valid = FALSE; } LEAVE(); } /*****************************************************************************/ static void free_dircache(dircache_t * the_dircache) { int i; for (i = 0; i < the_dircache->cache_size; i++) { if(the_dircache->cache[i].complete_path != NULL) free(the_dircache->cache[i].complete_path); } free(the_dircache); } static void smba_cleanup_dircache(struct smba_server * server) { dircache_t * the_dircache; the_dircache = server->dircache; if(the_dircache != NULL) { free_dircache(the_dircache); server->dircache = NULL; } } static int smba_setup_dircache (struct smba_server * server,int cache_size, int * error_ptr) { const int complete_path_size = SMB_MAXNAMELEN + 1; dircache_t * the_dircache; int result = -1; int i; the_dircache = malloc(sizeof(*the_dircache) + (cache_size-1) * sizeof(the_dircache->cache)); if(the_dircache == NULL) { (*error_ptr) = ENOMEM; goto out; } memset(the_dircache, 0, sizeof(*the_dircache)); the_dircache->cache_size = cache_size; for (i = 0; i < the_dircache->cache_size; i++) { the_dircache->cache[i].complete_path = malloc (complete_path_size); if(the_dircache->cache[i].complete_path == NULL) { (*error_ptr) = ENOMEM; goto out; } the_dircache->cache[i].complete_path_size = complete_path_size; } server->dircache = the_dircache; the_dircache = NULL; result = 0; out: if(the_dircache != NULL) free_dircache(the_dircache); return(result); } /*****************************************************************************/ static int extract_service ( const char * service, char * server, size_t server_size, char * tcp_service_name, size_t tcp_service_name_size, char * share, size_t share_size, int * error_ptr) { char * share_start; char * root_start; char * complete_service; char * service_copy = NULL; char * service_name; int result = -1; if (strlen (service) < 4) { report_error("Service name '%s' is too short.",service); (*error_ptr) = EINVAL; goto out; } if (service[0] != '/') { report_error("Service name '%s' must begin with '/'.",service); (*error_ptr) = EINVAL; goto out; } service_copy = malloc(strlen(service)+1); if(service_copy == NULL) { report_error("Not enough memory."); (*error_ptr) = ENOMEM; goto out; } strcpy (service_copy, service); complete_service = service_copy; while (complete_service[0] == '/') complete_service++; share_start = strchr (complete_service, '/'); if (share_start == NULL) { report_error("Share name '%s' must contain a '/'.",complete_service); (*error_ptr) = EINVAL; goto out; } (*share_start++) = '\0'; root_start = strchr (share_start, '/'); if (root_start != NULL) (*root_start) = '\0'; /* Check if there's a port number encoded in the service name. */ service_name = strchr(complete_service,':'); if(service_name != NULL) { int len; /* Cut off the TCP service name, but remember where * the name starts. */ (*service_name++) = '\0'; /* Skip leading and trailing blank spaces, just to be safe. */ while((*service_name) == ' ') service_name++; len = strlen(service_name); while(len > 0 && (*service_name) == ' ') len--; service_name[len] = '\0'; if(len > tcp_service_name_size) { report_error("TCP service name/port number is too long in '%s' (%ld characters are possible).",service_name,tcp_service_name_size); (*error_ptr) = EINVAL; goto out; } } if (strlen (complete_service) > server_size) { report_error("Server name is too long in '%s' (%ld characters are possible).",service,server_size); (*error_ptr) = ENAMETOOLONG; goto out; } if (strlen (share_start) > share_size) { report_error("Share name is too long in '%s' (%ld characters are possible).",service,share_size); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy (server, complete_service, server_size); strlcpy (share, share_start, share_size); strlcpy (tcp_service_name, service_name != NULL ? service_name : "", tcp_service_name_size); result = 0; out: if(service_copy != NULL) free(service_copy); return(result); } int smba_start( const char * service, const char * opt_workgroup, const char * opt_username, const char * opt_password, const char * opt_clientname, const char * opt_servername, int opt_cachesize, int opt_max_transmit, int opt_timeout, int opt_raw_smb, int opt_unicode, int opt_prefer_core_protocol, int opt_case_sensitive, int opt_session_setup_delay_unicode, int opt_write_behind, int opt_smb_request_write_threshold, int opt_smb_request_read_threshold, int opt_scatter_gather, int opt_tcp_no_delay, int opt_socket_receive_buffer_size, int opt_socket_send_buffer_size, int * error_ptr, int * smb_error_class_ptr, int * smb_error_ptr, smba_connect_parameters_t * smba_connect_par, smba_server_t ** smba_server_ptr) { smba_connect_parameters_t par; smba_server_t *the_server = NULL; int i; struct hostent *h; int use_extended = 0; char server_name[17], client_name[17]; /* Maximum length appears to be 16 characters for NetBIOS */ char username[64], password[64]; char workgroup[64]; /* Maximum length appears to be 15 characters */ char server[64], share[256], tcp_service_name[40]; struct sockaddr_in server_ip_address; int result = -1; ASSERT( error_ptr != NULL ); ASSERT( smb_error_class_ptr != NULL ); ASSERT( smb_error_ptr != NULL ); (*error_ptr) = (*smb_error_class_ptr) = (*smb_error_ptr) = 0; (*smba_server_ptr) = NULL; (*username) = (*password) = (*server_name) = (*client_name) = '\0'; if(extract_service (service, server, sizeof(server), tcp_service_name, sizeof(tcp_service_name), share, sizeof(share), error_ptr) < 0) goto out; /* Use the workgroup name provided? */ if (opt_workgroup != NULL) { strlcpy (workgroup, opt_workgroup, sizeof(workgroup)); string_toupper (workgroup); } /* Use the default workgroup name instead. */ else { strlcpy (workgroup, "WORKGROUP", sizeof(workgroup)); } memset(&server_ip_address,0,sizeof(server_ip_address)); server_ip_address.sin_family = AF_INET; server_ip_address.sin_addr.s_addr = inet_addr (server); if (server_ip_address.sin_addr.s_addr == INADDR_NONE) /* name was given, not numeric */ { char * dot; int lookup_error; LOG(("server name is '%s'\n", server)); h_errno = 0; h = gethostbyname (server); lookup_error = h_errno; if (h != NULL) { memcpy(&server_ip_address.sin_addr,h->h_addr,h->h_length); } else if (strlen(server) > 16 || BroadcastNameQuery(server,"",(UBYTE *)&server_ip_address.sin_addr) != 0) { if(lookup_error == 0) report_error("Could not look up network address of '%s' (%ld, %s).",server,errno,posix_strerror(errno)); else report_error("Could not look up network address of '%s' (%ld, %s).",server,lookup_error,host_strerror(lookup_error)); (*error_ptr) = ENOENT; goto out; } LOG(("server network address found (%s)\n", Inet_NtoA(server_ip_address.sin_addr.s_addr))); /* No workgroup given? Ask the server... */ if(opt_workgroup == NULL) { char workgroup_name[16]; if(SendNetBIOSStatusQuery(server_ip_address,NULL,0,workgroup_name,sizeof(workgroup_name)) == 0) { if(workgroup_name[0] != '\0') strlcpy (workgroup, workgroup_name, sizeof(workgroup)); } } /* Remember the server name, retaining only the * host name. */ strlcpy (server_name, server, sizeof(server_name)); dot = strchr(server_name,'.'); if(dot != NULL) (*dot) = '\0'; } else { char host_name[MAXHOSTNAMELEN+1]; char workgroup_name[16]; const char * name; LOG(("server network address is %s\n", server)); /* Ask the server about its name and its workgroup. We need to * know the name to continue, and the workgroup name may * be useful, too. */ if(SendNetBIOSStatusQuery(server_ip_address,server_name,sizeof(server_name),workgroup_name,sizeof(workgroup_name)) == 0 && server_name[0] != '\0') { LOG(("server %s provided its own name '%s', with workgroup '%s'\n",Inet_NtoA(server_ip_address.sin_addr.s_addr),server_name,workgroup_name)); /* No workgroup given? Use what the server told us... */ if(opt_workgroup == NULL && workgroup_name[0] != '\0') strlcpy (workgroup, workgroup_name, sizeof(workgroup)); } else { h_errno = 0; h = gethostbyaddr ((char *) &server_ip_address.sin_addr.s_addr, sizeof (server_ip_address.sin_addr.s_addr), server_ip_address.sin_family); if (h == NULL) { if(h_errno == 0) { report_error("Could not look up name of server with network address %s (%ld, %s).",server,errno,posix_strerror(errno)); if(errno != 0) (*error_ptr) = errno; else (*error_ptr) = ENOENT; } else { report_error("Could not look up name of server with network address %s (%ld, %s).",server,h_errno,host_strerror(h_errno)); (*error_ptr) = ENOENT; } goto out; } name = h->h_name; LOG(("server host name found (%s)\n",name)); /* Brian Willette: Now we will set the server name to the DNS * hostname, hopefully this will be the same as the NetBIOS name for * the server. * * We do this because the user supplied no hostname, and we * need one for NetBIOS, this is the best guess choice we have * NOTE: If the names are different between DNS and NetBIOS on * the windows side, the user MUST use the -s option. */ for (i = 0; i < MAXHOSTNAMELEN && name[i] != '.' && name[i] != '\0' ; i++) host_name[i] = name[i]; host_name[i] = '\0'; /* Make sure the hostname is 16 characters or less (for NetBIOS) */ if (!opt_raw_smb && strlen (host_name) > 16) { report_error("Server name '%s' is too long (%ld characters are possible).", host_name, 16); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy (server_name, host_name, sizeof(server_name)); } } if(opt_password != NULL) { if(strlen(opt_password) >= sizeof(password)) { report_error("Password is too long (%ld characters are possible).", sizeof(password)-1); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy(password,opt_password,sizeof(password)); } if(strlen(opt_username) >= sizeof(username)) { report_error("User name '%s' is too long (%ld characters are possible).", username,sizeof(username)-1); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy(username,opt_username,sizeof(username)); if(opt_servername != NULL) { if (!opt_raw_smb && strlen (opt_servername) > 16) { report_error("Server name '%s' is too long (%ld characters are possible).", opt_servername,16); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy (server_name, opt_servername, sizeof(server_name)); } if(opt_clientname != NULL) { if (!opt_raw_smb && strlen (opt_clientname) > 16) { report_error("Client name '%s' is too long (%ld characters are possible).", opt_clientname,16); (*error_ptr) = ENAMETOOLONG; goto out; } strlcpy (client_name, opt_clientname, sizeof(client_name)); } if(opt_cachesize < 1) opt_cachesize = DIRCACHE_SIZE; else if (opt_cachesize < 10) opt_cachesize = 10; strlcpy(par.server_ipname,server,sizeof(par.server_ipname)); par.server_name = server_name; par.client_name = client_name; strlcpy(par.service,share,sizeof(par.service)); par.username = username; par.password = password; LOG(("server name = '%s', client name = '%s', workgroup name = '%s', user name = '%s'\n", server_name, client_name, workgroup, username)); if(smba_connect ( &par, server_ip_address, tcp_service_name, use_extended, workgroup, opt_cachesize, opt_max_transmit, opt_timeout, opt_raw_smb, opt_unicode, opt_prefer_core_protocol, opt_case_sensitive, opt_session_setup_delay_unicode, opt_write_behind, opt_smb_request_write_threshold, opt_smb_request_read_threshold, opt_scatter_gather, opt_tcp_no_delay, opt_socket_receive_buffer_size, opt_socket_send_buffer_size, error_ptr, smb_error_class_ptr, smb_error_ptr, &the_server) < 0) { if((*error_ptr) == error_check_smb_error) { char * smb_class_name; char * smb_code_text; smb_translate_error_class_and_code((*smb_error_class_ptr),(*smb_error_ptr),&smb_class_name,&smb_code_text); report_error("Could not connect to server '%s' (%ld/%ld, %s/%s).", server,(*smb_error_class_ptr),(*smb_error_ptr),smb_class_name,smb_code_text); } else { report_error("Could not connect to server '%s' (%ld, %s).", server,(*error_ptr),posix_strerror(*error_ptr)); } goto out; } if(smba_connect_par != NULL) (*smba_connect_par) = par; (*smba_server_ptr) = the_server; result = 0; out: return(result); } /*****************************************************************************/ int smba_get_dircache_size(struct smba_server * server) { int result; result = server->dircache->cache_size; return(result); } /*****************************************************************************/ int smba_change_dircache_size(struct smba_server * server,int cache_size) { const int complete_path_size = SMB_MAXNAMELEN + 1; dircache_t * new_cache; dircache_t * old_dircache = server->dircache; int result; int i; result = old_dircache->cache_size; /* We have to have a minimum cache size. */ if(cache_size < 10) cache_size = 10; /* Don't do anything if the cache size has not changed. */ if(cache_size == old_dircache->cache_size) goto out; /* Allocate a new cache and set it up with defaults. Note that * the file name pointers in the cache are still not initialized. */ new_cache = malloc(sizeof(*new_cache) + (cache_size-1) * sizeof(new_cache->cache)); if(new_cache == NULL) goto out; memset(new_cache,0,sizeof(*new_cache)); new_cache->cache_size = cache_size; /* If the new cache is to be larger than the old one, allocate additional file name slots. */ if(cache_size > old_dircache->cache_size) { /* Initialize the file name pointers so that free_dircache() * can be called safely, if necessary. */ for(i = 0 ; i < cache_size ; i++) new_cache->cache[i].complete_path = NULL; /* Allocate memory for the file names. */ for(i = old_dircache->cache_size ; i < cache_size ; i++) { new_cache->cache[i].complete_path = malloc (complete_path_size); if(new_cache->cache[i].complete_path == NULL) { free_dircache(new_cache); goto out; } new_cache->cache[i].complete_path_size = complete_path_size; } /* Reuse the file name buffers allocated for the old cache. */ for(i = 0 ; i < old_dircache->cache_size ; i++) { new_cache->cache[i].complete_path = old_dircache->cache[i].complete_path; new_cache->cache[i].complete_path_size = old_dircache->cache[i].complete_path_size; old_dircache->cache[i].complete_path = NULL; } } else { /* Reuse the file name buffers allocated for the old cache. */ for(i = 0 ; i < cache_size ; i++) { new_cache->cache[i].complete_path = old_dircache->cache[i].complete_path; new_cache->cache[i].complete_path_size = old_dircache->cache[i].complete_path_size; old_dircache->cache[i].complete_path = NULL; } } invalidate_dircache(server); free_dircache(old_dircache); server->dircache = new_cache; result = cache_size; out: return(result); }