/* Unix SMB/CIFS implementation. Main SMB reply routines Copyright (C) Andrew Tridgell 1992-1998 Copyright (C) Andrew Bartlett 2001 Copyright (C) Jeremy Allison 1992-2007. Copyright (C) Volker Lendecke 2007 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 3 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, see . */ /* This file handles most of the reply_ calls that the server makes to handle specific protocols */ #include "includes.h" #include "libsmb/namequery.h" #include "system/filesys.h" #include "printing.h" #include "locking/share_mode_lock.h" #include "smbd/smbd.h" #include "smbd/globals.h" #include "smbd/smbXsrv_open.h" #include "fake_file.h" #include "rpc_client/rpc_client.h" #include "../librpc/gen_ndr/ndr_spoolss_c.h" #include "rpc_client/cli_spoolss.h" #include "rpc_client/init_spoolss.h" #include "rpc_server/rpc_ncacn_np.h" #include "libcli/security/security.h" #include "libsmb/nmblib.h" #include "auth.h" #include "smbprofile.h" #include "../lib/tsocket/tsocket.h" #include "lib/util/tevent_ntstatus.h" #include "libcli/smb/smb_signing.h" #include "lib/util/sys_rw_data.h" #include "librpc/gen_ndr/open_files.h" #include "smb1_utils.h" #include "libcli/smb/smb2_posix.h" #include "lib/util/string_wrappers.h" #include "source3/printing/rap_jobid.h" #include "source3/lib/substitute.h" /**************************************************************************** Ensure we check the path in *exactly* the same way as W2K for a findfirst/findnext path or anything including wildcards. We're assuming here that '/' is not the second byte in any multibyte char set (a safe assumption). '\\' *may* be the second byte in a multibyte char set. ****************************************************************************/ /* Custom version for processing POSIX paths. */ #define IS_PATH_SEP(c,posix_only) ((c) == '/' || (!(posix_only) && (c) == '\\')) static NTSTATUS check_path_syntax_internal(char *path, bool posix_path) { char *d = path; const char *s = path; NTSTATUS ret = NT_STATUS_OK; bool start_of_name_component = True; bool stream_started = false; bool last_component_contains_wcard = false; while (*s) { if (stream_started) { switch (*s) { case '/': case '\\': return NT_STATUS_OBJECT_NAME_INVALID; case ':': if (s[1] == '\0') { return NT_STATUS_OBJECT_NAME_INVALID; } if (strchr_m(&s[1], ':')) { return NT_STATUS_OBJECT_NAME_INVALID; } break; } } if ((*s == ':') && !posix_path && !stream_started) { if (last_component_contains_wcard) { return NT_STATUS_OBJECT_NAME_INVALID; } /* Stream names allow more characters than file names. We're overloading posix_path here to allow a wider range of characters. If stream_started is true this is still a Windows path even if posix_path is true. JRA. */ stream_started = true; start_of_name_component = false; posix_path = true; if (s[1] == '\0') { return NT_STATUS_OBJECT_NAME_INVALID; } } if (!stream_started && IS_PATH_SEP(*s,posix_path)) { /* * Safe to assume is not the second part of a mb char * as this is handled below. */ /* Eat multiple '/' or '\\' */ while (IS_PATH_SEP(*s,posix_path)) { s++; } if ((d != path) && (*s != '\0')) { /* We only care about non-leading or trailing '/' or '\\' */ *d++ = '/'; } start_of_name_component = True; /* New component. */ last_component_contains_wcard = false; continue; } if (start_of_name_component) { if ((s[0] == '.') && (s[1] == '.') && (IS_PATH_SEP(s[2],posix_path) || s[2] == '\0')) { /* Uh oh - "/../" or "\\..\\" or "/..\0" or "\\..\0" ! */ /* * No mb char starts with '.' so we're safe checking the directory separator here. */ /* If we just added a '/' - delete it */ if ((d > path) && (*(d-1) == '/')) { *(d-1) = '\0'; d--; } /* Are we at the start ? Can't go back further if so. */ if (d <= path) { ret = NT_STATUS_OBJECT_PATH_SYNTAX_BAD; break; } /* Go back one level... */ /* We know this is safe as '/' cannot be part of a mb sequence. */ /* NOTE - if this assumption is invalid we are not in good shape... */ /* Decrement d first as d points to the *next* char to write into. */ for (d--; d > path; d--) { if (*d == '/') break; } s += 2; /* Else go past the .. */ /* We're still at the start of a name component, just the previous one. */ continue; } else if ((s[0] == '.') && ((s[1] == '\0') || IS_PATH_SEP(s[1],posix_path))) { if (posix_path) { /* Eat the '.' */ s++; continue; } } } if (!(*s & 0x80)) { if (!posix_path) { if (*s <= 0x1f || *s == '|') { return NT_STATUS_OBJECT_NAME_INVALID; } switch (*s) { case '*': case '?': case '<': case '>': case '"': last_component_contains_wcard = true; break; default: break; } } *d++ = *s++; } else { size_t siz; /* Get the size of the next MB character. */ next_codepoint(s,&siz); switch(siz) { case 5: *d++ = *s++; FALL_THROUGH; case 4: *d++ = *s++; FALL_THROUGH; case 3: *d++ = *s++; FALL_THROUGH; case 2: *d++ = *s++; FALL_THROUGH; case 1: *d++ = *s++; break; default: DEBUG(0,("check_path_syntax_internal: character length assumptions invalid !\n")); *d = '\0'; return NT_STATUS_INVALID_PARAMETER; } } start_of_name_component = False; } *d = '\0'; return ret; } /**************************************************************************** Ensure we check the path in *exactly* the same way as W2K for regular pathnames. No wildcards allowed. ****************************************************************************/ NTSTATUS check_path_syntax(char *path) { return check_path_syntax_internal(path, false); } /**************************************************************************** Check the path for a POSIX client. We're assuming here that '/' is not the second byte in any multibyte char set (a safe assumption). ****************************************************************************/ NTSTATUS check_path_syntax_posix(char *path) { return check_path_syntax_internal(path, true); } /**************************************************************************** Pull a string and check the path allowing a wildcard - provide for error return. Passes in posix flag. ****************************************************************************/ static size_t srvstr_get_path_internal(TALLOC_CTX *ctx, const char *base_ptr, uint16_t smb_flags2, char **pp_dest, const char *src, size_t src_len, int flags, bool posix_pathnames, NTSTATUS *err) { size_t ret; *pp_dest = NULL; ret = srvstr_pull_talloc(ctx, base_ptr, smb_flags2, pp_dest, src, src_len, flags); if (!*pp_dest) { *err = NT_STATUS_INVALID_PARAMETER; return ret; } if (smb_flags2 & FLAGS2_DFS_PATHNAMES) { /* * For a DFS path the function parse_dfs_path() * will do the path processing, just make a copy. */ *err = NT_STATUS_OK; return ret; } if (posix_pathnames) { *err = check_path_syntax_posix(*pp_dest); } else { *err = check_path_syntax(*pp_dest); } return ret; } /**************************************************************************** Pull a string and check the path - provide for error return. ****************************************************************************/ size_t srvstr_get_path(TALLOC_CTX *ctx, const char *base_ptr, uint16_t smb_flags2, char **pp_dest, const char *src, size_t src_len, int flags, NTSTATUS *err) { return srvstr_get_path_internal(ctx, base_ptr, smb_flags2, pp_dest, src, src_len, flags, false, err); } /**************************************************************************** Pull a string and check the path - provide for error return. posix_pathnames version. ****************************************************************************/ size_t srvstr_get_path_posix(TALLOC_CTX *ctx, const char *base_ptr, uint16_t smb_flags2, char **pp_dest, const char *src, size_t src_len, int flags, NTSTATUS *err) { return srvstr_get_path_internal(ctx, base_ptr, smb_flags2, pp_dest, src, src_len, flags, true, err); } size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req, char **pp_dest, const char *src, int flags, NTSTATUS *err) { ssize_t bufrem = smbreq_bufrem(req, src); if (bufrem < 0) { *err = NT_STATUS_INVALID_PARAMETER; return 0; } if (req->posix_pathnames) { return srvstr_get_path_internal(mem_ctx, (const char *)req->inbuf, req->flags2, pp_dest, src, bufrem, flags, true, err); } else { return srvstr_get_path_internal(mem_ctx, (const char *)req->inbuf, req->flags2, pp_dest, src, bufrem, flags, false, err); } } /** * pull a string from the smb_buf part of a packet. In this case the * string can either be null terminated or it can be terminated by the * end of the smbbuf area */ size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req, char **dest, const uint8_t *src, int flags) { ssize_t bufrem = smbreq_bufrem(req, src); if (bufrem < 0) { return 0; } return pull_string_talloc(ctx, req->inbuf, req->flags2, dest, src, bufrem, flags); } /**************************************************************************** Check if we have a correct fsp pointing to a file. Basic check for open fsp. ****************************************************************************/ bool check_fsp_open(connection_struct *conn, struct smb_request *req, files_struct *fsp) { if ((fsp == NULL) || (conn == NULL)) { reply_nterror(req, NT_STATUS_INVALID_HANDLE); return False; } if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) { reply_nterror(req, NT_STATUS_INVALID_HANDLE); return False; } return True; } /**************************************************************************** Check if we have a correct fsp pointing to a file. ****************************************************************************/ bool check_fsp(connection_struct *conn, struct smb_request *req, files_struct *fsp) { if (!check_fsp_open(conn, req, fsp)) { return False; } if (fsp->fsp_flags.is_directory) { reply_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST); return False; } if (fsp_get_pathref_fd(fsp) == -1) { reply_nterror(req, NT_STATUS_ACCESS_DENIED); return False; } fsp->num_smb_operations++; return True; } /**************************************************************************** Check if we have a correct fsp pointing to a quota fake file. Replacement for the CHECK_NTQUOTA_HANDLE_OK macro. ****************************************************************************/ bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req, files_struct *fsp) { if (!check_fsp_open(conn, req, fsp)) { return false; } if (fsp->fsp_flags.is_directory) { return false; } if (fsp->fake_file_handle == NULL) { return false; } if (fsp->fake_file_handle->type != FAKE_FILE_TYPE_QUOTA) { return false; } if (fsp->fake_file_handle->private_data == NULL) { return false; } return true; } /**************************************************************************** Return the port number we've bound to on a socket. ****************************************************************************/ static int get_socket_port(int fd) { struct samba_sockaddr saddr = { .sa_socklen = sizeof(struct sockaddr_storage), }; if (fd == -1) { return -1; } if (getsockname(fd, &saddr.u.sa, &saddr.sa_socklen) < 0) { int level = (errno == ENOTCONN) ? 2 : 0; DEBUG(level, ("getsockname failed. Error was %s\n", strerror(errno))); return -1; } #if defined(HAVE_IPV6) if (saddr.u.sa.sa_family == AF_INET6) { return ntohs(saddr.u.in6.sin6_port); } #endif if (saddr.u.sa.sa_family == AF_INET) { return ntohs(saddr.u.in.sin_port); } return -1; } static bool netbios_session_retarget(struct smbXsrv_connection *xconn, const char *name, int name_type) { char *trim_name; char *trim_name_type; const char *retarget_parm; char *retarget; char *p; int retarget_type = 0x20; int retarget_port = NBT_SMB_PORT; struct sockaddr_storage retarget_addr; struct sockaddr_in *in_addr; bool ret = false; uint8_t outbuf[10]; if (get_socket_port(xconn->transport.sock) != NBT_SMB_PORT) { return false; } trim_name = talloc_strdup(talloc_tos(), name); if (trim_name == NULL) { goto fail; } trim_char(trim_name, ' ', ' '); trim_name_type = talloc_asprintf(trim_name, "%s#%2.2x", trim_name, name_type); if (trim_name_type == NULL) { goto fail; } retarget_parm = lp_parm_const_string(-1, "netbios retarget", trim_name_type, NULL); if (retarget_parm == NULL) { retarget_parm = lp_parm_const_string(-1, "netbios retarget", trim_name, NULL); } if (retarget_parm == NULL) { goto fail; } retarget = talloc_strdup(trim_name, retarget_parm); if (retarget == NULL) { goto fail; } DEBUG(10, ("retargeting %s to %s\n", trim_name_type, retarget)); p = strchr(retarget, ':'); if (p != NULL) { *p++ = '\0'; retarget_port = atoi(p); } p = strchr_m(retarget, '#'); if (p != NULL) { *p++ = '\0'; if (sscanf(p, "%x", &retarget_type) != 1) { goto fail; } } ret = resolve_name(retarget, &retarget_addr, retarget_type, false); if (!ret) { DEBUG(10, ("could not resolve %s\n", retarget)); goto fail; } if (retarget_addr.ss_family != AF_INET) { DEBUG(10, ("Retarget target not an IPv4 addr\n")); goto fail; } in_addr = (struct sockaddr_in *)(void *)&retarget_addr; _smb_setlen(outbuf, 6); SCVAL(outbuf, 0, 0x84); *(uint32_t *)(outbuf+4) = in_addr->sin_addr.s_addr; *(uint16_t *)(outbuf+8) = htons(retarget_port); if (!srv_send_smb(xconn, (char *)outbuf, false, 0, false, NULL)) { exit_server_cleanly("netbios_session_retarget: srv_send_smb " "failed."); } ret = true; fail: TALLOC_FREE(trim_name); return ret; } static void reply_called_name_not_present(char *outbuf) { smb_setlen(outbuf, 1); SCVAL(outbuf, 0, 0x83); SCVAL(outbuf, 4, 0x82); } /**************************************************************************** Reply to a (netbios-level) special message. ****************************************************************************/ void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size) { struct smbd_server_connection *sconn = xconn->client->sconn; int msg_type = CVAL(inbuf,0); int msg_flags = CVAL(inbuf,1); /* * We only really use 4 bytes of the outbuf, but for the smb_setlen * calculation & friends (srv_send_smb uses that) we need the full smb * header. */ char outbuf[smb_size]; memset(outbuf, '\0', sizeof(outbuf)); smb_setlen(outbuf,0); switch (msg_type) { case NBSSrequest: /* session request */ { /* inbuf_size is guarenteed to be at least 4. */ fstring name1,name2; int name_type1, name_type2; int name_len1, name_len2; *name1 = *name2 = 0; if (xconn->transport.nbt.got_session) { exit_server_cleanly("multiple session request not permitted"); } SCVAL(outbuf,0,NBSSpositive); SCVAL(outbuf,3,0); /* inbuf_size is guaranteed to be at least 4. */ name_len1 = name_len((unsigned char *)(inbuf+4),inbuf_size - 4); if (name_len1 <= 0 || name_len1 > inbuf_size - 4) { DEBUG(0,("Invalid name length in session request\n")); reply_called_name_not_present(outbuf); break; } name_len2 = name_len((unsigned char *)(inbuf+4+name_len1),inbuf_size - 4 - name_len1); if (name_len2 <= 0 || name_len2 > inbuf_size - 4 - name_len1) { DEBUG(0,("Invalid name length in session request\n")); reply_called_name_not_present(outbuf); break; } name_type1 = name_extract((unsigned char *)inbuf, inbuf_size,(unsigned int)4,name1); name_type2 = name_extract((unsigned char *)inbuf, inbuf_size,(unsigned int)(4 + name_len1),name2); if (name_type1 == -1 || name_type2 == -1) { DEBUG(0,("Invalid name type in session request\n")); reply_called_name_not_present(outbuf); break; } DEBUG(2,("netbios connect: name1=%s0x%x name2=%s0x%x\n", name1, name_type1, name2, name_type2)); if (netbios_session_retarget(xconn, name1, name_type1)) { exit_server_cleanly("retargeted client"); } /* * Windows NT/2k uses "*SMBSERVER" and XP uses * "*SMBSERV" arrggg!!! */ if (strequal(name1, "*SMBSERVER ") || strequal(name1, "*SMBSERV ")) { char *raddr; raddr = tsocket_address_inet_addr_string(sconn->remote_address, talloc_tos()); if (raddr == NULL) { exit_server_cleanly("could not allocate raddr"); } fstrcpy(name1, raddr); } set_local_machine_name(name1, True); set_remote_machine_name(name2, True); if (is_ipaddress(sconn->remote_hostname)) { char *p = discard_const_p(char, sconn->remote_hostname); talloc_free(p); sconn->remote_hostname = talloc_strdup(sconn, get_remote_machine_name()); if (sconn->remote_hostname == NULL) { exit_server_cleanly("could not copy remote name"); } xconn->remote_hostname = sconn->remote_hostname; } DEBUG(2,("netbios connect: local=%s remote=%s, name type = %x\n", get_local_machine_name(), get_remote_machine_name(), name_type2)); if (name_type2 == 'R') { /* We are being asked for a pathworks session --- no thanks! */ reply_called_name_not_present(outbuf); break; } reload_services(sconn, conn_snum_used, true); reopen_logs(); xconn->transport.nbt.got_session = true; break; } case 0x89: /* session keepalive request (some old clients produce this?) */ SCVAL(outbuf,0,NBSSkeepalive); SCVAL(outbuf,3,0); break; case NBSSpositive: /* positive session response */ case NBSSnegative: /* negative session response */ case NBSSretarget: /* retarget session response */ DEBUG(0,("Unexpected session response\n")); break; case NBSSkeepalive: /* session keepalive */ default: return; } DEBUG(5,("init msg_type=0x%x msg_flags=0x%x\n", msg_type, msg_flags)); if (!srv_send_smb(xconn, outbuf, false, 0, false, NULL)) { exit_server_cleanly("reply_special: srv_send_smb failed."); } if (CVAL(outbuf, 0) != 0x82) { exit_server_cleanly("invalid netbios session"); } return; }