diff options
Diffstat (limited to 'source3/libsmb')
62 files changed, 46026 insertions, 0 deletions
diff --git a/source3/libsmb/asn1.c b/source3/libsmb/asn1.c new file mode 100644 index 0000000000..aca1c48841 --- /dev/null +++ b/source3/libsmb/asn1.c @@ -0,0 +1,603 @@ +/* + Unix SMB/CIFS implementation. + simple SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* free an asn1 structure */ +void asn1_free(ASN1_DATA *data) +{ + struct nesting *nesting = data->nesting; + + while (nesting) { + struct nesting *nnext = nesting->next; + free(nesting); + nesting = nnext; + }; + data->nesting = NULL; + SAFE_FREE(data->data); +} + +/* write to the ASN1 buffer, advancing the buffer pointer */ +bool asn1_write(ASN1_DATA *data, const void *p, int len) +{ + if (data->has_error) return false; + if (data->length < data->ofs+len) { + data->data = SMB_REALLOC_ARRAY(data->data, unsigned char, + data->ofs+len); + if (!data->data) { + data->has_error = true; + return false; + } + data->length = data->ofs+len; + } + memcpy(data->data + data->ofs, p, len); + data->ofs += len; + return true; +} + +/* useful fn for writing a uint8 */ +bool asn1_write_uint8(ASN1_DATA *data, uint8 v) +{ + return asn1_write(data, &v, 1); +} + +/* push a tag onto the asn1 data buffer. Used for nested structures */ +bool asn1_push_tag(ASN1_DATA *data, uint8 tag) +{ + struct nesting *nesting; + + asn1_write_uint8(data, tag); + nesting = SMB_MALLOC_P(struct nesting); + if (!nesting) { + data->has_error = true; + return false; + } + + nesting->start = data->ofs; + nesting->next = data->nesting; + data->nesting = nesting; + return asn1_write_uint8(data, 0xff); +} + +/* pop a tag */ +bool asn1_pop_tag(ASN1_DATA *data) +{ + struct nesting *nesting; + size_t len; + + if (data->has_error) { + return false; + } + + nesting = data->nesting; + + if (!nesting) { + data->has_error = true; + return false; + } + len = data->ofs - (nesting->start+1); + /* yes, this is ugly. We don't know in advance how many bytes the length + of a tag will take, so we assumed 1 byte. If we were wrong then we + need to correct our mistake */ + if (len > 0xFFFF) { + data->data[nesting->start] = 0x83; + if (!asn1_write_uint8(data, 0)) return false; + if (!asn1_write_uint8(data, 0)) return false; + if (!asn1_write_uint8(data, 0)) return false; + memmove(data->data+nesting->start+4, data->data+nesting->start+1, len); + data->data[nesting->start+1] = (len>>16) & 0xFF; + data->data[nesting->start+2] = (len>>8) & 0xFF; + data->data[nesting->start+3] = len&0xff; + } else if (len > 255) { + data->data[nesting->start] = 0x82; + if (!asn1_write_uint8(data, 0)) return false; + if (!asn1_write_uint8(data, 0)) return false; + memmove(data->data+nesting->start+3, data->data+nesting->start+1, len); + data->data[nesting->start+1] = len>>8; + data->data[nesting->start+2] = len&0xff; + } else if (len > 127) { + data->data[nesting->start] = 0x81; + if (!asn1_write_uint8(data, 0)) return false; + memmove(data->data+nesting->start+2, data->data+nesting->start+1, len); + data->data[nesting->start+1] = len; + } else { + data->data[nesting->start] = len; + } + + data->nesting = nesting->next; + free(nesting); + return true; +} + + +/* write an integer */ +bool asn1_write_Integer(ASN1_DATA *data, int i) +{ + if (!asn1_push_tag(data, ASN1_INTEGER)) return false; + do { + asn1_write_uint8(data, i); + i = i >> 8; + } while (i); + return asn1_pop_tag(data); +} + +/* write an object ID to a ASN1 buffer */ +bool asn1_write_OID(ASN1_DATA *data, const char *OID) +{ + unsigned v, v2; + const char *p = (const char *)OID; + char *newp; + + if (!asn1_push_tag(data, ASN1_OID)) + return false; + v = strtol(p, &newp, 10); + p = newp; + v2 = strtol(p, &newp, 10); + p = newp; + if (!asn1_write_uint8(data, 40*v + v2)) + return false; + + while (*p) { + v = strtol(p, &newp, 10); + p = newp; + if (v >= (1<<28)) asn1_write_uint8(data, 0x80 | ((v>>28)&0xff)); + if (v >= (1<<21)) asn1_write_uint8(data, 0x80 | ((v>>21)&0xff)); + if (v >= (1<<14)) asn1_write_uint8(data, 0x80 | ((v>>14)&0xff)); + if (v >= (1<<7)) asn1_write_uint8(data, 0x80 | ((v>>7)&0xff)); + if (!asn1_write_uint8(data, v&0x7f)) + return false; + } + return asn1_pop_tag(data); +} + +/* write an octet string */ +bool asn1_write_OctetString(ASN1_DATA *data, const void *p, size_t length) +{ + asn1_push_tag(data, ASN1_OCTET_STRING); + asn1_write(data, p, length); + asn1_pop_tag(data); + return !data->has_error; +} + +/* write a general string */ +bool asn1_write_GeneralString(ASN1_DATA *data, const char *s) +{ + asn1_push_tag(data, ASN1_GENERAL_STRING); + asn1_write(data, s, strlen(s)); + asn1_pop_tag(data); + return !data->has_error; +} + +/* write a BOOLEAN */ +bool asn1_write_BOOLEAN(ASN1_DATA *data, bool v) +{ + asn1_write_uint8(data, ASN1_BOOLEAN); + asn1_write_uint8(data, v); + return !data->has_error; +} + +/* write a BOOLEAN - hmm, I suspect this one is the correct one, and the + above boolean is bogus. Need to check */ +bool asn1_write_BOOLEAN2(ASN1_DATA *data, bool v) +{ + asn1_push_tag(data, ASN1_BOOLEAN); + asn1_write_uint8(data, v); + asn1_pop_tag(data); + return !data->has_error; +} + +/* check a BOOLEAN */ +bool asn1_check_BOOLEAN(ASN1_DATA *data, bool v) +{ + uint8 b = 0; + + asn1_read_uint8(data, &b); + if (b != ASN1_BOOLEAN) { + data->has_error = true; + return false; + } + asn1_read_uint8(data, &b); + if (b != v) { + data->has_error = true; + return false; + } + return !data->has_error; +} + + +/* load a ASN1_DATA structure with a lump of data, ready to be parsed */ +bool asn1_load(ASN1_DATA *data, DATA_BLOB blob) +{ + ZERO_STRUCTP(data); + data->data = (unsigned char *)memdup(blob.data, blob.length); + if (!data->data) { + data->has_error = true; + return false; + } + data->length = blob.length; + return true; +} + +/* read from a ASN1 buffer, advancing the buffer pointer */ +bool asn1_read(ASN1_DATA *data, void *p, int len) +{ + if (data->has_error) + return false; + + if (len < 0 || data->ofs + len < data->ofs || data->ofs + len < len) { + data->has_error = true; + return false; + } + + if (data->ofs + len > data->length) { + data->has_error = true; + return false; + } + memcpy(p, data->data + data->ofs, len); + data->ofs += len; + return true; +} + +/* read a uint8 from a ASN1 buffer */ +bool asn1_read_uint8(ASN1_DATA *data, uint8 *v) +{ + return asn1_read(data, v, 1); +} + +/* + * Check thta the value of the ASN1 buffer at the current offset equals tag. + */ +bool asn1_check_tag(ASN1_DATA *data, uint8 tag) +{ + if (data->has_error || data->ofs >= data->length || data->ofs < 0) { + data->has_error = true; + return false; + } + + return (tag == data->data[data->ofs]); +} + +/* start reading a nested asn1 structure */ +bool asn1_start_tag(ASN1_DATA *data, uint8 tag) +{ + uint8 b; + struct nesting *nesting; + + if (!asn1_read_uint8(data, &b)) + return false; + + if (b != tag) { + data->has_error = true; + return false; + } + nesting = SMB_MALLOC_P(struct nesting); + if (!nesting) { + data->has_error = true; + return false; + } + + if (!asn1_read_uint8(data, &b)) { + SAFE_FREE(nesting); + return false; + } + + if (b & 0x80) { + int n = b & 0x7f; + if (!asn1_read_uint8(data, &b)) { + SAFE_FREE(nesting); + return false; + } + nesting->taglen = b; + while (n > 1) { + if (!asn1_read_uint8(data, &b)) { + SAFE_FREE(nesting); + return false; + } + nesting->taglen = (nesting->taglen << 8) | b; + n--; + } + } else { + nesting->taglen = b; + } + nesting->start = data->ofs; + nesting->next = data->nesting; + data->nesting = nesting; + return !data->has_error; +} + + +/* stop reading a tag */ +bool asn1_end_tag(ASN1_DATA *data) +{ + struct nesting *nesting; + + /* make sure we read it all */ + if (asn1_tag_remaining(data) != 0) { + data->has_error = true; + return false; + } + + nesting = data->nesting; + + if (!nesting) { + data->has_error = true; + return false; + } + + data->nesting = nesting->next; + free(nesting); + return true; +} + +/* work out how many bytes are left in this nested tag */ +int asn1_tag_remaining(ASN1_DATA *data) +{ + if (data->has_error) + return 0; + + if (!data->nesting) { + data->has_error = true; + return -1; + } + return data->nesting->taglen - (data->ofs - data->nesting->start); +} + +/* read an object ID from a ASN1 buffer */ +bool asn1_read_OID(ASN1_DATA *data, char **OID) +{ + uint8 b = 0; + char *oid_str = NULL; + + *OID = NULL; + + if (!asn1_start_tag(data, ASN1_OID)) { + return false; + } + asn1_read_uint8(data, &b); + + oid_str = talloc_asprintf(NULL, + "%u", + b/40); + if (!oid_str) { + data->has_error = true; + goto out; + } + oid_str = talloc_asprintf_append(oid_str, + " %u", + b%40); + if (!oid_str) { + data->has_error = true; + goto out; + } + + while (asn1_tag_remaining(data) > 0) { + unsigned v = 0; + do { + asn1_read_uint8(data, &b); + v = (v<<7) | (b&0x7f); + } while (!data->has_error && b & 0x80); + oid_str = talloc_asprintf_append(oid_str, + " %u", + v); + if (!oid_str) { + data->has_error = true; + goto out; + } + } + + out: + + asn1_end_tag(data); + + if (!data->has_error) { + *OID = SMB_STRDUP(oid_str); + } + + TALLOC_FREE(oid_str); + + return !data->has_error; +} + +/* check that the next object ID is correct */ +bool asn1_check_OID(ASN1_DATA *data, const char *OID) +{ + char *id; + + if (!asn1_read_OID(data, &id)) { + return false; + } + + if (strcmp(id, OID) != 0) { + data->has_error = true; + free(id); + return false; + } + free(id); + return true; +} + +/* read a GeneralString from a ASN1 buffer */ +bool asn1_read_GeneralString(ASN1_DATA *data, char **s) +{ + int len; + char *str; + + *s = NULL; + + if (!asn1_start_tag(data, ASN1_GENERAL_STRING)) { + return false; + } + len = asn1_tag_remaining(data); + if (len < 0) { + data->has_error = true; + return false; + } + str = SMB_MALLOC_ARRAY(char, len+1); + if (!str) { + data->has_error = true; + return false; + } + asn1_read(data, str, len); + str[len] = 0; + asn1_end_tag(data); + + if (!data->has_error) { + *s = str; + } + return !data->has_error; +} + +/* read a octet string blob */ +bool asn1_read_OctetString(ASN1_DATA *data, DATA_BLOB *blob) +{ + int len; + ZERO_STRUCTP(blob); + if (!asn1_start_tag(data, ASN1_OCTET_STRING)) return false; + len = asn1_tag_remaining(data); + if (len < 0) { + data->has_error = true; + return false; + } + *blob = data_blob(NULL, len); + asn1_read(data, blob->data, len); + asn1_end_tag(data); + return !data->has_error; +} + +/* read an interger */ +bool asn1_read_Integer(ASN1_DATA *data, int *i) +{ + uint8 b; + *i = 0; + + if (!asn1_start_tag(data, ASN1_INTEGER)) return false; + while (asn1_tag_remaining(data)>0) { + asn1_read_uint8(data, &b); + *i = (*i << 8) + b; + } + return asn1_end_tag(data); + +} + +/* check a enumarted value is correct */ +bool asn1_check_enumerated(ASN1_DATA *data, int v) +{ + uint8 b; + if (!asn1_start_tag(data, ASN1_ENUMERATED)) return false; + asn1_read_uint8(data, &b); + asn1_end_tag(data); + + if (v != b) + data->has_error = false; + + return !data->has_error; +} + +/* write an enumarted value to the stream */ +bool asn1_write_enumerated(ASN1_DATA *data, uint8 v) +{ + if (!asn1_push_tag(data, ASN1_ENUMERATED)) return false; + asn1_write_uint8(data, v); + asn1_pop_tag(data); + return !data->has_error; +} + +bool ber_write_OID_String(DATA_BLOB *blob, const char *OID) +{ + uint_t v, v2; + const char *p = (const char *)OID; + char *newp; + int i; + + v = strtoul(p, &newp, 10); + if (newp[0] != '.') return false; + p = newp + 1; + + v2 = strtoul(p, &newp, 10); + if (newp[0] != '.') return false; + p = newp + 1; + + /*the ber representation can't use more space then the string one */ + *blob = data_blob(NULL, strlen(OID)); + if (!blob->data) return false; + + blob->data[0] = 40*v + v2; + + i = 1; + while (*p) { + v = strtoul(p, &newp, 10); + if (newp[0] == '.') { + p = newp + 1; + } else if (newp[0] == '\0') { + p = newp; + } else { + data_blob_free(blob); + return false; + } + if (v >= (1<<28)) blob->data[i++] = (0x80 | ((v>>28)&0x7f)); + if (v >= (1<<21)) blob->data[i++] = (0x80 | ((v>>21)&0x7f)); + if (v >= (1<<14)) blob->data[i++] = (0x80 | ((v>>14)&0x7f)); + if (v >= (1<<7)) blob->data[i++] = (0x80 | ((v>>7)&0x7f)); + blob->data[i++] = (v&0x7f); + } + + blob->length = i; + + return true; +} + +/* read an object ID from a data blob */ +bool ber_read_OID_String(TALLOC_CTX *mem_ctx, DATA_BLOB blob, const char **OID) +{ + int i; + uint8_t *b; + uint_t v; + char *tmp_oid = NULL; + + if (blob.length < 2) return false; + + b = blob.data; + + tmp_oid = talloc_asprintf(mem_ctx, "%u", b[0]/40); + if (!tmp_oid) goto nomem; + tmp_oid = talloc_asprintf_append_buffer(tmp_oid, ".%u", b[0]%40); + if (!tmp_oid) goto nomem; + + for(i = 1, v = 0; i < blob.length; i++) { + v = (v<<7) | (b[i]&0x7f); + if ( ! (b[i] & 0x80)) { + tmp_oid = talloc_asprintf_append_buffer(tmp_oid, ".%u", v); + v = 0; + } + if (!tmp_oid) goto nomem; + } + + if (v != 0) { + talloc_free(tmp_oid); + return false; + } + + *OID = tmp_oid; + return true; + +nomem: + return false; +} + + diff --git a/source3/libsmb/async_smb.c b/source3/libsmb/async_smb.c new file mode 100644 index 0000000000..d5eac07b48 --- /dev/null +++ b/source3/libsmb/async_smb.c @@ -0,0 +1,999 @@ +/* + Unix SMB/CIFS implementation. + Infrastructure for async SMB client requests + Copyright (C) Volker Lendecke 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +static void cli_state_handler(struct event_context *event_ctx, + struct fd_event *event, uint16 flags, void *p); + +/** + * Fetch an error out of a NBT packet + * @param[in] buf The SMB packet + * @retval The error, converted to NTSTATUS + */ + +NTSTATUS cli_pull_error(char *buf) +{ + uint32_t flags2 = SVAL(buf, smb_flg2); + + if (flags2 & FLAGS2_32_BIT_ERROR_CODES) { + return NT_STATUS(IVAL(buf, smb_rcls)); + } + + /* if the client uses dos errors, but there is no error, + we should return no error here, otherwise it looks + like an unknown bad NT_STATUS. jmcd */ + if (CVAL(buf, smb_rcls) == 0) + return NT_STATUS_OK; + + return NT_STATUS_DOS(CVAL(buf, smb_rcls), SVAL(buf,smb_err)); +} + +/** + * Compatibility helper for the sync APIs: Fake NTSTATUS in cli->inbuf + * @param[in] cli The client connection that just received an error + * @param[in] status The error to set on "cli" + */ + +void cli_set_error(struct cli_state *cli, NTSTATUS status) +{ + uint32_t flags2 = SVAL(cli->inbuf, smb_flg2); + + if (NT_STATUS_IS_DOS(status)) { + SSVAL(cli->inbuf, smb_flg2, + flags2 & ~FLAGS2_32_BIT_ERROR_CODES); + SCVAL(cli->inbuf, smb_rcls, NT_STATUS_DOS_CLASS(status)); + SSVAL(cli->inbuf, smb_err, NT_STATUS_DOS_CODE(status)); + return; + } + + SSVAL(cli->inbuf, smb_flg2, flags2 | FLAGS2_32_BIT_ERROR_CODES); + SIVAL(cli->inbuf, smb_rcls, NT_STATUS_V(status)); + return; +} + +/** + * Allocate a new mid + * @param[in] cli The client connection + * @retval The new, unused mid + */ + +static uint16_t cli_new_mid(struct cli_state *cli) +{ + uint16_t result; + struct cli_request *req; + + while (true) { + result = cli->mid++; + if (result == 0) { + continue; + } + + for (req = cli->outstanding_requests; req; req = req->next) { + if (result == req->mid) { + break; + } + } + + if (req == NULL) { + return result; + } + } +} + +/** + * Print an async req that happens to be a cli_request + * @param[in] mem_ctx The TALLOC_CTX to put the result on + * @param[in] req The request to print + * @retval The string representation of "req" + */ + +static char *cli_request_print(TALLOC_CTX *mem_ctx, struct async_req *req) +{ + char *result = async_req_print(mem_ctx, req); + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + + if (result == NULL) { + return NULL; + } + + return talloc_asprintf_append_buffer( + result, "mid=%d\n", cli_req->mid); +} + +/** + * Destroy a cli_request + * @param[in] req The cli_request to kill + * @retval Can't fail + */ + +static int cli_request_destructor(struct cli_request *req) +{ + if (req->enc_state != NULL) { + common_free_enc_buffer(req->enc_state, req->outbuf); + } + DLIST_REMOVE(req->cli->outstanding_requests, req); + if (req->cli->outstanding_requests == NULL) { + TALLOC_FREE(req->cli->fd_event); + } + return 0; +} + +/** + * Are there already requests waiting in the chain_accumulator? + * @param[in] cli The cli_state we want to check + * @retval reply :-) + */ + +bool cli_in_chain(struct cli_state *cli) +{ + if (cli->chain_accumulator == NULL) { + return false; + } + + return (cli->chain_accumulator->num_async != 0); +} + +/** + * Is the SMB command able to hold an AND_X successor + * @param[in] cmd The SMB command in question + * @retval Can we add a chained request after "cmd"? + */ + +static bool is_andx_req(uint8_t cmd) +{ + switch (cmd) { + case SMBtconX: + case SMBlockingX: + case SMBopenX: + case SMBreadX: + case SMBwriteX: + case SMBsesssetupX: + case SMBulogoffX: + case SMBntcreateX: + return true; + break; + default: + break; + } + + return false; +} + +/** + * @brief Find the smb_cmd offset of the last command pushed + * @param[in] buf The buffer we're building up + * @retval Where can we put our next andx cmd? + * + * While chaining requests, the "next" request we're looking at needs to put + * its SMB_Command before the data the previous request already built up added + * to the chain. Find the offset to the place where we have to put our cmd. + */ + +static bool find_andx_cmd_ofs(char *buf, size_t *pofs) +{ + uint8_t cmd; + size_t ofs; + + cmd = CVAL(buf, smb_com); + + SMB_ASSERT(is_andx_req(cmd)); + + ofs = smb_vwv0; + + while (CVAL(buf, ofs) != 0xff) { + + if (!is_andx_req(CVAL(buf, ofs))) { + return false; + } + + /* + * ofs is from start of smb header, so add the 4 length + * bytes. The next cmd is right after the wct field. + */ + ofs = SVAL(buf, ofs+2) + 4 + 1; + + SMB_ASSERT(ofs+4 < talloc_get_size(buf)); + } + + *pofs = ofs; + return true; +} + +/** + * @brief Destroy an async_req that is the visible part of a cli_request + * @param[in] req The request to kill + * @retval Return 0 to make talloc happy + * + * This destructor is a bit tricky: Because a cli_request can host more than + * one async_req for chained requests, we need to make sure that the + * "cli_request" that we were part of is correctly destroyed at the right + * time. This is done by NULLing out ourself from the "async" member of our + * "cli_request". If there is none left, then also TALLOC_FREE() the + * cli_request, which was a talloc child of the client connection cli_state. + */ + +static int cli_async_req_destructor(struct async_req *req) +{ + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + int i, pending; + bool found = false; + + pending = 0; + + for (i=0; i<cli_req->num_async; i++) { + if (cli_req->async[i] == req) { + cli_req->async[i] = NULL; + found = true; + } + if (cli_req->async[i] != NULL) { + pending += 1; + } + } + + SMB_ASSERT(found); + + if (pending == 0) { + TALLOC_FREE(cli_req); + } + + return 0; +} + +/** + * @brief Chain up a request + * @param[in] mem_ctx The TALLOC_CTX for the result + * @param[in] ev The event context that will call us back + * @param[in] cli The cli_state we queue the request up for + * @param[in] smb_command The command that we want to issue + * @param[in] additional_flags open_and_x wants to add oplock header flags + * @param[in] wct How many words? + * @param[in] vwv The words, already in network order + * @param[in] num_bytes How many bytes? + * @param[in] bytes The data the request ships + * + * cli_request_chain() is the core of the SMB request marshalling routine. It + * will create a new async_req structure in the cli->chain_accumulator->async + * array and marshall the smb_cmd, the vwv array and the bytes into + * cli->chain_accumulator->outbuf. + */ + +static struct async_req *cli_request_chain(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t wct, const uint16_t *vwv, + uint16_t num_bytes, + const uint8_t *bytes) +{ + struct async_req **tmp_reqs; + char *tmp_buf; + struct cli_request *req; + size_t old_size, new_size; + size_t ofs; + + req = cli->chain_accumulator; + + tmp_reqs = TALLOC_REALLOC_ARRAY(req, req->async, struct async_req *, + req->num_async + 1); + if (tmp_reqs == NULL) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + req->async = tmp_reqs; + req->num_async += 1; + + req->async[req->num_async-1] = async_req_new(mem_ctx, ev); + if (req->async[req->num_async-1] == NULL) { + DEBUG(0, ("async_req_new failed\n")); + req->num_async -= 1; + return NULL; + } + req->async[req->num_async-1]->private_data = req; + req->async[req->num_async-1]->print = cli_request_print; + talloc_set_destructor(req->async[req->num_async-1], + cli_async_req_destructor); + + old_size = talloc_get_size(req->outbuf); + + /* + * We need space for the wct field, the words, the byte count field + * and the bytes themselves. + */ + new_size = old_size + 1 + wct * sizeof(uint16_t) + 2 + num_bytes; + + if (new_size > 0xffff) { + DEBUG(1, ("cli_request_chain: %u bytes won't fit\n", + (unsigned)new_size)); + goto fail; + } + + tmp_buf = TALLOC_REALLOC_ARRAY(NULL, req->outbuf, char, new_size); + if (tmp_buf == NULL) { + DEBUG(0, ("talloc failed\n")); + goto fail; + } + req->outbuf = tmp_buf; + + if (old_size == smb_wct) { + SCVAL(req->outbuf, smb_com, smb_command); + } else { + size_t andx_cmd_ofs; + if (!find_andx_cmd_ofs(req->outbuf, &andx_cmd_ofs)) { + DEBUG(1, ("invalid command chain\n")); + goto fail; + } + SCVAL(req->outbuf, andx_cmd_ofs, smb_command); + SSVAL(req->outbuf, andx_cmd_ofs + 2, old_size - 4); + } + + ofs = old_size; + + SCVAL(req->outbuf, ofs, wct); + ofs += 1; + + memcpy(req->outbuf + ofs, vwv, sizeof(uint16_t) * wct); + ofs += sizeof(uint16_t) * wct; + + SSVAL(req->outbuf, ofs, num_bytes); + ofs += sizeof(uint16_t); + + memcpy(req->outbuf + ofs, bytes, num_bytes); + + return req->async[req->num_async-1]; + + fail: + TALLOC_FREE(req->async[req->num_async-1]); + req->num_async -= 1; + return NULL; +} + +/** + * @brief prepare a cli_state to accept a chain of requests + * @param[in] cli The cli_state we want to queue up in + * @param[in] ev The event_context that will call us back for the socket + * @param[in] size_hint How many bytes are expected, just an optimization + * @retval Did we have enough memory? + * + * cli_chain_cork() sets up a new cli_request in cli->chain_accumulator. If + * cli is used in an async fashion, i.e. if we have outstanding requests, then + * we do not have to create a fd event. If cli is used only with the sync + * helpers, we need to create the fd_event here. + * + * If you want to issue a chained request to the server, do a + * cli_chain_cork(), then do you cli_open_send(), cli_read_and_x_send(), + * cli_close_send() and so on. The async requests that come out of + * cli_xxx_send() are normal async requests with the difference that they + * won't be shipped individually. But the event_context will still trigger the + * req->async.fn to be called on every single request. + * + * You have to take care yourself that you only issue chainable requests in + * the middle of the chain. + */ + +bool cli_chain_cork(struct cli_state *cli, struct event_context *ev, + size_t size_hint) +{ + struct cli_request *req = NULL; + + SMB_ASSERT(cli->chain_accumulator == NULL); + + if (cli->fd == -1) { + DEBUG(10, ("cli->fd closed\n")); + return false; + } + + if (cli->fd_event == NULL) { + SMB_ASSERT(cli->outstanding_requests == NULL); + cli->fd_event = event_add_fd(ev, cli, cli->fd, + EVENT_FD_READ, + cli_state_handler, cli); + if (cli->fd_event == NULL) { + return false; + } + } + + req = talloc(cli, struct cli_request); + if (req == NULL) { + goto fail; + } + req->cli = cli; + + if (size_hint == 0) { + size_hint = 100; + } + req->outbuf = talloc_array(req, char, smb_wct + size_hint); + if (req->outbuf == NULL) { + goto fail; + } + req->outbuf = TALLOC_REALLOC_ARRAY(NULL, req->outbuf, char, smb_wct); + + req->num_async = 0; + req->async = NULL; + + req->enc_state = NULL; + req->recv_helper.fn = NULL; + + SSVAL(req->outbuf, smb_tid, cli->cnum); + cli_setup_packet_buf(cli, req->outbuf); + + req->mid = cli_new_mid(cli); + + cli->chain_accumulator = req; + + DEBUG(10, ("cli_chain_cork: mid=%d\n", req->mid)); + + return true; + fail: + TALLOC_FREE(req); + if (cli->outstanding_requests == NULL) { + TALLOC_FREE(cli->fd_event); + } + return false; +} + +/** + * Ship a request queued up via cli_request_chain() + * @param[in] cl The connection + */ + +void cli_chain_uncork(struct cli_state *cli) +{ + struct cli_request *req = cli->chain_accumulator; + + SMB_ASSERT(req != NULL); + + DLIST_ADD_END(cli->outstanding_requests, req, struct cli_request *); + talloc_set_destructor(req, cli_request_destructor); + + cli->chain_accumulator = NULL; + + SSVAL(req->outbuf, smb_mid, req->mid); + smb_setlen(req->outbuf, talloc_get_size(req->outbuf) - 4); + + cli_calculate_sign_mac(cli, req->outbuf); + + if (cli_encryption_on(cli)) { + NTSTATUS status; + char *enc_buf; + + status = cli_encrypt_message(cli, req->outbuf, &enc_buf); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Error in encrypting client message. " + "Error %s\n", nt_errstr(status))); + TALLOC_FREE(req); + return; + } + req->outbuf = enc_buf; + req->enc_state = cli->trans_enc_state; + } + + req->sent = 0; + + event_fd_set_writeable(cli->fd_event); +} + +/** + * @brief Send a request to the server + * @param[in] mem_ctx The TALLOC_CTX for the result + * @param[in] ev The event context that will call us back + * @param[in] cli The cli_state we queue the request up for + * @param[in] smb_command The command that we want to issue + * @param[in] additional_flags open_and_x wants to add oplock header flags + * @param[in] wct How many words? + * @param[in] vwv The words, already in network order + * @param[in] num_bytes How many bytes? + * @param[in] bytes The data the request ships + * + * This is the generic routine to be used by the cli_xxx_send routines. + */ + +struct async_req *cli_request_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, + uint8_t smb_command, + uint8_t additional_flags, + uint8_t wct, const uint16_t *vwv, + uint16_t num_bytes, const uint8_t *bytes) +{ + struct async_req *result; + bool uncork = false; + + if (cli->chain_accumulator == NULL) { + if (!cli_chain_cork(cli, ev, + wct * sizeof(uint16_t) + num_bytes + 3)) { + DEBUG(1, ("cli_chain_cork failed\n")); + return NULL; + } + uncork = true; + } + + result = cli_request_chain(mem_ctx, ev, cli, smb_command, + additional_flags, wct, vwv, + num_bytes, bytes); + + if (result == NULL) { + DEBUG(1, ("cli_request_chain failed\n")); + } + + if (uncork) { + cli_chain_uncork(cli); + } + + return result; +} + +/** + * Figure out if there is an andx command behind the current one + * @param[in] buf The smb buffer to look at + * @param[in] ofs The offset to the wct field that is followed by the cmd + * @retval Is there a command following? + */ + +static bool have_andx_command(const char *buf, uint16_t ofs) +{ + uint8_t wct; + size_t buflen = talloc_get_size(buf); + + if ((ofs == buflen-1) || (ofs == buflen)) { + return false; + } + + wct = CVAL(buf, ofs); + if (wct < 2) { + /* + * Not enough space for the command and a following pointer + */ + return false; + } + return (CVAL(buf, ofs+1) != 0xff); +} + +/** + * @brief Pull reply data out of a request + * @param[in] req The request that we just received a reply for + * @param[out] pwct How many words did the server send? + * @param[out] pvwv The words themselves + * @param[out] pnum_bytes How many bytes did the server send? + * @param[out] pbytes The bytes themselves + * @retval Was the reply formally correct? + */ + +NTSTATUS cli_pull_reply(struct async_req *req, + uint8_t *pwct, uint16_t **pvwv, + uint16_t *pnum_bytes, uint8_t **pbytes) +{ + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + uint8_t wct, cmd; + uint16_t num_bytes; + size_t wct_ofs, bytes_offset; + int i, j; + NTSTATUS status; + + for (i = 0; i < cli_req->num_async; i++) { + if (req == cli_req->async[i]) { + break; + } + } + + if (i == cli_req->num_async) { + cli_set_error(cli_req->cli, NT_STATUS_INVALID_PARAMETER); + return NT_STATUS_INVALID_PARAMETER; + } + + /** + * The status we pull here is only relevant for the last reply in the + * chain. + */ + + status = cli_pull_error(cli_req->inbuf); + + if (i == 0) { + if (NT_STATUS_IS_ERR(status) + && !have_andx_command(cli_req->inbuf, smb_wct)) { + cli_set_error(cli_req->cli, status); + return status; + } + wct_ofs = smb_wct; + goto done; + } + + cmd = CVAL(cli_req->inbuf, smb_com); + wct_ofs = smb_wct; + + for (j = 0; j < i; j++) { + if (j < i-1) { + if (cmd == 0xff) { + return NT_STATUS_REQUEST_ABORTED; + } + if (!is_andx_req(cmd)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + } + + if (!have_andx_command(cli_req->inbuf, wct_ofs)) { + /* + * This request was not completed because a previous + * request in the chain had received an error. + */ + return NT_STATUS_REQUEST_ABORTED; + } + + wct_ofs = SVAL(cli_req->inbuf, wct_ofs + 3); + + /* + * Skip the all-present length field. No overflow, we've just + * put a 16-bit value into a size_t. + */ + wct_ofs += 4; + + if (wct_ofs+2 > talloc_get_size(cli_req->inbuf)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + cmd = CVAL(cli_req->inbuf, wct_ofs + 1); + } + + if (!have_andx_command(cli_req->inbuf, wct_ofs) + && NT_STATUS_IS_ERR(status)) { + /* + * The last command takes the error code. All further commands + * down the requested chain will get a + * NT_STATUS_REQUEST_ABORTED. + */ + return status; + } + + done: + wct = CVAL(cli_req->inbuf, wct_ofs); + + bytes_offset = wct_ofs + 1 + wct * sizeof(uint16_t); + num_bytes = SVAL(cli_req->inbuf, bytes_offset); + + /* + * wct_ofs is a 16-bit value plus 4, wct is a 8-bit value, num_bytes + * is a 16-bit value. So bytes_offset being size_t should be far from + * wrapping. + */ + + if ((bytes_offset + 2 > talloc_get_size(cli_req->inbuf)) + || (bytes_offset > 0xffff)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + *pwct = wct; + *pvwv = (uint16_t *)(cli_req->inbuf + wct_ofs + 1); + *pnum_bytes = num_bytes; + *pbytes = (uint8_t *)cli_req->inbuf + bytes_offset + 2; + + return NT_STATUS_OK; +} + +/** + * Decrypt a PDU, check the signature + * @param[in] cli The cli_state that received something + * @param[in] pdu The incoming bytes + * @retval error code + */ + + +static NTSTATUS validate_smb_crypto(struct cli_state *cli, char *pdu) +{ + NTSTATUS status; + + if ((IVAL(pdu, 4) != 0x424d53ff) /* 0xFF"SMB" */ + && (SVAL(pdu, 4) != 0x45ff)) /* 0xFF"E" */ { + DEBUG(10, ("Got non-SMB PDU\n")); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (cli_encryption_on(cli) && CVAL(pdu, 0) == 0) { + uint16_t enc_ctx_num; + + status = get_enc_ctx_num((uint8_t *)pdu, &enc_ctx_num); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("get_enc_ctx_num returned %s\n", + nt_errstr(status))); + return status; + } + + if (enc_ctx_num != cli->trans_enc_state->enc_ctx_num) { + DEBUG(10, ("wrong enc_ctx %d, expected %d\n", + enc_ctx_num, + cli->trans_enc_state->enc_ctx_num)); + return NT_STATUS_INVALID_HANDLE; + } + + status = common_decrypt_buffer(cli->trans_enc_state, pdu); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("common_decrypt_buffer returned %s\n", + nt_errstr(status))); + return status; + } + } + + if (!cli_check_sign_mac(cli, pdu)) { + DEBUG(10, ("cli_check_sign_mac failed\n")); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +/** + * A PDU has arrived on cli->evt_inbuf + * @param[in] cli The cli_state that received something + */ + +static void handle_incoming_pdu(struct cli_state *cli) +{ + struct cli_request *req; + uint16_t mid; + size_t raw_pdu_len, buf_len, pdu_len, rest_len; + char *pdu; + int i; + NTSTATUS status; + + int num_async; + + /* + * The encrypted PDU len might differ from the unencrypted one + */ + raw_pdu_len = smb_len(cli->evt_inbuf) + 4; + buf_len = talloc_get_size(cli->evt_inbuf); + rest_len = buf_len - raw_pdu_len; + + if (buf_len == raw_pdu_len) { + /* + * Optimal case: Exactly one PDU was in the socket buffer + */ + pdu = cli->evt_inbuf; + cli->evt_inbuf = NULL; + } + else { + DEBUG(11, ("buf_len = %d, raw_pdu_len = %d, splitting " + "buffer\n", (int)buf_len, (int)raw_pdu_len)); + + if (raw_pdu_len < rest_len) { + /* + * The PDU is shorter, talloc_memdup that one. + */ + pdu = (char *)talloc_memdup( + cli, cli->evt_inbuf, raw_pdu_len); + + memmove(cli->evt_inbuf, cli->evt_inbuf + raw_pdu_len, + buf_len - raw_pdu_len); + + cli->evt_inbuf = TALLOC_REALLOC_ARRAY( + NULL, cli->evt_inbuf, char, rest_len); + + if (pdu == NULL) { + status = NT_STATUS_NO_MEMORY; + goto invalidate_requests; + } + } + else { + /* + * The PDU is larger than the rest, talloc_memdup the + * rest + */ + pdu = cli->evt_inbuf; + + cli->evt_inbuf = (char *)talloc_memdup( + cli, pdu + raw_pdu_len, rest_len); + + if (cli->evt_inbuf == NULL) { + status = NT_STATUS_NO_MEMORY; + goto invalidate_requests; + } + } + } + + status = validate_smb_crypto(cli, pdu); + if (!NT_STATUS_IS_OK(status)) { + goto invalidate_requests; + } + + mid = SVAL(pdu, smb_mid); + + DEBUG(10, ("handle_incoming_pdu: got mid %d\n", mid)); + + for (req = cli->outstanding_requests; req; req = req->next) { + if (req->mid == mid) { + break; + } + } + + pdu_len = smb_len(pdu) + 4; + + if (req == NULL) { + DEBUG(3, ("Request for mid %d not found, dumping PDU\n", mid)); + + TALLOC_FREE(pdu); + return; + } + + req->inbuf = talloc_move(req, &pdu); + + /* + * Freeing the last async_req will free the req (see + * cli_async_req_destructor). So make a copy of req->num_async, we + * can't reference it in the last round. + */ + + num_async = req->num_async; + + for (i=0; i<num_async; i++) { + /** + * A request might have been talloc_free()'ed before we arrive + * here. It will have removed itself from req->async via its + * destructor cli_async_req_destructor(). + */ + if (req->async[i] != NULL) { + if (req->recv_helper.fn != NULL) { + req->recv_helper.fn(req->async[i]); + } else { + async_req_done(req->async[i]); + } + } + } + return; + + invalidate_requests: + + DEBUG(10, ("handle_incoming_pdu: Aborting with %s\n", + nt_errstr(status))); + + for (req = cli->outstanding_requests; req; req = req->next) { + async_req_error(req->async[0], status); + } + return; +} + +/** + * fd event callback. This is the basic connection to the socket + * @param[in] event_ctx The event context that called us + * @param[in] event The event that fired + * @param[in] flags EVENT_FD_READ | EVENT_FD_WRITE + * @param[in] p private_data, in this case the cli_state + */ + +static void cli_state_handler(struct event_context *event_ctx, + struct fd_event *event, uint16 flags, void *p) +{ + struct cli_state *cli = (struct cli_state *)p; + struct cli_request *req; + NTSTATUS status; + + DEBUG(11, ("cli_state_handler called with flags %d\n", flags)); + + if (flags & EVENT_FD_READ) { + int res, available; + size_t old_size, new_size; + char *tmp; + + res = ioctl(cli->fd, FIONREAD, &available); + if (res == -1) { + DEBUG(10, ("ioctl(FIONREAD) failed: %s\n", + strerror(errno))); + status = map_nt_error_from_unix(errno); + goto sock_error; + } + + if (available == 0) { + /* EOF */ + status = NT_STATUS_END_OF_FILE; + goto sock_error; + } + + old_size = talloc_get_size(cli->evt_inbuf); + new_size = old_size + available; + + if (new_size < old_size) { + /* wrap */ + status = NT_STATUS_UNEXPECTED_IO_ERROR; + goto sock_error; + } + + tmp = TALLOC_REALLOC_ARRAY(cli, cli->evt_inbuf, char, + new_size); + if (tmp == NULL) { + /* nomem */ + status = NT_STATUS_NO_MEMORY; + goto sock_error; + } + cli->evt_inbuf = tmp; + + res = recv(cli->fd, cli->evt_inbuf + old_size, available, 0); + if (res == -1) { + DEBUG(10, ("recv failed: %s\n", strerror(errno))); + status = map_nt_error_from_unix(errno); + goto sock_error; + } + + DEBUG(11, ("cli_state_handler: received %d bytes, " + "smb_len(evt_inbuf) = %d\n", (int)res, + smb_len(cli->evt_inbuf))); + + /* recv *might* have returned less than announced */ + new_size = old_size + res; + + /* shrink, so I don't expect errors here */ + cli->evt_inbuf = TALLOC_REALLOC_ARRAY(cli, cli->evt_inbuf, + char, new_size); + + while ((cli->evt_inbuf != NULL) + && ((smb_len(cli->evt_inbuf) + 4) <= new_size)) { + /* + * we've got a complete NBT level PDU in evt_inbuf + */ + handle_incoming_pdu(cli); + new_size = talloc_get_size(cli->evt_inbuf); + } + } + + if (flags & EVENT_FD_WRITE) { + size_t to_send; + ssize_t sent; + + for (req = cli->outstanding_requests; req; req = req->next) { + to_send = smb_len(req->outbuf)+4; + if (to_send > req->sent) { + break; + } + } + + if (req == NULL) { + if (cli->fd_event != NULL) { + event_fd_set_not_writeable(cli->fd_event); + } + return; + } + + sent = send(cli->fd, req->outbuf + req->sent, + to_send - req->sent, 0); + + if (sent < 0) { + status = map_nt_error_from_unix(errno); + goto sock_error; + } + + req->sent += sent; + + if (req->sent == to_send) { + return; + } + } + return; + + sock_error: + for (req = cli->outstanding_requests; req; req = req->next) { + int i; + for (i=0; i<req->num_async; i++) { + async_req_error(req->async[i], status); + } + } + TALLOC_FREE(cli->fd_event); + close(cli->fd); + cli->fd = -1; +} diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c new file mode 100644 index 0000000000..8ef14d7973 --- /dev/null +++ b/source3/libsmb/cliconnect.c @@ -0,0 +1,2011 @@ +/* + Unix SMB/CIFS implementation. + client connect/disconnect routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Andrew Bartlett 2001-2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +static const struct { + int prot; + const char *name; +} prots[] = { + {PROTOCOL_CORE,"PC NETWORK PROGRAM 1.0"}, + {PROTOCOL_COREPLUS,"MICROSOFT NETWORKS 1.03"}, + {PROTOCOL_LANMAN1,"MICROSOFT NETWORKS 3.0"}, + {PROTOCOL_LANMAN1,"LANMAN1.0"}, + {PROTOCOL_LANMAN2,"LM1.2X002"}, + {PROTOCOL_LANMAN2,"DOS LANMAN2.1"}, + {PROTOCOL_LANMAN2,"LANMAN2.1"}, + {PROTOCOL_LANMAN2,"Samba"}, + {PROTOCOL_NT1,"NT LANMAN 1.0"}, + {PROTOCOL_NT1,"NT LM 0.12"}, + {-1,NULL} +}; + +static const char *star_smbserver_name = "*SMBSERVER"; + +/** + * Set the user session key for a connection + * @param cli The cli structure to add it too + * @param session_key The session key used. (A copy of this is taken for the cli struct) + * + */ + +static void cli_set_session_key (struct cli_state *cli, const DATA_BLOB session_key) +{ + cli->user_session_key = data_blob(session_key.data, session_key.length); +} + +/**************************************************************************** + Do an old lanman2 style session setup. +****************************************************************************/ + +static NTSTATUS cli_session_setup_lanman2(struct cli_state *cli, + const char *user, + const char *pass, size_t passlen, + const char *workgroup) +{ + DATA_BLOB session_key = data_blob_null; + DATA_BLOB lm_response = data_blob_null; + fstring pword; + char *p; + + if (passlen > sizeof(pword)-1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* LANMAN servers predate NT status codes and Unicode and ignore those + smb flags so we must disable the corresponding default capabilities + that would otherwise cause the Unicode and NT Status flags to be + set (and even returned by the server) */ + + cli->capabilities &= ~(CAP_UNICODE | CAP_STATUS32); + + /* if in share level security then don't send a password now */ + if (!(cli->sec_mode & NEGOTIATE_SECURITY_USER_LEVEL)) + passlen = 0; + + if (passlen > 0 && (cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) && passlen != 24) { + /* Encrypted mode needed, and non encrypted password supplied. */ + lm_response = data_blob(NULL, 24); + if (!SMBencrypt(pass, cli->secblob.data,(uchar *)lm_response.data)) { + DEBUG(1, ("Password is > 14 chars in length, and is therefore incompatible with Lanman authentication\n")); + return NT_STATUS_ACCESS_DENIED; + } + } else if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) && passlen == 24) { + /* Encrypted mode needed, and encrypted password supplied. */ + lm_response = data_blob(pass, passlen); + } else if (passlen > 0) { + /* Plaintext mode needed, assume plaintext supplied. */ + passlen = clistr_push(cli, pword, pass, sizeof(pword), STR_TERMINATE); + lm_response = data_blob(pass, passlen); + } + + /* send a session setup command */ + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,10, 0, True); + SCVAL(cli->outbuf,smb_com,SMBsesssetupX); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,cli->max_xmit); + SSVAL(cli->outbuf,smb_vwv3,2); + SSVAL(cli->outbuf,smb_vwv4,1); + SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); + SSVAL(cli->outbuf,smb_vwv7,lm_response.length); + + p = smb_buf(cli->outbuf); + memcpy(p,lm_response.data,lm_response.length); + p += lm_response.length; + p += clistr_push(cli, p, user, -1, STR_TERMINATE|STR_UPPER); + p += clistr_push(cli, p, workgroup, -1, STR_TERMINATE|STR_UPPER); + p += clistr_push(cli, p, "Unix", -1, STR_TERMINATE); + p += clistr_push(cli, p, "Samba", -1, STR_TERMINATE); + cli_setup_bcc(cli, p); + + if (!cli_send_smb(cli) || !cli_receive_smb(cli)) { + return cli_nt_error(cli); + } + + show_msg(cli->inbuf); + + if (cli_is_error(cli)) { + return cli_nt_error(cli); + } + + /* use the returned vuid from now on */ + cli->vuid = SVAL(cli->inbuf,smb_uid); + fstrcpy(cli->user_name, user); + + if (session_key.data) { + /* Have plaintext orginal */ + cli_set_session_key(cli, session_key); + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Work out suitable capabilities to offer the server. +****************************************************************************/ + +static uint32 cli_session_setup_capabilities(struct cli_state *cli) +{ + uint32 capabilities = CAP_NT_SMBS; + + if (!cli->force_dos_errors) + capabilities |= CAP_STATUS32; + + if (cli->use_level_II_oplocks) + capabilities |= CAP_LEVEL_II_OPLOCKS; + + capabilities |= (cli->capabilities & (CAP_UNICODE|CAP_LARGE_FILES|CAP_LARGE_READX|CAP_LARGE_WRITEX|CAP_DFS)); + return capabilities; +} + +/**************************************************************************** + Do a NT1 guest session setup. +****************************************************************************/ + +static NTSTATUS cli_session_setup_guest(struct cli_state *cli) +{ + char *p; + uint32 capabilities = cli_session_setup_capabilities(cli); + + memset(cli->outbuf, '\0', smb_size); + cli_set_message(cli->outbuf,13,0,True); + SCVAL(cli->outbuf,smb_com,SMBsesssetupX); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,CLI_BUFFER_SIZE); + SSVAL(cli->outbuf,smb_vwv3,2); + SSVAL(cli->outbuf,smb_vwv4,cli->pid); + SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); + SSVAL(cli->outbuf,smb_vwv7,0); + SSVAL(cli->outbuf,smb_vwv8,0); + SIVAL(cli->outbuf,smb_vwv11,capabilities); + p = smb_buf(cli->outbuf); + p += clistr_push(cli, p, "", -1, STR_TERMINATE); /* username */ + p += clistr_push(cli, p, "", -1, STR_TERMINATE); /* workgroup */ + p += clistr_push(cli, p, "Unix", -1, STR_TERMINATE); + p += clistr_push(cli, p, "Samba", -1, STR_TERMINATE); + cli_setup_bcc(cli, p); + + if (!cli_send_smb(cli) || !cli_receive_smb(cli)) { + return cli_nt_error(cli); + } + + show_msg(cli->inbuf); + + if (cli_is_error(cli)) { + return cli_nt_error(cli); + } + + cli->vuid = SVAL(cli->inbuf,smb_uid); + + p = smb_buf(cli->inbuf); + p += clistr_pull(cli, cli->server_os, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_type, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_domain, p, sizeof(fstring), -1, STR_TERMINATE); + + if (strstr(cli->server_type, "Samba")) { + cli->is_samba = True; + } + + fstrcpy(cli->user_name, ""); + + return NT_STATUS_OK; +} + +/**************************************************************************** + Do a NT1 plaintext session setup. +****************************************************************************/ + +static NTSTATUS cli_session_setup_plaintext(struct cli_state *cli, + const char *user, const char *pass, + const char *workgroup) +{ + uint32 capabilities = cli_session_setup_capabilities(cli); + char *p; + fstring lanman; + + fstr_sprintf( lanman, "Samba %s", SAMBA_VERSION_STRING); + + memset(cli->outbuf, '\0', smb_size); + cli_set_message(cli->outbuf,13,0,True); + SCVAL(cli->outbuf,smb_com,SMBsesssetupX); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,CLI_BUFFER_SIZE); + SSVAL(cli->outbuf,smb_vwv3,2); + SSVAL(cli->outbuf,smb_vwv4,cli->pid); + SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); + SSVAL(cli->outbuf,smb_vwv8,0); + SIVAL(cli->outbuf,smb_vwv11,capabilities); + p = smb_buf(cli->outbuf); + + /* check wether to send the ASCII or UNICODE version of the password */ + + if ( (capabilities & CAP_UNICODE) == 0 ) { + p += clistr_push(cli, p, pass, -1, STR_TERMINATE); /* password */ + SSVAL(cli->outbuf,smb_vwv7,PTR_DIFF(p, smb_buf(cli->outbuf))); + } + else { + /* For ucs2 passwords clistr_push calls ucs2_align, which causes + * the space taken by the unicode password to be one byte too + * long (as we're on an odd byte boundary here). Reduce the + * count by 1 to cope with this. Fixes smbclient against NetApp + * servers which can't cope. Fix from + * bryan.kolodziej@allenlund.com in bug #3840. + */ + p += clistr_push(cli, p, pass, -1, STR_UNICODE|STR_TERMINATE); /* unicode password */ + SSVAL(cli->outbuf,smb_vwv8,PTR_DIFF(p, smb_buf(cli->outbuf))-1); + } + + p += clistr_push(cli, p, user, -1, STR_TERMINATE); /* username */ + p += clistr_push(cli, p, workgroup, -1, STR_TERMINATE); /* workgroup */ + p += clistr_push(cli, p, "Unix", -1, STR_TERMINATE); + p += clistr_push(cli, p, lanman, -1, STR_TERMINATE); + cli_setup_bcc(cli, p); + + if (!cli_send_smb(cli) || !cli_receive_smb(cli)) { + return cli_nt_error(cli); + } + + show_msg(cli->inbuf); + + if (cli_is_error(cli)) { + return cli_nt_error(cli); + } + + cli->vuid = SVAL(cli->inbuf,smb_uid); + p = smb_buf(cli->inbuf); + p += clistr_pull(cli, cli->server_os, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_type, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_domain, p, sizeof(fstring), -1, STR_TERMINATE); + fstrcpy(cli->user_name, user); + + if (strstr(cli->server_type, "Samba")) { + cli->is_samba = True; + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + do a NT1 NTLM/LM encrypted session setup - for when extended security + is not negotiated. + @param cli client state to create do session setup on + @param user username + @param pass *either* cleartext password (passlen !=24) or LM response. + @param ntpass NT response, implies ntpasslen >=24, implies pass is not clear + @param workgroup The user's domain. +****************************************************************************/ + +static NTSTATUS cli_session_setup_nt1(struct cli_state *cli, const char *user, + const char *pass, size_t passlen, + const char *ntpass, size_t ntpasslen, + const char *workgroup) +{ + uint32 capabilities = cli_session_setup_capabilities(cli); + DATA_BLOB lm_response = data_blob_null; + DATA_BLOB nt_response = data_blob_null; + DATA_BLOB session_key = data_blob_null; + NTSTATUS result; + char *p; + + if (passlen == 0) { + /* do nothing - guest login */ + } else if (passlen != 24) { + if (lp_client_ntlmv2_auth()) { + DATA_BLOB server_chal; + DATA_BLOB names_blob; + server_chal = data_blob(cli->secblob.data, MIN(cli->secblob.length, 8)); + + /* note that the 'workgroup' here is a best guess - we don't know + the server's domain at this point. The 'server name' is also + dodgy... + */ + names_blob = NTLMv2_generate_names_blob(cli->called.name, workgroup); + + if (!SMBNTLMv2encrypt(user, workgroup, pass, &server_chal, + &names_blob, + &lm_response, &nt_response, &session_key)) { + data_blob_free(&names_blob); + data_blob_free(&server_chal); + return NT_STATUS_ACCESS_DENIED; + } + data_blob_free(&names_blob); + data_blob_free(&server_chal); + + } else { + uchar nt_hash[16]; + E_md4hash(pass, nt_hash); + +#ifdef LANMAN_ONLY + nt_response = data_blob_null; +#else + nt_response = data_blob(NULL, 24); + SMBNTencrypt(pass,cli->secblob.data,nt_response.data); +#endif + /* non encrypted password supplied. Ignore ntpass. */ + if (lp_client_lanman_auth()) { + lm_response = data_blob(NULL, 24); + if (!SMBencrypt(pass,cli->secblob.data, lm_response.data)) { + /* Oops, the LM response is invalid, just put + the NT response there instead */ + data_blob_free(&lm_response); + lm_response = data_blob(nt_response.data, nt_response.length); + } + } else { + /* LM disabled, place NT# in LM field instead */ + lm_response = data_blob(nt_response.data, nt_response.length); + } + + session_key = data_blob(NULL, 16); +#ifdef LANMAN_ONLY + E_deshash(pass, session_key.data); + memset(&session_key.data[8], '\0', 8); +#else + SMBsesskeygen_ntv1(nt_hash, NULL, session_key.data); +#endif + } +#ifdef LANMAN_ONLY + cli_simple_set_signing(cli, session_key, lm_response); +#else + cli_simple_set_signing(cli, session_key, nt_response); +#endif + } else { + /* pre-encrypted password supplied. Only used for + security=server, can't do + signing because we don't have original key */ + + lm_response = data_blob(pass, passlen); + nt_response = data_blob(ntpass, ntpasslen); + } + + /* send a session setup command */ + memset(cli->outbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,13,0,True); + SCVAL(cli->outbuf,smb_com,SMBsesssetupX); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,CLI_BUFFER_SIZE); + SSVAL(cli->outbuf,smb_vwv3,2); + SSVAL(cli->outbuf,smb_vwv4,cli->pid); + SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); + SSVAL(cli->outbuf,smb_vwv7,lm_response.length); + SSVAL(cli->outbuf,smb_vwv8,nt_response.length); + SIVAL(cli->outbuf,smb_vwv11,capabilities); + p = smb_buf(cli->outbuf); + if (lm_response.length) { + memcpy(p,lm_response.data, lm_response.length); p += lm_response.length; + } + if (nt_response.length) { + memcpy(p,nt_response.data, nt_response.length); p += nt_response.length; + } + p += clistr_push(cli, p, user, -1, STR_TERMINATE); + + /* Upper case here might help some NTLMv2 implementations */ + p += clistr_push(cli, p, workgroup, -1, STR_TERMINATE|STR_UPPER); + p += clistr_push(cli, p, "Unix", -1, STR_TERMINATE); + p += clistr_push(cli, p, "Samba", -1, STR_TERMINATE); + cli_setup_bcc(cli, p); + + if (!cli_send_smb(cli) || !cli_receive_smb(cli)) { + result = cli_nt_error(cli); + goto end; + } + + /* show_msg(cli->inbuf); */ + + if (cli_is_error(cli)) { + result = cli_nt_error(cli); + goto end; + } + + /* use the returned vuid from now on */ + cli->vuid = SVAL(cli->inbuf,smb_uid); + + p = smb_buf(cli->inbuf); + p += clistr_pull(cli, cli->server_os, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_type, p, sizeof(fstring), -1, STR_TERMINATE); + p += clistr_pull(cli, cli->server_domain, p, sizeof(fstring), -1, STR_TERMINATE); + + if (strstr(cli->server_type, "Samba")) { + cli->is_samba = True; + } + + fstrcpy(cli->user_name, user); + + if (session_key.data) { + /* Have plaintext orginal */ + cli_set_session_key(cli, session_key); + } + + result = NT_STATUS_OK; +end: + data_blob_free(&lm_response); + data_blob_free(&nt_response); + data_blob_free(&session_key); + return result; +} + +/**************************************************************************** + Send a extended security session setup blob +****************************************************************************/ + +static bool cli_session_setup_blob_send(struct cli_state *cli, DATA_BLOB blob) +{ + uint32 capabilities = cli_session_setup_capabilities(cli); + char *p; + + capabilities |= CAP_EXTENDED_SECURITY; + + /* send a session setup command */ + memset(cli->outbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,12,0,True); + SCVAL(cli->outbuf,smb_com,SMBsesssetupX); + + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,CLI_BUFFER_SIZE); + SSVAL(cli->outbuf,smb_vwv3,2); + SSVAL(cli->outbuf,smb_vwv4,1); + SIVAL(cli->outbuf,smb_vwv5,0); + SSVAL(cli->outbuf,smb_vwv7,blob.length); + SIVAL(cli->outbuf,smb_vwv10,capabilities); + p = smb_buf(cli->outbuf); + memcpy(p, blob.data, blob.length); + p += blob.length; + p += clistr_push(cli, p, "Unix", -1, STR_TERMINATE); + p += clistr_push(cli, p, "Samba", -1, STR_TERMINATE); + cli_setup_bcc(cli, p); + return cli_send_smb(cli); +} + +/**************************************************************************** + Send a extended security session setup blob, returning a reply blob. +****************************************************************************/ + +static DATA_BLOB cli_session_setup_blob_receive(struct cli_state *cli) +{ + DATA_BLOB blob2 = data_blob_null; + char *p; + size_t len; + + if (!cli_receive_smb(cli)) + return blob2; + + show_msg(cli->inbuf); + + if (cli_is_error(cli) && !NT_STATUS_EQUAL(cli_nt_error(cli), + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + return blob2; + } + + /* use the returned vuid from now on */ + cli->vuid = SVAL(cli->inbuf,smb_uid); + + p = smb_buf(cli->inbuf); + + blob2 = data_blob(p, SVAL(cli->inbuf, smb_vwv3)); + + p += blob2.length; + p += clistr_pull(cli, cli->server_os, p, sizeof(fstring), -1, STR_TERMINATE); + + /* w2k with kerberos doesn't properly null terminate this field */ + len = smb_buflen(cli->inbuf) - PTR_DIFF(p, smb_buf(cli->inbuf)); + p += clistr_pull(cli, cli->server_type, p, sizeof(fstring), len, 0); + + return blob2; +} + +#ifdef HAVE_KRB5 +/**************************************************************************** + Send a extended security session setup blob, returning a reply blob. +****************************************************************************/ + +/* The following is calculated from : + * (smb_size-4) = 35 + * (smb_wcnt * 2) = 24 (smb_wcnt == 12 in cli_session_setup_blob_send() ) + * (strlen("Unix") + 1 + strlen("Samba") + 1) * 2 = 22 (unicode strings at + * end of packet. + */ + +#define BASE_SESSSETUP_BLOB_PACKET_SIZE (35 + 24 + 22) + +static bool cli_session_setup_blob(struct cli_state *cli, DATA_BLOB blob, DATA_BLOB session_key_krb5) +{ + int32 remaining = blob.length; + int32 cur = 0; + DATA_BLOB send_blob = data_blob_null; + int32 max_blob_size = 0; + DATA_BLOB receive_blob = data_blob_null; + + if (cli->max_xmit < BASE_SESSSETUP_BLOB_PACKET_SIZE + 1) { + DEBUG(0,("cli_session_setup_blob: cli->max_xmit too small " + "(was %u, need minimum %u)\n", + (unsigned int)cli->max_xmit, + BASE_SESSSETUP_BLOB_PACKET_SIZE)); + cli_set_nt_error(cli, NT_STATUS_INVALID_PARAMETER); + return False; + } + + max_blob_size = cli->max_xmit - BASE_SESSSETUP_BLOB_PACKET_SIZE; + + while ( remaining > 0) { + if (remaining >= max_blob_size) { + send_blob.length = max_blob_size; + remaining -= max_blob_size; + } else { + DATA_BLOB null_blob = data_blob_null; + + send_blob.length = remaining; + remaining = 0; + + /* This is the last packet in the sequence - turn signing on. */ + cli_simple_set_signing(cli, session_key_krb5, null_blob); + } + + send_blob.data = &blob.data[cur]; + cur += send_blob.length; + + DEBUG(10, ("cli_session_setup_blob: Remaining (%u) sending (%u) current (%u)\n", + (unsigned int)remaining, + (unsigned int)send_blob.length, + (unsigned int)cur )); + + if (!cli_session_setup_blob_send(cli, send_blob)) { + DEBUG(0, ("cli_session_setup_blob: send failed\n")); + return False; + } + + receive_blob = cli_session_setup_blob_receive(cli); + data_blob_free(&receive_blob); + + if (cli_is_error(cli) && + !NT_STATUS_EQUAL( cli_get_nt_error(cli), + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(0, ("cli_session_setup_blob: receive failed " + "(%s)\n", nt_errstr(cli_get_nt_error(cli)))); + cli->vuid = 0; + return False; + } + } + + return True; +} + +/**************************************************************************** + Use in-memory credentials cache +****************************************************************************/ + +static void use_in_memory_ccache(void) { + setenv(KRB5_ENV_CCNAME, "MEMORY:cliconnect", 1); +} + +/**************************************************************************** + Do a spnego/kerberos encrypted session setup. +****************************************************************************/ + +static ADS_STATUS cli_session_setup_kerberos(struct cli_state *cli, const char *principal, const char *workgroup) +{ + DATA_BLOB negTokenTarg; + DATA_BLOB session_key_krb5; + int rc; + + DEBUG(2,("Doing kerberos session setup\n")); + + /* generate the encapsulated kerberos5 ticket */ + rc = spnego_gen_negTokenTarg(principal, 0, &negTokenTarg, &session_key_krb5, 0, NULL); + + if (rc) { + DEBUG(1, ("cli_session_setup_kerberos: spnego_gen_negTokenTarg failed: %s\n", + error_message(rc))); + return ADS_ERROR_KRB5(rc); + } + +#if 0 + file_save("negTokenTarg.dat", negTokenTarg.data, negTokenTarg.length); +#endif + + if (!cli_session_setup_blob(cli, negTokenTarg, session_key_krb5)) { + data_blob_free(&negTokenTarg); + data_blob_free(&session_key_krb5); + return ADS_ERROR_NT(cli_nt_error(cli)); + } + + cli_set_session_key(cli, session_key_krb5); + + data_blob_free(&negTokenTarg); + data_blob_free(&session_key_krb5); + + if (cli_is_error(cli)) { + if (NT_STATUS_IS_OK(cli_nt_error(cli))) { + return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + } + } + return ADS_ERROR_NT(cli_nt_error(cli)); +} +#endif /* HAVE_KRB5 */ + + +/**************************************************************************** + Do a spnego/NTLMSSP encrypted session setup. +****************************************************************************/ + +static NTSTATUS cli_session_setup_ntlmssp(struct cli_state *cli, const char *user, + const char *pass, const char *domain) +{ + struct ntlmssp_state *ntlmssp_state; + NTSTATUS nt_status; + int turn = 1; + DATA_BLOB msg1; + DATA_BLOB blob = data_blob_null; + DATA_BLOB blob_in = data_blob_null; + DATA_BLOB blob_out = data_blob_null; + + cli_temp_set_signing(cli); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) { + return nt_status; + } + ntlmssp_want_feature(ntlmssp_state, NTLMSSP_FEATURE_SESSION_KEY); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, user))) { + return nt_status; + } + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, domain))) { + return nt_status; + } + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, pass))) { + return nt_status; + } + + do { + nt_status = ntlmssp_update(ntlmssp_state, + blob_in, &blob_out); + data_blob_free(&blob_in); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) || NT_STATUS_IS_OK(nt_status)) { + if (turn == 1) { + /* and wrap it in a SPNEGO wrapper */ + msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out); + } else { + /* wrap it in SPNEGO */ + msg1 = spnego_gen_auth(blob_out); + } + + /* now send that blob on its way */ + if (!cli_session_setup_blob_send(cli, msg1)) { + DEBUG(3, ("Failed to send NTLMSSP/SPNEGO blob to server!\n")); + nt_status = NT_STATUS_UNSUCCESSFUL; + } else { + blob = cli_session_setup_blob_receive(cli); + + nt_status = cli_nt_error(cli); + if (cli_is_error(cli) && NT_STATUS_IS_OK(nt_status)) { + if (cli->smb_rw_error == SMB_READ_BAD_SIG) { + nt_status = NT_STATUS_ACCESS_DENIED; + } else { + nt_status = NT_STATUS_UNSUCCESSFUL; + } + } + } + data_blob_free(&msg1); + } + + if (!blob.length) { + if (NT_STATUS_IS_OK(nt_status)) { + nt_status = NT_STATUS_UNSUCCESSFUL; + } + } else if ((turn == 1) && + NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DATA_BLOB tmp_blob = data_blob_null; + /* the server might give us back two challenges */ + if (!spnego_parse_challenge(blob, &blob_in, + &tmp_blob)) { + DEBUG(3,("Failed to parse challenges\n")); + nt_status = NT_STATUS_INVALID_PARAMETER; + } + data_blob_free(&tmp_blob); + } else { + if (!spnego_parse_auth_response(blob, nt_status, OID_NTLMSSP, + &blob_in)) { + DEBUG(3,("Failed to parse auth response\n")); + if (NT_STATUS_IS_OK(nt_status) + || NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) + nt_status = NT_STATUS_INVALID_PARAMETER; + } + } + data_blob_free(&blob); + data_blob_free(&blob_out); + turn++; + } while (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)); + + data_blob_free(&blob_in); + + if (NT_STATUS_IS_OK(nt_status)) { + + DATA_BLOB key = data_blob(ntlmssp_state->session_key.data, + ntlmssp_state->session_key.length); + DATA_BLOB null_blob = data_blob_null; + bool res; + + fstrcpy(cli->server_domain, ntlmssp_state->server_domain); + cli_set_session_key(cli, ntlmssp_state->session_key); + + res = cli_simple_set_signing(cli, key, null_blob); + + data_blob_free(&key); + + if (res) { + + /* 'resign' the last message, so we get the right sequence numbers + for checking the first reply from the server */ + cli_calculate_sign_mac(cli, cli->outbuf); + + if (!cli_check_sign_mac(cli, cli->inbuf)) { + nt_status = NT_STATUS_ACCESS_DENIED; + } + } + } + + /* we have a reference conter on ntlmssp_state, if we are signing + then the state will be kept by the signing engine */ + + ntlmssp_end(&ntlmssp_state); + + if (!NT_STATUS_IS_OK(nt_status)) { + cli->vuid = 0; + } + return nt_status; +} + +/**************************************************************************** + Do a spnego encrypted session setup. + + user_domain: The shortname of the domain the user/machine is a member of. + dest_realm: The realm we're connecting to, if NULL we use our default realm. +****************************************************************************/ + +ADS_STATUS cli_session_setup_spnego(struct cli_state *cli, const char *user, + const char *pass, const char *user_domain, + const char * dest_realm) +{ + char *principal = NULL; + char *OIDs[ASN1_MAX_OIDS]; + int i; + DATA_BLOB blob; + const char *p = NULL; + char *account = NULL; + + DEBUG(3,("Doing spnego session setup (blob length=%lu)\n", (unsigned long)cli->secblob.length)); + + /* the server might not even do spnego */ + if (cli->secblob.length <= 16) { + DEBUG(3,("server didn't supply a full spnego negprot\n")); + goto ntlmssp; + } + +#if 0 + file_save("negprot.dat", cli->secblob.data, cli->secblob.length); +#endif + + /* there is 16 bytes of GUID before the real spnego packet starts */ + blob = data_blob(cli->secblob.data+16, cli->secblob.length-16); + + /* The server sent us the first part of the SPNEGO exchange in the + * negprot reply. It is WRONG to depend on the principal sent in the + * negprot reply, but right now we do it. If we don't receive one, + * we try to best guess, then fall back to NTLM. */ + if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) { + data_blob_free(&blob); + return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER); + } + data_blob_free(&blob); + + /* make sure the server understands kerberos */ + for (i=0;OIDs[i];i++) { + DEBUG(3,("got OID=%s\n", OIDs[i])); + if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 || + strcmp(OIDs[i], OID_KERBEROS5) == 0) { + cli->got_kerberos_mechanism = True; + } + free(OIDs[i]); + } + + DEBUG(3,("got principal=%s\n", principal ? principal : "<null>")); + + fstrcpy(cli->user_name, user); + +#ifdef HAVE_KRB5 + /* If password is set we reauthenticate to kerberos server + * and do not store results */ + + if (cli->got_kerberos_mechanism && cli->use_kerberos) { + ADS_STATUS rc; + + if (pass && *pass) { + int ret; + + use_in_memory_ccache(); + ret = kerberos_kinit_password(user, pass, 0 /* no time correction for now */, NULL); + + if (ret){ + SAFE_FREE(principal); + DEBUG(0, ("Kinit failed: %s\n", error_message(ret))); + if (cli->fallback_after_kerberos) + goto ntlmssp; + return ADS_ERROR_KRB5(ret); + } + } + + /* If we get a bad principal, try to guess it if + we have a valid host NetBIOS name. + */ + if (strequal(principal, ADS_IGNORE_PRINCIPAL)) { + SAFE_FREE(principal); + } + + if (principal == NULL && + !is_ipaddress(cli->desthost) && + !strequal(star_smbserver_name, + cli->desthost)) { + char *realm = NULL; + char *machine = NULL; + char *host = NULL; + DEBUG(3,("cli_session_setup_spnego: got a " + "bad server principal, trying to guess ...\n")); + + host = strchr_m(cli->desthost, '.'); + if (host) { + machine = SMB_STRNDUP(cli->desthost, + host - cli->desthost); + } else { + machine = SMB_STRDUP(cli->desthost); + } + if (machine == NULL) { + return ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + } + + if (dest_realm) { + realm = SMB_STRDUP(dest_realm); + strupper_m(realm); + } else { + realm = kerberos_get_default_realm_from_ccache(); + } + if (realm && *realm) { + if (asprintf(&principal, "%s$@%s", + machine, realm) < 0) { + SAFE_FREE(machine); + SAFE_FREE(realm); + return ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + } + DEBUG(3,("cli_session_setup_spnego: guessed " + "server principal=%s\n", + principal ? principal : "<null>")); + } + SAFE_FREE(machine); + SAFE_FREE(realm); + } + + if (principal) { + rc = cli_session_setup_kerberos(cli, principal, + dest_realm); + if (ADS_ERR_OK(rc) || !cli->fallback_after_kerberos) { + SAFE_FREE(principal); + return rc; + } + } + } +#endif + + SAFE_FREE(principal); + +ntlmssp: + + account = talloc_strdup(talloc_tos(), user); + if (!account) { + return ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + } + + /* when falling back to ntlmssp while authenticating with a machine + * account strip off the realm - gd */ + + if ((p = strchr_m(user, '@')) != NULL) { + account[PTR_DIFF(p,user)] = '\0'; + } + + return ADS_ERROR_NT(cli_session_setup_ntlmssp(cli, account, pass, user_domain)); +} + +/**************************************************************************** + Send a session setup. The username and workgroup is in UNIX character + format and must be converted to DOS codepage format before sending. If the + password is in plaintext, the same should be done. +****************************************************************************/ + +NTSTATUS cli_session_setup(struct cli_state *cli, + const char *user, + const char *pass, int passlen, + const char *ntpass, int ntpasslen, + const char *workgroup) +{ + char *p; + fstring user2; + + if (user) { + fstrcpy(user2, user); + } else { + user2[0] ='\0'; + } + + if (!workgroup) { + workgroup = ""; + } + + /* allow for workgroups as part of the username */ + if ((p=strchr_m(user2,'\\')) || (p=strchr_m(user2,'/')) || + (p=strchr_m(user2,*lp_winbind_separator()))) { + *p = 0; + user = p+1; + workgroup = user2; + } + + if (cli->protocol < PROTOCOL_LANMAN1) { + return NT_STATUS_OK; + } + + /* now work out what sort of session setup we are going to + do. I have split this into separate functions to make the + flow a bit easier to understand (tridge) */ + + /* if its an older server then we have to use the older request format */ + + if (cli->protocol < PROTOCOL_NT1) { + if (!lp_client_lanman_auth() && passlen != 24 && (*pass)) { + DEBUG(1, ("Server requested LM password but 'client lanman auth'" + " is disabled\n")); + return NT_STATUS_ACCESS_DENIED; + } + + if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0 && + !lp_client_plaintext_auth() && (*pass)) { + DEBUG(1, ("Server requested plaintext password but " + "'client plaintext auth' is disabled\n")); + return NT_STATUS_ACCESS_DENIED; + } + + return cli_session_setup_lanman2(cli, user, pass, passlen, + workgroup); + } + + /* if no user is supplied then we have to do an anonymous connection. + passwords are ignored */ + + if (!user || !*user) + return cli_session_setup_guest(cli); + + /* if the server is share level then send a plaintext null + password at this point. The password is sent in the tree + connect */ + + if ((cli->sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) == 0) + return cli_session_setup_plaintext(cli, user, "", workgroup); + + /* if the server doesn't support encryption then we have to use + plaintext. The second password is ignored */ + + if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0) { + if (!lp_client_plaintext_auth() && (*pass)) { + DEBUG(1, ("Server requested plaintext password but " + "'client plaintext auth' is disabled\n")); + return NT_STATUS_ACCESS_DENIED; + } + return cli_session_setup_plaintext(cli, user, pass, workgroup); + } + + /* if the server supports extended security then use SPNEGO */ + + if (cli->capabilities & CAP_EXTENDED_SECURITY) { + ADS_STATUS status = cli_session_setup_spnego(cli, user, pass, + workgroup, NULL); + if (!ADS_ERR_OK(status)) { + DEBUG(3, ("SPNEGO login failed: %s\n", ads_errstr(status))); + return ads_ntstatus(status); + } + } else { + NTSTATUS status; + + /* otherwise do a NT1 style session setup */ + status = cli_session_setup_nt1(cli, user, pass, passlen, + ntpass, ntpasslen, workgroup); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3,("cli_session_setup: NT1 session setup " + "failed: %s\n", nt_errstr(status))); + return status; + } + } + + if (strstr(cli->server_type, "Samba")) { + cli->is_samba = True; + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Send a uloggoff. +*****************************************************************************/ + +bool cli_ulogoff(struct cli_state *cli) +{ + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,2,0,True); + SCVAL(cli->outbuf,smb_com,SMBulogoffX); + cli_setup_packet(cli); + SSVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,0); /* no additional info */ + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) + return False; + + if (cli_is_error(cli)) { + return False; + } + + cli->cnum = -1; + return True; +} + +/**************************************************************************** + Send a tconX. +****************************************************************************/ + +bool cli_send_tconX(struct cli_state *cli, + const char *share, const char *dev, const char *pass, int passlen) +{ + fstring fullshare, pword; + char *p; + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + fstrcpy(cli->share, share); + + /* in user level security don't send a password now */ + if (cli->sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) { + passlen = 1; + pass = ""; + } else if (!pass) { + DEBUG(1, ("Server not using user level security and no password supplied.\n")); + return False; + } + + if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) && + *pass && passlen != 24) { + if (!lp_client_lanman_auth()) { + DEBUG(1, ("Server requested LANMAN password " + "(share-level security) but " + "'client lanman auth' is disabled\n")); + return False; + } + + /* + * Non-encrypted passwords - convert to DOS codepage before encryption. + */ + passlen = 24; + SMBencrypt(pass,cli->secblob.data,(uchar *)pword); + } else { + if((cli->sec_mode & (NEGOTIATE_SECURITY_USER_LEVEL|NEGOTIATE_SECURITY_CHALLENGE_RESPONSE)) == 0) { + if (!lp_client_plaintext_auth() && (*pass)) { + DEBUG(1, ("Server requested plaintext " + "password but 'client plaintext " + "auth' is disabled\n")); + return False; + } + + /* + * Non-encrypted passwords - convert to DOS codepage before using. + */ + passlen = clistr_push(cli, pword, pass, sizeof(pword), STR_TERMINATE); + + } else { + if (passlen) { + memcpy(pword, pass, passlen); + } + } + } + + slprintf(fullshare, sizeof(fullshare)-1, + "\\\\%s\\%s", cli->desthost, share); + + cli_set_message(cli->outbuf,4, 0, True); + SCVAL(cli->outbuf,smb_com,SMBtconX); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,TCONX_FLAG_EXTENDED_RESPONSE); + SSVAL(cli->outbuf,smb_vwv3,passlen); + + p = smb_buf(cli->outbuf); + if (passlen) { + memcpy(p,pword,passlen); + } + p += passlen; + p += clistr_push(cli, p, fullshare, -1, STR_TERMINATE |STR_UPPER); + p += clistr_push(cli, p, dev, -1, STR_TERMINATE |STR_UPPER | STR_ASCII); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) + return False; + + if (cli_is_error(cli)) + return False; + + clistr_pull(cli, cli->dev, smb_buf(cli->inbuf), sizeof(fstring), -1, STR_TERMINATE|STR_ASCII); + + if (cli->protocol >= PROTOCOL_NT1 && + smb_buflen(cli->inbuf) == 3) { + /* almost certainly win95 - enable bug fixes */ + cli->win95 = True; + } + + /* Make sure that we have the optional support 16-bit field. WCT > 2 */ + /* Avoids issues when connecting to Win9x boxes sharing files */ + + cli->dfsroot = False; + if ( (CVAL(cli->inbuf, smb_wct))>2 && cli->protocol >= PROTOCOL_LANMAN2 ) + cli->dfsroot = (SVAL( cli->inbuf, smb_vwv2 ) & SMB_SHARE_IN_DFS) ? True : False; + + cli->cnum = SVAL(cli->inbuf,smb_tid); + return True; +} + +/**************************************************************************** + Send a tree disconnect. +****************************************************************************/ + +bool cli_tdis(struct cli_state *cli) +{ + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,0,0,True); + SCVAL(cli->outbuf,smb_com,SMBtdis); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) + return False; + + if (cli_is_error(cli)) { + return False; + } + + cli->cnum = -1; + return True; +} + +/**************************************************************************** + Send a negprot command. +****************************************************************************/ + +void cli_negprot_send(struct cli_state *cli) +{ + char *p; + int numprots; + + if (cli->protocol < PROTOCOL_NT1) + cli->use_spnego = False; + + memset(cli->outbuf,'\0',smb_size); + + /* setup the protocol strings */ + cli_set_message(cli->outbuf,0,0,True); + + p = smb_buf(cli->outbuf); + for (numprots=0; + prots[numprots].name && prots[numprots].prot<=cli->protocol; + numprots++) { + *p++ = 2; + p += clistr_push(cli, p, prots[numprots].name, -1, STR_TERMINATE); + } + + SCVAL(cli->outbuf,smb_com,SMBnegprot); + cli_setup_bcc(cli, p); + cli_setup_packet(cli); + + SCVAL(smb_buf(cli->outbuf),0,2); + + cli_send_smb(cli); +} + +/**************************************************************************** + Send a negprot command. +****************************************************************************/ + +bool cli_negprot(struct cli_state *cli) +{ + char *p; + int numprots; + int plength; + + if (cli->protocol < PROTOCOL_NT1) + cli->use_spnego = False; + + memset(cli->outbuf,'\0',smb_size); + + /* setup the protocol strings */ + for (plength=0,numprots=0; + prots[numprots].name && prots[numprots].prot<=cli->protocol; + numprots++) + plength += strlen(prots[numprots].name)+2; + + cli_set_message(cli->outbuf,0,plength,True); + + p = smb_buf(cli->outbuf); + for (numprots=0; + prots[numprots].name && prots[numprots].prot<=cli->protocol; + numprots++) { + *p++ = 2; + p += clistr_push(cli, p, prots[numprots].name, -1, STR_TERMINATE); + } + + SCVAL(cli->outbuf,smb_com,SMBnegprot); + cli_setup_packet(cli); + + SCVAL(smb_buf(cli->outbuf),0,2); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) + return False; + + show_msg(cli->inbuf); + + if (cli_is_error(cli) || + ((int)SVAL(cli->inbuf,smb_vwv0) >= numprots)) { + return(False); + } + + cli->protocol = prots[SVAL(cli->inbuf,smb_vwv0)].prot; + + if ((cli->protocol < PROTOCOL_NT1) && cli->sign_info.mandatory_signing) { + DEBUG(0,("cli_negprot: SMB signing is mandatory and the selected protocol level doesn't support it.\n")); + return False; + } + + if (cli->protocol >= PROTOCOL_NT1) { + struct timespec ts; + /* NT protocol */ + cli->sec_mode = CVAL(cli->inbuf,smb_vwv1); + cli->max_mux = SVAL(cli->inbuf, smb_vwv1+1); + cli->max_xmit = IVAL(cli->inbuf,smb_vwv3+1); + cli->sesskey = IVAL(cli->inbuf,smb_vwv7+1); + cli->serverzone = SVALS(cli->inbuf,smb_vwv15+1); + cli->serverzone *= 60; + /* this time arrives in real GMT */ + ts = interpret_long_date(cli->inbuf+smb_vwv11+1); + cli->servertime = ts.tv_sec; + cli->secblob = data_blob(smb_buf(cli->inbuf),smb_buflen(cli->inbuf)); + cli->capabilities = IVAL(cli->inbuf,smb_vwv9+1); + if (cli->capabilities & CAP_RAW_MODE) { + cli->readbraw_supported = True; + cli->writebraw_supported = True; + } + /* work out if they sent us a workgroup */ + if (!(cli->capabilities & CAP_EXTENDED_SECURITY) && + smb_buflen(cli->inbuf) > 8) { + clistr_pull(cli, cli->server_domain, + smb_buf(cli->inbuf)+8, sizeof(cli->server_domain), + smb_buflen(cli->inbuf)-8, STR_UNICODE|STR_NOALIGN); + } + + /* + * As signing is slow we only turn it on if either the client or + * the server require it. JRA. + */ + + if (cli->sec_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) { + /* Fail if server says signing is mandatory and we don't want to support it. */ + if (!cli->sign_info.allow_smb_signing) { + DEBUG(0,("cli_negprot: SMB signing is mandatory and we have disabled it.\n")); + return False; + } + cli->sign_info.negotiated_smb_signing = True; + cli->sign_info.mandatory_signing = True; + } else if (cli->sign_info.mandatory_signing && cli->sign_info.allow_smb_signing) { + /* Fail if client says signing is mandatory and the server doesn't support it. */ + if (!(cli->sec_mode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED)) { + DEBUG(1,("cli_negprot: SMB signing is mandatory and the server doesn't support it.\n")); + return False; + } + cli->sign_info.negotiated_smb_signing = True; + cli->sign_info.mandatory_signing = True; + } else if (cli->sec_mode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED) { + cli->sign_info.negotiated_smb_signing = True; + } + + if (cli->capabilities & (CAP_LARGE_READX|CAP_LARGE_WRITEX)) { + SAFE_FREE(cli->outbuf); + SAFE_FREE(cli->inbuf); + cli->outbuf = (char *)SMB_MALLOC(CLI_SAMBA_MAX_LARGE_READX_SIZE+LARGE_WRITEX_HDR_SIZE+SAFETY_MARGIN); + cli->inbuf = (char *)SMB_MALLOC(CLI_SAMBA_MAX_LARGE_READX_SIZE+LARGE_WRITEX_HDR_SIZE+SAFETY_MARGIN); + cli->bufsize = CLI_SAMBA_MAX_LARGE_READX_SIZE + LARGE_WRITEX_HDR_SIZE; + } + + } else if (cli->protocol >= PROTOCOL_LANMAN1) { + cli->use_spnego = False; + cli->sec_mode = SVAL(cli->inbuf,smb_vwv1); + cli->max_xmit = SVAL(cli->inbuf,smb_vwv2); + cli->max_mux = SVAL(cli->inbuf, smb_vwv3); + cli->sesskey = IVAL(cli->inbuf,smb_vwv6); + cli->serverzone = SVALS(cli->inbuf,smb_vwv10); + cli->serverzone *= 60; + /* this time is converted to GMT by make_unix_date */ + cli->servertime = cli_make_unix_date(cli,cli->inbuf+smb_vwv8); + cli->readbraw_supported = ((SVAL(cli->inbuf,smb_vwv5) & 0x1) != 0); + cli->writebraw_supported = ((SVAL(cli->inbuf,smb_vwv5) & 0x2) != 0); + cli->secblob = data_blob(smb_buf(cli->inbuf),smb_buflen(cli->inbuf)); + } else { + /* the old core protocol */ + cli->use_spnego = False; + cli->sec_mode = 0; + cli->serverzone = get_time_zone(time(NULL)); + } + + cli->max_xmit = MIN(cli->max_xmit, CLI_BUFFER_SIZE); + + /* a way to force ascii SMB */ + if (getenv("CLI_FORCE_ASCII")) + cli->capabilities &= ~CAP_UNICODE; + + return True; +} + +/**************************************************************************** + Send a session request. See rfc1002.txt 4.3 and 4.3.2. +****************************************************************************/ + +bool cli_session_request(struct cli_state *cli, + struct nmb_name *calling, struct nmb_name *called) +{ + char *p; + int len = 4; + + memcpy(&(cli->calling), calling, sizeof(*calling)); + memcpy(&(cli->called ), called , sizeof(*called )); + + /* put in the destination name */ + p = cli->outbuf+len; + name_mangle(cli->called .name, p, cli->called .name_type); + len += name_len(p); + + /* and my name */ + p = cli->outbuf+len; + name_mangle(cli->calling.name, p, cli->calling.name_type); + len += name_len(p); + + /* 445 doesn't have session request */ + if (cli->port == 445) + return True; + + /* send a session request (RFC 1002) */ + /* setup the packet length + * Remove four bytes from the length count, since the length + * field in the NBT Session Service header counts the number + * of bytes which follow. The cli_send_smb() function knows + * about this and accounts for those four bytes. + * CRH. + */ + len -= 4; + _smb_setlen(cli->outbuf,len); + SCVAL(cli->outbuf,0,0x81); + + cli_send_smb(cli); + DEBUG(5,("Sent session request\n")); + + if (!cli_receive_smb(cli)) + return False; + + if (CVAL(cli->inbuf,0) == 0x84) { + /* C. Hoch 9/14/95 Start */ + /* For information, here is the response structure. + * We do the byte-twiddling to for portability. + struct RetargetResponse{ + unsigned char type; + unsigned char flags; + int16 length; + int32 ip_addr; + int16 port; + }; + */ + uint16_t port = (CVAL(cli->inbuf,8)<<8)+CVAL(cli->inbuf,9); + struct in_addr dest_ip; + + /* SESSION RETARGET */ + putip((char *)&dest_ip,cli->inbuf+4); + in_addr_to_sockaddr_storage(&cli->dest_ss, dest_ip); + + cli->fd = open_socket_out(SOCK_STREAM, + &cli->dest_ss, + port, + LONG_CONNECT_TIMEOUT); + if (cli->fd == -1) + return False; + + DEBUG(3,("Retargeted\n")); + + set_socket_options(cli->fd, lp_socket_options()); + + /* Try again */ + { + static int depth; + bool ret; + if (depth > 4) { + DEBUG(0,("Retarget recursion - failing\n")); + return False; + } + depth++; + ret = cli_session_request(cli, calling, called); + depth--; + return ret; + } + } /* C. Hoch 9/14/95 End */ + + if (CVAL(cli->inbuf,0) != 0x82) { + /* This is the wrong place to put the error... JRA. */ + cli->rap_error = CVAL(cli->inbuf,4); + return False; + } + return(True); +} + +/**************************************************************************** + Open the client sockets. +****************************************************************************/ + +NTSTATUS cli_connect(struct cli_state *cli, + const char *host, + struct sockaddr_storage *dest_ss) + +{ + int name_type = 0x20; + TALLOC_CTX *frame = talloc_stackframe(); + unsigned int num_addrs = 0; + unsigned int i = 0; + struct sockaddr_storage *ss_arr = NULL; + char *p = NULL; + + /* reasonable default hostname */ + if (!host) { + host = star_smbserver_name; + } + + fstrcpy(cli->desthost, host); + + /* allow hostnames of the form NAME#xx and do a netbios lookup */ + if ((p = strchr(cli->desthost, '#'))) { + name_type = strtol(p+1, NULL, 16); + *p = 0; + } + + if (!dest_ss || is_zero_addr(dest_ss)) { + NTSTATUS status =resolve_name_list(frame, + cli->desthost, + name_type, + &ss_arr, + &num_addrs); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return NT_STATUS_BAD_NETWORK_NAME; + } + } else { + num_addrs = 1; + ss_arr = TALLOC_P(frame, struct sockaddr_storage); + if (!ss_arr) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + *ss_arr = *dest_ss; + } + + for (i = 0; i < num_addrs; i++) { + cli->dest_ss = ss_arr[i]; + if (getenv("LIBSMB_PROG")) { + cli->fd = sock_exec(getenv("LIBSMB_PROG")); + } else { + /* try 445 first, then 139 */ + uint16_t port = cli->port?cli->port:445; + cli->fd = open_socket_out(SOCK_STREAM, &cli->dest_ss, + port, cli->timeout); + if (cli->fd == -1 && cli->port == 0) { + port = 139; + cli->fd = open_socket_out(SOCK_STREAM, &cli->dest_ss, + port, cli->timeout); + } + if (cli->fd != -1) { + cli->port = port; + } + } + if (cli->fd == -1) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &ss_arr[i]); + DEBUG(2,("Error connecting to %s (%s)\n", + dest_ss?addr:host,strerror(errno))); + } else { + /* Exit from loop on first connection. */ + break; + } + } + + if (cli->fd == -1) { + TALLOC_FREE(frame); + return map_nt_error_from_unix(errno); + } + + if (dest_ss) { + *dest_ss = cli->dest_ss; + } + + set_socket_options(cli->fd, lp_socket_options()); + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +/** + establishes a connection to after the negprot. + @param output_cli A fully initialised cli structure, non-null only on success + @param dest_host The netbios name of the remote host + @param dest_ss (optional) The the destination IP, NULL for name based lookup + @param port (optional) The destination port (0 for default) + @param retry bool. Did this connection fail with a retryable error ? + +*/ +NTSTATUS cli_start_connection(struct cli_state **output_cli, + const char *my_name, + const char *dest_host, + struct sockaddr_storage *dest_ss, int port, + int signing_state, int flags, + bool *retry) +{ + NTSTATUS nt_status; + struct nmb_name calling; + struct nmb_name called; + struct cli_state *cli; + struct sockaddr_storage ss; + + if (retry) + *retry = False; + + if (!my_name) + my_name = global_myname(); + + if (!(cli = cli_initialise())) { + return NT_STATUS_NO_MEMORY; + } + + make_nmb_name(&calling, my_name, 0x0); + make_nmb_name(&called , dest_host, 0x20); + + if (cli_set_port(cli, port) != port) { + cli_shutdown(cli); + return NT_STATUS_UNSUCCESSFUL; + } + + cli_set_timeout(cli, 10000); /* 10 seconds. */ + + if (dest_ss) { + ss = *dest_ss; + } else { + zero_addr(&ss); + } + +again: + + DEBUG(3,("Connecting to host=%s\n", dest_host)); + + nt_status = cli_connect(cli, dest_host, &ss); + if (!NT_STATUS_IS_OK(nt_status)) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &ss); + DEBUG(1,("cli_start_connection: failed to connect to %s (%s). Error %s\n", + nmb_namestr(&called), addr, nt_errstr(nt_status) )); + cli_shutdown(cli); + return nt_status; + } + + if (retry) + *retry = True; + + if (!cli_session_request(cli, &calling, &called)) { + char *p; + DEBUG(1,("session request to %s failed (%s)\n", + called.name, cli_errstr(cli))); + if ((p=strchr(called.name, '.')) && !is_ipaddress(called.name)) { + *p = 0; + goto again; + } + if (strcmp(called.name, star_smbserver_name)) { + make_nmb_name(&called , star_smbserver_name, 0x20); + goto again; + } + return NT_STATUS_BAD_NETWORK_NAME; + } + + cli_setup_signing_state(cli, signing_state); + + if (flags & CLI_FULL_CONNECTION_DONT_SPNEGO) + cli->use_spnego = False; + else if (flags & CLI_FULL_CONNECTION_USE_KERBEROS) + cli->use_kerberos = True; + + if ((flags & CLI_FULL_CONNECTION_FALLBACK_AFTER_KERBEROS) && + cli->use_kerberos) { + cli->fallback_after_kerberos = true; + } + + if (!cli_negprot(cli)) { + DEBUG(1,("failed negprot\n")); + nt_status = cli_nt_error(cli); + if (NT_STATUS_IS_OK(nt_status)) { + nt_status = NT_STATUS_UNSUCCESSFUL; + } + cli_shutdown(cli); + return nt_status; + } + + *output_cli = cli; + return NT_STATUS_OK; +} + + +/** + establishes a connection right up to doing tconX, password specified. + @param output_cli A fully initialised cli structure, non-null only on success + @param dest_host The netbios name of the remote host + @param dest_ip (optional) The the destination IP, NULL for name based lookup + @param port (optional) The destination port (0 for default) + @param service (optional) The share to make the connection to. Should be 'unqualified' in any way. + @param service_type The 'type' of serivice. + @param user Username, unix string + @param domain User's domain + @param password User's password, unencrypted unix string. + @param retry bool. Did this connection fail with a retryable error ? +*/ + +NTSTATUS cli_full_connection(struct cli_state **output_cli, + const char *my_name, + const char *dest_host, + struct sockaddr_storage *dest_ss, int port, + const char *service, const char *service_type, + const char *user, const char *domain, + const char *password, int flags, + int signing_state, + bool *retry) +{ + NTSTATUS nt_status; + struct cli_state *cli = NULL; + int pw_len = password ? strlen(password)+1 : 0; + + *output_cli = NULL; + + if (password == NULL) { + password = ""; + } + + nt_status = cli_start_connection(&cli, my_name, dest_host, + dest_ss, port, signing_state, + flags, retry); + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = cli_session_setup(cli, user, password, pw_len, password, + pw_len, domain); + if (!NT_STATUS_IS_OK(nt_status)) { + + if (!(flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK)) { + DEBUG(1,("failed session setup with %s\n", + nt_errstr(nt_status))); + cli_shutdown(cli); + return nt_status; + } + + nt_status = cli_session_setup(cli, "", "", 0, "", 0, domain); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1,("anonymous failed session setup with %s\n", + nt_errstr(nt_status))); + cli_shutdown(cli); + return nt_status; + } + } + + if (service) { + if (!cli_send_tconX(cli, service, service_type, password, pw_len)) { + nt_status = cli_nt_error(cli); + DEBUG(1,("failed tcon_X with %s\n", nt_errstr(nt_status))); + cli_shutdown(cli); + if (NT_STATUS_IS_OK(nt_status)) { + nt_status = NT_STATUS_UNSUCCESSFUL; + } + return nt_status; + } + } + + cli_init_creds(cli, user, domain, password); + + *output_cli = cli; + return NT_STATUS_OK; +} + +/**************************************************************************** + Attempt a NetBIOS session request, falling back to *SMBSERVER if needed. +****************************************************************************/ + +bool attempt_netbios_session_request(struct cli_state **ppcli, const char *srchost, const char *desthost, + struct sockaddr_storage *pdest_ss) +{ + struct nmb_name calling, called; + + make_nmb_name(&calling, srchost, 0x0); + + /* + * If the called name is an IP address + * then use *SMBSERVER immediately. + */ + + if(is_ipaddress(desthost)) { + make_nmb_name(&called, star_smbserver_name, 0x20); + } else { + make_nmb_name(&called, desthost, 0x20); + } + + if (!cli_session_request(*ppcli, &calling, &called)) { + NTSTATUS status; + struct nmb_name smbservername; + + make_nmb_name(&smbservername, star_smbserver_name, 0x20); + + /* + * If the name wasn't *SMBSERVER then + * try with *SMBSERVER if the first name fails. + */ + + if (nmb_name_equal(&called, &smbservername)) { + + /* + * The name used was *SMBSERVER, don't bother with another name. + */ + + DEBUG(0,("attempt_netbios_session_request: %s rejected the session for name *SMBSERVER \ +with error %s.\n", desthost, cli_errstr(*ppcli) )); + return False; + } + + /* Try again... */ + cli_shutdown(*ppcli); + + *ppcli = cli_initialise(); + if (!*ppcli) { + /* Out of memory... */ + return False; + } + + status = cli_connect(*ppcli, desthost, pdest_ss); + if (!NT_STATUS_IS_OK(status) || + !cli_session_request(*ppcli, &calling, &smbservername)) { + DEBUG(0,("attempt_netbios_session_request: %s rejected the session for \ +name *SMBSERVER with error %s\n", desthost, cli_errstr(*ppcli) )); + return False; + } + } + + return True; +} + +/**************************************************************************** + Send an old style tcon. +****************************************************************************/ +NTSTATUS cli_raw_tcon(struct cli_state *cli, + const char *service, const char *pass, const char *dev, + uint16 *max_xmit, uint16 *tid) +{ + char *p; + + if (!lp_client_plaintext_auth() && (*pass)) { + DEBUG(1, ("Server requested plaintext password but 'client " + "plaintext auth' is disabled\n")); + return NT_STATUS_ACCESS_DENIED; + } + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf, 0, 0, True); + SCVAL(cli->outbuf,smb_com,SMBtcon); + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + *p++ = 4; p += clistr_push(cli, p, service, -1, STR_TERMINATE | STR_NOALIGN); + *p++ = 4; p += clistr_push(cli, p, pass, -1, STR_TERMINATE | STR_NOALIGN); + *p++ = 4; p += clistr_push(cli, p, dev, -1, STR_TERMINATE | STR_NOALIGN); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + if (cli_is_error(cli)) { + return cli_nt_error(cli); + } + + *max_xmit = SVAL(cli->inbuf, smb_vwv0); + *tid = SVAL(cli->inbuf, smb_vwv1); + + return NT_STATUS_OK; +} + +/* Return a cli_state pointing at the IPC$ share for the given server */ + +struct cli_state *get_ipc_connect(char *server, + struct sockaddr_storage *server_ss, + const struct user_auth_info *user_info) +{ + struct cli_state *cli; + NTSTATUS nt_status; + uint32_t flags = CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK; + + if (user_info->use_kerberos) { + flags |= CLI_FULL_CONNECTION_USE_KERBEROS; + } + + nt_status = cli_full_connection(&cli, NULL, server, server_ss, 0, "IPC$", "IPC", + user_info->username ? user_info->username : "", + lp_workgroup(), + user_info->password ? user_info->password : "", + flags, + Undefined, NULL); + + if (NT_STATUS_IS_OK(nt_status)) { + return cli; + } else if (is_ipaddress(server)) { + /* windows 9* needs a correct NMB name for connections */ + fstring remote_name; + + if (name_status_find("*", 0, 0, server_ss, remote_name)) { + cli = get_ipc_connect(remote_name, server_ss, user_info); + if (cli) + return cli; + } + } + return NULL; +} + +/* + * Given the IP address of a master browser on the network, return its + * workgroup and connect to it. + * + * This function is provided to allow additional processing beyond what + * get_ipc_connect_master_ip_bcast() does, e.g. to retrieve the list of master + * browsers and obtain each master browsers' list of domains (in case the + * first master browser is recently on the network and has not yet + * synchronized with other master browsers and therefore does not yet have the + * entire network browse list) + */ + +struct cli_state *get_ipc_connect_master_ip(TALLOC_CTX *ctx, + struct ip_service *mb_ip, + const struct user_auth_info *user_info, + char **pp_workgroup_out) +{ + char addr[INET6_ADDRSTRLEN]; + fstring name; + struct cli_state *cli; + struct sockaddr_storage server_ss; + + *pp_workgroup_out = NULL; + + print_sockaddr(addr, sizeof(addr), &mb_ip->ss); + DEBUG(99, ("Looking up name of master browser %s\n", + addr)); + + /* + * Do a name status query to find out the name of the master browser. + * We use <01><02>__MSBROWSE__<02>#01 if *#00 fails because a domain + * master browser will not respond to a wildcard query (or, at least, + * an NT4 server acting as the domain master browser will not). + * + * We might be able to use ONLY the query on MSBROWSE, but that's not + * yet been tested with all Windows versions, so until it is, leave + * the original wildcard query as the first choice and fall back to + * MSBROWSE if the wildcard query fails. + */ + if (!name_status_find("*", 0, 0x1d, &mb_ip->ss, name) && + !name_status_find(MSBROWSE, 1, 0x1d, &mb_ip->ss, name)) { + + DEBUG(99, ("Could not retrieve name status for %s\n", + addr)); + return NULL; + } + + if (!find_master_ip(name, &server_ss)) { + DEBUG(99, ("Could not find master ip for %s\n", name)); + return NULL; + } + + *pp_workgroup_out = talloc_strdup(ctx, name); + + DEBUG(4, ("found master browser %s, %s\n", name, addr)); + + print_sockaddr(addr, sizeof(addr), &server_ss); + cli = get_ipc_connect(addr, &server_ss, user_info); + + return cli; +} + +/* + * Return the IP address and workgroup of a master browser on the network, and + * connect to it. + */ + +struct cli_state *get_ipc_connect_master_ip_bcast(TALLOC_CTX *ctx, + const struct user_auth_info *user_info, + char **pp_workgroup_out) +{ + struct ip_service *ip_list; + struct cli_state *cli; + int i, count; + + *pp_workgroup_out = NULL; + + DEBUG(99, ("Do broadcast lookup for workgroups on local network\n")); + + /* Go looking for workgroups by broadcasting on the local network */ + + if (!NT_STATUS_IS_OK(name_resolve_bcast(MSBROWSE, 1, &ip_list, + &count))) { + DEBUG(99, ("No master browsers responded\n")); + return False; + } + + for (i = 0; i < count; i++) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &ip_list[i].ss); + DEBUG(99, ("Found master browser %s\n", addr)); + + cli = get_ipc_connect_master_ip(ctx, &ip_list[i], + user_info, pp_workgroup_out); + if (cli) + return(cli); + } + + return NULL; +} diff --git a/source3/libsmb/clidfs.c b/source3/libsmb/clidfs.c new file mode 100644 index 0000000000..7b63f9535e --- /dev/null +++ b/source3/libsmb/clidfs.c @@ -0,0 +1,1125 @@ +/* + Unix SMB/CIFS implementation. + client connect/disconnect routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Gerald (Jerry) Carter 2004 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/******************************************************************** + Important point. + + DFS paths are *always* of the form \server\share\<pathname> (the \ characters + are not C escaped here). + + - but if we're using POSIX paths then <pathname> may contain + '/' separators, not '\\' separators. So cope with '\\' or '/' + as a separator when looking at the pathname part.... JRA. +********************************************************************/ + +struct client_connection { + struct client_connection *prev, *next; + struct cli_state *cli; + char *mount; +}; + +/* global state....globals reek! */ +int max_protocol = PROTOCOL_NT1; + +static struct cm_cred_struct { + char *username; + char *password; + bool got_pass; + bool use_kerberos; + bool fallback_after_kerberos; + int signing_state; +} cm_creds; + +static void cm_set_password(const char *newpass); + +static int port; +static int name_type = 0x20; +static bool have_ip; +static struct sockaddr_storage dest_ss; + +static struct client_connection *connections; + +static bool cli_check_msdfs_proxy(TALLOC_CTX *ctx, + struct cli_state *cli, + const char *sharename, + char **pp_newserver, + char **pp_newshare, + bool force_encrypt, + const char *username, + const char *password, + const char *domain); + +/******************************************************************** + Ensure a connection is encrypted. +********************************************************************/ + +NTSTATUS cli_cm_force_encryption(struct cli_state *c, + const char *username, + const char *password, + const char *domain, + const char *sharename) +{ + NTSTATUS status = cli_force_encryption(c, + username, + password, + domain); + + if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_SUPPORTED)) { + d_printf("Encryption required and " + "server that doesn't support " + "UNIX extensions - failing connect\n"); + } else if (NT_STATUS_EQUAL(status,NT_STATUS_UNKNOWN_REVISION)) { + d_printf("Encryption required and " + "can't get UNIX CIFS extensions " + "version from server.\n"); + } else if (NT_STATUS_EQUAL(status,NT_STATUS_UNSUPPORTED_COMPRESSION)) { + d_printf("Encryption required and " + "share %s doesn't support " + "encryption.\n", sharename); + } else if (!NT_STATUS_IS_OK(status)) { + d_printf("Encryption required and " + "setup failed with error %s.\n", + nt_errstr(status)); + } + + return status; +} + +/******************************************************************** + Return a connection to a server. +********************************************************************/ + +static struct cli_state *do_connect(TALLOC_CTX *ctx, + const char *server, + const char *share, + bool show_sessetup, + bool force_encrypt) +{ + struct cli_state *c = NULL; + struct nmb_name called, calling; + const char *server_n; + struct sockaddr_storage ss; + char *servicename; + char *sharename; + char *newserver, *newshare; + const char *username; + const char *password; + NTSTATUS status; + + /* make a copy so we don't modify the global string 'service' */ + servicename = talloc_strdup(ctx,share); + if (!servicename) { + return NULL; + } + sharename = servicename; + if (*sharename == '\\') { + server = sharename+2; + sharename = strchr_m(server,'\\'); + if (!sharename) { + return NULL; + } + *sharename = 0; + sharename++; + } + + server_n = server; + + zero_addr(&ss); + + make_nmb_name(&calling, global_myname(), 0x0); + make_nmb_name(&called , server, name_type); + + again: + zero_addr(&ss); + if (have_ip) + ss = dest_ss; + + /* have to open a new connection */ + if (!(c=cli_initialise()) || (cli_set_port(c, port) != port)) { + d_printf("Connection to %s failed\n", server_n); + if (c) { + cli_shutdown(c); + } + return NULL; + } + status = cli_connect(c, server_n, &ss); + if (!NT_STATUS_IS_OK(status)) { + d_printf("Connection to %s failed (Error %s)\n", + server_n, + nt_errstr(status)); + cli_shutdown(c); + return NULL; + } + + c->protocol = max_protocol; + c->use_kerberos = cm_creds.use_kerberos; + c->fallback_after_kerberos = cm_creds.fallback_after_kerberos; + cli_setup_signing_state(c, cm_creds.signing_state); + + if (!cli_session_request(c, &calling, &called)) { + char *p; + d_printf("session request to %s failed (%s)\n", + called.name, cli_errstr(c)); + cli_shutdown(c); + c = NULL; + if ((p=strchr_m(called.name, '.'))) { + *p = 0; + goto again; + } + if (strcmp(called.name, "*SMBSERVER")) { + make_nmb_name(&called , "*SMBSERVER", 0x20); + goto again; + } + return NULL; + } + + DEBUG(4,(" session request ok\n")); + + if (!cli_negprot(c)) { + d_printf("protocol negotiation failed\n"); + cli_shutdown(c); + return NULL; + } + + if (!cm_creds.got_pass && !cm_creds.use_kerberos) { + char *label = NULL; + char *pass; + label = talloc_asprintf(ctx, "Enter %s's password: ", + cm_creds.username); + pass = getpass(label); + if (pass) { + cm_set_password(pass); + } + TALLOC_FREE(label); + } + + username = cm_creds.username ? cm_creds.username : ""; + password = cm_creds.password ? cm_creds.password : ""; + + if (!NT_STATUS_IS_OK(cli_session_setup(c, username, + password, strlen(password), + password, strlen(password), + lp_workgroup()))) { + /* If a password was not supplied then + * try again with a null username. */ + if (password[0] || !username[0] || cm_creds.use_kerberos || + !NT_STATUS_IS_OK(cli_session_setup(c, "", + "", 0, + "", 0, + lp_workgroup()))) { + d_printf("session setup failed: %s\n", cli_errstr(c)); + if (NT_STATUS_V(cli_nt_error(c)) == + NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED)) + d_printf("did you forget to run kinit?\n"); + cli_shutdown(c); + return NULL; + } + d_printf("Anonymous login successful\n"); + } + + if ( show_sessetup ) { + if (*c->server_domain) { + DEBUG(0,("Domain=[%s] OS=[%s] Server=[%s]\n", + c->server_domain,c->server_os,c->server_type)); + } else if (*c->server_os || *c->server_type) { + DEBUG(0,("OS=[%s] Server=[%s]\n", + c->server_os,c->server_type)); + } + } + DEBUG(4,(" session setup ok\n")); + + /* here's the fun part....to support 'msdfs proxy' shares + (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL + here before trying to connect to the original share. + check_dfs_proxy() will fail if it is a normal share. */ + + if ((c->capabilities & CAP_DFS) && + cli_check_msdfs_proxy(ctx, c, sharename, + &newserver, &newshare, + force_encrypt, + username, + password, + lp_workgroup())) { + cli_shutdown(c); + return do_connect(ctx, newserver, + newshare, false, force_encrypt); + } + + /* must be a normal share */ + + if (!cli_send_tconX(c, sharename, "?????", + password, strlen(password)+1)) { + d_printf("tree connect failed: %s\n", cli_errstr(c)); + cli_shutdown(c); + return NULL; + } + + if (force_encrypt) { + status = cli_cm_force_encryption(c, + username, + password, + lp_workgroup(), + sharename); + if (!NT_STATUS_IS_OK(status)) { + cli_shutdown(c); + return NULL; + } + } + + DEBUG(4,(" tconx ok\n")); + return c; +} + +/**************************************************************************** +****************************************************************************/ + +static void cli_cm_set_mntpoint(struct cli_state *c, const char *mnt) +{ + struct client_connection *p; + int i; + + for (p=connections,i=0; p; p=p->next,i++) { + if (strequal(p->cli->desthost, c->desthost) && + strequal(p->cli->share, c->share)) { + break; + } + } + + if (p) { + char *name = clean_name(NULL, p->mount); + if (!name) { + return; + } + p->mount = talloc_strdup(p, name); + TALLOC_FREE(name); + } +} + +/**************************************************************************** +****************************************************************************/ + +const char *cli_cm_get_mntpoint(struct cli_state *c) +{ + struct client_connection *p; + int i; + + for (p=connections,i=0; p; p=p->next,i++) { + if (strequal(p->cli->desthost, c->desthost) && + strequal(p->cli->share, c->share)) { + break; + } + } + + if (p) { + return p->mount; + } + return NULL; +} + +/******************************************************************** + Add a new connection to the list +********************************************************************/ + +static struct cli_state *cli_cm_connect(TALLOC_CTX *ctx, + struct cli_state *referring_cli, + const char *server, + const char *share, + bool show_hdr, + bool force_encrypt) +{ + struct client_connection *node; + + /* NB This must be the null context here... JRA. */ + node = TALLOC_ZERO_ARRAY(NULL, struct client_connection, 1); + if (!node) { + return NULL; + } + + node->cli = do_connect(ctx, server, share, show_hdr, force_encrypt); + + if ( !node->cli ) { + TALLOC_FREE( node ); + return NULL; + } + + DLIST_ADD( connections, node ); + + cli_cm_set_mntpoint(node->cli, ""); + + if (referring_cli && referring_cli->posix_capabilities) { + uint16 major, minor; + uint32 caplow, caphigh; + if (cli_unix_extensions_version(node->cli, &major, + &minor, &caplow, &caphigh)) { + cli_set_unix_extensions_capabilities(node->cli, + major, minor, + caplow, caphigh); + } + } + + return node->cli; +} + +/******************************************************************** + Return a connection to a server. +********************************************************************/ + +static struct cli_state *cli_cm_find(const char *server, const char *share) +{ + struct client_connection *p; + + for (p=connections; p; p=p->next) { + if ( strequal(server, p->cli->desthost) && + strequal(share,p->cli->share)) { + return p->cli; + } + } + + return NULL; +} + +/**************************************************************************** + Open a client connection to a \\server\share. Set's the current *cli + global variable as a side-effect (but only if the connection is successful). +****************************************************************************/ + +struct cli_state *cli_cm_open(TALLOC_CTX *ctx, + struct cli_state *referring_cli, + const char *server, + const char *share, + bool show_hdr, + bool force_encrypt) +{ + struct cli_state *c; + + /* try to reuse an existing connection */ + + c = cli_cm_find(server, share); + if (!c) { + c = cli_cm_connect(ctx, referring_cli, + server, share, show_hdr, force_encrypt); + } + + return c; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_shutdown(void) +{ + struct client_connection *p, *x; + + for (p=connections; p;) { + cli_shutdown(p->cli); + x = p; + p = p->next; + + TALLOC_FREE(x); + } + + connections = NULL; + return; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_display(void) +{ + struct client_connection *p; + int i; + + for ( p=connections,i=0; p; p=p->next,i++ ) { + d_printf("%d:\tserver=%s, share=%s\n", + i, p->cli->desthost, p->cli->share ); + } +} + +/**************************************************************************** +****************************************************************************/ + +static void cm_set_password(const char *newpass) +{ + SAFE_FREE(cm_creds.password); + cm_creds.password = SMB_STRDUP(newpass); + if (cm_creds.password) { + cm_creds.got_pass = true; + } +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_credentials(void) +{ + SAFE_FREE(cm_creds.username); + cm_creds.username = SMB_STRDUP(get_cmdline_auth_info_username()); + + if (get_cmdline_auth_info_got_pass()) { + cm_set_password(get_cmdline_auth_info_password()); + } + + cm_creds.use_kerberos = get_cmdline_auth_info_use_kerberos(); + cm_creds.fallback_after_kerberos = false; + cm_creds.signing_state = get_cmdline_auth_info_signing_state(); +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_port(int port_number) +{ + port = port_number; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_dest_name_type(int type) +{ + name_type = type; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_signing_state(int state) +{ + cm_creds.signing_state = state; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_username(const char *username) +{ + SAFE_FREE(cm_creds.username); + cm_creds.username = SMB_STRDUP(username); +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_password(const char *newpass) +{ + SAFE_FREE(cm_creds.password); + cm_creds.password = SMB_STRDUP(newpass); + if (cm_creds.password) { + cm_creds.got_pass = true; + } +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_use_kerberos(void) +{ + cm_creds.use_kerberos = true; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_fallback_after_kerberos(void) +{ + cm_creds.fallback_after_kerberos = true; +} + +/**************************************************************************** +****************************************************************************/ + +void cli_cm_set_dest_ss(struct sockaddr_storage *pss) +{ + dest_ss = *pss; + have_ip = true; +} + +/********************************************************************** + split a dfs path into the server, share name, and extrapath components +**********************************************************************/ + +static void split_dfs_path(TALLOC_CTX *ctx, + const char *nodepath, + char **pp_server, + char **pp_share, + char **pp_extrapath) +{ + char *p, *q; + char *path; + + *pp_server = NULL; + *pp_share = NULL; + *pp_extrapath = NULL; + + path = talloc_strdup(ctx, nodepath); + if (!path) { + return; + } + + if ( path[0] != '\\' ) { + return; + } + + p = strchr_m( path + 1, '\\' ); + if ( !p ) { + return; + } + + *p = '\0'; + p++; + + /* Look for any extra/deep path */ + q = strchr_m(p, '\\'); + if (q != NULL) { + *q = '\0'; + q++; + *pp_extrapath = talloc_strdup(ctx, q); + } else { + *pp_extrapath = talloc_strdup(ctx, ""); + } + + *pp_share = talloc_strdup(ctx, p); + *pp_server = talloc_strdup(ctx, &path[1]); +} + +/**************************************************************************** + Return the original path truncated at the directory component before + the first wildcard character. Trust the caller to provide a NULL + terminated string +****************************************************************************/ + +static char *clean_path(TALLOC_CTX *ctx, const char *path) +{ + size_t len; + char *p1, *p2, *p; + char *path_out; + + /* No absolute paths. */ + while (IS_DIRECTORY_SEP(*path)) { + path++; + } + + path_out = talloc_strdup(ctx, path); + if (!path_out) { + return NULL; + } + + p1 = strchr_m(path_out, '*'); + p2 = strchr_m(path_out, '?'); + + if (p1 || p2) { + if (p1 && p2) { + p = MIN(p1,p2); + } else if (!p1) { + p = p2; + } else { + p = p1; + } + *p = '\0'; + + /* Now go back to the start of this component. */ + p1 = strrchr_m(path_out, '/'); + p2 = strrchr_m(path_out, '\\'); + p = MAX(p1,p2); + if (p) { + *p = '\0'; + } + } + + /* Strip any trailing separator */ + + len = strlen(path_out); + if ( (len > 0) && IS_DIRECTORY_SEP(path_out[len-1])) { + path_out[len-1] = '\0'; + } + + return path_out; +} + +/**************************************************************************** +****************************************************************************/ + +static char *cli_dfs_make_full_path(TALLOC_CTX *ctx, + struct cli_state *cli, + const char *dir) +{ + /* Ensure the extrapath doesn't start with a separator. */ + while (IS_DIRECTORY_SEP(*dir)) { + dir++; + } + + return talloc_asprintf(ctx, "\\%s\\%s\\%s", + cli->desthost, cli->share, dir); +} + +/******************************************************************** + check for dfs referral +********************************************************************/ + +static bool cli_dfs_check_error( struct cli_state *cli, NTSTATUS status ) +{ + uint32 flgs2 = SVAL(cli->inbuf,smb_flg2); + + /* only deal with DS when we negotiated NT_STATUS codes and UNICODE */ + + if (!((flgs2&FLAGS2_32_BIT_ERROR_CODES) && + (flgs2&FLAGS2_UNICODE_STRINGS))) + return false; + + if (NT_STATUS_EQUAL(status, NT_STATUS(IVAL(cli->inbuf,smb_rcls)))) + return true; + + return false; +} + +/******************************************************************** + Get the dfs referral link. +********************************************************************/ + +bool cli_dfs_get_referral(TALLOC_CTX *ctx, + struct cli_state *cli, + const char *path, + CLIENT_DFS_REFERRAL**refs, + size_t *num_refs, + uint16 *consumed) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_GET_DFS_REFERRAL; + char *param; + char *rparam=NULL, *rdata=NULL; + char *p; + char *endp; + size_t pathlen = 2*(strlen(path)+1); + uint16 num_referrals; + CLIENT_DFS_REFERRAL *referrals = NULL; + bool ret = false; + + *num_refs = 0; + *refs = NULL; + + param = SMB_MALLOC_ARRAY(char, 2+pathlen+2); + if (!param) { + return false; + } + SSVAL(param, 0, 0x03); /* max referral level */ + p = ¶m[2]; + + p += clistr_push(cli, p, path, pathlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, 0, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return false; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + if (data_len < 4) { + goto out; + } + + endp = rdata + data_len; + + *consumed = SVAL(rdata, 0); + num_referrals = SVAL(rdata, 2); + + if (num_referrals != 0) { + uint16 ref_version; + uint16 ref_size; + int i; + uint16 node_offset; + + referrals = TALLOC_ARRAY(ctx, CLIENT_DFS_REFERRAL, + num_referrals); + + if (!referrals) { + goto out; + } + /* start at the referrals array */ + + p = rdata+8; + for (i=0; i<num_referrals && p < endp; i++) { + if (p + 18 > endp) { + goto out; + } + ref_version = SVAL(p, 0); + ref_size = SVAL(p, 2); + node_offset = SVAL(p, 16); + + if (ref_version != 3) { + p += ref_size; + continue; + } + + referrals[i].proximity = SVAL(p, 8); + referrals[i].ttl = SVAL(p, 10); + + if (p + node_offset > endp) { + goto out; + } + clistr_pull_talloc(ctx, cli, &referrals[i].dfspath, + p+node_offset, -1, + STR_TERMINATE|STR_UNICODE ); + + if (!referrals[i].dfspath) { + goto out; + } + p += ref_size; + } + if (i < num_referrals) { + goto out; + } + } + + ret = true; + + *num_refs = num_referrals; + *refs = referrals; + + out: + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return ret; +} + +/******************************************************************** +********************************************************************/ + +bool cli_resolve_path(TALLOC_CTX *ctx, + const char *mountpt, + struct cli_state *rootcli, + const char *path, + struct cli_state **targetcli, + char **pp_targetpath) +{ + CLIENT_DFS_REFERRAL *refs = NULL; + size_t num_refs = 0; + uint16 consumed; + struct cli_state *cli_ipc = NULL; + char *dfs_path = NULL; + char *cleanpath = NULL; + char *extrapath = NULL; + int pathlen; + char *server = NULL; + char *share = NULL; + struct cli_state *newcli = NULL; + char *newpath = NULL; + char *newmount = NULL; + char *ppath = NULL; + SMB_STRUCT_STAT sbuf; + uint32 attributes; + + if ( !rootcli || !path || !targetcli ) { + return false; + } + + /* Don't do anything if this is not a DFS root. */ + + if ( !rootcli->dfsroot) { + *targetcli = rootcli; + *pp_targetpath = talloc_strdup(ctx, path); + if (!*pp_targetpath) { + return false; + } + return true; + } + + *targetcli = NULL; + + /* Send a trans2_query_path_info to check for a referral. */ + + cleanpath = clean_path(ctx, path); + if (!cleanpath) { + return false; + } + + dfs_path = cli_dfs_make_full_path(ctx, rootcli, cleanpath); + if (!dfs_path) { + return false; + } + + if (cli_qpathinfo_basic( rootcli, dfs_path, &sbuf, &attributes)) { + /* This is an ordinary path, just return it. */ + *targetcli = rootcli; + *pp_targetpath = talloc_strdup(ctx, path); + if (!*pp_targetpath) { + return false; + } + goto done; + } + + /* Special case where client asked for a path that does not exist */ + + if (cli_dfs_check_error(rootcli, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + *targetcli = rootcli; + *pp_targetpath = talloc_strdup(ctx, path); + if (!*pp_targetpath) { + return false; + } + goto done; + } + + /* We got an error, check for DFS referral. */ + + if (!cli_dfs_check_error(rootcli, NT_STATUS_PATH_NOT_COVERED)) { + return false; + } + + /* Check for the referral. */ + + if (!(cli_ipc = cli_cm_open(ctx, rootcli, + rootcli->desthost, + "IPC$", false, + (rootcli->trans_enc_state != NULL)))) { + return false; + } + + if (!cli_dfs_get_referral(ctx, cli_ipc, dfs_path, &refs, + &num_refs, &consumed) || !num_refs) { + return false; + } + + /* Just store the first referral for now. */ + + if (!refs[0].dfspath) { + return false; + } + split_dfs_path(ctx, refs[0].dfspath, &server, &share, &extrapath ); + + if (!server || !share) { + return false; + } + + /* Make sure to recreate the original string including any wildcards. */ + + dfs_path = cli_dfs_make_full_path(ctx, rootcli, path); + if (!dfs_path) { + return false; + } + pathlen = strlen(dfs_path)*2; + consumed = MIN(pathlen, consumed); + *pp_targetpath = talloc_strdup(ctx, &dfs_path[consumed/2]); + if (!*pp_targetpath) { + return false; + } + dfs_path[consumed/2] = '\0'; + + /* + * *pp_targetpath is now the unconsumed part of the path. + * dfs_path is now the consumed part of the path + * (in \server\share\path format). + */ + + /* Open the connection to the target server & share */ + if ((*targetcli = cli_cm_open(ctx, rootcli, + server, + share, + false, + (rootcli->trans_enc_state != NULL))) == NULL) { + d_printf("Unable to follow dfs referral [\\%s\\%s]\n", + server, share ); + return false; + } + + if (extrapath && strlen(extrapath) > 0) { + *pp_targetpath = talloc_asprintf(ctx, + "%s%s", + extrapath, + *pp_targetpath); + if (!*pp_targetpath) { + return false; + } + } + + /* parse out the consumed mount path */ + /* trim off the \server\share\ */ + + ppath = dfs_path; + + if (*ppath != '\\') { + d_printf("cli_resolve_path: " + "dfs_path (%s) not in correct format.\n", + dfs_path ); + return false; + } + + ppath++; /* Now pointing at start of server name. */ + + if ((ppath = strchr_m( dfs_path, '\\' )) == NULL) { + return false; + } + + ppath++; /* Now pointing at start of share name. */ + + if ((ppath = strchr_m( ppath+1, '\\' )) == NULL) { + return false; + } + + ppath++; /* Now pointing at path component. */ + + newmount = talloc_asprintf(ctx, "%s\\%s", mountpt, ppath ); + if (!newmount) { + return false; + } + + cli_cm_set_mntpoint(*targetcli, newmount); + + /* Check for another dfs referral, note that we are not + checking for loops here. */ + + if (!strequal(*pp_targetpath, "\\") && !strequal(*pp_targetpath, "/")) { + if (cli_resolve_path(ctx, + newmount, + *targetcli, + *pp_targetpath, + &newcli, + &newpath)) { + /* + * When cli_resolve_path returns true here it's always + * returning the complete path in newpath, so we're done + * here. + */ + *targetcli = newcli; + *pp_targetpath = newpath; + return true; + } + } + + done: + + /* If returning true ensure we return a dfs root full path. */ + if ((*targetcli)->dfsroot) { + dfs_path = talloc_strdup(ctx, *pp_targetpath); + if (!dfs_path) { + return false; + } + *pp_targetpath = cli_dfs_make_full_path(ctx, *targetcli, dfs_path); + } + + return true; +} + +/******************************************************************** +********************************************************************/ + +static bool cli_check_msdfs_proxy(TALLOC_CTX *ctx, + struct cli_state *cli, + const char *sharename, + char **pp_newserver, + char **pp_newshare, + bool force_encrypt, + const char *username, + const char *password, + const char *domain) +{ + CLIENT_DFS_REFERRAL *refs = NULL; + size_t num_refs = 0; + uint16 consumed; + char *fullpath = NULL; + bool res; + uint16 cnum; + char *newextrapath = NULL; + + if (!cli || !sharename) { + return false; + } + + cnum = cli->cnum; + + /* special case. never check for a referral on the IPC$ share */ + + if (strequal(sharename, "IPC$")) { + return false; + } + + /* send a trans2_query_path_info to check for a referral */ + + fullpath = talloc_asprintf(ctx, "\\%s\\%s", cli->desthost, sharename ); + if (!fullpath) { + return false; + } + + /* check for the referral */ + + if (!cli_send_tconX(cli, "IPC$", "IPC", NULL, 0)) { + return false; + } + + if (force_encrypt) { + NTSTATUS status = cli_cm_force_encryption(cli, + username, + password, + lp_workgroup(), + "IPC$"); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + } + + res = cli_dfs_get_referral(ctx, cli, fullpath, &refs, &num_refs, &consumed); + + if (!cli_tdis(cli)) { + return false; + } + + cli->cnum = cnum; + + if (!res || !num_refs) { + return false; + } + + if (!refs[0].dfspath) { + return false; + } + + split_dfs_path(ctx, refs[0].dfspath, pp_newserver, + pp_newshare, &newextrapath ); + + if ((*pp_newserver == NULL) || (*pp_newshare == NULL)) { + return false; + } + + /* check that this is not a self-referral */ + + if (strequal(cli->desthost, *pp_newserver) && + strequal(sharename, *pp_newshare)) { + return false; + } + + return true; +} diff --git a/source3/libsmb/clidgram.c b/source3/libsmb/clidgram.c new file mode 100644 index 0000000000..8b35a69def --- /dev/null +++ b/source3/libsmb/clidgram.c @@ -0,0 +1,357 @@ +/* + Unix SMB/CIFS implementation. + client dgram calls + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Richard Sharpe 2001 + Copyright (C) John Terpstra 2001 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* + * cli_send_mailslot, send a mailslot for client code ... + */ + +bool cli_send_mailslot(struct messaging_context *msg_ctx, + bool unique, const char *mailslot, + uint16 priority, + char *buf, int len, + const char *srcname, int src_type, + const char *dstname, int dest_type, + const struct sockaddr_storage *dest_ss) +{ + struct packet_struct p; + struct dgram_packet *dgram = &p.packet.dgram; + char *ptr, *p2; + char tmp[4]; + pid_t nmbd_pid; + char addr[INET6_ADDRSTRLEN]; + + if ((nmbd_pid = pidfile_pid("nmbd")) == 0) { + DEBUG(3, ("No nmbd found\n")); + return False; + } + + if (dest_ss->ss_family != AF_INET) { + DEBUG(3, ("cli_send_mailslot: can't send to IPv6 address.\n")); + return false; + } + + memset((char *)&p, '\0', sizeof(p)); + + /* + * Next, build the DGRAM ... + */ + + /* DIRECT GROUP or UNIQUE datagram. */ + dgram->header.msg_type = unique ? 0x10 : 0x11; + dgram->header.flags.node_type = M_NODE; + dgram->header.flags.first = True; + dgram->header.flags.more = False; + dgram->header.dgm_id = ((unsigned)time(NULL)%(unsigned)0x7FFF) + + ((unsigned)sys_getpid()%(unsigned)100); + /* source ip is filled by nmbd */ + dgram->header.dgm_length = 0; /* Let build_dgram() handle this. */ + dgram->header.packet_offset = 0; + + make_nmb_name(&dgram->source_name,srcname,src_type); + make_nmb_name(&dgram->dest_name,dstname,dest_type); + + ptr = &dgram->data[0]; + + /* Setup the smb part. */ + ptr -= 4; /* XXX Ugliness because of handling of tcp SMB length. */ + memcpy(tmp,ptr,4); + + if (smb_size + 17*2 + strlen(mailslot) + 1 + len > MAX_DGRAM_SIZE) { + DEBUG(0, ("cli_send_mailslot: Cannot write beyond end of packet\n")); + return False; + } + + cli_set_message(ptr,17,strlen(mailslot) + 1 + len,True); + memcpy(ptr,tmp,4); + + SCVAL(ptr,smb_com,SMBtrans); + SSVAL(ptr,smb_vwv1,len); + SSVAL(ptr,smb_vwv11,len); + SSVAL(ptr,smb_vwv12,70 + strlen(mailslot)); + SSVAL(ptr,smb_vwv13,3); + SSVAL(ptr,smb_vwv14,1); + SSVAL(ptr,smb_vwv15,priority); + SSVAL(ptr,smb_vwv16,2); + p2 = smb_buf(ptr); + fstrcpy(p2,mailslot); + p2 = skip_string(ptr,MAX_DGRAM_SIZE,p2); + if (!p2) { + return False; + } + + memcpy(p2,buf,len); + p2 += len; + + dgram->datasize = PTR_DIFF(p2,ptr+4); /* +4 for tcp length. */ + + p.packet_type = DGRAM_PACKET; + p.ip = ((const struct sockaddr_in *)dest_ss)->sin_addr; + p.timestamp = time(NULL); + + DEBUG(4,("send_mailslot: Sending to mailslot %s from %s ", + mailslot, nmb_namestr(&dgram->source_name))); + print_sockaddr(addr, sizeof(addr), dest_ss); + + DEBUGADD(4,("to %s IP %s\n", nmb_namestr(&dgram->dest_name), addr)); + + return NT_STATUS_IS_OK(messaging_send_buf(msg_ctx, + pid_to_procid(nmbd_pid), + MSG_SEND_PACKET, + (uint8 *)&p, sizeof(p))); +} + +static const char *mailslot_name(TALLOC_CTX *mem_ctx, struct in_addr dc_ip) +{ + return talloc_asprintf(mem_ctx, "%s%X", + NBT_MAILSLOT_GETDC, dc_ip.s_addr); +} + +bool send_getdc_request(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + struct sockaddr_storage *dc_ss, + const char *domain_name, + const DOM_SID *sid, + uint32_t nt_version) +{ + struct in_addr dc_ip; + const char *my_acct_name = NULL; + const char *my_mailslot = NULL; + struct nbt_ntlogon_packet packet; + struct nbt_ntlogon_sam_logon *s; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + struct dom_sid my_sid; + + ZERO_STRUCT(packet); + ZERO_STRUCT(my_sid); + + if (dc_ss->ss_family != AF_INET) { + return false; + } + + if (sid) { + my_sid = *sid; + } + + dc_ip = ((struct sockaddr_in *)dc_ss)->sin_addr; + my_mailslot = mailslot_name(mem_ctx, dc_ip); + if (!my_mailslot) { + return false; + } + + my_acct_name = talloc_asprintf(mem_ctx, "%s$", global_myname()); + if (!my_acct_name) { + return false; + } + + packet.command = NTLOGON_SAM_LOGON; + s = &packet.req.logon; + + s->request_count = 0; + s->computer_name = global_myname(); + s->user_name = my_acct_name; + s->mailslot_name = my_mailslot; + s->acct_control = ACB_WSTRUST; + s->sid = my_sid; + s->nt_version = nt_version; + s->lmnt_token = 0xffff; + s->lm20_token = 0xffff; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(nbt_ntlogon_packet, &packet); + } + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &packet, + (ndr_push_flags_fn_t)ndr_push_nbt_ntlogon_packet); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + + return cli_send_mailslot(msg_ctx, + false, NBT_MAILSLOT_NTLOGON, 0, + (char *)blob.data, blob.length, + global_myname(), 0, domain_name, 0x1c, + dc_ss); +} + +bool receive_getdc_response(TALLOC_CTX *mem_ctx, + struct sockaddr_storage *dc_ss, + const char *domain_name, + uint32_t *nt_version, + const char **dc_name, + union nbt_cldap_netlogon **reply) +{ + struct packet_struct *packet; + const char *my_mailslot = NULL; + struct in_addr dc_ip; + DATA_BLOB blob; + union nbt_cldap_netlogon r; + union dgram_message_body p; + enum ndr_err_code ndr_err; + + const char *returned_dc = NULL; + const char *returned_domain = NULL; + + if (dc_ss->ss_family != AF_INET) { + return false; + } + + dc_ip = ((struct sockaddr_in *)dc_ss)->sin_addr; + + my_mailslot = mailslot_name(mem_ctx, dc_ip); + if (!my_mailslot) { + return false; + } + + packet = receive_unexpected(DGRAM_PACKET, 0, my_mailslot); + + if (packet == NULL) { + DEBUG(5, ("Did not receive packet for %s\n", my_mailslot)); + return False; + } + + DEBUG(5, ("Received packet for %s\n", my_mailslot)); + + blob = data_blob_const(packet->packet.dgram.data, + packet->packet.dgram.datasize); + + if (blob.length < 4) { + DEBUG(0,("invalid length: %d\n", (int)blob.length)); + return false; + } + + if (RIVAL(blob.data,0) != DGRAM_SMB) { + DEBUG(0,("invalid packet\n")); + return false; + } + + blob.data += 4; + blob.length -= 4; + + ndr_err = ndr_pull_union_blob_all(&blob, mem_ctx, &p, DGRAM_SMB, + (ndr_pull_flags_fn_t)ndr_pull_dgram_smb_packet); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0,("failed to parse packet\n")); + return false; + } + + if (p.smb.smb_command != SMB_TRANSACTION) { + DEBUG(0,("invalid smb_command: %d\n", p.smb.smb_command)); + return false; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(dgram_smb_packet, &p); + } + + blob = p.smb.body.trans.data; + + if (!pull_mailslot_cldap_reply(mem_ctx, &blob, + &r, nt_version)) + { + return false; + } + + switch (*nt_version) { + case 1: + case 16: + case 17: + + returned_domain = r.logon1.domain_name; + returned_dc = r.logon1.pdc_name; + break; + case 2: + case 3: + case 18: + case 19: + returned_domain = r.logon3.domain_name; + returned_dc = r.logon3.pdc_name; + break; + case 4: + case 5: + case 6: + case 7: + returned_domain = r.logon5.domain; + returned_dc = r.logon5.pdc_name; + break; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + returned_domain = r.logon13.domain; + returned_dc = r.logon13.pdc_name; + break; + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + returned_domain = r.logon15.domain; + returned_dc = r.logon15.pdc_name; + break; + case 29: + case 30: + case 31: + returned_domain = r.logon29.domain; + returned_dc = r.logon29.pdc_name; + break; + default: + return false; + } + + if (!strequal(returned_domain, domain_name)) { + DEBUG(3, ("GetDC: Expected domain %s, got %s\n", + domain_name, returned_domain)); + return false; + } + + *dc_name = talloc_strdup(mem_ctx, returned_dc); + if (!*dc_name) { + return false; + } + + if (**dc_name == '\\') *dc_name += 1; + if (**dc_name == '\\') *dc_name += 1; + + if (reply) { + *reply = (union nbt_cldap_netlogon *)talloc_memdup( + mem_ctx, &r, sizeof(union nbt_cldap_netlogon)); + if (!*reply) { + return false; + } + } + + DEBUG(10, ("GetDC gave name %s for domain %s\n", + *dc_name, returned_domain)); + + return True; +} + diff --git a/source3/libsmb/clientgen.c b/source3/libsmb/clientgen.c new file mode 100644 index 0000000000..9d65fb4e94 --- /dev/null +++ b/source3/libsmb/clientgen.c @@ -0,0 +1,789 @@ +/* + Unix SMB/CIFS implementation. + SMB client generic functions + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/******************************************************************* + Setup the word count and byte count for a client smb message. +********************************************************************/ + +int cli_set_message(char *buf,int num_words,int num_bytes,bool zero) +{ + if (zero && (num_words || num_bytes)) { + memset(buf + smb_size,'\0',num_words*2 + num_bytes); + } + SCVAL(buf,smb_wct,num_words); + SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes); + smb_setlen(buf,smb_size + num_words*2 + num_bytes - 4); + return (smb_size + num_words*2 + num_bytes); +} + +/**************************************************************************** + Change the timeout (in milliseconds). +****************************************************************************/ + +unsigned int cli_set_timeout(struct cli_state *cli, unsigned int timeout) +{ + unsigned int old_timeout = cli->timeout; + cli->timeout = timeout; + return old_timeout; +} + +/**************************************************************************** + Change the port number used to call on. +****************************************************************************/ + +int cli_set_port(struct cli_state *cli, int port) +{ + cli->port = port; + return port; +} + +/**************************************************************************** + Read an smb from a fd ignoring all keepalive packets. + The timeout is in milliseconds + + This is exactly the same as receive_smb except that it never returns + a session keepalive packet (just as receive_smb used to do). + receive_smb was changed to return keepalives as the oplock processing means this call + should never go into a blocking read. +****************************************************************************/ + +static ssize_t client_receive_smb(struct cli_state *cli, size_t maxlen) +{ + size_t len; + + for(;;) { + NTSTATUS status; + + set_smb_read_error(&cli->smb_rw_error, SMB_READ_OK); + + status = receive_smb_raw(cli->fd, cli->inbuf, cli->bufsize, + cli->timeout, maxlen, &len); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("client_receive_smb failed\n")); + show_msg(cli->inbuf); + + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) { + set_smb_read_error(&cli->smb_rw_error, + SMB_READ_EOF); + return -1; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + set_smb_read_error(&cli->smb_rw_error, + SMB_READ_TIMEOUT); + return -1; + } + + set_smb_read_error(&cli->smb_rw_error, SMB_READ_ERROR); + return -1; + } + + /* + * I don't believe len can be < 0 with NT_STATUS_OK + * returned above, but this check doesn't hurt. JRA. + */ + + if ((ssize_t)len < 0) { + return len; + } + + /* Ignore session keepalive packets. */ + if(CVAL(cli->inbuf,0) != SMBkeepalive) { + break; + } + } + + if (cli_encryption_on(cli)) { + NTSTATUS status = cli_decrypt_message(cli); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("SMB decryption failed on incoming packet! Error %s\n", + nt_errstr(status))); + cli->smb_rw_error = SMB_READ_BAD_DECRYPT; + return -1; + } + } + + show_msg(cli->inbuf); + return len; +} + +/**************************************************************************** + Recv an smb. +****************************************************************************/ + +bool cli_receive_smb(struct cli_state *cli) +{ + ssize_t len; + + /* fd == -1 causes segfaults -- Tom (tom@ninja.nl) */ + if (cli->fd == -1) + return false; + + again: + len = client_receive_smb(cli, 0); + + if (len > 0) { + /* it might be an oplock break request */ + if (!(CVAL(cli->inbuf, smb_flg) & FLAG_REPLY) && + CVAL(cli->inbuf,smb_com) == SMBlockingX && + SVAL(cli->inbuf,smb_vwv6) == 0 && + SVAL(cli->inbuf,smb_vwv7) == 0) { + if (cli->oplock_handler) { + int fnum = SVAL(cli->inbuf,smb_vwv2); + unsigned char level = CVAL(cli->inbuf,smb_vwv3+1); + if (!cli->oplock_handler(cli, fnum, level)) { + return false; + } + } + /* try to prevent loops */ + SCVAL(cli->inbuf,smb_com,0xFF); + goto again; + } + } + + /* If the server is not responding, note that now */ + if (len < 0) { + DEBUG(0, ("Receiving SMB: Server stopped responding\n")); + close(cli->fd); + cli->fd = -1; + return false; + } + + if (!cli_check_sign_mac(cli, cli->inbuf)) { + /* + * If we get a signature failure in sessionsetup, then + * the server sometimes just reflects the sent signature + * back to us. Detect this and allow the upper layer to + * retrieve the correct Windows error message. + */ + if (CVAL(cli->outbuf,smb_com) == SMBsesssetupX && + (smb_len(cli->inbuf) > (smb_ss_field + 8 - 4)) && + (SVAL(cli->inbuf,smb_flg2) & FLAGS2_SMB_SECURITY_SIGNATURES) && + memcmp(&cli->outbuf[smb_ss_field],&cli->inbuf[smb_ss_field],8) == 0 && + cli_is_error(cli)) { + + /* + * Reflected signature on login error. + * Set bad sig but don't close fd. + */ + cli->smb_rw_error = SMB_READ_BAD_SIG; + return true; + } + + DEBUG(0, ("SMB Signature verification failed on incoming packet!\n")); + cli->smb_rw_error = SMB_READ_BAD_SIG; + close(cli->fd); + cli->fd = -1; + return false; + }; + return true; +} + +/**************************************************************************** + Read the data portion of a readX smb. + The timeout is in milliseconds +****************************************************************************/ + +ssize_t cli_receive_smb_data(struct cli_state *cli, char *buffer, size_t len) +{ + NTSTATUS status; + + set_smb_read_error(&cli->smb_rw_error, SMB_READ_OK); + + status = read_socket_with_timeout( + cli->fd, buffer, len, len, cli->timeout, NULL); + if (NT_STATUS_IS_OK(status)) { + return len; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) { + set_smb_read_error(&cli->smb_rw_error, SMB_READ_EOF); + return -1; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + set_smb_read_error(&cli->smb_rw_error, SMB_READ_TIMEOUT); + return -1; + } + + set_smb_read_error(&cli->smb_rw_error, SMB_READ_ERROR); + return -1; +} + +static ssize_t write_socket(int fd, const char *buf, size_t len) +{ + ssize_t ret=0; + + DEBUG(6,("write_socket(%d,%d)\n",fd,(int)len)); + ret = write_data(fd,buf,len); + + DEBUG(6,("write_socket(%d,%d) wrote %d\n",fd,(int)len,(int)ret)); + if(ret <= 0) + DEBUG(0,("write_socket: Error writing %d bytes to socket %d: ERRNO = %s\n", + (int)len, fd, strerror(errno) )); + + return(ret); +} + +/**************************************************************************** + Send an smb to a fd. +****************************************************************************/ + +bool cli_send_smb(struct cli_state *cli) +{ + size_t len; + size_t nwritten=0; + ssize_t ret; + char *buf_out = cli->outbuf; + bool enc_on = cli_encryption_on(cli); + + /* fd == -1 causes segfaults -- Tom (tom@ninja.nl) */ + if (cli->fd == -1) + return false; + + cli_calculate_sign_mac(cli, cli->outbuf); + + if (enc_on) { + NTSTATUS status = cli_encrypt_message(cli, cli->outbuf, + &buf_out); + if (!NT_STATUS_IS_OK(status)) { + close(cli->fd); + cli->fd = -1; + cli->smb_rw_error = SMB_WRITE_ERROR; + DEBUG(0,("Error in encrypting client message. Error %s\n", + nt_errstr(status) )); + return false; + } + } + + len = smb_len(buf_out) + 4; + + while (nwritten < len) { + ret = write_socket(cli->fd,buf_out+nwritten,len - nwritten); + if (ret <= 0) { + if (enc_on) { + cli_free_enc_buffer(cli, buf_out); + } + close(cli->fd); + cli->fd = -1; + cli->smb_rw_error = SMB_WRITE_ERROR; + DEBUG(0,("Error writing %d bytes to client. %d (%s)\n", + (int)len,(int)ret, strerror(errno) )); + return false; + } + nwritten += ret; + } + + if (enc_on) { + cli_free_enc_buffer(cli, buf_out); + } + + /* Increment the mid so we can tell between responses. */ + cli->mid++; + if (!cli->mid) + cli->mid++; + return true; +} + +/**************************************************************************** + Send a "direct" writeX smb to a fd. +****************************************************************************/ + +bool cli_send_smb_direct_writeX(struct cli_state *cli, + const char *p, + size_t extradata) +{ + /* First length to send is the offset to the data. */ + size_t len = SVAL(cli->outbuf,smb_vwv11) + 4; + size_t nwritten=0; + ssize_t ret; + + /* fd == -1 causes segfaults -- Tom (tom@ninja.nl) */ + if (cli->fd == -1) { + return false; + } + + if (client_is_signing_on(cli)) { + DEBUG(0,("cli_send_smb_large: cannot send signed packet.\n")); + return false; + } + + while (nwritten < len) { + ret = write_socket(cli->fd,cli->outbuf+nwritten,len - nwritten); + if (ret <= 0) { + close(cli->fd); + cli->fd = -1; + cli->smb_rw_error = SMB_WRITE_ERROR; + DEBUG(0,("Error writing %d bytes to client. %d (%s)\n", + (int)len,(int)ret, strerror(errno) )); + return false; + } + nwritten += ret; + } + + /* Now write the extra data. */ + nwritten=0; + while (nwritten < extradata) { + ret = write_socket(cli->fd,p+nwritten,extradata - nwritten); + if (ret <= 0) { + close(cli->fd); + cli->fd = -1; + cli->smb_rw_error = SMB_WRITE_ERROR; + DEBUG(0,("Error writing %d extradata " + "bytes to client. %d (%s)\n", + (int)extradata,(int)ret, strerror(errno) )); + return false; + } + nwritten += ret; + } + + /* Increment the mid so we can tell between responses. */ + cli->mid++; + if (!cli->mid) + cli->mid++; + return true; +} + +/**************************************************************************** + Setup basics in a outgoing packet. +****************************************************************************/ + +void cli_setup_packet_buf(struct cli_state *cli, char *buf) +{ + uint16 flags2; + cli->rap_error = 0; + SIVAL(buf,smb_rcls,0); + SSVAL(buf,smb_pid,cli->pid); + memset(buf+smb_pidhigh, 0, 12); + SSVAL(buf,smb_uid,cli->vuid); + SSVAL(buf,smb_mid,cli->mid); + + if (cli->protocol <= PROTOCOL_CORE) { + return; + } + + if (cli->case_sensitive) { + SCVAL(buf,smb_flg,0x0); + } else { + /* Default setting, case insensitive. */ + SCVAL(buf,smb_flg,0x8); + } + flags2 = FLAGS2_LONG_PATH_COMPONENTS; + if (cli->capabilities & CAP_UNICODE) + flags2 |= FLAGS2_UNICODE_STRINGS; + if ((cli->capabilities & CAP_DFS) && cli->dfsroot) + flags2 |= FLAGS2_DFS_PATHNAMES; + if (cli->capabilities & CAP_STATUS32) + flags2 |= FLAGS2_32_BIT_ERROR_CODES; + if (cli->use_spnego) + flags2 |= FLAGS2_EXTENDED_SECURITY; + SSVAL(buf,smb_flg2, flags2); +} + +void cli_setup_packet(struct cli_state *cli) +{ + cli_setup_packet_buf(cli, cli->outbuf); +} + +/**************************************************************************** + Setup the bcc length of the packet from a pointer to the end of the data. +****************************************************************************/ + +void cli_setup_bcc(struct cli_state *cli, void *p) +{ + set_message_bcc(cli->outbuf, PTR_DIFF(p, smb_buf(cli->outbuf))); +} + +/**************************************************************************** + Initialise credentials of a client structure. +****************************************************************************/ + +void cli_init_creds(struct cli_state *cli, const char *username, const char *domain, const char *password) +{ + fstrcpy(cli->domain, domain); + fstrcpy(cli->user_name, username); + pwd_set_cleartext(&cli->pwd, password); + if (!*username) { + cli->pwd.null_pwd = true; + } + + DEBUG(10,("cli_init_creds: user %s domain %s\n", cli->user_name, cli->domain)); +} + +/**************************************************************************** + Set the signing state (used from the command line). +****************************************************************************/ + +void cli_setup_signing_state(struct cli_state *cli, int signing_state) +{ + if (signing_state == Undefined) + return; + + if (signing_state == false) { + cli->sign_info.allow_smb_signing = false; + cli->sign_info.mandatory_signing = false; + return; + } + + cli->sign_info.allow_smb_signing = true; + + if (signing_state == Required) + cli->sign_info.mandatory_signing = true; +} + +/**************************************************************************** + Initialise a client structure. Always returns a malloc'ed struct. +****************************************************************************/ + +struct cli_state *cli_initialise(void) +{ + struct cli_state *cli = NULL; + + /* Check the effective uid - make sure we are not setuid */ + if (is_setuid_root()) { + DEBUG(0,("libsmb based programs must *NOT* be setuid root.\n")); + return NULL; + } + + cli = talloc(NULL, struct cli_state); + if (!cli) { + return NULL; + } + + ZERO_STRUCTP(cli); + + cli->port = 0; + cli->fd = -1; + cli->cnum = -1; + cli->pid = (uint16)sys_getpid(); + cli->mid = 1; + cli->vuid = UID_FIELD_INVALID; + cli->protocol = PROTOCOL_NT1; + cli->timeout = 20000; /* Timeout is in milliseconds. */ + cli->bufsize = CLI_BUFFER_SIZE+4; + cli->max_xmit = cli->bufsize; + cli->outbuf = (char *)SMB_MALLOC(cli->bufsize+SAFETY_MARGIN); + cli->inbuf = (char *)SMB_MALLOC(cli->bufsize+SAFETY_MARGIN); + cli->oplock_handler = cli_oplock_ack; + cli->case_sensitive = false; + cli->smb_rw_error = SMB_READ_OK; + + cli->use_spnego = lp_client_use_spnego(); + + cli->capabilities = CAP_UNICODE | CAP_STATUS32 | CAP_DFS; + + /* Set the CLI_FORCE_DOSERR environment variable to test + client routines using DOS errors instead of STATUS32 + ones. This intended only as a temporary hack. */ + if (getenv("CLI_FORCE_DOSERR")) + cli->force_dos_errors = true; + + if (lp_client_signing()) + cli->sign_info.allow_smb_signing = true; + + if (lp_client_signing() == Required) + cli->sign_info.mandatory_signing = true; + + if (!cli->outbuf || !cli->inbuf) + goto error; + + memset(cli->outbuf, 0, cli->bufsize); + memset(cli->inbuf, 0, cli->bufsize); + + +#if defined(DEVELOPER) + /* just because we over-allocate, doesn't mean it's right to use it */ + clobber_region(FUNCTION_MACRO, __LINE__, cli->outbuf+cli->bufsize, SAFETY_MARGIN); + clobber_region(FUNCTION_MACRO, __LINE__, cli->inbuf+cli->bufsize, SAFETY_MARGIN); +#endif + + /* initialise signing */ + cli_null_set_signing(cli); + + cli->initialised = 1; + + return cli; + + /* Clean up after malloc() error */ + + error: + + SAFE_FREE(cli->inbuf); + SAFE_FREE(cli->outbuf); + SAFE_FREE(cli); + return NULL; +} + +/**************************************************************************** + Close all pipes open on this session. +****************************************************************************/ + +void cli_nt_pipes_close(struct cli_state *cli) +{ + while (cli->pipe_list != NULL) { + /* + * No TALLOC_FREE here! + */ + talloc_free(cli->pipe_list); + } +} + +/**************************************************************************** + Shutdown a client structure. +****************************************************************************/ + +void cli_shutdown(struct cli_state *cli) +{ + cli_nt_pipes_close(cli); + + /* + * tell our peer to free his resources. Wihtout this, when an + * application attempts to do a graceful shutdown and calls + * smbc_free_context() to clean up all connections, some connections + * can remain active on the peer end, until some (long) timeout period + * later. This tree disconnect forces the peer to clean up, since the + * connection will be going away. + * + * Also, do not do tree disconnect when cli->smb_rw_error is SMB_DO_NOT_DO_TDIS + * the only user for this so far is smbmount which passes opened connection + * down to kernel's smbfs module. + */ + if ( (cli->cnum != (uint16)-1) && (cli->smb_rw_error != SMB_DO_NOT_DO_TDIS ) ) { + cli_tdis(cli); + } + + SAFE_FREE(cli->outbuf); + SAFE_FREE(cli->inbuf); + + cli_free_signing_context(cli); + data_blob_free(&cli->secblob); + data_blob_free(&cli->user_session_key); + + if (cli->fd != -1) { + close(cli->fd); + } + cli->fd = -1; + cli->smb_rw_error = SMB_READ_OK; + + TALLOC_FREE(cli); +} + +/**************************************************************************** + Set socket options on a open connection. +****************************************************************************/ + +void cli_sockopt(struct cli_state *cli, const char *options) +{ + set_socket_options(cli->fd, options); +} + +/**************************************************************************** + Set the PID to use for smb messages. Return the old pid. +****************************************************************************/ + +uint16 cli_setpid(struct cli_state *cli, uint16 pid) +{ + uint16 ret = cli->pid; + cli->pid = pid; + return ret; +} + +/**************************************************************************** + Set the case sensitivity flag on the packets. Returns old state. +****************************************************************************/ + +bool cli_set_case_sensitive(struct cli_state *cli, bool case_sensitive) +{ + bool ret = cli->case_sensitive; + cli->case_sensitive = case_sensitive; + return ret; +} + +/**************************************************************************** +Send a keepalive packet to the server +****************************************************************************/ + +bool cli_send_keepalive(struct cli_state *cli) +{ + if (cli->fd == -1) { + DEBUG(3, ("cli_send_keepalive: fd == -1\n")); + return false; + } + if (!send_keepalive(cli->fd)) { + close(cli->fd); + cli->fd = -1; + DEBUG(0,("Error sending keepalive packet to client.\n")); + return false; + } + return true; +} + +/** + * @brief: Collect a echo reply + * @param[in] req The corresponding async request + * + * There might be more than one echo reply. This helper pulls the reply out of + * the data stream. If all expected replies have arrived, declare the + * async_req done. + */ + +static void cli_echo_recv_helper(struct async_req *req) +{ + struct cli_request *cli_req; + uint8_t wct; + uint16_t *vwv; + uint16_t num_bytes; + uint8_t *bytes; + NTSTATUS status; + + status = cli_pull_reply(req, &wct, &vwv, &num_bytes, &bytes); + if (!NT_STATUS_IS_OK(status)) { + async_req_error(req, status); + return; + } + + cli_req = talloc_get_type_abort(req->private_data, struct cli_request); + + if ((num_bytes != cli_req->data.echo.data.length) + || (memcmp(cli_req->data.echo.data.data, bytes, + num_bytes) != 0)) { + async_req_error(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + cli_req->data.echo.num_echos -= 1; + + if (cli_req->data.echo.num_echos == 0) { + client_set_trans_sign_state_off(cli_req->cli, cli_req->mid); + async_req_done(req); + return; + } + + return; +} + +/** + * @brief Send SMBEcho requests + * @param[in] mem_ctx The memory context to put the async_req on + * @param[in] ev The event context that will call us back + * @param[in] cli The connection to send the echo to + * @param[in] num_echos How many times do we want to get the reply? + * @param[in] data The data we want to get back + * @retval The async request + */ + +struct async_req *cli_echo_send(TALLOC_CTX *mem_ctx, struct event_context *ev, + struct cli_state *cli, uint16_t num_echos, + DATA_BLOB data) +{ + uint16_t vwv[1]; + uint8_t *data_copy; + struct async_req *result; + struct cli_request *req; + + SSVAL(vwv, 0, num_echos); + + data_copy = (uint8_t *)talloc_memdup(mem_ctx, data.data, data.length); + if (data_copy == NULL) { + return NULL; + } + + result = cli_request_send(mem_ctx, ev, cli, SMBecho, 0, 1, vwv, + data.length, data.data); + if (result == NULL) { + TALLOC_FREE(data_copy); + return NULL; + } + req = talloc_get_type_abort(result->private_data, struct cli_request); + + client_set_trans_sign_state_on(cli, req->mid); + + req->data.echo.num_echos = num_echos; + req->data.echo.data.data = talloc_move(req, &data_copy); + req->data.echo.data.length = data.length; + + req->recv_helper.fn = cli_echo_recv_helper; + + return result; +} + +/** + * Get the result out from an echo request + * @param[in] req The async_req from cli_echo_send + * @retval Did the server reply correctly? + */ + +NTSTATUS cli_echo_recv(struct async_req *req) +{ + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + + return NT_STATUS_OK; +} + +/** + * @brief Send/Receive SMBEcho requests + * @param[in] mem_ctx The memory context to put the async_req on + * @param[in] ev The event context that will call us back + * @param[in] cli The connection to send the echo to + * @param[in] num_echos How many times do we want to get the reply? + * @param[in] data The data we want to get back + * @retval Did the server reply correctly? + */ + +NTSTATUS cli_echo(struct cli_state *cli, uint16_t num_echos, DATA_BLOB data) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct async_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (cli->fd_event != NULL) { + /* + * Can't use sync call while an async call is in flight + */ + cli_set_error(cli, NT_STATUS_INVALID_PARAMETER); + goto fail; + } + + ev = event_context_init(frame); + if (ev == NULL) { + goto fail; + } + + req = cli_echo_send(frame, ev, cli, num_echos, data); + if (req == NULL) { + goto fail; + } + + while (req->state < ASYNC_REQ_DONE) { + event_loop_once(ev); + } + + status = cli_echo_recv(req); + + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/source3/libsmb/clierror.c b/source3/libsmb/clierror.c new file mode 100644 index 0000000000..36746419f7 --- /dev/null +++ b/source3/libsmb/clierror.c @@ -0,0 +1,497 @@ +/* + Unix SMB/CIFS implementation. + client error handling routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jelmer Vernooij 2003 + Copyright (C) Jeremy Allison 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/***************************************************** + RAP error codes - a small start but will be extended. + + XXX: Perhaps these should move into a common function because they're + duplicated in clirap2.c + +*******************************************************/ + +static const struct { + int err; + const char *message; +} rap_errmap[] = { + {5, "RAP5: User has insufficient privilege" }, + {50, "RAP50: Not supported by server" }, + {65, "RAP65: Access denied" }, + {86, "RAP86: The specified password is invalid" }, + {2220, "RAP2220: Group does not exist" }, + {2221, "RAP2221: User does not exist" }, + {2226, "RAP2226: Operation only permitted on a Primary Domain Controller" }, + {2237, "RAP2237: User is not in group" }, + {2242, "RAP2242: The password of this user has expired." }, + {2243, "RAP2243: The password of this user cannot change." }, + {2244, "RAP2244: This password cannot be used now (password history conflict)." }, + {2245, "RAP2245: The password is shorter than required." }, + {2246, "RAP2246: The password of this user is too recent to change."}, + + /* these really shouldn't be here ... */ + {0x80, "Not listening on called name"}, + {0x81, "Not listening for calling name"}, + {0x82, "Called name not present"}, + {0x83, "Called name present, but insufficient resources"}, + + {0, NULL} +}; + +/**************************************************************************** + Return a description of an SMB error. +****************************************************************************/ + +static const char *cli_smb_errstr(struct cli_state *cli) +{ + return smb_dos_errstr(cli->inbuf); +} + +/**************************************************************************** + Convert a socket error into an NTSTATUS. +****************************************************************************/ + +static NTSTATUS cli_smb_rw_error_to_ntstatus(struct cli_state *cli) +{ + switch(cli->smb_rw_error) { + case SMB_READ_TIMEOUT: + return NT_STATUS_IO_TIMEOUT; + case SMB_READ_EOF: + return NT_STATUS_END_OF_FILE; + /* What we shoud really do for read/write errors is convert from errno. */ + /* FIXME. JRA. */ + case SMB_READ_ERROR: + return NT_STATUS_INVALID_NETWORK_RESPONSE; + case SMB_WRITE_ERROR: + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + case SMB_READ_BAD_SIG: + return NT_STATUS_INVALID_PARAMETER; + case SMB_NO_MEMORY: + return NT_STATUS_NO_MEMORY; + default: + break; + } + return NT_STATUS_UNSUCCESSFUL; +} + +/*************************************************************************** + Return an error message - either an NT error, SMB error or a RAP error. + Note some of the NT errors are actually warnings or "informational" errors + in which case they can be safely ignored. +****************************************************************************/ + +const char *cli_errstr(struct cli_state *cli) +{ + fstring cli_error_message; + uint32 flgs2 = SVAL(cli->inbuf,smb_flg2), errnum; + uint8 errclass; + int i; + char *result; + + if (!cli->initialised) { + fstrcpy(cli_error_message, "[Programmer's error] cli_errstr called on unitialized cli_stat struct!\n"); + goto done; + } + + /* Was it server socket error ? */ + if (cli->fd == -1 && cli->smb_rw_error) { + switch(cli->smb_rw_error) { + case SMB_READ_TIMEOUT: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Call timed out: server did not respond after %d milliseconds", + cli->timeout); + break; + case SMB_READ_EOF: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Call returned zero bytes (EOF)" ); + break; + case SMB_READ_ERROR: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Read error: %s", strerror(errno) ); + break; + case SMB_WRITE_ERROR: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Write error: %s", strerror(errno) ); + break; + case SMB_READ_BAD_SIG: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Server packet had invalid SMB signature!"); + break; + case SMB_NO_MEMORY: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Out of memory"); + break; + default: + slprintf(cli_error_message, sizeof(cli_error_message) - 1, + "Unknown error code %d\n", cli->smb_rw_error ); + break; + } + goto done; + } + + /* Case #1: RAP error */ + if (cli->rap_error) { + for (i = 0; rap_errmap[i].message != NULL; i++) { + if (rap_errmap[i].err == cli->rap_error) { + return rap_errmap[i].message; + } + } + + slprintf(cli_error_message, sizeof(cli_error_message) - 1, "RAP code %d", + cli->rap_error); + + goto done; + } + + /* Case #2: 32-bit NT errors */ + if (flgs2 & FLAGS2_32_BIT_ERROR_CODES) { + NTSTATUS status = NT_STATUS(IVAL(cli->inbuf,smb_rcls)); + + return nt_errstr(status); + } + + cli_dos_error(cli, &errclass, &errnum); + + /* Case #3: SMB error */ + + return cli_smb_errstr(cli); + + done: + result = talloc_strdup(talloc_tos(), cli_error_message); + SMB_ASSERT(result); + return result; +} + + +/**************************************************************************** + Return the 32-bit NT status code from the last packet. +****************************************************************************/ + +NTSTATUS cli_nt_error(struct cli_state *cli) +{ + int flgs2 = SVAL(cli->inbuf,smb_flg2); + + /* Deal with socket errors first. */ + if (cli->fd == -1 && cli->smb_rw_error) { + return cli_smb_rw_error_to_ntstatus(cli); + } + + if (!(flgs2 & FLAGS2_32_BIT_ERROR_CODES)) { + int e_class = CVAL(cli->inbuf,smb_rcls); + int code = SVAL(cli->inbuf,smb_err); + return dos_to_ntstatus(e_class, code); + } + + return NT_STATUS(IVAL(cli->inbuf,smb_rcls)); +} + + +/**************************************************************************** + Return the DOS error from the last packet - an error class and an error + code. +****************************************************************************/ + +void cli_dos_error(struct cli_state *cli, uint8 *eclass, uint32 *ecode) +{ + int flgs2; + + if(!cli->initialised) { + return; + } + + /* Deal with socket errors first. */ + if (cli->fd == -1 && cli->smb_rw_error) { + NTSTATUS status = cli_smb_rw_error_to_ntstatus(cli); + ntstatus_to_dos( status, eclass, ecode); + return; + } + + flgs2 = SVAL(cli->inbuf,smb_flg2); + + if (flgs2 & FLAGS2_32_BIT_ERROR_CODES) { + NTSTATUS ntstatus = NT_STATUS(IVAL(cli->inbuf, smb_rcls)); + ntstatus_to_dos(ntstatus, eclass, ecode); + return; + } + + *eclass = CVAL(cli->inbuf,smb_rcls); + *ecode = SVAL(cli->inbuf,smb_err); +} + +/* Return a UNIX errno from a NT status code */ +static const struct { + NTSTATUS status; + int error; +} nt_errno_map[] = { + {NT_STATUS_ACCESS_VIOLATION, EACCES}, + {NT_STATUS_INVALID_HANDLE, EBADF}, + {NT_STATUS_ACCESS_DENIED, EACCES}, + {NT_STATUS_OBJECT_NAME_NOT_FOUND, ENOENT}, + {NT_STATUS_OBJECT_PATH_NOT_FOUND, ENOENT}, + {NT_STATUS_SHARING_VIOLATION, EBUSY}, + {NT_STATUS_OBJECT_PATH_INVALID, ENOTDIR}, + {NT_STATUS_OBJECT_NAME_COLLISION, EEXIST}, + {NT_STATUS_PATH_NOT_COVERED, ENOENT}, + {NT_STATUS_UNSUCCESSFUL, EINVAL}, + {NT_STATUS_NOT_IMPLEMENTED, ENOSYS}, + {NT_STATUS_IN_PAGE_ERROR, EFAULT}, + {NT_STATUS_BAD_NETWORK_NAME, ENOENT}, +#ifdef EDQUOT + {NT_STATUS_PAGEFILE_QUOTA, EDQUOT}, + {NT_STATUS_QUOTA_EXCEEDED, EDQUOT}, + {NT_STATUS_REGISTRY_QUOTA_LIMIT, EDQUOT}, + {NT_STATUS_LICENSE_QUOTA_EXCEEDED, EDQUOT}, +#endif +#ifdef ETIME + {NT_STATUS_TIMER_NOT_CANCELED, ETIME}, +#endif + {NT_STATUS_INVALID_PARAMETER, EINVAL}, + {NT_STATUS_NO_SUCH_DEVICE, ENODEV}, + {NT_STATUS_NO_SUCH_FILE, ENOENT}, +#ifdef ENODATA + {NT_STATUS_END_OF_FILE, ENODATA}, +#endif +#ifdef ENOMEDIUM + {NT_STATUS_NO_MEDIA_IN_DEVICE, ENOMEDIUM}, + {NT_STATUS_NO_MEDIA, ENOMEDIUM}, +#endif + {NT_STATUS_NONEXISTENT_SECTOR, ESPIPE}, + {NT_STATUS_NO_MEMORY, ENOMEM}, + {NT_STATUS_CONFLICTING_ADDRESSES, EADDRINUSE}, + {NT_STATUS_NOT_MAPPED_VIEW, EINVAL}, + {NT_STATUS_UNABLE_TO_FREE_VM, EADDRINUSE}, + {NT_STATUS_ACCESS_DENIED, EACCES}, + {NT_STATUS_BUFFER_TOO_SMALL, ENOBUFS}, + {NT_STATUS_WRONG_PASSWORD, EACCES}, + {NT_STATUS_LOGON_FAILURE, EACCES}, + {NT_STATUS_INVALID_WORKSTATION, EACCES}, + {NT_STATUS_INVALID_LOGON_HOURS, EACCES}, + {NT_STATUS_PASSWORD_EXPIRED, EACCES}, + {NT_STATUS_ACCOUNT_DISABLED, EACCES}, + {NT_STATUS_DISK_FULL, ENOSPC}, + {NT_STATUS_INVALID_PIPE_STATE, EPIPE}, + {NT_STATUS_PIPE_BUSY, EPIPE}, + {NT_STATUS_PIPE_DISCONNECTED, EPIPE}, + {NT_STATUS_PIPE_NOT_AVAILABLE, ENOSYS}, + {NT_STATUS_FILE_IS_A_DIRECTORY, EISDIR}, + {NT_STATUS_NOT_SUPPORTED, ENOSYS}, + {NT_STATUS_NOT_A_DIRECTORY, ENOTDIR}, + {NT_STATUS_DIRECTORY_NOT_EMPTY, ENOTEMPTY}, + {NT_STATUS_NETWORK_UNREACHABLE, ENETUNREACH}, + {NT_STATUS_HOST_UNREACHABLE, EHOSTUNREACH}, + {NT_STATUS_CONNECTION_ABORTED, ECONNABORTED}, + {NT_STATUS_CONNECTION_REFUSED, ECONNREFUSED}, + {NT_STATUS_TOO_MANY_LINKS, EMLINK}, + {NT_STATUS_NETWORK_BUSY, EBUSY}, + {NT_STATUS_DEVICE_DOES_NOT_EXIST, ENODEV}, +#ifdef ELIBACC + {NT_STATUS_DLL_NOT_FOUND, ELIBACC}, +#endif + {NT_STATUS_PIPE_BROKEN, EPIPE}, + {NT_STATUS_REMOTE_NOT_LISTENING, ECONNREFUSED}, + {NT_STATUS_NETWORK_ACCESS_DENIED, EACCES}, + {NT_STATUS_TOO_MANY_OPENED_FILES, EMFILE}, +#ifdef EPROTO + {NT_STATUS_DEVICE_PROTOCOL_ERROR, EPROTO}, +#endif + {NT_STATUS_FLOAT_OVERFLOW, ERANGE}, + {NT_STATUS_FLOAT_UNDERFLOW, ERANGE}, + {NT_STATUS_INTEGER_OVERFLOW, ERANGE}, + {NT_STATUS_MEDIA_WRITE_PROTECTED, EROFS}, + {NT_STATUS_PIPE_CONNECTED, EISCONN}, + {NT_STATUS_MEMORY_NOT_ALLOCATED, EFAULT}, + {NT_STATUS_FLOAT_INEXACT_RESULT, ERANGE}, + {NT_STATUS_ILL_FORMED_PASSWORD, EACCES}, + {NT_STATUS_PASSWORD_RESTRICTION, EACCES}, + {NT_STATUS_ACCOUNT_RESTRICTION, EACCES}, + {NT_STATUS_PORT_CONNECTION_REFUSED, ECONNREFUSED}, + {NT_STATUS_NAME_TOO_LONG, ENAMETOOLONG}, + {NT_STATUS_REMOTE_DISCONNECT, ESHUTDOWN}, + {NT_STATUS_CONNECTION_DISCONNECTED, ECONNABORTED}, + {NT_STATUS_CONNECTION_RESET, ENETRESET}, +#ifdef ENOTUNIQ + {NT_STATUS_IP_ADDRESS_CONFLICT1, ENOTUNIQ}, + {NT_STATUS_IP_ADDRESS_CONFLICT2, ENOTUNIQ}, +#endif + {NT_STATUS_PORT_MESSAGE_TOO_LONG, EMSGSIZE}, + {NT_STATUS_PROTOCOL_UNREACHABLE, ENOPROTOOPT}, + {NT_STATUS_ADDRESS_ALREADY_EXISTS, EADDRINUSE}, + {NT_STATUS_PORT_UNREACHABLE, EHOSTUNREACH}, + {NT_STATUS_IO_TIMEOUT, ETIMEDOUT}, + {NT_STATUS_RETRY, EAGAIN}, +#ifdef ENOTUNIQ + {NT_STATUS_DUPLICATE_NAME, ENOTUNIQ}, +#endif +#ifdef ECOMM + {NT_STATUS_NET_WRITE_FAULT, ECOMM}, +#endif +#ifdef EXDEV + {NT_STATUS_NOT_SAME_DEVICE, EXDEV}, +#endif + {NT_STATUS(0), 0} +}; + +/**************************************************************************** + The following mappings need tidying up and moving into libsmb/errormap.c... +****************************************************************************/ + +static int cli_errno_from_nt(NTSTATUS status) +{ + int i; + DEBUG(10,("cli_errno_from_nt: 32 bit codes: code=%08x\n", NT_STATUS_V(status))); + + /* Status codes without this bit set are not errors */ + + if (!(NT_STATUS_V(status) & 0xc0000000)) { + return 0; + } + + for (i=0;nt_errno_map[i].error;i++) { + if (NT_STATUS_V(nt_errno_map[i].status) == + NT_STATUS_V(status)) return nt_errno_map[i].error; + } + + /* for all other cases - a default code */ + return EINVAL; +} + +/* Return a UNIX errno appropriate for the error received in the last + packet. */ + +int cli_errno(struct cli_state *cli) +{ + NTSTATUS status; + + if (cli_is_nt_error(cli)) { + status = cli_nt_error(cli); + return cli_errno_from_nt(status); + } + + if (cli_is_dos_error(cli)) { + uint8 eclass; + uint32 ecode; + + cli_dos_error(cli, &eclass, &ecode); + status = dos_to_ntstatus(eclass, ecode); + return cli_errno_from_nt(status); + } + + /* + * Yuck! A special case for this Vista error. Since its high-order + * byte isn't 0xc0, it doesn't match cli_is_nt_error() above. + */ + status = cli_nt_error(cli); + if (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_INACCESSIBLE_SYSTEM_SHORTCUT)) { + return EACCES; + } + + /* for other cases */ + return EINVAL; +} + +/* Return true if the last packet was in error */ + +bool cli_is_error(struct cli_state *cli) +{ + uint32 flgs2 = SVAL(cli->inbuf,smb_flg2), rcls = 0; + + /* A socket error is always an error. */ + if (cli->fd == -1 && cli->smb_rw_error != 0) { + return True; + } + + if (flgs2 & FLAGS2_32_BIT_ERROR_CODES) { + /* Return error is error bits are set */ + rcls = IVAL(cli->inbuf, smb_rcls); + return (rcls & 0xF0000000) == 0xC0000000; + } + + /* Return error if error class in non-zero */ + + rcls = CVAL(cli->inbuf, smb_rcls); + return rcls != 0; +} + +/* Return true if the last error was an NT error */ + +bool cli_is_nt_error(struct cli_state *cli) +{ + uint32 flgs2 = SVAL(cli->inbuf,smb_flg2); + + /* A socket error is always an NT error. */ + if (cli->fd == -1 && cli->smb_rw_error != 0) { + return True; + } + + return cli_is_error(cli) && (flgs2 & FLAGS2_32_BIT_ERROR_CODES); +} + +/* Return true if the last error was a DOS error */ + +bool cli_is_dos_error(struct cli_state *cli) +{ + uint32 flgs2 = SVAL(cli->inbuf,smb_flg2); + + /* A socket error is always a DOS error. */ + if (cli->fd == -1 && cli->smb_rw_error != 0) { + return True; + } + + return cli_is_error(cli) && !(flgs2 & FLAGS2_32_BIT_ERROR_CODES); +} + +/* Return the last error always as an NTSTATUS. */ + +NTSTATUS cli_get_nt_error(struct cli_state *cli) +{ + if (cli_is_nt_error(cli)) { + return cli_nt_error(cli); + } else if (cli_is_dos_error(cli)) { + uint32 ecode; + uint8 eclass; + cli_dos_error(cli, &eclass, &ecode); + return dos_to_ntstatus(eclass, ecode); + } else { + /* Something went wrong, we don't know what. */ + return NT_STATUS_UNSUCCESSFUL; + } +} + +/* Push an error code into the inbuf to be returned on the next + * query. */ + +void cli_set_nt_error(struct cli_state *cli, NTSTATUS status) +{ + SSVAL(cli->inbuf,smb_flg2, SVAL(cli->inbuf,smb_flg2)|FLAGS2_32_BIT_ERROR_CODES); + SIVAL(cli->inbuf, smb_rcls, NT_STATUS_V(status)); +} + +/* Reset an error. */ + +void cli_reset_error(struct cli_state *cli) +{ + if (SVAL(cli->inbuf,smb_flg2) & FLAGS2_32_BIT_ERROR_CODES) { + SIVAL(cli->inbuf, smb_rcls, NT_STATUS_V(NT_STATUS_OK)); + } else { + SCVAL(cli->inbuf,smb_rcls,0); + SSVAL(cli->inbuf,smb_err,0); + } +} diff --git a/source3/libsmb/clifile.c b/source3/libsmb/clifile.c new file mode 100644 index 0000000000..d3819af444 --- /dev/null +++ b/source3/libsmb/clifile.c @@ -0,0 +1,2281 @@ +/* + Unix SMB/CIFS implementation. + client file operations + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 2001-2002 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Hard/Symlink a file (UNIX extensions). + Creates new name (sym)linked to oldname. +****************************************************************************/ + +static bool cli_link_internal(struct cli_state *cli, const char *oldname, const char *newname, bool hard_link) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_SETPATHINFO; + char *param; + char *data; + char *rparam=NULL, *rdata=NULL; + char *p; + size_t oldlen = 2*(strlen(oldname)+1); + size_t newlen = 2*(strlen(newname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+newlen+2); + + if (!param) { + return false; + } + + data = SMB_MALLOC_ARRAY(char, oldlen+2); + + if (!data) { + SAFE_FREE(param); + return false; + } + + SSVAL(param,0,hard_link ? SMB_SET_FILE_UNIX_HLINK : SMB_SET_FILE_UNIX_LINK); + SIVAL(param,2,0); + p = ¶m[6]; + + p += clistr_push(cli, p, newname, newlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + p = data; + p += clistr_push(cli, p, oldname, oldlen, STR_TERMINATE); + data_len = PTR_DIFF(p, data); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(data); + SAFE_FREE(param); + return false; + } + + SAFE_FREE(data); + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + SAFE_FREE(data); + SAFE_FREE(param); + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return true; +} + +/**************************************************************************** + Map standard UNIX permissions onto wire representations. +****************************************************************************/ + +uint32 unix_perms_to_wire(mode_t perms) +{ + unsigned int ret = 0; + + ret |= ((perms & S_IXOTH) ? UNIX_X_OTH : 0); + ret |= ((perms & S_IWOTH) ? UNIX_W_OTH : 0); + ret |= ((perms & S_IROTH) ? UNIX_R_OTH : 0); + ret |= ((perms & S_IXGRP) ? UNIX_X_GRP : 0); + ret |= ((perms & S_IWGRP) ? UNIX_W_GRP : 0); + ret |= ((perms & S_IRGRP) ? UNIX_R_GRP : 0); + ret |= ((perms & S_IXUSR) ? UNIX_X_USR : 0); + ret |= ((perms & S_IWUSR) ? UNIX_W_USR : 0); + ret |= ((perms & S_IRUSR) ? UNIX_R_USR : 0); +#ifdef S_ISVTX + ret |= ((perms & S_ISVTX) ? UNIX_STICKY : 0); +#endif +#ifdef S_ISGID + ret |= ((perms & S_ISGID) ? UNIX_SET_GID : 0); +#endif +#ifdef S_ISUID + ret |= ((perms & S_ISUID) ? UNIX_SET_UID : 0); +#endif + return ret; +} + +/**************************************************************************** + Map wire permissions to standard UNIX. +****************************************************************************/ + +mode_t wire_perms_to_unix(uint32 perms) +{ + mode_t ret = (mode_t)0; + + ret |= ((perms & UNIX_X_OTH) ? S_IXOTH : 0); + ret |= ((perms & UNIX_W_OTH) ? S_IWOTH : 0); + ret |= ((perms & UNIX_R_OTH) ? S_IROTH : 0); + ret |= ((perms & UNIX_X_GRP) ? S_IXGRP : 0); + ret |= ((perms & UNIX_W_GRP) ? S_IWGRP : 0); + ret |= ((perms & UNIX_R_GRP) ? S_IRGRP : 0); + ret |= ((perms & UNIX_X_USR) ? S_IXUSR : 0); + ret |= ((perms & UNIX_W_USR) ? S_IWUSR : 0); + ret |= ((perms & UNIX_R_USR) ? S_IRUSR : 0); +#ifdef S_ISVTX + ret |= ((perms & UNIX_STICKY) ? S_ISVTX : 0); +#endif +#ifdef S_ISGID + ret |= ((perms & UNIX_SET_GID) ? S_ISGID : 0); +#endif +#ifdef S_ISUID + ret |= ((perms & UNIX_SET_UID) ? S_ISUID : 0); +#endif + return ret; +} + +/**************************************************************************** + Return the file type from the wire filetype for UNIX extensions. +****************************************************************************/ + +static mode_t unix_filetype_from_wire(uint32 wire_type) +{ + switch (wire_type) { + case UNIX_TYPE_FILE: + return S_IFREG; + case UNIX_TYPE_DIR: + return S_IFDIR; +#ifdef S_IFLNK + case UNIX_TYPE_SYMLINK: + return S_IFLNK; +#endif +#ifdef S_IFCHR + case UNIX_TYPE_CHARDEV: + return S_IFCHR; +#endif +#ifdef S_IFBLK + case UNIX_TYPE_BLKDEV: + return S_IFBLK; +#endif +#ifdef S_IFIFO + case UNIX_TYPE_FIFO: + return S_IFIFO; +#endif +#ifdef S_IFSOCK + case UNIX_TYPE_SOCKET: + return S_IFSOCK; +#endif + default: + return (mode_t)0; + } +} + +/**************************************************************************** + Do a POSIX getfacl (UNIX extensions). +****************************************************************************/ + +bool cli_unix_getfacl(struct cli_state *cli, const char *name, size_t *prb_size, char **retbuf) +{ + unsigned int param_len = 0; + unsigned int data_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + size_t nlen = 2*(strlen(name)+1); + char *rparam=NULL, *rdata=NULL; + char *p; + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + + p = param; + memset(p, '\0', 6); + SSVAL(p, 0, SMB_QUERY_POSIX_ACL); + p += 6; + p += clistr_push(cli, p, name, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, 0, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return false; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + if (data_len < 6) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return false; + } + + SAFE_FREE(rparam); + *retbuf = rdata; + *prb_size = (size_t)data_len; + + return true; +} + +/**************************************************************************** + Stat a file (UNIX extensions). +****************************************************************************/ + +bool cli_unix_stat(struct cli_state *cli, const char *name, SMB_STRUCT_STAT *sbuf) +{ + unsigned int param_len = 0; + unsigned int data_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + size_t nlen = 2*(strlen(name)+1); + char *rparam=NULL, *rdata=NULL; + char *p; + + ZERO_STRUCTP(sbuf); + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + p = param; + memset(p, '\0', 6); + SSVAL(p, 0, SMB_QUERY_FILE_UNIX_BASIC); + p += 6; + p += clistr_push(cli, p, name, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, 0, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return false; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + if (data_len < 96) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return false; + } + + sbuf->st_size = IVAL2_TO_SMB_BIG_UINT(rdata,0); /* total size, in bytes */ + sbuf->st_blocks = IVAL2_TO_SMB_BIG_UINT(rdata,8); /* number of blocks allocated */ +#if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE) + sbuf->st_blocks /= STAT_ST_BLOCKSIZE; +#else + /* assume 512 byte blocks */ + sbuf->st_blocks /= 512; +#endif + set_ctimespec(sbuf, interpret_long_date(rdata + 16)); /* time of last change */ + set_atimespec(sbuf, interpret_long_date(rdata + 24)); /* time of last access */ + set_mtimespec(sbuf, interpret_long_date(rdata + 32)); /* time of last modification */ + + sbuf->st_uid = (uid_t) IVAL(rdata,40); /* user ID of owner */ + sbuf->st_gid = (gid_t) IVAL(rdata,48); /* group ID of owner */ + sbuf->st_mode |= unix_filetype_from_wire(IVAL(rdata, 56)); +#if defined(HAVE_MAKEDEV) + { + uint32 dev_major = IVAL(rdata,60); + uint32 dev_minor = IVAL(rdata,68); + sbuf->st_rdev = makedev(dev_major, dev_minor); + } +#endif + sbuf->st_ino = (SMB_INO_T)IVAL2_TO_SMB_BIG_UINT(rdata,76); /* inode */ + sbuf->st_mode |= wire_perms_to_unix(IVAL(rdata,84)); /* protection */ + sbuf->st_nlink = IVAL(rdata,92); /* number of hard links */ + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return true; +} + +/**************************************************************************** + Symlink a file (UNIX extensions). +****************************************************************************/ + +bool cli_unix_symlink(struct cli_state *cli, const char *oldname, const char *newname) +{ + return cli_link_internal(cli, oldname, newname, False); +} + +/**************************************************************************** + Hard a file (UNIX extensions). +****************************************************************************/ + +bool cli_unix_hardlink(struct cli_state *cli, const char *oldname, const char *newname) +{ + return cli_link_internal(cli, oldname, newname, True); +} + +/**************************************************************************** + Chmod or chown a file internal (UNIX extensions). +****************************************************************************/ + +static bool cli_unix_chmod_chown_internal(struct cli_state *cli, const char *fname, uint32 mode, uint32 uid, uint32 gid) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_SETPATHINFO; + size_t nlen = 2*(strlen(fname)+1); + char *param; + char data[100]; + char *rparam=NULL, *rdata=NULL; + char *p; + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + memset(param, '\0', 6); + memset(data, 0, sizeof(data)); + + SSVAL(param,0,SMB_SET_FILE_UNIX_BASIC); + p = ¶m[6]; + + p += clistr_push(cli, p, fname, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + memset(data, 0xff, 40); /* Set all sizes/times to no change. */ + + SIVAL(data,40,uid); + SIVAL(data,48,gid); + SIVAL(data,84,mode); + + data_len = 100; + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return False; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return true; +} + +/**************************************************************************** + chmod a file (UNIX extensions). +****************************************************************************/ + +bool cli_unix_chmod(struct cli_state *cli, const char *fname, mode_t mode) +{ + return cli_unix_chmod_chown_internal(cli, fname, + unix_perms_to_wire(mode), SMB_UID_NO_CHANGE, SMB_GID_NO_CHANGE); +} + +/**************************************************************************** + chown a file (UNIX extensions). +****************************************************************************/ + +bool cli_unix_chown(struct cli_state *cli, const char *fname, uid_t uid, gid_t gid) +{ + return cli_unix_chmod_chown_internal(cli, fname, + SMB_MODE_NO_CHANGE, (uint32)uid, (uint32)gid); +} + +/**************************************************************************** + Rename a file. +****************************************************************************/ + +bool cli_rename(struct cli_state *cli, const char *fname_src, const char *fname_dst) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,1, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBmv); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,aSYSTEM | aHIDDEN | aDIR); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname_src, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + *p++ = 4; + p += clistr_push(cli, p, fname_dst, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return false; + } + + if (cli_is_error(cli)) { + return false; + } + + return true; +} + +/**************************************************************************** + NT Rename a file. +****************************************************************************/ + +bool cli_ntrename(struct cli_state *cli, const char *fname_src, const char *fname_dst) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf, 4, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBntrename); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,aSYSTEM | aHIDDEN | aDIR); + SSVAL(cli->outbuf,smb_vwv1, RENAME_FLAG_RENAME); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname_src, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + *p++ = 4; + p += clistr_push(cli, p, fname_dst, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return false; + } + + if (cli_is_error(cli)) { + return false; + } + + return true; +} + +/**************************************************************************** + NT hardlink a file. +****************************************************************************/ + +bool cli_nt_hardlink(struct cli_state *cli, const char *fname_src, const char *fname_dst) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf, 4, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBntrename); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,aSYSTEM | aHIDDEN | aDIR); + SSVAL(cli->outbuf,smb_vwv1, RENAME_FLAG_HARD_LINK); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname_src, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + *p++ = 4; + p += clistr_push(cli, p, fname_dst, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return false; + } + + if (cli_is_error(cli)) { + return false; + } + + return true; +} + +/**************************************************************************** + Delete a file. +****************************************************************************/ + +bool cli_unlink_full(struct cli_state *cli, const char *fname, uint16 attrs) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,1, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBunlink); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0, attrs); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return false; + } + + if (cli_is_error(cli)) { + return false; + } + + return true; +} + +/**************************************************************************** + Delete a file. +****************************************************************************/ + +bool cli_unlink(struct cli_state *cli, const char *fname) +{ + return cli_unlink_full(cli, fname, aSYSTEM | aHIDDEN); +} + +/**************************************************************************** + Create a directory. +****************************************************************************/ + +bool cli_mkdir(struct cli_state *cli, const char *dname) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,0, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBmkdir); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, dname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Remove a directory. +****************************************************************************/ + +bool cli_rmdir(struct cli_state *cli, const char *dname) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,0, 0, true); + + SCVAL(cli->outbuf,smb_com,SMBrmdir); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, dname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return false; + } + + if (cli_is_error(cli)) { + return false; + } + + return true; +} + +/**************************************************************************** + Set or clear the delete on close flag. +****************************************************************************/ + +int cli_nt_delete_on_close(struct cli_state *cli, int fnum, bool flag) +{ + unsigned int data_len = 1; + unsigned int param_len = 6; + uint16 setup = TRANSACT2_SETFILEINFO; + char param[6]; + unsigned char data; + char *rparam=NULL, *rdata=NULL; + + memset(param, 0, param_len); + SSVAL(param,0,fnum); + SSVAL(param,2,SMB_SET_FILE_DISPOSITION_INFO); + + data = flag ? 1 : 0; + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + return false; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return true; +} + +/**************************************************************************** + Open a file - exposing the full horror of the NT API :-). + Used in smbtorture. +****************************************************************************/ + +int cli_nt_create_full(struct cli_state *cli, const char *fname, + uint32 CreatFlags, uint32 DesiredAccess, + uint32 FileAttributes, uint32 ShareAccess, + uint32 CreateDisposition, uint32 CreateOptions, + uint8 SecurityFlags) +{ + char *p; + int len; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,24,0, true); + + SCVAL(cli->outbuf,smb_com,SMBntcreateX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,0xFF); + if (cli->use_oplocks) + CreatFlags |= (REQUEST_OPLOCK|REQUEST_BATCH_OPLOCK); + + SIVAL(cli->outbuf,smb_ntcreate_Flags, CreatFlags); + SIVAL(cli->outbuf,smb_ntcreate_RootDirectoryFid, 0x0); + SIVAL(cli->outbuf,smb_ntcreate_DesiredAccess, DesiredAccess); + SIVAL(cli->outbuf,smb_ntcreate_FileAttributes, FileAttributes); + SIVAL(cli->outbuf,smb_ntcreate_ShareAccess, ShareAccess); + SIVAL(cli->outbuf,smb_ntcreate_CreateDisposition, CreateDisposition); + SIVAL(cli->outbuf,smb_ntcreate_CreateOptions, CreateOptions); + SIVAL(cli->outbuf,smb_ntcreate_ImpersonationLevel, 0x02); + SCVAL(cli->outbuf,smb_ntcreate_SecurityFlags, SecurityFlags); + + p = smb_buf(cli->outbuf); + /* this alignment and termination is critical for netapp filers. Don't change */ + p += clistr_align_out(cli, p, 0); + len = clistr_push(cli, p, fname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), 0); + p += len; + SSVAL(cli->outbuf,smb_ntcreate_NameLength, len); + /* sigh. this copes with broken netapp filer behaviour */ + p += clistr_push(cli, p, "", + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return -1; + } + + if (cli_is_error(cli)) { + return -1; + } + + return SVAL(cli->inbuf,smb_vwv2 + 1); +} + +/**************************************************************************** + Open a file. +****************************************************************************/ + +int cli_nt_create(struct cli_state *cli, const char *fname, uint32 DesiredAccess) +{ + return cli_nt_create_full(cli, fname, 0, DesiredAccess, 0, + FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0); +} + +uint8_t *smb_bytes_push_str(uint8_t *buf, bool ucs2, const char *str) +{ + size_t buflen = talloc_get_size(buf); + char *converted; + size_t converted_size; + + /* + * We're pushing into an SMB buffer, align odd + */ + if (ucs2 && (buflen % 2 == 0)) { + buf = TALLOC_REALLOC_ARRAY(NULL, buf, uint8_t, buflen + 1); + if (buf == NULL) { + return NULL; + } + buf[buflen] = '\0'; + buflen += 1; + } + + if (!convert_string_allocate(talloc_tos(), CH_UNIX, + ucs2 ? CH_UTF16LE : CH_DOS, + str, strlen(str)+1, &converted, + &converted_size, true)) { + return NULL; + } + + buf = TALLOC_REALLOC_ARRAY(NULL, buf, uint8_t, + buflen + converted_size); + if (buf == NULL) { + return NULL; + } + + memcpy(buf + buflen, converted, converted_size); + + TALLOC_FREE(converted); + return buf; +} + +/**************************************************************************** + Open a file + WARNING: if you open with O_WRONLY then getattrE won't work! +****************************************************************************/ + +struct async_req *cli_open_send(TALLOC_CTX *mem_ctx, struct event_context *ev, + struct cli_state *cli, + const char *fname, int flags, int share_mode) +{ + unsigned openfn = 0; + unsigned accessmode = 0; + uint8_t additional_flags = 0; + uint8_t *bytes; + uint16_t vwv[15]; + struct async_req *result; + + if (flags & O_CREAT) { + openfn |= (1<<4); + } + if (!(flags & O_EXCL)) { + if (flags & O_TRUNC) + openfn |= (1<<1); + else + openfn |= (1<<0); + } + + accessmode = (share_mode<<4); + + if ((flags & O_ACCMODE) == O_RDWR) { + accessmode |= 2; + } else if ((flags & O_ACCMODE) == O_WRONLY) { + accessmode |= 1; + } + +#if defined(O_SYNC) + if ((flags & O_SYNC) == O_SYNC) { + accessmode |= (1<<14); + } +#endif /* O_SYNC */ + + if (share_mode == DENY_FCB) { + accessmode = 0xFF; + } + + SCVAL(vwv + 0, 0, 0xFF); + SCVAL(vwv + 0, 1, 0); + SSVAL(vwv + 1, 0, 0); + SSVAL(vwv + 2, 0, 0); /* no additional info */ + SSVAL(vwv + 3, 0, accessmode); + SSVAL(vwv + 4, 0, aSYSTEM | aHIDDEN); + SSVAL(vwv + 5, 0, 0); + SIVAL(vwv + 6, 0, 0); + SSVAL(vwv + 8, 0, openfn); + SIVAL(vwv + 9, 0, 0); + SIVAL(vwv + 11, 0, 0); + SIVAL(vwv + 13, 0, 0); + + if (cli->use_oplocks) { + /* if using oplocks then ask for a batch oplock via + core and extended methods */ + additional_flags = + FLAG_REQUEST_OPLOCK|FLAG_REQUEST_BATCH_OPLOCK; + SSVAL(vwv+2, 0, SVAL(vwv+2, 0) | 6); + } + + bytes = talloc_array(talloc_tos(), uint8_t, 0); + if (bytes == NULL) { + return NULL; + } + + bytes = smb_bytes_push_str( + bytes, (cli->capabilities & CAP_UNICODE) != 0, fname); + if (bytes == NULL) { + return NULL; + } + + result = cli_request_send(mem_ctx, ev, cli, SMBopenX, additional_flags, + 15, vwv, talloc_get_size(bytes), bytes); + TALLOC_FREE(bytes); + return result; +} + +NTSTATUS cli_open_recv(struct async_req *req, int *fnum) +{ + uint8_t wct; + uint16_t *vwv; + uint16_t num_bytes; + uint8_t *bytes; + NTSTATUS status; + + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + + status = cli_pull_reply(req, &wct, &vwv, &num_bytes, &bytes); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (wct < 3) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + *fnum = SVAL(vwv+2, 0); + + return NT_STATUS_OK; +} + +int cli_open(struct cli_state *cli, const char *fname, int flags, + int share_mode) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct async_req *req; + int result = -1; + + if (cli->fd_event != NULL) { + /* + * Can't use sync call while an async call is in flight + */ + cli_set_error(cli, NT_STATUS_INVALID_PARAMETER); + goto fail; + } + + ev = event_context_init(frame); + if (ev == NULL) { + goto fail; + } + + req = cli_open_send(frame, ev, cli, fname, flags, share_mode); + if (req == NULL) { + goto fail; + } + + while (req->state < ASYNC_REQ_DONE) { + event_loop_once(ev); + } + + cli_open_recv(req, &result); + fail: + TALLOC_FREE(frame); + return result; +} + +/**************************************************************************** + Close a file. +****************************************************************************/ + +struct async_req *cli_close_send(TALLOC_CTX *mem_ctx, struct event_context *ev, + struct cli_state *cli, int fnum) +{ + uint16_t vwv[3]; + + SSVAL(vwv+0, 0, fnum); + SIVALS(vwv+1, 0, -1); + + return cli_request_send(mem_ctx, ev, cli, SMBclose, 0, 3, vwv, + 0, NULL); +} + +NTSTATUS cli_close_recv(struct async_req *req) +{ + uint8_t wct; + uint16_t *vwv; + uint16_t num_bytes; + uint8_t *bytes; + + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + + return cli_pull_reply(req, &wct, &vwv, &num_bytes, &bytes); +} + +bool cli_close(struct cli_state *cli, int fnum) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct async_req *req; + bool result = false; + + if (cli->fd_event != NULL) { + /* + * Can't use sync call while an async call is in flight + */ + cli_set_error(cli, NT_STATUS_INVALID_PARAMETER); + goto fail; + } + + ev = event_context_init(frame); + if (ev == NULL) { + goto fail; + } + + req = cli_close_send(frame, ev, cli, fnum); + if (req == NULL) { + goto fail; + } + + while (req->state < ASYNC_REQ_DONE) { + event_loop_once(ev); + } + + result = NT_STATUS_IS_OK(cli_close_recv(req)); + fail: + TALLOC_FREE(frame); + return result; +} + +/**************************************************************************** + Truncate a file to a specified size +****************************************************************************/ + +bool cli_ftruncate(struct cli_state *cli, int fnum, uint64_t size) +{ + unsigned int param_len = 6; + unsigned int data_len = 8; + uint16 setup = TRANSACT2_SETFILEINFO; + char param[6]; + unsigned char data[8]; + char *rparam=NULL, *rdata=NULL; + int saved_timeout = cli->timeout; + + SSVAL(param,0,fnum); + SSVAL(param,2,SMB_SET_FILE_END_OF_FILE_INFO); + SSVAL(param,4,0); + + SBVAL(data, 0, size); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len,/* data, length, ... */ + cli->max_xmit)) { /* ... max */ + cli->timeout = saved_timeout; + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + cli->timeout = saved_timeout; + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return False; + } + + cli->timeout = saved_timeout; + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return True; +} + + +/**************************************************************************** + send a lock with a specified locktype + this is used for testing LOCKING_ANDX_CANCEL_LOCK +****************************************************************************/ + +NTSTATUS cli_locktype(struct cli_state *cli, int fnum, + uint32 offset, uint32 len, + int timeout, unsigned char locktype) +{ + char *p; + int saved_timeout = cli->timeout; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0', smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBlockingX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + SCVAL(cli->outbuf,smb_vwv3,locktype); + SIVALS(cli->outbuf, smb_vwv4, timeout); + SSVAL(cli->outbuf,smb_vwv6,0); + SSVAL(cli->outbuf,smb_vwv7,1); + + p = smb_buf(cli->outbuf); + SSVAL(p, 0, cli->pid); + SIVAL(p, 2, offset); + SIVAL(p, 6, len); + + p += 10; + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + + if (timeout != 0) { + cli->timeout = (timeout == -1) ? 0x7FFFFFFF : (timeout + 2*1000); + } + + if (!cli_receive_smb(cli)) { + cli->timeout = saved_timeout; + return NT_STATUS_UNSUCCESSFUL; + } + + cli->timeout = saved_timeout; + + return cli_nt_error(cli); +} + +/**************************************************************************** + Lock a file. + note that timeout is in units of 2 milliseconds +****************************************************************************/ + +bool cli_lock(struct cli_state *cli, int fnum, + uint32 offset, uint32 len, int timeout, enum brl_type lock_type) +{ + char *p; + int saved_timeout = cli->timeout; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0', smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBlockingX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + SCVAL(cli->outbuf,smb_vwv3,(lock_type == READ_LOCK? 1 : 0)); + SIVALS(cli->outbuf, smb_vwv4, timeout); + SSVAL(cli->outbuf,smb_vwv6,0); + SSVAL(cli->outbuf,smb_vwv7,1); + + p = smb_buf(cli->outbuf); + SSVAL(p, 0, cli->pid); + SIVAL(p, 2, offset); + SIVAL(p, 6, len); + + p += 10; + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + + if (timeout != 0) { + cli->timeout = (timeout == -1) ? 0x7FFFFFFF : (timeout*2 + 5*1000); + } + + if (!cli_receive_smb(cli)) { + cli->timeout = saved_timeout; + return False; + } + + cli->timeout = saved_timeout; + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Unlock a file. +****************************************************************************/ + +bool cli_unlock(struct cli_state *cli, int fnum, uint32 offset, uint32 len) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBlockingX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + SCVAL(cli->outbuf,smb_vwv3,0); + SIVALS(cli->outbuf, smb_vwv4, 0); + SSVAL(cli->outbuf,smb_vwv6,1); + SSVAL(cli->outbuf,smb_vwv7,0); + + p = smb_buf(cli->outbuf); + SSVAL(p, 0, cli->pid); + SIVAL(p, 2, offset); + SIVAL(p, 6, len); + p += 10; + cli_setup_bcc(cli, p); + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Lock a file with 64 bit offsets. +****************************************************************************/ + +bool cli_lock64(struct cli_state *cli, int fnum, + SMB_BIG_UINT offset, SMB_BIG_UINT len, int timeout, enum brl_type lock_type) +{ + char *p; + int saved_timeout = cli->timeout; + int ltype; + + if (! (cli->capabilities & CAP_LARGE_FILES)) { + return cli_lock(cli, fnum, offset, len, timeout, lock_type); + } + + ltype = (lock_type == READ_LOCK? 1 : 0); + ltype |= LOCKING_ANDX_LARGE_FILES; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0', smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBlockingX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + SCVAL(cli->outbuf,smb_vwv3,ltype); + SIVALS(cli->outbuf, smb_vwv4, timeout); + SSVAL(cli->outbuf,smb_vwv6,0); + SSVAL(cli->outbuf,smb_vwv7,1); + + p = smb_buf(cli->outbuf); + SIVAL(p, 0, cli->pid); + SOFF_T_R(p, 4, offset); + SOFF_T_R(p, 12, len); + p += 20; + + cli_setup_bcc(cli, p); + cli_send_smb(cli); + + if (timeout != 0) { + cli->timeout = (timeout == -1) ? 0x7FFFFFFF : (timeout + 5*1000); + } + + if (!cli_receive_smb(cli)) { + cli->timeout = saved_timeout; + return False; + } + + cli->timeout = saved_timeout; + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Unlock a file with 64 bit offsets. +****************************************************************************/ + +bool cli_unlock64(struct cli_state *cli, int fnum, SMB_BIG_UINT offset, SMB_BIG_UINT len) +{ + char *p; + + if (! (cli->capabilities & CAP_LARGE_FILES)) { + return cli_unlock(cli, fnum, offset, len); + } + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBlockingX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + SCVAL(cli->outbuf,smb_vwv3,LOCKING_ANDX_LARGE_FILES); + SIVALS(cli->outbuf, smb_vwv4, 0); + SSVAL(cli->outbuf,smb_vwv6,1); + SSVAL(cli->outbuf,smb_vwv7,0); + + p = smb_buf(cli->outbuf); + SIVAL(p, 0, cli->pid); + SOFF_T_R(p, 4, offset); + SOFF_T_R(p, 12, len); + p += 20; + cli_setup_bcc(cli, p); + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Get/unlock a POSIX lock on a file - internal function. +****************************************************************************/ + +static bool cli_posix_lock_internal(struct cli_state *cli, int fnum, + SMB_BIG_UINT offset, SMB_BIG_UINT len, bool wait_lock, enum brl_type lock_type) +{ + unsigned int param_len = 4; + unsigned int data_len = POSIX_LOCK_DATA_SIZE; + uint16 setup = TRANSACT2_SETFILEINFO; + char param[4]; + unsigned char data[POSIX_LOCK_DATA_SIZE]; + char *rparam=NULL, *rdata=NULL; + int saved_timeout = cli->timeout; + + SSVAL(param,0,fnum); + SSVAL(param,2,SMB_SET_POSIX_LOCK); + + switch (lock_type) { + case READ_LOCK: + SSVAL(data, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_READ); + break; + case WRITE_LOCK: + SSVAL(data, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_WRITE); + break; + case UNLOCK_LOCK: + SSVAL(data, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_UNLOCK); + break; + default: + return False; + } + + if (wait_lock) { + SSVAL(data, POSIX_LOCK_FLAGS_OFFSET, POSIX_LOCK_FLAG_WAIT); + cli->timeout = 0x7FFFFFFF; + } else { + SSVAL(data, POSIX_LOCK_FLAGS_OFFSET, POSIX_LOCK_FLAG_NOWAIT); + } + + SIVAL(data, POSIX_LOCK_PID_OFFSET, cli->pid); + SOFF_T(data, POSIX_LOCK_START_OFFSET, offset); + SOFF_T(data, POSIX_LOCK_LEN_OFFSET, len); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + cli->timeout = saved_timeout; + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + cli->timeout = saved_timeout; + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return False; + } + + cli->timeout = saved_timeout; + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return True; +} + +/**************************************************************************** + POSIX Lock a file. +****************************************************************************/ + +bool cli_posix_lock(struct cli_state *cli, int fnum, + SMB_BIG_UINT offset, SMB_BIG_UINT len, + bool wait_lock, enum brl_type lock_type) +{ + if (lock_type != READ_LOCK && lock_type != WRITE_LOCK) { + return False; + } + return cli_posix_lock_internal(cli, fnum, offset, len, wait_lock, lock_type); +} + +/**************************************************************************** + POSIX Unlock a file. +****************************************************************************/ + +bool cli_posix_unlock(struct cli_state *cli, int fnum, SMB_BIG_UINT offset, SMB_BIG_UINT len) +{ + return cli_posix_lock_internal(cli, fnum, offset, len, False, UNLOCK_LOCK); +} + +/**************************************************************************** + POSIX Get any lock covering a file. +****************************************************************************/ + +bool cli_posix_getlock(struct cli_state *cli, int fnum, SMB_BIG_UINT *poffset, SMB_BIG_UINT *plen) +{ + return True; +} + +/**************************************************************************** + Do a SMBgetattrE call. +****************************************************************************/ + +bool cli_getattrE(struct cli_state *cli, int fd, + uint16 *attr, SMB_OFF_T *size, + time_t *change_time, + time_t *access_time, + time_t *write_time) +{ + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,1,0,True); + + SCVAL(cli->outbuf,smb_com,SMBgetattrE); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,fd); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + if (size) { + *size = IVAL(cli->inbuf, smb_vwv6); + } + + if (attr) { + *attr = SVAL(cli->inbuf,smb_vwv10); + } + + if (change_time) { + *change_time = cli_make_unix_date2(cli, cli->inbuf+smb_vwv0); + } + + if (access_time) { + *access_time = cli_make_unix_date2(cli, cli->inbuf+smb_vwv2); + } + + if (write_time) { + *write_time = cli_make_unix_date2(cli, cli->inbuf+smb_vwv4); + } + + return True; +} + +/**************************************************************************** + Do a SMBgetatr call +****************************************************************************/ + +bool cli_getatr(struct cli_state *cli, const char *fname, + uint16 *attr, SMB_OFF_T *size, time_t *write_time) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,0,0,True); + + SCVAL(cli->outbuf,smb_com,SMBgetatr); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + if (size) { + *size = IVAL(cli->inbuf, smb_vwv3); + } + + if (write_time) { + *write_time = cli_make_unix_date3(cli, cli->inbuf+smb_vwv1); + } + + if (attr) { + *attr = SVAL(cli->inbuf,smb_vwv0); + } + + return True; +} + +/**************************************************************************** + Do a SMBsetattrE call. +****************************************************************************/ + +bool cli_setattrE(struct cli_state *cli, int fd, + time_t change_time, + time_t access_time, + time_t write_time) + +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,7,0,True); + + SCVAL(cli->outbuf,smb_com,SMBsetattrE); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0, fd); + cli_put_dos_date2(cli, cli->outbuf,smb_vwv1, change_time); + cli_put_dos_date2(cli, cli->outbuf,smb_vwv3, access_time); + cli_put_dos_date2(cli, cli->outbuf,smb_vwv5, write_time); + + p = smb_buf(cli->outbuf); + *p++ = 4; + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Do a SMBsetatr call. +****************************************************************************/ + +bool cli_setatr(struct cli_state *cli, const char *fname, uint16 attr, time_t t) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,8,0,True); + + SCVAL(cli->outbuf,smb_com,SMBsetatr); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0, attr); + cli_put_dos_date3(cli, cli->outbuf,smb_vwv1, t); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, fname, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + *p++ = 4; + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) { + return False; + } + + return True; +} + +/**************************************************************************** + Check for existance of a dir. +****************************************************************************/ + +bool cli_chkpath(struct cli_state *cli, const char *path) +{ + char *path2 = NULL; + char *p; + TALLOC_CTX *frame = talloc_stackframe(); + + path2 = talloc_strdup(frame, path); + if (!path2) { + TALLOC_FREE(frame); + return false; + } + trim_char(path2,'\0','\\'); + if (!*path2) { + path2 = talloc_strdup(frame, "\\"); + if (!path2) { + TALLOC_FREE(frame); + return false; + } + } + + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,0,0,True); + SCVAL(cli->outbuf,smb_com,SMBcheckpath); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, path2, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + TALLOC_FREE(frame); + return False; + } + + TALLOC_FREE(frame); + + if (cli_is_error(cli)) return False; + + return True; +} + +/**************************************************************************** + Query disk space. +****************************************************************************/ + +bool cli_dskattr(struct cli_state *cli, int *bsize, int *total, int *avail) +{ + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,0,0,True); + SCVAL(cli->outbuf,smb_com,SMBdskattr); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + *bsize = SVAL(cli->inbuf,smb_vwv1)*SVAL(cli->inbuf,smb_vwv2); + *total = SVAL(cli->inbuf,smb_vwv0); + *avail = SVAL(cli->inbuf,smb_vwv3); + + return True; +} + +/**************************************************************************** + Create and open a temporary file. +****************************************************************************/ + +int cli_ctemp(struct cli_state *cli, const char *path, char **tmp_path) +{ + int len; + char *p; + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,3,0,True); + + SCVAL(cli->outbuf,smb_com,SMBctemp); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,0); + SIVALS(cli->outbuf,smb_vwv1,-1); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, path, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return -1; + } + + if (cli_is_error(cli)) { + return -1; + } + + /* despite the spec, the result has a -1, followed by + length, followed by name */ + p = smb_buf(cli->inbuf); + p += 4; + len = smb_buflen(cli->inbuf) - 4; + if (len <= 0 || len > PATH_MAX) return -1; + + if (tmp_path) { + char *path2 = SMB_MALLOC_ARRAY(char, len+1); + if (!path2) { + return -1; + } + clistr_pull(cli, path2, p, + len+1, len, STR_ASCII); + *tmp_path = path2; + } + + return SVAL(cli->inbuf,smb_vwv0); +} + +/* + send a raw ioctl - used by the torture code +*/ +NTSTATUS cli_raw_ioctl(struct cli_state *cli, int fnum, uint32 code, DATA_BLOB *blob) +{ + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf, 3, 0, True); + SCVAL(cli->outbuf,smb_com,SMBioctl); + cli_setup_packet(cli); + + SSVAL(cli->outbuf, smb_vwv0, fnum); + SSVAL(cli->outbuf, smb_vwv1, code>>16); + SSVAL(cli->outbuf, smb_vwv2, (code&0xFFFF)); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + if (cli_is_error(cli)) { + return cli_nt_error(cli); + } + + *blob = data_blob_null; + + return NT_STATUS_OK; +} + +/********************************************************* + Set an extended attribute utility fn. +*********************************************************/ + +static bool cli_set_ea(struct cli_state *cli, uint16 setup, char *param, unsigned int param_len, + const char *ea_name, const char *ea_val, size_t ea_len) +{ + unsigned int data_len = 0; + char *data = NULL; + char *rparam=NULL, *rdata=NULL; + char *p; + size_t ea_namelen = strlen(ea_name); + + if (ea_namelen == 0 && ea_len == 0) { + data_len = 4; + data = (char *)SMB_MALLOC(data_len); + if (!data) { + return False; + } + p = data; + SIVAL(p,0,data_len); + } else { + data_len = 4 + 4 + ea_namelen + 1 + ea_len; + data = (char *)SMB_MALLOC(data_len); + if (!data) { + return False; + } + p = data; + SIVAL(p,0,data_len); + p += 4; + SCVAL(p, 0, 0); /* EA flags. */ + SCVAL(p, 1, ea_namelen); + SSVAL(p, 2, ea_len); + memcpy(p+4, ea_name, ea_namelen+1); /* Copy in the name. */ + memcpy(p+4+ea_namelen+1, ea_val, ea_len); + } + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + data, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(data); + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + SAFE_FREE(data); + return false; + } + + SAFE_FREE(data); + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return True; +} + +/********************************************************* + Set an extended attribute on a pathname. +*********************************************************/ + +bool cli_set_ea_path(struct cli_state *cli, const char *path, const char *ea_name, const char *ea_val, size_t ea_len) +{ + uint16 setup = TRANSACT2_SETPATHINFO; + unsigned int param_len = 0; + char *param; + size_t srclen = 2*(strlen(path)+1); + char *p; + bool ret; + + param = SMB_MALLOC_ARRAY(char, 6+srclen+2); + if (!param) { + return false; + } + memset(param, '\0', 6); + SSVAL(param,0,SMB_INFO_SET_EA); + p = ¶m[6]; + + p += clistr_push(cli, p, path, srclen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + ret = cli_set_ea(cli, setup, param, param_len, ea_name, ea_val, ea_len); + SAFE_FREE(param); + return ret; +} + +/********************************************************* + Set an extended attribute on an fnum. +*********************************************************/ + +bool cli_set_ea_fnum(struct cli_state *cli, int fnum, const char *ea_name, const char *ea_val, size_t ea_len) +{ + char param[6]; + uint16 setup = TRANSACT2_SETFILEINFO; + + memset(param, 0, 6); + SSVAL(param,0,fnum); + SSVAL(param,2,SMB_INFO_SET_EA); + + return cli_set_ea(cli, setup, param, 6, ea_name, ea_val, ea_len); +} + +/********************************************************* + Get an extended attribute list utility fn. +*********************************************************/ + +static bool cli_get_ea_list(struct cli_state *cli, + uint16 setup, char *param, unsigned int param_len, + TALLOC_CTX *ctx, + size_t *pnum_eas, + struct ea_struct **pea_list) +{ + unsigned int data_len = 0; + unsigned int rparam_len, rdata_len; + char *rparam=NULL, *rdata=NULL; + char *p; + size_t ea_size; + size_t num_eas; + bool ret = False; + struct ea_struct *ea_list; + + *pnum_eas = 0; + if (pea_list) { + *pea_list = NULL; + } + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* Name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + )) { + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_len, + &rdata, &rdata_len)) { + return False; + } + + if (!rdata || rdata_len < 4) { + goto out; + } + + ea_size = (size_t)IVAL(rdata,0); + if (ea_size > rdata_len) { + goto out; + } + + if (ea_size == 0) { + /* No EA's present. */ + ret = True; + goto out; + } + + p = rdata + 4; + ea_size -= 4; + + /* Validate the EA list and count it. */ + for (num_eas = 0; ea_size >= 4; num_eas++) { + unsigned int ea_namelen = CVAL(p,1); + unsigned int ea_valuelen = SVAL(p,2); + if (ea_namelen == 0) { + goto out; + } + if (4 + ea_namelen + 1 + ea_valuelen > ea_size) { + goto out; + } + ea_size -= 4 + ea_namelen + 1 + ea_valuelen; + p += 4 + ea_namelen + 1 + ea_valuelen; + } + + if (num_eas == 0) { + ret = True; + goto out; + } + + *pnum_eas = num_eas; + if (!pea_list) { + /* Caller only wants number of EA's. */ + ret = True; + goto out; + } + + ea_list = TALLOC_ARRAY(ctx, struct ea_struct, num_eas); + if (!ea_list) { + goto out; + } + + ea_size = (size_t)IVAL(rdata,0); + p = rdata + 4; + + for (num_eas = 0; num_eas < *pnum_eas; num_eas++ ) { + struct ea_struct *ea = &ea_list[num_eas]; + fstring unix_ea_name; + unsigned int ea_namelen = CVAL(p,1); + unsigned int ea_valuelen = SVAL(p,2); + + ea->flags = CVAL(p,0); + unix_ea_name[0] = '\0'; + pull_ascii_fstring(unix_ea_name, p + 4); + ea->name = talloc_strdup(ctx, unix_ea_name); + /* Ensure the value is null terminated (in case it's a string). */ + ea->value = data_blob_talloc(ctx, NULL, ea_valuelen + 1); + if (!ea->value.data) { + goto out; + } + if (ea_valuelen) { + memcpy(ea->value.data, p+4+ea_namelen+1, ea_valuelen); + } + ea->value.data[ea_valuelen] = 0; + ea->value.length--; + p += 4 + ea_namelen + 1 + ea_valuelen; + } + + *pea_list = ea_list; + ret = True; + + out : + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return ret; +} + +/********************************************************* + Get an extended attribute list from a pathname. +*********************************************************/ + +bool cli_get_ea_list_path(struct cli_state *cli, const char *path, + TALLOC_CTX *ctx, + size_t *pnum_eas, + struct ea_struct **pea_list) +{ + uint16 setup = TRANSACT2_QPATHINFO; + unsigned int param_len = 0; + char *param; + char *p; + size_t srclen = 2*(strlen(path)+1); + bool ret; + + param = SMB_MALLOC_ARRAY(char, 6+srclen+2); + if (!param) { + return false; + } + p = param; + memset(p, 0, 6); + SSVAL(p, 0, SMB_INFO_QUERY_ALL_EAS); + p += 6; + p += clistr_push(cli, p, path, srclen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + ret = cli_get_ea_list(cli, setup, param, param_len, ctx, pnum_eas, pea_list); + SAFE_FREE(param); + return ret; +} + +/********************************************************* + Get an extended attribute list from an fnum. +*********************************************************/ + +bool cli_get_ea_list_fnum(struct cli_state *cli, int fnum, + TALLOC_CTX *ctx, + size_t *pnum_eas, + struct ea_struct **pea_list) +{ + uint16 setup = TRANSACT2_QFILEINFO; + char param[6]; + + memset(param, 0, 6); + SSVAL(param,0,fnum); + SSVAL(param,2,SMB_INFO_SET_EA); + + return cli_get_ea_list(cli, setup, param, 6, ctx, pnum_eas, pea_list); +} + +/**************************************************************************** + Convert open "flags" arg to uint32 on wire. +****************************************************************************/ + +static uint32 open_flags_to_wire(int flags) +{ + int open_mode = flags & O_ACCMODE; + uint32 ret = 0; + + switch (open_mode) { + case O_WRONLY: + ret |= SMB_O_WRONLY; + break; + case O_RDWR: + ret |= SMB_O_RDWR; + break; + default: + case O_RDONLY: + ret |= SMB_O_RDONLY; + break; + } + + if (flags & O_CREAT) { + ret |= SMB_O_CREAT; + } + if (flags & O_EXCL) { + ret |= SMB_O_EXCL; + } + if (flags & O_TRUNC) { + ret |= SMB_O_TRUNC; + } +#if defined(O_SYNC) + if (flags & O_SYNC) { + ret |= SMB_O_SYNC; + } +#endif /* O_SYNC */ + if (flags & O_APPEND) { + ret |= SMB_O_APPEND; + } +#if defined(O_DIRECT) + if (flags & O_DIRECT) { + ret |= SMB_O_DIRECT; + } +#endif +#if defined(O_DIRECTORY) + if (flags & O_DIRECTORY) { + ret &= ~(SMB_O_RDONLY|SMB_O_RDWR|SMB_O_WRONLY); + ret |= SMB_O_DIRECTORY; + } +#endif + return ret; +} + +/**************************************************************************** + Open a file - POSIX semantics. Returns fnum. Doesn't request oplock. +****************************************************************************/ + +static int cli_posix_open_internal(struct cli_state *cli, const char *fname, int flags, mode_t mode, bool is_dir) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_SETPATHINFO; + char *param; + char data[18]; + char *rparam=NULL, *rdata=NULL; + char *p; + int fnum = -1; + uint32 wire_flags = open_flags_to_wire(flags); + size_t srclen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+srclen+2); + if (!param) { + return false; + } + memset(param, '\0', 6); + SSVAL(param,0, SMB_POSIX_PATH_OPEN); + p = ¶m[6]; + + p += clistr_push(cli, p, fname, srclen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + if (is_dir) { + wire_flags &= ~(SMB_O_RDONLY|SMB_O_RDWR|SMB_O_WRONLY); + wire_flags |= SMB_O_DIRECTORY; + } + + p = data; + SIVAL(p,0,0); /* No oplock. */ + SIVAL(p,4,wire_flags); + SIVAL(p,8,unix_perms_to_wire(mode)); + SIVAL(p,12,0); /* Top bits of perms currently undefined. */ + SSVAL(p,16,SMB_NO_INFO_LEVEL_RETURNED); /* No info level returned. */ + + data_len = 18; + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return -1; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return -1; + } + + fnum = SVAL(rdata,2); + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return fnum; +} + +/**************************************************************************** + open - POSIX semantics. +****************************************************************************/ + +int cli_posix_open(struct cli_state *cli, const char *fname, int flags, mode_t mode) +{ + return cli_posix_open_internal(cli, fname, flags, mode, False); +} + +/**************************************************************************** + mkdir - POSIX semantics. +****************************************************************************/ + +int cli_posix_mkdir(struct cli_state *cli, const char *fname, mode_t mode) +{ + return (cli_posix_open_internal(cli, fname, O_CREAT, mode, True) == -1) ? -1 : 0; +} + +/**************************************************************************** + unlink or rmdir - POSIX semantics. +****************************************************************************/ + +static bool cli_posix_unlink_internal(struct cli_state *cli, const char *fname, bool is_dir) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_SETPATHINFO; + char *param; + char data[2]; + char *rparam=NULL, *rdata=NULL; + char *p; + size_t srclen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+srclen+2); + if (!param) { + return false; + } + memset(param, '\0', 6); + SSVAL(param,0, SMB_POSIX_PATH_UNLINK); + p = ¶m[6]; + + p += clistr_push(cli, p, fname, srclen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + SSVAL(data, 0, is_dir ? SMB_POSIX_UNLINK_DIRECTORY_TARGET : + SMB_POSIX_UNLINK_FILE_TARGET); + data_len = 2; + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + (char *)&data, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return False; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return False; + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return True; +} + +/**************************************************************************** + unlink - POSIX semantics. +****************************************************************************/ + +bool cli_posix_unlink(struct cli_state *cli, const char *fname) +{ + return cli_posix_unlink_internal(cli, fname, False); +} + +/**************************************************************************** + rmdir - POSIX semantics. +****************************************************************************/ + +int cli_posix_rmdir(struct cli_state *cli, const char *fname) +{ + return cli_posix_unlink_internal(cli, fname, True); +} diff --git a/source3/libsmb/clifsinfo.c b/source3/libsmb/clifsinfo.c new file mode 100644 index 0000000000..5e73b61cd2 --- /dev/null +++ b/source3/libsmb/clifsinfo.c @@ -0,0 +1,664 @@ +/* + Unix SMB/CIFS implementation. + FS info functions + Copyright (C) Stefan (metze) Metzmacher 2003 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Get UNIX extensions version info. +****************************************************************************/ + +bool cli_unix_extensions_version(struct cli_state *cli, uint16 *pmajor, uint16 *pminor, + uint32 *pcaplow, uint32 *pcaphigh) +{ + bool ret = False; + uint16 setup; + char param[2]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + + setup = TRANSACT2_QFSINFO; + + SSVAL(param,0,SMB_QUERY_CIFS_UNIX_INFO); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 2, 0, + NULL, 0, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count < 12) { + goto cleanup; + } + + *pmajor = SVAL(rdata,0); + *pminor = SVAL(rdata,2); + cli->posix_capabilities = *pcaplow = IVAL(rdata,4); + *pcaphigh = IVAL(rdata,8); + + /* todo: but not yet needed + * return the other stuff + */ + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +/**************************************************************************** + Set UNIX extensions capabilities. +****************************************************************************/ + +bool cli_set_unix_extensions_capabilities(struct cli_state *cli, uint16 major, uint16 minor, + uint32 caplow, uint32 caphigh) +{ + bool ret = False; + uint16 setup; + char param[4]; + char data[12]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + + setup = TRANSACT2_SETFSINFO; + + SSVAL(param,0,0); + SSVAL(param,2,SMB_SET_CIFS_UNIX_INFO); + + SSVAL(data,0,major); + SSVAL(data,2,minor); + SIVAL(data,4,caplow); + SIVAL(data,8,caphigh); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 4, 0, + data, 12, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +bool cli_get_fs_attr_info(struct cli_state *cli, uint32 *fs_attr) +{ + bool ret = False; + uint16 setup; + char param[2]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + + if (!cli||!fs_attr) + smb_panic("cli_get_fs_attr_info() called with NULL Pionter!"); + + setup = TRANSACT2_QFSINFO; + + SSVAL(param,0,SMB_QUERY_FS_ATTRIBUTE_INFO); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 2, 0, + NULL, 0, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count < 12) { + goto cleanup; + } + + *fs_attr = IVAL(rdata,0); + + /* todo: but not yet needed + * return the other stuff + */ + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +bool cli_get_fs_volume_info_old(struct cli_state *cli, fstring volume_name, uint32 *pserial_number) +{ + bool ret = False; + uint16 setup; + char param[2]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + unsigned char nlen; + + setup = TRANSACT2_QFSINFO; + + SSVAL(param,0,SMB_INFO_VOLUME); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 2, 0, + NULL, 0, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count < 5) { + goto cleanup; + } + + if (pserial_number) { + *pserial_number = IVAL(rdata,0); + } + nlen = CVAL(rdata,l2_vol_cch); + clistr_pull(cli, volume_name, rdata + l2_vol_szVolLabel, sizeof(fstring), nlen, STR_NOALIGN); + + /* todo: but not yet needed + * return the other stuff + */ + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +bool cli_get_fs_volume_info(struct cli_state *cli, fstring volume_name, uint32 *pserial_number, time_t *pdate) +{ + bool ret = False; + uint16 setup; + char param[2]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + unsigned int nlen; + + setup = TRANSACT2_QFSINFO; + + SSVAL(param,0,SMB_QUERY_FS_VOLUME_INFO); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 2, 0, + NULL, 0, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count < 19) { + goto cleanup; + } + + if (pdate) { + struct timespec ts; + ts = interpret_long_date(rdata); + *pdate = ts.tv_sec; + } + if (pserial_number) { + *pserial_number = IVAL(rdata,8); + } + nlen = IVAL(rdata,12); + clistr_pull(cli, volume_name, rdata + 18, sizeof(fstring), nlen, STR_UNICODE); + + /* todo: but not yet needed + * return the other stuff + */ + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +/****************************************************************************** + Send/receive the request encryption blob. +******************************************************************************/ + +static NTSTATUS enc_blob_send_receive(struct cli_state *cli, DATA_BLOB *in, DATA_BLOB *out, DATA_BLOB *param_out) +{ + uint16 setup; + char param[4]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + NTSTATUS status = NT_STATUS_OK; + + setup = TRANSACT2_SETFSINFO; + + SSVAL(param,0,0); + SSVAL(param,2,SMB_REQUEST_TRANSPORT_ENCRYPTION); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 4, 0, + (char *)in->data, in->length, CLI_BUFFER_SIZE)) { + status = cli_nt_error(cli); + goto out; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + status = cli_nt_error(cli); + goto out; + } + + if (cli_is_error(cli)) { + status = cli_nt_error(cli); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + goto out; + } + } + + *out = data_blob(rdata, rdata_count); + *param_out = data_blob(rparam, rparam_count); + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return status; +} + +/****************************************************************************** + Make a client state struct. +******************************************************************************/ + +static struct smb_trans_enc_state *make_cli_enc_state(enum smb_trans_enc_type smb_enc_type) +{ + struct smb_trans_enc_state *es = NULL; + es = SMB_MALLOC_P(struct smb_trans_enc_state); + if (!es) { + return NULL; + } + ZERO_STRUCTP(es); + es->smb_enc_type = smb_enc_type; + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + if (smb_enc_type == SMB_TRANS_ENC_GSS) { + es->s.gss_state = SMB_MALLOC_P(struct smb_tran_enc_state_gss); + if (!es->s.gss_state) { + SAFE_FREE(es); + return NULL; + } + ZERO_STRUCTP(es->s.gss_state); + } +#endif + return es; +} + +/****************************************************************************** + Start a raw ntlmssp encryption. +******************************************************************************/ + +NTSTATUS cli_raw_ntlm_smb_encryption_start(struct cli_state *cli, + const char *user, + const char *pass, + const char *domain) +{ + DATA_BLOB blob_in = data_blob_null; + DATA_BLOB blob_out = data_blob_null; + DATA_BLOB param_out = data_blob_null; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct smb_trans_enc_state *es = make_cli_enc_state(SMB_TRANS_ENC_NTLM); + + if (!es) { + return NT_STATUS_NO_MEMORY; + } + status = ntlmssp_client_start(&es->s.ntlmssp_state); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + ntlmssp_want_feature(es->s.ntlmssp_state, NTLMSSP_FEATURE_SESSION_KEY); + es->s.ntlmssp_state->neg_flags |= (NTLMSSP_NEGOTIATE_SIGN|NTLMSSP_NEGOTIATE_SEAL); + + if (!NT_STATUS_IS_OK(status = ntlmssp_set_username(es->s.ntlmssp_state, user))) { + goto fail; + } + if (!NT_STATUS_IS_OK(status = ntlmssp_set_domain(es->s.ntlmssp_state, domain))) { + goto fail; + } + if (!NT_STATUS_IS_OK(status = ntlmssp_set_password(es->s.ntlmssp_state, pass))) { + goto fail; + } + + do { + status = ntlmssp_update(es->s.ntlmssp_state, blob_in, &blob_out); + data_blob_free(&blob_in); + data_blob_free(¶m_out); + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) || NT_STATUS_IS_OK(status)) { + NTSTATUS trans_status = enc_blob_send_receive(cli, + &blob_out, + &blob_in, + ¶m_out); + if (!NT_STATUS_EQUAL(trans_status, + NT_STATUS_MORE_PROCESSING_REQUIRED) && + !NT_STATUS_IS_OK(trans_status)) { + status = trans_status; + } else { + if (param_out.length == 2) { + es->enc_ctx_num = SVAL(param_out.data, 0); + } + } + } + data_blob_free(&blob_out); + } while (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)); + + data_blob_free(&blob_in); + + if (NT_STATUS_IS_OK(status)) { + /* Replace the old state, if any. */ + if (cli->trans_enc_state) { + common_free_encryption_state(&cli->trans_enc_state); + } + cli->trans_enc_state = es; + cli->trans_enc_state->enc_on = True; + es = NULL; + } + + fail: + + common_free_encryption_state(&es); + return status; +} + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + +#ifndef SMB_GSS_REQUIRED_FLAGS +#define SMB_GSS_REQUIRED_FLAGS (GSS_C_CONF_FLAG|GSS_C_INTEG_FLAG|GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG) +#endif + +/****************************************************************************** + Get client gss blob to send to a server. +******************************************************************************/ + +static NTSTATUS make_cli_gss_blob(struct smb_trans_enc_state *es, + const char *service, + const char *host, + NTSTATUS status_in, + DATA_BLOB spnego_blob_in, + DATA_BLOB *p_blob_out) +{ + const char *krb_mechs[] = {OID_KERBEROS5, NULL}; + OM_uint32 ret; + OM_uint32 min; + gss_name_t srv_name; + gss_buffer_desc input_name; + gss_buffer_desc *p_tok_in; + gss_buffer_desc tok_out, tok_in; + DATA_BLOB blob_out = data_blob_null; + DATA_BLOB blob_in = data_blob_null; + char *host_princ_s = NULL; + OM_uint32 ret_flags = 0; + NTSTATUS status = NT_STATUS_OK; + + gss_OID_desc nt_hostbased_service = + {10, CONST_DISCARD(char *,"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")}; + + memset(&tok_out, '\0', sizeof(tok_out)); + + /* Get a ticket for the service@host */ + if (asprintf(&host_princ_s, "%s@%s", service, host) == -1) { + return NT_STATUS_NO_MEMORY; + } + + input_name.value = host_princ_s; + input_name.length = strlen(host_princ_s) + 1; + + ret = gss_import_name(&min, + &input_name, + &nt_hostbased_service, + &srv_name); + + if (ret != GSS_S_COMPLETE) { + SAFE_FREE(host_princ_s); + return map_nt_error_from_gss(ret, min); + } + + if (spnego_blob_in.length == 0) { + p_tok_in = GSS_C_NO_BUFFER; + } else { + /* Remove the SPNEGO wrapper */ + if (!spnego_parse_auth_response(spnego_blob_in, status_in, OID_KERBEROS5, &blob_in)) { + status = NT_STATUS_UNSUCCESSFUL; + goto fail; + } + tok_in.value = blob_in.data; + tok_in.length = blob_in.length; + p_tok_in = &tok_in; + } + + ret = gss_init_sec_context(&min, + GSS_C_NO_CREDENTIAL, /* Use our default cred. */ + &es->s.gss_state->gss_ctx, + srv_name, + GSS_C_NO_OID, /* default OID. */ + GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_DELEG_FLAG, + GSS_C_INDEFINITE, /* requested ticket lifetime. */ + NULL, /* no channel bindings */ + p_tok_in, + NULL, /* ignore mech type */ + &tok_out, + &ret_flags, + NULL); /* ignore time_rec */ + + status = map_nt_error_from_gss(ret, min); + if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status,NT_STATUS_MORE_PROCESSING_REQUIRED)) { + ADS_STATUS adss = ADS_ERROR_GSS(ret, min); + DEBUG(10,("make_cli_gss_blob: gss_init_sec_context failed with %s\n", + ads_errstr(adss))); + goto fail; + } + + if ((ret_flags & SMB_GSS_REQUIRED_FLAGS) != SMB_GSS_REQUIRED_FLAGS) { + status = NT_STATUS_ACCESS_DENIED; + } + + blob_out = data_blob(tok_out.value, tok_out.length); + + /* Wrap in an SPNEGO wrapper */ + *p_blob_out = gen_negTokenTarg(krb_mechs, blob_out); + + fail: + + data_blob_free(&blob_out); + data_blob_free(&blob_in); + SAFE_FREE(host_princ_s); + gss_release_name(&min, &srv_name); + if (tok_out.value) { + gss_release_buffer(&min, &tok_out); + } + return status; +} + +/****************************************************************************** + Start a SPNEGO gssapi encryption context. +******************************************************************************/ + +NTSTATUS cli_gss_smb_encryption_start(struct cli_state *cli) +{ + DATA_BLOB blob_recv = data_blob_null; + DATA_BLOB blob_send = data_blob_null; + DATA_BLOB param_out = data_blob_null; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + fstring fqdn; + const char *servicename; + struct smb_trans_enc_state *es = make_cli_enc_state(SMB_TRANS_ENC_GSS); + + if (!es) { + return NT_STATUS_NO_MEMORY; + } + + name_to_fqdn(fqdn, cli->desthost); + strlower_m(fqdn); + + servicename = "cifs"; + status = make_cli_gss_blob(es, servicename, fqdn, NT_STATUS_OK, blob_recv, &blob_send); + if (!NT_STATUS_EQUAL(status,NT_STATUS_MORE_PROCESSING_REQUIRED)) { + servicename = "host"; + status = make_cli_gss_blob(es, servicename, fqdn, NT_STATUS_OK, blob_recv, &blob_send); + if (!NT_STATUS_EQUAL(status,NT_STATUS_MORE_PROCESSING_REQUIRED)) { + goto fail; + } + } + + do { + data_blob_free(&blob_recv); + status = enc_blob_send_receive(cli, &blob_send, &blob_recv, ¶m_out); + if (param_out.length == 2) { + es->enc_ctx_num = SVAL(param_out.data, 0); + } + data_blob_free(&blob_send); + status = make_cli_gss_blob(es, servicename, fqdn, status, blob_recv, &blob_send); + } while (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)); + data_blob_free(&blob_recv); + + if (NT_STATUS_IS_OK(status)) { + /* Replace the old state, if any. */ + if (cli->trans_enc_state) { + common_free_encryption_state(&cli->trans_enc_state); + } + cli->trans_enc_state = es; + cli->trans_enc_state->enc_on = True; + es = NULL; + } + + fail: + + common_free_encryption_state(&es); + return status; +} +#else +NTSTATUS cli_gss_smb_encryption_start(struct cli_state *cli) +{ + return NT_STATUS_NOT_SUPPORTED; +} +#endif + +/******************************************************************** + Ensure a connection is encrypted. +********************************************************************/ + +NTSTATUS cli_force_encryption(struct cli_state *c, + const char *username, + const char *password, + const char *domain) +{ + uint16 major, minor; + uint32 caplow, caphigh; + + if (!SERVER_HAS_UNIX_CIFS(c)) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (!cli_unix_extensions_version(c, &major, &minor, &caplow, &caphigh)) { + return NT_STATUS_UNKNOWN_REVISION; + } + + if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) { + return NT_STATUS_UNSUPPORTED_COMPRESSION; + } + + if (c->use_kerberos) { + return cli_gss_smb_encryption_start(c); + } + return cli_raw_ntlm_smb_encryption_start(c, + username, + password, + domain); +} diff --git a/source3/libsmb/clikrb5.c b/source3/libsmb/clikrb5.c new file mode 100644 index 0000000000..b8afb57977 --- /dev/null +++ b/source3/libsmb/clikrb5.c @@ -0,0 +1,1923 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Guenther Deschner 2005-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 <http://www.gnu.org/licenses/>. +*/ + +#define KRB5_PRIVATE 1 /* this file uses PRIVATE interfaces! */ +#define KRB5_DEPRECATED 1 /* this file uses DEPRECATED interfaces! */ + +#include "includes.h" + +#ifdef HAVE_KRB5 + +#define GSSAPI_CHECKSUM 0x8003 /* Checksum type value for Kerberos */ +#define GSSAPI_BNDLENGTH 16 /* Bind Length (rfc-1964 pg.3) */ +#define GSSAPI_CHECKSUM_SIZE (12+GSSAPI_BNDLENGTH) + +#if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_FWD_TGT_CREDS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) +static krb5_error_code ads_krb5_get_fwd_ticket( krb5_context context, + krb5_auth_context *auth_context, + krb5_creds *credsp, + krb5_ccache ccache, + krb5_data *authenticator); +#endif + +/************************************************************** + Wrappers around kerberos string functions that convert from + utf8 -> unix charset and vica versa. +**************************************************************/ + +/************************************************************** + krb5_parse_name that takes a UNIX charset. +**************************************************************/ + + krb5_error_code smb_krb5_parse_name(krb5_context context, + const char *name, /* in unix charset */ + krb5_principal *principal) +{ + krb5_error_code ret; + char *utf8_name; + size_t converted_size; + + if (!push_utf8_allocate(&utf8_name, name, &converted_size)) { + return ENOMEM; + } + + ret = krb5_parse_name(context, utf8_name, principal); + SAFE_FREE(utf8_name); + return ret; +} + +#ifdef HAVE_KRB5_PARSE_NAME_NOREALM +/************************************************************** + krb5_parse_name_norealm that takes a UNIX charset. +**************************************************************/ + +static krb5_error_code smb_krb5_parse_name_norealm_conv(krb5_context context, + const char *name, /* in unix charset */ + krb5_principal *principal) +{ + krb5_error_code ret; + char *utf8_name; + size_t converted_size; + + *principal = NULL; + if (!push_utf8_allocate(&utf8_name, name, &converted_size)) { + return ENOMEM; + } + + ret = krb5_parse_name_norealm(context, utf8_name, principal); + SAFE_FREE(utf8_name); + return ret; +} +#endif + +/************************************************************** + krb5_parse_name that returns a UNIX charset name. Must + be freed with normal free() call. +**************************************************************/ + + krb5_error_code smb_krb5_unparse_name(krb5_context context, + krb5_const_principal principal, + char **unix_name) +{ + krb5_error_code ret; + char *utf8_name; + size_t converted_size; + + *unix_name = NULL; + ret = krb5_unparse_name(context, principal, &utf8_name); + if (ret) { + return ret; + } + + if (!pull_utf8_allocate(unix_name, utf8_name, &converted_size)) { + krb5_free_unparsed_name(context, utf8_name); + return ENOMEM; + } + krb5_free_unparsed_name(context, utf8_name); + return 0; +} + +#ifndef HAVE_KRB5_SET_REAL_TIME +/* + * This function is not in the Heimdal mainline. + */ + krb5_error_code krb5_set_real_time(krb5_context context, int32_t seconds, int32_t microseconds) +{ + krb5_error_code ret; + int32_t sec, usec; + + ret = krb5_us_timeofday(context, &sec, &usec); + if (ret) + return ret; + + context->kdc_sec_offset = seconds - sec; + context->kdc_usec_offset = microseconds - usec; + + return 0; +} +#endif + +#if !defined(HAVE_KRB5_SET_DEFAULT_TGS_KTYPES) + +#if defined(HAVE_KRB5_SET_DEFAULT_TGS_ENCTYPES) + +/* With MIT kerberos, we should use krb5_set_default_tgs_enctypes in preference + * to krb5_set_default_tgs_ktypes. See + * http://lists.samba.org/archive/samba-technical/2006-July/048271.html + * + * If the MIT libraries are not exporting internal symbols, we will end up in + * this branch, which is correct. Otherwise we will continue to use the + * internal symbol + */ + krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc) +{ + return krb5_set_default_tgs_enctypes(ctx, enc); +} + +#elif defined(HAVE_KRB5_SET_DEFAULT_IN_TKT_ETYPES) + +/* Heimdal */ + krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc) +{ + return krb5_set_default_in_tkt_etypes(ctx, enc); +} + +#endif /* HAVE_KRB5_SET_DEFAULT_TGS_ENCTYPES */ + +#endif /* HAVE_KRB5_SET_DEFAULT_TGS_KTYPES */ + +#if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) +/* HEIMDAL */ + bool setup_kaddr( krb5_address *pkaddr, struct sockaddr_storage *paddr) +{ + memset(pkaddr, '\0', sizeof(krb5_address)); +#if defined(HAVE_IPV6) && defined(KRB5_ADDRESS_INET6) + if (paddr->ss_family == AF_INET6) { + pkaddr->addr_type = KRB5_ADDRESS_INET6; + pkaddr->address.length = sizeof(((struct sockaddr_in6 *)paddr)->sin6_addr); + pkaddr->address.data = (char *)&(((struct sockaddr_in6 *)paddr)->sin6_addr); + return true; + } +#endif + if (paddr->ss_family == AF_INET) { + pkaddr->addr_type = KRB5_ADDRESS_INET; + pkaddr->address.length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); + pkaddr->address.data = (char *)&(((struct sockaddr_in *)paddr)->sin_addr); + return true; + } + return false; +} +#elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) +/* MIT */ + bool setup_kaddr( krb5_address *pkaddr, struct sockaddr_storage *paddr) +{ + memset(pkaddr, '\0', sizeof(krb5_address)); +#if defined(HAVE_IPV6) && defined(ADDRTYPE_INET6) + if (paddr->ss_family == AF_INET6) { + pkaddr->addrtype = ADDRTYPE_INET6; + pkaddr->length = sizeof(((struct sockaddr_in6 *)paddr)->sin6_addr); + pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in6 *)paddr)->sin6_addr); + return true; + } +#endif + if (paddr->ss_family == AF_INET) { + pkaddr->addrtype = ADDRTYPE_INET; + pkaddr->length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); + pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in *)paddr)->sin_addr); + return true; + } + return false; +} +#else +#error UNKNOWN_ADDRTYPE +#endif + +#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_USE_ENCTYPE) && defined(HAVE_KRB5_STRING_TO_KEY) && defined(HAVE_KRB5_ENCRYPT_BLOCK) +static int create_kerberos_key_from_string_direct(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype) +{ + int ret = 0; + krb5_data salt; + krb5_encrypt_block eblock; + + ret = krb5_principal2salt(context, host_princ, &salt); + if (ret) { + DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret))); + return ret; + } + krb5_use_enctype(context, &eblock, enctype); + ret = krb5_string_to_key(context, &eblock, key, password, &salt); + SAFE_FREE(salt.data); + + return ret; +} +#elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT) +static int create_kerberos_key_from_string_direct(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype) +{ + int ret; + krb5_salt salt; + + ret = krb5_get_pw_salt(context, host_princ, &salt); + if (ret) { + DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret))); + return ret; + } + + ret = krb5_string_to_key_salt(context, enctype, (const char *)password->data, salt, key); + krb5_free_salt(context, salt); + + return ret; +} +#else +#error UNKNOWN_CREATE_KEY_FUNCTIONS +#endif + + int create_kerberos_key_from_string(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype, + bool no_salt) +{ + krb5_principal salt_princ = NULL; + int ret; + /* + * Check if we've determined that the KDC is salting keys for this + * principal/enctype in a non-obvious way. If it is, try to match + * its behavior. + */ + if (no_salt) { + KRB5_KEY_DATA(key) = (KRB5_KEY_DATA_CAST *)SMB_MALLOC(password->length); + if (!KRB5_KEY_DATA(key)) { + return ENOMEM; + } + memcpy(KRB5_KEY_DATA(key), password->data, password->length); + KRB5_KEY_LENGTH(key) = password->length; + KRB5_KEY_TYPE(key) = enctype; + return 0; + } + salt_princ = kerberos_fetch_salt_princ_for_host_princ(context, host_princ, enctype); + ret = create_kerberos_key_from_string_direct(context, salt_princ ? salt_princ : host_princ, password, key, enctype); + if (salt_princ) { + krb5_free_principal(context, salt_princ); + } + return ret; +} + +#if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES) + krb5_error_code get_kerberos_allowed_etypes(krb5_context context, + krb5_enctype **enctypes) +{ + return krb5_get_permitted_enctypes(context, enctypes); +} +#elif defined(HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES) + krb5_error_code get_kerberos_allowed_etypes(krb5_context context, + krb5_enctype **enctypes) +{ + return krb5_get_default_in_tkt_etypes(context, enctypes); +} +#else +#error UNKNOWN_GET_ENCTYPES_FUNCTIONS +#endif + +#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) + krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, + krb5_auth_context auth_context, + krb5_keyblock *keyblock) +{ + return krb5_auth_con_setkey(context, auth_context, keyblock); +} +#endif + +bool unwrap_edata_ntstatus(TALLOC_CTX *mem_ctx, + DATA_BLOB *edata, + DATA_BLOB *edata_out) +{ + DATA_BLOB edata_contents; + ASN1_DATA data; + int edata_type; + + if (!edata->length) { + return False; + } + + asn1_load(&data, *edata); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_read_Integer(&data, &edata_type); + + if (edata_type != KRB5_PADATA_PW_SALT) { + DEBUG(0,("edata is not of required type %d but of type %d\n", + KRB5_PADATA_PW_SALT, edata_type)); + asn1_free(&data); + return False; + } + + asn1_start_tag(&data, ASN1_CONTEXT(2)); + asn1_read_OctetString(&data, &edata_contents); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_free(&data); + + *edata_out = data_blob_talloc(mem_ctx, edata_contents.data, edata_contents.length); + + data_blob_free(&edata_contents); + + return True; +} + + +bool unwrap_pac(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data, DATA_BLOB *unwrapped_pac_data) +{ + DATA_BLOB pac_contents; + ASN1_DATA data; + int data_type; + + if (!auth_data->length) { + return False; + } + + asn1_load(&data, *auth_data); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_read_Integer(&data, &data_type); + + if (data_type != KRB5_AUTHDATA_WIN2K_PAC ) { + DEBUG(10,("authorization data is not a Windows PAC (type: %d)\n", data_type)); + asn1_free(&data); + return False; + } + + asn1_end_tag(&data); + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_read_OctetString(&data, &pac_contents); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_free(&data); + + *unwrapped_pac_data = data_blob_talloc(mem_ctx, pac_contents.data, pac_contents.length); + + data_blob_free(&pac_contents); + + return True; +} + + bool get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data, krb5_ticket *tkt) +{ + DATA_BLOB auth_data_wrapped; + bool got_auth_data_pac = False; + int i; + +#if defined(HAVE_KRB5_TKT_ENC_PART2) + if (tkt->enc_part2 && tkt->enc_part2->authorization_data && + tkt->enc_part2->authorization_data[0] && + tkt->enc_part2->authorization_data[0]->length) + { + for (i = 0; tkt->enc_part2->authorization_data[i] != NULL; i++) { + + if (tkt->enc_part2->authorization_data[i]->ad_type != + KRB5_AUTHDATA_IF_RELEVANT) { + DEBUG(10,("get_auth_data_from_tkt: ad_type is %d\n", + tkt->enc_part2->authorization_data[i]->ad_type)); + continue; + } + + auth_data_wrapped = data_blob(tkt->enc_part2->authorization_data[i]->contents, + tkt->enc_part2->authorization_data[i]->length); + + /* check if it is a PAC */ + got_auth_data_pac = unwrap_pac(mem_ctx, &auth_data_wrapped, auth_data); + data_blob_free(&auth_data_wrapped); + + if (got_auth_data_pac) { + return true; + } + } + + return got_auth_data_pac; + } + +#else + if (tkt->ticket.authorization_data && + tkt->ticket.authorization_data->len) + { + for (i = 0; i < tkt->ticket.authorization_data->len; i++) { + + if (tkt->ticket.authorization_data->val[i].ad_type != + KRB5_AUTHDATA_IF_RELEVANT) { + DEBUG(10,("get_auth_data_from_tkt: ad_type is %d\n", + tkt->ticket.authorization_data->val[i].ad_type)); + continue; + } + + auth_data_wrapped = data_blob(tkt->ticket.authorization_data->val[i].ad_data.data, + tkt->ticket.authorization_data->val[i].ad_data.length); + + /* check if it is a PAC */ + got_auth_data_pac = unwrap_pac(mem_ctx, &auth_data_wrapped, auth_data); + data_blob_free(&auth_data_wrapped); + + if (got_auth_data_pac) { + return true; + } + } + + return got_auth_data_pac; + } +#endif + return False; +} + + krb5_const_principal get_principal_from_tkt(krb5_ticket *tkt) +{ +#if defined(HAVE_KRB5_TKT_ENC_PART2) + return tkt->enc_part2->client; +#else + return tkt->client; +#endif +} + +#if !defined(HAVE_KRB5_LOCATE_KDC) + +/* krb5_locate_kdc is an internal MIT symbol. MIT are not yet willing to commit + * to a public interface for this functionality, so we have to be able to live + * without it if the MIT libraries are hiding their internal symbols. + */ + +#if defined(KRB5_KRBHST_INIT) +/* Heimdal */ + krb5_error_code smb_krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, struct sockaddr **addr_pp, int *naddrs, int get_masters) +{ + krb5_krbhst_handle hnd; + krb5_krbhst_info *hinfo; + krb5_error_code rc; + int num_kdcs, i; + struct sockaddr *sa; + struct addrinfo *ai; + + *addr_pp = NULL; + *naddrs = 0; + + rc = krb5_krbhst_init(ctx, realm->data, KRB5_KRBHST_KDC, &hnd); + if (rc) { + DEBUG(0, ("smb_krb5_locate_kdc: krb5_krbhst_init failed (%s)\n", error_message(rc))); + return rc; + } + + for ( num_kdcs = 0; (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); num_kdcs++) + ; + + krb5_krbhst_reset(ctx, hnd); + + if (!num_kdcs) { + DEBUG(0, ("smb_krb5_locate_kdc: zero kdcs found !\n")); + krb5_krbhst_free(ctx, hnd); + return -1; + } + + sa = SMB_MALLOC_ARRAY( struct sockaddr, num_kdcs ); + if (!sa) { + DEBUG(0, ("smb_krb5_locate_kdc: malloc failed\n")); + krb5_krbhst_free(ctx, hnd); + naddrs = 0; + return -1; + } + + memset(sa, '\0', sizeof(struct sockaddr) * num_kdcs ); + + for (i = 0; i < num_kdcs && (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); i++) { + +#if defined(HAVE_KRB5_KRBHST_GET_ADDRINFO) + rc = krb5_krbhst_get_addrinfo(ctx, hinfo, &ai); + if (rc) { + DEBUG(0,("krb5_krbhst_get_addrinfo failed: %s\n", error_message(rc))); + continue; + } +#endif + if (hinfo->ai && hinfo->ai->ai_family == AF_INET) + memcpy(&sa[i], hinfo->ai->ai_addr, sizeof(struct sockaddr)); + } + + krb5_krbhst_free(ctx, hnd); + + *naddrs = num_kdcs; + *addr_pp = sa; + return 0; +} + +#else /* ! defined(KRB5_KRBHST_INIT) */ + + krb5_error_code smb_krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, + struct sockaddr **addr_pp, int *naddrs, int get_masters) +{ + DEBUG(0, ("unable to explicitly locate the KDC on this platform\n")); + return KRB5_KDC_UNREACH; +} + +#endif /* KRB5_KRBHST_INIT */ + +#else /* ! HAVE_KRB5_LOCATE_KDC */ + + krb5_error_code smb_krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, + struct sockaddr **addr_pp, int *naddrs, int get_masters) +{ + return krb5_locate_kdc(ctx, realm, addr_pp, naddrs, get_masters); +} + +#endif /* HAVE_KRB5_LOCATE_KDC */ + +#if !defined(HAVE_KRB5_FREE_UNPARSED_NAME) + void krb5_free_unparsed_name(krb5_context context, char *val) +{ + SAFE_FREE(val); +} +#endif + + void kerberos_free_data_contents(krb5_context context, krb5_data *pdata) +{ +#if defined(HAVE_KRB5_FREE_DATA_CONTENTS) + if (pdata->data) { + krb5_free_data_contents(context, pdata); + } +#else + SAFE_FREE(pdata->data); +#endif +} + + void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype) +{ +#if defined(HAVE_KRB5_KEYBLOCK_IN_CREDS) + KRB5_KEY_TYPE((&pcreds->keyblock)) = enctype; +#elif defined(HAVE_KRB5_SESSION_IN_CREDS) + KRB5_KEY_TYPE((&pcreds->session)) = enctype; +#else +#error UNKNOWN_KEYBLOCK_MEMBER_IN_KRB5_CREDS_STRUCT +#endif +} + + bool kerberos_compatible_enctypes(krb5_context context, + krb5_enctype enctype1, + krb5_enctype enctype2) +{ +#if defined(HAVE_KRB5_C_ENCTYPE_COMPARE) + krb5_boolean similar = 0; + + krb5_c_enctype_compare(context, enctype1, enctype2, &similar); + return similar ? True : False; +#elif defined(HAVE_KRB5_ENCTYPES_COMPATIBLE_KEYS) + return krb5_enctypes_compatible_keys(context, enctype1, enctype2) ? True : False; +#endif +} + +static bool ads_cleanup_expired_creds(krb5_context context, + krb5_ccache ccache, + krb5_creds *credsp) +{ + krb5_error_code retval; + const char *cc_type = krb5_cc_get_type(context, ccache); + + DEBUG(3, ("ads_cleanup_expired_creds: Ticket in ccache[%s:%s] expiration %s\n", + cc_type, krb5_cc_get_name(context, ccache), + http_timestring(credsp->times.endtime))); + + /* we will probably need new tickets if the current ones + will expire within 10 seconds. + */ + if (credsp->times.endtime >= (time(NULL) + 10)) + return False; + + /* heimdal won't remove creds from a file ccache, and + perhaps we shouldn't anyway, since internally we + use memory ccaches, and a FILE one probably means that + we're using creds obtained outside of our exectuable + */ + if (strequal(cc_type, "FILE")) { + DEBUG(5, ("ads_cleanup_expired_creds: We do not remove creds from a %s ccache\n", cc_type)); + return False; + } + + retval = krb5_cc_remove_cred(context, ccache, 0, credsp); + if (retval) { + DEBUG(1, ("ads_cleanup_expired_creds: krb5_cc_remove_cred failed, err %s\n", + error_message(retval))); + /* If we have an error in this, we want to display it, + but continue as though we deleted it */ + } + return True; +} + +/* + we can't use krb5_mk_req because w2k wants the service to be in a particular format +*/ +static krb5_error_code ads_krb5_mk_req(krb5_context context, + krb5_auth_context *auth_context, + const krb5_flags ap_req_options, + const char *principal, + krb5_ccache ccache, + krb5_data *outbuf, + time_t *expire_time) +{ + krb5_error_code retval; + krb5_principal server; + krb5_creds * credsp; + krb5_creds creds; + krb5_data in_data; + bool creds_ready = False; + int i = 0, maxtries = 3; + + ZERO_STRUCT(in_data); + + retval = smb_krb5_parse_name(context, principal, &server); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: Failed to parse principal %s\n", principal)); + return retval; + } + + /* obtain ticket & session key */ + ZERO_STRUCT(creds); + if ((retval = krb5_copy_principal(context, server, &creds.server))) { + DEBUG(1,("ads_krb5_mk_req: krb5_copy_principal failed (%s)\n", + error_message(retval))); + goto cleanup_princ; + } + + if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) { + /* This can commonly fail on smbd startup with no ticket in the cache. + * Report at higher level than 1. */ + DEBUG(3,("ads_krb5_mk_req: krb5_cc_get_principal failed (%s)\n", + error_message(retval))); + goto cleanup_creds; + } + + while (!creds_ready && (i < maxtries)) { + + if ((retval = krb5_get_credentials(context, 0, ccache, + &creds, &credsp))) { + DEBUG(1,("ads_krb5_mk_req: krb5_get_credentials failed for %s (%s)\n", + principal, error_message(retval))); + goto cleanup_creds; + } + + /* cope with ticket being in the future due to clock skew */ + if ((unsigned)credsp->times.starttime > time(NULL)) { + time_t t = time(NULL); + int time_offset =(int)((unsigned)credsp->times.starttime-t); + DEBUG(4,("ads_krb5_mk_req: Advancing clock by %d seconds to cope with clock skew\n", time_offset)); + krb5_set_real_time(context, t + time_offset + 1, 0); + } + + if (!ads_cleanup_expired_creds(context, ccache, credsp)) { + creds_ready = True; + } + + i++; + } + + DEBUG(10,("ads_krb5_mk_req: Ticket (%s) in ccache (%s:%s) is valid until: (%s - %u)\n", + principal, krb5_cc_get_type(context, ccache), krb5_cc_get_name(context, ccache), + http_timestring((unsigned)credsp->times.endtime), + (unsigned)credsp->times.endtime)); + + if (expire_time) { + *expire_time = (time_t)credsp->times.endtime; + } + +#if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_FWD_TGT_CREDS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) + if( credsp->ticket_flags & TKT_FLG_OK_AS_DELEGATE ) { + /* Fetch a forwarded TGT from the KDC so that we can hand off a 2nd ticket + as part of the kerberos exchange. */ + + DEBUG( 3, ("ads_krb5_mk_req: server marked as OK to delegate to, building forwardable TGT\n") ); + + if( *auth_context == NULL ) { + /* Allocate if it has not yet been allocated. */ + retval = krb5_auth_con_init( context, auth_context ); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: krb5_auth_con_init failed (%s)\n", + error_message(retval))); + goto cleanup_creds; + } + } + + retval = krb5_auth_con_setuseruserkey( context, *auth_context, &credsp->keyblock ); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: krb5_auth_con_setuseruserkey failed (%s)\n", + error_message(retval))); + goto cleanup_creds; + } + + /* Must use a subkey for forwarded tickets. */ + retval = krb5_auth_con_setflags( context, *auth_context, KRB5_AUTH_CONTEXT_USE_SUBKEY); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: krb5_auth_con_setflags failed (%s)\n", + error_message(retval))); + goto cleanup_creds; + } + + retval = ads_krb5_get_fwd_ticket( context, + auth_context, + credsp, + ccache, + &in_data ); + if (retval) { + DEBUG( 3, ("ads_krb5_get_fwd_ticket failed (%s)\n", + error_message( retval ) ) ); + + /* + * This is not fatal. Delete the *auth_context and continue + * with krb5_mk_req_extended to get a non-forwardable ticket. + */ + + if (in_data.data) { + free( in_data.data ); + in_data.data = NULL; + in_data.length = 0; + } + krb5_auth_con_free(context, *auth_context); + *auth_context = NULL; + } + } +#endif + + retval = krb5_mk_req_extended(context, auth_context, ap_req_options, + &in_data, credsp, outbuf); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: krb5_mk_req_extended failed (%s)\n", + error_message(retval))); + } + + if (in_data.data) { + free( in_data.data ); + in_data.length = 0; + } + + krb5_free_creds(context, credsp); + +cleanup_creds: + krb5_free_cred_contents(context, &creds); + +cleanup_princ: + krb5_free_principal(context, server); + + return retval; +} + +/* + get a kerberos5 ticket for the given service +*/ +int cli_krb5_get_ticket(const char *principal, time_t time_offset, + DATA_BLOB *ticket, DATA_BLOB *session_key_krb5, + uint32 extra_ap_opts, const char *ccname, + time_t *tgs_expire) + +{ + krb5_error_code retval; + krb5_data packet; + krb5_context context = NULL; + krb5_ccache ccdef = NULL; + krb5_auth_context auth_context = NULL; + krb5_enctype enc_types[] = { +#ifdef ENCTYPE_ARCFOUR_HMAC + ENCTYPE_ARCFOUR_HMAC, +#endif + ENCTYPE_DES_CBC_MD5, + ENCTYPE_DES_CBC_CRC, + ENCTYPE_NULL}; + + initialize_krb5_error_table(); + retval = krb5_init_context(&context); + if (retval) { + DEBUG(1,("cli_krb5_get_ticket: krb5_init_context failed (%s)\n", + error_message(retval))); + goto failed; + } + + if (time_offset != 0) { + krb5_set_real_time(context, time(NULL) + time_offset, 0); + } + + if ((retval = krb5_cc_resolve(context, ccname ? + ccname : krb5_cc_default_name(context), &ccdef))) { + DEBUG(1,("cli_krb5_get_ticket: krb5_cc_default failed (%s)\n", + error_message(retval))); + goto failed; + } + + if ((retval = krb5_set_default_tgs_ktypes(context, enc_types))) { + DEBUG(1,("cli_krb5_get_ticket: krb5_set_default_tgs_ktypes failed (%s)\n", + error_message(retval))); + goto failed; + } + + if ((retval = ads_krb5_mk_req(context, + &auth_context, + AP_OPTS_USE_SUBKEY | (krb5_flags)extra_ap_opts, + principal, + ccdef, &packet, + tgs_expire))) { + goto failed; + } + + get_krb5_smb_session_key(context, auth_context, session_key_krb5, False); + + *ticket = data_blob(packet.data, packet.length); + + kerberos_free_data_contents(context, &packet); + +failed: + + if ( context ) { + if (ccdef) + krb5_cc_close(context, ccdef); + if (auth_context) + krb5_auth_con_free(context, auth_context); + krb5_free_context(context); + } + + return retval; +} + + bool get_krb5_smb_session_key(krb5_context context, krb5_auth_context auth_context, DATA_BLOB *session_key, bool remote) + { + krb5_keyblock *skey; + krb5_error_code err; + bool ret = False; + + if (remote) + err = krb5_auth_con_getremotesubkey(context, auth_context, &skey); + else + err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey); + if (err == 0 && skey != NULL) { + DEBUG(10, ("Got KRB5 session key of length %d\n", (int)KRB5_KEY_LENGTH(skey))); + *session_key = data_blob(KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey)); + dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length); + + ret = True; + + krb5_free_keyblock(context, skey); + } else { + DEBUG(10, ("KRB5 error getting session key %d\n", err)); + } + + return ret; + } + + +#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT) + const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ); + + const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ) +{ + static krb5_data kdata; + + kdata.data = (char *)krb5_principal_get_comp_string(context, principal, i); + kdata.length = strlen((const char *)kdata.data); + return &kdata; +} +#endif + + krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry) +{ +#if defined(HAVE_KRB5_KT_FREE_ENTRY) + return krb5_kt_free_entry(context, kt_entry); +#elif defined(HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS) + return krb5_free_keytab_entry_contents(context, kt_entry); +#else +#error UNKNOWN_KT_FREE_FUNCTION +#endif +} + + void smb_krb5_checksum_from_pac_sig(krb5_checksum *cksum, + struct PAC_SIGNATURE_DATA *sig) +{ +#ifdef HAVE_CHECKSUM_IN_KRB5_CHECKSUM + cksum->cksumtype = (krb5_cksumtype)sig->type; + cksum->checksum.length = sig->signature.length; + cksum->checksum.data = sig->signature.data; +#else + cksum->checksum_type = (krb5_cksumtype)sig->type; + cksum->length = sig->signature.length; + cksum->contents = sig->signature.data; +#endif +} + + krb5_error_code smb_krb5_verify_checksum(krb5_context context, + const krb5_keyblock *keyblock, + krb5_keyusage usage, + krb5_checksum *cksum, + uint8 *data, + size_t length) +{ + krb5_error_code ret; + + /* verify the checksum */ + + /* welcome to the wonderful world of samba's kerberos abstraction layer: + * + * function heimdal 0.6.1rc3 heimdal 0.7 MIT krb 1.4.2 + * ----------------------------------------------------------------------------- + * krb5_c_verify_checksum - works works + * krb5_verify_checksum works (6 args) works (6 args) broken (7 args) + */ + +#if defined(HAVE_KRB5_C_VERIFY_CHECKSUM) + { + krb5_boolean checksum_valid = False; + krb5_data input; + + input.data = (char *)data; + input.length = length; + + ret = krb5_c_verify_checksum(context, + keyblock, + usage, + &input, + cksum, + &checksum_valid); + if (ret) { + DEBUG(3,("smb_krb5_verify_checksum: krb5_c_verify_checksum() failed: %s\n", + error_message(ret))); + return ret; + } + + if (!checksum_valid) + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } + +#elif KRB5_VERIFY_CHECKSUM_ARGS == 6 && defined(HAVE_KRB5_CRYPTO_INIT) && defined(HAVE_KRB5_CRYPTO) && defined(HAVE_KRB5_CRYPTO_DESTROY) + + /* Warning: MIT's krb5_verify_checksum cannot be used as it will use a key + * without enctype and it ignores any key_usage types - Guenther */ + + { + + krb5_crypto crypto; + ret = krb5_crypto_init(context, + keyblock, + 0, + &crypto); + if (ret) { + DEBUG(0,("smb_krb5_verify_checksum: krb5_crypto_init() failed: %s\n", + error_message(ret))); + return ret; + } + + ret = krb5_verify_checksum(context, + crypto, + usage, + data, + length, + cksum); + + krb5_crypto_destroy(context, crypto); + } + +#else +#error UNKNOWN_KRB5_VERIFY_CHECKSUM_FUNCTION +#endif + + return ret; +} + + time_t get_authtime_from_tkt(krb5_ticket *tkt) +{ +#if defined(HAVE_KRB5_TKT_ENC_PART2) + return tkt->enc_part2->times.authtime; +#else + return tkt->ticket.authtime; +#endif +} + +#ifdef HAVE_KRB5_DECODE_AP_REQ /* Heimdal */ +static int get_kvno_from_ap_req(krb5_ap_req *ap_req) +{ +#ifdef HAVE_TICKET_POINTER_IN_KRB5_AP_REQ /* MIT */ + if (ap_req->ticket->enc_part.kvno) + return ap_req->ticket->enc_part.kvno; +#else /* Heimdal */ + if (ap_req->ticket.enc_part.kvno) + return *ap_req->ticket.enc_part.kvno; +#endif + return 0; +} + +static krb5_enctype get_enctype_from_ap_req(krb5_ap_req *ap_req) +{ +#ifdef HAVE_ETYPE_IN_ENCRYPTEDDATA /* Heimdal */ + return ap_req->ticket.enc_part.etype; +#else /* MIT */ + return ap_req->ticket->enc_part.enctype; +#endif +} +#endif /* HAVE_KRB5_DECODE_AP_REQ */ + +static krb5_error_code +get_key_from_keytab(krb5_context context, + krb5_const_principal server, + krb5_enctype enctype, + krb5_kvno kvno, + krb5_keyblock **out_key) +{ + krb5_keytab_entry entry; + krb5_error_code ret; + krb5_keytab keytab; + char *name = NULL; + krb5_keyblock *keyp; + + /* We have to open a new keytab handle here, as MIT does + an implicit open/getnext/close on krb5_kt_get_entry. We + may be in the middle of a keytab enumeration when this is + called. JRA. */ + + ret = smb_krb5_open_keytab(context, NULL, False, &keytab); + if (ret) { + DEBUG(1,("get_key_from_keytab: smb_krb5_open_keytab failed (%s)\n", error_message(ret))); + return ret; + } + + if ( DEBUGLEVEL >= 10 ) { + if (smb_krb5_unparse_name(context, server, &name) == 0) { + DEBUG(10,("get_key_from_keytab: will look for kvno %d, enctype %d and name: %s\n", + kvno, enctype, name)); + SAFE_FREE(name); + } + } + + ret = krb5_kt_get_entry(context, + keytab, + server, + kvno, + enctype, + &entry); + + if (ret) { + DEBUG(0,("get_key_from_keytab: failed to retrieve key: %s\n", error_message(ret))); + goto out; + } + + keyp = KRB5_KT_KEY(&entry); + + ret = krb5_copy_keyblock(context, keyp, out_key); + if (ret) { + DEBUG(0,("get_key_from_keytab: failed to copy key: %s\n", error_message(ret))); + goto out; + } + + smb_krb5_kt_free_entry(context, &entry); + +out: + krb5_kt_close(context, keytab); + return ret; +} + +/* Prototypes */ + + krb5_error_code smb_krb5_get_keyinfo_from_ap_req(krb5_context context, + const krb5_data *inbuf, + krb5_kvno *kvno, + krb5_enctype *enctype) +{ +#ifdef HAVE_KRB5_DECODE_AP_REQ /* Heimdal */ + { + krb5_error_code ret; + krb5_ap_req ap_req; + + ret = krb5_decode_ap_req(context, inbuf, &ap_req); + if (ret) + return ret; + + *kvno = get_kvno_from_ap_req(&ap_req); + *enctype = get_enctype_from_ap_req(&ap_req); + + free_AP_REQ(&ap_req); + return 0; + } +#endif + + /* Possibly not an appropriate error code. */ + return KRB5KDC_ERR_BADOPTION; +} + + krb5_error_code krb5_rd_req_return_keyblock_from_keytab(krb5_context context, + krb5_auth_context *auth_context, + const krb5_data *inbuf, + krb5_const_principal server, + krb5_keytab keytab, + krb5_flags *ap_req_options, + krb5_ticket **ticket, + krb5_keyblock **keyblock) +{ + krb5_error_code ret; + krb5_kvno kvno; + krb5_enctype enctype; + krb5_keyblock *local_keyblock; + + ret = krb5_rd_req(context, + auth_context, + inbuf, + server, + keytab, + ap_req_options, + ticket); + if (ret) { + return ret; + } + +#ifdef KRB5_TICKET_HAS_KEYINFO + enctype = (*ticket)->enc_part.enctype; + kvno = (*ticket)->enc_part.kvno; +#else + ret = smb_krb5_get_keyinfo_from_ap_req(context, inbuf, &kvno, &enctype); + if (ret) { + return ret; + } +#endif + + ret = get_key_from_keytab(context, + server, + enctype, + kvno, + &local_keyblock); + if (ret) { + DEBUG(0,("krb5_rd_req_return_keyblock_from_keytab: failed to call get_key_from_keytab\n")); + goto out; + } + +out: + if (ret && local_keyblock != NULL) { + krb5_free_keyblock(context, local_keyblock); + } else { + *keyblock = local_keyblock; + } + + return ret; +} + + krb5_error_code smb_krb5_parse_name_norealm(krb5_context context, + const char *name, + krb5_principal *principal) +{ +#ifdef HAVE_KRB5_PARSE_NAME_NOREALM + return smb_krb5_parse_name_norealm_conv(context, name, principal); +#endif + + /* we are cheating here because parse_name will in fact set the realm. + * We don't care as the only caller of smb_krb5_parse_name_norealm + * ignores the realm anyway when calling + * smb_krb5_principal_compare_any_realm later - Guenther */ + + return smb_krb5_parse_name(context, name, principal); +} + + bool smb_krb5_principal_compare_any_realm(krb5_context context, + krb5_const_principal princ1, + krb5_const_principal princ2) +{ +#ifdef HAVE_KRB5_PRINCIPAL_COMPARE_ANY_REALM + + return krb5_principal_compare_any_realm(context, princ1, princ2); + +/* krb5_princ_size is a macro in MIT */ +#elif defined(HAVE_KRB5_PRINC_SIZE) || defined(krb5_princ_size) + + int i, len1, len2; + const krb5_data *p1, *p2; + + len1 = krb5_princ_size(context, princ1); + len2 = krb5_princ_size(context, princ2); + + if (len1 != len2) + return False; + + for (i = 0; i < len1; i++) { + + p1 = krb5_princ_component(context, CONST_DISCARD(krb5_principal, princ1), i); + p2 = krb5_princ_component(context, CONST_DISCARD(krb5_principal, princ2), i); + + if (p1->length != p2->length || memcmp(p1->data, p2->data, p1->length)) + return False; + } + + return True; +#else +#error NO_SUITABLE_PRINCIPAL_COMPARE_FUNCTION +#endif +} + + krb5_error_code smb_krb5_renew_ticket(const char *ccache_string, /* FILE:/tmp/krb5cc_0 */ + const char *client_string, /* gd@BER.SUSE.DE */ + const char *service_string, /* krbtgt/BER.SUSE.DE@BER.SUSE.DE */ + time_t *expire_time) +{ + krb5_error_code ret; + krb5_context context = NULL; + krb5_ccache ccache = NULL; + krb5_principal client = NULL; + krb5_creds creds, creds_in, *creds_out = NULL; + + ZERO_STRUCT(creds); + ZERO_STRUCT(creds_in); + + initialize_krb5_error_table(); + ret = krb5_init_context(&context); + if (ret) { + goto done; + } + + if (!ccache_string) { + ccache_string = krb5_cc_default_name(context); + } + + if (!ccache_string) { + ret = EINVAL; + goto done; + } + + DEBUG(10,("smb_krb5_renew_ticket: using %s as ccache\n", ccache_string)); + + /* FIXME: we should not fall back to defaults */ + ret = krb5_cc_resolve(context, CONST_DISCARD(char *, ccache_string), &ccache); + if (ret) { + goto done; + } + + if (client_string) { + ret = smb_krb5_parse_name(context, client_string, &client); + if (ret) { + goto done; + } + } else { + ret = krb5_cc_get_principal(context, ccache, &client); + if (ret) { + goto done; + } + } + +#ifdef HAVE_KRB5_GET_RENEWED_CREDS /* MIT */ + { + ret = krb5_get_renewed_creds(context, &creds, client, ccache, CONST_DISCARD(char *, service_string)); + if (ret) { + DEBUG(10,("smb_krb5_renew_ticket: krb5_get_kdc_cred failed: %s\n", error_message(ret))); + goto done; + } + } +#elif defined(HAVE_KRB5_GET_KDC_CRED) /* Heimdal */ + { + krb5_kdc_flags flags; + krb5_realm *client_realm = NULL; + + ret = krb5_copy_principal(context, client, &creds_in.client); + if (ret) { + goto done; + } + + if (service_string) { + ret = smb_krb5_parse_name(context, service_string, &creds_in.server); + if (ret) { + goto done; + } + } else { + /* build tgt service by default */ + client_realm = krb5_princ_realm(context, creds_in.client); + if (!client_realm) { + ret = ENOMEM; + goto done; + } + ret = krb5_make_principal(context, &creds_in.server, *client_realm, KRB5_TGS_NAME, *client_realm, NULL); + if (ret) { + goto done; + } + } + + flags.i = 0; + flags.b.renewable = flags.b.renew = True; + + ret = krb5_get_kdc_cred(context, ccache, flags, NULL, NULL, &creds_in, &creds_out); + if (ret) { + DEBUG(10,("smb_krb5_renew_ticket: krb5_get_kdc_cred failed: %s\n", error_message(ret))); + goto done; + } + + creds = *creds_out; + } +#else +#error NO_SUITABLE_KRB5_TICKET_RENEW_FUNCTION_AVAILABLE +#endif + + /* hm, doesn't that create a new one if the old one wasn't there? - Guenther */ + ret = krb5_cc_initialize(context, ccache, client); + if (ret) { + goto done; + } + + ret = krb5_cc_store_cred(context, ccache, &creds); + + if (expire_time) { + *expire_time = (time_t) creds.times.endtime; + } + +done: + krb5_free_cred_contents(context, &creds_in); + + if (creds_out) { + krb5_free_creds(context, creds_out); + } else { + krb5_free_cred_contents(context, &creds); + } + + if (client) { + krb5_free_principal(context, client); + } + if (ccache) { + krb5_cc_close(context, ccache); + } + if (context) { + krb5_free_context(context); + } + + return ret; +} + + krb5_error_code smb_krb5_free_addresses(krb5_context context, smb_krb5_addresses *addr) +{ + krb5_error_code ret = 0; + if (addr == NULL) { + return ret; + } +#if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */ + krb5_free_addresses(context, addr->addrs); +#elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */ + ret = krb5_free_addresses(context, addr->addrs); + SAFE_FREE(addr->addrs); +#endif + SAFE_FREE(addr); + addr = NULL; + return ret; +} + + krb5_error_code smb_krb5_gen_netbios_krb5_address(smb_krb5_addresses **kerb_addr) +{ + krb5_error_code ret = 0; + nstring buf; +#if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */ + krb5_address **addrs = NULL; +#elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */ + krb5_addresses *addrs = NULL; +#endif + + *kerb_addr = (smb_krb5_addresses *)SMB_MALLOC(sizeof(smb_krb5_addresses)); + if (*kerb_addr == NULL) { + return ENOMEM; + } + + put_name(buf, global_myname(), ' ', 0x20); + +#if defined(HAVE_MAGIC_IN_KRB5_ADDRESS) && defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) /* MIT */ + { + int num_addr = 2; + + addrs = (krb5_address **)SMB_MALLOC(sizeof(krb5_address *) * num_addr); + if (addrs == NULL) { + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + memset(addrs, 0, sizeof(krb5_address *) * num_addr); + + addrs[0] = (krb5_address *)SMB_MALLOC(sizeof(krb5_address)); + if (addrs[0] == NULL) { + SAFE_FREE(addrs); + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + addrs[0]->magic = KV5M_ADDRESS; + addrs[0]->addrtype = KRB5_ADDR_NETBIOS; + addrs[0]->length = MAX_NETBIOSNAME_LEN; + addrs[0]->contents = (unsigned char *)SMB_MALLOC(addrs[0]->length); + if (addrs[0]->contents == NULL) { + SAFE_FREE(addrs[0]); + SAFE_FREE(addrs); + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + memcpy(addrs[0]->contents, buf, addrs[0]->length); + + addrs[1] = NULL; + } +#elif defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) /* Heimdal */ + { + addrs = (krb5_addresses *)SMB_MALLOC(sizeof(krb5_addresses)); + if (addrs == NULL) { + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + memset(addrs, 0, sizeof(krb5_addresses)); + + addrs->len = 1; + addrs->val = (krb5_address *)SMB_MALLOC(sizeof(krb5_address)); + if (addrs->val == NULL) { + SAFE_FREE(addrs); + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + addrs->val[0].addr_type = KRB5_ADDR_NETBIOS; + addrs->val[0].address.length = MAX_NETBIOSNAME_LEN; + addrs->val[0].address.data = (unsigned char *)SMB_MALLOC(addrs->val[0].address.length); + if (addrs->val[0].address.data == NULL) { + SAFE_FREE(addrs->val); + SAFE_FREE(addrs); + SAFE_FREE(kerb_addr); + return ENOMEM; + } + + memcpy(addrs->val[0].address.data, buf, addrs->val[0].address.length); + } +#else +#error UNKNOWN_KRB5_ADDRESS_FORMAT +#endif + (*kerb_addr)->addrs = addrs; + + return ret; +} + + void smb_krb5_free_error(krb5_context context, krb5_error *krberror) +{ +#ifdef HAVE_KRB5_FREE_ERROR_CONTENTS /* Heimdal */ + krb5_free_error_contents(context, krberror); +#else /* MIT */ + krb5_free_error(context, krberror); +#endif +} + + krb5_error_code handle_krberror_packet(krb5_context context, + krb5_data *packet) +{ + krb5_error_code ret; + bool got_error_code = False; + + DEBUG(10,("handle_krberror_packet: got error packet\n")); + +#ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR /* Heimdal */ + { + krb5_error krberror; + + if ((ret = krb5_rd_error(context, packet, &krberror))) { + DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", + error_message(ret))); + return ret; + } + + if (krberror.e_data == NULL || krberror.e_data->data == NULL) { + ret = (krb5_error_code) krberror.error_code; + got_error_code = True; + } + + smb_krb5_free_error(context, &krberror); + } +#else /* MIT */ + { + krb5_error *krberror; + + if ((ret = krb5_rd_error(context, packet, &krberror))) { + DEBUG(10,("handle_krberror_packet: krb5_rd_error failed with: %s\n", + error_message(ret))); + return ret; + } + + if (krberror->e_data.data == NULL) { + ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error; + got_error_code = True; + } + smb_krb5_free_error(context, krberror); + } +#endif + if (got_error_code) { + DEBUG(5,("handle_krberror_packet: got KERBERR from kpasswd: %s (%d)\n", + error_message(ret), ret)); + } + return ret; +} + + krb5_error_code smb_krb5_get_init_creds_opt_alloc(krb5_context context, + krb5_get_init_creds_opt **opt) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC + /* Heimdal or modern MIT version */ + return krb5_get_init_creds_opt_alloc(context, opt); +#else + /* Historical MIT version */ + krb5_get_init_creds_opt *my_opt; + + *opt = NULL; + + if ((my_opt = SMB_MALLOC_P(krb5_get_init_creds_opt)) == NULL) { + return ENOMEM; + } + + krb5_get_init_creds_opt_init(my_opt); + + *opt = my_opt; + return 0; +#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC */ +} + + void smb_krb5_get_init_creds_opt_free(krb5_context context, + krb5_get_init_creds_opt *opt) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_FREE + +#ifdef KRB5_CREDS_OPT_FREE_REQUIRES_CONTEXT + /* Modern MIT or Heimdal version */ + krb5_get_init_creds_opt_free(context, opt); +#else + /* Heimdal version */ + krb5_get_init_creds_opt_free(opt); +#endif /* KRB5_CREDS_OPT_FREE_REQUIRES_CONTEXT */ + +#else /* HAVE_KRB5_GET_INIT_CREDS_OPT_FREE */ + /* Historical MIT version */ + SAFE_FREE(opt); + opt = NULL; +#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_FREE */ +} + + krb5_enctype smb_get_enctype_from_kt_entry(krb5_keytab_entry *kt_entry) +{ + return KRB5_KEY_TYPE(KRB5_KT_KEY(kt_entry)); +} + + +/* caller needs to free etype_s */ + krb5_error_code smb_krb5_enctype_to_string(krb5_context context, + krb5_enctype enctype, + char **etype_s) +{ +#ifdef HAVE_KRB5_ENCTYPE_TO_STRING_WITH_KRB5_CONTEXT_ARG + return krb5_enctype_to_string(context, enctype, etype_s); /* Heimdal */ +#elif defined(HAVE_KRB5_ENCTYPE_TO_STRING_WITH_SIZE_T_ARG) + char buf[256]; + krb5_error_code ret = krb5_enctype_to_string(enctype, buf, 256); /* MIT */ + if (ret) { + return ret; + } + *etype_s = SMB_STRDUP(buf); + if (!*etype_s) { + return ENOMEM; + } + return ret; +#else +#error UNKNOWN_KRB5_ENCTYPE_TO_STRING_FUNCTION +#endif +} + + krb5_error_code smb_krb5_mk_error(krb5_context context, + krb5_error_code error_code, + const krb5_principal server, + krb5_data *reply) +{ +#ifdef HAVE_SHORT_KRB5_MK_ERROR_INTERFACE /* MIT */ + /* + * The MIT interface is *terrible*. + * We have to construct this ourselves... + */ + krb5_error e; + + memset(&e, 0, sizeof(e)); + krb5_us_timeofday(context, &e.stime, &e.susec); + e.server = server; +#if defined(krb5_err_base) + e.error = error_code - krb5_err_base; +#elif defined(ERROR_TABLE_BASE_krb5) + e.error = error_code - ERROR_TABLE_BASE_krb5; +#else + e.error = error_code; /* Almost certainly wrong, but what can we do... ? */ +#endif + + return krb5_mk_error(context, &e, reply); +#else /* Heimdal. */ + return krb5_mk_error(context, + error_code, + NULL, + NULL, /* e_data */ + NULL, + server, + NULL, + NULL, + reply); +#endif +} + +/********************************************************************** + * Open a krb5 keytab with flags, handles readonly or readwrite access and + * allows to process non-default keytab names. + * @param context krb5_context + * @param keytab_name_req string + * @param write_access bool if writable keytab is required + * @param krb5_keytab pointer to krb5_keytab (close with krb5_kt_close()) + * @return krb5_error_code +**********************************************************************/ + +/* This MAX_NAME_LEN is a constant defined in krb5.h */ +#ifndef MAX_KEYTAB_NAME_LEN +#define MAX_KEYTAB_NAME_LEN 1100 +#endif + + krb5_error_code smb_krb5_open_keytab(krb5_context context, + const char *keytab_name_req, + bool write_access, + krb5_keytab *keytab) +{ + krb5_error_code ret = 0; + TALLOC_CTX *mem_ctx; + char keytab_string[MAX_KEYTAB_NAME_LEN]; + char *kt_str = NULL; + bool found_valid_name = False; + const char *pragma = "FILE"; + const char *tmp = NULL; + + if (!write_access && !keytab_name_req) { + /* caller just wants to read the default keytab readonly, so be it */ + return krb5_kt_default(context, keytab); + } + + mem_ctx = talloc_init("smb_krb5_open_keytab"); + if (!mem_ctx) { + return ENOMEM; + } + +#ifdef HAVE_WRFILE_KEYTAB + if (write_access) { + pragma = "WRFILE"; + } +#endif + + if (keytab_name_req) { + + if (strlen(keytab_name_req) > MAX_KEYTAB_NAME_LEN) { + ret = KRB5_CONFIG_NOTENUFSPACE; + goto out; + } + + if ((strncmp(keytab_name_req, "WRFILE:/", 8) == 0) || + (strncmp(keytab_name_req, "FILE:/", 6) == 0)) { + tmp = keytab_name_req; + goto resolve; + } + + if (keytab_name_req[0] != '/') { + ret = KRB5_KT_BADNAME; + goto out; + } + + tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, keytab_name_req); + if (!tmp) { + ret = ENOMEM; + goto out; + } + + goto resolve; + } + + /* we need to handle more complex keytab_strings, like: + * "ANY:FILE:/etc/krb5.keytab,krb4:/etc/srvtab" */ + + ret = krb5_kt_default_name(context, &keytab_string[0], MAX_KEYTAB_NAME_LEN - 2); + if (ret) { + goto out; + } + + DEBUG(10,("smb_krb5_open_keytab: krb5_kt_default_name returned %s\n", keytab_string)); + + tmp = talloc_strdup(mem_ctx, keytab_string); + if (!tmp) { + ret = ENOMEM; + goto out; + } + + if (strncmp(tmp, "ANY:", 4) == 0) { + tmp += 4; + } + + memset(&keytab_string, '\0', sizeof(keytab_string)); + + while (next_token_talloc(mem_ctx, &tmp, &kt_str, ",")) { + if (strncmp(kt_str, "WRFILE:", 7) == 0) { + found_valid_name = True; + tmp = kt_str; + tmp += 7; + } + + if (strncmp(kt_str, "FILE:", 5) == 0) { + found_valid_name = True; + tmp = kt_str; + tmp += 5; + } + + if (found_valid_name) { + if (tmp[0] != '/') { + ret = KRB5_KT_BADNAME; + goto out; + } + + tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, tmp); + if (!tmp) { + ret = ENOMEM; + goto out; + } + break; + } + } + + if (!found_valid_name) { + ret = KRB5_KT_UNKNOWN_TYPE; + goto out; + } + + resolve: + DEBUG(10,("smb_krb5_open_keytab: resolving: %s\n", tmp)); + ret = krb5_kt_resolve(context, tmp, keytab); + + out: + TALLOC_FREE(mem_ctx); + return ret; +} + +krb5_error_code smb_krb5_keytab_name(TALLOC_CTX *mem_ctx, + krb5_context context, + krb5_keytab keytab, + const char **keytab_name) +{ + char keytab_string[MAX_KEYTAB_NAME_LEN]; + krb5_error_code ret = 0; + + ret = krb5_kt_get_name(context, keytab, + keytab_string, MAX_KEYTAB_NAME_LEN - 2); + if (ret) { + return ret; + } + + *keytab_name = talloc_strdup(mem_ctx, keytab_string); + if (!*keytab_name) { + return ENOMEM; + } + + return ret; +} + +#if defined(TKT_FLG_OK_AS_DELEGATE ) && defined(HAVE_KRB5_FWD_TGT_CREDS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE) && defined(KRB5_AUTH_CONTEXT_USE_SUBKEY) +/************************************************************** +Routine: ads_krb5_get_fwd_ticket + Description: + When a service ticket is flagged as trusted + for delegation we should provide a forwardable + ticket so that the remote host can act on our + behalf. This is done by taking the 2nd forwardable + TGT and storing it in the GSS-API authenticator + "checksum". This routine will populate + the krb5_data authenticator with this TGT. + Parameters: + krb5_context context: The kerberos context for this authentication. + krb5_auth_context: The authentication context. + krb5_creds *credsp: The ticket credentials (AS-REP). + krb5_ccache ccache: The credentials cache. + krb5_data &authenticator: The checksum field that will store the TGT, and + authenticator.data must be freed by the caller. + + Returns: + krb5_error_code: 0 if no errors, otherwise set. +**************************************************************/ + +static krb5_error_code ads_krb5_get_fwd_ticket( krb5_context context, + krb5_auth_context *auth_context, + krb5_creds *credsp, + krb5_ccache ccache, + krb5_data *authenticator) +{ + krb5_data fwdData; + krb5_error_code retval = 0; + char *pChksum = NULL; + char *p = NULL; + + ZERO_STRUCT(fwdData); + ZERO_STRUCTP(authenticator); + + retval = krb5_fwd_tgt_creds(context,/* Krb5 context [in] */ + *auth_context, /* Authentication context [in] */ + CONST_DISCARD(char *, KRB5_TGS_NAME), /* Ticket service name ("krbtgt") [in] */ + credsp->client, /* Client principal for the tgt [in] */ + credsp->server, /* Server principal for the tgt [in] */ + ccache, /* Credential cache to use for storage [in] */ + 1, /* Turn on for "Forwardable ticket" [in] */ + &fwdData ); /* Resulting response [out] */ + + + if (retval) { + DEBUG(1,("ads_krb5_get_fwd_ticket: krb5_fwd_tgt_creds failed (%s)\n", + error_message(retval))); + goto out; + } + + if ((unsigned int)GSSAPI_CHECKSUM_SIZE + (unsigned int)fwdData.length < + (unsigned int)GSSAPI_CHECKSUM_SIZE) { + retval = EINVAL; + goto out; + } + + /* We're going to allocate a gssChecksum structure with a little + extra data the length of the kerberos credentials length + (APPLICATION 22) so that we can pack it on the end of the structure. + */ + + pChksum = (char *)SMB_MALLOC(GSSAPI_CHECKSUM_SIZE + fwdData.length ); + if (!pChksum) { + retval = ENOMEM; + goto out; + } + + p = pChksum; + + SIVAL(p, 0, GSSAPI_BNDLENGTH); + p += 4; + + /* Zero out the bindings fields */ + memset(p, '\0', GSSAPI_BNDLENGTH ); + p += GSSAPI_BNDLENGTH; + + SIVAL(p, 0, GSS_C_DELEG_FLAG ); + p += 4; + SSVAL(p, 0, 1 ); + p += 2; + SSVAL(p, 0, fwdData.length ); + p += 2; + + /* Migrate the kerberos KRB_CRED data to the checksum delegation */ + memcpy(p, fwdData.data, fwdData.length ); + p += fwdData.length; + + /* We need to do this in order to allow our GSS-API */ + retval = krb5_auth_con_set_req_cksumtype( context, *auth_context, GSSAPI_CHECKSUM ); + if (retval) { + goto out; + } + + /* We now have a service ticket, now turn it into an AP-REQ. */ + authenticator->length = fwdData.length + GSSAPI_CHECKSUM_SIZE; + + /* Caller should call free() when they're done with this. */ + authenticator->data = (char *)pChksum; + + out: + + /* Remove that input data, we never needed it anyway. */ + if (fwdData.length > 0) { + krb5_free_data_contents( context, &fwdData ); + } + + return retval; +} +#endif + +#else /* HAVE_KRB5 */ + /* this saves a few linking headaches */ + int cli_krb5_get_ticket(const char *principal, time_t time_offset, + DATA_BLOB *ticket, DATA_BLOB *session_key_krb5, uint32 extra_ap_opts, + const char *ccname, time_t *tgs_expire) +{ + DEBUG(0,("NO KERBEROS SUPPORT\n")); + return 1; +} + +#endif diff --git a/source3/libsmb/clilist.c b/source3/libsmb/clilist.c new file mode 100644 index 0000000000..50918458b0 --- /dev/null +++ b/source3/libsmb/clilist.c @@ -0,0 +1,683 @@ +/* + Unix SMB/CIFS implementation. + client directory list routines + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Calculate a safe next_entry_offset. +****************************************************************************/ + +static size_t calc_next_entry_offset(const char *base, const char *pdata_end) +{ + size_t next_entry_offset = (size_t)IVAL(base,0); + + if (next_entry_offset == 0 || + base + next_entry_offset < base || + base + next_entry_offset > pdata_end) { + next_entry_offset = pdata_end - base; + } + return next_entry_offset; +} + +/**************************************************************************** + Interpret a long filename structure - this is mostly guesses at the moment. + The length of the structure is returned + The structure of a long filename depends on the info level. 260 is used + by NT and 2 is used by OS/2 +****************************************************************************/ + +static size_t interpret_long_filename(TALLOC_CTX *ctx, + struct cli_state *cli, + int level, + const char *p, + const char *pdata_end, + file_info *finfo, + uint32 *p_resume_key, + DATA_BLOB *p_last_name_raw) +{ + int len; + size_t ret; + const char *base = p; + + data_blob_free(p_last_name_raw); + + if (p_resume_key) { + *p_resume_key = 0; + } + ZERO_STRUCTP(finfo); + finfo->cli = cli; + + switch (level) { + case 1: /* OS/2 understands this */ + /* these dates are converted to GMT by + make_unix_date */ + if (pdata_end - base < 27) { + return pdata_end - base; + } + finfo->ctime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+4)); + finfo->atime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+8)); + finfo->mtime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+12)); + finfo->size = IVAL(p,16); + finfo->mode = CVAL(p,24); + len = CVAL(p, 26); + p += 27; + p += clistr_align_in(cli, p, 0); + + /* We can safely use +1 here (which is required by OS/2) + * instead of +2 as the STR_TERMINATE flag below is + * actually used as the length calculation. + * The len+2 is merely an upper bound. + * Due to the explicit 2 byte null termination + * in cli_receive_trans/cli_receive_nt_trans + * we know this is safe. JRA + kukks + */ + + if (p + len + 1 > pdata_end) { + return pdata_end - base; + } + + /* the len+2 below looks strange but it is + important to cope with the differences + between win2000 and win9x for this call + (tridge) */ + ret = clistr_pull_talloc(ctx, + cli, + &finfo->name, + p, + len+2, + STR_TERMINATE); + if (ret == (size_t)-1) { + return pdata_end - base; + } + p += ret; + return PTR_DIFF(p, base); + + case 2: /* this is what OS/2 uses mostly */ + /* these dates are converted to GMT by + make_unix_date */ + if (pdata_end - base < 31) { + return pdata_end - base; + } + finfo->ctime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+4)); + finfo->atime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+8)); + finfo->mtime_ts = convert_time_t_to_timespec(cli_make_unix_date2(cli, p+12)); + finfo->size = IVAL(p,16); + finfo->mode = CVAL(p,24); + len = CVAL(p, 30); + p += 31; + /* check for unisys! */ + if (p + len + 1 > pdata_end) { + return pdata_end - base; + } + ret = clistr_pull_talloc(ctx, + cli, + &finfo->name, + p, + len, + STR_NOALIGN); + if (ret == (size_t)-1) { + return pdata_end - base; + } + p += ret; + return PTR_DIFF(p, base) + 1; + + case 260: /* NT uses this, but also accepts 2 */ + { + size_t namelen, slen; + + if (pdata_end - base < 94) { + return pdata_end - base; + } + + p += 4; /* next entry offset */ + + if (p_resume_key) { + *p_resume_key = IVAL(p,0); + } + p += 4; /* fileindex */ + + /* Offset zero is "create time", not "change time". */ + p += 8; + finfo->atime_ts = interpret_long_date(p); + p += 8; + finfo->mtime_ts = interpret_long_date(p); + p += 8; + finfo->ctime_ts = interpret_long_date(p); + p += 8; + finfo->size = IVAL2_TO_SMB_BIG_UINT(p,0); + p += 8; + p += 8; /* alloc size */ + finfo->mode = CVAL(p,0); + p += 4; + namelen = IVAL(p,0); + p += 4; + p += 4; /* EA size */ + slen = SVAL(p, 0); + if (slen > 24) { + /* Bad short name length. */ + return pdata_end - base; + } + p += 2; + { + /* stupid NT bugs. grr */ + int flags = 0; + if (p[1] == 0 && namelen > 1) flags |= STR_UNICODE; + clistr_pull(cli, finfo->short_name, p, + sizeof(finfo->short_name), + slen, flags); + } + p += 24; /* short name? */ + if (p + namelen < p || p + namelen > pdata_end) { + return pdata_end - base; + } + ret = clistr_pull_talloc(ctx, + cli, + &finfo->name, + p, + namelen, + 0); + if (ret == (size_t)-1) { + return pdata_end - base; + } + + /* To be robust in the face of unicode conversion failures + we need to copy the raw bytes of the last name seen here. + Namelen doesn't include the terminating unicode null, so + copy it here. */ + + if (p_last_name_raw) { + *p_last_name_raw = data_blob(NULL, namelen+2); + memcpy(p_last_name_raw->data, p, namelen); + SSVAL(p_last_name_raw->data, namelen, 0); + } + return calc_next_entry_offset(base, pdata_end); + } + } + + DEBUG(1,("Unknown long filename format %d\n",level)); + return calc_next_entry_offset(base, pdata_end); +} + +/**************************************************************************** + Do a directory listing, calling fn on each file found. +****************************************************************************/ + +int cli_list_new(struct cli_state *cli,const char *Mask,uint16 attribute, + void (*fn)(const char *, file_info *, const char *, void *), void *state) +{ +#if 1 + int max_matches = 1366; /* Match W2k - was 512. */ +#else + int max_matches = 512; +#endif + int info_level; + char *p, *p2, *rdata_end; + char *mask = NULL; + file_info finfo; + int i; + char *dirlist = NULL; + int dirlist_len = 0; + int total_received = -1; + bool First = True; + int ff_searchcount=0; + int ff_eos=0; + int ff_dir_handle=0; + int loop_count = 0; + char *rparam=NULL, *rdata=NULL; + unsigned int param_len, data_len; + uint16 setup; + char *param; + const char *mnt; + uint32 resume_key = 0; + TALLOC_CTX *frame = talloc_stackframe(); + DATA_BLOB last_name_raw = data_blob(NULL, 0); + + /* NT uses 260, OS/2 uses 2. Both accept 1. */ + info_level = (cli->capabilities&CAP_NT_SMBS)?260:1; + + mask = SMB_STRDUP(Mask); + if (!mask) { + TALLOC_FREE(frame); + return -1; + } + + while (ff_eos == 0) { + size_t nlen = 2*(strlen(mask)+1); + + loop_count++; + if (loop_count > 200) { + DEBUG(0,("Error: Looping in FIND_NEXT??\n")); + break; + } + + param = SMB_MALLOC_ARRAY(char, 12+nlen+last_name_raw.length+2); + if (!param) { + break; + } + + if (First) { + setup = TRANSACT2_FINDFIRST; + SSVAL(param,0,attribute); /* attribute */ + SSVAL(param,2,max_matches); /* max count */ + SSVAL(param,4,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END)); /* resume required + close on end */ + SSVAL(param,6,info_level); + SIVAL(param,8,0); + p = param+12; + p += clistr_push(cli, param+12, mask, + nlen, STR_TERMINATE); + } else { + setup = TRANSACT2_FINDNEXT; + SSVAL(param,0,ff_dir_handle); + SSVAL(param,2,max_matches); /* max count */ + SSVAL(param,4,info_level); + /* For W2K servers serving out FAT filesystems we *must* set the + resume key. If it's not FAT then it's returned as zero. */ + SIVAL(param,6,resume_key); /* ff_resume_key */ + /* NB. *DON'T* use continue here. If you do it seems that W2K and bretheren + can miss filenames. Use last filename continue instead. JRA */ + SSVAL(param,10,(FLAG_TRANS2_FIND_REQUIRE_RESUME|FLAG_TRANS2_FIND_CLOSE_IF_END)); /* resume required + close on end */ + p = param+12; + if (last_name_raw.length) { + memcpy(p, last_name_raw.data, last_name_raw.length); + p += last_name_raw.length; + } else { + p += clistr_push(cli, param+12, mask, + nlen, STR_TERMINATE); + } + } + + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* Name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + NULL, 0, +#if 0 + /* w2k value. */ + MIN(16384,cli->max_xmit) /* data, length, max. */ +#else + cli->max_xmit /* data, length, max. */ +#endif + )) { + SAFE_FREE(param); + TALLOC_FREE(frame); + break; + } + + SAFE_FREE(param); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len) && + cli_is_dos_error(cli)) { + /* We need to work around a Win95 bug - sometimes + it gives ERRSRV/ERRerror temprarily */ + uint8 eclass; + uint32 ecode; + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + cli_dos_error(cli, &eclass, &ecode); + + /* + * OS/2 might return "no more files", + * which just tells us, that searchcount is zero + * in this search. + * Guenter Kukkukk <linux@kukkukk.com> + */ + + if (eclass == ERRDOS && ecode == ERRnofiles) { + ff_searchcount = 0; + cli_reset_error(cli); + break; + } + + if (eclass != ERRSRV || ecode != ERRerror) + break; + smb_msleep(100); + continue; + } + + if (cli_is_error(cli) || !rdata || !rparam) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + break; + } + + if (total_received == -1) + total_received = 0; + + /* parse out some important return info */ + p = rparam; + if (First) { + ff_dir_handle = SVAL(p,0); + ff_searchcount = SVAL(p,2); + ff_eos = SVAL(p,4); + } else { + ff_searchcount = SVAL(p,0); + ff_eos = SVAL(p,2); + } + + if (ff_searchcount == 0) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + break; + } + + /* point to the data bytes */ + p = rdata; + rdata_end = rdata + data_len; + + /* we might need the lastname for continuations */ + for (p2=p,i=0;i<ff_searchcount && p2 < rdata_end;i++) { + if ((info_level == 260) && (i == ff_searchcount-1)) { + /* Last entry - fixup the last offset length. */ + SIVAL(p2,0,PTR_DIFF((rdata + data_len),p2)); + } + p2 += interpret_long_filename(frame, + cli, + info_level, + p2, + rdata_end, + &finfo, + &resume_key, + &last_name_raw); + + if (!finfo.name) { + DEBUG(0,("cli_list_new: Error: unable to parse name from info level %d\n", + info_level)); + ff_eos = 1; + break; + } + if (!First && *mask && strcsequal(finfo.name, mask)) { + DEBUG(0,("Error: Looping in FIND_NEXT as name %s has already been seen?\n", + finfo.name)); + ff_eos = 1; + break; + } + } + + SAFE_FREE(mask); + if (ff_searchcount > 0) { + mask = SMB_STRDUP(finfo.name); + } else { + mask = SMB_STRDUP(""); + } + if (!mask) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + break; + } + + /* grab the data for later use */ + /* and add them to the dirlist pool */ + dirlist = (char *)SMB_REALLOC(dirlist,dirlist_len + data_len); + + if (!dirlist) { + DEBUG(0,("cli_list_new: Failed to expand dirlist\n")); + SAFE_FREE(rdata); + SAFE_FREE(rparam); + break; + } + + memcpy(dirlist+dirlist_len,p,data_len); + dirlist_len += data_len; + + total_received += ff_searchcount; + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + DEBUG(3,("received %d entries (eos=%d)\n", + ff_searchcount,ff_eos)); + + if (ff_searchcount > 0) + loop_count = 0; + + First = False; + } + + mnt = cli_cm_get_mntpoint( cli ); + + /* see if the server disconnected or the connection otherwise failed */ + if (cli_is_error(cli)) { + total_received = -1; + } else { + /* no connection problem. let user function add each entry */ + rdata_end = dirlist + dirlist_len; + for (p=dirlist,i=0;i<total_received;i++) { + p += interpret_long_filename(frame, + cli, + info_level, + p, + rdata_end, + &finfo, + NULL, + NULL); + if (!finfo.name) { + DEBUG(0,("cli_list_new: unable to parse name from info level %d\n", + info_level)); + break; + } + fn(mnt,&finfo, Mask, state); + } + } + + /* free up the dirlist buffer and last name raw blob */ + SAFE_FREE(dirlist); + data_blob_free(&last_name_raw); + SAFE_FREE(mask); + TALLOC_FREE(frame); + return(total_received); +} + +/**************************************************************************** + Interpret a short filename structure. + The length of the structure is returned. +****************************************************************************/ + +static bool interpret_short_filename(TALLOC_CTX *ctx, + struct cli_state *cli, + char *p, + file_info *finfo) +{ + size_t ret; + ZERO_STRUCTP(finfo); + + finfo->cli = cli; + finfo->mode = CVAL(p,21); + + /* this date is converted to GMT by make_unix_date */ + finfo->ctime_ts.tv_sec = cli_make_unix_date(cli, p+22); + finfo->ctime_ts.tv_nsec = 0; + finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec; + finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0; + finfo->size = IVAL(p,26); + ret = clistr_pull_talloc(ctx, + cli, + &finfo->name, + p+30, + 12, + STR_ASCII); + if (ret == (size_t)-1) { + return false; + } + + if (finfo->name) { + strlcpy(finfo->short_name, + finfo->name, + sizeof(finfo->short_name)); + } + return true; + return(DIR_STRUCT_SIZE); +} + +/**************************************************************************** + Do a directory listing, calling fn on each file found. + this uses the old SMBsearch interface. It is needed for testing Samba, + but should otherwise not be used. +****************************************************************************/ + +int cli_list_old(struct cli_state *cli,const char *Mask,uint16 attribute, + void (*fn)(const char *, file_info *, const char *, void *), void *state) +{ + char *p; + int received = 0; + bool first = True; + char status[21]; + int num_asked = (cli->max_xmit - 100)/DIR_STRUCT_SIZE; + int num_received = 0; + int i; + char *dirlist = NULL; + char *mask = NULL; + TALLOC_CTX *frame = NULL; + + ZERO_ARRAY(status); + + mask = SMB_STRDUP(Mask); + if (!mask) { + return -1; + } + + while (1) { + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,2,0,True); + + SCVAL(cli->outbuf,smb_com,SMBsearch); + + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,num_asked); + SSVAL(cli->outbuf,smb_vwv1,attribute); + + p = smb_buf(cli->outbuf); + *p++ = 4; + + p += clistr_push(cli, p, first?mask:"", + cli->bufsize - PTR_DIFF(p,cli->outbuf), + STR_TERMINATE); + *p++ = 5; + if (first) { + SSVAL(p,0,0); + p += 2; + } else { + SSVAL(p,0,21); + p += 2; + memcpy(p,status,21); + p += 21; + } + + cli_setup_bcc(cli, p); + cli_send_smb(cli); + if (!cli_receive_smb(cli)) break; + + received = SVAL(cli->inbuf,smb_vwv0); + if (received <= 0) break; + + /* Ensure we received enough data. */ + if ((cli->inbuf+4+smb_len(cli->inbuf) - (smb_buf(cli->inbuf)+3)) < + received*DIR_STRUCT_SIZE) { + break; + } + + first = False; + + dirlist = (char *)SMB_REALLOC( + dirlist,(num_received + received)*DIR_STRUCT_SIZE); + if (!dirlist) { + DEBUG(0,("cli_list_old: failed to expand dirlist")); + SAFE_FREE(mask); + return 0; + } + + p = smb_buf(cli->inbuf) + 3; + + memcpy(dirlist+num_received*DIR_STRUCT_SIZE, + p,received*DIR_STRUCT_SIZE); + + memcpy(status,p + ((received-1)*DIR_STRUCT_SIZE),21); + + num_received += received; + + if (cli_is_error(cli)) break; + } + + if (!first) { + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,2,0,True); + SCVAL(cli->outbuf,smb_com,SMBfclose); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf, smb_vwv0, 0); /* find count? */ + SSVAL(cli->outbuf, smb_vwv1, attribute); + + p = smb_buf(cli->outbuf); + *p++ = 4; + fstrcpy(p, ""); + p += strlen(p) + 1; + *p++ = 5; + SSVAL(p, 0, 21); + p += 2; + memcpy(p,status,21); + p += 21; + + cli_setup_bcc(cli, p); + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + DEBUG(0,("Error closing search: %s\n",cli_errstr(cli))); + } + } + + frame = talloc_stackframe(); + for (p=dirlist,i=0;i<num_received;i++) { + file_info finfo; + if (!interpret_short_filename(frame, cli, p, &finfo)) { + break; + } + p += DIR_STRUCT_SIZE; + fn("\\", &finfo, Mask, state); + } + TALLOC_FREE(frame); + + SAFE_FREE(mask); + SAFE_FREE(dirlist); + return(num_received); +} + +/**************************************************************************** + Do a directory listing, calling fn on each file found. + This auto-switches between old and new style. +****************************************************************************/ + +int cli_list(struct cli_state *cli,const char *Mask,uint16 attribute, + void (*fn)(const char *, file_info *, const char *, void *), void *state) +{ + if (cli->protocol <= PROTOCOL_LANMAN1) + return cli_list_old(cli, Mask, attribute, fn, state); + return cli_list_new(cli, Mask, attribute, fn, state); +} diff --git a/source3/libsmb/climessage.c b/source3/libsmb/climessage.c new file mode 100644 index 0000000000..808190e79c --- /dev/null +++ b/source3/libsmb/climessage.c @@ -0,0 +1,162 @@ +/* + Unix SMB/CIFS implementation. + client message handling routines + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Start a message sequence. +****************************************************************************/ + +int cli_message_start_build(struct cli_state *cli, const char *host, const char *username) +{ + char *p; + + /* construct a SMBsendstrt command */ + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,0,0,True); + SCVAL(cli->outbuf,smb_com,SMBsendstrt); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + *p++ = 4; + p += clistr_push(cli, p, username, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_ASCII|STR_TERMINATE); + *p++ = 4; + p += clistr_push(cli, p, host, + cli->bufsize - PTR_DIFF(p,cli->outbuf), STR_ASCII|STR_TERMINATE); + + cli_setup_bcc(cli, p); + + return(PTR_DIFF(p, cli->outbuf)); +} + +bool cli_message_start(struct cli_state *cli, const char *host, const char *username, + int *grp) +{ + cli_message_start_build(cli, host, username); + cli_send_smb(cli); + + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) return False; + + *grp = SVAL(cli->inbuf,smb_vwv0); + + return True; +} + +/**************************************************************************** + Send a message +****************************************************************************/ + +int cli_message_text_build(struct cli_state *cli, const char *msg, int len, int grp) +{ + char *msgdos; + size_t lendos; + char *p; + + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,1,0,True); + SCVAL(cli->outbuf,smb_com,SMBsendtxt); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,grp); + + p = smb_buf(cli->outbuf); + *p++ = 1; + + if (!convert_string_allocate(NULL, CH_UNIX, CH_DOS, msg, len, + (void **)(void *)&msgdos, &lendos, True) || !msgdos) { + DEBUG(3,("Conversion failed, sending message in UNIX charset\n")); + SSVAL(p, 0, len); p += 2; + if (len > cli->bufsize - PTR_DIFF(p,cli->outbuf)) { + return -1; + } + memcpy(p, msg, len); + p += len; + } else { + SSVAL(p, 0, lendos); p += 2; + if (lendos > cli->bufsize - PTR_DIFF(p,cli->outbuf)) { + return -1; + } + memcpy(p, msgdos, lendos); + p += lendos; + SAFE_FREE(msgdos); + } + + cli_setup_bcc(cli, p); + + return(PTR_DIFF(p, cli->outbuf)); +} + +bool cli_message_text(struct cli_state *cli, const char *msg, int len, int grp) +{ + cli_message_text_build(cli, msg, len, grp); + + cli_send_smb(cli); + + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) return False; + + return True; +} + +/**************************************************************************** + End a message. +****************************************************************************/ + +int cli_message_end_build(struct cli_state *cli, int grp) +{ + char *p; + + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,1,0,True); + SCVAL(cli->outbuf,smb_com,SMBsendend); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + + SSVAL(cli->outbuf,smb_vwv0,grp); + + cli_setup_packet(cli); + + p = smb_buf(cli->outbuf); + + return(PTR_DIFF(p, cli->outbuf)); +} + +bool cli_message_end(struct cli_state *cli, int grp) +{ + cli_message_end_build(cli, grp); + + cli_send_smb(cli); + + if (!cli_receive_smb(cli)) { + return False; + } + + if (cli_is_error(cli)) return False; + + return True; +} diff --git a/source3/libsmb/clioplock.c b/source3/libsmb/clioplock.c new file mode 100644 index 0000000000..ef8b396461 --- /dev/null +++ b/source3/libsmb/clioplock.c @@ -0,0 +1,66 @@ +/* + Unix SMB/CIFS implementation. + SMB client oplock functions + Copyright (C) Andrew Tridgell 2001 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** +send an ack for an oplock break request +****************************************************************************/ + +bool cli_oplock_ack(struct cli_state *cli, int fnum, unsigned char level) +{ + char *oldbuf = cli->outbuf; + char buf[smb_size+16]; + bool ret; + + cli->outbuf = buf; + + memset(buf,'\0',smb_size); + cli_set_message(buf,8,0,True); + + SCVAL(buf,smb_com,SMBlockingX); + SSVAL(buf,smb_tid, cli->cnum); + cli_setup_packet(cli); + SSVAL(buf,smb_vwv0,0xFF); + SSVAL(buf,smb_vwv1,0); + SSVAL(buf,smb_vwv2,fnum); + if (level == 1) + SSVAL(buf,smb_vwv3,0x102); /* levelII oplock break ack */ + else + SSVAL(buf,smb_vwv3,2); /* exclusive oplock break ack */ + SIVAL(buf,smb_vwv4,0); /* timoeut */ + SSVAL(buf,smb_vwv6,0); /* unlockcount */ + SSVAL(buf,smb_vwv7,0); /* lockcount */ + + ret = cli_send_smb(cli); + + cli->outbuf = oldbuf; + + return ret; +} + + +/**************************************************************************** +set the oplock handler for a connection +****************************************************************************/ +void cli_oplock_handler(struct cli_state *cli, + bool (*handler)(struct cli_state *, int, unsigned char)) +{ + cli->oplock_handler = handler; +} diff --git a/source3/libsmb/cliprint.c b/source3/libsmb/cliprint.c new file mode 100644 index 0000000000..223ddb4186 --- /dev/null +++ b/source3/libsmb/cliprint.c @@ -0,0 +1,260 @@ +/* + Unix SMB/CIFS implementation. + client print routines + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/***************************************************************************** + Convert a character pointer in a cli_call_api() response to a form we can use. + This function contains code to prevent core dumps if the server returns + invalid data. +*****************************************************************************/ +static const char *fix_char_ptr(unsigned int datap, unsigned int converter, + char *rdata, int rdrcnt) +{ + if (datap == 0) { + /* turn NULL pointers into zero length strings */ + return ""; + } else { + unsigned int offset = datap - converter; + + if (offset >= rdrcnt) { + DEBUG(1,("bad char ptr: datap=%u, converter=%u rdrcnt=%d>", + datap, converter, rdrcnt)); + return "<ERROR>"; + } else { + return &rdata[offset]; + } + } +} + +/**************************************************************************** +call fn() on each entry in a print queue +****************************************************************************/ + +int cli_print_queue(struct cli_state *cli, + void (*fn)(struct print_job_info *)) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt, rprcnt; + char param[1024]; + int result_code=0; + int i = -1; + + memset(param,'\0',sizeof(param)); + + p = param; + SSVAL(p,0,76); /* API function number 76 (DosPrintJobEnum) */ + p += 2; + safe_strcpy_base(p,"zWrLeh", param, sizeof(param)); /* parameter description? */ + p = skip_string(param,sizeof(param),p); + safe_strcpy_base(p,"WWzWWDDzz", param, sizeof(param)); /* returned data format */ + p = skip_string(param,sizeof(param),p); + safe_strcpy_base(p,cli->share, param, sizeof(param)); /* name of queue */ + p = skip_string(param,sizeof(param),p); + SSVAL(p,0,2); /* API function level 2, PRJINFO_2 data structure */ + SSVAL(p,2,1000); /* size of bytes of returned data buffer */ + p += 4; + safe_strcpy_base(p,"", param,sizeof(param)); /* subformat */ + p = skip_string(param,sizeof(param),p); + + DEBUG(4,("doing cli_print_queue for %s\n", cli->share)); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) { /* return data, length */ + int converter; + result_code = SVAL(rparam,0); + converter = SVAL(rparam,2); /* conversion factor */ + + if (result_code == 0) { + struct print_job_info job; + + p = rdata; + + for (i = 0; i < SVAL(rparam,4); ++i) { + job.id = SVAL(p,0); + job.priority = SVAL(p,2); + fstrcpy(job.user, + fix_char_ptr(SVAL(p,4), converter, + rdata, rdrcnt)); + job.t = cli_make_unix_date3(cli, p + 12); + job.size = IVAL(p,16); + fstrcpy(job.name,fix_char_ptr(SVAL(p,24), + converter, + rdata, rdrcnt)); + fn(&job); + p += 28; + } + } + } + + /* If any parameters or data were returned, free the storage. */ + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return i; +} + +/**************************************************************************** + cancel a print job + ****************************************************************************/ + +int cli_printjob_del(struct cli_state *cli, int job) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int ret = -1; + char param[1024]; + + memset(param,'\0',sizeof(param)); + + p = param; + SSVAL(p,0,81); /* DosPrintJobDel() */ + p += 2; + safe_strcpy_base(p,"W", param,sizeof(param)); + p = skip_string(param,sizeof(param),p); + safe_strcpy_base(p,"", param,sizeof(param)); + p = skip_string(param,sizeof(param),p); + SSVAL(p,0,job); + p += 2; + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) { /* return data, length */ + ret = SVAL(rparam,0); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + + +/**************************************************************************** + Open a spool file +****************************************************************************/ + +int cli_spl_open(struct cli_state *cli, const char *fname, int flags, int share_mode) +{ + char *p; + unsigned openfn=0; + unsigned accessmode=0; + + if (flags & O_CREAT) + openfn |= (1<<4); + if (!(flags & O_EXCL)) { + if (flags & O_TRUNC) + openfn |= (1<<1); + else + openfn |= (1<<0); + } + + accessmode = (share_mode<<4); + + if ((flags & O_ACCMODE) == O_RDWR) { + accessmode |= 2; + } else if ((flags & O_ACCMODE) == O_WRONLY) { + accessmode |= 1; + } + +#if defined(O_SYNC) + if ((flags & O_SYNC) == O_SYNC) { + accessmode |= (1<<14); + } +#endif /* O_SYNC */ + + if (share_mode == DENY_FCB) { + accessmode = 0xFF; + } + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,15,0,True); + + SCVAL(cli->outbuf,smb_com,SMBsplopen); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,0); /* no additional info */ + SSVAL(cli->outbuf,smb_vwv3,accessmode); + SSVAL(cli->outbuf,smb_vwv4,aSYSTEM | aHIDDEN); + SSVAL(cli->outbuf,smb_vwv5,0); + SSVAL(cli->outbuf,smb_vwv8,openfn); + + if (cli->use_oplocks) { + /* if using oplocks then ask for a batch oplock via + core and extended methods */ + SCVAL(cli->outbuf,smb_flg, CVAL(cli->outbuf,smb_flg)| + FLAG_REQUEST_OPLOCK|FLAG_REQUEST_BATCH_OPLOCK); + SSVAL(cli->outbuf,smb_vwv2,SVAL(cli->outbuf,smb_vwv2) | 6); + } + + p = smb_buf(cli->outbuf); + p += clistr_push(cli, p, fname, -1, STR_TERMINATE); + + cli_setup_bcc(cli, p); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return -1; + } + + if (cli_is_error(cli)) { + return -1; + } + + return SVAL(cli->inbuf,smb_vwv2); +} + +/**************************************************************************** + Close a file. +****************************************************************************/ + +bool cli_spl_close(struct cli_state *cli, int fnum) +{ + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,3,0,True); + + SCVAL(cli->outbuf,smb_com,SMBsplclose); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,fnum); + SIVALS(cli->outbuf,smb_vwv1,-1); + + cli_send_smb(cli); + if (!cli_receive_smb(cli)) { + return False; + } + + return !cli_is_error(cli); +} diff --git a/source3/libsmb/cliquota.c b/source3/libsmb/cliquota.c new file mode 100644 index 0000000000..f369d28dff --- /dev/null +++ b/source3/libsmb/cliquota.c @@ -0,0 +1,642 @@ +/* + Unix SMB/CIFS implementation. + client quota functions + Copyright (C) Stefan (metze) Metzmacher 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +bool cli_get_quota_handle(struct cli_state *cli, int *quota_fnum) +{ + *quota_fnum = cli_nt_create_full(cli, FAKE_FILE_NAME_QUOTA_WIN32, + 0x00000016, DESIRED_ACCESS_PIPE, + 0x00000000, FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, 0x00000000, 0x03); + + if (*quota_fnum == (-1)) { + return False; + } + + return True; +} + +void free_ntquota_list(SMB_NTQUOTA_LIST **qt_list) +{ + if (!qt_list) + return; + + if ((*qt_list)->mem_ctx) + talloc_destroy((*qt_list)->mem_ctx); + + (*qt_list) = NULL; + + return; +} + +static bool parse_user_quota_record(const char *rdata, unsigned int rdata_count, unsigned int *offset, SMB_NTQUOTA_STRUCT *pqt) +{ + int sid_len; + SMB_NTQUOTA_STRUCT qt; + + ZERO_STRUCT(qt); + + if (!rdata||!offset||!pqt) { + smb_panic("parse_quota_record: called with NULL POINTER!"); + } + + if (rdata_count < 40) { + return False; + } + + /* offset to next quota record. + * 4 bytes IVAL(rdata,0) + * unused here... + */ + *offset = IVAL(rdata,0); + + /* sid len */ + sid_len = IVAL(rdata,4); + + if (rdata_count < 40+sid_len) { + return False; + } + + /* unknown 8 bytes in pdata + * maybe its the change time in NTTIME + */ + + /* the used space 8 bytes (SMB_BIG_UINT)*/ + qt.usedspace = (SMB_BIG_UINT)IVAL(rdata,16); +#ifdef LARGE_SMB_OFF_T + qt.usedspace |= (((SMB_BIG_UINT)IVAL(rdata,20)) << 32); +#else /* LARGE_SMB_OFF_T */ + if ((IVAL(rdata,20) != 0)&& + ((qt.usedspace != 0xFFFFFFFF)|| + (IVAL(rdata,20)!=0xFFFFFFFF))) { + /* more than 32 bits? */ + return False; + } +#endif /* LARGE_SMB_OFF_T */ + + /* the soft quotas 8 bytes (SMB_BIG_UINT)*/ + qt.softlim = (SMB_BIG_UINT)IVAL(rdata,24); +#ifdef LARGE_SMB_OFF_T + qt.softlim |= (((SMB_BIG_UINT)IVAL(rdata,28)) << 32); +#else /* LARGE_SMB_OFF_T */ + if ((IVAL(rdata,28) != 0)&& + ((qt.softlim != 0xFFFFFFFF)|| + (IVAL(rdata,28)!=0xFFFFFFFF))) { + /* more than 32 bits? */ + return False; + } +#endif /* LARGE_SMB_OFF_T */ + + /* the hard quotas 8 bytes (SMB_BIG_UINT)*/ + qt.hardlim = (SMB_BIG_UINT)IVAL(rdata,32); +#ifdef LARGE_SMB_OFF_T + qt.hardlim |= (((SMB_BIG_UINT)IVAL(rdata,36)) << 32); +#else /* LARGE_SMB_OFF_T */ + if ((IVAL(rdata,36) != 0)&& + ((qt.hardlim != 0xFFFFFFFF)|| + (IVAL(rdata,36)!=0xFFFFFFFF))) { + /* more than 32 bits? */ + return False; + } +#endif /* LARGE_SMB_OFF_T */ + + sid_parse(rdata+40,sid_len,&qt.sid); + + qt.qtype = SMB_USER_QUOTA_TYPE; + + *pqt = qt; + + return True; +} + +bool cli_get_user_quota(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_STRUCT *pqt) +{ + bool ret = False; + uint16 setup; + char params[16]; + unsigned int data_len; + char data[SID_MAX_SIZE+8]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + unsigned int sid_len; + unsigned int offset; + + if (!cli||!pqt) { + smb_panic("cli_get_user_quota() called with NULL Pointer!"); + } + + setup = NT_TRANSACT_GET_USER_QUOTA; + + SSVAL(params, 0,quota_fnum); + SSVAL(params, 2,TRANSACT_GET_USER_QUOTA_FOR_SID); + SIVAL(params, 4,0x00000024); + SIVAL(params, 8,0x00000000); + SIVAL(params,12,0x00000024); + + sid_len = ndr_size_dom_sid(&pqt->sid, 0); + data_len = sid_len+8; + SIVAL(data, 0, 0x00000000); + SIVAL(data, 4, sid_len); + sid_linearize(data+8, sid_len, &pqt->sid); + + if (!cli_send_nt_trans(cli, + NT_TRANSACT_GET_USER_QUOTA, + 0, + &setup, 1, 0, + params, 16, 4, + data, data_len, 112)) { + DEBUG(1,("Failed to send NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + + if (!cli_receive_nt_trans(cli, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + DEBUG(1,("Failed to recv NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if ((rparam&&rdata)&&(rparam_count>=4&&rdata_count>=8)) { + ret = parse_user_quota_record(rdata, rdata_count, &offset, pqt); + } else { + DEBUG(0,("Got INVALID NT_TRANSACT_GET_USER_QUOTA reply.\n")); + ret = False; + } + + cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return ret; +} + +bool cli_set_user_quota(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_STRUCT *pqt) +{ + bool ret = False; + uint16 setup; + char params[2]; + char data[112]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + unsigned int sid_len; + memset(data,'\0',112); + + if (!cli||!pqt) { + smb_panic("cli_set_user_quota() called with NULL Pointer!"); + } + + setup = NT_TRANSACT_SET_USER_QUOTA; + + SSVAL(params,0,quota_fnum); + + sid_len = ndr_size_dom_sid(&pqt->sid, 0); + SIVAL(data,0,0); + SIVAL(data,4,sid_len); + SBIG_UINT(data, 8,(SMB_BIG_UINT)0); + SBIG_UINT(data,16,pqt->usedspace); + SBIG_UINT(data,24,pqt->softlim); + SBIG_UINT(data,32,pqt->hardlim); + sid_linearize(data+40, sid_len, &pqt->sid); + + if (!cli_send_nt_trans(cli, + NT_TRANSACT_SET_USER_QUOTA, + 0, + &setup, 1, 0, + params, 2, 0, + data, 112, 0)) { + DEBUG(1,("Failed to send NT_TRANSACT_SET_USER_QUOTA\n")); + goto cleanup; + } + + + if (!cli_receive_nt_trans(cli, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + DEBUG(1,("NT_TRANSACT_SET_USER_QUOTA failed\n")); + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return ret; +} + +bool cli_list_user_quota(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_LIST **pqt_list) +{ + bool ret = False; + uint16 setup; + char params[16]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + unsigned int offset; + const char *curdata = NULL; + unsigned int curdata_count = 0; + TALLOC_CTX *mem_ctx = NULL; + SMB_NTQUOTA_STRUCT qt; + SMB_NTQUOTA_LIST *tmp_list_ent; + + if (!cli||!pqt_list) { + smb_panic("cli_list_user_quota() called with NULL Pointer!"); + } + + setup = NT_TRANSACT_GET_USER_QUOTA; + + SSVAL(params, 0,quota_fnum); + SSVAL(params, 2,TRANSACT_GET_USER_QUOTA_LIST_START); + SIVAL(params, 4,0x00000000); + SIVAL(params, 8,0x00000000); + SIVAL(params,12,0x00000000); + + if (!cli_send_nt_trans(cli, + NT_TRANSACT_GET_USER_QUOTA, + 0, + &setup, 1, 0, + params, 16, 4, + NULL, 0, 2048)) { + DEBUG(1,("Failed to send NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + + if (!cli_receive_nt_trans(cli, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + DEBUG(1,("Failed to recv NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count == 0) { + *pqt_list = NULL; + return True; + } + + if ((mem_ctx=talloc_init("SMB_USER_QUOTA_LIST"))==NULL) { + DEBUG(0,("talloc_init() failed\n")); + return (-1); + } + + offset = 1; + for (curdata=rdata,curdata_count=rdata_count; + ((curdata)&&(curdata_count>=8)&&(offset>0)); + curdata +=offset,curdata_count -= offset) { + ZERO_STRUCT(qt); + if (!parse_user_quota_record(curdata, curdata_count, &offset, &qt)) { + DEBUG(1,("Failed to parse the quota record\n")); + goto cleanup; + } + + if ((tmp_list_ent=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_LIST))==NULL) { + DEBUG(0,("TALLOC_ZERO() failed\n")); + talloc_destroy(mem_ctx); + return (-1); + } + + if ((tmp_list_ent->quotas=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_STRUCT))==NULL) { + DEBUG(0,("TALLOC_ZERO() failed\n")); + talloc_destroy(mem_ctx); + return (-1); + } + + memcpy(tmp_list_ent->quotas,&qt,sizeof(qt)); + tmp_list_ent->mem_ctx = mem_ctx; + + DLIST_ADD((*pqt_list),tmp_list_ent); + } + + SSVAL(params, 2,TRANSACT_GET_USER_QUOTA_LIST_CONTINUE); + while(1) { + if (!cli_send_nt_trans(cli, + NT_TRANSACT_GET_USER_QUOTA, + 0, + &setup, 1, 0, + params, 16, 4, + NULL, 0, 2048)) { + DEBUG(1,("Failed to send NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + if (!cli_receive_nt_trans(cli, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + DEBUG(1,("Failed to recv NT_TRANSACT_GET_USER_QUOTA\n")); + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count == 0) { + break; + } + + offset = 1; + for (curdata=rdata,curdata_count=rdata_count; + ((curdata)&&(curdata_count>=8)&&(offset>0)); + curdata +=offset,curdata_count -= offset) { + ZERO_STRUCT(qt); + if (!parse_user_quota_record(curdata, curdata_count, &offset, &qt)) { + DEBUG(1,("Failed to parse the quota record\n")); + goto cleanup; + } + + if ((tmp_list_ent=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_LIST))==NULL) { + DEBUG(0,("TALLOC_ZERO() failed\n")); + talloc_destroy(mem_ctx); + goto cleanup; + } + + if ((tmp_list_ent->quotas=TALLOC_ZERO_P(mem_ctx,SMB_NTQUOTA_STRUCT))==NULL) { + DEBUG(0,("TALLOC_ZERO() failed\n")); + talloc_destroy(mem_ctx); + goto cleanup; + } + + memcpy(tmp_list_ent->quotas,&qt,sizeof(qt)); + tmp_list_ent->mem_ctx = mem_ctx; + + DLIST_ADD((*pqt_list),tmp_list_ent); + } + } + + + ret = True; + cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +bool cli_get_fs_quota_info(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_STRUCT *pqt) +{ + bool ret = False; + uint16 setup; + char param[2]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + SMB_NTQUOTA_STRUCT qt; + ZERO_STRUCT(qt); + + if (!cli||!pqt) { + smb_panic("cli_get_fs_quota_info() called with NULL Pointer!"); + } + + setup = TRANSACT2_QFSINFO; + + SSVAL(param,0,SMB_FS_QUOTA_INFORMATION); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 2, 0, + NULL, 0, 560)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + + if (rdata_count < 48) { + goto cleanup; + } + + /* unknown_1 24 NULL bytes in pdata*/ + + /* the soft quotas 8 bytes (SMB_BIG_UINT)*/ + qt.softlim = (SMB_BIG_UINT)IVAL(rdata,24); +#ifdef LARGE_SMB_OFF_T + qt.softlim |= (((SMB_BIG_UINT)IVAL(rdata,28)) << 32); +#else /* LARGE_SMB_OFF_T */ + if ((IVAL(rdata,28) != 0)&& + ((qt.softlim != 0xFFFFFFFF)|| + (IVAL(rdata,28)!=0xFFFFFFFF))) { + /* more than 32 bits? */ + goto cleanup; + } +#endif /* LARGE_SMB_OFF_T */ + + /* the hard quotas 8 bytes (SMB_BIG_UINT)*/ + qt.hardlim = (SMB_BIG_UINT)IVAL(rdata,32); +#ifdef LARGE_SMB_OFF_T + qt.hardlim |= (((SMB_BIG_UINT)IVAL(rdata,36)) << 32); +#else /* LARGE_SMB_OFF_T */ + if ((IVAL(rdata,36) != 0)&& + ((qt.hardlim != 0xFFFFFFFF)|| + (IVAL(rdata,36)!=0xFFFFFFFF))) { + /* more than 32 bits? */ + goto cleanup; + } +#endif /* LARGE_SMB_OFF_T */ + + /* quota_flags 2 bytes **/ + qt.qflags = SVAL(rdata,40); + + qt.qtype = SMB_USER_FS_QUOTA_TYPE; + + *pqt = qt; + + ret = True; +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +bool cli_set_fs_quota_info(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_STRUCT *pqt) +{ + bool ret = False; + uint16 setup; + char param[4]; + char data[48]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + SMB_NTQUOTA_STRUCT qt; + ZERO_STRUCT(qt); + memset(data,'\0',48); + + if (!cli||!pqt) { + smb_panic("cli_set_fs_quota_info() called with NULL Pointer!"); + } + + setup = TRANSACT2_SETFSINFO; + + SSVAL(param,0,quota_fnum); + SSVAL(param,2,SMB_FS_QUOTA_INFORMATION); + + /* Unknown1 24 NULL bytes*/ + + /* Default Soft Quota 8 bytes */ + SBIG_UINT(data,24,pqt->softlim); + + /* Default Hard Quota 8 bytes */ + SBIG_UINT(data,32,pqt->hardlim); + + /* Quota flag 2 bytes */ + SSVAL(data,40,pqt->qflags); + + /* Unknown3 6 NULL bytes */ + + if (!cli_send_trans(cli, SMBtrans2, + NULL, + 0, 0, + &setup, 1, 0, + param, 4, 0, + data, 48, 0)) { + goto cleanup; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + goto cleanup; + } + + if (cli_is_error(cli)) { + ret = False; + goto cleanup; + } else { + ret = True; + } + +cleanup: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return ret; +} + +static const char *quota_str_static(SMB_BIG_UINT val, bool special, bool _numeric) +{ + const char *result; + + if (!_numeric&&special&&(val == SMB_NTQUOTAS_NO_LIMIT)) { + return "NO LIMIT"; + } +#if defined(HAVE_LONGLONG) + result = talloc_asprintf(talloc_tos(), "%llu", val); +#else + result = talloc_asprintf(talloc_tos(), "%lu", val); +#endif + SMB_ASSERT(result != NULL); + return result; +} + +void dump_ntquota(SMB_NTQUOTA_STRUCT *qt, bool _verbose, bool _numeric, void (*_sidtostring)(fstring str, DOM_SID *sid, bool _numeric)) +{ + TALLOC_CTX *frame = talloc_stackframe(); + + if (!qt) { + smb_panic("dump_ntquota() called with NULL pointer"); + } + + switch (qt->qtype) { + case SMB_USER_FS_QUOTA_TYPE: + { + d_printf("File System QUOTAS:\n"); + d_printf("Limits:\n"); + d_printf(" Default Soft Limit: %15s\n",quota_str_static(qt->softlim,True,_numeric)); + d_printf(" Default Hard Limit: %15s\n",quota_str_static(qt->hardlim,True,_numeric)); + d_printf("Quota Flags:\n"); + d_printf(" Quotas Enabled: %s\n", + ((qt->qflags"AS_ENABLED)||(qt->qflags"AS_DENY_DISK))?"On":"Off"); + d_printf(" Deny Disk: %s\n",(qt->qflags"AS_DENY_DISK)?"On":"Off"); + d_printf(" Log Soft Limit: %s\n",(qt->qflags"AS_LOG_THRESHOLD)?"On":"Off"); + d_printf(" Log Hard Limit: %s\n",(qt->qflags"AS_LOG_LIMIT)?"On":"Off"); + } + break; + case SMB_USER_QUOTA_TYPE: + { + fstring username_str = {0}; + + if (_sidtostring) { + _sidtostring(username_str,&qt->sid,_numeric); + } else { + sid_to_fstring(username_str, &qt->sid); + } + + if (_verbose) { + d_printf("Quotas for User: %s\n",username_str); + d_printf("Used Space: %15s\n",quota_str_static(qt->usedspace,False,_numeric)); + d_printf("Soft Limit: %15s\n",quota_str_static(qt->softlim,True,_numeric)); + d_printf("Hard Limit: %15s\n",quota_str_static(qt->hardlim,True,_numeric)); + } else { + d_printf("%-30s: ",username_str); + d_printf("%15s/",quota_str_static(qt->usedspace,False,_numeric)); + d_printf("%15s/",quota_str_static(qt->softlim,True,_numeric)); + d_printf("%15s\n",quota_str_static(qt->hardlim,True,_numeric)); + } + } + break; + default: + d_printf("dump_ntquota() invalid qtype(%d)\n",qt->qtype); + } + TALLOC_FREE(frame); + return; +} + +void dump_ntquota_list(SMB_NTQUOTA_LIST **qtl, bool _verbose, bool _numeric, void (*_sidtostring)(fstring str, DOM_SID *sid, bool _numeric)) +{ + SMB_NTQUOTA_LIST *cur; + + for (cur = *qtl;cur;cur = cur->next) { + if (cur->quotas) + dump_ntquota(cur->quotas,_verbose,_numeric,_sidtostring); + } +} diff --git a/source3/libsmb/clirap.c b/source3/libsmb/clirap.c new file mode 100644 index 0000000000..eee8636fdd --- /dev/null +++ b/source3/libsmb/clirap.c @@ -0,0 +1,1264 @@ +/* + Unix SMB/CIFS implementation. + client RAP calls + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Gerald (Jerry) Carter 2004 + Copyright (C) James Peach 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Call a remote api on an arbitrary pipe. takes param, data and setup buffers. +****************************************************************************/ + +bool cli_api_pipe(struct cli_state *cli, const char *pipe_name, + uint16 *setup, uint32 setup_count, uint32 max_setup_count, + char *params, uint32 param_count, uint32 max_param_count, + char *data, uint32 data_count, uint32 max_data_count, + char **rparam, uint32 *rparam_count, + char **rdata, uint32 *rdata_count) +{ + cli_send_trans(cli, SMBtrans, + pipe_name, + 0,0, /* fid, flags */ + setup, setup_count, max_setup_count, + params, param_count, max_param_count, + data, data_count, max_data_count); + + return (cli_receive_trans(cli, SMBtrans, + rparam, (unsigned int *)rparam_count, + rdata, (unsigned int *)rdata_count)); +} + +/**************************************************************************** + Call a remote api +****************************************************************************/ + +bool cli_api(struct cli_state *cli, + char *param, int prcnt, int mprcnt, + char *data, int drcnt, int mdrcnt, + char **rparam, unsigned int *rprcnt, + char **rdata, unsigned int *rdrcnt) +{ + cli_send_trans(cli,SMBtrans, + PIPE_LANMAN, /* Name */ + 0,0, /* fid, flags */ + NULL,0,0, /* Setup, length, max */ + param, prcnt, mprcnt, /* Params, length, max */ + data, drcnt, mdrcnt /* Data, length, max */ + ); + + return (cli_receive_trans(cli,SMBtrans, + rparam, rprcnt, + rdata, rdrcnt)); +} + +/**************************************************************************** + Perform a NetWkstaUserLogon. +****************************************************************************/ + +bool cli_NetWkstaUserLogon(struct cli_state *cli,char *user, char *workstation) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + char param[1024]; + + memset(param, 0, sizeof(param)); + + /* send a SMBtrans command with api NetWkstaUserLogon */ + p = param; + SSVAL(p,0,132); /* api number */ + p += 2; + strlcpy(p,"OOWb54WrLh",sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + strlcpy(p,"WB21BWDWWDDDDDDDzzzD",sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + SSVAL(p,0,1); + p += 2; + strlcpy(p,user,sizeof(param)-PTR_DIFF(p,param)); + strupper_m(p); + p += 21; + p++; + p += 15; + p++; + strlcpy(p, workstation,sizeof(param)-PTR_DIFF(p,param)); + strupper_m(p); + p += 16; + SSVAL(p, 0, CLI_BUFFER_SIZE); + p += 2; + SSVAL(p, 0, CLI_BUFFER_SIZE); + p += 2; + + if (cli_api(cli, + param, PTR_DIFF(p,param),1024, /* param, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + cli->rap_error = rparam? SVAL(rparam,0) : -1; + p = rdata; + + if (cli->rap_error == 0) { + DEBUG(4,("NetWkstaUserLogon success\n")); + cli->privileges = SVAL(p, 24); + /* The cli->eff_name field used to be set here + but it wasn't used anywhere else. */ + } else { + DEBUG(1,("NetwkstaUserLogon gave error %d\n", cli->rap_error)); + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return (cli->rap_error == 0); +} + +/**************************************************************************** + Call a NetShareEnum - try and browse available connections on a host. +****************************************************************************/ + +int cli_RNetShareEnum(struct cli_state *cli, void (*fn)(const char *, uint32, const char *, void *), void *state) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + char param[1024]; + int count = -1; + + /* now send a SMBtrans command with api RNetShareEnum */ + p = param; + SSVAL(p,0,0); /* api number */ + p += 2; + strlcpy(p,"WrLeh",sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + strlcpy(p,"B13BWz",sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + SSVAL(p,0,1); + /* + * Win2k needs a *smaller* buffer than 0xFFFF here - + * it returns "out of server memory" with 0xFFFF !!! JRA. + */ + SSVAL(p,2,0xFFE0); + p += 4; + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 0xFFE0, /* data, length, maxlen - Win2k needs a small buffer here too ! */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + int res = rparam? SVAL(rparam,0) : -1; + + if (res == 0 || res == ERRmoredata) { + int converter=SVAL(rparam,2); + int i; + char *rdata_end = rdata + rdrcnt; + + count=SVAL(rparam,4); + p = rdata; + + for (i=0;i<count;i++,p+=20) { + char *sname; + int type; + int comment_offset; + const char *cmnt; + const char *p1; + char *s1, *s2; + size_t len; + TALLOC_CTX *frame = talloc_stackframe(); + + if (p + 20 > rdata_end) { + TALLOC_FREE(frame); + break; + } + + sname = p; + type = SVAL(p,14); + comment_offset = (IVAL(p,16) & 0xFFFF) - converter; + if (comment_offset < 0 || + comment_offset > (int)rdrcnt) { + TALLOC_FREE(frame); + break; + } + cmnt = comment_offset?(rdata+comment_offset):""; + + /* Work out the comment length. */ + for (p1 = cmnt, len = 0; *p1 && + p1 < rdata_end; len++) + p1++; + if (!*p1) { + len++; + } + pull_string_talloc(frame,rdata,0, + &s1,sname,14,STR_ASCII); + pull_string_talloc(frame,rdata,0, + &s2,cmnt,len,STR_ASCII); + if (!s1 || !s2) { + TALLOC_FREE(frame); + continue; + } + + fn(s1, type, s2, state); + + TALLOC_FREE(frame); + } + } else { + DEBUG(4,("NetShareEnum res=%d\n", res)); + } + } else { + DEBUG(4,("NetShareEnum failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return count; +} + +/**************************************************************************** + Call a NetServerEnum for the specified workgroup and servertype mask. This + function then calls the specified callback function for each name returned. + + The callback function takes 4 arguments: the machine name, the server type, + the comment and a state pointer. +****************************************************************************/ + +bool cli_NetServerEnum(struct cli_state *cli, char *workgroup, uint32 stype, + void (*fn)(const char *, uint32, const char *, void *), + void *state) +{ + char *rparam = NULL; + char *rdata = NULL; + char *rdata_end = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[1024]; + int uLevel = 1; + size_t len; + uint32 func = RAP_NetServerEnum2; + char *last_entry = NULL; + int total_cnt = 0; + int return_cnt = 0; + int res; + + errno = 0; /* reset */ + + /* + * This may take more than one transaction, so we should loop until + * we no longer get a more data to process or we have all of the + * items. + */ + do { + /* send a SMBtrans command with api NetServerEnum */ + p = param; + SIVAL(p,0,func); /* api number */ + p += 2; + /* Next time through we need to use the continue api */ + func = RAP_NetServerEnum3; + + if (last_entry) { + strlcpy(p,"WrLehDOz", sizeof(param)-PTR_DIFF(p,param)); + } else { + strlcpy(p,"WrLehDz", sizeof(param)-PTR_DIFF(p,param)); + } + + p = skip_string(param, sizeof(param), p); + strlcpy(p,"B16BBDz", sizeof(param)-PTR_DIFF(p,param)); + + p = skip_string(param, sizeof(param), p); + SSVAL(p,0,uLevel); + SSVAL(p,2,CLI_BUFFER_SIZE); + p += 4; + SIVAL(p,0,stype); + p += 4; + + /* If we have more data, tell the server where + * to continue from. + */ + len = push_ascii(p, + last_entry ? last_entry : workgroup, + sizeof(param) - PTR_DIFF(p,param) - 1, + STR_TERMINATE|STR_UPPER); + + if (len == (size_t)-1) { + SAFE_FREE(last_entry); + return false; + } + p += len; + + if (!cli_api(cli, + param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt)) { /* return data, return size */ + + /* break out of the loop on error */ + res = -1; + break; + } + + rdata_end = rdata + rdrcnt; + res = rparam ? SVAL(rparam,0) : -1; + + if (res == 0 || res == ERRmoredata || + (res != -1 && cli_errno(cli) == 0)) { + char *sname = NULL; + int i, count; + int converter=SVAL(rparam,2); + + /* Get the number of items returned in this buffer */ + count = SVAL(rparam, 4); + + /* The next field contains the number of items left, + * including those returned in this buffer. So the + * first time through this should contain all of the + * entries. + */ + if (total_cnt == 0) { + total_cnt = SVAL(rparam, 6); + } + + /* Keep track of how many we have read */ + return_cnt += count; + p = rdata; + + /* The last name in the previous NetServerEnum reply is + * sent back to server in the NetServerEnum3 request + * (last_entry). The next reply should repeat this entry + * as the first element. We have no proof that this is + * always true, but from traces that seems to be the + * behavior from Window Servers. So first lets do a lot + * of checking, just being paranoid. If the string + * matches then we already saw this entry so skip it. + * + * NOTE: sv1_name field must be null terminated and has + * a max size of 16 (NetBIOS Name). + */ + if (last_entry && count && p && + (strncmp(last_entry, p, 16) == 0)) { + count -= 1; /* Skip this entry */ + return_cnt = -1; /* Not part of total, so don't count. */ + p = rdata + 26; /* Skip the whole record */ + } + + for (i = 0; i < count; i++, p += 26) { + int comment_offset; + const char *cmnt; + const char *p1; + char *s1, *s2; + TALLOC_CTX *frame = talloc_stackframe(); + + if (p + 26 > rdata_end) { + TALLOC_FREE(frame); + break; + } + + sname = p; + comment_offset = (IVAL(p,22) & 0xFFFF)-converter; + cmnt = comment_offset?(rdata+comment_offset):""; + + if (comment_offset < 0 || comment_offset > (int)rdrcnt) { + TALLOC_FREE(frame); + continue; + } + + /* Work out the comment length. */ + for (p1 = cmnt, len = 0; *p1 && + p1 < rdata_end; len++) + p1++; + if (!*p1) { + len++; + } + + stype = IVAL(p,18) & ~SV_TYPE_LOCAL_LIST_ONLY; + + pull_string_talloc(frame,rdata,0, + &s1,sname,16,STR_ASCII); + pull_string_talloc(frame,rdata,0, + &s2,cmnt,len,STR_ASCII); + + if (!s1 || !s2) { + TALLOC_FREE(frame); + continue; + } + + fn(s1, stype, s2, state); + TALLOC_FREE(frame); + } + + /* We are done with the old last entry, so now we can free it */ + if (last_entry) { + SAFE_FREE(last_entry); /* This will set it to null */ + } + + /* We always make a copy of the last entry if we have one */ + if (sname) { + last_entry = smb_xstrdup(sname); + } + + /* If we have more data, but no last entry then error out */ + if (!last_entry && (res == ERRmoredata)) { + errno = EINVAL; + res = 0; + } + + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + } while ((res == ERRmoredata) && (total_cnt > return_cnt)); + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + SAFE_FREE(last_entry); + + if (res == -1) { + errno = cli_errno(cli); + } else { + if (!return_cnt) { + /* this is a very special case, when the domain master for the + work group isn't part of the work group itself, there is something + wild going on */ + errno = ENOENT; + } + } + + return(return_cnt > 0); +} + +/**************************************************************************** + Send a SamOEMChangePassword command. +****************************************************************************/ + +bool cli_oem_change_password(struct cli_state *cli, const char *user, const char *new_password, + const char *old_password) +{ + char param[1024]; + unsigned char data[532]; + char *p = param; + unsigned char old_pw_hash[16]; + unsigned char new_pw_hash[16]; + unsigned int data_len; + unsigned int param_len = 0; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + + if (strlen(user) >= sizeof(fstring)-1) { + DEBUG(0,("cli_oem_change_password: user name %s is too long.\n", user)); + return False; + } + + SSVAL(p,0,214); /* SamOEMChangePassword command. */ + p += 2; + strlcpy(p, "zsT", sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + strlcpy(p, "B516B16", sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + strlcpy(p,user, sizeof(param)-PTR_DIFF(p,param)); + p = skip_string(param,sizeof(param),p); + SSVAL(p,0,532); + p += 2; + + param_len = PTR_DIFF(p,param); + + /* + * Get the Lanman hash of the old password, we + * use this as the key to make_oem_passwd_hash(). + */ + E_deshash(old_password, old_pw_hash); + + encode_pw_buffer(data, new_password, STR_ASCII); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("make_oem_passwd_hash\n")); + dump_data(100, data, 516); +#endif + SamOEMhash( (unsigned char *)data, (unsigned char *)old_pw_hash, 516); + + /* + * Now place the old password hash in the data. + */ + E_deshash(new_password, new_pw_hash); + + E_old_pw_hash( new_pw_hash, old_pw_hash, (uchar *)&data[516]); + + data_len = 532; + + if (cli_send_trans(cli,SMBtrans, + PIPE_LANMAN, /* name */ + 0,0, /* fid, flags */ + NULL,0,0, /* setup, length, max */ + param,param_len,2, /* param, length, max */ + (char *)data,data_len,0 /* data, length, max */ + ) == False) { + DEBUG(0,("cli_oem_change_password: Failed to send password change for user %s\n", + user )); + return False; + } + + if (!cli_receive_trans(cli,SMBtrans, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + DEBUG(0,("cli_oem_change_password: Failed to recieve reply to password change for user %s\n", + user )); + return False; + } + + if (rparam) { + cli->rap_error = SVAL(rparam,0); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return (cli->rap_error == 0); +} + +/**************************************************************************** + Send a qpathinfo call. +****************************************************************************/ + +bool cli_qpathinfo(struct cli_state *cli, + const char *fname, + time_t *change_time, + time_t *access_time, + time_t *write_time, + SMB_OFF_T *size, + uint16 *mode) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + unsigned int rparam_len, rdata_len; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + char *rparam=NULL, *rdata=NULL; + int count=8; + bool ret; + time_t (*date_fn)(struct cli_state *, const void *); + char *p; + size_t nlen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + p = param; + memset(p, '\0', 6); + SSVAL(p, 0, SMB_INFO_STANDARD); + p += 6; + p += clistr_push(cli, p, fname, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + do { + ret = (cli_send_trans(cli, SMBtrans2, + NULL, /* Name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + ) && + cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_len, + &rdata, &rdata_len)); + if (!cli_is_dos_error(cli)) break; + if (!ret) { + /* we need to work around a Win95 bug - sometimes + it gives ERRSRV/ERRerror temprarily */ + uint8 eclass; + uint32 ecode; + cli_dos_error(cli, &eclass, &ecode); + if (eclass != ERRSRV || ecode != ERRerror) break; + smb_msleep(100); + } + } while (count-- && ret==False); + + SAFE_FREE(param); + if (!ret || !rdata || rdata_len < 22) { + return False; + } + + if (cli->win95) { + date_fn = cli_make_unix_date; + } else { + date_fn = cli_make_unix_date2; + } + + if (change_time) { + *change_time = date_fn(cli, rdata+0); + } + if (access_time) { + *access_time = date_fn(cli, rdata+4); + } + if (write_time) { + *write_time = date_fn(cli, rdata+8); + } + if (size) { + *size = IVAL(rdata, 12); + } + if (mode) { + *mode = SVAL(rdata,l1_attrFile); + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return True; +} + +/**************************************************************************** + Send a setpathinfo call. +****************************************************************************/ + +bool cli_setpathinfo(struct cli_state *cli, const char *fname, + time_t create_time, + time_t access_time, + time_t write_time, + time_t change_time, + uint16 mode) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + unsigned int rparam_len, rdata_len; + uint16 setup = TRANSACT2_SETPATHINFO; + char *param; + char data[40]; + char *rparam=NULL, *rdata=NULL; + int count=8; + bool ret; + char *p; + size_t nlen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + memset(param, '\0', 6); + memset(data, 0, sizeof(data)); + + p = param; + + /* Add the information level */ + SSVAL(p, 0, SMB_FILE_BASIC_INFORMATION); + + /* Skip reserved */ + p += 6; + + /* Add the file name */ + p += clistr_push(cli, p, fname, nlen, STR_TERMINATE); + + param_len = PTR_DIFF(p, param); + + p = data; + + /* + * Add the create, last access, modification, and status change times + */ + put_long_date(p, create_time); + p += 8; + + put_long_date(p, access_time); + p += 8; + + put_long_date(p, write_time); + p += 8; + + put_long_date(p, change_time); + p += 8; + + /* Add attributes */ + SIVAL(p, 0, mode); + p += 4; + + /* Add padding */ + SIVAL(p, 0, 0); + p += 4; + + data_len = PTR_DIFF(p, data); + + do { + ret = (cli_send_trans(cli, SMBtrans2, + NULL, /* Name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + data, data_len, cli->max_xmit /* data, length, max */ + ) && + cli_receive_trans(cli, SMBtrans2, + &rparam, &rparam_len, + &rdata, &rdata_len)); + if (!cli_is_dos_error(cli)) break; + if (!ret) { + /* we need to work around a Win95 bug - sometimes + it gives ERRSRV/ERRerror temprarily */ + uint8 eclass; + uint32 ecode; + cli_dos_error(cli, &eclass, &ecode); + if (eclass != ERRSRV || ecode != ERRerror) break; + smb_msleep(100); + } + } while (count-- && ret==False); + + SAFE_FREE(param); + if (!ret) { + return False; + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return True; +} + +/**************************************************************************** + Send a qpathinfo call with the SMB_QUERY_FILE_ALL_INFO info level. +****************************************************************************/ + +bool cli_qpathinfo2(struct cli_state *cli, const char *fname, + struct timespec *create_time, + struct timespec *access_time, + struct timespec *write_time, + struct timespec *change_time, + SMB_OFF_T *size, uint16 *mode, + SMB_INO_T *ino) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + char *rparam=NULL, *rdata=NULL; + char *p; + size_t nlen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return false; + } + p = param; + memset(param, '\0', 6); + SSVAL(p, 0, SMB_QUERY_FILE_ALL_INFO); + p += 6; + p += clistr_push(cli, p, fname, nlen, STR_TERMINATE); + + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + )) { + SAFE_FREE(param); + return False; + } + + SAFE_FREE(param); + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return False; + } + + if (!rdata || data_len < 22) { + return False; + } + + if (create_time) { + *create_time = interpret_long_date(rdata+0); + } + if (access_time) { + *access_time = interpret_long_date(rdata+8); + } + if (write_time) { + *write_time = interpret_long_date(rdata+16); + } + if (change_time) { + *change_time = interpret_long_date(rdata+24); + } + if (mode) { + *mode = SVAL(rdata, 32); + } + if (size) { + *size = IVAL2_TO_SMB_BIG_UINT(rdata,48); + } + if (ino) { + *ino = IVAL(rdata, 64); + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return True; +} + +/**************************************************************************** + Get the stream info +****************************************************************************/ + +bool cli_qpathinfo_streams(struct cli_state *cli, const char *fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + char *rparam=NULL, *rdata=NULL; + char *p; + unsigned int num_streams; + struct stream_struct *streams; + unsigned int ofs; + size_t namelen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+namelen+2); + if (param == NULL) { + return false; + } + p = param; + memset(p, 0, 6); + SSVAL(p, 0, SMB_FILE_STREAM_INFORMATION); + p += 6; + p += clistr_push(cli, p, fname, namelen, STR_TERMINATE); + + param_len = PTR_DIFF(p, param); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, len, max */ + param, param_len, 10, /* param, len, max */ + NULL, data_len, cli->max_xmit /* data, len, max */ + )) { + return false; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return false; + } + + if (!rdata) { + SAFE_FREE(rparam); + return false; + } + + num_streams = 0; + streams = NULL; + ofs = 0; + + while ((data_len > ofs) && (data_len - ofs >= 24)) { + uint32_t nlen, len; + size_t size; + void *vstr; + struct stream_struct *tmp; + uint8_t *tmp_buf; + + tmp = TALLOC_REALLOC_ARRAY(mem_ctx, streams, + struct stream_struct, + num_streams+1); + + if (tmp == NULL) { + goto fail; + } + streams = tmp; + + nlen = IVAL(rdata, ofs + 0x04); + + streams[num_streams].size = IVAL_TO_SMB_OFF_T( + rdata, ofs + 0x08); + streams[num_streams].alloc_size = IVAL_TO_SMB_OFF_T( + rdata, ofs + 0x10); + + if (nlen > data_len - (ofs + 24)) { + goto fail; + } + + /* + * We need to null-terminate src, how do I do this with + * convert_string_talloc?? + */ + + tmp_buf = TALLOC_ARRAY(streams, uint8_t, nlen+2); + if (tmp_buf == NULL) { + goto fail; + } + + memcpy(tmp_buf, rdata+ofs+24, nlen); + tmp_buf[nlen] = 0; + tmp_buf[nlen+1] = 0; + + if (!convert_string_talloc(streams, CH_UTF16, CH_UNIX, tmp_buf, + nlen+2, &vstr, &size, false)) + { + TALLOC_FREE(tmp_buf); + goto fail; + } + + TALLOC_FREE(tmp_buf); + streams[num_streams].name = (char *)vstr; + num_streams++; + + len = IVAL(rdata, ofs); + if (len > data_len - ofs) { + goto fail; + } + if (len == 0) break; + ofs += len; + } + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + *pnum_streams = num_streams; + *pstreams = streams; + return true; + + fail: + TALLOC_FREE(streams); + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return false; +} + +/**************************************************************************** + Send a qfileinfo QUERY_FILE_NAME_INFO call. +****************************************************************************/ + +bool cli_qfilename(struct cli_state *cli, int fnum, char *name, size_t namelen) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_QFILEINFO; + char param[4]; + char *rparam=NULL, *rdata=NULL; + + param_len = 4; + SSVAL(param, 0, fnum); + SSVAL(param, 2, SMB_QUERY_FILE_NAME_INFO); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + )) { + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return False; + } + + if (!rdata || data_len < 4) { + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return False; + } + + clistr_pull(cli, name, rdata+4, namelen, IVAL(rdata, 0), STR_UNICODE); + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return True; +} + +/**************************************************************************** + Send a qfileinfo call. +****************************************************************************/ + +bool cli_qfileinfo(struct cli_state *cli, int fnum, + uint16 *mode, SMB_OFF_T *size, + struct timespec *create_time, + struct timespec *access_time, + struct timespec *write_time, + struct timespec *change_time, + SMB_INO_T *ino) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup; + uint8_t param[4]; + uint8_t *rparam=NULL, *rdata=NULL; + NTSTATUS status; + + /* if its a win95 server then fail this - win95 totally screws it + up */ + if (cli->win95) return False; + + param_len = 4; + + SSVAL(param, 0, fnum); + SSVAL(param, 2, SMB_QUERY_FILE_ALL_INFO); + + SSVAL(&setup, 0, TRANSACT2_QFILEINFO); + + status = cli_trans(talloc_tos(), cli, SMBtrans2, + NULL, -1, 0, 0, /* name, fid, function, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, 0, MIN(cli->max_xmit, 0xffff), /* data, length, max */ + NULL, NULL, /* rsetup, length */ + &rparam, ¶m_len, /* rparam, length */ + &rdata, &data_len); + + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + if (!rdata || data_len < 68) { + return False; + } + + if (create_time) { + *create_time = interpret_long_date((char *)rdata+0); + } + if (access_time) { + *access_time = interpret_long_date((char *)rdata+8); + } + if (write_time) { + *write_time = interpret_long_date((char *)rdata+16); + } + if (change_time) { + *change_time = interpret_long_date((char *)rdata+24); + } + if (mode) { + *mode = SVAL(rdata, 32); + } + if (size) { + *size = IVAL2_TO_SMB_BIG_UINT(rdata,48); + } + if (ino) { + *ino = IVAL(rdata, 64); + } + + TALLOC_FREE(rdata); + TALLOC_FREE(rparam); + return True; +} + +/**************************************************************************** + Send a qpathinfo BASIC_INFO call. +****************************************************************************/ + +bool cli_qpathinfo_basic( struct cli_state *cli, const char *name, + SMB_STRUCT_STAT *sbuf, uint32 *attributes ) +{ + unsigned int param_len = 0; + unsigned int data_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + char *rparam=NULL, *rdata=NULL; + char *p; + char *path; + int len; + size_t nlen; + TALLOC_CTX *frame = talloc_stackframe(); + + path = talloc_strdup(frame, name); + if (!path) { + TALLOC_FREE(frame); + return false; + } + /* cleanup */ + + len = strlen(path); + if ( path[len-1] == '\\' || path[len-1] == '/') { + path[len-1] = '\0'; + } + nlen = 2*(strlen(path)+1); + + param = TALLOC_ARRAY(frame,char,6+nlen+2); + if (!param) { + return false; + } + p = param; + memset(param, '\0', 6); + + SSVAL(p, 0, SMB_QUERY_FILE_BASIC_INFO); + p += 6; + p += clistr_push(cli, p, path, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, 0, cli->max_xmit /* data, length, max */ + )) { + TALLOC_FREE(frame); + return False; + } + + TALLOC_FREE(frame); + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return False; + } + + if (data_len < 36) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return False; + } + + set_atimespec(sbuf, interpret_long_date( rdata+8 )); /* Access time. */ + set_mtimespec(sbuf, interpret_long_date( rdata+16 )); /* Write time. */ + set_ctimespec(sbuf, interpret_long_date( rdata+24 )); /* Change time. */ + + *attributes = IVAL( rdata, 32 ); + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return True; +} + +/**************************************************************************** + Send a qfileinfo call. +****************************************************************************/ + +bool cli_qfileinfo_test(struct cli_state *cli, int fnum, int level, char **poutdata, uint32 *poutlen) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_QFILEINFO; + char param[4]; + char *rparam=NULL, *rdata=NULL; + + *poutdata = NULL; + *poutlen = 0; + + /* if its a win95 server then fail this - win95 totally screws it + up */ + if (cli->win95) + return False; + + param_len = 4; + + SSVAL(param, 0, fnum); + SSVAL(param, 2, level); + + if (!cli_send_trans(cli, SMBtrans2, + NULL, /* name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 2, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + )) { + return False; + } + + if (!cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)) { + return False; + } + + *poutdata = (char *)memdup(rdata, data_len); + if (!*poutdata) { + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return False; + } + + *poutlen = data_len; + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return True; +} + +/**************************************************************************** + Send a qpathinfo SMB_QUERY_FILE_ALT_NAME_INFO call. +****************************************************************************/ + +NTSTATUS cli_qpathinfo_alt_name(struct cli_state *cli, const char *fname, fstring alt_name) +{ + unsigned int data_len = 0; + unsigned int param_len = 0; + uint16 setup = TRANSACT2_QPATHINFO; + char *param; + char *rparam=NULL, *rdata=NULL; + int count=8; + char *p; + bool ret; + unsigned int len; + size_t nlen = 2*(strlen(fname)+1); + + param = SMB_MALLOC_ARRAY(char, 6+nlen+2); + if (!param) { + return NT_STATUS_NO_MEMORY; + } + p = param; + memset(param, '\0', 6); + SSVAL(p, 0, SMB_QUERY_FILE_ALT_NAME_INFO); + p += 6; + p += clistr_push(cli, p, fname, nlen, STR_TERMINATE); + param_len = PTR_DIFF(p, param); + + do { + ret = (cli_send_trans(cli, SMBtrans2, + NULL, /* Name */ + -1, 0, /* fid, flags */ + &setup, 1, 0, /* setup, length, max */ + param, param_len, 10, /* param, length, max */ + NULL, data_len, cli->max_xmit /* data, length, max */ + ) && + cli_receive_trans(cli, SMBtrans2, + &rparam, ¶m_len, + &rdata, &data_len)); + if (!ret && cli_is_dos_error(cli)) { + /* we need to work around a Win95 bug - sometimes + it gives ERRSRV/ERRerror temprarily */ + uint8 eclass; + uint32 ecode; + cli_dos_error(cli, &eclass, &ecode); + if (eclass != ERRSRV || ecode != ERRerror) break; + smb_msleep(100); + } + } while (count-- && ret==False); + + SAFE_FREE(param); + + if (!ret || !rdata || data_len < 4) { + return NT_STATUS_UNSUCCESSFUL; + } + + len = IVAL(rdata, 0); + + if (len > data_len - 4) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + clistr_pull(cli, alt_name, rdata+4, sizeof(fstring), len, STR_UNICODE); + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + + return NT_STATUS_OK; +} diff --git a/source3/libsmb/clirap2.c b/source3/libsmb/clirap2.c new file mode 100644 index 0000000000..a15fa5f7d8 --- /dev/null +++ b/source3/libsmb/clirap2.c @@ -0,0 +1,2675 @@ +/* + Samba Unix/Linux SMB client library + More client RAP (SMB Remote Procedure Calls) functions + Copyright (C) 2001 Steve French (sfrench@us.ibm.com) + Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com) + Copyright (C) 2007 Jeremy Allison. jra@samba.org + + 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 <http://www.gnu.org/licenses/>. +*/ + +/*****************************************************/ +/* */ +/* Additional RAP functionality */ +/* */ +/* RAP is the original SMB RPC, documented */ +/* by Microsoft and X/Open in the 1990s and */ +/* supported by most SMB/CIFS servers although */ +/* it is unlikely that any one implementation */ +/* supports all RAP command codes since some */ +/* are quite obsolete and a few are specific */ +/* to a particular network operating system */ +/* */ +/* Although it has largely been replaced */ +/* for complex remote admistration and management */ +/* (of servers) by the relatively newer */ +/* DCE/RPC based remote API (which better handles */ +/* large >64K data structures), there are many */ +/* important administrative and resource location */ +/* tasks and user tasks (e.g. password change) */ +/* that are performed via RAP. */ +/* */ +/* Although a few of the RAP calls are implemented */ +/* in the Samba client library already (clirap.c) */ +/* the new ones are in clirap2.c for easy patching */ +/* and integration and a corresponding header */ +/* file, rap.h, has been created. */ +/* */ +/* This is based on data from the CIFS spec */ +/* and the LAN Server and LAN Manager */ +/* Programming Reference books and published */ +/* RAP document and CIFS forum postings and */ +/* lots of trial and error */ +/* */ +/* Function names changed from API_ (as they are */ +/* in the CIFS specification) to RAP_ in order */ +/* to avoid confusion with other API calls */ +/* sent via DCE RPC */ +/* */ +/*****************************************************/ + +/*****************************************************/ +/* */ +/* cifsrap.c already includes support for: */ +/* */ +/* WshareEnum ( API number 0, level 1) */ +/* NetServerEnum2 (API num 104, level 1) */ +/* WWkstaUserLogon (132) */ +/* SamOEMchgPasswordUser2_P (214) */ +/* */ +/* cifsprint.c already includes support for: */ +/* */ +/* WPrintJobEnum (API num 76, level 2) */ +/* WPrintJobDel (API num 81) */ +/* */ +/*****************************************************/ + +#include "includes.h" + +#define WORDSIZE 2 +#define DWORDSIZE 4 + +#define PUTBYTE(p,b) do {SCVAL(p,0,b); p++;} while(0) + +#define GETBYTE(p,b,endp) \ + do {\ + if (p+1 < endp) {\ + b = CVAL(p,0);\ + }\ + p++;\ + } while(0) + +#define PUTWORD(p,w) do {SSVAL(p,0,w); p += WORDSIZE;} while(0) + +#define GETWORD(p,w,endp) \ + do {\ + if (p+WORDSIZE < endp) {\ + w = SVAL(p,0);\ + }\ + p += WORDSIZE;\ + } while(0) + +#define PUTDWORD(p,d) do {SIVAL(p,0,d); p += DWORDSIZE;} while(0) + +#define GETDWORD(p,d,endp) \ + do {\ + if (p+DWORDSIZE < endp) {\ + d = IVAL(p,0);\ + }\ + p += DWORDSIZE;\ + } while(0) + +#define GETRES(p,endp) ((p && p+2 < endp) ? SVAL(p,0) : -1) + +/* put string s at p with max len n and increment p past string */ +#define PUTSTRING(p,s,n) \ + do {\ + push_ascii(p,s?s:"",n?n:256,STR_TERMINATE);\ + p = push_skip_string(p);\ + } while(0) + +/* put string s and p, using fixed len l, and increment p by l */ +#define PUTSTRINGF(p,s,l) \ + do {\ + push_ascii(p,s?s:"",l,STR_TERMINATE);\ + p += l;\ + } while (0) + +/* put string pointer at p, supplying offset o from rdata r, store */ +/* dword offset at p, increment p by 4 and o by length of s. This */ +/* means on the first call, you must calc the offset yourself! */ + +#define PUTSTRINGP(p,s,r,o) \ + do {\ + if (s) {\ + push_ascii(r+o,s,strlen(s)+1,STR_TERMINATE);\ + PUTDWORD(p,o);\ + o += strlen(s) + 1;\ + } else {\ + PUTDWORD(p,0);\ + }\ + }while(0); + +/* get asciiz string dest from src, return increment past string */ + +static size_t rap_getstring(TALLOC_CTX *ctx, char *src, char **dest, const char *endp) +{ + char *p1; + size_t len; + + *dest = NULL; + for (p1 = src, len = 0; *p1 && p1 < endp; len++) + p1++; + if (!*p1) { + len++; + } + pull_string_talloc(ctx,src,0,dest,src,len,STR_ASCII); + return len; +} + +/* get fixed length l string dest from src, return increment for src */ + +static size_t rap_getstringf(char *src, char *dest, size_t l, size_t dlen, char *endp) +{ + char *p1; + size_t len; + + if (dlen) { + dest[0] = '\0'; + } + for (p1 = src, len = 0; *p1 && p1 < endp; len++) { + p1++; + } + if (!*p1) { + len++; + } + if (len > l) { + len = l; + } + if (len) { + pull_ascii(dest,src,len,len,STR_ASCII); + } + return l; +} + +/* get string dest from offset (obtained at p) from rdata r - converter c */ +static size_t rap_getstringp(TALLOC_CTX *ctx, char *p, char **dest, char *r, uint16_t c, char *endp) +{ + uint32_t off = 0; + const char *src; + size_t len=0; + + *dest = NULL; + if (p+4 < endp) { + GETDWORD(p,off,endp); + off &= 0x0000FFFF; /* mask the obsolete segment number from the offset */ + off -= c; + } + if (r+off > endp || r+off < r) { + src=""; + len=1; + } else { + const char *p1; + src=r+off; + for (p1 = src, len = 0; *p1 && p1 < endp; len++) { + p1++; + } + if (!*p1) { + len++; + } + } + pull_string_talloc(ctx,src,0,dest,src,len,STR_ASCII); + return len; +} + +static char *make_header(char *param, uint16 apinum, const char *reqfmt, const char *datafmt) +{ + PUTWORD(param,apinum); + if (reqfmt) + PUTSTRING(param,reqfmt,0); + else + *param++ = (char) 0; + + if (datafmt) + PUTSTRING(param,datafmt,0); + else + *param++ = (char) 0; + + return param; +} + +/**************************************************************************** + call a NetGroupDelete - delete user group from remote server +****************************************************************************/ + +int cli_NetGroupDelete(struct cli_state *cli, const char *group_name) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupDel_REQ) /* parm string */ + +1 /* no ret string */ + +RAP_GROUPNAME_LEN /* group to del */ + +WORDSIZE]; /* reserved word */ + + /* now send a SMBtrans command with api GroupDel */ + p = make_header(param, RAP_WGroupDel, RAP_NetGroupDel_REQ, NULL); + PUTSTRING(p, group_name, RAP_GROUPNAME_LEN); + PUTWORD(p,0); /* reserved word MBZ on input */ + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + + if (res == 0) { + /* nothing to do */ + } else if ((res == 5) || (res == 65)) { + DEBUG(1, ("Access Denied\n")); + } else if (res == 2220) { + DEBUG (1, ("Group does not exist\n")); + } else { + DEBUG(4,("NetGroupDelete res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetGroupDelete failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + call a NetGroupAdd - add user group to remote server +****************************************************************************/ + +int cli_NetGroupAdd(struct cli_state *cli, RAP_GROUP_INFO_1 *grinfo) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupAdd_REQ) /* req string */ + +sizeof(RAP_GROUP_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* reserved word */ + + /* offset into data of free format strings. Will be updated */ + /* by PUTSTRINGP macro and end up with total data length. */ + int soffset = RAP_GROUPNAME_LEN + 1 + DWORDSIZE; + char *data; + size_t data_size; + + /* Allocate data. */ + data_size = MAX(soffset + strlen(grinfo->comment) + 1, 1024); + + data = SMB_MALLOC_ARRAY(char, data_size); + if (!data) { + DEBUG (1, ("Malloc fail\n")); + return -1; + } + + /* now send a SMBtrans command with api WGroupAdd */ + + p = make_header(param, RAP_WGroupAdd, + RAP_NetGroupAdd_REQ, RAP_GROUP_INFO_L1); + PUTWORD(p, 1); /* info level */ + PUTWORD(p, 0); /* reserved word 0 */ + + p = data; + PUTSTRINGF(p, grinfo->group_name, RAP_GROUPNAME_LEN); + PUTBYTE(p, 0); /* pad byte 0 */ + PUTSTRINGP(p, grinfo->comment, data, soffset); + + if (cli_api(cli, + param, sizeof(param), 1024, /* Param, length, maxlen */ + data, soffset, sizeof(data), /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + /* nothing to do */ + } else if ((res == 5) || (res == 65)) { + DEBUG(1, ("Access Denied\n")); + } else if (res == 2223) { + DEBUG (1, ("Group already exists\n")); + } else { + DEBUG(4,("NetGroupAdd res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetGroupAdd failed\n")); + } + + SAFE_FREE(data); + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetGroupEnum - try and list user groups on a different host. +****************************************************************************/ + +int cli_RNetGroupEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupEnum_REQ) /* parm string */ + +sizeof(RAP_GROUP_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WGroupEnum, + RAP_NetGroupEnum_REQ, RAP_GROUP_INFO_L1); + PUTWORD(p,1); /* Info level 1 */ /* add level 0 */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, 0xFFE0 /* data area size */, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rdrcnt; + + res = GETRES(rparam, endp); + cli->rap_error = res; + if(cli->rap_error == 234) { + DEBUG(1,("Not all group names were returned (such as those longer than 21 characters)\n")); + } else if (cli->rap_error != 0) { + DEBUG(1,("NetGroupEnum gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetGroupEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + char *endp = rparam + rprcnt; + int i, converter = 0, count = 0; + TALLOC_CTX *frame = talloc_stackframe(); + + p = rparam + WORDSIZE; /* skip result */ + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata; i<count && p < endp;i++) { + char *comment = NULL; + char groupname[RAP_GROUPNAME_LEN]; + + p += rap_getstringf(p, + groupname, + RAP_GROUPNAME_LEN, + RAP_GROUPNAME_LEN, + endp); + p++; /* pad byte */ + p += rap_getstringp(frame, + p, + &comment, + rdata, + converter, + endp); + + if (!comment || !groupname[0]) { + break; + } + + fn(groupname, comment, cli); + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetGroupEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_RNetGroupEnum0(struct cli_state *cli, + void (*fn)(const char *, void *), + void *state) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupEnum_REQ) /* parm string */ + +sizeof(RAP_GROUP_INFO_L0) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WGroupEnum, + RAP_NetGroupEnum_REQ, RAP_GROUP_INFO_L0); + PUTWORD(p,0); /* Info level 0 */ /* Hmmm. I *very* much suspect this + is the resume count, at least + that's what smbd believes... */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, 0xFFE0 /* data area size */, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam+rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + if(cli->rap_error == 234) { + DEBUG(1,("Not all group names were returned (such as those longer than 21 characters)\n")); + } else if (cli->rap_error != 0) { + DEBUG(1,("NetGroupEnum gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetGroupEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + char *endp = rparam + rprcnt; + int i, count = 0; + + p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */ + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata; i<count && p < endp;i++) { + char groupname[RAP_GROUPNAME_LEN]; + + p += rap_getstringf(p, + groupname, + RAP_GROUPNAME_LEN, + RAP_GROUPNAME_LEN, + endp); + if (groupname[0]) { + fn(groupname, cli); + } + } + } else { + DEBUG(4,("NetGroupEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_NetGroupDelUser(struct cli_state * cli, const char *group_name, const char *user_name) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupDelUser_REQ) /* parm string */ + +1 /* no ret string */ + +RAP_GROUPNAME_LEN /* group name */ + +RAP_USERNAME_LEN]; /* user to del */ + + /* now send a SMBtrans command with api GroupMemberAdd */ + p = make_header(param, RAP_WGroupDelUser, RAP_NetGroupDelUser_REQ, NULL); + PUTSTRING(p,group_name,RAP_GROUPNAME_LEN); + PUTSTRING(p,user_name,RAP_USERNAME_LEN); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + + switch(res) { + case 0: + break; + case 5: + case 65: + DEBUG(1, ("Access Denied\n")); + break; + case 50: + DEBUG(1, ("Not supported by server\n")); + break; + case 2220: + DEBUG(1, ("Group does not exist\n")); + break; + case 2221: + DEBUG(1, ("User does not exist\n")); + break; + case 2237: + DEBUG(1, ("User is not in group\n")); + break; + default: + DEBUG(4,("NetGroupDelUser res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetGroupDelUser failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_NetGroupAddUser(struct cli_state * cli, const char *group_name, const char *user_name) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupAddUser_REQ) /* parm string */ + +1 /* no ret string */ + +RAP_GROUPNAME_LEN /* group name */ + +RAP_USERNAME_LEN]; /* user to add */ + + /* now send a SMBtrans command with api GroupMemberAdd */ + p = make_header(param, RAP_WGroupAddUser, RAP_NetGroupAddUser_REQ, NULL); + PUTSTRING(p,group_name,RAP_GROUPNAME_LEN); + PUTSTRING(p,user_name,RAP_USERNAME_LEN); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + + switch(res) { + case 0: + break; + case 5: + case 65: + DEBUG(1, ("Access Denied\n")); + break; + case 50: + DEBUG(1, ("Not supported by server\n")); + break; + case 2220: + DEBUG(1, ("Group does not exist\n")); + break; + case 2221: + DEBUG(1, ("User does not exist\n")); + break; + default: + DEBUG(4,("NetGroupAddUser res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetGroupAddUser failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + + +int cli_NetGroupGetUsers(struct cli_state * cli, const char *group_name, void (*fn)(const char *, void *), void *state ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupGetUsers_REQ)/* parm string */ + +sizeof(RAP_GROUP_USERS_INFO_0) /* return string */ + +RAP_GROUPNAME_LEN /* group name */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + + /* now send a SMBtrans command with api GroupGetUsers */ + p = make_header(param, RAP_WGroupGetUsers, + RAP_NetGroupGetUsers_REQ, RAP_GROUP_USERS_INFO_0); + PUTSTRING(p,group_name,RAP_GROUPNAME_LEN-1); + PUTWORD(p,0); /* info level 0 */ + PUTWORD(p,0xFFE0); /* return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),PTR_DIFF(p,param), + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetGroupGetUsers gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetGroupGetUsers no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + char *endp = rparam + rprcnt; + int i, count = 0; + char username[RAP_USERNAME_LEN]; + + p = rparam + WORDSIZE + WORDSIZE; + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata; i<count && p < endp; i++) { + p += rap_getstringf(p, + username, + RAP_USERNAME_LEN, + RAP_USERNAME_LEN, + endp); + if (username[0]) { + fn(username, state); + } + } + } else { + DEBUG(4,("NetGroupGetUsers res=%d\n", res)); + } + + out: + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return res; +} + +int cli_NetUserGetGroups(struct cli_state * cli, const char *user_name, void (*fn)(const char *, void *), void *state ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetUserGetGroups_REQ)/* parm string */ + +sizeof(RAP_GROUP_USERS_INFO_0) /* return string */ + +RAP_USERNAME_LEN /* user name */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + + /* now send a SMBtrans command with api GroupGetUsers */ + p = make_header(param, RAP_WUserGetGroups, + RAP_NetUserGetGroups_REQ, RAP_GROUP_USERS_INFO_0); + PUTSTRING(p,user_name,RAP_USERNAME_LEN-1); + PUTWORD(p,0); /* info level 0 */ + PUTWORD(p,0xFFE0); /* return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),PTR_DIFF(p,param), + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetUserGetGroups gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetUserGetGroups no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + char *endp = rparam + rprcnt; + int i, count = 0; + char groupname[RAP_GROUPNAME_LEN]; + + p = rparam + WORDSIZE + WORDSIZE; + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata; i<count && p < endp; i++) { + p += rap_getstringf(p, + groupname, + RAP_GROUPNAME_LEN, + RAP_GROUPNAME_LEN, + endp); + if (groupname[0]) { + fn(groupname, state); + } + } + } else { + DEBUG(4,("NetUserGetGroups res=%d\n", res)); + } + + out: + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return res; +} + +/**************************************************************************** + Call a NetUserDelete - delete user from remote server. +****************************************************************************/ + +int cli_NetUserDelete(struct cli_state *cli, const char * user_name ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetGroupDel_REQ) /* parm string */ + +1 /* no ret string */ + +RAP_USERNAME_LEN /* user to del */ + +WORDSIZE]; /* reserved word */ + + /* now send a SMBtrans command with api UserDel */ + p = make_header(param, RAP_WUserDel, RAP_NetGroupDel_REQ, NULL); + PUTSTRING(p, user_name, RAP_USERNAME_LEN); + PUTWORD(p,0); /* reserved word MBZ on input */ + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + + if (res == 0) { + /* nothing to do */ + } else if ((res == 5) || (res == 65)) { + DEBUG(1, ("Access Denied\n")); + } else if (res == 2221) { + DEBUG (1, ("User does not exist\n")); + } else { + DEBUG(4,("NetUserDelete res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetUserDelete failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetUserAdd - add user to remote server. +****************************************************************************/ + +int cli_NetUserAdd(struct cli_state *cli, RAP_USER_INFO_1 * userinfo ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetUserAdd2_REQ) /* req string */ + +sizeof(RAP_USER_INFO_L1) /* data string */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer length */ + +WORDSIZE]; /* reserved */ + + char data[1024]; + /* offset into data of free format strings. Will be updated */ + /* by PUTSTRINGP macro and end up with total data length. */ + int soffset=RAP_USERNAME_LEN+1 /* user name + pad */ + + RAP_UPASSWD_LEN /* password */ + + DWORDSIZE /* password age */ + + WORDSIZE /* privilege */ + + DWORDSIZE /* home dir ptr */ + + DWORDSIZE /* comment ptr */ + + WORDSIZE /* flags */ + + DWORDSIZE; /* login script ptr*/ + + /* now send a SMBtrans command with api NetUserAdd */ + p = make_header(param, RAP_WUserAdd2, + RAP_NetUserAdd2_REQ, RAP_USER_INFO_L1); + + PUTWORD(p, 1); /* info level */ + PUTWORD(p, 0); /* pwencrypt */ + if(userinfo->passwrd) + PUTWORD(p,MIN(strlen(userinfo->passwrd), RAP_UPASSWD_LEN)); + else + PUTWORD(p, 0); /* password length */ + + p = data; + memset(data, '\0', soffset); + + PUTSTRINGF(p, userinfo->user_name, RAP_USERNAME_LEN); + PUTBYTE(p, 0); /* pad byte 0 */ + PUTSTRINGF(p, userinfo->passwrd, RAP_UPASSWD_LEN); + PUTDWORD(p, 0); /* pw age - n.a. on user add */ + PUTWORD(p, userinfo->priv); + PUTSTRINGP(p, userinfo->home_dir, data, soffset); + PUTSTRINGP(p, userinfo->comment, data, soffset); + PUTWORD(p, userinfo->userflags); + PUTSTRINGP(p, userinfo->logon_script, data, soffset); + + if (cli_api(cli, + param, sizeof(param), 1024, /* Param, length, maxlen */ + data, soffset, sizeof(data), /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + /* nothing to do */ + } else if ((res == 5) || (res == 65)) { + DEBUG(1, ("Access Denied\n")); + } else if (res == 2224) { + DEBUG (1, ("User already exists\n")); + } else { + DEBUG(4,("NetUserAdd res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetUserAdd failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** +call a NetUserEnum - try and list users on a different host +****************************************************************************/ + +int cli_RNetUserEnum(struct cli_state *cli, void (*fn)(const char *, const char *, const char *, const char *, void *), void *state) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetUserEnum_REQ) /* parm string */ + +sizeof(RAP_USER_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WUserEnum, + RAP_NetUserEnum_REQ, RAP_USER_INFO_L1); + PUTWORD(p,1); /* Info level 1 */ + PUTWORD(p,0xFF00); /* Return buffer size */ + + /* BB Fix handling of large numbers of users to be returned */ + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + if (cli->rap_error != 0) { + DEBUG(1,("NetUserEnum gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetUserEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + int i, converter = 0, count = 0; + char username[RAP_USERNAME_LEN]; + char userpw[RAP_UPASSWD_LEN]; + char *endp = rparam + rprcnt; + char *comment, *homedir, *logonscript; + TALLOC_CTX *frame = talloc_stackframe(); + + p = rparam + WORDSIZE; /* skip result */ + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata;i<count && p < endp;i++) { + p += rap_getstringf(p, + username, + RAP_USERNAME_LEN, + RAP_USERNAME_LEN, + endp); + p++; /* pad byte */ + p += rap_getstringf(p, + userpw, + RAP_UPASSWD_LEN, + RAP_UPASSWD_LEN, + endp); + p += DWORDSIZE; /* skip password age */ + p += WORDSIZE; /* skip priv: 0=guest, 1=user, 2=admin */ + p += rap_getstringp(frame, + p, + &homedir, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &comment, + rdata, + converter, + endp); + p += WORDSIZE; /* skip flags */ + p += rap_getstringp(frame, + p, + &logonscript, + rdata, + converter, + endp); + if (username[0] && comment && + homedir && logonscript) { + fn(username, + comment, + homedir, + logonscript, + cli); + } + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetUserEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_RNetUserEnum0(struct cli_state *cli, + void (*fn)(const char *, void *), + void *state) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetUserEnum_REQ) /* parm string */ + +sizeof(RAP_USER_INFO_L0) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WUserEnum, + RAP_NetUserEnum_REQ, RAP_USER_INFO_L0); + PUTWORD(p,0); /* Info level 1 */ + PUTWORD(p,0xFF00); /* Return buffer size */ + + /* BB Fix handling of large numbers of users to be returned */ + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + if (cli->rap_error != 0) { + DEBUG(1,("NetUserEnum gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetUserEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + int i, count = 0; + char *endp = rparam + rprcnt; + char username[RAP_USERNAME_LEN]; + + p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */ + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata;i<count && p < endp;i++) { + p += rap_getstringf(p, + username, + RAP_USERNAME_LEN, + RAP_USERNAME_LEN, + endp); + if (username[0]) { + fn(username, cli); + } + } + } else { + DEBUG(4,("NetUserEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetFileClose2 - close open file on another session to server. +****************************************************************************/ + +int cli_NetFileClose(struct cli_state *cli, uint32 file_id ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WFileClose2_REQ) /* req string */ + +1 /* no ret string */ + +DWORDSIZE]; /* file ID */ + int res = -1; + + /* now send a SMBtrans command with api RNetShareEnum */ + p = make_header(param, RAP_WFileClose2, RAP_WFileClose2_REQ, NULL); + PUTDWORD(p, file_id); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + /* nothing to do */ + } else if (res == 2314){ + DEBUG(1, ("NetFileClose2 - attempt to close non-existant file open instance\n")); + } else { + DEBUG(4,("NetFileClose2 res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetFileClose2 failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetFileGetInfo - get information about server file opened from other + workstation. +****************************************************************************/ + +int cli_NetFileGetInfo(struct cli_state *cli, uint32 file_id, void (*fn)(const char *, const char *, uint16, uint16, uint32)) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WFileGetInfo2_REQ) /* req string */ + +sizeof(RAP_FILE_INFO_L3) /* return string */ + +DWORDSIZE /* file ID */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + + /* now send a SMBtrans command with api RNetShareEnum */ + p = make_header(param, RAP_WFileGetInfo2, + RAP_WFileGetInfo2_REQ, RAP_FILE_INFO_L3); + PUTDWORD(p, file_id); + PUTWORD(p, 3); /* info level */ + PUTWORD(p, 0x1000); /* buffer size */ + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 0x1000, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + int converter = 0,id = 0, perms = 0, locks = 0; + char *fpath, *fuser; + + p = rparam + WORDSIZE; /* skip result */ + GETWORD(p, converter, endp); + + p = rdata; + endp = rdata + rdrcnt; + + GETDWORD(p, id, endp); + GETWORD(p, perms, endp); + GETWORD(p, locks, endp); + + p += rap_getstringp(frame, + p, + &fpath, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &fuser, + rdata, + converter, + endp); + + if (fpath && fuser) { + fn(fpath, fuser, perms, locks, id); + } + + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetFileGetInfo2 res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetFileGetInfo2 failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** +* Call a NetFileEnum2 - list open files on an SMB server +* +* PURPOSE: Remotes a NetFileEnum API call to the current server or target +* server listing the files open via the network (and their +* corresponding open instance ids) +* +* Dependencies: none +* +* Parameters: +* cli - pointer to cli_state structure +* user - if present, return only files opened by this remote user +* base_path - if present, return only files opened below this +* base path +* fn - display function to invoke for each entry in the result +* +* +* Returns: +* True - success +* False - failure +* +****************************************************************************/ + +int cli_NetFileEnum(struct cli_state *cli, const char * user, + const char * base_path, + void (*fn)(const char *, const char *, uint16, uint16, + uint32)) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WFileEnum2_REQ) /* req string */ + +sizeof(RAP_FILE_INFO_L3) /* return string */ + +1024 /* base path (opt) */ + +RAP_USERNAME_LEN /* user name (opt) */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer size */ + +DWORDSIZE /* resume key ? */ + +DWORDSIZE]; /* resume key ? */ + int count = -1; + int res = -1; + + /* now send a SMBtrans command with api RNetShareEnum */ + p = make_header(param, RAP_WFileEnum2, + RAP_WFileEnum2_REQ, RAP_FILE_INFO_L3); + + PUTSTRING(p, base_path, 1024); + PUTSTRING(p, user, RAP_USERNAME_LEN); + PUTWORD(p, 3); /* info level */ + PUTWORD(p, 0xFF00); /* buffer size */ + PUTDWORD(p, 0); /* zero out the resume key */ + PUTDWORD(p, 0); /* or is this one the resume key? */ + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 0xFF00, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + int converter = 0, i; + + p = rparam + WORDSIZE; /* skip result */ + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + p = rdata; + endp = rdata + rdrcnt; + for (i=0; i<count && p < endp; i++) { + int id = 0, perms = 0, locks = 0; + char *fpath, *fuser; + + GETDWORD(p, id, endp); + GETWORD(p, perms, endp); + GETWORD(p, locks, endp); + p += rap_getstringp(frame, + p, + &fpath, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &fuser, + rdata, + converter, + endp); + + if (fpath && fuser) { + fn(fpath, fuser, perms, locks, id); + } + } /* BB fix ERRmoredata case to send resume request */ + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetFileEnum2 res=%d\n", res)); + } + } else { + DEBUG(4,("NetFileEnum2 failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return count; +} + +/**************************************************************************** + Call a NetShareAdd - share/export directory on remote server. +****************************************************************************/ + +int cli_NetShareAdd(struct cli_state *cli, RAP_SHARE_INFO_2 * sinfo ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WShareAdd_REQ) /* req string */ + +sizeof(RAP_SHARE_INFO_L2) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* reserved word */ + char data[1024]; + /* offset to free format string section following fixed length data. */ + /* will be updated by PUTSTRINGP macro and will end up with total len */ + int soffset = RAP_SHARENAME_LEN + 1 /* share name + pad */ + + WORDSIZE /* share type */ + + DWORDSIZE /* comment pointer */ + + WORDSIZE /* permissions */ + + WORDSIZE /* max users */ + + WORDSIZE /* active users */ + + DWORDSIZE /* share path */ + + RAP_SPASSWD_LEN + 1; /* share password + pad */ + + memset(param,'\0',sizeof(param)); + /* now send a SMBtrans command with api RNetShareAdd */ + p = make_header(param, RAP_WshareAdd, + RAP_WShareAdd_REQ, RAP_SHARE_INFO_L2); + PUTWORD(p, 2); /* info level */ + PUTWORD(p, 0); /* reserved word 0 */ + + p = data; + PUTSTRINGF(p, sinfo->share_name, RAP_SHARENAME_LEN); + PUTBYTE(p, 0); /* pad byte 0 */ + + PUTWORD(p, sinfo->share_type); + PUTSTRINGP(p, sinfo->comment, data, soffset); + PUTWORD(p, sinfo->perms); + PUTWORD(p, sinfo->maximum_users); + PUTWORD(p, sinfo->active_users); + PUTSTRINGP(p, sinfo->path, data, soffset); + PUTSTRINGF(p, sinfo->password, RAP_SPASSWD_LEN); + SCVAL(p,-1,0x0A); /* required 0x0A at end of password */ + + if (cli_api(cli, + param, sizeof(param), 1024, /* Param, length, maxlen */ + data, soffset, sizeof(data), /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + /* nothing to do */ + } else { + DEBUG(4,("NetShareAdd res=%d\n", res)); + } + } else { + DEBUG(4,("NetShareAdd failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetShareDelete - unshare exported directory on remote server. +****************************************************************************/ + +int cli_NetShareDelete(struct cli_state *cli, const char * share_name ) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + int res = -1; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WShareDel_REQ) /* req string */ + +1 /* no ret string */ + +RAP_SHARENAME_LEN /* share to del */ + +WORDSIZE]; /* reserved word */ + + /* now send a SMBtrans command with api RNetShareDelete */ + p = make_header(param, RAP_WshareDel, RAP_WShareDel_REQ, NULL); + PUTSTRING(p,share_name,RAP_SHARENAME_LEN); + PUTWORD(p,0); /* reserved word MBZ on input */ + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + /* nothing to do */ + } else { + DEBUG(4,("NetShareDelete res=%d\n", res)); + } + } else { + DEBUG(4,("NetShareDelete failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/************************************************************************* +* +* Function Name: cli_get_pdc_name +* +* PURPOSE: Remotes a NetServerEnum API call to the current server +* requesting the name of a server matching the server +* type of SV_TYPE_DOMAIN_CTRL (PDC). +* +* Dependencies: none +* +* Parameters: +* cli - pointer to cli_state structure +* workgroup - pointer to string containing name of domain +* pdc_name - pointer to string that will contain PDC name +* on successful return +* +* Returns: +* True - success +* False - failure +* +************************************************************************/ + +bool cli_get_pdc_name(struct cli_state *cli, const char *workgroup, char **pdc_name) +{ + char *rparam = NULL; + char *rdata = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetServerEnum2_REQ) /* req string */ + +sizeof(RAP_SERVER_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer size */ + +DWORDSIZE /* server type */ + +RAP_MACHNAME_LEN]; /* workgroup */ + int count = -1; + int res = -1; + + *pdc_name = NULL; + + /* send a SMBtrans command with api NetServerEnum */ + p = make_header(param, RAP_NetServerEnum2, + RAP_NetServerEnum2_REQ, RAP_SERVER_INFO_L1); + PUTWORD(p, 1); /* info level */ + PUTWORD(p, CLI_BUFFER_SIZE); + PUTDWORD(p, SV_TYPE_DOMAIN_CTRL); + PUTSTRING(p, workgroup, RAP_MACHNAME_LEN); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + + /* + * We only really care to copy a name if the + * API succeeded and we got back a name. + */ + if (cli->rap_error == 0) { + p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */ + GETWORD(p, count, endp); + p = rdata; + endp = rdata + rdrcnt; + + if (count > 0) { + TALLOC_CTX *frame = talloc_stackframe(); + char *dcname; + p += rap_getstring(frame, + p, + &dcname, + endp); + if (dcname) { + *pdc_name = SMB_STRDUP(dcname); + } + TALLOC_FREE(frame); + } + } else { + DEBUG(4,("cli_get_pdc_name: machine %s failed the NetServerEnum call. " + "Error was : %s.\n", cli->desthost, cli_errstr(cli) )); + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return(count > 0); +} + +/************************************************************************* +* +* Function Name: cli_get_server_domain +* +* PURPOSE: Remotes a NetWkstaGetInfo API call to the current server +* requesting wksta_info_10 level information to determine +* the domain the server belongs to. On success, this +* routine sets the server_domain field in the cli_state structure +* to the server's domain name. +* +* Dependencies: none +* +* Parameters: +* cli - pointer to cli_state structure +* +* Returns: +* True - success +* False - failure +* +* Origins: samba 2.0.6 source/libsmb/clientgen.c cli_NetServerEnum() +* +************************************************************************/ + +bool cli_get_server_domain(struct cli_state *cli) +{ + char *rparam = NULL; + char *rdata = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WWkstaGetInfo_REQ) /* req string */ + +sizeof(RAP_WKSTA_INFO_L10) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + int res = -1; + + /* send a SMBtrans command with api NetWkstaGetInfo */ + p = make_header(param, RAP_WWkstaGetInfo, + RAP_WWkstaGetInfo_REQ, RAP_WKSTA_INFO_L10); + PUTWORD(p, 10); /* info level */ + PUTWORD(p, CLI_BUFFER_SIZE); + + if (cli_api(cli, param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt)) { /* return data, return size */ + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0) { + TALLOC_CTX *frame = talloc_stackframe(); + char *server_domain; + int converter = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter, endp); + + p = rdata + DWORDSIZE + DWORDSIZE; /* skip computer & user names */ + endp = rdata + rdrcnt; + p += rap_getstringp(frame, + p, + &server_domain, + rdata, + converter, + endp); + + if (server_domain) { + fstrcpy(cli->server_domain, server_domain); + } + TALLOC_FREE(frame); + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return(res == 0); +} + +/************************************************************************* +* +* Function Name: cli_get_server_type +* +* PURPOSE: Remotes a NetServerGetInfo API call to the current server +* requesting server_info_1 level information to retrieve +* the server type. +* +* Dependencies: none +* +* Parameters: +* cli - pointer to cli_state structure +* pstype - pointer to uint32 to contain returned server type +* +* Returns: +* True - success +* False - failure +* +* Origins: samba 2.0.6 source/libsmb/clientgen.c cli_NetServerEnum() +* +************************************************************************/ + +bool cli_get_server_type(struct cli_state *cli, uint32 *pstype) +{ + char *rparam = NULL; + char *rdata = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WserverGetInfo_REQ) /* req string */ + +sizeof(RAP_SERVER_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + int res = -1; + + /* send a SMBtrans command with api NetServerGetInfo */ + p = make_header(param, RAP_WserverGetInfo, + RAP_WserverGetInfo_REQ, RAP_SERVER_INFO_L1); + PUTWORD(p, 1); /* info level */ + PUTWORD(p, CLI_BUFFER_SIZE); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + + if (res == 0 || res == ERRmoredata) { + p = rdata; + endp = rparam + rprcnt; + p += 18; + GETDWORD(p,*pstype,endp); + *pstype &= ~SV_TYPE_LOCAL_LIST_ONLY; + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return(res == 0 || res == ERRmoredata); +} + +bool cli_get_server_name(TALLOC_CTX *mem_ctx, struct cli_state *cli, + char **servername) +{ + char *rparam = NULL; + char *rdata = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[WORDSIZE /* api number */ + +sizeof(RAP_WserverGetInfo_REQ) /* req string */ + +sizeof(RAP_SERVER_INFO_L1) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + bool res = false; + char *endp; + fstring tmp; + + /* send a SMBtrans command with api NetServerGetInfo */ + p = make_header(param, RAP_WserverGetInfo, + RAP_WserverGetInfo_REQ, RAP_SERVER_INFO_L1); + PUTWORD(p, 1); /* info level */ + PUTWORD(p, CLI_BUFFER_SIZE); + + if (!cli_api(cli, + param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + goto failed; + } + + endp = rparam + rprcnt; + if (GETRES(rparam, endp) != 0) { + goto failed; + } + + if (rdrcnt < 16) { + DEBUG(10, ("invalid data count %d, expected >= 16\n", rdrcnt)); + goto failed; + } + + if (pull_ascii(tmp, rdata, sizeof(tmp)-1, 16, STR_TERMINATE) == -1) { + DEBUG(10, ("pull_ascii failed\n")); + goto failed; + } + + if (!(*servername = talloc_strdup(mem_ctx, tmp))) { + DEBUG(1, ("talloc_strdup failed\n")); + goto failed; + } + + res = true; + + failed: + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return res; +} + +/************************************************************************* +* +* Function Name: cli_ns_check_server_type +* +* PURPOSE: Remotes a NetServerEnum2 API call to the current server +* requesting server_info_0 level information of machines +* matching the given server type. If the returned server +* list contains the machine name contained in cli->desthost +* then we conclude the server type checks out. This routine +* is useful to retrieve list of server's of a certain +* type when all you have is a null session connection and +* can't remote API calls such as NetWkstaGetInfo or +* NetServerGetInfo. +* +* Dependencies: none +* +* Parameters: +* cli - pointer to cli_state structure +* workgroup - pointer to string containing domain +* stype - server type +* +* Returns: +* True - success +* False - failure +* +************************************************************************/ + +bool cli_ns_check_server_type(struct cli_state *cli, char *workgroup, uint32 stype) +{ + char *rparam = NULL; + char *rdata = NULL; + unsigned int rdrcnt,rprcnt; + char *p; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetServerEnum2_REQ) /* req string */ + +sizeof(RAP_SERVER_INFO_L0) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer size */ + +DWORDSIZE /* server type */ + +RAP_MACHNAME_LEN]; /* workgroup */ + bool found_server = false; + int res = -1; + + /* send a SMBtrans command with api NetServerEnum */ + p = make_header(param, RAP_NetServerEnum2, + RAP_NetServerEnum2_REQ, RAP_SERVER_INFO_L0); + PUTWORD(p, 0); /* info level 0 */ + PUTWORD(p, CLI_BUFFER_SIZE); + PUTDWORD(p, stype); + PUTSTRING(p, workgroup, RAP_MACHNAME_LEN); + + if (cli_api(cli, + param, PTR_DIFF(p,param), 8, /* params, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + + if (res == 0 || res == ERRmoredata) { + int i, count = 0; + + p = rparam + WORDSIZE + WORDSIZE; + GETWORD(p, count,endp); + + p = rdata; + endp = rdata + rdrcnt; + for (i = 0;i < count && p < endp;i++, p += 16) { + char ret_server[RAP_MACHNAME_LEN]; + + p += rap_getstringf(p, + ret_server, + RAP_MACHNAME_LEN, + RAP_MACHNAME_LEN, + endp); + if (strequal(ret_server, cli->desthost)) { + found_server = true; + break; + } + } + } else { + DEBUG(4,("cli_ns_check_server_type: machine %s failed the NetServerEnum call. " + "Error was : %s.\n", cli->desthost, cli_errstr(cli) )); + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return found_server; +} + +/**************************************************************************** + Perform a NetWkstaUserLogoff. +****************************************************************************/ + +bool cli_NetWkstaUserLogoff(struct cli_state *cli, const char *user, const char *workstation) +{ + char *rparam = NULL; + char *rdata = NULL; + char *p; + unsigned int rdrcnt,rprcnt; + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetWkstaUserLogoff_REQ) /* req string */ + +sizeof(RAP_USER_LOGOFF_INFO_L1) /* return string */ + +RAP_USERNAME_LEN+1 /* user name+pad */ + +RAP_MACHNAME_LEN /* wksta name */ + +WORDSIZE /* buffer size */ + +WORDSIZE]; /* buffer size? */ + char upperbuf[MAX(RAP_USERNAME_LEN,RAP_MACHNAME_LEN)]; + int res = -1; + char *tmp = NULL; + + memset(param, 0, sizeof(param)); + + /* send a SMBtrans command with api NetWkstaUserLogoff */ + p = make_header(param, RAP_WWkstaUserLogoff, + RAP_NetWkstaUserLogoff_REQ, RAP_USER_LOGOFF_INFO_L1); + PUTDWORD(p, 0); /* Null pointer */ + PUTDWORD(p, 0); /* Null pointer */ + strlcpy(upperbuf, user, sizeof(upperbuf)); + strupper_m(upperbuf); + tmp = upperbuf; + PUTSTRINGF(p, tmp, RAP_USERNAME_LEN); + p++; /* strange format, but ok */ + strlcpy(upperbuf, workstation, sizeof(upperbuf)); + strupper_m(upperbuf); + tmp = upperbuf; + PUTSTRINGF(p, tmp, RAP_MACHNAME_LEN); + PUTWORD(p, CLI_BUFFER_SIZE); + PUTWORD(p, CLI_BUFFER_SIZE); + + if (cli_api(cli, + param, PTR_DIFF(p,param),1024, /* param, length, max */ + NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */ + &rparam, &rprcnt, /* return params, return size */ + &rdata, &rdrcnt /* return data, return size */ + )) { + char *endp = rparam + rprcnt; + res = GETRES(rparam,endp); + cli->rap_error = res; + + if (cli->rap_error != 0) { + DEBUG(4,("NetwkstaUserLogoff gave error %d\n", cli->rap_error)); + } + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + return (cli->rap_error == 0); +} + +int cli_NetPrintQEnum(struct cli_state *cli, + void (*qfn)(const char*,uint16,uint16,uint16,const char*,const char*,const char*,const char*,const char*,uint16,uint16), + void (*jfn)(uint16,const char*,const char*,const char*,const char*,uint16,uint16,const char*,uint,uint,const char*)) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetPrintQEnum_REQ) /* req string */ + +sizeof(RAP_PRINTQ_INFO_L2) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer size */ + +sizeof(RAP_SMB_PRINT_JOB_L1)]; /* more ret data */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0',sizeof(param)); + p = make_header(param, RAP_WPrintQEnum, + RAP_NetPrintQEnum_REQ, RAP_PRINTQ_INFO_L2); + PUTWORD(p,2); /* Info level 2 */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + PUTSTRING(p, RAP_SMB_PRINT_JOB_L1, 0); + + if (cli_api(cli, + param, PTR_DIFF(p,param),1024, + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetPrintQEnum gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetPrintQEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + char *endp = rparam + rprcnt; + int i, converter = 0, count = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + p = rdata; + endp = rdata + rdrcnt; + for (i=0;i<count && p < endp;i++) { + char qname[RAP_SHARENAME_LEN]; + char *sep_file, *print_proc, *dest, *parms, *comment; + uint16_t jobcount = 0, priority = 0; + uint16_t start_time = 0, until_time = 0, status = 0; + + p += rap_getstringf(p, + qname, + RAP_SHARENAME_LEN, + RAP_SHARENAME_LEN, + endp); + p++; /* pad */ + GETWORD(p, priority, endp); + GETWORD(p, start_time, endp); + GETWORD(p, until_time, endp); + p += rap_getstringp(frame, + p, + &sep_file, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &print_proc, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &dest, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &parms, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &comment, + rdata, + converter, + endp); + GETWORD(p, status, endp); + GETWORD(p, jobcount, endp); + + if (sep_file && print_proc && dest && parms && + comment) { + qfn(qname, priority, start_time, until_time, sep_file, print_proc, + dest, parms, comment, status, jobcount); + } + + if (jobcount) { + int j; + for (j=0;j<jobcount;j++) { + uint16 jid = 0, pos = 0, fsstatus = 0; + char ownername[RAP_USERNAME_LEN]; + char notifyname[RAP_MACHNAME_LEN]; + char datatype[RAP_DATATYPE_LEN]; + char *jparms, *jstatus, *jcomment; + unsigned int submitted = 0, jsize = 0; + + GETWORD(p, jid, endp); + p += rap_getstringf(p, + ownername, + RAP_USERNAME_LEN, + RAP_USERNAME_LEN, + endp); + p++; /* pad byte */ + p += rap_getstringf(p, + notifyname, + RAP_MACHNAME_LEN, + RAP_MACHNAME_LEN, + endp); + p += rap_getstringf(p, + datatype, + RAP_DATATYPE_LEN, + RAP_DATATYPE_LEN, + endp); + p += rap_getstringp(frame, + p, + &jparms, + rdata, + converter, + endp); + GETWORD(p, pos, endp); + GETWORD(p, fsstatus, endp); + p += rap_getstringp(frame, + p, + &jstatus, + rdata, + converter, + endp); + GETDWORD(p, submitted, endp); + GETDWORD(p, jsize, endp); + p += rap_getstringp(frame, + p, + &jcomment, + rdata, + converter, + endp); + + if (jparms && jstatus && jcomment) { + jfn(jid, ownername, notifyname, datatype, jparms, pos, fsstatus, + jstatus, submitted, jsize, jcomment); + } + } + } + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetPrintQEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_NetPrintQGetInfo(struct cli_state *cli, const char *printer, + void (*qfn)(const char*,uint16,uint16,uint16,const char*,const char*,const char*,const char*,const char*,uint16,uint16), + void (*jfn)(uint16,const char*,const char*,const char*,const char*,uint16,uint16,const char*,uint,uint,const char*)) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetPrintQGetInfo_REQ) /* req string */ + +sizeof(RAP_PRINTQ_INFO_L2) /* return string */ + +RAP_SHARENAME_LEN /* printer name */ + +WORDSIZE /* info level */ + +WORDSIZE /* buffer size */ + +sizeof(RAP_SMB_PRINT_JOB_L1)]; /* more ret data */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0',sizeof(param)); + p = make_header(param, RAP_WPrintQGetInfo, + RAP_NetPrintQGetInfo_REQ, RAP_PRINTQ_INFO_L2); + PUTSTRING(p, printer, RAP_SHARENAME_LEN-1); + PUTWORD(p, 2); /* Info level 2 */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + PUTSTRING(p, RAP_SMB_PRINT_JOB_L1, 0); + + if (cli_api(cli, + param, PTR_DIFF(p,param),1024, + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetPrintQGetInfo gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetPrintQGetInfo no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + char *endp = rparam + rprcnt; + int rsize = 0, converter = 0; + char qname[RAP_SHARENAME_LEN]; + char *sep_file, *print_proc, *dest, *parms, *comment; + uint16_t jobcount = 0, priority = 0; + uint16_t start_time = 0, until_time = 0, status = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter, endp); + GETWORD(p, rsize, endp); + + p = rdata; + endp = rdata + rdrcnt; + p += rap_getstringf(p, + qname, + RAP_SHARENAME_LEN, + RAP_SHARENAME_LEN, + endp); + p++; /* pad */ + GETWORD(p, priority, endp); + GETWORD(p, start_time, endp); + GETWORD(p, until_time, endp); + p += rap_getstringp(frame, + p, + &sep_file, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &print_proc, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &dest, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &parms, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &comment, + rdata, + converter, + endp); + GETWORD(p, status, endp); + GETWORD(p, jobcount, endp); + + if (sep_file && print_proc && dest && + parms && comment) { + qfn(qname, priority, start_time, until_time, sep_file, print_proc, + dest, parms, comment, status, jobcount); + } + if (jobcount) { + int j; + for (j=0;(j<jobcount)&&(PTR_DIFF(p,rdata)< rsize)&& + p<endp;j++) { + uint16_t jid = 0, pos = 0, fsstatus = 0; + char ownername[RAP_USERNAME_LEN]; + char notifyname[RAP_MACHNAME_LEN]; + char datatype[RAP_DATATYPE_LEN]; + char *jparms, *jstatus, *jcomment; + unsigned int submitted = 0, jsize = 0; + + GETWORD(p, jid, endp); + p += rap_getstringf(p, + ownername, + RAP_USERNAME_LEN, + RAP_USERNAME_LEN, + endp); + p++; /* pad byte */ + p += rap_getstringf(p, + notifyname, + RAP_MACHNAME_LEN, + RAP_MACHNAME_LEN, + endp); + p += rap_getstringf(p, + datatype, + RAP_DATATYPE_LEN, + RAP_DATATYPE_LEN, + endp); + p += rap_getstringp(frame, + p, + &jparms, + rdata, + converter, + endp); + GETWORD(p, pos,endp); + GETWORD(p, fsstatus,endp); + p += rap_getstringp(frame, + p, + &jstatus, + rdata, + converter, + endp); + GETDWORD(p, submitted,endp); + GETDWORD(p, jsize,endp); + p += rap_getstringp(frame, + p, + &jcomment, + rdata, + converter, + endp); + + if (jparms && jstatus && jcomment) { + jfn(jid, ownername, notifyname, datatype, jparms, pos, fsstatus, + jstatus, submitted, jsize, jcomment); + } + } + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetPrintQGetInfo res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetServiceEnum - list running services on a different host. +****************************************************************************/ + +int cli_RNetServiceEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetServiceEnum_REQ) /* parm string */ + +sizeof(RAP_SERVICE_INFO_L2) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WServiceEnum, + RAP_NetServiceEnum_REQ, RAP_SERVICE_INFO_L2); + PUTWORD(p,2); /* Info level 2 */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, 0xFFE0 /* data area size */, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if(cli->rap_error == 234) { + DEBUG(1,("Not all service names were returned (such as those longer than 15 characters)\n")); + } else if (cli->rap_error != 0) { + DEBUG(1,("NetServiceEnum gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetServiceEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + char *endp = rparam + rprcnt; + int i, count = 0; + + p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */ + GETWORD(p, count,endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata;i<count && p < endp;i++) { + char comment[RAP_SRVCCMNT_LEN]; + char servicename[RAP_SRVCNAME_LEN]; + + p += rap_getstringf(p, + servicename, + RAP_SRVCNAME_LEN, + RAP_SRVCNAME_LEN, + endp); + p+=8; /* pass status words */ + p += rap_getstringf(p, + comment, + RAP_SRVCCMNT_LEN, + RAP_SRVCCMNT_LEN, + endp); + + if (servicename[0]) { + fn(servicename, comment, cli); /* BB add status too */ + } + } + } else { + DEBUG(4,("NetServiceEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetSessionEnum - list workstations with sessions to an SMB server. +****************************************************************************/ + +int cli_NetSessionEnum(struct cli_state *cli, void (*fn)(char *, char *, uint16, uint16, uint16, uint, uint, uint, char *)) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetSessionEnum_REQ) /* parm string */ + +sizeof(RAP_SESSION_INFO_L2) /* return string */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WsessionEnum, + RAP_NetSessionEnum_REQ, RAP_SESSION_INFO_L2); + PUTWORD(p,2); /* Info level 2 */ + PUTWORD(p,0xFF); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),8, + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetSessionEnum gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetSesssionEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + char *endp = rparam + rprcnt; + int i, converter = 0, count = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata;i<count && p < endp;i++) { + char *wsname, *username, *clitype_name; + uint16_t num_conns = 0, num_opens = 0, num_users = 0; + unsigned int sess_time = 0, idle_time = 0, user_flags = 0; + + p += rap_getstringp(frame, + p, + &wsname, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &username, + rdata, + converter, + endp); + GETWORD(p, num_conns, endp); + GETWORD(p, num_opens, endp); + GETWORD(p, num_users, endp); + GETDWORD(p, sess_time, endp); + GETDWORD(p, idle_time, endp); + GETDWORD(p, user_flags, endp); + p += rap_getstringp(frame, + p, + &clitype_name, + rdata, + converter, + endp); + + if (wsname && username && clitype_name) { + fn(wsname, username, num_conns, num_opens, num_users, sess_time, + idle_time, user_flags, clitype_name); + } + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetSessionEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetSessionGetInfo - get information about other session to an SMB server. +****************************************************************************/ + +int cli_NetSessionGetInfo(struct cli_state *cli, const char *workstation, + void (*fn)(const char *, const char *, uint16, uint16, uint16, uint, uint, uint, const char *)) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetSessionGetInfo_REQ) /* req string */ + +sizeof(RAP_SESSION_INFO_L2) /* return string */ + +RAP_MACHNAME_LEN /* wksta name */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + char *endp; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WsessionGetInfo, + RAP_NetSessionGetInfo_REQ, RAP_SESSION_INFO_L2); + PUTSTRING(p, workstation, RAP_MACHNAME_LEN-1); + PUTWORD(p,2); /* Info level 2 */ + PUTWORD(p,0xFF); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),PTR_DIFF(p,param), + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if (cli->rap_error != 0) { + DEBUG(1,("NetSessionGetInfo gave error %d\n", cli->rap_error)); + } + } + + if (!rdata) { + DEBUG(4,("NetSessionGetInfo no data returned\n")); + goto out; + } + + endp = rparam + rprcnt; + res = GETRES(rparam, endp); + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + int converter = 0; + char *wsname, *username, *clitype_name; + uint16_t num_conns = 0, num_opens = 0, num_users = 0; + unsigned int sess_time = 0, idle_time = 0, user_flags = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter,endp); + p += WORDSIZE; /* skip rsize */ + + p = rdata; + endp = rdata + rdrcnt; + p += rap_getstringp(frame, + p, + &wsname, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &username, + rdata, + converter, + endp); + GETWORD(p, num_conns, endp); + GETWORD(p, num_opens, endp); + GETWORD(p, num_users, endp); + GETDWORD(p, sess_time, endp); + GETDWORD(p, idle_time, endp); + GETDWORD(p, user_flags, endp); + p += rap_getstringp(frame, + p, + &clitype_name, + rdata, + converter, + endp); + + if (wsname && username && clitype_name) { + fn(wsname, username, num_conns, num_opens, num_users, sess_time, + idle_time, user_flags, clitype_name); + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetSessionGetInfo res=%d\n", res)); + } + + out: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +/**************************************************************************** + Call a NetSessionDel - close a session to an SMB server. +****************************************************************************/ + +int cli_NetSessionDel(struct cli_state *cli, const char *workstation) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetSessionDel_REQ) /* req string */ + +1 /* no return string */ + +RAP_MACHNAME_LEN /* workstation name */ + +WORDSIZE]; /* reserved (0) */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WsessionDel, RAP_NetSessionDel_REQ, NULL); + PUTSTRING(p, workstation, RAP_MACHNAME_LEN-1); + PUTWORD(p,0); /* reserved word of 0 */ + + if (cli_api(cli, + param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */ + NULL, 0, 200, /* data, length, maxlen */ + &rparam, &rprcnt, /* return params, length */ + &rdata, &rdrcnt)) /* return data, length */ + { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + + if (res == 0) { + /* nothing to do */ + } else { + DEBUG(4,("NetFileClose2 res=%d\n", res)); + } + } else { + res = -1; + DEBUG(4,("NetFileClose2 failed\n")); + } + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + return res; +} + +int cli_NetConnectionEnum(struct cli_state *cli, const char *qualifier, + void (*fn)(uint16_t conid, uint16_t contype, + uint16_t numopens, uint16_t numusers, + uint32_t contime, const char *username, + const char *netname)) +{ + char param[WORDSIZE /* api number */ + +sizeof(RAP_NetConnectionEnum_REQ) /* req string */ + +sizeof(RAP_CONNECTION_INFO_L1) /* return string */ + +RAP_MACHNAME_LEN /* wksta name */ + +WORDSIZE /* info level */ + +WORDSIZE]; /* buffer size */ + char *p; + char *rparam = NULL; + char *rdata = NULL; + unsigned int rprcnt, rdrcnt; + int res = -1; + + memset(param, '\0', sizeof(param)); + p = make_header(param, RAP_WconnectionEnum, + RAP_NetConnectionEnum_REQ, RAP_CONNECTION_INFO_L1); + PUTSTRING(p, qualifier, RAP_MACHNAME_LEN-1);/* Workstation name */ + PUTWORD(p,1); /* Info level 1 */ + PUTWORD(p,0xFFE0); /* Return buffer size */ + + if (cli_api(cli, + param, PTR_DIFF(p,param),PTR_DIFF(p,param), + NULL, 0, CLI_BUFFER_SIZE, + &rparam, &rprcnt, + &rdata, &rdrcnt)) { + char *endp = rparam + rprcnt; + res = GETRES(rparam, endp); + cli->rap_error = res; + if (res != 0) { + DEBUG(1,("NetConnectionEnum gave error %d\n", res)); + } + } + + if (!rdata) { + DEBUG(4,("NetConnectionEnum no data returned\n")); + goto out; + } + + if (res == 0 || res == ERRmoredata) { + TALLOC_CTX *frame = talloc_stackframe(); + char *endp = rparam + rprcnt; + int i, converter = 0, count = 0; + + p = rparam + WORDSIZE; + GETWORD(p, converter, endp); + GETWORD(p, count, endp); + + endp = rdata + rdrcnt; + for (i=0,p=rdata;i<count && p < endp;i++) { + char *netname, *username; + uint16_t conn_id = 0, conn_type = 0, num_opens = 0, num_users = 0; + unsigned int conn_time = 0; + + GETWORD(p,conn_id, endp); + GETWORD(p,conn_type, endp); + GETWORD(p,num_opens, endp); + GETWORD(p,num_users, endp); + GETDWORD(p,conn_time, endp); + p += rap_getstringp(frame, + p, + &username, + rdata, + converter, + endp); + p += rap_getstringp(frame, + p, + &netname, + rdata, + converter, + endp); + + if (username && netname) { + fn(conn_id, conn_type, num_opens, num_users, conn_time, + username, netname); + } + } + TALLOC_FREE(frame); + } else { + DEBUG(4,("NetConnectionEnum res=%d\n", res)); + } + + out: + + SAFE_FREE(rdata); + SAFE_FREE(rparam); + return res; +} diff --git a/source3/libsmb/clireadwrite.c b/source3/libsmb/clireadwrite.c new file mode 100644 index 0000000000..ec63281630 --- /dev/null +++ b/source3/libsmb/clireadwrite.c @@ -0,0 +1,717 @@ +/* + Unix SMB/CIFS implementation. + client file read/write routines + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Calculate the recommended read buffer size +****************************************************************************/ +static size_t cli_read_max_bufsize(struct cli_state *cli) +{ + if (!client_is_signing_on(cli) && !cli_encryption_on(cli) + && (cli->posix_capabilities & CIFS_UNIX_LARGE_READ_CAP)) { + return CLI_SAMBA_MAX_POSIX_LARGE_READX_SIZE; + } + if (cli->capabilities & CAP_LARGE_READX) { + return cli->is_samba + ? CLI_SAMBA_MAX_LARGE_READX_SIZE + : CLI_WINDOWS_MAX_LARGE_READX_SIZE; + } + return (cli->max_xmit - (smb_size+32)) & ~1023; +} + +/* + * Send a read&x request + */ + +struct async_req *cli_read_andx_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, int fnum, + off_t offset, size_t size) +{ + struct async_req *result; + struct cli_request *req; + bool bigoffset = False; + + uint16_t vwv[12]; + uint8_t wct = 10; + + if (size > cli_read_max_bufsize(cli)) { + DEBUG(0, ("cli_read_andx_send got size=%d, can only handle " + "size=%d\n", (int)size, + (int)cli_read_max_bufsize(cli))); + return NULL; + } + + SCVAL(vwv + 0, 0, 0xFF); + SCVAL(vwv + 0, 1, 0); + SSVAL(vwv + 1, 0, 0); + SSVAL(vwv + 2, 0, fnum); + SIVAL(vwv + 3, 0, offset); + SSVAL(vwv + 5, 0, size); + SSVAL(vwv + 6, 0, size); + SSVAL(vwv + 7, 0, (size >> 16)); + SSVAL(vwv + 8, 0, 0); + SSVAL(vwv + 9, 0, 0); + + if ((SMB_BIG_UINT)offset >> 32) { + bigoffset = True; + SIVAL(vwv + 10, 0, + (((SMB_BIG_UINT)offset)>>32) & 0xffffffff); + wct += 2; + } + + result = cli_request_send(mem_ctx, ev, cli, SMBreadX, 0, wct, vwv, + 0, NULL); + if (result == NULL) { + return NULL; + } + + req = talloc_get_type_abort(result->private_data, struct cli_request); + + req->data.read.ofs = offset; + req->data.read.size = size; + req->data.read.received = 0; + req->data.read.rcvbuf = NULL; + + return result; +} + +/* + * Pull the data out of a finished async read_and_x request. rcvbuf is + * talloced from the request, so better make sure that you copy it away before + * you talloc_free(req). "rcvbuf" is NOT a talloc_ctx of its own, so do not + * talloc_move it! + */ + +NTSTATUS cli_read_andx_recv(struct async_req *req, ssize_t *received, + uint8_t **rcvbuf) +{ + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + uint8_t wct; + uint16_t *vwv; + uint16_t num_bytes; + uint8_t *bytes; + NTSTATUS status; + size_t size; + + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + + status = cli_pull_reply(req, &wct, &vwv, &num_bytes, &bytes); + + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + if (wct < 12) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* size is the number of bytes the server returned. + * Might be zero. */ + size = SVAL(vwv + 5, 0); + size |= (((unsigned int)SVAL(vwv + 7, 0)) << 16); + + if (size > cli_req->data.read.size) { + DEBUG(5,("server returned more than we wanted!\n")); + return NT_STATUS_UNEXPECTED_IO_ERROR; + } + + *rcvbuf = (uint8_t *)(smb_base(cli_req->inbuf) + SVAL(vwv + 6, 0)); + *received = size; + return NT_STATUS_OK; +} + +/* + * Parallel read support. + * + * cli_pull sends as many read&x requests as the server would allow via + * max_mux at a time. When replies flow back in, the data is written into + * the callback function "sink" in the right order. + */ + +struct cli_pull_state { + struct async_req *req; + + struct event_context *ev; + struct cli_state *cli; + uint16_t fnum; + off_t start_offset; + SMB_OFF_T size; + + NTSTATUS (*sink)(char *buf, size_t n, void *priv); + void *priv; + + size_t chunk_size; + + /* + * Outstanding requests + */ + int num_reqs; + struct async_req **reqs; + + /* + * For how many bytes did we send requests already? + */ + SMB_OFF_T requested; + + /* + * Next request index to push into "sink". This walks around the "req" + * array, taking care that the requests are pushed to "sink" in the + * right order. If necessary (i.e. replies don't come in in the right + * order), replies are held back in "reqs". + */ + int top_req; + + /* + * How many bytes did we push into "sink"? + */ + + SMB_OFF_T pushed; +}; + +static char *cli_pull_print(TALLOC_CTX *mem_ctx, struct async_req *req) +{ + struct cli_pull_state *state = talloc_get_type_abort( + req->private_data, struct cli_pull_state); + char *result; + + result = async_req_print(mem_ctx, req); + if (result == NULL) { + return NULL; + } + + return talloc_asprintf_append_buffer( + result, "num_reqs=%d, top_req=%d", + state->num_reqs, state->top_req); +} + +static void cli_pull_read_done(struct async_req *read_req); + +/* + * Prepare an async pull request + */ + +struct async_req *cli_pull_send(TALLOC_CTX *mem_ctx, + struct event_context *ev, + struct cli_state *cli, + uint16_t fnum, off_t start_offset, + SMB_OFF_T size, size_t window_size, + NTSTATUS (*sink)(char *buf, size_t n, + void *priv), + void *priv) +{ + struct async_req *result; + struct cli_pull_state *state; + int i; + + result = async_req_new(mem_ctx, ev); + if (result == NULL) { + goto failed; + } + state = talloc(result, struct cli_pull_state); + if (state == NULL) { + goto failed; + } + result->private_data = state; + result->print = cli_pull_print; + state->req = result; + + state->cli = cli; + state->ev = ev; + state->fnum = fnum; + state->start_offset = start_offset; + state->size = size; + state->sink = sink; + state->priv = priv; + + state->pushed = 0; + state->top_req = 0; + + if (size == 0) { + if (!async_post_status(result, NT_STATUS_OK)) { + goto failed; + } + return result; + } + + state->chunk_size = cli_read_max_bufsize(cli); + + state->num_reqs = MAX(window_size/state->chunk_size, 1); + state->num_reqs = MIN(state->num_reqs, cli->max_mux); + + state->reqs = TALLOC_ZERO_ARRAY(state, struct async_req *, + state->num_reqs); + if (state->reqs == NULL) { + goto failed; + } + + state->requested = 0; + + for (i=0; i<state->num_reqs; i++) { + SMB_OFF_T size_left; + size_t request_thistime; + + if (state->requested >= size) { + state->num_reqs = i; + break; + } + + size_left = size - state->requested; + request_thistime = MIN(size_left, state->chunk_size); + + state->reqs[i] = cli_read_andx_send( + state->reqs, ev, cli, fnum, + state->start_offset + state->requested, + request_thistime); + + if (state->reqs[i] == NULL) { + goto failed; + } + + state->reqs[i]->async.fn = cli_pull_read_done; + state->reqs[i]->async.priv = result; + + state->requested += request_thistime; + } + return result; + +failed: + TALLOC_FREE(result); + return NULL; +} + +/* + * Handle incoming read replies, push the data into sink and send out new + * requests if necessary. + */ + +static void cli_pull_read_done(struct async_req *read_req) +{ + struct async_req *pull_req = talloc_get_type_abort( + read_req->async.priv, struct async_req); + struct cli_pull_state *state = talloc_get_type_abort( + pull_req->private_data, struct cli_pull_state); + struct cli_request *read_state = talloc_get_type_abort( + read_req->private_data, struct cli_request); + NTSTATUS status; + + status = cli_read_andx_recv(read_req, &read_state->data.read.received, + &read_state->data.read.rcvbuf); + if (!NT_STATUS_IS_OK(status)) { + async_req_error(state->req, status); + return; + } + + /* + * This loop is the one to take care of out-of-order replies. All + * pending requests are in state->reqs, state->reqs[top_req] is the + * one that is to be pushed next. If however a request later than + * top_req is replied to, then we can't push yet. If top_req is + * replied to at a later point then, we need to push all the finished + * requests. + */ + + while (state->reqs[state->top_req] != NULL) { + struct cli_request *top_read; + + DEBUG(11, ("cli_pull_read_done: top_req = %d\n", + state->top_req)); + + if (state->reqs[state->top_req]->state < ASYNC_REQ_DONE) { + DEBUG(11, ("cli_pull_read_done: top request not yet " + "done\n")); + return; + } + + top_read = talloc_get_type_abort( + state->reqs[state->top_req]->private_data, + struct cli_request); + + DEBUG(10, ("cli_pull_read_done: Pushing %d bytes, %d already " + "pushed\n", (int)top_read->data.read.received, + (int)state->pushed)); + + status = state->sink((char *)top_read->data.read.rcvbuf, + top_read->data.read.received, + state->priv); + if (!NT_STATUS_IS_OK(status)) { + async_req_error(state->req, status); + return; + } + state->pushed += top_read->data.read.received; + + TALLOC_FREE(state->reqs[state->top_req]); + + if (state->requested < state->size) { + struct async_req *new_req; + SMB_OFF_T size_left; + size_t request_thistime; + + size_left = state->size - state->requested; + request_thistime = MIN(size_left, state->chunk_size); + + DEBUG(10, ("cli_pull_read_done: Requesting %d bytes " + "at %d, position %d\n", + (int)request_thistime, + (int)(state->start_offset + + state->requested), + state->top_req)); + + new_req = cli_read_andx_send( + state->reqs, state->ev, state->cli, + state->fnum, + state->start_offset + state->requested, + request_thistime); + + if (async_req_nomem(new_req, state->req)) { + return; + } + + new_req->async.fn = cli_pull_read_done; + new_req->async.priv = pull_req; + + state->reqs[state->top_req] = new_req; + state->requested += request_thistime; + } + + state->top_req = (state->top_req+1) % state->num_reqs; + } + + async_req_done(pull_req); +} + +NTSTATUS cli_pull_recv(struct async_req *req, SMB_OFF_T *received) +{ + struct cli_pull_state *state = talloc_get_type_abort( + req->private_data, struct cli_pull_state); + + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + *received = state->pushed; + return NT_STATUS_OK; +} + +NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum, + off_t start_offset, SMB_OFF_T size, size_t window_size, + NTSTATUS (*sink)(char *buf, size_t n, void *priv), + void *priv, SMB_OFF_T *received) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct async_req *req; + NTSTATUS result = NT_STATUS_NO_MEMORY; + + if (cli->fd_event != NULL) { + /* + * Can't use sync call while an async call is in flight + */ + return NT_STATUS_INVALID_PARAMETER; + } + + ev = event_context_init(frame); + if (ev == NULL) { + goto nomem; + } + + req = cli_pull_send(frame, ev, cli, fnum, start_offset, size, + window_size, sink, priv); + if (req == NULL) { + goto nomem; + } + + while (req->state < ASYNC_REQ_DONE) { + event_loop_once(ev); + } + + result = cli_pull_recv(req, received); + nomem: + TALLOC_FREE(frame); + return result; +} + +static NTSTATUS cli_read_sink(char *buf, size_t n, void *priv) +{ + char **pbuf = (char **)priv; + memcpy(*pbuf, buf, n); + *pbuf += n; + return NT_STATUS_OK; +} + +ssize_t cli_read(struct cli_state *cli, int fnum, char *buf, + off_t offset, size_t size) +{ + NTSTATUS status; + SMB_OFF_T ret; + + status = cli_pull(cli, fnum, offset, size, size, + cli_read_sink, &buf, &ret); + if (!NT_STATUS_IS_OK(status)) { + cli_set_error(cli, status); + return -1; + } + return ret; +} + +/**************************************************************************** + Issue a single SMBwrite and don't wait for a reply. +****************************************************************************/ + +static bool cli_issue_write(struct cli_state *cli, + int fnum, + off_t offset, + uint16 mode, + const char *buf, + size_t size, + int i) +{ + char *p; + bool large_writex = false; + /* We can only do direct writes if not signing and not encrypting. */ + bool direct_writes = !client_is_signing_on(cli) && !cli_encryption_on(cli); + + if (!direct_writes && size + 1 > cli->bufsize) { + cli->outbuf = (char *)SMB_REALLOC(cli->outbuf, size + 1024); + if (!cli->outbuf) { + return False; + } + cli->inbuf = (char *)SMB_REALLOC(cli->inbuf, size + 1024); + if (cli->inbuf == NULL) { + SAFE_FREE(cli->outbuf); + return False; + } + cli->bufsize = size + 1024; + } + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + if (cli->capabilities & CAP_LARGE_FILES) { + large_writex = True; + } + + if (large_writex) { + cli_set_message(cli->outbuf,14,0,True); + } else { + cli_set_message(cli->outbuf,12,0,True); + } + + SCVAL(cli->outbuf,smb_com,SMBwriteX); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SCVAL(cli->outbuf,smb_vwv0,0xFF); + SSVAL(cli->outbuf,smb_vwv2,fnum); + + SIVAL(cli->outbuf,smb_vwv3,offset); + SIVAL(cli->outbuf,smb_vwv5,0); + SSVAL(cli->outbuf,smb_vwv7,mode); + + SSVAL(cli->outbuf,smb_vwv8,(mode & 0x0008) ? size : 0); + /* + * According to CIFS-TR-1p00, this following field should only + * be set if CAP_LARGE_WRITEX is set. We should check this + * locally. However, this check might already have been + * done by our callers. + */ + SSVAL(cli->outbuf,smb_vwv9,(size>>16)); + SSVAL(cli->outbuf,smb_vwv10,size); + /* +1 is pad byte. */ + SSVAL(cli->outbuf,smb_vwv11, + smb_buf(cli->outbuf) - smb_base(cli->outbuf) + 1); + + if (large_writex) { + SIVAL(cli->outbuf,smb_vwv12,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff); + } + + p = smb_base(cli->outbuf) + SVAL(cli->outbuf,smb_vwv11) -1; + *p++ = '\0'; /* pad byte. */ + if (!direct_writes) { + memcpy(p, buf, size); + } + if (size > 0x1FFFF) { + /* This is a POSIX 14 word large write. */ + set_message_bcc(cli->outbuf, 0); /* Set bcc to zero. */ + _smb_setlen_large(cli->outbuf,smb_size + 28 + 1 /* pad */ + size - 4); + } else { + cli_setup_bcc(cli, p+size); + } + + SSVAL(cli->outbuf,smb_mid,cli->mid + i); + + show_msg(cli->outbuf); + if (direct_writes) { + /* For direct writes we now need to write the data + * directly out of buf. */ + return cli_send_smb_direct_writeX(cli, buf, size); + } else { + return cli_send_smb(cli); + } +} + +/**************************************************************************** + write to a file + write_mode: 0x0001 disallow write cacheing + 0x0002 return bytes remaining + 0x0004 use raw named pipe protocol + 0x0008 start of message mode named pipe protocol +****************************************************************************/ + +ssize_t cli_write(struct cli_state *cli, + int fnum, uint16 write_mode, + const char *buf, off_t offset, size_t size) +{ + ssize_t bwritten = 0; + unsigned int issued = 0; + unsigned int received = 0; + int mpx = 1; + size_t writesize; + int blocks; + + if(cli->max_mux > 1) { + mpx = cli->max_mux-1; + } else { + mpx = 1; + } + + /* Default (small) writesize. */ + writesize = (cli->max_xmit - (smb_size+32)) & ~1023; + + if (write_mode == 0 && + !client_is_signing_on(cli) && + !cli_encryption_on(cli) && + (cli->posix_capabilities & CIFS_UNIX_LARGE_WRITE_CAP) && + (cli->capabilities & CAP_LARGE_FILES)) { + /* Only do massive writes if we can do them direct + * with no signing or encrypting - not on a pipe. */ + writesize = CLI_SAMBA_MAX_POSIX_LARGE_WRITEX_SIZE; + } else if ((cli->capabilities & CAP_LARGE_WRITEX) && + (strcmp(cli->dev, "LPT1:") != 0)) { + + /* Printer devices are restricted to max_xmit + * writesize in Vista and XPSP3. */ + + if (cli->is_samba) { + writesize = CLI_SAMBA_MAX_LARGE_WRITEX_SIZE; + } else if (!client_is_signing_on(cli)) { + /* Windows restricts signed writes to max_xmit. + * Found by Volker. */ + writesize = CLI_WINDOWS_MAX_LARGE_WRITEX_SIZE; + } + } + + blocks = (size + (writesize-1)) / writesize; + + while (received < blocks) { + + while ((issued - received < mpx) && (issued < blocks)) { + ssize_t bsent = issued * writesize; + ssize_t size1 = MIN(writesize, size - bsent); + + if (!cli_issue_write(cli, fnum, offset + bsent, + write_mode, + buf + bsent, + size1, issued)) + return -1; + issued++; + } + + if (!cli_receive_smb(cli)) { + return bwritten; + } + + received++; + + if (cli_is_error(cli)) + break; + + bwritten += SVAL(cli->inbuf, smb_vwv2); + if (writesize > 0xFFFF) { + bwritten += (((int)(SVAL(cli->inbuf, smb_vwv4)))<<16); + } + } + + while (received < issued && cli_receive_smb(cli)) { + received++; + } + + return bwritten; +} + +/**************************************************************************** + write to a file using a SMBwrite and not bypassing 0 byte writes +****************************************************************************/ + +ssize_t cli_smbwrite(struct cli_state *cli, + int fnum, char *buf, off_t offset, size_t size1) +{ + char *p; + ssize_t total = 0; + + do { + size_t size = MIN(size1, cli->max_xmit - 48); + + memset(cli->outbuf,'\0',smb_size); + memset(cli->inbuf,'\0',smb_size); + + cli_set_message(cli->outbuf,5, 0,True); + + SCVAL(cli->outbuf,smb_com,SMBwrite); + SSVAL(cli->outbuf,smb_tid,cli->cnum); + cli_setup_packet(cli); + + SSVAL(cli->outbuf,smb_vwv0,fnum); + SSVAL(cli->outbuf,smb_vwv1,size); + SIVAL(cli->outbuf,smb_vwv2,offset); + SSVAL(cli->outbuf,smb_vwv4,0); + + p = smb_buf(cli->outbuf); + *p++ = 1; + SSVAL(p, 0, size); p += 2; + memcpy(p, buf + total, size); p += size; + + cli_setup_bcc(cli, p); + + if (!cli_send_smb(cli)) + return -1; + + if (!cli_receive_smb(cli)) + return -1; + + if (cli_is_error(cli)) + return -1; + + size = SVAL(cli->inbuf,smb_vwv0); + if (size == 0) + break; + + size1 -= size; + total += size; + offset += size; + + } while (size1); + + return total; +} diff --git a/source3/libsmb/clisecdesc.c b/source3/libsmb/clisecdesc.c new file mode 100644 index 0000000000..f0b786c899 --- /dev/null +++ b/source3/libsmb/clisecdesc.c @@ -0,0 +1,130 @@ +/* + Unix SMB/CIFS implementation. + client security descriptor functions + Copyright (C) Andrew Tridgell 2000 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + query the security descriptor for a open file + ****************************************************************************/ +SEC_DESC *cli_query_secdesc(struct cli_state *cli, int fnum, + TALLOC_CTX *mem_ctx) +{ + uint8_t param[8]; + uint8_t *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + SEC_DESC *psd = NULL; + NTSTATUS status; + + SIVAL(param, 0, fnum); + SIVAL(param, 4, 0x7); + + status = cli_trans(talloc_tos(), cli, SMBnttrans, + NULL, -1, /* name, fid */ + NT_TRANSACT_QUERY_SECURITY_DESC, 0, /* function, flags */ + NULL, 0, 0, /* setup, length, max */ + param, 8, 4, /* param, length, max */ + NULL, 0, 0x10000, /* data, length, max */ + NULL, NULL, /* rsetup, length */ + &rparam, &rparam_count, + &rdata, &rdata_count); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("NT_TRANSACT_QUERY_SECURITY_DESC failed: %s\n", + nt_errstr(status))); + goto cleanup; + } + + status = unmarshall_sec_desc(mem_ctx, (uint8 *)rdata, rdata_count, + &psd); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("unmarshall_sec_desc failed: %s\n", + nt_errstr(status))); + goto cleanup; + } + + cleanup: + + TALLOC_FREE(rparam); + TALLOC_FREE(rdata); + + return psd; +} + +/**************************************************************************** + set the security descriptor for a open file + ****************************************************************************/ +bool cli_set_secdesc(struct cli_state *cli, int fnum, SEC_DESC *sd) +{ + char param[8]; + char *rparam=NULL, *rdata=NULL; + unsigned int rparam_count=0, rdata_count=0; + uint32 sec_info = 0; + TALLOC_CTX *frame = talloc_stackframe(); + bool ret = False; + uint8 *data; + size_t len; + NTSTATUS status; + + status = marshall_sec_desc(talloc_tos(), sd, &data, &len); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("marshall_sec_desc failed: %s\n", + nt_errstr(status))); + goto cleanup; + } + + SIVAL(param, 0, fnum); + + if (sd->dacl) + sec_info |= DACL_SECURITY_INFORMATION; + if (sd->owner_sid) + sec_info |= OWNER_SECURITY_INFORMATION; + if (sd->group_sid) + sec_info |= GROUP_SECURITY_INFORMATION; + SSVAL(param, 4, sec_info); + + if (!cli_send_nt_trans(cli, + NT_TRANSACT_SET_SECURITY_DESC, + 0, + NULL, 0, 0, + param, 8, 0, + (char *)data, len, 0)) { + DEBUG(1,("Failed to send NT_TRANSACT_SET_SECURITY_DESC\n")); + goto cleanup; + } + + + if (!cli_receive_nt_trans(cli, + &rparam, &rparam_count, + &rdata, &rdata_count)) { + DEBUG(1,("NT_TRANSACT_SET_SECURITY_DESC failed\n")); + goto cleanup; + } + + ret = True; + + cleanup: + + SAFE_FREE(rparam); + SAFE_FREE(rdata); + + TALLOC_FREE(frame); + + return ret; +} diff --git a/source3/libsmb/clispnego.c b/source3/libsmb/clispnego.c new file mode 100644 index 0000000000..fa9dba098f --- /dev/null +++ b/source3/libsmb/clispnego.c @@ -0,0 +1,600 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5/SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + Copyright (C) Luke Howard 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* + generate a negTokenInit packet given a GUID, a list of supported + OIDs (the mechanisms) and a principal name string +*/ +DATA_BLOB spnego_gen_negTokenInit(char guid[16], + const char *OIDs[], + const char *principal) +{ + int i; + ASN1_DATA data; + DATA_BLOB ret; + + memset(&data, 0, sizeof(data)); + + asn1_write(&data, guid, 16); + asn1_push_tag(&data,ASN1_APPLICATION(0)); + asn1_write_OID(&data,OID_SPNEGO); + asn1_push_tag(&data,ASN1_CONTEXT(0)); + asn1_push_tag(&data,ASN1_SEQUENCE(0)); + + asn1_push_tag(&data,ASN1_CONTEXT(0)); + asn1_push_tag(&data,ASN1_SEQUENCE(0)); + for (i=0; OIDs[i]; i++) { + asn1_write_OID(&data,OIDs[i]); + } + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_push_tag(&data, ASN1_CONTEXT(3)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_write_GeneralString(&data,principal); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + + if (data.has_error) { + DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data.ofs)); + asn1_free(&data); + } + + ret = data_blob(data.data, data.length); + asn1_free(&data); + + return ret; +} + +/* + Generate a negTokenInit as used by the client side ... It has a mechType + (OID), and a mechToken (a security blob) ... + + Really, we need to break out the NTLMSSP stuff as well, because it could be + raw in the packets! +*/ +DATA_BLOB gen_negTokenInit(const char *OID, DATA_BLOB blob) +{ + ASN1_DATA data; + DATA_BLOB ret; + + memset(&data, 0, sizeof(data)); + + asn1_push_tag(&data, ASN1_APPLICATION(0)); + asn1_write_OID(&data,OID_SPNEGO); + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + asn1_write_OID(&data, OID); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_push_tag(&data, ASN1_CONTEXT(2)); + asn1_write_OctetString(&data,blob.data,blob.length); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + + if (data.has_error) { + DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data.ofs)); + asn1_free(&data); + } + + ret = data_blob(data.data, data.length); + asn1_free(&data); + + return ret; +} + +/* + parse a negTokenInit packet giving a GUID, a list of supported + OIDs (the mechanisms) and a principal name string +*/ +bool spnego_parse_negTokenInit(DATA_BLOB blob, + char *OIDs[ASN1_MAX_OIDS], + char **principal) +{ + int i; + bool ret; + ASN1_DATA data; + + asn1_load(&data, blob); + + asn1_start_tag(&data,ASN1_APPLICATION(0)); + asn1_check_OID(&data,OID_SPNEGO); + asn1_start_tag(&data,ASN1_CONTEXT(0)); + asn1_start_tag(&data,ASN1_SEQUENCE(0)); + + asn1_start_tag(&data,ASN1_CONTEXT(0)); + asn1_start_tag(&data,ASN1_SEQUENCE(0)); + for (i=0; asn1_tag_remaining(&data) > 0 && i < ASN1_MAX_OIDS-1; i++) { + char *oid_str = NULL; + asn1_read_OID(&data,&oid_str); + OIDs[i] = oid_str; + } + OIDs[i] = NULL; + asn1_end_tag(&data); + asn1_end_tag(&data); + + *principal = NULL; + if (asn1_tag_remaining(&data) > 0) { + asn1_start_tag(&data, ASN1_CONTEXT(3)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_read_GeneralString(&data,principal); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + } + + asn1_end_tag(&data); + asn1_end_tag(&data); + + asn1_end_tag(&data); + + ret = !data.has_error; + if (data.has_error) { + int j; + SAFE_FREE(*principal); + for(j = 0; j < i && j < ASN1_MAX_OIDS-1; j++) { + SAFE_FREE(OIDs[j]); + } + } + + asn1_free(&data); + return ret; +} + +/* + generate a negTokenTarg packet given a list of OIDs and a security blob +*/ +DATA_BLOB gen_negTokenTarg(const char *OIDs[], DATA_BLOB blob) +{ + int i; + ASN1_DATA data; + DATA_BLOB ret; + + memset(&data, 0, sizeof(data)); + + asn1_push_tag(&data, ASN1_APPLICATION(0)); + asn1_write_OID(&data,OID_SPNEGO); + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + for (i=0; OIDs[i]; i++) { + asn1_write_OID(&data,OIDs[i]); + } + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_push_tag(&data, ASN1_CONTEXT(2)); + asn1_write_OctetString(&data,blob.data,blob.length); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + asn1_pop_tag(&data); + + if (data.has_error) { + DEBUG(1,("Failed to build negTokenTarg at offset %d\n", (int)data.ofs)); + asn1_free(&data); + } + + ret = data_blob(data.data, data.length); + asn1_free(&data); + + return ret; +} + +/* + parse a negTokenTarg packet giving a list of OIDs and a security blob +*/ +bool parse_negTokenTarg(DATA_BLOB blob, char *OIDs[ASN1_MAX_OIDS], DATA_BLOB *secblob) +{ + int i; + ASN1_DATA data; + + asn1_load(&data, blob); + asn1_start_tag(&data, ASN1_APPLICATION(0)); + asn1_check_OID(&data,OID_SPNEGO); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + for (i=0; asn1_tag_remaining(&data) > 0 && i < ASN1_MAX_OIDS-1; i++) { + char *oid_str = NULL; + asn1_read_OID(&data,&oid_str); + OIDs[i] = oid_str; + } + OIDs[i] = NULL; + asn1_end_tag(&data); + asn1_end_tag(&data); + + /* Skip any optional req_flags that are sent per RFC 4178 */ + if (asn1_check_tag(&data, ASN1_CONTEXT(1))) { + uint8 flags; + + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_start_tag(&data, ASN1_BITFIELD); + while (asn1_tag_remaining(&data) > 0) + asn1_read_uint8(&data, &flags); + asn1_end_tag(&data); + asn1_end_tag(&data); + } + + asn1_start_tag(&data, ASN1_CONTEXT(2)); + asn1_read_OctetString(&data,secblob); + asn1_end_tag(&data); + + asn1_end_tag(&data); + asn1_end_tag(&data); + + asn1_end_tag(&data); + + if (data.has_error) { + int j; + data_blob_free(secblob); + for(j = 0; j < i && j < ASN1_MAX_OIDS-1; j++) { + SAFE_FREE(OIDs[j]); + } + DEBUG(1,("Failed to parse negTokenTarg at offset %d\n", (int)data.ofs)); + asn1_free(&data); + return False; + } + + asn1_free(&data); + return True; +} + +/* + generate a krb5 GSS-API wrapper packet given a ticket +*/ +DATA_BLOB spnego_gen_krb5_wrap(const DATA_BLOB ticket, const uint8 tok_id[2]) +{ + ASN1_DATA data; + DATA_BLOB ret; + + memset(&data, 0, sizeof(data)); + + asn1_push_tag(&data, ASN1_APPLICATION(0)); + asn1_write_OID(&data, OID_KERBEROS5); + + asn1_write(&data, tok_id, 2); + asn1_write(&data, ticket.data, ticket.length); + asn1_pop_tag(&data); + + if (data.has_error) { + DEBUG(1,("Failed to build krb5 wrapper at offset %d\n", (int)data.ofs)); + asn1_free(&data); + } + + ret = data_blob(data.data, data.length); + asn1_free(&data); + + return ret; +} + +/* + parse a krb5 GSS-API wrapper packet giving a ticket +*/ +bool spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket, uint8 tok_id[2]) +{ + bool ret; + ASN1_DATA data; + int data_remaining; + + asn1_load(&data, blob); + asn1_start_tag(&data, ASN1_APPLICATION(0)); + asn1_check_OID(&data, OID_KERBEROS5); + + data_remaining = asn1_tag_remaining(&data); + + if (data_remaining < 3) { + data.has_error = True; + } else { + asn1_read(&data, tok_id, 2); + data_remaining -= 2; + *ticket = data_blob(NULL, data_remaining); + asn1_read(&data, ticket->data, ticket->length); + } + + asn1_end_tag(&data); + + ret = !data.has_error; + + if (data.has_error) { + data_blob_free(ticket); + } + + asn1_free(&data); + + return ret; +} + + +/* + generate a SPNEGO negTokenTarg packet, ready for a EXTENDED_SECURITY + kerberos session setup +*/ +int spnego_gen_negTokenTarg(const char *principal, int time_offset, + DATA_BLOB *targ, + DATA_BLOB *session_key_krb5, uint32 extra_ap_opts, + time_t *expire_time) +{ + int retval; + DATA_BLOB tkt, tkt_wrapped; + const char *krb_mechs[] = {OID_KERBEROS5_OLD, OID_KERBEROS5, OID_NTLMSSP, NULL}; + + /* get a kerberos ticket for the service and extract the session key */ + retval = cli_krb5_get_ticket(principal, time_offset, + &tkt, session_key_krb5, extra_ap_opts, NULL, + expire_time); + + if (retval) + return retval; + + /* wrap that up in a nice GSS-API wrapping */ + tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ); + + /* and wrap that in a shiny SPNEGO wrapper */ + *targ = gen_negTokenTarg(krb_mechs, tkt_wrapped); + + data_blob_free(&tkt_wrapped); + data_blob_free(&tkt); + + return retval; +} + + +/* + parse a spnego NTLMSSP challenge packet giving two security blobs +*/ +bool spnego_parse_challenge(const DATA_BLOB blob, + DATA_BLOB *chal1, DATA_BLOB *chal2) +{ + bool ret; + ASN1_DATA data; + + ZERO_STRUCTP(chal1); + ZERO_STRUCTP(chal2); + + asn1_load(&data, blob); + asn1_start_tag(&data,ASN1_CONTEXT(1)); + asn1_start_tag(&data,ASN1_SEQUENCE(0)); + + asn1_start_tag(&data,ASN1_CONTEXT(0)); + asn1_check_enumerated(&data,1); + asn1_end_tag(&data); + + asn1_start_tag(&data,ASN1_CONTEXT(1)); + asn1_check_OID(&data, OID_NTLMSSP); + asn1_end_tag(&data); + + asn1_start_tag(&data,ASN1_CONTEXT(2)); + asn1_read_OctetString(&data, chal1); + asn1_end_tag(&data); + + /* the second challenge is optional (XP doesn't send it) */ + if (asn1_tag_remaining(&data)) { + asn1_start_tag(&data,ASN1_CONTEXT(3)); + asn1_read_OctetString(&data, chal2); + asn1_end_tag(&data); + } + + asn1_end_tag(&data); + asn1_end_tag(&data); + + ret = !data.has_error; + + if (data.has_error) { + data_blob_free(chal1); + data_blob_free(chal2); + } + + asn1_free(&data); + return ret; +} + + +/* + generate a SPNEGO auth packet. This will contain the encrypted passwords +*/ +DATA_BLOB spnego_gen_auth(DATA_BLOB blob) +{ + ASN1_DATA data; + DATA_BLOB ret; + + memset(&data, 0, sizeof(data)); + + asn1_push_tag(&data, ASN1_CONTEXT(1)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + asn1_push_tag(&data, ASN1_CONTEXT(2)); + asn1_write_OctetString(&data,blob.data,blob.length); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + ret = data_blob(data.data, data.length); + + asn1_free(&data); + + return ret; +} + +/* + parse a SPNEGO auth packet. This contains the encrypted passwords +*/ +bool spnego_parse_auth(DATA_BLOB blob, DATA_BLOB *auth) +{ + ASN1_DATA data; + + asn1_load(&data, blob); + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(2)); + asn1_read_OctetString(&data,auth); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + + if (data.has_error) { + DEBUG(3,("spnego_parse_auth failed at %d\n", (int)data.ofs)); + data_blob_free(auth); + asn1_free(&data); + return False; + } + + asn1_free(&data); + return True; +} + +/* + generate a minimal SPNEGO response packet. Doesn't contain much. +*/ +DATA_BLOB spnego_gen_auth_response(DATA_BLOB *reply, NTSTATUS nt_status, + const char *mechOID) +{ + ASN1_DATA data; + DATA_BLOB ret; + uint8 negResult; + + if (NT_STATUS_IS_OK(nt_status)) { + negResult = SPNEGO_NEG_RESULT_ACCEPT; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + negResult = SPNEGO_NEG_RESULT_INCOMPLETE; + } else { + negResult = SPNEGO_NEG_RESULT_REJECT; + } + + ZERO_STRUCT(data); + + asn1_push_tag(&data, ASN1_CONTEXT(1)); + asn1_push_tag(&data, ASN1_SEQUENCE(0)); + asn1_push_tag(&data, ASN1_CONTEXT(0)); + asn1_write_enumerated(&data, negResult); + asn1_pop_tag(&data); + + if (mechOID) { + asn1_push_tag(&data,ASN1_CONTEXT(1)); + asn1_write_OID(&data, mechOID); + asn1_pop_tag(&data); + } + + if (reply && reply->data != NULL) { + asn1_push_tag(&data,ASN1_CONTEXT(2)); + asn1_write_OctetString(&data, reply->data, reply->length); + asn1_pop_tag(&data); + } + + asn1_pop_tag(&data); + asn1_pop_tag(&data); + + ret = data_blob(data.data, data.length); + asn1_free(&data); + return ret; +} + +/* + parse a SPNEGO auth packet. This contains the encrypted passwords +*/ +bool spnego_parse_auth_response(DATA_BLOB blob, NTSTATUS nt_status, + const char *mechOID, + DATA_BLOB *auth) +{ + ASN1_DATA data; + uint8 negResult; + + if (NT_STATUS_IS_OK(nt_status)) { + negResult = SPNEGO_NEG_RESULT_ACCEPT; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + negResult = SPNEGO_NEG_RESULT_INCOMPLETE; + } else { + negResult = SPNEGO_NEG_RESULT_REJECT; + } + + asn1_load(&data, blob); + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_check_enumerated(&data, negResult); + asn1_end_tag(&data); + + *auth = data_blob_null; + + if (asn1_tag_remaining(&data)) { + asn1_start_tag(&data,ASN1_CONTEXT(1)); + asn1_check_OID(&data, mechOID); + asn1_end_tag(&data); + + if (asn1_tag_remaining(&data)) { + asn1_start_tag(&data,ASN1_CONTEXT(2)); + asn1_read_OctetString(&data, auth); + asn1_end_tag(&data); + } + } else if (negResult == SPNEGO_NEG_RESULT_INCOMPLETE) { + data.has_error = 1; + } + + /* Binding against Win2K DC returns a duplicate of the responseToken in + * the optional mechListMIC field. This is a bug in Win2K. We ignore + * this field if it exists. Win2K8 may return a proper mechListMIC at + * which point we need to implement the integrity checking. */ + if (asn1_tag_remaining(&data)) { + DATA_BLOB mechList = data_blob_null; + asn1_start_tag(&data, ASN1_CONTEXT(3)); + asn1_read_OctetString(&data, &mechList); + asn1_end_tag(&data); + data_blob_free(&mechList); + DEBUG(5,("spnego_parse_auth_response received mechListMIC, " + "ignoring.\n")); + } + + asn1_end_tag(&data); + asn1_end_tag(&data); + + if (data.has_error) { + DEBUG(3,("spnego_parse_auth_response failed at %d\n", (int)data.ofs)); + asn1_free(&data); + data_blob_free(auth); + return False; + } + + asn1_free(&data); + return True; +} diff --git a/source3/libsmb/clistr.c b/source3/libsmb/clistr.c new file mode 100644 index 0000000000..5d20d632aa --- /dev/null +++ b/source3/libsmb/clistr.c @@ -0,0 +1,94 @@ +/* + Unix SMB/CIFS implementation. + client string routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +size_t clistr_push_fn(const char *function, + unsigned int line, + struct cli_state *cli, + void *dest, + const char *src, + int dest_len, + int flags) +{ + size_t buf_used = PTR_DIFF(dest, cli->outbuf); + if (dest_len == -1) { + if (((ptrdiff_t)dest < (ptrdiff_t)cli->outbuf) || (buf_used > cli->bufsize)) { + DEBUG(0, ("Pushing string of 'unlimited' length into non-SMB buffer!\n")); + return push_string_fn(function, line, + cli->outbuf, + SVAL(cli->outbuf, smb_flg2), + dest, src, -1, flags); + } + return push_string_fn(function, line, cli->outbuf, + SVAL(cli->outbuf, smb_flg2), + dest, src, cli->bufsize - buf_used, + flags); + } + + /* 'normal' push into size-specified buffer */ + return push_string_fn(function, line, cli->outbuf, + SVAL(cli->outbuf, smb_flg2), + dest, src, dest_len, flags); +} + +size_t clistr_pull_fn(const char *function, + unsigned int line, + struct cli_state *cli, + char *dest, + const void *src, + int dest_len, + int src_len, + int flags) +{ + return pull_string_fn(function, line, cli->inbuf, + SVAL(cli->inbuf, smb_flg2), dest, src, dest_len, + src_len, flags); +} + +size_t clistr_pull_talloc_fn(const char *function, + unsigned int line, + TALLOC_CTX *ctx, + struct cli_state *cli, + char **pp_dest, + const void *src, + int src_len, + int flags) +{ + return pull_string_talloc_fn(function, + line, + ctx, + cli->inbuf, + SVAL(cli->inbuf, smb_flg2), + pp_dest, + src, + src_len, + flags); +} + +size_t clistr_align_out(struct cli_state *cli, const void *p, int flags) +{ + return align_string(cli->outbuf, (const char *)p, flags); +} + +size_t clistr_align_in(struct cli_state *cli, const void *p, int flags) +{ + return align_string(cli->inbuf, (const char *)p, flags); +} diff --git a/source3/libsmb/clitrans.c b/source3/libsmb/clitrans.c new file mode 100644 index 0000000000..c929f0b7a9 --- /dev/null +++ b/source3/libsmb/clitrans.c @@ -0,0 +1,1409 @@ +/* + Unix SMB/CIFS implementation. + client transaction calls + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + + +/**************************************************************************** + Send a SMB trans or trans2 request. +****************************************************************************/ + +bool cli_send_trans(struct cli_state *cli, int trans, + const char *pipe_name, + int fid, int flags, + uint16 *setup, unsigned int lsetup, unsigned int msetup, + const char *param, unsigned int lparam, unsigned int mparam, + const char *data, unsigned int ldata, unsigned int mdata) +{ + unsigned int i; + unsigned int this_ldata,this_lparam; + unsigned int tot_data=0,tot_param=0; + char *outdata,*outparam; + char *p; + int pipe_name_len=0; + uint16 mid; + + this_lparam = MIN(lparam,cli->max_xmit - (500+lsetup*2)); /* hack */ + this_ldata = MIN(ldata,cli->max_xmit - (500+lsetup*2+this_lparam)); + + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,14+lsetup,0,True); + SCVAL(cli->outbuf,smb_com,trans); + SSVAL(cli->outbuf,smb_tid, cli->cnum); + cli_setup_packet(cli); + + /* + * Save the mid we're using. We need this for finding + * signing replies. + */ + + mid = cli->mid; + + if (pipe_name) { + pipe_name_len = clistr_push(cli, smb_buf(cli->outbuf), pipe_name, -1, STR_TERMINATE); + } + + outparam = smb_buf(cli->outbuf)+(trans==SMBtrans ? pipe_name_len : 3); + outdata = outparam+this_lparam; + + /* primary request */ + SSVAL(cli->outbuf,smb_tpscnt,lparam); /* tpscnt */ + SSVAL(cli->outbuf,smb_tdscnt,ldata); /* tdscnt */ + SSVAL(cli->outbuf,smb_mprcnt,mparam); /* mprcnt */ + SSVAL(cli->outbuf,smb_mdrcnt,mdata); /* mdrcnt */ + SCVAL(cli->outbuf,smb_msrcnt,msetup); /* msrcnt */ + SSVAL(cli->outbuf,smb_flags,flags); /* flags */ + SIVAL(cli->outbuf,smb_timeout,0); /* timeout */ + SSVAL(cli->outbuf,smb_pscnt,this_lparam); /* pscnt */ + SSVAL(cli->outbuf,smb_psoff,smb_offset(outparam,cli->outbuf)); /* psoff */ + SSVAL(cli->outbuf,smb_dscnt,this_ldata); /* dscnt */ + SSVAL(cli->outbuf,smb_dsoff,smb_offset(outdata,cli->outbuf)); /* dsoff */ + SCVAL(cli->outbuf,smb_suwcnt,lsetup); /* suwcnt */ + for (i=0;i<lsetup;i++) /* setup[] */ + SSVAL(cli->outbuf,smb_setup+i*2,setup[i]); + p = smb_buf(cli->outbuf); + if (trans != SMBtrans) { + *p++ = 0; /* put in a null smb_name */ + *p++ = 'D'; *p++ = ' '; /* observed in OS/2 */ + } + if (this_lparam) /* param[] */ + memcpy(outparam,param,this_lparam); + if (this_ldata) /* data[] */ + memcpy(outdata,data,this_ldata); + cli_setup_bcc(cli, outdata+this_ldata); + + show_msg(cli->outbuf); + + if (!cli_send_smb(cli)) { + return False; + } + + /* Note we're in a trans state. Save the sequence + * numbers for replies. */ + client_set_trans_sign_state_on(cli, mid); + + if (this_ldata < ldata || this_lparam < lparam) { + /* receive interim response */ + if (!cli_receive_smb(cli) || cli_is_error(cli)) { + client_set_trans_sign_state_off(cli, mid); + return(False); + } + + tot_data = this_ldata; + tot_param = this_lparam; + + while (tot_data < ldata || tot_param < lparam) { + this_lparam = MIN(lparam-tot_param,cli->max_xmit - 500); /* hack */ + this_ldata = MIN(ldata-tot_data,cli->max_xmit - (500+this_lparam)); + + client_set_trans_sign_state_off(cli, mid); + client_set_trans_sign_state_on(cli, mid); + + cli_set_message(cli->outbuf,trans==SMBtrans?8:9,0,True); + SCVAL(cli->outbuf,smb_com,(trans==SMBtrans ? SMBtranss : SMBtranss2)); + + outparam = smb_buf(cli->outbuf); + outdata = outparam+this_lparam; + + /* secondary request */ + SSVAL(cli->outbuf,smb_tpscnt,lparam); /* tpscnt */ + SSVAL(cli->outbuf,smb_tdscnt,ldata); /* tdscnt */ + SSVAL(cli->outbuf,smb_spscnt,this_lparam); /* pscnt */ + SSVAL(cli->outbuf,smb_spsoff,smb_offset(outparam,cli->outbuf)); /* psoff */ + SSVAL(cli->outbuf,smb_spsdisp,tot_param); /* psdisp */ + SSVAL(cli->outbuf,smb_sdscnt,this_ldata); /* dscnt */ + SSVAL(cli->outbuf,smb_sdsoff,smb_offset(outdata,cli->outbuf)); /* dsoff */ + SSVAL(cli->outbuf,smb_sdsdisp,tot_data); /* dsdisp */ + if (trans==SMBtrans2) + SSVALS(cli->outbuf,smb_sfid,fid); /* fid */ + if (this_lparam) /* param[] */ + memcpy(outparam,param+tot_param,this_lparam); + if (this_ldata) /* data[] */ + memcpy(outdata,data+tot_data,this_ldata); + cli_setup_bcc(cli, outdata+this_ldata); + + /* + * Save the mid we're using. We need this for finding + * signing replies. + */ + mid = cli->mid; + + show_msg(cli->outbuf); + if (!cli_send_smb(cli)) { + client_set_trans_sign_state_off(cli, mid); + return False; + } + + /* Ensure we use the same mid for the secondaries. */ + cli->mid = mid; + + tot_data += this_ldata; + tot_param += this_lparam; + } + } + + return(True); +} + +/**************************************************************************** + Receive a SMB trans or trans2 response allocating the necessary memory. +****************************************************************************/ + +bool cli_receive_trans(struct cli_state *cli,int trans, + char **param, unsigned int *param_len, + char **data, unsigned int *data_len) +{ + unsigned int total_data=0; + unsigned int total_param=0; + unsigned int this_data,this_param; + NTSTATUS status; + bool ret = False; + + *data_len = *param_len = 0; + + if (!cli_receive_smb(cli)) { + return False; + } + + show_msg(cli->inbuf); + + /* sanity check */ + if (CVAL(cli->inbuf,smb_com) != trans) { + DEBUG(0,("Expected %s response, got command 0x%02x\n", + trans==SMBtrans?"SMBtrans":"SMBtrans2", + CVAL(cli->inbuf,smb_com))); + return False; + } + + /* + * An NT RPC pipe call can return ERRDOS, ERRmoredata + * to a trans call. This is not an error and should not + * be treated as such. Note that STATUS_NO_MORE_FILES is + * returned when a trans2 findfirst/next finishes. + * When setting up an encrypted transport we can also + * see NT_STATUS_MORE_PROCESSING_REQUIRED here. + * + * Vista returns NT_STATUS_INACCESSIBLE_SYSTEM_SHORTCUT if the folder + * "<share>/Users/All Users" is enumerated. This is a special pseudo + * folder, and the response does not have parameters (nor a parameter + * length). + */ + status = cli_nt_error(cli); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + if (NT_STATUS_IS_ERR(status) || + NT_STATUS_EQUAL(status,STATUS_NO_MORE_FILES) || + NT_STATUS_EQUAL(status,NT_STATUS_INACCESSIBLE_SYSTEM_SHORTCUT)) { + goto out; + } + } + + /* parse out the lengths */ + total_data = SVAL(cli->inbuf,smb_tdrcnt); + total_param = SVAL(cli->inbuf,smb_tprcnt); + + /* allocate it */ + if (total_data!=0) { + /* We know adding 2 is safe as total_data is an + * SVAL <= 0xFFFF. */ + *data = (char *)SMB_REALLOC(*data,total_data+2); + if (!(*data)) { + DEBUG(0,("cli_receive_trans: failed to enlarge data buffer\n")); + goto out; + } + } + + if (total_param!=0) { + /* We know adding 2 is safe as total_param is an + * SVAL <= 0xFFFF. */ + *param = (char *)SMB_REALLOC(*param,total_param+2); + if (!(*param)) { + DEBUG(0,("cli_receive_trans: failed to enlarge param buffer\n")); + goto out; + } + } + + for (;;) { + this_data = SVAL(cli->inbuf,smb_drcnt); + this_param = SVAL(cli->inbuf,smb_prcnt); + + if (this_data + *data_len > total_data || + this_param + *param_len > total_param) { + DEBUG(1,("Data overflow in cli_receive_trans\n")); + goto out; + } + + if (this_data + *data_len < this_data || + this_data + *data_len < *data_len || + this_param + *param_len < this_param || + this_param + *param_len < *param_len) { + DEBUG(1,("Data overflow in cli_receive_trans\n")); + goto out; + } + + if (this_data) { + unsigned int data_offset_out = SVAL(cli->inbuf,smb_drdisp); + unsigned int data_offset_in = SVAL(cli->inbuf,smb_droff); + + if (data_offset_out > total_data || + data_offset_out + this_data > total_data || + data_offset_out + this_data < data_offset_out || + data_offset_out + this_data < this_data) { + DEBUG(1,("Data overflow in cli_receive_trans\n")); + goto out; + } + if (data_offset_in > cli->bufsize || + data_offset_in + this_data > cli->bufsize || + data_offset_in + this_data < data_offset_in || + data_offset_in + this_data < this_data) { + DEBUG(1,("Data overflow in cli_receive_trans\n")); + goto out; + } + + memcpy(*data + data_offset_out, smb_base(cli->inbuf) + data_offset_in, this_data); + } + if (this_param) { + unsigned int param_offset_out = SVAL(cli->inbuf,smb_prdisp); + unsigned int param_offset_in = SVAL(cli->inbuf,smb_proff); + + if (param_offset_out > total_param || + param_offset_out + this_param > total_param || + param_offset_out + this_param < param_offset_out || + param_offset_out + this_param < this_param) { + DEBUG(1,("Param overflow in cli_receive_trans\n")); + goto out; + } + if (param_offset_in > cli->bufsize || + param_offset_in + this_param > cli->bufsize || + param_offset_in + this_param < param_offset_in || + param_offset_in + this_param < this_param) { + DEBUG(1,("Param overflow in cli_receive_trans\n")); + goto out; + } + + memcpy(*param + param_offset_out, smb_base(cli->inbuf) + param_offset_in, this_param); + } + *data_len += this_data; + *param_len += this_param; + + if (total_data <= *data_len && total_param <= *param_len) { + ret = True; + break; + } + + if (!cli_receive_smb(cli)) { + goto out; + } + + show_msg(cli->inbuf); + + /* sanity check */ + if (CVAL(cli->inbuf,smb_com) != trans) { + DEBUG(0,("Expected %s response, got command 0x%02x\n", + trans==SMBtrans?"SMBtrans":"SMBtrans2", + CVAL(cli->inbuf,smb_com))); + goto out; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + if (NT_STATUS_IS_ERR(cli_nt_error(cli))) { + goto out; + } + } + + /* parse out the total lengths again - they can shrink! */ + if (SVAL(cli->inbuf,smb_tdrcnt) < total_data) + total_data = SVAL(cli->inbuf,smb_tdrcnt); + if (SVAL(cli->inbuf,smb_tprcnt) < total_param) + total_param = SVAL(cli->inbuf,smb_tprcnt); + + if (total_data <= *data_len && total_param <= *param_len) { + ret = True; + break; + } + } + + out: + + if (ret) { + /* Ensure the last 2 bytes of param and data are 2 null + * bytes. These are malloc'ed, but not included in any + * length counts. This allows cli_XX string reading functions + * to safely null terminate. */ + if (total_data) { + SSVAL(*data,total_data,0); + } + if (total_param) { + SSVAL(*param,total_param,0); + } + } + + client_set_trans_sign_state_off(cli, SVAL(cli->inbuf,smb_mid)); + return ret; +} + +/**************************************************************************** + Send a SMB nttrans request. +****************************************************************************/ + +bool cli_send_nt_trans(struct cli_state *cli, + int function, + int flags, + uint16 *setup, unsigned int lsetup, unsigned int msetup, + char *param, unsigned int lparam, unsigned int mparam, + char *data, unsigned int ldata, unsigned int mdata) +{ + unsigned int i; + unsigned int this_ldata,this_lparam; + unsigned int tot_data=0,tot_param=0; + uint16 mid; + char *outdata,*outparam; + + this_lparam = MIN(lparam,cli->max_xmit - (500+lsetup*2)); /* hack */ + this_ldata = MIN(ldata,cli->max_xmit - (500+lsetup*2+this_lparam)); + + memset(cli->outbuf,'\0',smb_size); + cli_set_message(cli->outbuf,19+lsetup,0,True); + SCVAL(cli->outbuf,smb_com,SMBnttrans); + SSVAL(cli->outbuf,smb_tid, cli->cnum); + cli_setup_packet(cli); + + /* + * Save the mid we're using. We need this for finding + * signing replies. + */ + + mid = cli->mid; + + outparam = smb_buf(cli->outbuf)+3; + outdata = outparam+this_lparam; + + /* primary request */ + SCVAL(cli->outbuf,smb_nt_MaxSetupCount,msetup); + SCVAL(cli->outbuf,smb_nt_Flags,flags); + SIVAL(cli->outbuf,smb_nt_TotalParameterCount, lparam); + SIVAL(cli->outbuf,smb_nt_TotalDataCount, ldata); + SIVAL(cli->outbuf,smb_nt_MaxParameterCount, mparam); + SIVAL(cli->outbuf,smb_nt_MaxDataCount, mdata); + SIVAL(cli->outbuf,smb_nt_ParameterCount, this_lparam); + SIVAL(cli->outbuf,smb_nt_ParameterOffset, smb_offset(outparam,cli->outbuf)); + SIVAL(cli->outbuf,smb_nt_DataCount, this_ldata); + SIVAL(cli->outbuf,smb_nt_DataOffset, smb_offset(outdata,cli->outbuf)); + SIVAL(cli->outbuf,smb_nt_SetupCount, lsetup); + SIVAL(cli->outbuf,smb_nt_Function, function); + for (i=0;i<lsetup;i++) /* setup[] */ + SSVAL(cli->outbuf,smb_nt_SetupStart+i*2,setup[i]); + + if (this_lparam) /* param[] */ + memcpy(outparam,param,this_lparam); + if (this_ldata) /* data[] */ + memcpy(outdata,data,this_ldata); + + cli_setup_bcc(cli, outdata+this_ldata); + + show_msg(cli->outbuf); + if (!cli_send_smb(cli)) { + return False; + } + + /* Note we're in a trans state. Save the sequence + * numbers for replies. */ + client_set_trans_sign_state_on(cli, mid); + + if (this_ldata < ldata || this_lparam < lparam) { + /* receive interim response */ + if (!cli_receive_smb(cli) || cli_is_error(cli)) { + client_set_trans_sign_state_off(cli, mid); + return(False); + } + + tot_data = this_ldata; + tot_param = this_lparam; + + while (tot_data < ldata || tot_param < lparam) { + this_lparam = MIN(lparam-tot_param,cli->max_xmit - 500); /* hack */ + this_ldata = MIN(ldata-tot_data,cli->max_xmit - (500+this_lparam)); + + cli_set_message(cli->outbuf,18,0,True); + SCVAL(cli->outbuf,smb_com,SMBnttranss); + + /* XXX - these should probably be aligned */ + outparam = smb_buf(cli->outbuf); + outdata = outparam+this_lparam; + + /* secondary request */ + SIVAL(cli->outbuf,smb_nts_TotalParameterCount,lparam); + SIVAL(cli->outbuf,smb_nts_TotalDataCount,ldata); + SIVAL(cli->outbuf,smb_nts_ParameterCount,this_lparam); + SIVAL(cli->outbuf,smb_nts_ParameterOffset,smb_offset(outparam,cli->outbuf)); + SIVAL(cli->outbuf,smb_nts_ParameterDisplacement,tot_param); + SIVAL(cli->outbuf,smb_nts_DataCount,this_ldata); + SIVAL(cli->outbuf,smb_nts_DataOffset,smb_offset(outdata,cli->outbuf)); + SIVAL(cli->outbuf,smb_nts_DataDisplacement,tot_data); + if (this_lparam) /* param[] */ + memcpy(outparam,param+tot_param,this_lparam); + if (this_ldata) /* data[] */ + memcpy(outdata,data+tot_data,this_ldata); + cli_setup_bcc(cli, outdata+this_ldata); + + /* + * Save the mid we're using. We need this for finding + * signing replies. + */ + mid = cli->mid; + + show_msg(cli->outbuf); + + if (!cli_send_smb(cli)) { + client_set_trans_sign_state_off(cli, mid); + return False; + } + + /* Ensure we use the same mid for the secondaries. */ + cli->mid = mid; + + tot_data += this_ldata; + tot_param += this_lparam; + } + } + + return(True); +} + +/**************************************************************************** + Receive a SMB nttrans response allocating the necessary memory. +****************************************************************************/ + +bool cli_receive_nt_trans(struct cli_state *cli, + char **param, unsigned int *param_len, + char **data, unsigned int *data_len) +{ + unsigned int total_data=0; + unsigned int total_param=0; + unsigned int this_data,this_param; + uint8 eclass; + uint32 ecode; + bool ret = False; + + *data_len = *param_len = 0; + + if (!cli_receive_smb(cli)) { + return False; + } + + show_msg(cli->inbuf); + + /* sanity check */ + if (CVAL(cli->inbuf,smb_com) != SMBnttrans) { + DEBUG(0,("Expected SMBnttrans response, got command 0x%02x\n", + CVAL(cli->inbuf,smb_com))); + return(False); + } + + /* + * An NT RPC pipe call can return ERRDOS, ERRmoredata + * to a trans call. This is not an error and should not + * be treated as such. + */ + if (cli_is_dos_error(cli)) { + cli_dos_error(cli, &eclass, &ecode); + if (!(eclass == ERRDOS && ecode == ERRmoredata)) { + goto out; + } + } + + /* + * Likewise for NT_STATUS_BUFFER_TOO_SMALL + */ + if (cli_is_nt_error(cli)) { + if (!NT_STATUS_EQUAL(cli_nt_error(cli), + NT_STATUS_BUFFER_TOO_SMALL)) { + goto out; + } + } + + /* parse out the lengths */ + total_data = IVAL(cli->inbuf,smb_ntr_TotalDataCount); + total_param = IVAL(cli->inbuf,smb_ntr_TotalParameterCount); + /* Only allow 16 megs. */ + if (total_param > 16*1024*1024) { + DEBUG(0,("cli_receive_nt_trans: param buffer too large %d\n", + total_param)); + goto out; + } + if (total_data > 16*1024*1024) { + DEBUG(0,("cli_receive_nt_trans: data buffer too large %d\n", + total_data)); + goto out; + } + + /* allocate it */ + if (total_data) { + /* We know adding 2 is safe as total_data is less + * than 16mb (above). */ + *data = (char *)SMB_REALLOC(*data,total_data+2); + if (!(*data)) { + DEBUG(0,("cli_receive_nt_trans: failed to enlarge data buffer to %d\n",total_data)); + goto out; + } + } + + if (total_param) { + /* We know adding 2 is safe as total_param is less + * than 16mb (above). */ + *param = (char *)SMB_REALLOC(*param,total_param+2); + if (!(*param)) { + DEBUG(0,("cli_receive_nt_trans: failed to enlarge param buffer to %d\n", total_param)); + goto out; + } + } + + while (1) { + this_data = SVAL(cli->inbuf,smb_ntr_DataCount); + this_param = SVAL(cli->inbuf,smb_ntr_ParameterCount); + + if (this_data + *data_len > total_data || + this_param + *param_len > total_param) { + DEBUG(1,("Data overflow in cli_receive_nt_trans\n")); + goto out; + } + + if (this_data + *data_len < this_data || + this_data + *data_len < *data_len || + this_param + *param_len < this_param || + this_param + *param_len < *param_len) { + DEBUG(1,("Data overflow in cli_receive_nt_trans\n")); + goto out; + } + + if (this_data) { + unsigned int data_offset_out = SVAL(cli->inbuf,smb_ntr_DataDisplacement); + unsigned int data_offset_in = SVAL(cli->inbuf,smb_ntr_DataOffset); + + if (data_offset_out > total_data || + data_offset_out + this_data > total_data || + data_offset_out + this_data < data_offset_out || + data_offset_out + this_data < this_data) { + DEBUG(1,("Data overflow in cli_receive_nt_trans\n")); + goto out; + } + if (data_offset_in > cli->bufsize || + data_offset_in + this_data > cli->bufsize || + data_offset_in + this_data < data_offset_in || + data_offset_in + this_data < this_data) { + DEBUG(1,("Data overflow in cli_receive_nt_trans\n")); + goto out; + } + + memcpy(*data + data_offset_out, smb_base(cli->inbuf) + data_offset_in, this_data); + } + + if (this_param) { + unsigned int param_offset_out = SVAL(cli->inbuf,smb_ntr_ParameterDisplacement); + unsigned int param_offset_in = SVAL(cli->inbuf,smb_ntr_ParameterOffset); + + if (param_offset_out > total_param || + param_offset_out + this_param > total_param || + param_offset_out + this_param < param_offset_out || + param_offset_out + this_param < this_param) { + DEBUG(1,("Param overflow in cli_receive_nt_trans\n")); + goto out; + } + if (param_offset_in > cli->bufsize || + param_offset_in + this_param > cli->bufsize || + param_offset_in + this_param < param_offset_in || + param_offset_in + this_param < this_param) { + DEBUG(1,("Param overflow in cli_receive_nt_trans\n")); + goto out; + } + + memcpy(*param + param_offset_out, smb_base(cli->inbuf) + param_offset_in, this_param); + } + + *data_len += this_data; + *param_len += this_param; + + if (total_data <= *data_len && total_param <= *param_len) { + ret = True; + break; + } + + if (!cli_receive_smb(cli)) { + goto out; + } + + show_msg(cli->inbuf); + + /* sanity check */ + if (CVAL(cli->inbuf,smb_com) != SMBnttrans) { + DEBUG(0,("Expected SMBnttrans response, got command 0x%02x\n", + CVAL(cli->inbuf,smb_com))); + goto out; + } + if (cli_is_dos_error(cli)) { + cli_dos_error(cli, &eclass, &ecode); + if(!(eclass == ERRDOS && ecode == ERRmoredata)) { + goto out; + } + } + /* + * Likewise for NT_STATUS_BUFFER_TOO_SMALL + */ + if (cli_is_nt_error(cli)) { + if (!NT_STATUS_EQUAL(cli_nt_error(cli), + NT_STATUS_BUFFER_TOO_SMALL)) { + goto out; + } + } + + /* parse out the total lengths again - they can shrink! */ + if (IVAL(cli->inbuf,smb_ntr_TotalDataCount) < total_data) + total_data = IVAL(cli->inbuf,smb_ntr_TotalDataCount); + if (IVAL(cli->inbuf,smb_ntr_TotalParameterCount) < total_param) + total_param = IVAL(cli->inbuf,smb_ntr_TotalParameterCount); + + if (total_data <= *data_len && total_param <= *param_len) { + ret = True; + break; + } + } + + out: + + if (ret) { + /* Ensure the last 2 bytes of param and data are 2 null + * bytes. These are malloc'ed, but not included in any + * length counts. This allows cli_XX string reading functions + * to safely null terminate. */ + if (total_data) { + SSVAL(*data,total_data,0); + } + if (total_param) { + SSVAL(*param,total_param,0); + } + } + + client_set_trans_sign_state_off(cli, SVAL(cli->inbuf,smb_mid)); + return ret; +} + +struct trans_recvblob { + uint8_t *data; + uint32_t max, total, received; +}; + +struct cli_trans_state { + struct cli_state *cli; + struct event_context *ev; + uint8_t cmd; + uint16_t mid; + const char *pipe_name; + uint16_t fid; + uint16_t function; + int flags; + uint16_t *setup; + uint8_t num_setup, max_setup; + uint8_t *param; + uint32_t num_param, param_sent; + uint8_t *data; + uint32_t num_data, data_sent; + + uint8_t num_rsetup; + uint16_t *rsetup; + struct trans_recvblob rparam; + struct trans_recvblob rdata; + + TALLOC_CTX *secondary_request_ctx; +}; + +static void cli_trans_recv_helper(struct async_req *req); + +static struct async_req *cli_ship_trans(TALLOC_CTX *mem_ctx, + struct cli_trans_state *state) +{ + TALLOC_CTX *frame; + struct async_req *result = NULL; + struct cli_request *cli_req; + uint8_t wct; + uint16_t *vwv; + uint8_t *bytes = NULL; + uint16_t param_offset; + uint16_t this_param = 0; + uint16_t this_data = 0; + uint32_t useable_space; + uint8_t cmd; + + frame = talloc_stackframe(); + + cmd = state->cmd; + + if ((state->param_sent != 0) || (state->data_sent != 0)) { + /* The secondary commands are one after the primary ones */ + cmd += 1; + } + + param_offset = smb_size - 4; + + switch (cmd) { + case SMBtrans: + bytes = TALLOC_ZERO_P(talloc_tos(), uint8_t); /* padding */ + if (bytes == NULL) { + goto fail; + } + bytes = smb_bytes_push_str( + bytes, (state->cli->capabilities & CAP_UNICODE) != 0, + state->pipe_name); + if (bytes == NULL) { + goto fail; + } + wct = 14 + state->num_setup; + param_offset += talloc_get_size(bytes); + break; + case SMBtrans2: + bytes = TALLOC_ARRAY(talloc_tos(), uint8_t, 3); /* padding */ + if (bytes == NULL) { + goto fail; + } + bytes[0] = 0; + bytes[1] = 'D'; /* Copy this from "old" 3.0 behaviour */ + bytes[2] = ' '; + wct = 14 + state->num_setup; + param_offset += talloc_get_size(bytes); + break; + case SMBtranss: + wct = 8; + break; + case SMBtranss2: + wct = 9; + break; + case SMBnttrans: + wct = 19 + state->num_setup; + break; + case SMBnttranss: + wct = 18; + break; + default: + goto fail; + } + + useable_space = state->cli->max_xmit - smb_size - sizeof(uint16_t)*wct; + + if (state->param_sent < state->num_param) { + this_param = MIN(state->num_param - state->param_sent, + useable_space); + } + + if (state->data_sent < state->num_data) { + this_data = MIN(state->num_data - state->data_sent, + useable_space - this_param); + } + + vwv = TALLOC_ARRAY(talloc_tos(), uint16_t, wct); + if (vwv == NULL) { + goto fail; + } + param_offset += wct * sizeof(uint16_t); + + DEBUG(10, ("num_setup=%u, max_setup=%u, " + "param_total=%u, this_param=%u, max_param=%u, " + "data_total=%u, this_data=%u, max_data=%u, " + "param_offset=%u, param_disp=%u, data_disp=%u\n", + (unsigned)state->num_setup, (unsigned)state->max_setup, + (unsigned)state->num_param, (unsigned)this_param, + (unsigned)state->rparam.max, + (unsigned)state->num_data, (unsigned)this_data, + (unsigned)state->rdata.max, + (unsigned)param_offset, + (unsigned)state->param_sent, (unsigned)state->data_sent)); + + switch (cmd) { + case SMBtrans: + case SMBtrans2: + SSVAL(vwv + 0, 0, state->num_param); + SSVAL(vwv + 1, 0, state->num_data); + SSVAL(vwv + 2, 0, state->rparam.max); + SSVAL(vwv + 3, 0, state->rdata.max); + SCVAL(vwv + 4, 0, state->max_setup); + SCVAL(vwv + 4, 1, 0); /* reserved */ + SSVAL(vwv + 5, 0, state->flags); + SIVAL(vwv + 6, 0, 0); /* timeout */ + SSVAL(vwv + 8, 0, 0); /* reserved */ + SSVAL(vwv + 9, 0, this_param); + SSVAL(vwv +10, 0, param_offset); + SSVAL(vwv +11, 0, this_data); + SSVAL(vwv +12, 0, param_offset + this_param); + SCVAL(vwv +13, 0, state->num_setup); + SCVAL(vwv +13, 1, 0); /* reserved */ + memcpy(vwv + 14, state->setup, + sizeof(uint16_t) * state->num_setup); + break; + case SMBtranss: + case SMBtranss2: + SSVAL(vwv + 0, 0, state->num_param); + SSVAL(vwv + 1, 0, state->num_data); + SSVAL(vwv + 2, 0, this_param); + SSVAL(vwv + 3, 0, param_offset); + SSVAL(vwv + 4, 0, state->param_sent); + SSVAL(vwv + 5, 0, this_data); + SSVAL(vwv + 6, 0, param_offset + this_param); + SSVAL(vwv + 7, 0, state->data_sent); + if (cmd == SMBtranss2) { + SSVAL(vwv + 8, 0, state->fid); + } + break; + case SMBnttrans: + SCVAL(vwv, 0, state->max_setup); + SSVAL(vwv, 1, 0); /* reserved */ + SIVAL(vwv, 3, state->num_param); + SIVAL(vwv, 7, state->num_data); + SIVAL(vwv, 11, state->rparam.max); + SIVAL(vwv, 15, state->rdata.max); + SIVAL(vwv, 19, this_param); + SIVAL(vwv, 23, param_offset); + SIVAL(vwv, 27, this_data); + SIVAL(vwv, 31, param_offset + this_param); + SCVAL(vwv, 35, state->num_setup); + SSVAL(vwv, 36, state->function); + memcpy(vwv + 19, state->setup, + sizeof(uint16_t) * state->num_setup); + break; + case SMBnttranss: + SSVAL(vwv, 0, 0); /* reserved */ + SCVAL(vwv, 2, 0); /* reserved */ + SIVAL(vwv, 3, state->num_param); + SIVAL(vwv, 7, state->num_data); + SIVAL(vwv, 11, this_param); + SIVAL(vwv, 15, param_offset); + SIVAL(vwv, 19, state->param_sent); + SIVAL(vwv, 23, this_data); + SIVAL(vwv, 27, param_offset + this_param); + SIVAL(vwv, 31, state->data_sent); + SCVAL(vwv, 35, 0); /* reserved */ + break; + } + + bytes = (uint8_t *)talloc_append_blob( + talloc_tos(), bytes, + data_blob_const(state->param + state->param_sent, this_param)); + if (bytes == NULL) { + goto fail; + } + state->param_sent += this_param; + + bytes = (uint8_t *)talloc_append_blob( + talloc_tos(), bytes, + data_blob_const(state->data + state->data_sent, this_data)); + if (bytes == NULL) { + goto fail; + } + state->data_sent += this_data; + + if ((cmd == SMBtrans) || (cmd == SMBtrans2) || (cmd == SMBnttrans)) { + /* + * Primary request, retrieve our mid + */ + result = cli_request_send(mem_ctx, state->ev, state->cli, + cmd, 0, wct, vwv, + talloc_get_size(bytes), bytes); + if (result == NULL) { + goto fail; + } + cli_req = talloc_get_type_abort(result->private_data, + struct cli_request); + state->mid = cli_req->mid; + } else { + uint16_t num_bytes = talloc_get_size(bytes); + /* + * Secondary request, we have to fix up the mid. Thus we do + * the chain_cork/chain/uncork ourselves. + */ + if (!cli_chain_cork(state->cli, state->ev, + wct * sizeof(uint16_t) + num_bytes + 3)) { + goto fail; + } + result = cli_request_send(mem_ctx, state->ev, state->cli, + cmd, 0, wct, vwv, num_bytes, bytes); + if (result == NULL) { + goto fail; + } + cli_req = talloc_get_type_abort(result->private_data, + struct cli_request); + cli_req->recv_helper.fn = cli_trans_recv_helper; + cli_req->recv_helper.priv = state; + cli_req->mid = state->mid; + client_set_trans_sign_state_off(state->cli, state->mid); + cli_chain_uncork(state->cli); + } + + client_set_trans_sign_state_on(state->cli, state->mid); + + fail: + TALLOC_FREE(frame); + return result; +} + +static void cli_trans_ship_rest(struct async_req *req, + struct cli_trans_state *state) +{ + state->secondary_request_ctx = talloc_new(state); + if (state->secondary_request_ctx == NULL) { + async_req_error(req, NT_STATUS_NO_MEMORY); + return; + } + + while ((state->param_sent < state->num_param) + || (state->data_sent < state->num_data)) { + struct async_req *cli_req; + + cli_req = cli_ship_trans(state->secondary_request_ctx, state); + if (cli_req == NULL) { + async_req_error(req, NT_STATUS_NO_MEMORY); + return; + } + } +} + +static bool cli_trans_oob(uint32_t bufsize, uint32_t offset, uint32_t length) +{ + if ((offset + length < offset) || (offset + length < length)) { + /* wrap */ + return true; + } + if ((offset > bufsize) || (offset + length > bufsize)) { + /* overflow */ + return true; + } + return false; +} + +static NTSTATUS cli_pull_trans(struct async_req *req, + struct cli_request *cli_req, + uint8_t smb_cmd, bool expect_first_reply, + uint8_t *pnum_setup, uint16_t **psetup, + uint32_t *ptotal_param, uint32_t *pnum_param, + uint32_t *pparam_disp, uint8_t **pparam, + uint32_t *ptotal_data, uint32_t *pnum_data, + uint32_t *pdata_disp, uint8_t **pdata) +{ + uint32_t param_ofs, data_ofs; + uint8_t wct; + uint16_t *vwv; + uint16_t num_bytes; + uint8_t *bytes; + NTSTATUS status; + + status = cli_pull_reply(req, &wct, &vwv, &num_bytes, &bytes); + + /* + * We can receive something like STATUS_MORE_ENTRIES, so don't use + * !NT_STATUS_IS_OK(status) here. + */ + + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + if (expect_first_reply) { + if ((wct != 0) || (num_bytes != 0)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + return NT_STATUS_OK; + } + + switch (smb_cmd) { + case SMBtrans: + case SMBtrans2: + if (wct < 10) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + *ptotal_param = SVAL(vwv + 0, 0); + *ptotal_data = SVAL(vwv + 1, 0); + *pnum_param = SVAL(vwv + 3, 0); + param_ofs = SVAL(vwv + 4, 0); + *pparam_disp = SVAL(vwv + 5, 0); + *pnum_data = SVAL(vwv + 6, 0); + data_ofs = SVAL(vwv + 7, 0); + *pdata_disp = SVAL(vwv + 8, 0); + *pnum_setup = CVAL(vwv + 9, 0); + if (wct < 10 + (*pnum_setup)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + *psetup = vwv + 10; + + break; + case SMBnttrans: + if (wct < 18) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + *ptotal_param = IVAL(vwv, 3); + *ptotal_data = IVAL(vwv, 7); + *pnum_param = IVAL(vwv, 11); + param_ofs = IVAL(vwv, 15); + *pparam_disp = IVAL(vwv, 19); + *pnum_data = IVAL(vwv, 23); + data_ofs = IVAL(vwv, 27); + *pdata_disp = IVAL(vwv, 31); + *pnum_setup = CVAL(vwv, 35); + *psetup = vwv + 18; + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Check for buffer overflows. data_ofs needs to be checked against + * the incoming buffer length, data_disp against the total + * length. Likewise for param_ofs/param_disp. + */ + + if (cli_trans_oob(smb_len(cli_req->inbuf), param_ofs, *pnum_param) + || cli_trans_oob(*ptotal_param, *pparam_disp, *pnum_param) + || cli_trans_oob(smb_len(cli_req->inbuf), data_ofs, *pnum_data) + || cli_trans_oob(*ptotal_data, *pdata_disp, *pnum_data)) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + *pparam = (uint8_t *)cli_req->inbuf + 4 + param_ofs; + *pdata = (uint8_t *)cli_req->inbuf + 4 + data_ofs; + + return NT_STATUS_OK; +} + +static NTSTATUS cli_trans_pull_blob(TALLOC_CTX *mem_ctx, + struct trans_recvblob *blob, + uint32_t total, uint32_t thistime, + uint8_t *buf, uint32_t displacement) +{ + if (blob->data == NULL) { + if (total > blob->max) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + blob->total = total; + blob->data = TALLOC_ARRAY(mem_ctx, uint8_t, total); + if (blob->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (total > blob->total) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (thistime) { + memcpy(blob->data + displacement, buf, thistime); + blob->received += thistime; + } + + return NT_STATUS_OK; +} + +static void cli_trans_recv_helper(struct async_req *req) +{ + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + struct cli_trans_state *state = talloc_get_type_abort( + cli_req->recv_helper.priv, struct cli_trans_state); + uint8_t num_setup = 0; + uint16_t *setup = NULL; + uint32_t total_param = 0; + uint32_t num_param = 0; + uint32_t param_disp = 0; + uint32_t total_data = 0; + uint32_t num_data = 0; + uint32_t data_disp = 0; + uint8_t *param = NULL; + uint8_t *data = NULL; + bool sent_all; + NTSTATUS status; + + sent_all = (state->param_sent == state->num_param) + && (state->data_sent == state->num_data); + + status = cli_pull_trans( + req, cli_req, state->cmd, !sent_all, &num_setup, &setup, + &total_param, &num_param, ¶m_disp, ¶m, + &total_data, &num_data, &data_disp, &data); + + /* + * We can receive something like STATUS_MORE_ENTRIES, so don't use + * !NT_STATUS_IS_OK(status) here. + */ + + if (NT_STATUS_IS_ERR(status)) { + async_req_error(req, status); + return; + } + + if (!sent_all) { + cli_trans_ship_rest(req, state); + return; + } + + /* + * We've just received a real response. This means that we don't need + * the secondary cli_request structures anymore, they have all been + * shipped to the server. + */ + TALLOC_FREE(state->secondary_request_ctx); + + if (num_setup != 0) { + TALLOC_FREE(state->rsetup); + state->rsetup = (uint16_t *)TALLOC_MEMDUP( + state, setup, sizeof(uint16_t) * num_setup); + if (state->rsetup == NULL) { + async_req_error(req, NT_STATUS_NO_MEMORY); + return; + } + } + + status = cli_trans_pull_blob( + state, &state->rparam, total_param, num_param, param, + param_disp); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Pulling params failed: %s\n", nt_errstr(status))); + async_req_error(req, status); + return; + } + + status = cli_trans_pull_blob( + state, &state->rdata, total_data, num_data, data, + data_disp); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Pulling data failed: %s\n", nt_errstr(status))); + async_req_error(req, status); + return; + } + + if ((state->rparam.total == state->rparam.received) + && (state->rdata.total == state->rdata.received)) { + client_set_trans_sign_state_off(state->cli, state->mid); + async_req_done(req); + } +} + +struct async_req *cli_trans_send( + TALLOC_CTX *mem_ctx, struct event_context *ev, + struct cli_state *cli, uint8_t trans_cmd, + const char *pipe_name, uint16_t fid, uint16_t function, int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data) +{ + struct async_req *req; + struct cli_request *cli_req; + struct cli_trans_state *state; + + /* + * We can't use it in a chained request chain, we'd get the offset + * calculations wrong. + */ + + if (cli_in_chain(cli)) { + return NULL; + } + + if ((trans_cmd == SMBtrans) || (trans_cmd == SMBtrans2)) { + if ((num_param > 0xffff) || (max_param > 0xffff) + || (num_data > 0xffff) || (max_data > 0xffff)) { + DEBUG(3, ("Attempt to send invalid trans2 request " + "(setup %u, params %u/%u, data %u/%u)\n", + (unsigned)num_setup, + (unsigned)num_param, (unsigned)max_param, + (unsigned)num_data, (unsigned)max_data)); + return NULL; + } + } + + state = talloc(mem_ctx, struct cli_trans_state); + if (state == NULL) { + goto nomem; + } + + state->cli = cli; + state->ev = ev; + state->cmd = trans_cmd; + state->num_rsetup = 0; + state->rsetup = NULL; + ZERO_STRUCT(state->rparam); + ZERO_STRUCT(state->rdata); + state->secondary_request_ctx = NULL; + + if (trans_cmd == SMBtrans) { + state->pipe_name = talloc_strdup(state, pipe_name); + if (state->pipe_name == NULL) { + goto nomem; + } + } + if (trans_cmd == SMBtrans2) { + state->fid = fid; + } + if (trans_cmd == SMBnttrans) { + state->function = function; + } + + state->flags = flags; + + if (setup != NULL) { + state->setup = (uint16_t *)TALLOC_MEMDUP( + state, setup, sizeof(*setup) * num_setup); + if (state->setup == NULL) { + goto nomem; + } + state->num_setup = num_setup; + } else { + state->setup = NULL; + state->num_setup = 0; + } + + state->max_setup = max_setup; + + if (param != NULL) { + state->param = (uint8_t *)TALLOC_MEMDUP(state, param, + num_param); + if (state->param == NULL) { + goto nomem; + } + state->num_param = num_param; + } else { + state->param = NULL; + state->num_param = 0; + } + + state->param_sent = 0; + state->rparam.max = max_param; + + if (data != NULL) { + state->data = (uint8_t *)TALLOC_MEMDUP(state, data, num_data); + if (state->data == NULL) { + goto nomem; + } + state->num_data = num_data; + } else { + state->data = NULL; + state->num_data = 0; + } + + state->data_sent = 0; + state->rdata.max = max_data; + + req = cli_ship_trans(state, state); + if (req == NULL) { + goto nomem; + } + + cli_req = talloc_get_type_abort(req->private_data, struct cli_request); + cli_req->recv_helper.fn = cli_trans_recv_helper; + cli_req->recv_helper.priv = state; + + return req; + + nomem: + TALLOC_FREE(state); + return NULL; +} + +NTSTATUS cli_trans_recv(struct async_req *req, TALLOC_CTX *mem_ctx, + uint16_t **setup, uint8_t *num_setup, + uint8_t **param, uint32_t *num_param, + uint8_t **data, uint32_t *num_data) +{ + struct cli_request *cli_req = talloc_get_type_abort( + req->private_data, struct cli_request); + struct cli_trans_state *state = talloc_get_type_abort( + cli_req->recv_helper.priv, struct cli_trans_state); + + SMB_ASSERT(req->state >= ASYNC_REQ_DONE); + if (req->state == ASYNC_REQ_ERROR) { + return req->status; + } + + if (setup != NULL) { + *setup = talloc_move(mem_ctx, &state->rsetup); + *num_setup = state->num_rsetup; + } else { + TALLOC_FREE(state->rsetup); + } + + if (param != NULL) { + *param = talloc_move(mem_ctx, &state->rparam.data); + *num_param = state->rparam.total; + } else { + TALLOC_FREE(state->rparam.data); + } + + if (data != NULL) { + *data = talloc_move(mem_ctx, &state->rdata.data); + *num_data = state->rdata.total; + } else { + TALLOC_FREE(state->rdata.data); + } + + return NT_STATUS_OK; +} + +NTSTATUS cli_trans(TALLOC_CTX *mem_ctx, struct cli_state *cli, + uint8_t trans_cmd, + const char *pipe_name, uint16_t fid, uint16_t function, + int flags, + uint16_t *setup, uint8_t num_setup, uint8_t max_setup, + uint8_t *param, uint32_t num_param, uint32_t max_param, + uint8_t *data, uint32_t num_data, uint32_t max_data, + uint16_t **rsetup, uint8_t *num_rsetup, + uint8_t **rparam, uint32_t *num_rparam, + uint8_t **rdata, uint32_t *num_rdata) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct event_context *ev; + struct async_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (cli->fd_event != NULL) { + /* + * Can't use sync call while an async call is in flight + */ + cli_set_error(cli, NT_STATUS_INVALID_PARAMETER); + goto fail; + } + + ev = event_context_init(frame); + if (ev == NULL) { + goto fail; + } + + req = cli_trans_send(frame, ev, cli, trans_cmd, + pipe_name, fid, function, flags, + setup, num_setup, max_setup, + param, num_param, max_param, + data, num_data, max_data); + if (req == NULL) { + goto fail; + } + + while (req->state < ASYNC_REQ_DONE) { + event_loop_once(ev); + } + + status = cli_trans_recv(req, mem_ctx, rsetup, num_rsetup, + rparam, num_rparam, rdata, num_rdata); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/source3/libsmb/conncache.c b/source3/libsmb/conncache.c new file mode 100644 index 0000000000..b440d61048 --- /dev/null +++ b/source3/libsmb/conncache.c @@ -0,0 +1,256 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Gerald (Jerry) Carter 2003 + Copyright (C) Marc VanHeyningen 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" + +/** + * @file + * Negative connection cache implemented in terms of gencache API + * + * The negative connection cache stores names of servers which have + * been unresponsive so that we don't waste time repeatedly trying + * to contact them. It used to use an in-memory linked list, but + * this limited its utility to a single process + */ + + +/** + * prefix used for all entries put into the general cache + */ +static const char NEGATIVE_CONN_CACHE_PREFIX[] = "NEG_CONN_CACHE"; + +/** + * Marshalls the domain and server name into the key for the gencache + * record + * + * @param[in] domain required + * @param[in] server may be a FQDN or an IP address + * @return the resulting string, which the caller is responsible for + * SAFE_FREE()ing + * @retval NULL returned on error + */ +static char *negative_conn_cache_keystr(const char *domain, const char *server) +{ + const char NEGATIVE_CONN_CACHE_KEY_FMT[] = "%s/%s,%s"; + char *keystr = NULL; + + SMB_ASSERT(domain != NULL); + if (server == NULL) + server = ""; + + keystr = talloc_asprintf(talloc_tos(),NEGATIVE_CONN_CACHE_KEY_FMT, + NEGATIVE_CONN_CACHE_PREFIX, domain, server); + if (keystr == NULL) { + DEBUG(0, ("negative_conn_cache_keystr: malloc error\n")); + } + + return keystr; +} + +/** + * Marshalls the NT status into a printable value field for the gencache + * record + * + * @param[in] status + * @return the resulting string, which the caller is responsible for + * SAFE_FREE()ing + * @retval NULL returned on error + */ +static char *negative_conn_cache_valuestr(NTSTATUS status) +{ + char *valuestr = NULL; + + valuestr = talloc_asprintf(talloc_tos(), "%x", NT_STATUS_V(status)); + if (valuestr == NULL) { + DEBUG(0, ("negative_conn_cache_valuestr: malloc error\n")); + } + + return valuestr; +} + +/** + * Un-marshalls the NT status from a printable field for the gencache + * record + * + * @param[in] value The value field from the record + * @return the decoded NT status + * @retval NT_STATUS_OK returned on error + */ +static NTSTATUS negative_conn_cache_valuedecode(const char *value) +{ + NTSTATUS result = NT_STATUS_OK; + + SMB_ASSERT(value != NULL); + if (sscanf(value, "%x", &(NT_STATUS_V(result))) != 1) + DEBUG(0, ("negative_conn_cache_valuestr: unable to parse " + "value field '%s'\n", value)); + return result; +} + +/** + * Function passed to gencache_iterate to remove any matching items + * from the list + * + * @param[in] key Key to the record found and to be deleted + * @param[in] value Value to the record (ignored) + * @param[in] timeout Timeout remaining for the record (ignored) + * @param[in] dptr Handle for passing additional data (ignored) + */ +static void delete_matches(const char *key, const char *value, + time_t timeout, void *dptr) +{ + gencache_del(key); +} + + +/** + * Checks for a given domain/server record in the negative cache + * + * @param[in] domain + * @param[in] server may be either a FQDN or an IP address + * @return The cached failure status + * @retval NT_STATUS_OK returned if no record is found or an error occurs + */ +NTSTATUS check_negative_conn_cache( const char *domain, const char *server) +{ + NTSTATUS result = NT_STATUS_OK; + char *key = NULL; + char *value = NULL; + + key = negative_conn_cache_keystr(domain, server); + if (key == NULL) + goto done; + + if (gencache_get(key, &value, (time_t *) NULL)) + result = negative_conn_cache_valuedecode(value); + done: + DEBUG(9,("check_negative_conn_cache returning result %d for domain %s " + "server %s\n", NT_STATUS_V(result), domain, server)); + TALLOC_FREE(key); + SAFE_FREE(value); + return result; +} + +/** + * Delete any negative cache entry for the given domain/server + * + * @param[in] domain + * @param[in] server may be either a FQDN or an IP address + */ +void delete_negative_conn_cache(const char *domain, const char *server) +{ + char *key = NULL; + + key = negative_conn_cache_keystr(domain, server); + if (key == NULL) + goto done; + + gencache_del(key); + DEBUG(9,("delete_negative_conn_cache removing domain %s server %s\n", + domain, server)); + done: + TALLOC_FREE(key); + return; +} + + +/** + * Add an entry to the failed connection cache + * + * @param[in] domain + * @param[in] server may be a FQDN or an IP addr in printable form + * @param[in] result error to cache; must not be NT_STATUS_OK + */ +void add_failed_connection_entry(const char *domain, const char *server, + NTSTATUS result) +{ + char *key = NULL; + char *value = NULL; + + SMB_ASSERT(!NT_STATUS_IS_OK(result)); + + key = negative_conn_cache_keystr(domain, server); + if (key == NULL) { + DEBUG(0, ("add_failed_connection_entry: key creation error\n")); + goto done; + } + + value = negative_conn_cache_valuestr(result); + if (value == NULL) { + DEBUG(0, ("add_failed_connection_entry: value creation error\n")); + goto done; + } + + if (gencache_set(key, value, + time((time_t *) NULL) + + FAILED_CONNECTION_CACHE_TIMEOUT)) + DEBUG(9,("add_failed_connection_entry: added domain %s (%s) " + "to failed conn cache\n", domain, server )); + else + DEBUG(1,("add_failed_connection_entry: failed to add " + "domain %s (%s) to failed conn cache\n", + domain, server)); + + done: + TALLOC_FREE(key); + TALLOC_FREE(value); + return; +} + +/** + * Deletes all records from the negative connection cache in all domains + */ +void flush_negative_conn_cache( void ) +{ + flush_negative_conn_cache_for_domain("*"); +} + + +/** + * Deletes all records for a specified domain from the negative connection + * cache + * + * @param[in] domain String to match against domain portion of keys, or "*" + * to match all domains + */ +void flush_negative_conn_cache_for_domain(const char *domain) +{ + char *key_pattern = NULL; + + key_pattern = negative_conn_cache_keystr(domain,"*"); + if (key_pattern == NULL) { + DEBUG(0, ("flush_negative_conn_cache_for_domain: " + "key creation error\n")); + goto done; + } + + gencache_iterate(delete_matches, (void *) NULL, key_pattern); + DEBUG(8, ("flush_negative_conn_cache_for_domain: flushed domain %s\n", + domain)); + + done: + TALLOC_FREE(key_pattern); + return; +} diff --git a/source3/libsmb/credentials.c b/source3/libsmb/credentials.c new file mode 100644 index 0000000000..9d33e6d93d --- /dev/null +++ b/source3/libsmb/credentials.c @@ -0,0 +1,361 @@ +/* + Unix SMB/CIFS implementation. + code to manipulate domain credentials + Copyright (C) Andrew Tridgell 1997-1998 + Largely rewritten by Jeremy Allison 2005. + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Represent a credential as a string. +****************************************************************************/ + +char *credstr(const unsigned char *cred) +{ + char *result; + result = talloc_asprintf(talloc_tos(), + "%02X%02X%02X%02X%02X%02X%02X%02X", + cred[0], cred[1], cred[2], cred[3], + cred[4], cred[5], cred[6], cred[7]); + SMB_ASSERT(result != NULL); + return result; +} + +/**************************************************************************** + Setup the session key and the client and server creds in dc. + ADS-style 128 bit session keys. + Used by both client and server creds setup. +****************************************************************************/ + +static void creds_init_128(struct dcinfo *dc, + const struct netr_Credential *clnt_chal_in, + const struct netr_Credential *srv_chal_in, + const unsigned char mach_pw[16]) +{ + unsigned char zero[4], tmp[16]; + HMACMD5Context ctx; + struct MD5Context md5; + + /* Just in case this isn't already there */ + memcpy(dc->mach_pw, mach_pw, 16); + + ZERO_STRUCT(dc->sess_key); + + memset(zero, 0, sizeof(zero)); + + hmac_md5_init_rfc2104(mach_pw, 16, &ctx); + MD5Init(&md5); + MD5Update(&md5, zero, sizeof(zero)); + MD5Update(&md5, clnt_chal_in->data, 8); + MD5Update(&md5, srv_chal_in->data, 8); + MD5Final(tmp, &md5); + hmac_md5_update(tmp, sizeof(tmp), &ctx); + hmac_md5_final(dc->sess_key, &ctx); + + /* debug output */ + DEBUG(5,("creds_init_128\n")); + DEBUG(5,("\tclnt_chal_in: %s\n", credstr(clnt_chal_in->data))); + DEBUG(5,("\tsrv_chal_in : %s\n", credstr(srv_chal_in->data))); + dump_data_pw("\tsession_key ", (const unsigned char *)dc->sess_key, 16); + + /* Generate the next client and server creds. */ + + des_crypt112(dc->clnt_chal.data, /* output */ + clnt_chal_in->data, /* input */ + dc->sess_key, /* input */ + 1); + + des_crypt112(dc->srv_chal.data, /* output */ + srv_chal_in->data, /* input */ + dc->sess_key, /* input */ + 1); + + /* Seed is the client chal. */ + memcpy(dc->seed_chal.data, dc->clnt_chal.data, 8); +} + +/**************************************************************************** + Setup the session key and the client and server creds in dc. + Used by both client and server creds setup. +****************************************************************************/ + +static void creds_init_64(struct dcinfo *dc, + const struct netr_Credential *clnt_chal_in, + const struct netr_Credential *srv_chal_in, + const unsigned char mach_pw[16]) +{ + uint32 sum[2]; + unsigned char sum2[8]; + + /* Just in case this isn't already there */ + if (dc->mach_pw != mach_pw) { + memcpy(dc->mach_pw, mach_pw, 16); + } + + sum[0] = IVAL(clnt_chal_in->data, 0) + IVAL(srv_chal_in->data, 0); + sum[1] = IVAL(clnt_chal_in->data, 4) + IVAL(srv_chal_in->data, 4); + + SIVAL(sum2,0,sum[0]); + SIVAL(sum2,4,sum[1]); + + ZERO_STRUCT(dc->sess_key); + + des_crypt128(dc->sess_key, sum2, dc->mach_pw); + + /* debug output */ + DEBUG(5,("creds_init_64\n")); + DEBUG(5,("\tclnt_chal_in: %s\n", credstr(clnt_chal_in->data))); + DEBUG(5,("\tsrv_chal_in : %s\n", credstr(srv_chal_in->data))); + DEBUG(5,("\tclnt+srv : %s\n", credstr(sum2))); + DEBUG(5,("\tsess_key_out : %s\n", credstr(dc->sess_key))); + + /* Generate the next client and server creds. */ + + des_crypt112(dc->clnt_chal.data, /* output */ + clnt_chal_in->data, /* input */ + dc->sess_key, /* input */ + 1); + + des_crypt112(dc->srv_chal.data, /* output */ + srv_chal_in->data, /* input */ + dc->sess_key, /* input */ + 1); + + /* Seed is the client chal. */ + memcpy(dc->seed_chal.data, dc->clnt_chal.data, 8); +} + +/**************************************************************************** + Utility function to step credential chain one forward. + Deliberately doesn't update the seed. See reseed comment below. +****************************************************************************/ + +static void creds_step(struct dcinfo *dc) +{ + DOM_CHAL time_chal; + + DEBUG(5,("\tsequence = 0x%x\n", (unsigned int)dc->sequence )); + + DEBUG(5,("\tseed: %s\n", credstr(dc->seed_chal.data) )); + + SIVAL(time_chal.data, 0, IVAL(dc->seed_chal.data, 0) + dc->sequence); + SIVAL(time_chal.data, 4, IVAL(dc->seed_chal.data, 4)); + + DEBUG(5,("\tseed+seq %s\n", credstr(time_chal.data) )); + + des_crypt112(dc->clnt_chal.data, time_chal.data, dc->sess_key, 1); + + DEBUG(5,("\tCLIENT %s\n", credstr(dc->clnt_chal.data) )); + + SIVAL(time_chal.data, 0, IVAL(dc->seed_chal.data, 0) + dc->sequence + 1); + SIVAL(time_chal.data, 4, IVAL(dc->seed_chal.data, 4)); + + DEBUG(5,("\tseed+seq+1 %s\n", credstr(time_chal.data) )); + + des_crypt112(dc->srv_chal.data, time_chal.data, dc->sess_key, 1); + + DEBUG(5,("\tSERVER %s\n", credstr(dc->srv_chal.data) )); +} + +/**************************************************************************** + Create a server credential struct. +****************************************************************************/ + +void creds_server_init(uint32 neg_flags, + struct dcinfo *dc, + struct netr_Credential *clnt_chal, + struct netr_Credential *srv_chal, + const unsigned char mach_pw[16], + struct netr_Credential *init_chal_out) +{ + DEBUG(10,("creds_server_init: neg_flags : %x\n", (unsigned int)neg_flags)); + DEBUG(10,("creds_server_init: client chal : %s\n", credstr(clnt_chal->data) )); + DEBUG(10,("creds_server_init: server chal : %s\n", credstr(srv_chal->data) )); + dump_data_pw("creds_server_init: machine pass", mach_pw, 16); + + /* Generate the session key and the next client and server creds. */ + if (neg_flags & NETLOGON_NEG_128BIT) { + creds_init_128(dc, + clnt_chal, + srv_chal, + mach_pw); + } else { + creds_init_64(dc, + clnt_chal, + srv_chal, + mach_pw); + } + + dump_data_pw("creds_server_init: session key", dc->sess_key, 16); + + DEBUG(10,("creds_server_init: clnt : %s\n", credstr(dc->clnt_chal.data) )); + DEBUG(10,("creds_server_init: server : %s\n", credstr(dc->srv_chal.data) )); + DEBUG(10,("creds_server_init: seed : %s\n", credstr(dc->seed_chal.data) )); + + memcpy(init_chal_out->data, dc->srv_chal.data, 8); +} + +/**************************************************************************** + Check a credential sent by the client. +****************************************************************************/ + +bool netlogon_creds_server_check(const struct dcinfo *dc, + const struct netr_Credential *rcv_cli_chal_in) +{ + if (memcmp(dc->clnt_chal.data, rcv_cli_chal_in->data, 8)) { + DEBUG(5,("netlogon_creds_server_check: challenge : %s\n", + credstr(rcv_cli_chal_in->data))); + DEBUG(5,("calculated: %s\n", credstr(dc->clnt_chal.data))); + DEBUG(2,("netlogon_creds_server_check: credentials check failed.\n")); + return false; + } + + DEBUG(10,("netlogon_creds_server_check: credentials check OK.\n")); + + return true; +} +/**************************************************************************** + Replace current seed chal. Internal function - due to split server step below. +****************************************************************************/ + +static void creds_reseed(struct dcinfo *dc) +{ + struct netr_Credential time_chal; + + SIVAL(time_chal.data, 0, IVAL(dc->seed_chal.data, 0) + dc->sequence + 1); + SIVAL(time_chal.data, 4, IVAL(dc->seed_chal.data, 4)); + + dc->seed_chal = time_chal; + + DEBUG(5,("cred_reseed: seed %s\n", credstr(dc->seed_chal.data) )); +} + +/**************************************************************************** + Step the server credential chain one forward. +****************************************************************************/ + +bool netlogon_creds_server_step(struct dcinfo *dc, + const struct netr_Authenticator *received_cred, + struct netr_Authenticator *cred_out) +{ + bool ret; + struct dcinfo tmp_dc = *dc; + + /* Do all operations on a temporary copy of the dc, + which we throw away if the checks fail. */ + + tmp_dc.sequence = received_cred->timestamp; + + creds_step(&tmp_dc); + + /* Create the outgoing credentials */ + cred_out->timestamp = tmp_dc.sequence + 1; + memcpy(&cred_out->cred, &tmp_dc.srv_chal, sizeof(cred_out->cred)); + + creds_reseed(&tmp_dc); + + ret = netlogon_creds_server_check(&tmp_dc, &received_cred->cred); + if (!ret) { + return false; + } + + /* creds step succeeded - replace the current creds. */ + *dc = tmp_dc; + return true; +} + +/**************************************************************************** + Create a client credential struct. +****************************************************************************/ + +void creds_client_init(uint32 neg_flags, + struct dcinfo *dc, + struct netr_Credential *clnt_chal, + struct netr_Credential *srv_chal, + const unsigned char mach_pw[16], + struct netr_Credential *init_chal_out) +{ + dc->sequence = time(NULL); + + DEBUG(10,("creds_client_init: neg_flags : %x\n", (unsigned int)neg_flags)); + DEBUG(10,("creds_client_init: client chal : %s\n", credstr(clnt_chal->data) )); + DEBUG(10,("creds_client_init: server chal : %s\n", credstr(srv_chal->data) )); + dump_data_pw("creds_client_init: machine pass", (const unsigned char *)mach_pw, 16); + + /* Generate the session key and the next client and server creds. */ + if (neg_flags & NETLOGON_NEG_128BIT) { + creds_init_128(dc, + clnt_chal, + srv_chal, + mach_pw); + } else { + creds_init_64(dc, + clnt_chal, + srv_chal, + mach_pw); + } + + dump_data_pw("creds_client_init: session key", dc->sess_key, 16); + + DEBUG(10,("creds_client_init: clnt : %s\n", credstr(dc->clnt_chal.data) )); + DEBUG(10,("creds_client_init: server : %s\n", credstr(dc->srv_chal.data) )); + DEBUG(10,("creds_client_init: seed : %s\n", credstr(dc->seed_chal.data) )); + + memcpy(init_chal_out->data, dc->clnt_chal.data, 8); +} + +/**************************************************************************** + Check a credential returned by the server. +****************************************************************************/ + +bool netlogon_creds_client_check(const struct dcinfo *dc, + const struct netr_Credential *rcv_srv_chal_in) +{ + if (memcmp(dc->srv_chal.data, rcv_srv_chal_in->data, + sizeof(dc->srv_chal.data))) { + + DEBUG(0,("netlogon_creds_client_check: credentials check failed.\n")); + DEBUGADD(5,("netlogon_creds_client_check: challenge : %s\n", + credstr(rcv_srv_chal_in->data))); + DEBUGADD(5,("calculated: %s\n", credstr(dc->srv_chal.data))); + return false; + } + + DEBUG(10,("netlogon_creds_client_check: credentials check OK.\n")); + + return true; +} + + +/**************************************************************************** + Step the client credentials to the next element in the chain, updating the + current client and server credentials and the seed + produce the next authenticator in the sequence ready to send to + the server +****************************************************************************/ + +void netlogon_creds_client_step(struct dcinfo *dc, + struct netr_Authenticator *next_cred_out) +{ + dc->sequence += 2; + creds_step(dc); + creds_reseed(dc); + + memcpy(&next_cred_out->cred.data, &dc->clnt_chal.data, + sizeof(next_cred_out->cred.data)); + next_cred_out->timestamp = dc->sequence; +} diff --git a/source3/libsmb/dcerpc_err.c b/source3/libsmb/dcerpc_err.c new file mode 100644 index 0000000000..900b8d769f --- /dev/null +++ b/source3/libsmb/dcerpc_err.c @@ -0,0 +1,55 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Stefan Metzmacher 2004 + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +struct dcerpc_fault_table { + const char *errstr; + uint32_t faultcode; +}; + +static const struct dcerpc_fault_table dcerpc_faults[] = +{ + { "DCERPC_FAULT_OP_RNG_ERROR", DCERPC_FAULT_OP_RNG_ERROR }, + { "DCERPC_FAULT_UNK_IF", DCERPC_FAULT_UNK_IF }, + { "DCERPC_FAULT_NDR", DCERPC_FAULT_NDR }, + { "DCERPC_FAULT_INVALID_TAG", DCERPC_FAULT_INVALID_TAG }, + { "DCERPC_FAULT_CONTEXT_MISMATCH", DCERPC_FAULT_CONTEXT_MISMATCH }, + { "DCERPC_FAULT_OTHER", DCERPC_FAULT_OTHER }, + { "DCERPC_FAULT_ACCESS_DENIED", DCERPC_FAULT_ACCESS_DENIED }, + + { NULL, 0} +}; + +const char *dcerpc_errstr(uint32 fault_code) +{ + char *result; + int idx = 0; + + while (dcerpc_faults[idx].errstr != NULL) { + if (dcerpc_faults[idx].faultcode == fault_code) { + return dcerpc_faults[idx].errstr; + } + idx++; + } + + result = talloc_asprintf(talloc_tos(), "DCERPC fault 0x%08x", + fault_code); + SMB_ASSERT(result != NULL); + return result; +} diff --git a/source3/libsmb/doserr.c b/source3/libsmb/doserr.c new file mode 100644 index 0000000000..2716e04d91 --- /dev/null +++ b/source3/libsmb/doserr.c @@ -0,0 +1,202 @@ +/* + * Unix SMB/CIFS implementation. + * DOS error routines + * Copyright (C) Tim Potter 2002. + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/* DOS error codes. please read doserr.h */ + +#include "includes.h" + +typedef const struct { + const char *dos_errstr; + WERROR werror; +} werror_code_struct; + +typedef const struct { + WERROR werror; + const char *friendly_errstr; +} werror_str_struct; + +werror_code_struct dos_errs[] = +{ + { "WERR_OK", WERR_OK }, + { "WERR_GENERAL_FAILURE", WERR_GENERAL_FAILURE }, + { "WERR_BADFILE", WERR_BADFILE }, + { "WERR_ACCESS_DENIED", WERR_ACCESS_DENIED }, + { "WERR_BADFID", WERR_BADFID }, + { "WERR_BADFUNC", WERR_BADFUNC }, + { "WERR_INSUFFICIENT_BUFFER", WERR_INSUFFICIENT_BUFFER }, + { "WERR_SEM_TIMEOUT", WERR_SEM_TIMEOUT }, + { "WERR_NO_SUCH_SHARE", WERR_NO_SUCH_SHARE }, + { "WERR_ALREADY_EXISTS", WERR_ALREADY_EXISTS }, + { "WERR_INVALID_PARAM", WERR_INVALID_PARAM }, + { "WERR_NOT_SUPPORTED", WERR_NOT_SUPPORTED }, + { "WERR_BAD_PASSWORD", WERR_BAD_PASSWORD }, + { "WERR_NOMEM", WERR_NOMEM }, + { "WERR_INVALID_NAME", WERR_INVALID_NAME }, + { "WERR_UNKNOWN_LEVEL", WERR_UNKNOWN_LEVEL }, + { "WERR_OBJECT_PATH_INVALID", WERR_OBJECT_PATH_INVALID }, + { "WERR_NO_MORE_ITEMS", WERR_NO_MORE_ITEMS }, + { "WERR_MORE_DATA", WERR_MORE_DATA }, + { "WERR_UNKNOWN_PRINTER_DRIVER", WERR_UNKNOWN_PRINTER_DRIVER }, + { "WERR_INVALID_PRINTER_NAME", WERR_INVALID_PRINTER_NAME }, + { "WERR_PRINTER_ALREADY_EXISTS", WERR_PRINTER_ALREADY_EXISTS }, + { "WERR_INVALID_DATATYPE", WERR_INVALID_DATATYPE }, + { "WERR_INVALID_ENVIRONMENT", WERR_INVALID_ENVIRONMENT }, + { "WERR_INVALID_FORM_NAME", WERR_INVALID_FORM_NAME }, + { "WERR_INVALID_FORM_SIZE", WERR_INVALID_FORM_SIZE }, + { "WERR_BUF_TOO_SMALL", WERR_BUF_TOO_SMALL }, + { "WERR_JOB_NOT_FOUND", WERR_JOB_NOT_FOUND }, + { "WERR_DEST_NOT_FOUND", WERR_DEST_NOT_FOUND }, + { "WERR_GROUP_NOT_FOUND", WERR_GROUP_NOT_FOUND }, + { "WERR_USER_NOT_FOUND", WERR_USER_NOT_FOUND }, + { "WERR_NOT_LOCAL_DOMAIN", WERR_NOT_LOCAL_DOMAIN }, + { "WERR_USER_EXISTS", WERR_USER_EXISTS }, + { "WERR_REVISION_MISMATCH", WERR_REVISION_MISMATCH }, + { "WERR_NO_LOGON_SERVERS", WERR_NO_LOGON_SERVERS }, + { "WERR_NO_SUCH_LOGON_SESSION", WERR_NO_SUCH_LOGON_SESSION }, + { "WERR_USER_ALREADY_EXISTS", WERR_USER_ALREADY_EXISTS }, + { "WERR_NO_SUCH_USER", WERR_NO_SUCH_USER }, + { "WERR_GROUP_EXISTS", WERR_GROUP_EXISTS }, + { "WERR_MEMBER_IN_GROUP", WERR_MEMBER_IN_GROUP }, + { "WERR_USER_NOT_IN_GROUP", WERR_USER_NOT_IN_GROUP }, + { "WERR_PRINTER_DRIVER_IN_USE", WERR_PRINTER_DRIVER_IN_USE }, + { "WERR_STATUS_MORE_ENTRIES ", WERR_STATUS_MORE_ENTRIES }, + { "WERR_DFS_NO_SUCH_VOL", WERR_DFS_NO_SUCH_VOL }, + { "WERR_DFS_NO_SUCH_SHARE", WERR_DFS_NO_SUCH_SHARE }, + { "WERR_DFS_NO_SUCH_SERVER", WERR_DFS_NO_SUCH_SERVER }, + { "WERR_DFS_INTERNAL_ERROR", WERR_DFS_INTERNAL_ERROR }, + { "WERR_DFS_CANT_CREATE_JUNCT", WERR_DFS_CANT_CREATE_JUNCT }, + { "WERR_INVALID_COMPUTER_NAME", WERR_INVALID_COMPUTER_NAME }, + { "WERR_INVALID_DOMAINNAME", WERR_INVALID_DOMAINNAME }, + { "WERR_MACHINE_LOCKED", WERR_MACHINE_LOCKED }, + { "WERR_DC_NOT_FOUND", WERR_DC_NOT_FOUND }, + { "WERR_SETUP_NOT_JOINED", WERR_SETUP_NOT_JOINED }, + { "WERR_SETUP_ALREADY_JOINED", WERR_SETUP_ALREADY_JOINED }, + { "WERR_SETUP_DOMAIN_CONTROLLER", WERR_SETUP_DOMAIN_CONTROLLER }, + { "WERR_DEFAULT_JOIN_REQUIRED", WERR_DEFAULT_JOIN_REQUIRED }, + { "WERR_DEVICE_NOT_AVAILABLE", WERR_DEVICE_NOT_AVAILABLE }, + { "WERR_LOGON_FAILURE", WERR_LOGON_FAILURE }, + { "WERR_WRONG_PASSWORD", WERR_WRONG_PASSWORD }, + { "WERR_PASSWORD_RESTRICTION", WERR_PASSWORD_RESTRICTION }, + { "WERR_NO_SUCH_DOMAIN", WERR_NO_SUCH_DOMAIN }, + { "WERR_NONE_MAPPED", WERR_NONE_MAPPED }, + { "WERR_INVALID_SECURITY_DESCRIPTOR", WERR_INVALID_SECURITY_DESCRIPTOR }, + { "WERR_INVALID_DOMAIN_STATE", WERR_INVALID_DOMAIN_STATE }, + { "WERR_INVALID_DOMAIN_ROLE", WERR_INVALID_DOMAIN_ROLE }, + { "WERR_SPECIAL_ACCOUNT", WERR_SPECIAL_ACCOUNT }, + { "WERR_ALIAS_EXISTS", WERR_ALIAS_EXISTS }, + { "WERR_NO_SUCH_ALIAS", WERR_NO_SUCH_ALIAS }, + { "WERR_MEMBER_IN_ALIAS", WERR_MEMBER_IN_ALIAS }, + { "WERR_TIME_SKEW", WERR_TIME_SKEW }, + { "WERR_INVALID_OWNER", WERR_INVALID_OWNER }, + { "WERR_SERVER_UNAVAILABLE", WERR_SERVER_UNAVAILABLE }, + { "WERR_IO_PENDING", WERR_IO_PENDING }, + { "WERR_INVALID_SERVICE_CONTROL", WERR_INVALID_SERVICE_CONTROL }, + { "WERR_SERVICE_ALREADY_RUNNING", WERR_SERVICE_ALREADY_RUNNING }, + { "WERR_NET_NAME_NOT_FOUND", WERR_NET_NAME_NOT_FOUND }, + { "WERR_REG_CORRUPT", WERR_REG_CORRUPT }, + { "WERR_REG_IO_FAILURE", WERR_REG_IO_FAILURE }, + { "WERR_REG_FILE_INVALID", WERR_REG_FILE_INVALID }, + { "WERR_NO_SUCH_SERVICE", WERR_NO_SUCH_SERVICE }, + { "WERR_SERVICE_DISABLED", WERR_SERVICE_DISABLED }, + { "WERR_SERVICE_NEVER_STARTED", WERR_SERVICE_NEVER_STARTED }, + { "WERR_NOT_FOUND", WERR_NOT_FOUND }, + { "WERR_CAN_NOT_COMPLETE", WERR_CAN_NOT_COMPLETE}, + { "WERR_INVALID_FLAGS", WERR_INVALID_FLAGS}, + { "WERR_PASSWORD_MUST_CHANGE", WERR_PASSWORD_MUST_CHANGE }, + { "WERR_DOMAIN_CONTROLLER_NOT_FOUND", WERR_DOMAIN_CONTROLLER_NOT_FOUND }, + { "WERR_ACCOUNT_LOCKED_OUT", WERR_ACCOUNT_LOCKED_OUT }, + { "WERR_DS_DRA_BAD_DN", WERR_DS_DRA_BAD_DN }, + { "WERR_DS_DRA_BAD_NC", WERR_DS_DRA_BAD_NC }, + { NULL, W_ERROR(0) } +}; + +werror_str_struct dos_err_strs[] = { + { WERR_OK, "Success" }, + { WERR_ACCESS_DENIED, "Access is denied" }, + { WERR_INVALID_PARAM, "Invalid parameter" }, + { WERR_NOT_SUPPORTED, "Not supported" }, + { WERR_BAD_PASSWORD, "A bad password was supplied" }, + { WERR_NOMEM, "Out of memory" }, + { WERR_NO_LOGON_SERVERS, "No logon servers found" }, + { WERR_NO_SUCH_LOGON_SESSION, "No such logon session" }, + { WERR_DOMAIN_CONTROLLER_NOT_FOUND, "A domain controller could not be found" }, + { WERR_DC_NOT_FOUND, "A domain controller could not be found" }, + { WERR_SETUP_NOT_JOINED, "Join failed" }, + { WERR_SETUP_ALREADY_JOINED, "Machine is already joined" }, + { WERR_SETUP_DOMAIN_CONTROLLER, "Machine is a Domain Controller" }, + { WERR_LOGON_FAILURE, "Invalid logon credentials" }, + { WERR_USER_EXISTS, "User account already exists" }, + { WERR_PASSWORD_MUST_CHANGE, "The password must be changed" }, + { WERR_ACCOUNT_LOCKED_OUT, "Account locked out" }, + { WERR_TIME_SKEW, "Time difference between client and server" }, + { WERR_USER_ALREADY_EXISTS, "User already exists" }, + { WERR_PASSWORD_RESTRICTION, "Password does not meet restrictions" }, + { WERR_NONE_MAPPED, "Could not map names to SIDs" }, + { WERR_NO_SUCH_USER, "No such User" }, + { WERR_GROUP_EXISTS, "Group already exists" }, + { WERR_DS_DRA_BAD_DN, "An invalid distinguished name was specified for this replication" }, + { WERR_DS_DRA_BAD_NC, "An invalid naming context was specified for this replication operation" }, + { WERR_WRONG_PASSWORD, "The current password is incorrect" } +}; + +/***************************************************************************** + Returns a DOS error message. not amazingly helpful, but better than a number. + *****************************************************************************/ + +const char *dos_errstr(WERROR werror) +{ + char *result; + int idx = 0; + + while (dos_errs[idx].dos_errstr != NULL) { + if (W_ERROR_V(dos_errs[idx].werror) == + W_ERROR_V(werror)) + return dos_errs[idx].dos_errstr; + idx++; + } + + result = talloc_asprintf(talloc_tos(), "DOS code 0x%08x", + W_ERROR_V(werror)); + SMB_ASSERT(result != NULL); + return result; +} + +/***************************************************************************** + Get friendly error string for WERRORs + *****************************************************************************/ + +const char *get_friendly_werror_msg(WERROR werror) +{ + int i = 0; + + for (i = 0; i < ARRAY_SIZE(dos_err_strs); i++) { + if (W_ERROR_V(dos_err_strs[i].werror) == + W_ERROR_V(werror)) { + return dos_err_strs[i].friendly_errstr; + } + } + + return dos_errstr(werror); +} + +/* compat function for samba4 */ +const char *win_errstr(WERROR werror) +{ + return dos_errstr(werror); +} diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c new file mode 100644 index 0000000000..2a445cbd5a --- /dev/null +++ b/source3/libsmb/dsgetdcname.c @@ -0,0 +1,1449 @@ +/* + Unix SMB/CIFS implementation. + + dsgetdcname + + Copyright (C) Gerald Carter 2006 + Copyright (C) Guenther Deschner 2007-2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#define DSGETDCNAME_FMT "DSGETDCNAME/DOMAIN/%s" +/* 15 minutes */ +#define DSGETDCNAME_CACHE_TTL 60*15 + +struct ip_service_name { + struct sockaddr_storage ss; + unsigned port; + const char *hostname; +}; + +static NTSTATUS make_dc_info_from_cldap_reply(TALLOC_CTX *mem_ctx, + uint32_t flags, + struct sockaddr_storage *ss, + uint32_t nt_version, + union nbt_cldap_netlogon *r, + struct netr_DsRGetDCNameInfo **info); + +/**************************************************************** +****************************************************************/ + +void debug_dsdcinfo_flags(int lvl, uint32_t flags) +{ + DEBUG(lvl,("debug_dsdcinfo_flags: 0x%08x\n\t", flags)); + + if (flags & DS_FORCE_REDISCOVERY) + DEBUGADD(lvl,("DS_FORCE_REDISCOVERY ")); + if (flags & 0x000000002) + DEBUGADD(lvl,("0x00000002 ")); + if (flags & 0x000000004) + DEBUGADD(lvl,("0x00000004 ")); + if (flags & 0x000000008) + DEBUGADD(lvl,("0x00000008 ")); + if (flags & DS_DIRECTORY_SERVICE_REQUIRED) + DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_REQUIRED ")); + if (flags & DS_DIRECTORY_SERVICE_PREFERRED) + DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_PREFERRED ")); + if (flags & DS_GC_SERVER_REQUIRED) + DEBUGADD(lvl,("DS_GC_SERVER_REQUIRED ")); + if (flags & DS_PDC_REQUIRED) + DEBUGADD(lvl,("DS_PDC_REQUIRED ")); + if (flags & DS_BACKGROUND_ONLY) + DEBUGADD(lvl,("DS_BACKGROUND_ONLY ")); + if (flags & DS_IP_REQUIRED) + DEBUGADD(lvl,("DS_IP_REQUIRED ")); + if (flags & DS_KDC_REQUIRED) + DEBUGADD(lvl,("DS_KDC_REQUIRED ")); + if (flags & DS_TIMESERV_REQUIRED) + DEBUGADD(lvl,("DS_TIMESERV_REQUIRED ")); + if (flags & DS_WRITABLE_REQUIRED) + DEBUGADD(lvl,("DS_WRITABLE_REQUIRED ")); + if (flags & DS_GOOD_TIMESERV_PREFERRED) + DEBUGADD(lvl,("DS_GOOD_TIMESERV_PREFERRED ")); + if (flags & DS_AVOID_SELF) + DEBUGADD(lvl,("DS_AVOID_SELF ")); + if (flags & DS_ONLY_LDAP_NEEDED) + DEBUGADD(lvl,("DS_ONLY_LDAP_NEEDED ")); + if (flags & DS_IS_FLAT_NAME) + DEBUGADD(lvl,("DS_IS_FLAT_NAME ")); + if (flags & DS_IS_DNS_NAME) + DEBUGADD(lvl,("DS_IS_DNS_NAME ")); + if (flags & 0x00040000) + DEBUGADD(lvl,("0x00040000 ")); + if (flags & 0x00080000) + DEBUGADD(lvl,("0x00080000 ")); + if (flags & 0x00100000) + DEBUGADD(lvl,("0x00100000 ")); + if (flags & 0x00200000) + DEBUGADD(lvl,("0x00200000 ")); + if (flags & 0x00400000) + DEBUGADD(lvl,("0x00400000 ")); + if (flags & 0x00800000) + DEBUGADD(lvl,("0x00800000 ")); + if (flags & 0x01000000) + DEBUGADD(lvl,("0x01000000 ")); + if (flags & 0x02000000) + DEBUGADD(lvl,("0x02000000 ")); + if (flags & 0x04000000) + DEBUGADD(lvl,("0x04000000 ")); + if (flags & 0x08000000) + DEBUGADD(lvl,("0x08000000 ")); + if (flags & 0x10000000) + DEBUGADD(lvl,("0x10000000 ")); + if (flags & 0x20000000) + DEBUGADD(lvl,("0x20000000 ")); + if (flags & DS_RETURN_DNS_NAME) + DEBUGADD(lvl,("DS_RETURN_DNS_NAME ")); + if (flags & DS_RETURN_FLAT_NAME) + DEBUGADD(lvl,("DS_RETURN_FLAT_NAME ")); + if (flags) + DEBUGADD(lvl,("\n")); +} + +/**************************************************************** +****************************************************************/ + +static char *dsgetdcname_cache_key(TALLOC_CTX *mem_ctx, const char *domain) +{ + if (!domain) { + return NULL; + } + + return talloc_asprintf_strupper_m(mem_ctx, DSGETDCNAME_FMT, domain); +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_cache_delete(TALLOC_CTX *mem_ctx, + const char *domain_name) +{ + char *key; + + if (!gencache_init()) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + key = dsgetdcname_cache_key(mem_ctx, domain_name); + if (!key) { + return NT_STATUS_NO_MEMORY; + } + + if (!gencache_del(key)) { + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_cache_store(TALLOC_CTX *mem_ctx, + const char *domain_name, + const DATA_BLOB *blob) +{ + time_t expire_time; + char *key; + bool ret = false; + + if (!gencache_init()) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + key = dsgetdcname_cache_key(mem_ctx, domain_name); + if (!key) { + return NT_STATUS_NO_MEMORY; + } + + expire_time = time(NULL) + DSGETDCNAME_CACHE_TTL; + + if (gencache_lock_entry(key) != 0) { + return NT_STATUS_LOCK_NOT_GRANTED; + } + + ret = gencache_set_data_blob(key, blob, expire_time); + + gencache_unlock_entry(key); + + return ret ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; +} + +/**************************************************************** +****************************************************************/ + +#define SET_STRING(x) \ + talloc_strdup(mem_ctx, x); \ + NT_STATUS_HAVE_NO_MEMORY(x); + +static NTSTATUS map_logon29_from_cldap_reply(TALLOC_CTX *mem_ctx, + uint32_t flags, + struct sockaddr_storage *ss, + uint32_t nt_version, + union nbt_cldap_netlogon *r, + struct nbt_cldap_netlogon_29 *p) +{ + char addr[INET6_ADDRSTRLEN]; + + ZERO_STRUCTP(p); + + print_sockaddr(addr, sizeof(addr), ss); + + /* FIXME */ + p->dc_sock_addr_size = 0x10; /* the w32 winsock addr size */ + p->dc_sock_addr.family = 2; /* AF_INET */ + p->dc_sock_addr.pdc_ip = talloc_strdup(mem_ctx, addr); + + switch (nt_version & 0x0000001f) { + case 0: + return NT_STATUS_INVALID_PARAMETER; + case 1: + case 16: + case 17: + p->pdc_name = SET_STRING(r->logon1.pdc_name); + p->domain = SET_STRING(r->logon1.domain_name); + + if (flags & DS_PDC_REQUIRED) { + p->server_type = NBT_SERVER_WRITABLE | + NBT_SERVER_PDC; + } + break; + case 2: + case 3: + case 18: + case 19: + p->pdc_name = SET_STRING(r->logon3.pdc_name); + p->domain = SET_STRING(r->logon3.domain_name); + p->pdc_dns_name = SET_STRING(r->logon3.pdc_dns_name); + p->dns_domain = SET_STRING(r->logon3.dns_domain); + p->server_type = r->logon3.server_type; + p->forest = SET_STRING(r->logon3.forest); + p->domain_uuid = r->logon3.domain_uuid; + + break; + case 4: + case 5: + case 6: + case 7: + p->pdc_name = SET_STRING(r->logon5.pdc_name); + p->domain = SET_STRING(r->logon5.domain); + p->pdc_dns_name = SET_STRING(r->logon5.pdc_dns_name); + p->dns_domain = SET_STRING(r->logon5.dns_domain); + p->server_type = r->logon5.server_type; + p->forest = SET_STRING(r->logon5.forest); + p->domain_uuid = r->logon5.domain_uuid; + p->server_site = SET_STRING(r->logon5.server_site); + p->client_site = SET_STRING(r->logon5.client_site); + + break; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + p->pdc_name = SET_STRING(r->logon13.pdc_name); + p->domain = SET_STRING(r->logon13.domain); + p->pdc_dns_name = SET_STRING(r->logon13.pdc_dns_name); + p->dns_domain = SET_STRING(r->logon13.dns_domain); + p->server_type = r->logon13.server_type; + p->forest = SET_STRING(r->logon13.forest); + p->domain_uuid = r->logon13.domain_uuid; + p->server_site = SET_STRING(r->logon13.server_site); + p->client_site = SET_STRING(r->logon13.client_site); + + break; + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + p->pdc_name = SET_STRING(r->logon15.pdc_name); + p->domain = SET_STRING(r->logon15.domain); + p->pdc_dns_name = SET_STRING(r->logon15.pdc_dns_name); + p->dns_domain = SET_STRING(r->logon15.dns_domain); + p->server_type = r->logon15.server_type; + p->forest = SET_STRING(r->logon15.forest); + p->domain_uuid = r->logon15.domain_uuid; + p->server_site = SET_STRING(r->logon15.server_site); + p->client_site = SET_STRING(r->logon15.client_site); + + break; + case 29: + case 30: + case 31: + p->pdc_name = SET_STRING(r->logon29.pdc_name); + p->domain = SET_STRING(r->logon29.domain); + p->pdc_dns_name = SET_STRING(r->logon29.pdc_dns_name); + p->dns_domain = SET_STRING(r->logon29.dns_domain); + p->server_type = r->logon29.server_type; + p->forest = SET_STRING(r->logon29.forest); + p->domain_uuid = r->logon29.domain_uuid; + p->server_site = SET_STRING(r->logon29.server_site); + p->client_site = SET_STRING(r->logon29.client_site); + p->next_closest_site = SET_STRING(r->logon29.next_closest_site); + + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS store_cldap_reply(TALLOC_CTX *mem_ctx, + uint32_t flags, + struct sockaddr_storage *ss, + uint32_t nt_version, + union nbt_cldap_netlogon *r) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + NTSTATUS status; + struct nbt_cldap_netlogon_29 logon29; + + status = map_logon29_from_cldap_reply(mem_ctx, flags, ss, + nt_version, r, &logon29); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &logon29, + (ndr_push_flags_fn_t)ndr_push_nbt_cldap_netlogon_29); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + if (logon29.domain) { + status = dsgetdcname_cache_store(mem_ctx, logon29.domain, &blob); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (logon29.client_site) { + sitename_store(logon29.domain, logon29.client_site); + } + } + if (logon29.dns_domain) { + status = dsgetdcname_cache_store(mem_ctx, logon29.dns_domain, &blob); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (logon29.client_site) { + sitename_store(logon29.dns_domain, logon29.client_site); + } + } + + done: + data_blob_free(&blob); + + return status; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_cache_refresh(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *domain_name, + struct GUID *domain_guid, + uint32_t flags, + const char *site_name, + struct netr_DsRGetDCNameInfo *info) +{ + struct netr_DsRGetDCNameInfo *dc_info; + + return dsgetdcname(mem_ctx, + msg_ctx, + domain_name, + domain_guid, + site_name, + flags | DS_FORCE_REDISCOVERY, + &dc_info); +} + +/**************************************************************** +****************************************************************/ + +static uint32_t get_cldap_reply_server_flags(union nbt_cldap_netlogon *r, + uint32_t nt_version) +{ + switch (nt_version & 0x0000001f) { + case 0: + case 1: + case 16: + case 17: + return 0; + case 2: + case 3: + case 18: + case 19: + return r->logon3.server_type; + case 4: + case 5: + case 6: + case 7: + return r->logon5.server_type; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + return r->logon13.server_type; + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + return r->logon15.server_type; + case 29: + case 30: + case 31: + return r->logon29.server_type; + default: + return 0; + } +} + +/**************************************************************** +****************************************************************/ + +#define RETURN_ON_FALSE(x) if (!x) return false; + +static bool check_cldap_reply_required_flags(uint32_t ret_flags, + uint32_t req_flags) +{ + if (ret_flags == 0) { + return true; + } + + if (req_flags & DS_PDC_REQUIRED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_PDC); + + if (req_flags & DS_GC_SERVER_REQUIRED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_GC); + + if (req_flags & DS_ONLY_LDAP_NEEDED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_LDAP); + + if ((req_flags & DS_DIRECTORY_SERVICE_REQUIRED) || + (req_flags & DS_DIRECTORY_SERVICE_PREFERRED)) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS); + + if (req_flags & DS_KDC_REQUIRED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_KDC); + + if (req_flags & DS_TIMESERV_REQUIRED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_TIMESERV); + + if (req_flags & DS_WRITABLE_REQUIRED) + RETURN_ON_FALSE(ret_flags & NBT_SERVER_WRITABLE); + + return true; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_cache_fetch(TALLOC_CTX *mem_ctx, + const char *domain_name, + struct GUID *domain_guid, + uint32_t flags, + const char *site_name, + struct netr_DsRGetDCNameInfo **info_p, + bool *expired) +{ + char *key; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct netr_DsRGetDCNameInfo *info; + union nbt_cldap_netlogon p; + struct nbt_cldap_netlogon_29 r; + NTSTATUS status; + + if (!gencache_init()) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + key = dsgetdcname_cache_key(mem_ctx, domain_name); + if (!key) { + return NT_STATUS_NO_MEMORY; + } + + if (!gencache_get_data_blob(key, &blob, expired)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + info = TALLOC_ZERO_P(mem_ctx, struct netr_DsRGetDCNameInfo); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r, + (ndr_pull_flags_fn_t)ndr_pull_nbt_cldap_netlogon_29); + + data_blob_free(&blob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dsgetdcname_cache_delete(mem_ctx, domain_name); + return ndr_map_error2ntstatus(ndr_err); + } + + p.logon29 = r; + + status = make_dc_info_from_cldap_reply(mem_ctx, flags, NULL, + 29, + &p, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, info); + } + + /* check flags */ + if (!check_cldap_reply_required_flags(info->dc_flags, flags)) { + DEBUG(10,("invalid flags\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if ((flags & DS_IP_REQUIRED) && + (info->dc_address_type != DS_ADDRESS_TYPE_INET)) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + *info_p = info; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_cached(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *domain_name, + struct GUID *domain_guid, + uint32_t flags, + const char *site_name, + struct netr_DsRGetDCNameInfo **info) +{ + NTSTATUS status; + bool expired = false; + + status = dsgetdcname_cache_fetch(mem_ctx, domain_name, domain_guid, + flags, site_name, info, &expired); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("dsgetdcname_cached: cache fetch failed with: %s\n", + nt_errstr(status))); + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + if (flags & DS_BACKGROUND_ONLY) { + return status; + } + + if (expired) { + status = dsgetdcname_cache_refresh(mem_ctx, msg_ctx, + domain_name, + domain_guid, flags, + site_name, *info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return status; +} + +/**************************************************************** +****************************************************************/ + +static bool check_allowed_required_flags(uint32_t flags, + const char *site_name) +{ + uint32_t return_type = flags & (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME); + uint32_t offered_type = flags & (DS_IS_FLAT_NAME|DS_IS_DNS_NAME); + uint32_t query_type = flags & (DS_BACKGROUND_ONLY|DS_FORCE_REDISCOVERY); + + /* FIXME: check for DSGETDC_VALID_FLAGS and check for excluse bits + * (DS_PDC_REQUIRED, DS_KDC_REQUIRED, DS_GC_SERVER_REQUIRED) */ + + debug_dsdcinfo_flags(10, flags); + + if ((flags & DS_TRY_NEXTCLOSEST_SITE) && site_name) { + return false; + } + + if (return_type == (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME)) { + return false; + } + + if (offered_type == (DS_IS_DNS_NAME|DS_IS_FLAT_NAME)) { + return false; + } + + if (query_type == (DS_BACKGROUND_ONLY|DS_FORCE_REDISCOVERY)) { + return false; + } + +#if 0 + if ((flags & DS_RETURN_DNS_NAME) && (!(flags & DS_IP_REQUIRED))) { + printf("gd: here5 \n"); + return false; + } +#endif + return true; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS discover_dc_netbios(TALLOC_CTX *mem_ctx, + const char *domain_name, + uint32_t flags, + struct ip_service_name **returned_dclist, + int *returned_count) +{ + NTSTATUS status; + enum nbt_name_type name_type = NBT_NAME_LOGON; + struct ip_service *iplist; + int i; + struct ip_service_name *dclist = NULL; + int count; + + *returned_dclist = NULL; + *returned_count = 0; + + if (lp_disable_netbios()) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (flags & DS_PDC_REQUIRED) { + name_type = NBT_NAME_PDC; + } + + status = internal_resolve_name(domain_name, name_type, NULL, + &iplist, &count, + "lmhosts wins bcast"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("discover_dc_netbios: failed to find DC\n")); + return status; + } + + dclist = TALLOC_ZERO_ARRAY(mem_ctx, struct ip_service_name, count); + if (!dclist) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<count; i++) { + + char addr[INET6_ADDRSTRLEN]; + struct ip_service_name *r = &dclist[i]; + + print_sockaddr(addr, sizeof(addr), + &iplist[i].ss); + + r->ss = iplist[i].ss; + r->port = iplist[i].port; + r->hostname = talloc_strdup(mem_ctx, addr); + if (!r->hostname) { + return NT_STATUS_NO_MEMORY; + } + + } + + *returned_dclist = dclist; + *returned_count = count; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS discover_dc_dns(TALLOC_CTX *mem_ctx, + const char *domain_name, + struct GUID *domain_guid, + uint32_t flags, + const char *site_name, + struct ip_service_name **returned_dclist, + int *return_count) +{ + int i, j; + NTSTATUS status; + struct dns_rr_srv *dcs = NULL; + int numdcs = 0; + int numaddrs = 0; + struct ip_service_name *dclist = NULL; + int count = 0; + + if (flags & DS_PDC_REQUIRED) { + status = ads_dns_query_pdc(mem_ctx, domain_name, + &dcs, &numdcs); + } else if (flags & DS_GC_SERVER_REQUIRED) { + status = ads_dns_query_gcs(mem_ctx, domain_name, site_name, + &dcs, &numdcs); + } else if (flags & DS_KDC_REQUIRED) { + status = ads_dns_query_kdcs(mem_ctx, domain_name, site_name, + &dcs, &numdcs); + } else if (flags & DS_DIRECTORY_SERVICE_REQUIRED) { + status = ads_dns_query_dcs(mem_ctx, domain_name, site_name, + &dcs, &numdcs); + } else if (domain_guid) { + status = ads_dns_query_dcs_guid(mem_ctx, domain_name, + domain_guid, &dcs, &numdcs); + } else { + status = ads_dns_query_dcs(mem_ctx, domain_name, site_name, + &dcs, &numdcs); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (numdcs == 0) { + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + for (i=0;i<numdcs;i++) { + numaddrs += MAX(dcs[i].num_ips,1); + } + + dclist = TALLOC_ZERO_ARRAY(mem_ctx, + struct ip_service_name, + numaddrs); + if (!dclist) { + return NT_STATUS_NO_MEMORY; + } + + /* now unroll the list of IP addresses */ + + *return_count = 0; + i = 0; + j = 0; + + while ((i < numdcs) && (count < numaddrs)) { + + struct ip_service_name *r = &dclist[count]; + + r->port = dcs[count].port; + r->hostname = dcs[count].hostname; + + /* If we don't have an IP list for a name, lookup it up */ + + if (!dcs[i].ss_s) { + interpret_string_addr(&r->ss, dcs[i].hostname, 0); + i++; + j = 0; + } else { + /* use the IP addresses from the SRV sresponse */ + + if (j >= dcs[i].num_ips) { + i++; + j = 0; + continue; + } + + r->ss = dcs[i].ss_s[j]; + j++; + } + + /* make sure it is a valid IP. I considered checking the + * negative connection cache, but this is the wrong place for + * it. Maybe only as a hac. After think about it, if all of + * the IP addresses retuend from DNS are dead, what hope does a + * netbios name lookup have? The standard reason for falling + * back to netbios lookups is that our DNS server doesn't know + * anything about the DC's -- jerry */ + + if (!is_zero_addr(&r->ss)) { + count++; + continue; + } + } + + *returned_dclist = dclist; + *return_count = count; + + if (count > 0) { + return NT_STATUS_OK; + } + + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS make_domain_controller_info(TALLOC_CTX *mem_ctx, + const char *dc_unc, + const char *dc_address, + uint32_t dc_address_type, + const struct GUID *domain_guid, + const char *domain_name, + const char *forest_name, + uint32_t flags, + const char *dc_site_name, + const char *client_site_name, + struct netr_DsRGetDCNameInfo **info_out) +{ + struct netr_DsRGetDCNameInfo *info; + + info = TALLOC_ZERO_P(mem_ctx, struct netr_DsRGetDCNameInfo); + NT_STATUS_HAVE_NO_MEMORY(info); + + if (dc_unc) { + info->dc_unc = talloc_strdup(mem_ctx, dc_unc); + NT_STATUS_HAVE_NO_MEMORY(info->dc_unc); + } + + if (dc_address) { + if (!(dc_address[0] == '\\' && dc_address[1] == '\\')) { + info->dc_address = talloc_asprintf(mem_ctx, "\\\\%s", + dc_address); + } else { + info->dc_address = talloc_strdup(mem_ctx, dc_address); + } + NT_STATUS_HAVE_NO_MEMORY(info->dc_address); + } + + info->dc_address_type = dc_address_type; + + if (domain_guid) { + info->domain_guid = *domain_guid; + } + + if (domain_name) { + info->domain_name = talloc_strdup(mem_ctx, domain_name); + NT_STATUS_HAVE_NO_MEMORY(info->domain_name); + } + + if (forest_name && *forest_name) { + info->forest_name = talloc_strdup(mem_ctx, forest_name); + NT_STATUS_HAVE_NO_MEMORY(info->forest_name); + flags |= DS_DNS_FOREST; + } + + info->dc_flags = flags; + + if (dc_site_name) { + info->dc_site_name = talloc_strdup(mem_ctx, dc_site_name); + NT_STATUS_HAVE_NO_MEMORY(info->dc_site_name); + } + + if (client_site_name) { + info->client_site_name = talloc_strdup(mem_ctx, + client_site_name); + NT_STATUS_HAVE_NO_MEMORY(info->client_site_name); + } + + *info_out = info; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static void map_dc_and_domain_names(uint32_t flags, + const char *dc_name, + const char *domain_name, + const char *dns_dc_name, + const char *dns_domain_name, + uint32_t *dc_flags, + const char **hostname_p, + const char **domain_p) +{ + switch (flags & 0xf0000000) { + case DS_RETURN_FLAT_NAME: + if (dc_name && domain_name && + *dc_name && *domain_name) { + *hostname_p = dc_name; + *domain_p = domain_name; + break; + } + case DS_RETURN_DNS_NAME: + default: + if (dns_dc_name && dns_domain_name && + *dns_dc_name && *dns_domain_name) { + *hostname_p = dns_dc_name; + *domain_p = dns_domain_name; + *dc_flags |= DS_DNS_DOMAIN | DS_DNS_CONTROLLER; + break; + } + if (dc_name && domain_name && + *dc_name && *domain_name) { + *hostname_p = dc_name; + *domain_p = domain_name; + break; + } + } +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS make_dc_info_from_cldap_reply(TALLOC_CTX *mem_ctx, + uint32_t flags, + struct sockaddr_storage *ss, + uint32_t nt_version, + union nbt_cldap_netlogon *r, + struct netr_DsRGetDCNameInfo **info) +{ + const char *dc_hostname = NULL; + const char *dc_domain_name = NULL; + const char *dc_address = NULL; + const char *dc_forest = NULL; + uint32_t dc_address_type = 0; + uint32_t dc_flags = 0; + struct GUID *dc_domain_guid = NULL; + const char *dc_server_site = NULL; + const char *dc_client_site = NULL; + + char addr[INET6_ADDRSTRLEN]; + + if (ss) { + print_sockaddr(addr, sizeof(addr), ss); + dc_address = addr; + dc_address_type = DS_ADDRESS_TYPE_INET; + } + + switch (nt_version & 0x0000001f) { + case 0: + case 1: + case 16: + case 17: + if (!ss) { + dc_address = r->logon1.pdc_name; + dc_address_type = DS_ADDRESS_TYPE_NETBIOS; + } + + map_dc_and_domain_names(flags, + r->logon1.pdc_name, + r->logon1.domain_name, + NULL, + NULL, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + if (flags & DS_PDC_REQUIRED) { + dc_flags = NBT_SERVER_WRITABLE | NBT_SERVER_PDC; + } + break; + case 2: + case 3: + case 18: + case 19: + if (!ss) { + dc_address = r->logon3.pdc_ip; + dc_address_type = DS_ADDRESS_TYPE_INET; + } + + map_dc_and_domain_names(flags, + r->logon3.pdc_name, + r->logon3.domain_name, + r->logon3.pdc_dns_name, + r->logon3.dns_domain, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + dc_flags |= r->logon3.server_type; + dc_forest = r->logon3.forest; + dc_domain_guid = &r->logon3.domain_uuid; + + break; + case 4: + case 5: + case 6: + case 7: + if (!ss) { + dc_address = r->logon5.pdc_name; + dc_address_type = DS_ADDRESS_TYPE_NETBIOS; + } + + map_dc_and_domain_names(flags, + r->logon5.pdc_name, + r->logon5.domain, + r->logon5.pdc_dns_name, + r->logon5.dns_domain, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + dc_flags |= r->logon5.server_type; + dc_forest = r->logon5.forest; + dc_domain_guid = &r->logon5.domain_uuid; + dc_server_site = r->logon5.server_site; + dc_client_site = r->logon5.client_site; + + break; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + if (!ss) { + dc_address = r->logon13.dc_sock_addr.pdc_ip; + dc_address_type = DS_ADDRESS_TYPE_INET; + } + + map_dc_and_domain_names(flags, + r->logon13.pdc_name, + r->logon13.domain, + r->logon13.pdc_dns_name, + r->logon13.dns_domain, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + dc_flags |= r->logon13.server_type; + dc_forest = r->logon13.forest; + dc_domain_guid = &r->logon13.domain_uuid; + dc_server_site = r->logon13.server_site; + dc_client_site = r->logon13.client_site; + + break; + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + if (!ss) { + dc_address = r->logon15.pdc_name; + dc_address_type = DS_ADDRESS_TYPE_NETBIOS; + } + + map_dc_and_domain_names(flags, + r->logon15.pdc_name, + r->logon15.domain, + r->logon15.pdc_dns_name, + r->logon15.dns_domain, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + dc_flags |= r->logon15.server_type; + dc_forest = r->logon15.forest; + dc_domain_guid = &r->logon15.domain_uuid; + dc_server_site = r->logon15.server_site; + dc_client_site = r->logon15.client_site; + + break; + case 29: + case 30: + case 31: + if (!ss) { + dc_address = r->logon29.dc_sock_addr.pdc_ip; + dc_address_type = DS_ADDRESS_TYPE_INET; + } + + map_dc_and_domain_names(flags, + r->logon29.pdc_name, + r->logon29.domain, + r->logon29.pdc_dns_name, + r->logon29.dns_domain, + &dc_flags, + &dc_hostname, + &dc_domain_name); + + dc_flags |= r->logon29.server_type; + dc_forest = r->logon29.forest; + dc_domain_guid = &r->logon29.domain_uuid; + dc_server_site = r->logon29.server_site; + dc_client_site = r->logon29.client_site; + + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + return make_domain_controller_info(mem_ctx, + dc_hostname, + dc_address, + dc_address_type, + dc_domain_guid, + dc_domain_name, + dc_forest, + dc_flags, + dc_server_site, + dc_client_site, + info); +} + +/**************************************************************** +****************************************************************/ + +static uint32_t map_ds_flags_to_nt_version(uint32_t flags) +{ + uint32_t nt_version = 0; + + if (flags & DS_PDC_REQUIRED) { + nt_version |= NETLOGON_VERSION_PDC; + } + + if (flags & DS_GC_SERVER_REQUIRED) { + nt_version |= NETLOGON_VERSION_GC; + } + + if (flags & DS_TRY_NEXTCLOSEST_SITE) { + nt_version |= NETLOGON_VERSION_WITH_CLOSEST_SITE; + } + + if (flags & DS_IP_REQUIRED) { + nt_version |= NETLOGON_VERSION_IP; + } + + return nt_version; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS process_dc_dns(TALLOC_CTX *mem_ctx, + const char *domain_name, + uint32_t flags, + struct ip_service_name *dclist, + int num_dcs, + struct netr_DsRGetDCNameInfo **info) +{ + int i = 0; + bool valid_dc = false; + union nbt_cldap_netlogon *r = NULL; + uint32_t nt_version = NETLOGON_VERSION_5 | + NETLOGON_VERSION_5EX; + uint32_t ret_flags = 0; + NTSTATUS status; + + nt_version |= map_ds_flags_to_nt_version(flags); + + for (i=0; i<num_dcs; i++) { + + DEBUG(10,("LDAP ping to %s\n", dclist[i].hostname)); + + if (ads_cldap_netlogon(mem_ctx, dclist[i].hostname, + domain_name, + &nt_version, + &r)) + { + ret_flags = get_cldap_reply_server_flags(r, nt_version); + + if (check_cldap_reply_required_flags(ret_flags, flags)) { + valid_dc = true; + break; + } + } + + continue; + } + + if (!valid_dc) { + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + status = make_dc_info_from_cldap_reply(mem_ctx, flags, &dclist[i].ss, + nt_version, r, info); + if (NT_STATUS_IS_OK(status)) { + return store_cldap_reply(mem_ctx, flags, &dclist[i].ss, + nt_version, r); + } + + return status; +} + +/**************************************************************** +****************************************************************/ + +static struct event_context *ev_context(void) +{ + static struct event_context *ctx; + + if (!ctx && !(ctx = event_context_init(NULL))) { + smb_panic("Could not init event context"); + } + return ctx; +} + +/**************************************************************** +****************************************************************/ + +static struct messaging_context *msg_context(TALLOC_CTX *mem_ctx) +{ + static struct messaging_context *ctx; + + if (!ctx && !(ctx = messaging_init(mem_ctx, server_id_self(), + ev_context()))) { + smb_panic("Could not init messaging context"); + } + return ctx; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS process_dc_netbios(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *domain_name, + uint32_t flags, + struct ip_service_name *dclist, + int num_dcs, + struct netr_DsRGetDCNameInfo **info) +{ + struct sockaddr_storage ss; + struct ip_service ip_list; + enum nbt_name_type name_type = NBT_NAME_LOGON; + NTSTATUS status; + int i; + const char *dc_name = NULL; + fstring tmp_dc_name; + union nbt_cldap_netlogon *r = NULL; + bool store_cache = false; + uint32_t nt_version = NETLOGON_VERSION_1 | + NETLOGON_VERSION_5 | + NETLOGON_VERSION_5EX_WITH_IP; + + if (!msg_ctx) { + msg_ctx = msg_context(mem_ctx); + } + + if (flags & DS_PDC_REQUIRED) { + name_type = NBT_NAME_PDC; + } + + nt_version |= map_ds_flags_to_nt_version(flags); + + DEBUG(10,("process_dc_netbios\n")); + + for (i=0; i<num_dcs; i++) { + + ip_list.ss = dclist[i].ss; + ip_list.port = 0; + + if (!interpret_string_addr(&ss, dclist[i].hostname, AI_NUMERICHOST)) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (send_getdc_request(mem_ctx, msg_ctx, + &dclist[i].ss, domain_name, + NULL, nt_version)) + { + int k; + smb_msleep(300); + for (k=0; k<5; k++) { + if (receive_getdc_response(mem_ctx, + &dclist[i].ss, + domain_name, + &nt_version, + &dc_name, + &r)) { + store_cache = true; + namecache_store(dc_name, NBT_NAME_SERVER, 1, &ip_list); + goto make_reply; + } + smb_msleep(1500); + } + } + + if (name_status_find(domain_name, + name_type, + NBT_NAME_SERVER, + &dclist[i].ss, + tmp_dc_name)) + { + struct nbt_cldap_netlogon_1 logon1; + + r = TALLOC_ZERO_P(mem_ctx, union nbt_cldap_netlogon); + NT_STATUS_HAVE_NO_MEMORY(r); + + ZERO_STRUCT(logon1); + + nt_version = NETLOGON_VERSION_1; + + logon1.nt_version = nt_version; + logon1.pdc_name = tmp_dc_name; + logon1.domain_name = talloc_strdup_upper(mem_ctx, domain_name); + NT_STATUS_HAVE_NO_MEMORY(logon1.domain_name); + + r->logon1 = logon1; + + namecache_store(tmp_dc_name, NBT_NAME_SERVER, 1, &ip_list); + + goto make_reply; + } + } + + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + + make_reply: + + status = make_dc_info_from_cldap_reply(mem_ctx, flags, &dclist[i].ss, + nt_version, r, info); + if (NT_STATUS_IS_OK(status) && store_cache) { + return store_cldap_reply(mem_ctx, flags, &dclist[i].ss, + nt_version, r); + } + + return status; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS dsgetdcname_rediscover(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *domain_name, + struct GUID *domain_guid, + uint32_t flags, + const char *site_name, + struct netr_DsRGetDCNameInfo **info) +{ + NTSTATUS status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + struct ip_service_name *dclist = NULL; + int num_dcs; + + DEBUG(10,("dsgetdcname_rediscover\n")); + + if (flags & DS_IS_FLAT_NAME) { + + status = discover_dc_netbios(mem_ctx, domain_name, flags, + &dclist, &num_dcs); + NT_STATUS_NOT_OK_RETURN(status); + + return process_dc_netbios(mem_ctx, msg_ctx, domain_name, flags, + dclist, num_dcs, info); + } + + if (flags & DS_IS_DNS_NAME) { + + status = discover_dc_dns(mem_ctx, domain_name, domain_guid, + flags, site_name, &dclist, &num_dcs); + NT_STATUS_NOT_OK_RETURN(status); + + return process_dc_dns(mem_ctx, domain_name, flags, + dclist, num_dcs, info); + } + + status = discover_dc_dns(mem_ctx, domain_name, domain_guid, flags, + site_name, &dclist, &num_dcs); + + if (NT_STATUS_IS_OK(status) && num_dcs != 0) { + + status = process_dc_dns(mem_ctx, domain_name, flags, dclist, + num_dcs, info); + if (NT_STATUS_IS_OK(status)) { + return status; + } + } + + status = discover_dc_netbios(mem_ctx, domain_name, flags, &dclist, + &num_dcs); + NT_STATUS_NOT_OK_RETURN(status); + + return process_dc_netbios(mem_ctx, msg_ctx, domain_name, flags, dclist, + num_dcs, info); +} + +/******************************************************************** + dsgetdcname. + + This will be the only public function here. +********************************************************************/ + +NTSTATUS dsgetdcname(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *domain_name, + struct GUID *domain_guid, + const char *site_name, + uint32_t flags, + struct netr_DsRGetDCNameInfo **info) +{ + NTSTATUS status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + struct netr_DsRGetDCNameInfo *myinfo = NULL; + char *query_site = NULL; + + DEBUG(10,("dsgetdcname: domain_name: %s, " + "domain_guid: %s, site_name: %s, flags: 0x%08x\n", + domain_name, + domain_guid ? GUID_string(mem_ctx, domain_guid) : "(null)", + site_name, flags)); + + *info = NULL; + + if (!check_allowed_required_flags(flags, site_name)) { + DEBUG(0,("invalid flags specified\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!site_name) { + query_site = sitename_fetch(domain_name); + } else { + query_site = SMB_STRDUP(site_name); + } + + if (flags & DS_FORCE_REDISCOVERY) { + goto rediscover; + } + + status = dsgetdcname_cached(mem_ctx, msg_ctx, domain_name, domain_guid, + flags, query_site, &myinfo); + if (NT_STATUS_IS_OK(status)) { + *info = myinfo; + goto done; + } + + if (flags & DS_BACKGROUND_ONLY) { + goto done; + } + + rediscover: + status = dsgetdcname_rediscover(mem_ctx, msg_ctx, domain_name, + domain_guid, flags, query_site, + &myinfo); + + if (NT_STATUS_IS_OK(status)) { + *info = myinfo; + } + + done: + SAFE_FREE(query_site); + + return status; +} diff --git a/source3/libsmb/errormap.c b/source3/libsmb/errormap.c new file mode 100644 index 0000000000..4ec30f7e17 --- /dev/null +++ b/source3/libsmb/errormap.c @@ -0,0 +1,1609 @@ +/* + * Unix SMB/CIFS implementation. + * error mapping functions + * Copyright (C) Andrew Tridgell 2001 + * Copyright (C) Andrew Bartlett 2001 + * Copyright (C) Tim Potter 2000 + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +/* This map was extracted by the ERRMAPEXTRACT smbtorture command. + The setup was a Samba HEAD (2002-01-03) PDC and an Win2k member + workstation. The PDC was modified (by using the 'name_to_nt_status' + authentication module) to convert the username (in hex) into the + corresponding NTSTATUS error return. + + By opening two nbt sessions to the Win2k workstation, one negotiating + DOS and one negotiating NT errors it was possible to extract the + error mapping. (Because the server only supplies NT errors, the + NT4 workstation had to use its own error tables to convert these + to dos errors). + + Some errors show up as 'squashed' because the NT error connection + got back a different error to the one it sent, so a mapping could + not be determined (a guess has been made in this case, to map the + error as squashed). This is done mainly to prevent users from getting + NT_STATUS_WRONG_PASSWORD and NT_STATUS_NO_SUCH_USER errors (they get + NT_STATUS_LOGON_FAILURE instead. + + -- abartlet (2002-01-03) +*/ + +/* NT status -> dos error map */ +static const struct { + uint8 dos_class; + uint32 dos_code; + NTSTATUS ntstatus; +} ntstatus_to_dos_map[] = { + {ERRDOS, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, + {ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, + {ERRDOS, 87, NT_STATUS_INVALID_INFO_CLASS}, + {ERRDOS, 24, NT_STATUS_INFO_LENGTH_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS_ACCESS_VIOLATION}, + {ERRHRD, ERRgeneral, NT_STATUS_IN_PAGE_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_QUOTA}, + {ERRDOS, ERRbadfid, NT_STATUS_INVALID_HANDLE}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_INITIAL_STACK}, + {ERRDOS, 193, NT_STATUS_BAD_INITIAL_PC}, + {ERRDOS, 87, NT_STATUS_INVALID_CID}, + {ERRHRD, ERRgeneral, NT_STATUS_TIMER_NOT_CANCELED}, + {ERRDOS, ERRinvalidparam, NT_STATUS_INVALID_PARAMETER}, + {ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_DEVICE}, + {ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_FILE}, + {ERRDOS, ERRbadfunc, NT_STATUS_INVALID_DEVICE_REQUEST}, + {ERRDOS, 38, NT_STATUS_END_OF_FILE}, + {ERRDOS, 34, NT_STATUS_WRONG_VOLUME}, + {ERRDOS, 21, NT_STATUS_NO_MEDIA_IN_DEVICE}, + {ERRHRD, ERRgeneral, NT_STATUS_UNRECOGNIZED_MEDIA}, + {ERRDOS, 27, NT_STATUS_NONEXISTENT_SECTOR}, +/** Session setup succeeded. This shouldn't happen...*/ +/** Session setup succeeded. This shouldn't happen...*/ +/** NT error on DOS connection! (NT_STATUS_OK) */ +/* { This NT error code was 'sqashed' + from NT_STATUS_MORE_PROCESSING_REQUIRED to NT_STATUS_OK + during the session setup } +*/ +#if 0 + {SUCCESS, 0, NT_STATUS_OK}, +#endif + {ERRDOS, ERRnomem, NT_STATUS_NO_MEMORY}, + {ERRDOS, 487, NT_STATUS_CONFLICTING_ADDRESSES}, + {ERRDOS, 487, NT_STATUS_NOT_MAPPED_VIEW}, + {ERRDOS, 87, NT_STATUS_UNABLE_TO_FREE_VM}, + {ERRDOS, 87, NT_STATUS_UNABLE_TO_DELETE_SECTION}, + {ERRDOS, 2142, NT_STATUS_INVALID_SYSTEM_SERVICE}, + {ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_INSTRUCTION}, + {ERRDOS, ERRnoaccess, NT_STATUS_INVALID_LOCK_SEQUENCE}, + {ERRDOS, ERRnoaccess, NT_STATUS_INVALID_VIEW_SIZE}, + {ERRDOS, 193, NT_STATUS_INVALID_FILE_FOR_SECTION}, + {ERRDOS, ERRnoaccess, NT_STATUS_ALREADY_COMMITTED}, +/* { This NT error code was 'sqashed' + from NT_STATUS_ACCESS_DENIED to NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE + during the session setup } +*/ + {ERRDOS, ERRnoaccess, NT_STATUS_ACCESS_DENIED}, + {ERRDOS, 111, NT_STATUS_BUFFER_TOO_SMALL}, +/* + * Not an official error, as only bit 0x80000000, not bits 0xC0000000 are set. + */ + {ERRDOS, ERRmoredata, STATUS_BUFFER_OVERFLOW}, + {ERRDOS, ERRnofiles, STATUS_NO_MORE_FILES}, + {ERRDOS, ERRbadfid, NT_STATUS_OBJECT_TYPE_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS_NONCONTINUABLE_EXCEPTION}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_DISPOSITION}, + {ERRHRD, ERRgeneral, NT_STATUS_UNWIND}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_STACK}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_UNWIND_TARGET}, + {ERRDOS, 158, NT_STATUS_NOT_LOCKED}, + {ERRHRD, ERRgeneral, NT_STATUS_PARITY_ERROR}, + {ERRDOS, 487, NT_STATUS_UNABLE_TO_DECOMMIT_VM}, + {ERRDOS, 487, NT_STATUS_NOT_COMMITTED}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_PORT_ATTRIBUTES}, + {ERRHRD, ERRgeneral, NT_STATUS_PORT_MESSAGE_TOO_LONG}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_MIX}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_QUOTA_LOWER}, + {ERRHRD, ERRgeneral, NT_STATUS_DISK_CORRUPT_ERROR}, + {ERRDOS, ERRinvalidname, NT_STATUS_OBJECT_NAME_INVALID}, + {ERRDOS, ERRbadfile, NT_STATUS_OBJECT_NAME_NOT_FOUND}, + {ERRDOS, 183, NT_STATUS_OBJECT_NAME_COLLISION}, + {ERRHRD, ERRgeneral, NT_STATUS_HANDLE_NOT_WAITABLE}, + {ERRDOS, ERRbadfid, NT_STATUS_PORT_DISCONNECTED}, + {ERRHRD, ERRgeneral, NT_STATUS_DEVICE_ALREADY_ATTACHED}, + {ERRDOS, ERRinvalidpath, NT_STATUS_OBJECT_PATH_INVALID}, + {ERRDOS, ERRbadpath, NT_STATUS_OBJECT_PATH_NOT_FOUND}, + {ERRDOS, ERRinvalidpath, NT_STATUS_OBJECT_PATH_SYNTAX_BAD}, + {ERRHRD, ERRgeneral, NT_STATUS_DATA_OVERRUN}, + {ERRHRD, ERRgeneral, NT_STATUS_DATA_LATE_ERROR}, + {ERRDOS, 23, NT_STATUS_DATA_ERROR}, + {ERRDOS, 23, NT_STATUS_CRC_ERROR}, + {ERRDOS, ERRnomem, NT_STATUS_SECTION_TOO_BIG}, + {ERRDOS, ERRnoaccess, NT_STATUS_PORT_CONNECTION_REFUSED}, + {ERRDOS, ERRbadfid, NT_STATUS_INVALID_PORT_HANDLE}, + {ERRDOS, ERRbadshare, NT_STATUS_SHARING_VIOLATION}, + {ERRHRD, ERRgeneral, NT_STATUS_QUOTA_EXCEEDED}, + {ERRDOS, 87, NT_STATUS_INVALID_PAGE_PROTECTION}, + {ERRDOS, 288, NT_STATUS_MUTANT_NOT_OWNED}, + {ERRDOS, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED}, + {ERRDOS, 87, NT_STATUS_PORT_ALREADY_SET}, + {ERRDOS, 87, NT_STATUS_SECTION_NOT_IMAGE}, + {ERRDOS, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED}, + {ERRDOS, ERRnoaccess, NT_STATUS_THREAD_IS_TERMINATING}, + {ERRDOS, 87, NT_STATUS_BAD_WORKING_SET_LIMIT}, + {ERRDOS, 87, NT_STATUS_INCOMPATIBLE_FILE_MAP}, + {ERRDOS, 87, NT_STATUS_SECTION_PROTECTION}, + {ERRDOS, 282, NT_STATUS_EAS_NOT_SUPPORTED}, + {ERRDOS, 255, NT_STATUS_EA_TOO_LARGE}, + {ERRHRD, ERRgeneral, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_EAS_ON_FILE}, + {ERRHRD, ERRgeneral, NT_STATUS_EA_CORRUPT_ERROR}, + {ERRDOS, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT}, + {ERRDOS, ERRlock, NT_STATUS_LOCK_NOT_GRANTED}, + {ERRDOS, ERRnoaccess, NT_STATUS_DELETE_PENDING}, + {ERRDOS, ERRunsup, NT_STATUS_CTL_FILE_NOT_SUPPORTED}, + {ERRHRD, ERRgeneral, NT_STATUS_UNKNOWN_REVISION}, + {ERRHRD, ERRgeneral, NT_STATUS_REVISION_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_OWNER}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_PRIMARY_GROUP}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_IMPERSONATION_TOKEN}, + {ERRHRD, ERRgeneral, NT_STATUS_CANT_DISABLE_MANDATORY}, + {ERRDOS, 2215, NT_STATUS_NO_LOGON_SERVERS}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_LOGON_SESSION}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_PRIVILEGE}, + {ERRDOS, ERRnoaccess, NT_STATUS_PRIVILEGE_NOT_HELD}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_ACCOUNT_NAME}, + {ERRHRD, ERRgeneral, NT_STATUS_USER_EXISTS}, +/* { This NT error code was 'sqashed' + from NT_STATUS_NO_SUCH_USER to NT_STATUS_LOGON_FAILURE + during the session setup } +*/ + {ERRDOS, ERRnoaccess, NT_STATUS_NO_SUCH_USER}, + {ERRHRD, ERRgeneral, NT_STATUS_GROUP_EXISTS}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_GROUP}, + {ERRHRD, ERRgeneral, NT_STATUS_MEMBER_IN_GROUP}, + {ERRHRD, ERRgeneral, NT_STATUS_MEMBER_NOT_IN_GROUP}, + {ERRHRD, ERRgeneral, NT_STATUS_LAST_ADMIN}, +/* { This NT error code was 'sqashed' + from NT_STATUS_WRONG_PASSWORD to NT_STATUS_LOGON_FAILURE + during the session setup } +*/ + {ERRSRV, ERRbadpw, NT_STATUS_WRONG_PASSWORD}, + {ERRHRD, ERRgeneral, NT_STATUS_ILL_FORMED_PASSWORD}, + {ERRHRD, ERRgeneral, NT_STATUS_PASSWORD_RESTRICTION}, + {ERRDOS, ERRnoaccess, NT_STATUS_LOGON_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_ACCOUNT_RESTRICTION}, + {ERRSRV, 2241, NT_STATUS_INVALID_LOGON_HOURS}, + {ERRSRV, 2240, NT_STATUS_INVALID_WORKSTATION}, + {ERRSRV, 2242, NT_STATUS_PASSWORD_EXPIRED}, + {ERRSRV, 2239, NT_STATUS_ACCOUNT_DISABLED}, + {ERRHRD, ERRgeneral, NT_STATUS_NONE_MAPPED}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_LUIDS_REQUESTED}, + {ERRHRD, ERRgeneral, NT_STATUS_LUIDS_EXHAUSTED}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_SUB_AUTHORITY}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_ACL}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_SID}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_SECURITY_DESCR}, + {ERRDOS, 127, NT_STATUS_PROCEDURE_NOT_FOUND}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_FORMAT}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_TOKEN}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_INHERITANCE_ACL}, + {ERRDOS, 158, NT_STATUS_RANGE_NOT_LOCKED}, + {ERRDOS, 112, NT_STATUS_DISK_FULL}, + {ERRHRD, ERRgeneral, NT_STATUS_SERVER_DISABLED}, + {ERRHRD, ERRgeneral, NT_STATUS_SERVER_NOT_DISABLED}, + {ERRDOS, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED}, + {ERRDOS, 259, NT_STATUS_GUIDS_EXHAUSTED}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_ID_AUTHORITY}, + {ERRDOS, 259, NT_STATUS_AGENTS_EXHAUSTED}, + {ERRDOS, 154, NT_STATUS_INVALID_VOLUME_LABEL}, + {ERRDOS, ERRres, NT_STATUS_SECTION_NOT_EXTENDED}, + {ERRDOS, 487, NT_STATUS_NOT_MAPPED_DATA}, + {ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_DATA_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_TYPE_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_NAME_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_ARRAY_BOUNDS_EXCEEDED}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_DENORMAL_OPERAND}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_DIVIDE_BY_ZERO}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_INEXACT_RESULT}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_INVALID_OPERATION}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_OVERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_STACK_CHECK}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOAT_UNDERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_INTEGER_DIVIDE_BY_ZERO}, + {ERRDOS, 534, NT_STATUS_INTEGER_OVERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_PRIVILEGED_INSTRUCTION}, + {ERRDOS, ERRnomem, NT_STATUS_TOO_MANY_PAGING_FILES}, + {ERRHRD, ERRgeneral, NT_STATUS_FILE_INVALID}, + {ERRHRD, ERRgeneral, NT_STATUS_ALLOTTED_SPACE_EXCEEDED}, +/* { This NT error code was 'sqashed' + from NT_STATUS_INSUFFICIENT_RESOURCES to NT_STATUS_INSUFF_SERVER_RESOURCES + during the session setup } +*/ + {ERRDOS, ERRnomem, NT_STATUS_INSUFFICIENT_RESOURCES}, + {ERRDOS, ERRbadpath, NT_STATUS_DFS_EXIT_PATH_FOUND}, + {ERRDOS, 23, NT_STATUS_DEVICE_DATA_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_DEVICE_NOT_CONNECTED}, + {ERRDOS, 21, NT_STATUS_DEVICE_POWER_FAILURE}, + {ERRDOS, 487, NT_STATUS_FREE_VM_NOT_AT_BASE}, + {ERRDOS, 487, NT_STATUS_MEMORY_NOT_ALLOCATED}, + {ERRHRD, ERRgeneral, NT_STATUS_WORKING_SET_QUOTA}, + {ERRDOS, 19, NT_STATUS_MEDIA_WRITE_PROTECTED}, + {ERRDOS, 21, NT_STATUS_DEVICE_NOT_READY}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_GROUP_ATTRIBUTES}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_IMPERSONATION_LEVEL}, + {ERRHRD, ERRgeneral, NT_STATUS_CANT_OPEN_ANONYMOUS}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_VALIDATION_CLASS}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_TOKEN_TYPE}, + {ERRDOS, 87, NT_STATUS_BAD_MASTER_BOOT_RECORD}, + {ERRHRD, ERRgeneral, NT_STATUS_INSTRUCTION_MISALIGNMENT}, + {ERRDOS, ERRpipebusy, NT_STATUS_INSTANCE_NOT_AVAILABLE}, + {ERRDOS, ERRpipebusy, NT_STATUS_PIPE_NOT_AVAILABLE}, + {ERRDOS, ERRbadpipe, NT_STATUS_INVALID_PIPE_STATE}, + {ERRDOS, ERRpipebusy, NT_STATUS_PIPE_BUSY}, + {ERRDOS, ERRbadfunc, NT_STATUS_ILLEGAL_FUNCTION}, + {ERRDOS, ERRnotconnected, NT_STATUS_PIPE_DISCONNECTED}, + {ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_CLOSING}, + {ERRHRD, ERRgeneral, NT_STATUS_PIPE_CONNECTED}, + {ERRHRD, ERRgeneral, NT_STATUS_PIPE_LISTENING}, + {ERRDOS, ERRbadpipe, NT_STATUS_INVALID_READ_MODE}, + {ERRDOS, 121, NT_STATUS_IO_TIMEOUT}, + {ERRDOS, 38, NT_STATUS_FILE_FORCED_CLOSED}, + {ERRHRD, ERRgeneral, NT_STATUS_PROFILING_NOT_STARTED}, + {ERRHRD, ERRgeneral, NT_STATUS_PROFILING_NOT_STOPPED}, + {ERRHRD, ERRgeneral, NT_STATUS_COULD_NOT_INTERPRET}, + {ERRDOS, ERRnoaccess, NT_STATUS_FILE_IS_A_DIRECTORY}, + {ERRDOS, ERRunsup, NT_STATUS_NOT_SUPPORTED}, + {ERRDOS, 51, NT_STATUS_REMOTE_NOT_LISTENING}, + {ERRDOS, 52, NT_STATUS_DUPLICATE_NAME}, + {ERRDOS, 53, NT_STATUS_BAD_NETWORK_PATH}, + {ERRDOS, 54, NT_STATUS_NETWORK_BUSY}, + {ERRDOS, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST}, + {ERRDOS, 56, NT_STATUS_TOO_MANY_COMMANDS}, + {ERRDOS, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR}, + {ERRDOS, 58, NT_STATUS_INVALID_NETWORK_RESPONSE}, + {ERRDOS, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR}, + {ERRDOS, 60, NT_STATUS_BAD_REMOTE_ADAPTER}, + {ERRDOS, 61, NT_STATUS_PRINT_QUEUE_FULL}, + {ERRDOS, 62, NT_STATUS_NO_SPOOL_SPACE}, + {ERRDOS, 63, NT_STATUS_PRINT_CANCELLED}, + {ERRDOS, 64, NT_STATUS_NETWORK_NAME_DELETED}, + {ERRDOS, 65, NT_STATUS_NETWORK_ACCESS_DENIED}, + {ERRDOS, 66, NT_STATUS_BAD_DEVICE_TYPE}, + {ERRDOS, ERRnosuchshare, NT_STATUS_BAD_NETWORK_NAME}, + {ERRDOS, 68, NT_STATUS_TOO_MANY_NAMES}, + {ERRDOS, 69, NT_STATUS_TOO_MANY_SESSIONS}, + {ERRDOS, 70, NT_STATUS_SHARING_PAUSED}, + {ERRDOS, 71, NT_STATUS_REQUEST_NOT_ACCEPTED}, + {ERRDOS, 72, NT_STATUS_REDIRECTOR_PAUSED}, + {ERRDOS, 88, NT_STATUS_NET_WRITE_FAULT}, + {ERRHRD, ERRgeneral, NT_STATUS_PROFILING_AT_LIMIT}, + {ERRDOS, ERRdiffdevice, NT_STATUS_NOT_SAME_DEVICE}, + {ERRDOS, ERRnoaccess, NT_STATUS_FILE_RENAMED}, + {ERRDOS, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SECURITY_ON_OBJECT}, + {ERRHRD, ERRgeneral, NT_STATUS_CANT_WAIT}, + {ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_EMPTY}, + {ERRHRD, ERRgeneral, NT_STATUS_CANT_ACCESS_DOMAIN_INFO}, + {ERRHRD, ERRgeneral, NT_STATUS_CANT_TERMINATE_SELF}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_SERVER_STATE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_DOMAIN_STATE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_DOMAIN_ROLE}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_DOMAIN}, + {ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_EXISTS}, + {ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_LIMIT_EXCEEDED}, + {ERRDOS, 300, NT_STATUS_OPLOCK_NOT_GRANTED}, + {ERRDOS, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL}, + {ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_DB_CORRUPTION}, + {ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_GENERIC_NOT_MAPPED}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_DESCRIPTOR_FORMAT}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_USER_BUFFER}, + {ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_IO_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_CREATE_ERR}, + {ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_MAP_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_UNEXPECTED_MM_EXTEND_ERR}, + {ERRHRD, ERRgeneral, NT_STATUS_NOT_LOGON_PROCESS}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGON_SESSION_EXISTS}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_1}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_2}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_3}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_4}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_5}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_6}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_7}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_8}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_9}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_10}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_11}, + {ERRDOS, 87, NT_STATUS_INVALID_PARAMETER_12}, + {ERRDOS, ERRbadpath, NT_STATUS_REDIRECTOR_NOT_STARTED}, + {ERRHRD, ERRgeneral, NT_STATUS_REDIRECTOR_STARTED}, + {ERRHRD, ERRgeneral, NT_STATUS_STACK_OVERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_PACKAGE}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_FUNCTION_TABLE}, + {ERRDOS, 203, NT_STATUS(0xc0000100)}, + {ERRDOS, 145, NT_STATUS_DIRECTORY_NOT_EMPTY}, + {ERRHRD, ERRgeneral, NT_STATUS_FILE_CORRUPT_ERROR}, + {ERRDOS, ERRbaddirectory, NT_STATUS_NOT_A_DIRECTORY}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_LOGON_SESSION_STATE}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGON_SESSION_COLLISION}, + {ERRDOS, 206, NT_STATUS_NAME_TOO_LONG}, + {ERRDOS, 2401, NT_STATUS_FILES_OPEN}, + {ERRDOS, 2404, NT_STATUS_CONNECTION_IN_USE}, + {ERRHRD, ERRgeneral, NT_STATUS_MESSAGE_NOT_FOUND}, + {ERRDOS, ERRnoaccess, NT_STATUS_PROCESS_IS_TERMINATING}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_LOGON_TYPE}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_GUID_TRANSLATION}, + {ERRHRD, ERRgeneral, NT_STATUS_CANNOT_IMPERSONATE}, + {ERRHRD, ERRgeneral, NT_STATUS_IMAGE_ALREADY_LOADED}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_NOT_PRESENT}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_LID_NOT_EXIST}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_LID_ALREADY_OWNED}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_NOT_LID_OWNER}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_COMMAND}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_LID}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_ABIOS_INVALID_SELECTOR}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_LDT}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_SIZE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_OFFSET}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_LDT_DESCRIPTOR}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_NE_FORMAT}, + {ERRHRD, ERRgeneral, NT_STATUS_RXACT_INVALID_STATE}, + {ERRHRD, ERRgeneral, NT_STATUS_RXACT_COMMIT_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_MAPPED_FILE_SIZE_ZERO}, + {ERRDOS, ERRnofids, NT_STATUS_TOO_MANY_OPENED_FILES}, + {ERRHRD, ERRgeneral, NT_STATUS_CANCELLED}, + {ERRDOS, ERRnoaccess, NT_STATUS_CANNOT_DELETE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_COMPUTER_NAME}, + {ERRDOS, ERRnoaccess, NT_STATUS_FILE_DELETED}, + {ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_ACCOUNT}, + {ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_GROUP}, + {ERRHRD, ERRgeneral, NT_STATUS_SPECIAL_USER}, + {ERRHRD, ERRgeneral, NT_STATUS_MEMBERS_PRIMARY_GROUP}, + {ERRDOS, ERRbadfid, NT_STATUS_FILE_CLOSED}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_THREADS}, + {ERRHRD, ERRgeneral, NT_STATUS_THREAD_NOT_IN_PROCESS}, + {ERRHRD, ERRgeneral, NT_STATUS_TOKEN_ALREADY_IN_USE}, + {ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_QUOTA_EXCEEDED}, + {ERRHRD, ERRgeneral, NT_STATUS_COMMITMENT_LIMIT}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_LE_FORMAT}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_NOT_MZ}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_PROTECT}, + {ERRDOS, 193, NT_STATUS_INVALID_IMAGE_WIN_16}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGON_SERVER_CONFLICT}, + {ERRHRD, ERRgeneral, NT_STATUS_TIME_DIFFERENCE_AT_DC}, + {ERRHRD, ERRgeneral, NT_STATUS_SYNCHRONIZATION_REQUIRED}, + {ERRDOS, 126, NT_STATUS_DLL_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_OPEN_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_IO_PRIVILEGE_FAILED}, + {ERRDOS, 182, NT_STATUS_ORDINAL_NOT_FOUND}, + {ERRDOS, 127, NT_STATUS_ENTRYPOINT_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_CONTROL_C_EXIT}, + {ERRDOS, 64, NT_STATUS_LOCAL_DISCONNECT}, + {ERRDOS, 64, NT_STATUS_REMOTE_DISCONNECT}, + {ERRDOS, 51, NT_STATUS_REMOTE_RESOURCES}, + {ERRDOS, 59, NT_STATUS_LINK_FAILED}, + {ERRDOS, 59, NT_STATUS_LINK_TIMEOUT}, + {ERRDOS, 59, NT_STATUS_INVALID_CONNECTION}, + {ERRDOS, 59, NT_STATUS_INVALID_ADDRESS}, + {ERRHRD, ERRgeneral, NT_STATUS_DLL_INIT_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_MISSING_SYSTEMFILE}, + {ERRHRD, ERRgeneral, NT_STATUS_UNHANDLED_EXCEPTION}, + {ERRHRD, ERRgeneral, NT_STATUS_APP_INIT_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_PAGEFILE_CREATE_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_PAGEFILE}, + {ERRDOS, ERRunknownlevel, NT_STATUS_INVALID_LEVEL}, + {ERRDOS, 86, NT_STATUS_WRONG_PASSWORD_CORE}, + {ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_FLOAT_CONTEXT}, + {ERRDOS, 109, NT_STATUS_PIPE_BROKEN}, + {ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_CORRUPT}, + {ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_IO_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_EVENT_PAIR}, + {ERRHRD, ERRgeneral, NT_STATUS_UNRECOGNIZED_VOLUME}, + {ERRHRD, ERRgeneral, NT_STATUS_SERIAL_NO_DEVICE_INITED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_ALIAS}, + {ERRHRD, ERRgeneral, NT_STATUS_MEMBER_NOT_IN_ALIAS}, + {ERRHRD, ERRgeneral, NT_STATUS_MEMBER_IN_ALIAS}, + {ERRHRD, ERRgeneral, NT_STATUS_ALIAS_EXISTS}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGON_NOT_GRANTED}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_SECRETS}, + {ERRHRD, ERRgeneral, NT_STATUS_SECRET_TOO_LONG}, + {ERRHRD, ERRgeneral, NT_STATUS_INTERNAL_DB_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_FULLSCREEN_MODE}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_CONTEXT_IDS}, + {ERRDOS, ERRnoaccess, NT_STATUS_LOGON_TYPE_NOT_GRANTED}, + {ERRHRD, ERRgeneral, NT_STATUS_NOT_REGISTRY_FILE}, + {ERRHRD, ERRgeneral, NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED}, + {ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_FT_MISSING_MEMBER}, + {ERRHRD, ERRgeneral, NT_STATUS_ILL_FORMED_SERVICE_ENTRY}, + {ERRHRD, ERRgeneral, NT_STATUS_ILLEGAL_CHARACTER}, + {ERRHRD, ERRgeneral, NT_STATUS_UNMAPPABLE_CHARACTER}, + {ERRHRD, ERRgeneral, NT_STATUS_UNDEFINED_CHARACTER}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_VOLUME}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_WRONG_CYLINDER}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_UNKNOWN_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_FLOPPY_BAD_REGISTERS}, + {ERRHRD, ERRgeneral, NT_STATUS_DISK_RECALIBRATE_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_DISK_OPERATION_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_DISK_RESET_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_SHARED_IRQ_BUSY}, + {ERRHRD, ERRgeneral, NT_STATUS_FT_ORPHANING}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000016e)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000016f)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc0000170)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc0000171)}, + {ERRHRD, ERRgeneral, NT_STATUS_PARTITION_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_BLOCK_LENGTH}, + {ERRHRD, ERRgeneral, NT_STATUS_DEVICE_NOT_PARTITIONED}, + {ERRHRD, ERRgeneral, NT_STATUS_UNABLE_TO_LOCK_MEDIA}, + {ERRHRD, ERRgeneral, NT_STATUS_UNABLE_TO_UNLOAD_MEDIA}, + {ERRHRD, ERRgeneral, NT_STATUS_EOM_OVERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_MEDIA}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc0000179)}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_SUCH_MEMBER}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_MEMBER}, + {ERRHRD, ERRgeneral, NT_STATUS_KEY_DELETED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_LOG_SPACE}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_SIDS}, + {ERRHRD, ERRgeneral, NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED}, + {ERRHRD, ERRgeneral, NT_STATUS_KEY_HAS_CHILDREN}, + {ERRHRD, ERRgeneral, NT_STATUS_CHILD_MUST_BE_VOLATILE}, + {ERRDOS, 87, NT_STATUS_DEVICE_CONFIGURATION_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_DRIVER_INTERNAL_ERROR}, + {ERRDOS, 22, NT_STATUS_INVALID_DEVICE_STATE}, + {ERRHRD, ERRgeneral, NT_STATUS_IO_DEVICE_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_DEVICE_PROTOCOL_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_BACKUP_CONTROLLER}, + {ERRHRD, ERRgeneral, NT_STATUS_LOG_FILE_FULL}, + {ERRDOS, 19, NT_STATUS_TOO_LATE}, + {ERRDOS, ERRnoaccess, NT_STATUS_NO_TRUST_LSA_SECRET}, +/* { This NT error code was 'sqashed' + from NT_STATUS_NO_TRUST_SAM_ACCOUNT to NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE + during the session setup } +*/ + {ERRDOS, ERRnoaccess, NT_STATUS_NO_TRUST_SAM_ACCOUNT}, + {ERRDOS, ERRnoaccess, NT_STATUS_TRUSTED_DOMAIN_FAILURE}, + {ERRDOS, ERRnoaccess, NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_FILE_CORRUPT}, + {ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_CANT_START}, + {ERRDOS, ERRnoaccess, NT_STATUS_TRUST_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_MUTANT_LIMIT_EXCEEDED}, + {ERRDOS, ERRinvgroup, NT_STATUS_NETLOGON_NOT_STARTED}, + {ERRSRV, 2239, NT_STATUS_ACCOUNT_EXPIRED}, + {ERRHRD, ERRgeneral, NT_STATUS_POSSIBLE_DEADLOCK}, + {ERRHRD, ERRgeneral, NT_STATUS_NETWORK_CREDENTIAL_CONFLICT}, + {ERRHRD, ERRgeneral, NT_STATUS_REMOTE_SESSION_LIMIT}, + {ERRHRD, ERRgeneral, NT_STATUS_EVENTLOG_FILE_CHANGED}, + {ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT}, + {ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT}, + {ERRDOS, ERRnoaccess, NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT}, +/* { This NT error code was 'sqashed' + from NT_STATUS_DOMAIN_TRUST_INCONSISTENT to NT_STATUS_LOGON_FAILURE + during the session setup } +*/ + {ERRDOS, ERRnoaccess, NT_STATUS_DOMAIN_TRUST_INCONSISTENT}, + {ERRHRD, ERRgeneral, NT_STATUS_FS_DRIVER_REQUIRED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_USER_SESSION_KEY}, + {ERRDOS, 59, NT_STATUS_USER_SESSION_DELETED}, + {ERRHRD, ERRgeneral, NT_STATUS_RESOURCE_LANG_NOT_FOUND}, + {ERRDOS, ERRnomem, NT_STATUS_INSUFF_SERVER_RESOURCES}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_BUFFER_SIZE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_ADDRESS_COMPONENT}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_ADDRESS_WILDCARD}, + {ERRDOS, 68, NT_STATUS_TOO_MANY_ADDRESSES}, + {ERRDOS, 52, NT_STATUS_ADDRESS_ALREADY_EXISTS}, + {ERRDOS, 64, NT_STATUS_ADDRESS_CLOSED}, + {ERRDOS, 64, NT_STATUS_CONNECTION_DISCONNECTED}, + {ERRDOS, 64, NT_STATUS_CONNECTION_RESET}, + {ERRDOS, 68, NT_STATUS_TOO_MANY_NODES}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_ABORTED}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_TIMED_OUT}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_NO_RELEASE}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_NO_MATCH}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_RESPONDED}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_INVALID_ID}, + {ERRDOS, 59, NT_STATUS_TRANSACTION_INVALID_TYPE}, + {ERRDOS, ERRunsup, NT_STATUS_NOT_SERVER_SESSION}, + {ERRDOS, ERRunsup, NT_STATUS_NOT_CLIENT_SESSION}, + {ERRHRD, ERRgeneral, NT_STATUS_CANNOT_LOAD_REGISTRY_FILE}, + {ERRHRD, ERRgeneral, NT_STATUS_DEBUG_ATTACH_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_SYSTEM_PROCESS_TERMINATED}, + {ERRHRD, ERRgeneral, NT_STATUS_DATA_NOT_ACCEPTED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_BROWSER_SERVERS_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_VDM_HARD_ERROR}, + {ERRHRD, ERRgeneral, NT_STATUS_DRIVER_CANCEL_TIMEOUT}, + {ERRHRD, ERRgeneral, NT_STATUS_REPLY_MESSAGE_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS_MAPPED_ALIGNMENT}, + {ERRDOS, 193, NT_STATUS_IMAGE_CHECKSUM_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS_LOST_WRITEBEHIND_DATA}, + {ERRHRD, ERRgeneral, NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID}, + {ERRSRV, 2242, NT_STATUS_PASSWORD_MUST_CHANGE}, + {ERRHRD, ERRgeneral, NT_STATUS_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_NOT_TINY_STREAM}, + {ERRHRD, ERRgeneral, NT_STATUS_RECOVERY_FAILURE}, + {ERRHRD, ERRgeneral, NT_STATUS_STACK_OVERFLOW_READ}, + {ERRHRD, ERRgeneral, NT_STATUS_FAIL_CHECK}, + {ERRHRD, ERRgeneral, NT_STATUS_DUPLICATE_OBJECTID}, + {ERRHRD, ERRgeneral, NT_STATUS_OBJECTID_EXISTS}, + {ERRHRD, ERRgeneral, NT_STATUS_CONVERT_TO_LARGE}, + {ERRHRD, ERRgeneral, NT_STATUS_RETRY}, + {ERRHRD, ERRgeneral, NT_STATUS_FOUND_OUT_OF_SCOPE}, + {ERRHRD, ERRgeneral, NT_STATUS_ALLOCATE_BUCKET}, + {ERRHRD, ERRgeneral, NT_STATUS_PROPSET_NOT_FOUND}, + {ERRHRD, ERRgeneral, NT_STATUS_MARSHALL_OVERFLOW}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_VARIANT}, + {ERRHRD, ERRgeneral, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND}, + {ERRDOS, ERRnoaccess, NT_STATUS_ACCOUNT_LOCKED_OUT}, + {ERRDOS, ERRbadfid, NT_STATUS_HANDLE_NOT_CLOSABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_REFUSED}, + {ERRHRD, ERRgeneral, NT_STATUS_GRACEFUL_DISCONNECT}, + {ERRHRD, ERRgeneral, NT_STATUS_ADDRESS_ALREADY_ASSOCIATED}, + {ERRHRD, ERRgeneral, NT_STATUS_ADDRESS_NOT_ASSOCIATED}, + {ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_INVALID}, + {ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_ACTIVE}, + {ERRHRD, ERRgeneral, NT_STATUS_NETWORK_UNREACHABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_HOST_UNREACHABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_PROTOCOL_UNREACHABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_PORT_UNREACHABLE}, + {ERRHRD, ERRgeneral, NT_STATUS_REQUEST_ABORTED}, + {ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_ABORTED}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_COMPRESSION_BUFFER}, + {ERRHRD, ERRgeneral, NT_STATUS_USER_MAPPED_FILE}, + {ERRHRD, ERRgeneral, NT_STATUS_AUDIT_FAILED}, + {ERRHRD, ERRgeneral, NT_STATUS_TIMER_RESOLUTION_NOT_SET}, + {ERRHRD, ERRgeneral, NT_STATUS_CONNECTION_COUNT_LIMIT}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGIN_TIME_RESTRICTION}, + {ERRHRD, ERRgeneral, NT_STATUS_LOGIN_WKSTA_RESTRICTION}, + {ERRDOS, 193, NT_STATUS_IMAGE_MP_UP_MISMATCH}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024a)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024b)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024c)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024d)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024e)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000024f)}, + {ERRHRD, ERRgeneral, NT_STATUS_INSUFFICIENT_LOGON_INFO}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_DLL_ENTRYPOINT}, + {ERRHRD, ERRgeneral, NT_STATUS_BAD_SERVICE_ENTRYPOINT}, + {ERRHRD, ERRgeneral, NT_STATUS_LPC_REPLY_LOST}, + {ERRHRD, ERRgeneral, NT_STATUS_IP_ADDRESS_CONFLICT1}, + {ERRHRD, ERRgeneral, NT_STATUS_IP_ADDRESS_CONFLICT2}, + {ERRHRD, ERRgeneral, NT_STATUS_REGISTRY_QUOTA_LIMIT}, + {ERRSRV, ERRbadtype, NT_STATUS_PATH_NOT_COVERED}, + {ERRHRD, ERRgeneral, NT_STATUS_NO_CALLBACK_ACTIVE}, + {ERRHRD, ERRgeneral, NT_STATUS_LICENSE_QUOTA_EXCEEDED}, + {ERRHRD, ERRgeneral, NT_STATUS_PWD_TOO_SHORT}, + {ERRHRD, ERRgeneral, NT_STATUS_PWD_TOO_RECENT}, + {ERRHRD, ERRgeneral, NT_STATUS_PWD_HISTORY_CONFLICT}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000025d)}, + {ERRHRD, ERRgeneral, NT_STATUS_PLUGPLAY_NO_DEVICE}, + {ERRHRD, ERRgeneral, NT_STATUS_UNSUPPORTED_COMPRESSION}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_HW_PROFILE}, + {ERRHRD, ERRgeneral, NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH}, + {ERRDOS, 182, NT_STATUS_DRIVER_ORDINAL_NOT_FOUND}, + {ERRDOS, 127, NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND}, + {ERRDOS, 288, NT_STATUS_RESOURCE_NOT_OWNED}, + {ERRHRD, ERRgeneral, NT_STATUS_TOO_MANY_LINKS}, + {ERRHRD, ERRgeneral, NT_STATUS_QUOTA_LIST_INCONSISTENT}, + {ERRHRD, ERRgeneral, NT_STATUS_FILE_IS_OFFLINE}, + {ERRDOS, 21, NT_STATUS(0xc000026e)}, + {ERRDOS, 161, NT_STATUS(0xc0000281)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc000028a)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc000028b)}, + {ERRHRD, ERRgeneral, NT_STATUS(0xc000028c)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc000028d)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc000028e)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc000028f)}, + {ERRDOS, ERRnoaccess, NT_STATUS(0xc0000290)}, + {ERRDOS, ERRbadfunc, NT_STATUS(0xc000029c)}, +}; + + +/* dos -> nt status error map */ +static const struct { + uint8 dos_class; + uint32 dos_code; + NTSTATUS ntstatus; +} dos_to_ntstatus_map[] = { + {ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED}, + {ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_FILE}, + {ERRDOS, ERRbadpath, NT_STATUS_OBJECT_PATH_NOT_FOUND}, + {ERRDOS, ERRnofids, NT_STATUS_TOO_MANY_OPENED_FILES}, + {ERRDOS, ERRnoaccess, NT_STATUS_ACCESS_DENIED}, + {ERRDOS, ERRbadfid, NT_STATUS_INVALID_HANDLE}, + {ERRDOS, ERRnomem, NT_STATUS_INSUFFICIENT_RESOURCES}, + {ERRDOS, ERRbadaccess, NT_STATUS_ACCESS_DENIED}, + {ERRDOS, ERRbaddata, NT_STATUS_DATA_ERROR}, + {ERRDOS, 14, NT_STATUS_SECTION_NOT_EXTENDED}, + {ERRDOS, ERRremcd, NT_STATUS_DIRECTORY_NOT_EMPTY}, + {ERRDOS, ERRdiffdevice, NT_STATUS_NOT_SAME_DEVICE}, + {ERRDOS, ERRnofiles, STATUS_NO_MORE_FILES}, + {ERRDOS, 19, NT_STATUS_MEDIA_WRITE_PROTECTED}, + {ERRDOS, 21, NT_STATUS_NO_MEDIA_IN_DEVICE}, + {ERRDOS, 22, NT_STATUS_INVALID_DEVICE_STATE}, + {ERRDOS, 23, NT_STATUS_DATA_ERROR}, + {ERRDOS, 24, NT_STATUS_DATA_ERROR}, + {ERRDOS, 26, NT_STATUS_DISK_CORRUPT_ERROR}, + {ERRDOS, 27, NT_STATUS_NONEXISTENT_SECTOR}, + {ERRDOS, 28, NT_STATUS(0x8000000e)}, + {ERRDOS, 31, NT_STATUS_UNSUCCESSFUL}, + {ERRDOS, ERRbadshare, NT_STATUS_SHARING_VIOLATION}, + {ERRDOS, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT}, + {ERRDOS, 34, NT_STATUS_WRONG_VOLUME}, + {ERRDOS, 38, NT_STATUS_END_OF_FILE}, + {ERRDOS, ERRunsup, NT_STATUS_CTL_FILE_NOT_SUPPORTED}, + {ERRDOS, 51, NT_STATUS_REMOTE_NOT_LISTENING}, + {ERRDOS, 52, NT_STATUS_DUPLICATE_NAME}, + {ERRDOS, 53, NT_STATUS_BAD_NETWORK_PATH}, + {ERRDOS, 54, NT_STATUS_NETWORK_BUSY}, + {ERRDOS, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST}, + {ERRDOS, 56, NT_STATUS_TOO_MANY_COMMANDS}, + {ERRDOS, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR}, + {ERRDOS, 58, NT_STATUS_INVALID_NETWORK_RESPONSE}, + {ERRDOS, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR}, + {ERRDOS, 60, NT_STATUS_BAD_REMOTE_ADAPTER}, + {ERRDOS, 61, NT_STATUS_PRINT_QUEUE_FULL}, + {ERRDOS, 62, NT_STATUS_NO_SPOOL_SPACE}, + {ERRDOS, 63, NT_STATUS_PRINT_CANCELLED}, + {ERRDOS, 64, NT_STATUS_NETWORK_NAME_DELETED}, + {ERRDOS, 65, NT_STATUS_NETWORK_ACCESS_DENIED}, + {ERRDOS, 66, NT_STATUS_BAD_DEVICE_TYPE}, + {ERRDOS, ERRnosuchshare, NT_STATUS_BAD_NETWORK_NAME}, + {ERRDOS, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED}, + {ERRDOS, 69, NT_STATUS_TOO_MANY_SESSIONS}, + {ERRDOS, 70, NT_STATUS_SHARING_PAUSED}, + {ERRDOS, 71, NT_STATUS_REQUEST_NOT_ACCEPTED}, + {ERRDOS, 72, NT_STATUS_REDIRECTOR_PAUSED}, + {ERRDOS, ERRfilexists, NT_STATUS_OBJECT_NAME_COLLISION}, + {ERRDOS, 86, NT_STATUS_WRONG_PASSWORD}, + {ERRDOS, 87, NT_STATUS_INVALID_INFO_CLASS}, + {ERRDOS, 88, NT_STATUS_NET_WRITE_FAULT}, + {ERRDOS, 109, NT_STATUS_PIPE_BROKEN}, + {ERRDOS, 111, STATUS_MORE_ENTRIES}, + {ERRDOS, 112, NT_STATUS_DISK_FULL}, + {ERRDOS, 121, NT_STATUS_IO_TIMEOUT}, + {ERRDOS, 122, NT_STATUS_BUFFER_TOO_SMALL}, + {ERRDOS, ERRinvalidname, NT_STATUS_OBJECT_NAME_INVALID}, + {ERRDOS, ERRunknownlevel, NT_STATUS_INVALID_LEVEL}, + {ERRDOS, 126, NT_STATUS_DLL_NOT_FOUND}, + {ERRDOS, 127, NT_STATUS_PROCEDURE_NOT_FOUND}, + {ERRDOS, 145, NT_STATUS_DIRECTORY_NOT_EMPTY}, + {ERRDOS, 154, NT_STATUS_INVALID_VOLUME_LABEL}, + {ERRDOS, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED}, + {ERRDOS, 158, NT_STATUS_NOT_LOCKED}, + {ERRDOS, 161, NT_STATUS_OBJECT_PATH_INVALID}, + {ERRDOS, 170, NT_STATUS(0x80000011)}, + {ERRDOS, 182, NT_STATUS_ORDINAL_NOT_FOUND}, + {ERRDOS, 183, NT_STATUS_OBJECT_NAME_COLLISION}, + {ERRDOS, 193, NT_STATUS_BAD_INITIAL_PC}, + {ERRDOS, 203, NT_STATUS(0xc0000100)}, + {ERRDOS, 206, NT_STATUS_NAME_TOO_LONG}, + {ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS}, + {ERRDOS, ERRpipebusy, NT_STATUS_INSTANCE_NOT_AVAILABLE}, + {ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_CLOSING}, + {ERRDOS, ERRnotconnected, NT_STATUS_PIPE_DISCONNECTED}, + {ERRDOS, ERRmoredata, NT_STATUS_MORE_PROCESSING_REQUIRED}, + {ERRDOS, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED}, + {ERRDOS, 254, NT_STATUS(0x80000013)}, + {ERRDOS, 255, NT_STATUS_EA_TOO_LARGE}, + {ERRDOS, 259, NT_STATUS_GUIDS_EXHAUSTED}, + {ERRDOS, 267, NT_STATUS_NOT_A_DIRECTORY}, + {ERRDOS, 275, NT_STATUS_EA_TOO_LARGE}, + {ERRDOS, 276, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRDOS, 277, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRDOS, 278, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRDOS, 282, NT_STATUS_EAS_NOT_SUPPORTED}, + {ERRDOS, 288, NT_STATUS_MUTANT_NOT_OWNED}, + {ERRDOS, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED}, + {ERRDOS, 299, NT_STATUS(0x8000000d)}, + {ERRDOS, 300, NT_STATUS_OPLOCK_NOT_GRANTED}, + {ERRDOS, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL}, + {ERRDOS, 487, NT_STATUS_CONFLICTING_ADDRESSES}, + {ERRDOS, 534, NT_STATUS_INTEGER_OVERFLOW}, + {ERRDOS, 535, NT_STATUS_PIPE_CONNECTED}, + {ERRDOS, 536, NT_STATUS_PIPE_LISTENING}, + {ERRDOS, 995, NT_STATUS_CANCELLED}, + {ERRDOS, 997, NT_STATUS(0x00000103)}, + {ERRDOS, 998, NT_STATUS_ACCESS_VIOLATION}, + {ERRDOS, 999, NT_STATUS_IN_PAGE_ERROR}, + {ERRDOS, 1001, NT_STATUS_BAD_INITIAL_STACK}, + {ERRDOS, 1005, NT_STATUS_UNRECOGNIZED_VOLUME}, + {ERRDOS, 1006, NT_STATUS_FILE_INVALID}, + {ERRDOS, 1007, NT_STATUS_FULLSCREEN_MODE}, + {ERRDOS, 1008, NT_STATUS_NO_TOKEN}, + {ERRDOS, 1009, NT_STATUS_REGISTRY_CORRUPT}, + {ERRDOS, 1016, NT_STATUS_REGISTRY_IO_FAILED}, + {ERRDOS, 1017, NT_STATUS_NOT_REGISTRY_FILE}, + {ERRDOS, 1018, NT_STATUS_KEY_DELETED}, + {ERRDOS, 1019, NT_STATUS_NO_LOG_SPACE}, + {ERRDOS, 1020, NT_STATUS_KEY_HAS_CHILDREN}, + {ERRDOS, 1021, NT_STATUS_CHILD_MUST_BE_VOLATILE}, + {ERRDOS, 1022, NT_STATUS(0x0000010c)}, + {ERRSRV, ERRbadpw, NT_STATUS_WRONG_PASSWORD}, + {ERRSRV, ERRbadtype, NT_STATUS_BAD_DEVICE_TYPE}, + {ERRSRV, ERRaccess, NT_STATUS_NETWORK_ACCESS_DENIED}, + {ERRSRV, ERRinvnid, NT_STATUS_NETWORK_NAME_DELETED}, + {ERRSRV, ERRinvnetname, NT_STATUS_BAD_NETWORK_NAME}, + {ERRSRV, ERRinvdevice, NT_STATUS_BAD_DEVICE_TYPE}, + {ERRSRV, ERRqfull, NT_STATUS_PRINT_QUEUE_FULL}, + {ERRSRV, ERRqtoobig, NT_STATUS_NO_SPOOL_SPACE}, + {ERRSRV, ERRinvpfid, NT_STATUS_PRINT_CANCELLED}, + {ERRSRV, ERRsmbcmd, NT_STATUS_NOT_IMPLEMENTED}, + {ERRSRV, ERRbadpermits, NT_STATUS_NETWORK_ACCESS_DENIED}, + {ERRSRV, ERRpaused, NT_STATUS_SHARING_PAUSED}, + {ERRSRV, ERRmsgoff, NT_STATUS_REQUEST_NOT_ACCEPTED}, + {ERRSRV, ERRnoroom, NT_STATUS_DISK_FULL}, + {ERRSRV, ERRnoresource, NT_STATUS_REQUEST_NOT_ACCEPTED}, + {ERRSRV, ERRtoomanyuids, NT_STATUS_TOO_MANY_SESSIONS}, + {ERRSRV, 123, NT_STATUS_OBJECT_NAME_INVALID}, + {ERRSRV, 206, NT_STATUS_OBJECT_NAME_INVALID}, + {ERRHRD, 1, NT_STATUS_NOT_IMPLEMENTED}, + {ERRHRD, 2, NT_STATUS_NO_SUCH_DEVICE}, + {ERRHRD, 3, NT_STATUS_OBJECT_PATH_NOT_FOUND}, + {ERRHRD, 4, NT_STATUS_TOO_MANY_OPENED_FILES}, + {ERRHRD, 5, NT_STATUS_INVALID_LOCK_SEQUENCE}, + {ERRHRD, 6, NT_STATUS_INVALID_HANDLE}, + {ERRHRD, 8, NT_STATUS_INSUFFICIENT_RESOURCES}, + {ERRHRD, 12, NT_STATUS_INVALID_LOCK_SEQUENCE}, + {ERRHRD, 13, NT_STATUS_DATA_ERROR}, + {ERRHRD, 14, NT_STATUS_SECTION_NOT_EXTENDED}, + {ERRHRD, 16, NT_STATUS_DIRECTORY_NOT_EMPTY}, + {ERRHRD, 17, NT_STATUS_NOT_SAME_DEVICE}, + {ERRHRD, 18, NT_STATUS(0x80000006)}, + {ERRHRD, ERRnowrite, NT_STATUS_MEDIA_WRITE_PROTECTED}, + {ERRHRD, ERRnotready, NT_STATUS_NO_MEDIA_IN_DEVICE}, + {ERRHRD, ERRbadcmd, NT_STATUS_INVALID_DEVICE_STATE}, + {ERRHRD, ERRdata, NT_STATUS_DATA_ERROR}, + {ERRHRD, ERRbadreq, NT_STATUS_DATA_ERROR}, + {ERRHRD, ERRbadmedia, NT_STATUS_DISK_CORRUPT_ERROR}, + {ERRHRD, ERRbadsector, NT_STATUS_NONEXISTENT_SECTOR}, + {ERRHRD, ERRnopaper, NT_STATUS(0x8000000e)}, + {ERRHRD, ERRgeneral, NT_STATUS_UNSUCCESSFUL}, + {ERRHRD, ERRbadshare, NT_STATUS_SHARING_VIOLATION}, + {ERRHRD, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT}, + {ERRHRD, ERRwrongdisk, NT_STATUS_WRONG_VOLUME}, + {ERRHRD, 38, NT_STATUS_END_OF_FILE}, + {ERRHRD, ERRdiskfull, NT_STATUS_DISK_FULL}, + {ERRHRD, 50, NT_STATUS_CTL_FILE_NOT_SUPPORTED}, + {ERRHRD, 51, NT_STATUS_REMOTE_NOT_LISTENING}, + {ERRHRD, 52, NT_STATUS_DUPLICATE_NAME}, + {ERRHRD, 53, NT_STATUS_BAD_NETWORK_PATH}, + {ERRHRD, 54, NT_STATUS_NETWORK_BUSY}, + {ERRHRD, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST}, + {ERRHRD, 56, NT_STATUS_TOO_MANY_COMMANDS}, + {ERRHRD, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR}, + {ERRHRD, 58, NT_STATUS_INVALID_NETWORK_RESPONSE}, + {ERRHRD, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR}, + {ERRHRD, 60, NT_STATUS_BAD_REMOTE_ADAPTER}, + {ERRHRD, 61, NT_STATUS_PRINT_QUEUE_FULL}, + {ERRHRD, 62, NT_STATUS_NO_SPOOL_SPACE}, + {ERRHRD, 63, NT_STATUS_PRINT_CANCELLED}, + {ERRHRD, 64, NT_STATUS_NETWORK_NAME_DELETED}, + {ERRHRD, 65, NT_STATUS_NETWORK_ACCESS_DENIED}, + {ERRHRD, 66, NT_STATUS_BAD_DEVICE_TYPE}, + {ERRHRD, 67, NT_STATUS_BAD_NETWORK_NAME}, + {ERRHRD, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED}, + {ERRHRD, 69, NT_STATUS_TOO_MANY_SESSIONS}, + {ERRHRD, 70, NT_STATUS_SHARING_PAUSED}, + {ERRHRD, 71, NT_STATUS_REQUEST_NOT_ACCEPTED}, + {ERRHRD, 72, NT_STATUS_REDIRECTOR_PAUSED}, + {ERRHRD, 80, NT_STATUS_OBJECT_NAME_COLLISION}, + {ERRHRD, 86, NT_STATUS_WRONG_PASSWORD}, + {ERRHRD, 87, NT_STATUS_INVALID_INFO_CLASS}, + {ERRHRD, 88, NT_STATUS_NET_WRITE_FAULT}, + {ERRHRD, 109, NT_STATUS_PIPE_BROKEN}, + {ERRHRD, 111, STATUS_MORE_ENTRIES}, + {ERRHRD, 112, NT_STATUS_DISK_FULL}, + {ERRHRD, 121, NT_STATUS_IO_TIMEOUT}, + {ERRHRD, 122, NT_STATUS_BUFFER_TOO_SMALL}, + {ERRHRD, 123, NT_STATUS_OBJECT_NAME_INVALID}, + {ERRHRD, 124, NT_STATUS_INVALID_LEVEL}, + {ERRHRD, 126, NT_STATUS_DLL_NOT_FOUND}, + {ERRHRD, 127, NT_STATUS_PROCEDURE_NOT_FOUND}, + {ERRHRD, 145, NT_STATUS_DIRECTORY_NOT_EMPTY}, + {ERRHRD, 154, NT_STATUS_INVALID_VOLUME_LABEL}, + {ERRHRD, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED}, + {ERRHRD, 158, NT_STATUS_NOT_LOCKED}, + {ERRHRD, 161, NT_STATUS_OBJECT_PATH_INVALID}, + {ERRHRD, 170, NT_STATUS(0x80000011)}, + {ERRHRD, 182, NT_STATUS_ORDINAL_NOT_FOUND}, + {ERRHRD, 183, NT_STATUS_OBJECT_NAME_COLLISION}, + {ERRHRD, 193, NT_STATUS_BAD_INITIAL_PC}, + {ERRHRD, 203, NT_STATUS(0xc0000100)}, + {ERRHRD, 206, NT_STATUS_NAME_TOO_LONG}, + {ERRHRD, 230, NT_STATUS_INVALID_INFO_CLASS}, + {ERRHRD, 231, NT_STATUS_INSTANCE_NOT_AVAILABLE}, + {ERRHRD, 232, NT_STATUS_PIPE_CLOSING}, + {ERRHRD, 233, NT_STATUS_PIPE_DISCONNECTED}, + {ERRHRD, 234, STATUS_MORE_ENTRIES}, + {ERRHRD, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED}, + {ERRHRD, 254, NT_STATUS(0x80000013)}, + {ERRHRD, 255, NT_STATUS_EA_TOO_LARGE}, + {ERRHRD, 259, NT_STATUS_GUIDS_EXHAUSTED}, + {ERRHRD, 267, NT_STATUS_NOT_A_DIRECTORY}, + {ERRHRD, 275, NT_STATUS_EA_TOO_LARGE}, + {ERRHRD, 276, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRHRD, 277, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRHRD, 278, NT_STATUS_NONEXISTENT_EA_ENTRY}, + {ERRHRD, 282, NT_STATUS_EAS_NOT_SUPPORTED}, + {ERRHRD, 288, NT_STATUS_MUTANT_NOT_OWNED}, + {ERRHRD, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED}, + {ERRHRD, 299, NT_STATUS(0x8000000d)}, + {ERRHRD, 300, NT_STATUS_OPLOCK_NOT_GRANTED}, + {ERRHRD, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL}, + {ERRHRD, 487, NT_STATUS_CONFLICTING_ADDRESSES}, + {ERRHRD, 534, NT_STATUS_INTEGER_OVERFLOW}, + {ERRHRD, 535, NT_STATUS_PIPE_CONNECTED}, + {ERRHRD, 536, NT_STATUS_PIPE_LISTENING}, + {ERRHRD, 995, NT_STATUS_CANCELLED}, + {ERRHRD, 997, NT_STATUS(0x00000103)}, + {ERRHRD, 998, NT_STATUS_ACCESS_VIOLATION}, + {ERRHRD, 999, NT_STATUS_IN_PAGE_ERROR}, + {ERRHRD, 1001, NT_STATUS_BAD_INITIAL_STACK}, + {ERRHRD, 1005, NT_STATUS_UNRECOGNIZED_VOLUME}, + {ERRHRD, 1006, NT_STATUS_FILE_INVALID}, + {ERRHRD, 1007, NT_STATUS_FULLSCREEN_MODE}, + {ERRHRD, 1008, NT_STATUS_NO_TOKEN}, + {ERRHRD, 1009, NT_STATUS_REGISTRY_CORRUPT}, + {ERRHRD, 1016, NT_STATUS_REGISTRY_IO_FAILED}, + {ERRHRD, 1017, NT_STATUS_NOT_REGISTRY_FILE}, + {ERRHRD, 1018, NT_STATUS_KEY_DELETED}, + {ERRHRD, 1019, NT_STATUS_NO_LOG_SPACE}, + {ERRHRD, 1020, NT_STATUS_KEY_HAS_CHILDREN}, + {ERRHRD, 1021, NT_STATUS_CHILD_MUST_BE_VOLATILE}, + {ERRHRD, 1022, NT_STATUS(0x0000010c)}, +}; + +/* errmap NTSTATUS->Win32 */ +static const struct { + NTSTATUS ntstatus; + WERROR werror; +} ntstatus_to_werror_map[] = { + {NT_STATUS(0x103), W_ERROR(0x3e5)}, + {NT_STATUS(0x105), W_ERROR(0xea)}, + {NT_STATUS(0x106), W_ERROR(0x514)}, + {NT_STATUS(0x107), W_ERROR(0x515)}, + {NT_STATUS(0x10c), W_ERROR(0x3fe)}, + {NT_STATUS(0x10d), W_ERROR(0x516)}, + {NT_STATUS(0x121), W_ERROR(0x2009)}, + {NT_STATUS(0xc0000001), W_ERROR(0x1f)}, + {NT_STATUS(0xc0000002), W_ERROR(0x1)}, + {NT_STATUS(0xc0000003), W_ERROR(0x57)}, + {NT_STATUS(0xc0000004), W_ERROR(0x18)}, + {NT_STATUS(0xc0000005), W_ERROR(0x3e6)}, + {NT_STATUS(0xc0000006), W_ERROR(0x3e7)}, + {NT_STATUS(0xc0000007), W_ERROR(0x5ae)}, + {NT_STATUS(0xc0000008), W_ERROR(0x6)}, + {NT_STATUS(0xc0000009), W_ERROR(0x3e9)}, + {NT_STATUS(0xc000000a), W_ERROR(0xc1)}, + {NT_STATUS(0xc000000b), W_ERROR(0x57)}, + {NT_STATUS(0xc000000d), W_ERROR(0x57)}, + {NT_STATUS(0xc000000e), W_ERROR(0x2)}, + {NT_STATUS(0xc000000f), W_ERROR(0x2)}, + {NT_STATUS(0xc0000010), W_ERROR(0x1)}, + {NT_STATUS(0xc0000011), W_ERROR(0x26)}, + {NT_STATUS(0xc0000012), W_ERROR(0x22)}, + {NT_STATUS(0xc0000013), W_ERROR(0x15)}, + {NT_STATUS(0xc0000014), W_ERROR(0x6f9)}, + {NT_STATUS(0xc0000015), W_ERROR(0x1b)}, + {NT_STATUS(0xc0000016), W_ERROR(0xea)}, + {NT_STATUS(0xc0000017), W_ERROR(0x8)}, + {NT_STATUS(0xc0000018), W_ERROR(0x1e7)}, + {NT_STATUS(0xc0000019), W_ERROR(0x1e7)}, + {NT_STATUS(0xc000001a), W_ERROR(0x57)}, + {NT_STATUS(0xc000001b), W_ERROR(0x57)}, + {NT_STATUS(0xc000001c), W_ERROR(0x1)}, + {NT_STATUS(0xc000001d), W_ERROR(0xc000001d)}, + {NT_STATUS(0xc000001e), W_ERROR(0x5)}, + {NT_STATUS(0xc000001f), W_ERROR(0x5)}, + {NT_STATUS(0xc0000020), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000021), W_ERROR(0x5)}, + {NT_STATUS(0xc0000022), W_ERROR(0x5)}, + {NT_STATUS(0xc0000023), W_ERROR(0x7a)}, + {NT_STATUS(0xc0000024), W_ERROR(0x6)}, + {NT_STATUS(0xc0000025), W_ERROR(0xc0000025)}, + {NT_STATUS(0xc0000026), W_ERROR(0xc0000026)}, + {NT_STATUS(0xc000002a), W_ERROR(0x9e)}, + {NT_STATUS(0xc000002b), W_ERROR(0xc000002b)}, + {NT_STATUS(0xc000002c), W_ERROR(0x1e7)}, + {NT_STATUS(0xc000002d), W_ERROR(0x1e7)}, + {NT_STATUS(0xc0000030), W_ERROR(0x57)}, + {NT_STATUS(0xc0000032), W_ERROR(0x571)}, + {NT_STATUS(0xc0000033), W_ERROR(0x7b)}, + {NT_STATUS(0xc0000034), W_ERROR(0x2)}, + {NT_STATUS(0xc0000035), W_ERROR(0xb7)}, + {NT_STATUS(0xc0000037), W_ERROR(0x6)}, + {NT_STATUS(0xc0000039), W_ERROR(0xa1)}, + {NT_STATUS(0xc000003a), W_ERROR(0x3)}, + {NT_STATUS(0xc000003b), W_ERROR(0xa1)}, + {NT_STATUS(0xc000003c), W_ERROR(0x45d)}, + {NT_STATUS(0xc000003d), W_ERROR(0x45d)}, + {NT_STATUS(0xc000003e), W_ERROR(0x17)}, + {NT_STATUS(0xc000003f), W_ERROR(0x17)}, + {NT_STATUS(0xc0000040), W_ERROR(0x8)}, + {NT_STATUS(0xc0000041), W_ERROR(0x5)}, + {NT_STATUS(0xc0000042), W_ERROR(0x6)}, + {NT_STATUS(0xc0000043), W_ERROR(0x20)}, + {NT_STATUS(0xc0000044), W_ERROR(0x718)}, + {NT_STATUS(0xc0000045), W_ERROR(0x57)}, + {NT_STATUS(0xc0000046), W_ERROR(0x120)}, + {NT_STATUS(0xc0000047), W_ERROR(0x12a)}, + {NT_STATUS(0xc0000048), W_ERROR(0x57)}, + {NT_STATUS(0xc0000049), W_ERROR(0x57)}, + {NT_STATUS(0xc000004a), W_ERROR(0x9c)}, + {NT_STATUS(0xc000004b), W_ERROR(0x5)}, + {NT_STATUS(0xc000004c), W_ERROR(0x57)}, + {NT_STATUS(0xc000004d), W_ERROR(0x57)}, + {NT_STATUS(0xc000004e), W_ERROR(0x57)}, + {NT_STATUS(0xc000004f), W_ERROR(0x11a)}, + {NT_STATUS(0xc0000050), W_ERROR(0xff)}, + {NT_STATUS(0xc0000051), W_ERROR(0x570)}, + {NT_STATUS(0xc0000052), W_ERROR(0x570)}, + {NT_STATUS(0xc0000053), W_ERROR(0x570)}, + {NT_STATUS(0xc0000054), W_ERROR(0x21)}, + {NT_STATUS(0xc0000055), W_ERROR(0x21)}, + {NT_STATUS(0xc0000056), W_ERROR(0x5)}, + {NT_STATUS(0xc0000057), W_ERROR(0x32)}, + {NT_STATUS(0xc0000058), W_ERROR(0x519)}, + {NT_STATUS(0xc0000059), W_ERROR(0x51a)}, + {NT_STATUS(0xc000005a), W_ERROR(0x51b)}, + {NT_STATUS(0xc000005b), W_ERROR(0x51c)}, + {NT_STATUS(0xc000005c), W_ERROR(0x51d)}, + {NT_STATUS(0xc000005d), W_ERROR(0x51e)}, + {NT_STATUS(0xc000005e), W_ERROR(0x51f)}, + {NT_STATUS(0xc000005f), W_ERROR(0x520)}, + {NT_STATUS(0xc0000060), W_ERROR(0x521)}, + {NT_STATUS(0xc0000061), W_ERROR(0x522)}, + {NT_STATUS(0xc0000062), W_ERROR(0x523)}, + {NT_STATUS(0xc0000063), W_ERROR(0x524)}, + {NT_STATUS(0xc0000064), W_ERROR(0x525)}, + {NT_STATUS(0xc0000065), W_ERROR(0x526)}, + {NT_STATUS(0xc0000066), W_ERROR(0x527)}, + {NT_STATUS(0xc0000067), W_ERROR(0x528)}, + {NT_STATUS(0xc0000068), W_ERROR(0x529)}, + {NT_STATUS(0xc0000069), W_ERROR(0x52a)}, + {NT_STATUS(0xc000006a), W_ERROR(0x56)}, + {NT_STATUS(0xc000006b), W_ERROR(0x52c)}, + {NT_STATUS(0xc000006c), W_ERROR(0x52d)}, + {NT_STATUS(0xc000006d), W_ERROR(0x52e)}, + {NT_STATUS(0xc000006e), W_ERROR(0x52f)}, + {NT_STATUS(0xc000006f), W_ERROR(0x530)}, + {NT_STATUS(0xc0000070), W_ERROR(0x531)}, + {NT_STATUS(0xc0000071), W_ERROR(0x532)}, + {NT_STATUS(0xc0000072), W_ERROR(0x533)}, + {NT_STATUS(0xc0000073), W_ERROR(0x534)}, + {NT_STATUS(0xc0000074), W_ERROR(0x535)}, + {NT_STATUS(0xc0000075), W_ERROR(0x536)}, + {NT_STATUS(0xc0000076), W_ERROR(0x537)}, + {NT_STATUS(0xc0000077), W_ERROR(0x538)}, + {NT_STATUS(0xc0000078), W_ERROR(0x539)}, + {NT_STATUS(0xc0000079), W_ERROR(0x53a)}, + {NT_STATUS(0xc000007a), W_ERROR(0x7f)}, + {NT_STATUS(0xc000007b), W_ERROR(0xc1)}, + {NT_STATUS(0xc000007c), W_ERROR(0x3f0)}, + {NT_STATUS(0xc000007d), W_ERROR(0x53c)}, + {NT_STATUS(0xc000007e), W_ERROR(0x9e)}, + {NT_STATUS(0xc000007f), W_ERROR(0x70)}, + {NT_STATUS(0xc0000080), W_ERROR(0x53d)}, + {NT_STATUS(0xc0000081), W_ERROR(0x53e)}, + {NT_STATUS(0xc0000082), W_ERROR(0x44)}, + {NT_STATUS(0xc0000083), W_ERROR(0x103)}, + {NT_STATUS(0xc0000084), W_ERROR(0x53f)}, + {NT_STATUS(0xc0000085), W_ERROR(0x103)}, + {NT_STATUS(0xc0000086), W_ERROR(0x9a)}, + {NT_STATUS(0xc0000087), W_ERROR(0xe)}, + {NT_STATUS(0xc0000088), W_ERROR(0x1e7)}, + {NT_STATUS(0xc0000089), W_ERROR(0x714)}, + {NT_STATUS(0xc000008a), W_ERROR(0x715)}, + {NT_STATUS(0xc000008b), W_ERROR(0x716)}, + {NT_STATUS(0xc000008c), W_ERROR(0xc000008c)}, + {NT_STATUS(0xc000008d), W_ERROR(0xc000008d)}, + {NT_STATUS(0xc000008e), W_ERROR(0xc000008e)}, + {NT_STATUS(0xc000008f), W_ERROR(0xc000008f)}, + {NT_STATUS(0xc0000090), W_ERROR(0xc0000090)}, + {NT_STATUS(0xc0000091), W_ERROR(0xc0000091)}, + {NT_STATUS(0xc0000092), W_ERROR(0xc0000092)}, + {NT_STATUS(0xc0000093), W_ERROR(0xc0000093)}, + {NT_STATUS(0xc0000094), W_ERROR(0xc0000094)}, + {NT_STATUS(0xc0000095), W_ERROR(0x216)}, + {NT_STATUS(0xc0000096), W_ERROR(0xc0000096)}, + {NT_STATUS(0xc0000097), W_ERROR(0x8)}, + {NT_STATUS(0xc0000098), W_ERROR(0x3ee)}, + {NT_STATUS(0xc0000099), W_ERROR(0x540)}, + {NT_STATUS(0xc000009a), W_ERROR(0x5aa)}, + {NT_STATUS(0xc000009b), W_ERROR(0x3)}, + {NT_STATUS(0xc000009c), W_ERROR(0x17)}, + {NT_STATUS(0xc000009d), W_ERROR(0x48f)}, + {NT_STATUS(0xc000009e), W_ERROR(0x15)}, + {NT_STATUS(0xc000009f), W_ERROR(0x1e7)}, + {NT_STATUS(0xc00000a0), W_ERROR(0x1e7)}, + {NT_STATUS(0xc00000a1), W_ERROR(0x5ad)}, + {NT_STATUS(0xc00000a2), W_ERROR(0x13)}, + {NT_STATUS(0xc00000a3), W_ERROR(0x15)}, + {NT_STATUS(0xc00000a4), W_ERROR(0x541)}, + {NT_STATUS(0xc00000a5), W_ERROR(0x542)}, + {NT_STATUS(0xc00000a6), W_ERROR(0x543)}, + {NT_STATUS(0xc00000a7), W_ERROR(0x544)}, + {NT_STATUS(0xc00000a8), W_ERROR(0x545)}, + {NT_STATUS(0xc00000a9), W_ERROR(0x57)}, + {NT_STATUS(0xc00000ab), W_ERROR(0xe7)}, + {NT_STATUS(0xc00000ac), W_ERROR(0xe7)}, + {NT_STATUS(0xc00000ad), W_ERROR(0xe6)}, + {NT_STATUS(0xc00000ae), W_ERROR(0xe7)}, + {NT_STATUS(0xc00000af), W_ERROR(0x1)}, + {NT_STATUS(0xc00000b0), W_ERROR(0xe9)}, + {NT_STATUS(0xc00000b1), W_ERROR(0xe8)}, + {NT_STATUS(0xc00000b2), W_ERROR(0x217)}, + {NT_STATUS(0xc00000b3), W_ERROR(0x218)}, + {NT_STATUS(0xc00000b4), W_ERROR(0xe6)}, + {NT_STATUS(0xc00000b5), W_ERROR(0x79)}, + {NT_STATUS(0xc00000b6), W_ERROR(0x26)}, + {NT_STATUS(0xc00000ba), W_ERROR(0x5)}, + {NT_STATUS(0xc00000bb), W_ERROR(0x32)}, + {NT_STATUS(0xc00000bc), W_ERROR(0x33)}, + {NT_STATUS(0xc00000bd), W_ERROR(0x34)}, + {NT_STATUS(0xc00000be), W_ERROR(0x35)}, + {NT_STATUS(0xc00000bf), W_ERROR(0x36)}, + {NT_STATUS(0xc00000c0), W_ERROR(0x37)}, + {NT_STATUS(0xc00000c1), W_ERROR(0x38)}, + {NT_STATUS(0xc00000c2), W_ERROR(0x39)}, + {NT_STATUS(0xc00000c3), W_ERROR(0x3a)}, + {NT_STATUS(0xc00000c4), W_ERROR(0x3b)}, + {NT_STATUS(0xc00000c5), W_ERROR(0x3c)}, + {NT_STATUS(0xc00000c6), W_ERROR(0x3d)}, + {NT_STATUS(0xc00000c7), W_ERROR(0x3e)}, + {NT_STATUS(0xc00000c8), W_ERROR(0x3f)}, + {NT_STATUS(0xc00000c9), W_ERROR(0x40)}, + {NT_STATUS(0xc00000ca), W_ERROR(0x41)}, + {NT_STATUS(0xc00000cb), W_ERROR(0x42)}, + {NT_STATUS(0xc00000cc), W_ERROR(0x43)}, + {NT_STATUS(0xc00000cd), W_ERROR(0x44)}, + {NT_STATUS(0xc00000ce), W_ERROR(0x45)}, + {NT_STATUS(0xc00000cf), W_ERROR(0x46)}, + {NT_STATUS(0xc00000d0), W_ERROR(0x47)}, + {NT_STATUS(0xc00000d1), W_ERROR(0x48)}, + {NT_STATUS(0xc00000d2), W_ERROR(0x58)}, + {NT_STATUS(0xc00000d4), W_ERROR(0x11)}, + {NT_STATUS(0xc00000d5), W_ERROR(0x5)}, + {NT_STATUS(0xc00000d6), W_ERROR(0xf0)}, + {NT_STATUS(0xc00000d7), W_ERROR(0x546)}, + {NT_STATUS(0xc00000d9), W_ERROR(0xe8)}, + {NT_STATUS(0xc00000da), W_ERROR(0x547)}, + {NT_STATUS(0xc00000dc), W_ERROR(0x548)}, + {NT_STATUS(0xc00000dd), W_ERROR(0x549)}, + {NT_STATUS(0xc00000de), W_ERROR(0x54a)}, + {NT_STATUS(0xc00000df), W_ERROR(0x54b)}, + {NT_STATUS(0xc00000e0), W_ERROR(0x54c)}, + {NT_STATUS(0xc00000e1), W_ERROR(0x54d)}, + {NT_STATUS(0xc00000e2), W_ERROR(0x12c)}, + {NT_STATUS(0xc00000e3), W_ERROR(0x12d)}, + {NT_STATUS(0xc00000e4), W_ERROR(0x54e)}, + {NT_STATUS(0xc00000e5), W_ERROR(0x54f)}, + {NT_STATUS(0xc00000e6), W_ERROR(0x550)}, + {NT_STATUS(0xc00000e7), W_ERROR(0x551)}, + {NT_STATUS(0xc00000e8), W_ERROR(0x6f8)}, + {NT_STATUS(0xc00000ed), W_ERROR(0x552)}, + {NT_STATUS(0xc00000ee), W_ERROR(0x553)}, + {NT_STATUS(0xc00000ef), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f0), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f1), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f2), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f3), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f4), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f5), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f6), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f7), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f8), W_ERROR(0x57)}, + {NT_STATUS(0xc00000f9), W_ERROR(0x57)}, + {NT_STATUS(0xc00000fa), W_ERROR(0x57)}, + {NT_STATUS(0xc00000fb), W_ERROR(0x3)}, + {NT_STATUS(0xc00000fd), W_ERROR(0x3e9)}, + {NT_STATUS(0xc00000fe), W_ERROR(0x554)}, + {NT_STATUS(0xc0000100), W_ERROR(0xcb)}, + {NT_STATUS(0xc0000101), W_ERROR(0x91)}, + {NT_STATUS(0xc0000102), W_ERROR(0x570)}, + {NT_STATUS(0xc0000103), W_ERROR(0x10b)}, + {NT_STATUS(0xc0000104), W_ERROR(0x555)}, + {NT_STATUS(0xc0000105), W_ERROR(0x556)}, + {NT_STATUS(0xc0000106), W_ERROR(0xce)}, + {NT_STATUS(0xc0000107), W_ERROR(0x961)}, + {NT_STATUS(0xc0000108), W_ERROR(0x964)}, + {NT_STATUS(0xc000010a), W_ERROR(0x5)}, + {NT_STATUS(0xc000010b), W_ERROR(0x557)}, + {NT_STATUS(0xc000010d), W_ERROR(0x558)}, + {NT_STATUS(0xc000010e), W_ERROR(0x420)}, + {NT_STATUS(0xc0000117), W_ERROR(0x5a4)}, + {NT_STATUS(0xc000011b), W_ERROR(0xc1)}, + {NT_STATUS(0xc000011c), W_ERROR(0x559)}, + {NT_STATUS(0xc000011d), W_ERROR(0x55a)}, + {NT_STATUS(0xc000011e), W_ERROR(0x3ee)}, + {NT_STATUS(0xc000011f), W_ERROR(0x4)}, + {NT_STATUS(0xc0000120), W_ERROR(0x3e3)}, + {NT_STATUS(0xc0000121), W_ERROR(0x5)}, + {NT_STATUS(0xc0000122), W_ERROR(0x4ba)}, + {NT_STATUS(0xc0000123), W_ERROR(0x5)}, + {NT_STATUS(0xc0000124), W_ERROR(0x55b)}, + {NT_STATUS(0xc0000125), W_ERROR(0x55c)}, + {NT_STATUS(0xc0000126), W_ERROR(0x55d)}, + {NT_STATUS(0xc0000127), W_ERROR(0x55e)}, + {NT_STATUS(0xc0000128), W_ERROR(0x6)}, + {NT_STATUS(0xc000012b), W_ERROR(0x55f)}, + {NT_STATUS(0xc000012d), W_ERROR(0x5af)}, + {NT_STATUS(0xc000012e), W_ERROR(0xc1)}, + {NT_STATUS(0xc000012f), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000130), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000131), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000133), W_ERROR(0x576)}, + {NT_STATUS(0xc0000135), W_ERROR(0x7e)}, + {NT_STATUS(0xc0000138), W_ERROR(0xb6)}, + {NT_STATUS(0xc0000139), W_ERROR(0x7f)}, + {NT_STATUS(0xc000013b), W_ERROR(0x40)}, + {NT_STATUS(0xc000013c), W_ERROR(0x40)}, + {NT_STATUS(0xc000013d), W_ERROR(0x33)}, + {NT_STATUS(0xc000013e), W_ERROR(0x3b)}, + {NT_STATUS(0xc000013f), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000140), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000141), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000142), W_ERROR(0x45a)}, + {NT_STATUS(0xc0000148), W_ERROR(0x7c)}, + {NT_STATUS(0xc0000149), W_ERROR(0x56)}, + {NT_STATUS(0xc000014b), W_ERROR(0x6d)}, + {NT_STATUS(0xc000014c), W_ERROR(0x3f1)}, + {NT_STATUS(0xc000014d), W_ERROR(0x3f8)}, + {NT_STATUS(0xc000014f), W_ERROR(0x3ed)}, + {NT_STATUS(0xc0000150), W_ERROR(0x45e)}, + {NT_STATUS(0xc0000151), W_ERROR(0x560)}, + {NT_STATUS(0xc0000152), W_ERROR(0x561)}, + {NT_STATUS(0xc0000153), W_ERROR(0x562)}, + {NT_STATUS(0xc0000154), W_ERROR(0x563)}, + {NT_STATUS(0xc0000155), W_ERROR(0x564)}, + {NT_STATUS(0xc0000156), W_ERROR(0x565)}, + {NT_STATUS(0xc0000157), W_ERROR(0x566)}, + {NT_STATUS(0xc0000158), W_ERROR(0x567)}, + {NT_STATUS(0xc0000159), W_ERROR(0x3ef)}, + {NT_STATUS(0xc000015a), W_ERROR(0x568)}, + {NT_STATUS(0xc000015b), W_ERROR(0x569)}, + {NT_STATUS(0xc000015c), W_ERROR(0x3f9)}, + {NT_STATUS(0xc000015d), W_ERROR(0x56a)}, + {NT_STATUS(0xc000015f), W_ERROR(0x45d)}, + {NT_STATUS(0xc0000162), W_ERROR(0x459)}, + {NT_STATUS(0xc0000165), W_ERROR(0x462)}, + {NT_STATUS(0xc0000166), W_ERROR(0x463)}, + {NT_STATUS(0xc0000167), W_ERROR(0x464)}, + {NT_STATUS(0xc0000168), W_ERROR(0x465)}, + {NT_STATUS(0xc0000169), W_ERROR(0x466)}, + {NT_STATUS(0xc000016a), W_ERROR(0x467)}, + {NT_STATUS(0xc000016b), W_ERROR(0x468)}, + {NT_STATUS(0xc000016c), W_ERROR(0x45f)}, + {NT_STATUS(0xc000016d), W_ERROR(0x45d)}, + {NT_STATUS(0xc0000172), W_ERROR(0x451)}, + {NT_STATUS(0xc0000173), W_ERROR(0x452)}, + {NT_STATUS(0xc0000174), W_ERROR(0x453)}, + {NT_STATUS(0xc0000175), W_ERROR(0x454)}, + {NT_STATUS(0xc0000176), W_ERROR(0x455)}, + {NT_STATUS(0xc0000177), W_ERROR(0x469)}, + {NT_STATUS(0xc0000178), W_ERROR(0x458)}, + {NT_STATUS(0xc000017a), W_ERROR(0x56b)}, + {NT_STATUS(0xc000017b), W_ERROR(0x56c)}, + {NT_STATUS(0xc000017c), W_ERROR(0x3fa)}, + {NT_STATUS(0xc000017d), W_ERROR(0x3fb)}, + {NT_STATUS(0xc000017e), W_ERROR(0x56d)}, + {NT_STATUS(0xc000017f), W_ERROR(0x56e)}, + {NT_STATUS(0xc0000180), W_ERROR(0x3fc)}, + {NT_STATUS(0xc0000181), W_ERROR(0x3fd)}, + {NT_STATUS(0xc0000182), W_ERROR(0x57)}, + {NT_STATUS(0xc0000183), W_ERROR(0x45d)}, + {NT_STATUS(0xc0000184), W_ERROR(0x16)}, + {NT_STATUS(0xc0000185), W_ERROR(0x45d)}, + {NT_STATUS(0xc0000186), W_ERROR(0x45d)}, + {NT_STATUS(0xc0000188), W_ERROR(0x5de)}, + {NT_STATUS(0xc0000189), W_ERROR(0x13)}, + {NT_STATUS(0xc000018a), W_ERROR(0x6fa)}, + {NT_STATUS(0xc000018b), W_ERROR(0x6fb)}, + {NT_STATUS(0xc000018c), W_ERROR(0x6fc)}, + {NT_STATUS(0xc000018d), W_ERROR(0x6fd)}, + {NT_STATUS(0xc000018e), W_ERROR(0x5dc)}, + {NT_STATUS(0xc000018f), W_ERROR(0x5dd)}, + {NT_STATUS(0xc0000190), W_ERROR(0x6fe)}, + {NT_STATUS(0xc0000192), W_ERROR(0x700)}, + {NT_STATUS(0xc0000193), W_ERROR(0x701)}, + {NT_STATUS(0xc0000194), W_ERROR(0x46b)}, + {NT_STATUS(0xc0000195), W_ERROR(0x4c3)}, + {NT_STATUS(0xc0000196), W_ERROR(0x4c4)}, + {NT_STATUS(0xc0000197), W_ERROR(0x5df)}, + {NT_STATUS(0xc0000198), W_ERROR(0x70f)}, + {NT_STATUS(0xc0000199), W_ERROR(0x710)}, + {NT_STATUS(0xc000019a), W_ERROR(0x711)}, + {NT_STATUS(0xc000019b), W_ERROR(0x712)}, + {NT_STATUS(0xc0000202), W_ERROR(0x572)}, + {NT_STATUS(0xc0000203), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000204), W_ERROR(0x717)}, + {NT_STATUS(0xc0000205), W_ERROR(0x46a)}, + {NT_STATUS(0xc0000206), W_ERROR(0x6f8)}, + {NT_STATUS(0xc0000207), W_ERROR(0x4be)}, + {NT_STATUS(0xc0000208), W_ERROR(0x4be)}, + {NT_STATUS(0xc0000209), W_ERROR(0x44)}, + {NT_STATUS(0xc000020a), W_ERROR(0x34)}, + {NT_STATUS(0xc000020b), W_ERROR(0x40)}, + {NT_STATUS(0xc000020c), W_ERROR(0x40)}, + {NT_STATUS(0xc000020d), W_ERROR(0x40)}, + {NT_STATUS(0xc000020e), W_ERROR(0x44)}, + {NT_STATUS(0xc000020f), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000210), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000211), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000212), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000213), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000214), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000215), W_ERROR(0x3b)}, + {NT_STATUS(0xc0000216), W_ERROR(0x32)}, + {NT_STATUS(0xc0000217), W_ERROR(0x32)}, + {NT_STATUS(0xc000021c), W_ERROR(0x17e6)}, + {NT_STATUS(0xc0000220), W_ERROR(0x46c)}, + {NT_STATUS(0xc0000221), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000224), W_ERROR(0x773)}, + {NT_STATUS(0xc0000225), W_ERROR(0x490)}, + {NT_STATUS(0xc000022a), W_ERROR(0xc000022a)}, + {NT_STATUS(0xc000022b), W_ERROR(0xc000022b)}, + {NT_STATUS(0xc000022d), W_ERROR(0x4d5)}, + {NT_STATUS(0xc0000230), W_ERROR(0x492)}, + {NT_STATUS(0xc0000233), W_ERROR(0x774)}, + {NT_STATUS(0xc0000234), W_ERROR(0x775)}, + {NT_STATUS(0xc0000235), W_ERROR(0x6)}, + {NT_STATUS(0xc0000236), W_ERROR(0x4c9)}, + {NT_STATUS(0xc0000237), W_ERROR(0x4ca)}, + {NT_STATUS(0xc0000238), W_ERROR(0x4cb)}, + {NT_STATUS(0xc0000239), W_ERROR(0x4cc)}, + {NT_STATUS(0xc000023a), W_ERROR(0x4cd)}, + {NT_STATUS(0xc000023b), W_ERROR(0x4ce)}, + {NT_STATUS(0xc000023c), W_ERROR(0x4cf)}, + {NT_STATUS(0xc000023d), W_ERROR(0x4d0)}, + {NT_STATUS(0xc000023e), W_ERROR(0x4d1)}, + {NT_STATUS(0xc000023f), W_ERROR(0x4d2)}, + {NT_STATUS(0xc0000240), W_ERROR(0x4d3)}, + {NT_STATUS(0xc0000241), W_ERROR(0x4d4)}, + {NT_STATUS(0xc0000243), W_ERROR(0x4c8)}, + {NT_STATUS(0xc0000246), W_ERROR(0x4d6)}, + {NT_STATUS(0xc0000247), W_ERROR(0x4d7)}, + {NT_STATUS(0xc0000248), W_ERROR(0x4d8)}, + {NT_STATUS(0xc0000249), W_ERROR(0xc1)}, + {NT_STATUS(0xc0000253), W_ERROR(0x54f)}, + {NT_STATUS(0xc0000257), W_ERROR(0x4d0)}, + {NT_STATUS(0xc0000259), W_ERROR(0x573)}, + {NT_STATUS(0xc000025e), W_ERROR(0x422)}, + {NT_STATUS(0xc0000262), W_ERROR(0xb6)}, + {NT_STATUS(0xc0000263), W_ERROR(0x7f)}, + {NT_STATUS(0xc0000264), W_ERROR(0x120)}, + {NT_STATUS(0xc0000265), W_ERROR(0x476)}, + {NT_STATUS(0xc0000267), W_ERROR(0x10fe)}, + {NT_STATUS(0xc000026c), W_ERROR(0x7d1)}, + {NT_STATUS(0xc000026d), W_ERROR(0x4b1)}, + {NT_STATUS(0xc000026e), W_ERROR(0x15)}, + {NT_STATUS(0xc0000272), W_ERROR(0x491)}, + {NT_STATUS(0xc0000275), W_ERROR(0x1126)}, + {NT_STATUS(0xc0000276), W_ERROR(0x1129)}, + {NT_STATUS(0xc0000277), W_ERROR(0x112a)}, + {NT_STATUS(0xc0000278), W_ERROR(0x1128)}, + {NT_STATUS(0xc0000279), W_ERROR(0x780)}, + {NT_STATUS(0xc0000280), W_ERROR(0x781)}, + {NT_STATUS(0xc0000281), W_ERROR(0xa1)}, + {NT_STATUS(0xc0000283), W_ERROR(0x488)}, + {NT_STATUS(0xc0000284), W_ERROR(0x489)}, + {NT_STATUS(0xc0000285), W_ERROR(0x48a)}, + {NT_STATUS(0xc0000286), W_ERROR(0x48b)}, + {NT_STATUS(0xc0000287), W_ERROR(0x48c)}, + {NT_STATUS(0xc000028a), W_ERROR(0x5)}, + {NT_STATUS(0xc000028b), W_ERROR(0x5)}, + {NT_STATUS(0xc000028d), W_ERROR(0x5)}, + {NT_STATUS(0xc000028e), W_ERROR(0x5)}, + {NT_STATUS(0xc000028f), W_ERROR(0x5)}, + {NT_STATUS(0xc0000290), W_ERROR(0x5)}, + {NT_STATUS(0xc0000291), W_ERROR(0x1777)}, + {NT_STATUS(0xc0000292), W_ERROR(0x1778)}, + {NT_STATUS(0xc0000293), W_ERROR(0x1772)}, + {NT_STATUS(0xc0000295), W_ERROR(0x1068)}, + {NT_STATUS(0xc0000296), W_ERROR(0x1069)}, + {NT_STATUS(0xc0000297), W_ERROR(0x106a)}, + {NT_STATUS(0xc0000298), W_ERROR(0x106b)}, + {NT_STATUS(0xc0000299), W_ERROR(0x201a)}, + {NT_STATUS(0xc000029a), W_ERROR(0x201b)}, + {NT_STATUS(0xc000029b), W_ERROR(0x201c)}, + {NT_STATUS(0xc000029c), W_ERROR(0x1)}, + {NT_STATUS(0xc000029d), W_ERROR(0x10ff)}, + {NT_STATUS(0xc000029e), W_ERROR(0x1100)}, + {NT_STATUS(0xc000029f), W_ERROR(0x494)}, + {NT_STATUS(0xc00002a1), W_ERROR(0x200a)}, + {NT_STATUS(0xc00002a2), W_ERROR(0x200b)}, + {NT_STATUS(0xc00002a3), W_ERROR(0x200c)}, + {NT_STATUS(0xc00002a4), W_ERROR(0x200d)}, + {NT_STATUS(0xc00002a5), W_ERROR(0x200e)}, + {NT_STATUS(0xc00002a6), W_ERROR(0x200f)}, + {NT_STATUS(0xc00002a7), W_ERROR(0x2010)}, + {NT_STATUS(0xc00002a8), W_ERROR(0x2011)}, + {NT_STATUS(0xc00002a9), W_ERROR(0x2012)}, + {NT_STATUS(0xc00002aa), W_ERROR(0x2013)}, + {NT_STATUS(0xc00002ab), W_ERROR(0x2014)}, + {NT_STATUS(0xc00002ac), W_ERROR(0x2015)}, + {NT_STATUS(0xc00002ad), W_ERROR(0x2016)}, + {NT_STATUS(0xc00002ae), W_ERROR(0x2017)}, + {NT_STATUS(0xc00002af), W_ERROR(0x2018)}, + {NT_STATUS(0xc00002b0), W_ERROR(0x2019)}, + {NT_STATUS(0xc00002b1), W_ERROR(0x211e)}, + {NT_STATUS(0xc00002b2), W_ERROR(0x1127)}, + {NT_STATUS(0xc00002b6), W_ERROR(0x651)}, + {NT_STATUS(0xc00002b7), W_ERROR(0x49a)}, + {NT_STATUS(0xc00002b8), W_ERROR(0x49b)}, + {NT_STATUS(0xc00002c1), W_ERROR(0x2024)}, + {NT_STATUS(0xc00002c3), W_ERROR(0x575)}, + {NT_STATUS(0xc00002c5), W_ERROR(0x3e6)}, + {NT_STATUS(0xc00002c6), W_ERROR(0x1075)}, + {NT_STATUS(0xc00002c7), W_ERROR(0x1076)}, + {NT_STATUS(0xc00002ca), W_ERROR(0x10e8)}, + {NT_STATUS(0xc00002cb), W_ERROR(0x2138)}, + {NT_STATUS(0xc00002cc), W_ERROR(0x4e3)}, + {NT_STATUS(0xc00002cd), W_ERROR(0x2139)}, + {NT_STATUS(0xc00002cf), W_ERROR(0x49d)}, + {NT_STATUS(0xc00002d0), W_ERROR(0x213a)}, + {NT_STATUS(0xc00002d4), W_ERROR(0x2141)}, + {NT_STATUS(0xc00002d5), W_ERROR(0x2142)}, + {NT_STATUS(0xc00002d6), W_ERROR(0x2143)}, + {NT_STATUS(0xc00002d7), W_ERROR(0x2144)}, + {NT_STATUS(0xc00002d8), W_ERROR(0x2145)}, + {NT_STATUS(0xc00002d9), W_ERROR(0x2146)}, + {NT_STATUS(0xc00002da), W_ERROR(0x2147)}, + {NT_STATUS(0xc00002db), W_ERROR(0x2148)}, + {NT_STATUS(0xc00002dc), W_ERROR(0x2149)}, + {NT_STATUS(0xc00002dd), W_ERROR(0x32)}, + {NT_STATUS(0xc00002df), W_ERROR(0x2151)}, + {NT_STATUS(0xc00002e0), W_ERROR(0x2152)}, + {NT_STATUS(0xc00002e1), W_ERROR(0x2153)}, + {NT_STATUS(0xc00002e2), W_ERROR(0x2154)}, + {NT_STATUS(0xc00002e3), W_ERROR(0x215d)}, + {NT_STATUS(0xc00002e4), W_ERROR(0x2163)}, + {NT_STATUS(0xc00002e5), W_ERROR(0x2164)}, + {NT_STATUS(0xc00002e6), W_ERROR(0x2165)}, + {NT_STATUS(0xc00002e7), W_ERROR(0x216d)}, + {NT_STATUS(0xc00002fe), W_ERROR(0x45b)}, + {NT_STATUS(0xc00002ff), W_ERROR(0x4e7)}, + {NT_STATUS(0xc0000300), W_ERROR(0x4e6)}, + {NT_STATUS(0x80000001), W_ERROR(0x80000001)}, + {NT_STATUS(0x80000002), W_ERROR(0x3e6)}, + {NT_STATUS(0x80000003), W_ERROR(0x80000003)}, + {NT_STATUS(0x80000004), W_ERROR(0x80000004)}, + {NT_STATUS(0x80000005), W_ERROR(0xea)}, + {NT_STATUS(0x80000006), W_ERROR(0x12)}, + {NT_STATUS(0x8000000b), W_ERROR(0x56f)}, + {NT_STATUS(0x8000000d), W_ERROR(0x12b)}, + {NT_STATUS(0x8000000e), W_ERROR(0x1c)}, + {NT_STATUS(0x8000000f), W_ERROR(0x15)}, + {NT_STATUS(0x80000010), W_ERROR(0x15)}, + {NT_STATUS(0x80000011), W_ERROR(0xaa)}, + {NT_STATUS(0x80000012), W_ERROR(0x103)}, + {NT_STATUS(0x80000013), W_ERROR(0xfe)}, + {NT_STATUS(0x80000014), W_ERROR(0xff)}, + {NT_STATUS(0x80000015), W_ERROR(0xff)}, + {NT_STATUS(0x80000016), W_ERROR(0x456)}, + {NT_STATUS(0x8000001a), W_ERROR(0x103)}, + {NT_STATUS(0x8000001b), W_ERROR(0x44d)}, + {NT_STATUS(0x8000001c), W_ERROR(0x456)}, + {NT_STATUS(0x8000001d), W_ERROR(0x457)}, + {NT_STATUS(0x8000001e), W_ERROR(0x44c)}, + {NT_STATUS(0x8000001f), W_ERROR(0x44e)}, + {NT_STATUS(0x80000021), W_ERROR(0x44f)}, + {NT_STATUS(0x80000022), W_ERROR(0x450)}, + {NT_STATUS(0x80000025), W_ERROR(0x962)}, + {NT_STATUS(0x80000288), W_ERROR(0x48d)}, + {NT_STATUS(0x80000289), W_ERROR(0x48e)}, + {NT_STATUS_OK, WERR_OK}}; + +static const struct { + WERROR werror; + NTSTATUS ntstatus; +} werror_to_ntstatus_map[] = { + { W_ERROR(0x5), NT_STATUS_ACCESS_DENIED }, + { WERR_OK, NT_STATUS_OK } +}; + +/***************************************************************************** +convert a dos eclas/ecode to a NT status32 code + *****************************************************************************/ +NTSTATUS dos_to_ntstatus(uint8 eclass, uint32 ecode) +{ + int i; + if (eclass == 0 && ecode == 0) return NT_STATUS_OK; + for (i=0; NT_STATUS_V(dos_to_ntstatus_map[i].ntstatus); i++) { + if (eclass == dos_to_ntstatus_map[i].dos_class && + ecode == dos_to_ntstatus_map[i].dos_code) { + return dos_to_ntstatus_map[i].ntstatus; + } + } + return NT_STATUS_UNSUCCESSFUL; +} + + +/***************************************************************************** +convert a NT status code to a dos class/code + *****************************************************************************/ +void ntstatus_to_dos(NTSTATUS ntstatus, uint8 *eclass, uint32 *ecode) +{ + int i; + if (NT_STATUS_IS_OK(ntstatus)) { + *eclass = 0; + *ecode = 0; + return; + } + for (i=0; NT_STATUS_V(ntstatus_to_dos_map[i].ntstatus); i++) { + if (NT_STATUS_V(ntstatus) == + NT_STATUS_V(ntstatus_to_dos_map[i].ntstatus)) { + *eclass = ntstatus_to_dos_map[i].dos_class; + *ecode = ntstatus_to_dos_map[i].dos_code; + return; + } + } + *eclass = ERRHRD; + *ecode = ERRgeneral; +} + + +/***************************************************************************** +convert a WERROR to a NT status32 code + *****************************************************************************/ +NTSTATUS werror_to_ntstatus(WERROR error) +{ + int i; + if (W_ERROR_IS_OK(error)) return NT_STATUS_OK; + + for (i=0; !W_ERROR_IS_OK(werror_to_ntstatus_map[i].werror); i++) { + if (W_ERROR_V(error) == + W_ERROR_V(werror_to_ntstatus_map[i].werror)) { + return werror_to_ntstatus_map[i].ntstatus; + } + } + + for (i=0; NT_STATUS_V(ntstatus_to_werror_map[i].ntstatus); i++) { + if (W_ERROR_V(error) == + W_ERROR_V(ntstatus_to_werror_map[i].werror)) { + return ntstatus_to_werror_map[i].ntstatus; + } + } + + /* just guess ... */ + return NT_STATUS(W_ERROR_V(error) | 0xc0000000); +} + +/***************************************************************************** +convert a NTSTATUS to a WERROR + *****************************************************************************/ +WERROR ntstatus_to_werror(NTSTATUS error) +{ + int i; + if (NT_STATUS_IS_OK(error)) return WERR_OK; + for (i=0; NT_STATUS_V(ntstatus_to_werror_map[i].ntstatus); i++) { + if (NT_STATUS_V(error) == + NT_STATUS_V(ntstatus_to_werror_map[i].ntstatus)) { + return ntstatus_to_werror_map[i].werror; + } + } + + /* a lame guess */ + return W_ERROR(NT_STATUS_V(error) & 0xffff); +} + +#if defined(HAVE_GSSAPI) +/******************************************************************************* + Map between gssapi errors and NT status. I made these up :-(. JRA. +*******************************************************************************/ + +static const struct { + unsigned long gss_err; + NTSTATUS ntstatus; +} gss_to_ntstatus_errormap[] = { +#if defined(GSS_S_CALL_INACCESSIBLE_READ) + {GSS_S_CALL_INACCESSIBLE_READ, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_CALL_INACCESSIBLE_WRITE) + {GSS_S_CALL_INACCESSIBLE_WRITE, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_CALL_BAD_STRUCTURE) + {GSS_S_CALL_BAD_STRUCTURE, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_BAD_MECH) + {GSS_S_BAD_MECH, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_BAD_NAME) + {GSS_S_BAD_NAME, NT_STATUS_INVALID_ACCOUNT_NAME}, +#endif +#if defined(GSS_S_BAD_NAMETYPE) + {GSS_S_BAD_NAMETYPE, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_BAD_BINDINGS) + {GSS_S_BAD_BINDINGS, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_BAD_STATUS) + {GSS_S_BAD_STATUS, NT_STATUS_UNSUCCESSFUL}, +#endif +#if defined(GSS_S_BAD_SIG) + {GSS_S_BAD_SIG, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_NO_CRED) + {GSS_S_NO_CRED, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_NO_CONTEXT) + {GSS_S_NO_CONTEXT, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_DEFECTIVE_TOKEN) + {GSS_S_DEFECTIVE_TOKEN, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_DEFECTIVE_CREDENTIAL) + {GSS_S_DEFECTIVE_CREDENTIAL, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_CREDENTIALS_EXPIRED) + {GSS_S_CREDENTIALS_EXPIRED, NT_STATUS_PASSWORD_EXPIRED}, +#endif +#if defined(GSS_S_CONTEXT_EXPIRED) + {GSS_S_CONTEXT_EXPIRED, NT_STATUS_PASSWORD_EXPIRED}, +#endif +#if defined(GSS_S_BAD_QOP) + {GSS_S_BAD_QOP, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_UNAUTHORIZED) + {GSS_S_UNAUTHORIZED, NT_STATUS_ACCESS_DENIED}, +#endif +#if defined(GSS_S_UNAVAILABLE) + {GSS_S_UNAVAILABLE, NT_STATUS_UNSUCCESSFUL}, +#endif +#if defined(GSS_S_DUPLICATE_ELEMENT) + {GSS_S_DUPLICATE_ELEMENT, NT_STATUS_INVALID_PARAMETER}, +#endif +#if defined(GSS_S_NAME_NOT_MN) + {GSS_S_NAME_NOT_MN, NT_STATUS_INVALID_PARAMETER}, +#endif + { 0, NT_STATUS_OK } +}; + +/********************************************************************* + Map an NT error code from a gssapi error code. +*********************************************************************/ + +NTSTATUS map_nt_error_from_gss(uint32 gss_maj, uint32 minor) +{ + int i = 0; + + if (gss_maj == GSS_S_COMPLETE) { + return NT_STATUS_OK; + } + + if (gss_maj == GSS_S_CONTINUE_NEEDED) { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + if (gss_maj == GSS_S_FAILURE) { + return map_nt_error_from_unix((int)minor); + } + + /* Look through list */ + while(gss_to_ntstatus_errormap[i].gss_err != 0) { + if (gss_to_ntstatus_errormap[i].gss_err == gss_maj) { + return gss_to_ntstatus_errormap[i].ntstatus; + } + i++; + } + + /* Default return */ + return NT_STATUS_ACCESS_DENIED; +} +#endif diff --git a/source3/libsmb/libsmb_cache.c b/source3/libsmb/libsmb_cache.c new file mode 100644 index 0000000000..bfacea368d --- /dev/null +++ b/source3/libsmb/libsmb_cache.c @@ -0,0 +1,244 @@ +/* + Unix SMB/CIFS implementation. + SMB client library implementation (server cache) + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + +/* + * Structure we use if internal caching mechanism is used + * nothing fancy here. + */ +struct smbc_server_cache { + char *server_name; + char *share_name; + char *workgroup; + char *username; + SMBCSRV *server; + + struct smbc_server_cache *next, *prev; +}; + + + +/* + * Add a new connection to the server cache. + * This function is only used if the external cache is not enabled + */ +int +SMBC_add_cached_server(SMBCCTX * context, + SMBCSRV * newsrv, + const char * server, + const char * share, + const char * workgroup, + const char * username) +{ + struct smbc_server_cache * srvcache = NULL; + + if (!(srvcache = SMB_MALLOC_P(struct smbc_server_cache))) { + errno = ENOMEM; + DEBUG(3, ("Not enough space for server cache allocation\n")); + return 1; + } + + ZERO_STRUCTP(srvcache); + + srvcache->server = newsrv; + + srvcache->server_name = SMB_STRDUP(server); + if (!srvcache->server_name) { + errno = ENOMEM; + goto failed; + } + + srvcache->share_name = SMB_STRDUP(share); + if (!srvcache->share_name) { + errno = ENOMEM; + goto failed; + } + + srvcache->workgroup = SMB_STRDUP(workgroup); + if (!srvcache->workgroup) { + errno = ENOMEM; + goto failed; + } + + srvcache->username = SMB_STRDUP(username); + if (!srvcache->username) { + errno = ENOMEM; + goto failed; + } + + DLIST_ADD(context->internal->server_cache, srvcache); + return 0; + +failed: + SAFE_FREE(srvcache->server_name); + SAFE_FREE(srvcache->share_name); + SAFE_FREE(srvcache->workgroup); + SAFE_FREE(srvcache->username); + SAFE_FREE(srvcache); + + return 1; +} + + + +/* + * Search the server cache for a server + * returns server handle on success, NULL on error (not found) + * This function is only used if the external cache is not enabled + */ +SMBCSRV * +SMBC_get_cached_server(SMBCCTX * context, + const char * server, + const char * share, + const char * workgroup, + const char * user) +{ + struct smbc_server_cache * srv = NULL; + + /* Search the cache lines */ + for (srv = context->internal->server_cache; srv; srv = srv->next) { + + if (strcmp(server,srv->server_name) == 0 && + strcmp(workgroup,srv->workgroup) == 0 && + strcmp(user, srv->username) == 0) { + + /* If the share name matches, we're cool */ + if (strcmp(share, srv->share_name) == 0) { + return srv->server; + } + + /* + * We only return an empty share name or the attribute + * server on an exact match (which would have been + * caught above). + */ + if (*share == '\0' || strcmp(share, "*IPC$") == 0) + continue; + + /* + * Never return an empty share name or the attribute + * server if it wasn't what was requested. + */ + if (*srv->share_name == '\0' || + strcmp(srv->share_name, "*IPC$") == 0) + continue; + + /* + * If we're only allowing one share per server, then + * a connection to the server (other than the + * attribute server connection) is cool. + */ + if (smbc_getOptionOneSharePerServer(context)) { + /* + * The currently connected share name + * doesn't match the requested share, so + * disconnect from the current share. + */ + if (! cli_tdis(srv->server->cli)) { + /* Sigh. Couldn't disconnect. */ + cli_shutdown(srv->server->cli); + srv->server->cli = NULL; + smbc_getFunctionRemoveCachedServer(context)(context, srv->server); + continue; + } + + /* + * Save the new share name. We've + * disconnected from the old share, and are + * about to connect to the new one. + */ + SAFE_FREE(srv->share_name); + srv->share_name = SMB_STRDUP(share); + if (!srv->share_name) { + /* Out of memory. */ + cli_shutdown(srv->server->cli); + srv->server->cli = NULL; + smbc_getFunctionRemoveCachedServer(context)(context, srv->server); + continue; + } + + + return srv->server; + } + } + } + + return NULL; +} + + +/* + * Search the server cache for a server and remove it + * returns 0 on success + * This function is only used if the external cache is not enabled + */ +int +SMBC_remove_cached_server(SMBCCTX * context, + SMBCSRV * server) +{ + struct smbc_server_cache * srv = NULL; + + for (srv = context->internal->server_cache; srv; srv = srv->next) { + if (server == srv->server) { + + /* remove this sucker */ + DLIST_REMOVE(context->internal->server_cache, srv); + SAFE_FREE(srv->server_name); + SAFE_FREE(srv->share_name); + SAFE_FREE(srv->workgroup); + SAFE_FREE(srv->username); + SAFE_FREE(srv); + return 0; + } + } + /* server not found */ + return 1; +} + + +/* + * Try to remove all the servers in cache + * returns 1 on failure and 0 if all servers could be removed. + */ +int +SMBC_purge_cached_servers(SMBCCTX * context) +{ + struct smbc_server_cache * srv; + struct smbc_server_cache * next; + int could_not_purge_all = 0; + + for (srv = context->internal->server_cache, + next = (srv ? srv->next :NULL); + srv; + srv = next, + next = (srv ? srv->next : NULL)) { + + if (SMBC_remove_unused_server(context, srv->server)) { + /* could not be removed */ + could_not_purge_all = 1; + } + } + return could_not_purge_all; +} diff --git a/source3/libsmb/libsmb_compat.c b/source3/libsmb/libsmb_compat.c new file mode 100644 index 0000000000..ad8fd922db --- /dev/null +++ b/source3/libsmb/libsmb_compat.c @@ -0,0 +1,542 @@ +/* + Unix SMB/CIFS implementation. + SMB client library implementation (Old interface compatibility) + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" +#include "libsmb_internal.h" + +struct smbc_compat_fdlist { + SMBCFILE * file; + int fd; + struct smbc_compat_fdlist *next, *prev; +}; + +static SMBCCTX * statcont = NULL; +static int smbc_compat_initialized = 0; +static int smbc_compat_nextfd = 0; +static struct smbc_compat_fdlist * smbc_compat_fd_in_use = NULL; +static struct smbc_compat_fdlist * smbc_compat_fd_avail = NULL; + +/* Find an fd and return the SMBCFILE * or NULL on failure */ +static SMBCFILE * +find_fd(int fd) +{ + struct smbc_compat_fdlist * f = smbc_compat_fd_in_use; + while (f) { + if (f->fd == fd) + return f->file; + f = f->next; + } + return NULL; +} + +/* Add an fd, returns 0 on success, -1 on error with errno set */ +static int +add_fd(SMBCFILE * file) +{ + struct smbc_compat_fdlist * f = smbc_compat_fd_avail; + + if (f) { + /* We found one that's available */ + DLIST_REMOVE(smbc_compat_fd_avail, f); + + } else { + /* + * None were available, so allocate one. Keep the number of + * file descriptors determinate. This allows the application + * to allocate bitmaps or mapping of file descriptors based on + * a known maximum number of file descriptors that will ever + * be returned. + */ + if (smbc_compat_nextfd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + f = SMB_MALLOC_P(struct smbc_compat_fdlist); + if (!f) { + errno = ENOMEM; + return -1; + } + + f->fd = SMBC_BASE_FD + smbc_compat_nextfd++; + } + + f->file = file; + DLIST_ADD(smbc_compat_fd_in_use, f); + + return f->fd; +} + + + +/* Delete an fd, returns 0 on success */ +static int +del_fd(int fd) +{ + struct smbc_compat_fdlist * f = smbc_compat_fd_in_use; + + while (f) { + if (f->fd == fd) + break; + f = f->next; + } + + if (f) { + /* found */ + DLIST_REMOVE(smbc_compat_fd_in_use, f); + f->file = NULL; + DLIST_ADD(smbc_compat_fd_avail, f); + return 0; + } + return 1; +} + + + +int +smbc_init(smbc_get_auth_data_fn fn, + int debug) +{ + if (!smbc_compat_initialized) { + statcont = smbc_new_context(); + if (!statcont) + return -1; + + smbc_setDebug(statcont, debug); + smbc_setFunctionAuthData(statcont, fn); + + if (!smbc_init_context(statcont)) { + smbc_free_context(statcont, False); + return -1; + } + + smbc_compat_initialized = 1; + + return 0; + } + return 0; +} + + +SMBCCTX * +smbc_set_context(SMBCCTX * context) +{ + SMBCCTX *old_context = statcont; + + if (context) { + /* Save provided context. It must have been initialized! */ + statcont = context; + + /* You'd better know what you're doing. We won't help you. */ + smbc_compat_initialized = 1; + } + + return old_context; +} + + +int +smbc_open(const char *furl, + int flags, + mode_t mode) +{ + SMBCFILE * file; + int fd; + + file = smbc_getFunctionOpen(statcont)(statcont, furl, flags, mode); + if (!file) + return -1; + + fd = add_fd(file); + if (fd == -1) + smbc_getFunctionClose(statcont)(statcont, file); + return fd; +} + + +int +smbc_creat(const char *furl, + mode_t mode) +{ + SMBCFILE * file; + int fd; + + file = smbc_getFunctionCreat(statcont)(statcont, furl, mode); + if (!file) + return -1; + + fd = add_fd(file); + if (fd == -1) { + /* Hmm... should we delete the file too ? I guess we could try */ + smbc_getFunctionClose(statcont)(statcont, file); + smbc_getFunctionUnlink(statcont)(statcont, furl); + } + return fd; +} + + +ssize_t +smbc_read(int fd, + void *buf, + size_t bufsize) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionRead(statcont)(statcont, file, buf, bufsize); +} + +ssize_t +smbc_write(int fd, + const void *buf, + size_t bufsize) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionWrite(statcont)(statcont, file, buf, bufsize); +} + +off_t +smbc_lseek(int fd, + off_t offset, + int whence) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionLseek(statcont)(statcont, file, offset, whence); +} + +int +smbc_close(int fd) +{ + SMBCFILE * file = find_fd(fd); + del_fd(fd); + return smbc_getFunctionClose(statcont)(statcont, file); +} + +int +smbc_unlink(const char *fname) +{ + return smbc_getFunctionUnlink(statcont)(statcont, fname); +} + +int +smbc_rename(const char *ourl, + const char *nurl) +{ + return smbc_getFunctionRename(statcont)(statcont, ourl, + statcont, nurl); +} + +int +smbc_opendir(const char *durl) +{ + SMBCFILE * file; + int fd; + + file = smbc_getFunctionOpendir(statcont)(statcont, durl); + if (!file) + return -1; + + fd = add_fd(file); + if (fd == -1) + smbc_getFunctionClosedir(statcont)(statcont, file); + + return fd; +} + +int +smbc_closedir(int dh) +{ + SMBCFILE * file = find_fd(dh); + del_fd(dh); + return smbc_getFunctionClosedir(statcont)(statcont, file); +} + +int +smbc_getdents(unsigned int dh, + struct smbc_dirent *dirp, + int count) +{ + SMBCFILE * file = find_fd(dh); + return smbc_getFunctionGetdents(statcont)(statcont, file, dirp, count); +} + +struct smbc_dirent * +smbc_readdir(unsigned int dh) +{ + SMBCFILE * file = find_fd(dh); + return smbc_getFunctionReaddir(statcont)(statcont, file); +} + +off_t +smbc_telldir(int dh) +{ + SMBCFILE * file = find_fd(dh); + return smbc_getFunctionTelldir(statcont)(statcont, file); +} + +int +smbc_lseekdir(int fd, + off_t offset) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionLseekdir(statcont)(statcont, file, offset); +} + +int +smbc_mkdir(const char *durl, + mode_t mode) +{ + return smbc_getFunctionMkdir(statcont)(statcont, durl, mode); +} + +int +smbc_rmdir(const char *durl) +{ + return smbc_getFunctionRmdir(statcont)(statcont, durl); +} + +int +smbc_stat(const char *url, + struct stat *st) +{ + return smbc_getFunctionStat(statcont)(statcont, url, st); +} + +int +smbc_fstat(int fd, + struct stat *st) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionFstat(statcont)(statcont, file, st); +} + +int +smbc_ftruncate(int fd, + off_t size) +{ + SMBCFILE * file = find_fd(fd); + return smbc_getFunctionFtruncate(statcont)(statcont, file, size); +} + +int +smbc_chmod(const char *url, + mode_t mode) +{ + return smbc_getFunctionChmod(statcont)(statcont, url, mode); +} + +int +smbc_utimes(const char *fname, + struct timeval *tbuf) +{ + return smbc_getFunctionUtimes(statcont)(statcont, fname, tbuf); +} + +#ifdef HAVE_UTIME_H +int +smbc_utime(const char *fname, + struct utimbuf *utbuf) +{ + struct timeval tv[2]; + + if (utbuf == NULL) + return smbc_getFunctionUtimes(statcont)(statcont, fname, NULL); + + tv[0].tv_sec = utbuf->actime; + tv[1].tv_sec = utbuf->modtime; + tv[0].tv_usec = tv[1].tv_usec = 0; + + return smbc_getFunctionUtimes(statcont)(statcont, fname, tv); +} +#endif + +int +smbc_setxattr(const char *fname, + const char *name, + const void *value, + size_t size, + int flags) +{ + return smbc_getFunctionSetxattr(statcont)(statcont, + fname, name, + value, size, flags); +} + +int +smbc_lsetxattr(const char *fname, + const char *name, + const void *value, + size_t size, + int flags) +{ + return smbc_getFunctionSetxattr(statcont)(statcont, + fname, name, + value, size, flags); +} + +int +smbc_fsetxattr(int fd, + const char *name, + const void *value, + size_t size, + int flags) +{ + SMBCFILE * file = find_fd(fd); + if (file == NULL) { + errno = EBADF; + return -1; + } + return smbc_getFunctionSetxattr(statcont)(statcont, + file->fname, name, + value, size, flags); +} + +int +smbc_getxattr(const char *fname, + const char *name, + const void *value, + size_t size) +{ + return smbc_getFunctionGetxattr(statcont)(statcont, + fname, name, + value, size); +} + +int +smbc_lgetxattr(const char *fname, + const char *name, + const void *value, + size_t size) +{ + return smbc_getFunctionGetxattr(statcont)(statcont, + fname, name, + value, size); +} + +int +smbc_fgetxattr(int fd, + const char *name, + const void *value, + size_t size) +{ + SMBCFILE * file = find_fd(fd); + if (file == NULL) { + errno = EBADF; + return -1; + } + return smbc_getFunctionGetxattr(statcont)(statcont, + file->fname, name, + value, size); +} + +int +smbc_removexattr(const char *fname, + const char *name) +{ + return smbc_getFunctionRemovexattr(statcont)(statcont, fname, name); +} + +int +smbc_lremovexattr(const char *fname, + const char *name) +{ + return smbc_getFunctionRemovexattr(statcont)(statcont, fname, name); +} + +int +smbc_fremovexattr(int fd, + const char *name) +{ + SMBCFILE * file = find_fd(fd); + if (file == NULL) { + errno = EBADF; + return -1; + } + return smbc_getFunctionRemovexattr(statcont)(statcont, + file->fname, name); +} + +int +smbc_listxattr(const char *fname, + char *list, + size_t size) +{ + return smbc_getFunctionListxattr(statcont)(statcont, + fname, list, size); +} + +int +smbc_llistxattr(const char *fname, + char *list, + size_t size) +{ + return smbc_getFunctionListxattr(statcont)(statcont, + fname, list, size); +} + +int +smbc_flistxattr(int fd, + char *list, + size_t size) +{ + SMBCFILE * file = find_fd(fd); + if (file == NULL) { + errno = EBADF; + return -1; + } + return smbc_getFunctionListxattr(statcont)(statcont, + file->fname, list, size); +} + +int +smbc_print_file(const char *fname, + const char *printq) +{ + return smbc_getFunctionPrintFile(statcont)(statcont, fname, + statcont, printq); +} + +int +smbc_open_print_job(const char *fname) +{ + SMBCFILE * file; + + file = smbc_getFunctionOpenPrintJob(statcont)(statcont, fname); + if (!file) return -1; + return file->cli_fd; +} + +int +smbc_list_print_jobs(const char *purl, + smbc_list_print_job_fn fn) +{ + return smbc_getFunctionListPrintJobs(statcont)(statcont, purl, fn); +} + +int +smbc_unlink_print_job(const char *purl, + int id) +{ + return smbc_getFunctionUnlinkPrintJob(statcont)(statcont, purl, id); +} + + diff --git a/source3/libsmb/libsmb_context.c b/source3/libsmb/libsmb_context.c new file mode 100644 index 0000000000..19843383de --- /dev/null +++ b/source3/libsmb/libsmb_context.c @@ -0,0 +1,645 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Is the logging working / configfile read ? + */ +static bool SMBC_initialized; +static unsigned int initialized_ctx_count; + +/* + * Get a new empty handle to fill in with your own info + */ +SMBCCTX * +smbc_new_context(void) +{ + SMBCCTX *context; + + /* + * All newly added context fields should be placed in + * SMBC_internal_data, not directly in SMBCCTX. + */ + context = SMB_MALLOC_P(SMBCCTX); + if (!context) { + errno = ENOMEM; + return NULL; + } + + ZERO_STRUCTP(context); + + context->internal = SMB_MALLOC_P(struct SMBC_internal_data); + if (!context->internal) { + SAFE_FREE(context); + errno = ENOMEM; + return NULL; + } + + /* Initialize the context and establish reasonable defaults */ + ZERO_STRUCTP(context->internal); + + smbc_setDebug(context, 0); + smbc_setTimeout(context, 20000); + + smbc_setOptionFullTimeNames(context, False); + smbc_setOptionOpenShareMode(context, SMBC_SHAREMODE_DENY_NONE); + smbc_setOptionSmbEncryptionLevel(context, SMBC_ENCRYPTLEVEL_NONE); + smbc_setOptionBrowseMaxLmbCount(context, 3); /* # LMBs to query */ + smbc_setOptionUrlEncodeReaddirEntries(context, False); + smbc_setOptionOneSharePerServer(context, False); + + smbc_setFunctionAuthData(context, SMBC_get_auth_data); + smbc_setFunctionCheckServer(context, SMBC_check_server); + smbc_setFunctionRemoveUnusedServer(context, SMBC_remove_unused_server); + + smbc_setOptionUserData(context, NULL); + smbc_setFunctionAddCachedServer(context, SMBC_add_cached_server); + smbc_setFunctionGetCachedServer(context, SMBC_get_cached_server); + smbc_setFunctionRemoveCachedServer(context, SMBC_remove_cached_server); + smbc_setFunctionPurgeCachedServers(context, SMBC_purge_cached_servers); + + smbc_setFunctionOpen(context, SMBC_open_ctx); + smbc_setFunctionCreat(context, SMBC_creat_ctx); + smbc_setFunctionRead(context, SMBC_read_ctx); + smbc_setFunctionWrite(context, SMBC_write_ctx); + smbc_setFunctionClose(context, SMBC_close_ctx); + smbc_setFunctionUnlink(context, SMBC_unlink_ctx); + smbc_setFunctionRename(context, SMBC_rename_ctx); + smbc_setFunctionLseek(context, SMBC_lseek_ctx); + smbc_setFunctionFtruncate(context, SMBC_ftruncate_ctx); + smbc_setFunctionStat(context, SMBC_stat_ctx); + smbc_setFunctionFstat(context, SMBC_fstat_ctx); + smbc_setFunctionOpendir(context, SMBC_opendir_ctx); + smbc_setFunctionClosedir(context, SMBC_closedir_ctx); + smbc_setFunctionReaddir(context, SMBC_readdir_ctx); + smbc_setFunctionGetdents(context, SMBC_getdents_ctx); + smbc_setFunctionMkdir(context, SMBC_mkdir_ctx); + smbc_setFunctionRmdir(context, SMBC_rmdir_ctx); + smbc_setFunctionTelldir(context, SMBC_telldir_ctx); + smbc_setFunctionLseekdir(context, SMBC_lseekdir_ctx); + smbc_setFunctionFstatdir(context, SMBC_fstatdir_ctx); + smbc_setFunctionChmod(context, SMBC_chmod_ctx); + smbc_setFunctionUtimes(context, SMBC_utimes_ctx); + smbc_setFunctionSetxattr(context, SMBC_setxattr_ctx); + smbc_setFunctionGetxattr(context, SMBC_getxattr_ctx); + smbc_setFunctionRemovexattr(context, SMBC_removexattr_ctx); + smbc_setFunctionListxattr(context, SMBC_listxattr_ctx); + + smbc_setFunctionOpenPrintJob(context, SMBC_open_print_job_ctx); + smbc_setFunctionPrintFile(context, SMBC_print_file_ctx); + smbc_setFunctionListPrintJobs(context, SMBC_list_print_jobs_ctx); + smbc_setFunctionUnlinkPrintJob(context, SMBC_unlink_print_job_ctx); + + return context; +} + +/* + * Free a context + * + * Returns 0 on success. Otherwise returns 1, the SMBCCTX is _not_ freed + * and thus you'll be leaking memory if not handled properly. + * + */ +int +smbc_free_context(SMBCCTX *context, + int shutdown_ctx) +{ + if (!context) { + errno = EBADF; + return 1; + } + + if (shutdown_ctx) { + SMBCFILE * f; + DEBUG(1,("Performing aggressive shutdown.\n")); + + f = context->internal->files; + while (f) { + smbc_getFunctionClose(context)(context, f); + f = f->next; + } + context->internal->files = NULL; + + /* First try to remove the servers the nice way. */ + if (smbc_getFunctionPurgeCachedServers(context)(context)) { + SMBCSRV * s; + SMBCSRV * next; + DEBUG(1, ("Could not purge all servers, " + "Nice way shutdown failed.\n")); + s = context->internal->servers; + while (s) { + DEBUG(1, ("Forced shutdown: %p (fd=%d)\n", + s, s->cli->fd)); + cli_shutdown(s->cli); + smbc_getFunctionRemoveCachedServer(context)(context, + s); + next = s->next; + DLIST_REMOVE(context->internal->servers, s); + SAFE_FREE(s); + s = next; + } + context->internal->servers = NULL; + } + } + else { + /* This is the polite way */ + if (smbc_getFunctionPurgeCachedServers(context)(context)) { + DEBUG(1, ("Could not purge all servers, " + "free_context failed.\n")); + errno = EBUSY; + return 1; + } + if (context->internal->servers) { + DEBUG(1, ("Active servers in context, " + "free_context failed.\n")); + errno = EBUSY; + return 1; + } + if (context->internal->files) { + DEBUG(1, ("Active files in context, " + "free_context failed.\n")); + errno = EBUSY; + return 1; + } + } + + /* Things we have to clean up */ + free(smbc_getWorkgroup(context)); + smbc_setWorkgroup(context, NULL); + + free(smbc_getNetbiosName(context)); + smbc_setNetbiosName(context, NULL); + + free(smbc_getUser(context)); + smbc_setUser(context, NULL); + + DEBUG(3, ("Context %p successfully freed\n", context)); + + SAFE_FREE(context->internal); + SAFE_FREE(context); + + if (initialized_ctx_count) { + initialized_ctx_count--; + } + + if (initialized_ctx_count == 0 && SMBC_initialized) { + gencache_shutdown(); + secrets_shutdown(); + gfree_all(); + SMBC_initialized = false; + } + return 0; +} + + +/** + * Deprecated interface. Do not use. Instead, use the various + * smbc_setOption*() functions or smbc_setFunctionAuthDataWithContext(). + */ +void +smbc_option_set(SMBCCTX *context, + char *option_name, + ... /* option_value */) +{ + va_list ap; + union { + int i; + bool b; + smbc_get_auth_data_with_context_fn auth_fn; + void *v; + const char *s; + } option_value; + + va_start(ap, option_name); + + if (strcmp(option_name, "debug_to_stderr") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionDebugToStderr(context, option_value.b); + + } else if (strcmp(option_name, "full_time_names") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionFullTimeNames(context, option_value.b); + + } else if (strcmp(option_name, "open_share_mode") == 0) { + option_value.i = va_arg(ap, int); + smbc_setOptionOpenShareMode(context, option_value.i); + + } else if (strcmp(option_name, "auth_function") == 0) { + option_value.auth_fn = + va_arg(ap, smbc_get_auth_data_with_context_fn); + smbc_setFunctionAuthDataWithContext(context, option_value.auth_fn); + + } else if (strcmp(option_name, "user_data") == 0) { + option_value.v = va_arg(ap, void *); + smbc_setOptionUserData(context, option_value.v); + + } else if (strcmp(option_name, "smb_encrypt_level") == 0) { + option_value.s = va_arg(ap, const char *); + if (strcmp(option_value.s, "none") == 0) { + smbc_setOptionSmbEncryptionLevel(context, + SMBC_ENCRYPTLEVEL_NONE); + } else if (strcmp(option_value.s, "request") == 0) { + smbc_setOptionSmbEncryptionLevel(context, + SMBC_ENCRYPTLEVEL_REQUEST); + } else if (strcmp(option_value.s, "require") == 0) { + smbc_setOptionSmbEncryptionLevel(context, + SMBC_ENCRYPTLEVEL_REQUIRE); + } + + } else if (strcmp(option_name, "browse_max_lmb_count") == 0) { + option_value.i = va_arg(ap, int); + smbc_setOptionBrowseMaxLmbCount(context, option_value.i); + + } else if (strcmp(option_name, "urlencode_readdir_entries") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionUrlEncodeReaddirEntries(context, option_value.b); + + } else if (strcmp(option_name, "one_share_per_server") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionOneSharePerServer(context, option_value.b); + + } else if (strcmp(option_name, "use_kerberos") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionUseKerberos(context, option_value.b); + + } else if (strcmp(option_name, "fallback_after_kerberos") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionFallbackAfterKerberos(context, option_value.b); + + } else if (strcmp(option_name, "no_auto_anonymous_login") == 0) { + option_value.b = (bool) va_arg(ap, int); + smbc_setOptionNoAutoAnonymousLogin(context, option_value.b); + } + + va_end(ap); +} + + +/* + * Deprecated interface. Do not use. Instead, use the various + * smbc_getOption*() functions. + */ +void * +smbc_option_get(SMBCCTX *context, + char *option_name) +{ + if (strcmp(option_name, "debug_stderr") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionDebugToStderr(context); +#else + return (void *) smbc_getOptionDebugToStderr(context); +#endif + + } else if (strcmp(option_name, "full_time_names") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionFullTimeNames(context); +#else + return (void *) smbc_getOptionFullTimeNames(context); +#endif + + } else if (strcmp(option_name, "open_share_mode") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionOpenShareMode(context); +#else + return (void *) smbc_getOptionOpenShareMode(context); +#endif + + } else if (strcmp(option_name, "auth_function") == 0) { + return (void *) smbc_getFunctionAuthDataWithContext(context); + + } else if (strcmp(option_name, "user_data") == 0) { + return smbc_getOptionUserData(context); + + } else if (strcmp(option_name, "smb_encrypt_level") == 0) { + switch(smbc_getOptionSmbEncryptionLevel(context)) + { + case 0: + return (void *) "none"; + case 1: + return (void *) "request"; + case 2: + return (void *) "require"; + } + + } else if (strcmp(option_name, "smb_encrypt_on") == 0) { + SMBCSRV *s; + unsigned int num_servers = 0; + + for (s = context->internal->servers; s; s = s->next) { + num_servers++; + if (s->cli->trans_enc_state == NULL) { + return (void *)false; + } + } +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) (bool) (num_servers > 0); +#else + return (void *) (bool) (num_servers > 0); +#endif + + } else if (strcmp(option_name, "browse_max_lmb_count") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionBrowseMaxLmbCount(context); +#else + return (void *) smbc_getOptionBrowseMaxLmbCount(context); +#endif + + } else if (strcmp(option_name, "urlencode_readdir_entries") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *)(intptr_t) smbc_getOptionUrlEncodeReaddirEntries(context); +#else + return (void *) (bool) smbc_getOptionUrlEncodeReaddirEntries(context); +#endif + + } else if (strcmp(option_name, "one_share_per_server") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionOneSharePerServer(context); +#else + return (void *) (bool) smbc_getOptionOneSharePerServer(context); +#endif + + } else if (strcmp(option_name, "use_kerberos") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionUseKerberos(context); +#else + return (void *) (bool) smbc_getOptionUseKerberos(context); +#endif + + } else if (strcmp(option_name, "fallback_after_kerberos") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *)(intptr_t) smbc_getOptionFallbackAfterKerberos(context); +#else + return (void *) (bool) smbc_getOptionFallbackAfterKerberos(context); +#endif + + } else if (strcmp(option_name, "no_auto_anonymous_login") == 0) { +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) + return (void *) (intptr_t) smbc_getOptionNoAutoAnonymousLogin(context); +#else + return (void *) (bool) smbc_getOptionNoAutoAnonymousLogin(context); +#endif + } + + return NULL; +} + + +/* + * Initialize the library, etc. + * + * We accept a struct containing handle information. + * valid values for info->debug from 0 to 100, + * and insist that info->fn must be non-null. + */ +SMBCCTX * +smbc_init_context(SMBCCTX *context) +{ + int pid; + char *user = NULL; + char *home = NULL; + + if (!context) { + errno = EBADF; + return NULL; + } + + /* Do not initialise the same client twice */ + if (context->internal->initialized) { + return NULL; + } + + if ((!smbc_getFunctionAuthData(context) && + !smbc_getFunctionAuthDataWithContext(context)) || + smbc_getDebug(context) < 0 || + smbc_getDebug(context) > 100) { + + errno = EINVAL; + return NULL; + + } + + if (!SMBC_initialized) { + /* + * Do some library-wide intializations the first time we get + * called + */ + bool conf_loaded = False; + TALLOC_CTX *frame = talloc_stackframe(); + + load_case_tables(); + + setup_logging("libsmbclient", True); + if (context->internal->debug_stderr) { + dbf = x_stderr; + x_setbuf(x_stderr, NULL); + } + + /* Here we would open the smb.conf file if needed ... */ + + lp_set_in_client(True); + + home = getenv("HOME"); + if (home) { + char *conf = NULL; + if (asprintf(&conf, "%s/.smb/smb.conf", home) > 0) { + if (lp_load(conf, True, False, False, True)) { + conf_loaded = True; + } else { + DEBUG(5, ("Could not load config file: %s\n", + conf)); + } + SAFE_FREE(conf); + } + } + + if (!conf_loaded) { + /* + * Well, if that failed, try the get_dyn_CONFIGFILE + * Which points to the standard locn, and if that + * fails, silently ignore it and use the internal + * defaults ... + */ + + if (!lp_load(get_dyn_CONFIGFILE(), True, False, False, False)) { + DEBUG(5, ("Could not load config file: %s\n", + get_dyn_CONFIGFILE())); + } else if (home) { + char *conf; + /* + * We loaded the global config file. Now lets + * load user-specific modifications to the + * global config. + */ + if (asprintf(&conf, + "%s/.smb/smb.conf.append", + home) > 0) { + if (!lp_load(conf, True, False, False, False)) { + DEBUG(10, + ("Could not append config file: " + "%s\n", + conf)); + } + SAFE_FREE(conf); + } + } + } + + load_interfaces(); /* Load the list of interfaces ... */ + + reopen_logs(); /* Get logging working ... */ + + /* + * Block SIGPIPE (from lib/util_sock.c: write()) + * It is not needed and should not stop execution + */ + BlockSignals(True, SIGPIPE); + + /* Done with one-time initialisation */ + SMBC_initialized = true; + + TALLOC_FREE(frame); + } + + if (!smbc_getUser(context)) { + /* + * FIXME: Is this the best way to get the user info? + */ + user = getenv("USER"); + /* walk around as "guest" if no username can be found */ + if (!user) { + user = SMB_STRDUP("guest"); + } else { + user = SMB_STRDUP(user); + } + + if (!user) { + errno = ENOMEM; + return NULL; + } + + smbc_setUser(context, user); + } + + if (!smbc_getNetbiosName(context)) { + /* + * We try to get our netbios name from the config. If that + * fails we fall back on constructing our netbios name from + * our hostname etc + */ + char *netbios_name; + if (global_myname()) { + netbios_name = SMB_STRDUP(global_myname()); + } else { + /* + * Hmmm, I want to get hostname as well, but I am too + * lazy for the moment + */ + pid = sys_getpid(); + netbios_name = (char *)SMB_MALLOC(17); + if (!netbios_name) { + errno = ENOMEM; + return NULL; + } + slprintf(netbios_name, 16, + "smbc%s%d", smbc_getUser(context), pid); + } + + if (!netbios_name) { + errno = ENOMEM; + return NULL; + } + + smbc_setNetbiosName(context, netbios_name); + } + + DEBUG(1, ("Using netbios name %s.\n", smbc_getNetbiosName(context))); + + if (!smbc_getWorkgroup(context)) { + char *workgroup; + + if (lp_workgroup()) { + workgroup = SMB_STRDUP(lp_workgroup()); + } + else { + /* TODO: Think about a decent default workgroup */ + workgroup = SMB_STRDUP("samba"); + } + + if (!workgroup) { + errno = ENOMEM; + return NULL; + } + + smbc_setWorkgroup(context, workgroup); + } + + DEBUG(1, ("Using workgroup %s.\n", smbc_getWorkgroup(context))); + + /* shortest timeout is 1 second */ + if (smbc_getTimeout(context) > 0 && smbc_getTimeout(context) < 1000) + smbc_setTimeout(context, 1000); + + /* + * FIXME: Should we check the function pointers here? + */ + + context->internal->initialized = True; + initialized_ctx_count++; + + return context; +} + + +/* Return the verion of samba, and thus libsmbclient */ +const char * +smbc_version(void) +{ + return SAMBA_VERSION_STRING; +} + + +/* + * Set the credentials so DFS will work when following referrals. + */ +void +smbc_set_credentials(char *workgroup, + char *user, + char *password, + smbc_bool use_kerberos, + char *signing_state) +{ + + set_cmdline_auth_info_username(user); + set_cmdline_auth_info_password(password); + set_cmdline_auth_info_use_kerberos(use_kerberos); + if (! set_cmdline_auth_info_signing_state(signing_state)) { + DEBUG(0, ("Invalid signing state: %s", signing_state)); + } + set_global_myworkgroup(workgroup); + cli_cm_set_credentials(); +} diff --git a/source3/libsmb/libsmb_dir.c b/source3/libsmb/libsmb_dir.c new file mode 100644 index 0000000000..aa313f2c05 --- /dev/null +++ b/source3/libsmb/libsmb_dir.c @@ -0,0 +1,1949 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Routine to open a directory + * We accept the URL syntax explained in SMBC_parse_path(), above. + */ + +static void +remove_dir(SMBCFILE *dir) +{ + struct smbc_dir_list *d,*f; + + d = dir->dir_list; + while (d) { + + f = d; d = d->next; + + SAFE_FREE(f->dirent); + SAFE_FREE(f); + + } + + dir->dir_list = dir->dir_end = dir->dir_next = NULL; + +} + +static int +add_dirent(SMBCFILE *dir, + const char *name, + const char *comment, + uint32 type) +{ + struct smbc_dirent *dirent; + int size; + int name_length = (name == NULL ? 0 : strlen(name)); + int comment_len = (comment == NULL ? 0 : strlen(comment)); + + /* + * Allocate space for the dirent, which must be increased by the + * size of the name and the comment and 1 each for the null terminator. + */ + + size = sizeof(struct smbc_dirent) + name_length + comment_len + 2; + + dirent = (struct smbc_dirent *)SMB_MALLOC(size); + + if (!dirent) { + + dir->dir_error = ENOMEM; + return -1; + + } + + ZERO_STRUCTP(dirent); + + if (dir->dir_list == NULL) { + + dir->dir_list = SMB_MALLOC_P(struct smbc_dir_list); + if (!dir->dir_list) { + + SAFE_FREE(dirent); + dir->dir_error = ENOMEM; + return -1; + + } + ZERO_STRUCTP(dir->dir_list); + + dir->dir_end = dir->dir_next = dir->dir_list; + } + else { + + dir->dir_end->next = SMB_MALLOC_P(struct smbc_dir_list); + + if (!dir->dir_end->next) { + + SAFE_FREE(dirent); + dir->dir_error = ENOMEM; + return -1; + + } + ZERO_STRUCTP(dir->dir_end->next); + + dir->dir_end = dir->dir_end->next; + } + + dir->dir_end->next = NULL; + dir->dir_end->dirent = dirent; + + dirent->smbc_type = type; + dirent->namelen = name_length; + dirent->commentlen = comment_len; + dirent->dirlen = size; + + /* + * dirent->namelen + 1 includes the null (no null termination needed) + * Ditto for dirent->commentlen. + * The space for the two null bytes was allocated. + */ + strncpy(dirent->name, (name?name:""), dirent->namelen + 1); + dirent->comment = (char *)(&dirent->name + dirent->namelen + 1); + strncpy(dirent->comment, (comment?comment:""), dirent->commentlen + 1); + + return 0; + +} + +static void +list_unique_wg_fn(const char *name, + uint32 type, + const char *comment, + void *state) +{ + SMBCFILE *dir = (SMBCFILE *)state; + struct smbc_dir_list *dir_list; + struct smbc_dirent *dirent; + int dirent_type; + int do_remove = 0; + + dirent_type = dir->dir_type; + + if (add_dirent(dir, name, comment, dirent_type) < 0) { + + /* An error occurred, what do we do? */ + /* FIXME: Add some code here */ + } + + /* Point to the one just added */ + dirent = dir->dir_end->dirent; + + /* See if this was a duplicate */ + for (dir_list = dir->dir_list; + dir_list != dir->dir_end; + dir_list = dir_list->next) { + if (! do_remove && + strcmp(dir_list->dirent->name, dirent->name) == 0) { + /* Duplicate. End end of list need to be removed. */ + do_remove = 1; + } + + if (do_remove && dir_list->next == dir->dir_end) { + /* Found the end of the list. Remove it. */ + dir->dir_end = dir_list; + free(dir_list->next); + free(dirent); + dir_list->next = NULL; + break; + } + } +} + +static void +list_fn(const char *name, + uint32 type, + const char *comment, + void *state) +{ + SMBCFILE *dir = (SMBCFILE *)state; + int dirent_type; + + /* + * We need to process the type a little ... + * + * Disk share = 0x00000000 + * Print share = 0x00000001 + * Comms share = 0x00000002 (obsolete?) + * IPC$ share = 0x00000003 + * + * administrative shares: + * ADMIN$, IPC$, C$, D$, E$ ... are type |= 0x80000000 + */ + + if (dir->dir_type == SMBC_FILE_SHARE) { + switch (type) { + case 0 | 0x80000000: + case 0: + dirent_type = SMBC_FILE_SHARE; + break; + + case 1: + dirent_type = SMBC_PRINTER_SHARE; + break; + + case 2: + dirent_type = SMBC_COMMS_SHARE; + break; + + case 3 | 0x80000000: + case 3: + dirent_type = SMBC_IPC_SHARE; + break; + + default: + dirent_type = SMBC_FILE_SHARE; /* FIXME, error? */ + break; + } + } + else { + dirent_type = dir->dir_type; + } + + if (add_dirent(dir, name, comment, dirent_type) < 0) { + + /* An error occurred, what do we do? */ + /* FIXME: Add some code here */ + + } +} + +static void +dir_list_fn(const char *mnt, + file_info *finfo, + const char *mask, + void *state) +{ + + if (add_dirent((SMBCFILE *)state, finfo->name, "", + (finfo->mode&aDIR?SMBC_DIR:SMBC_FILE)) < 0) { + + /* Handle an error ... */ + + /* FIXME: Add some code ... */ + + } + +} + +static int +net_share_enum_rpc(struct cli_state *cli, + void (*fn)(const char *name, + uint32 type, + const char *comment, + void *state), + void *state) +{ + int i; + WERROR result; + uint32 preferred_len = 0xffffffff; + uint32 type; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr1 ctr1; + fstring name = ""; + fstring comment = ""; + struct rpc_pipe_client *pipe_hnd; + NTSTATUS nt_status; + uint32_t resume_handle = 0; + uint32_t total_entries = 0; + + /* Open the server service pipe */ + nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_srvsvc.syntax_id, + &pipe_hnd); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("net_share_enum_rpc pipe open fail!\n")); + return -1; + } + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(ctr1); + + info_ctr.level = 1; + info_ctr.ctr.ctr1 = &ctr1; + + /* Issue the NetShareEnum RPC call and retrieve the response */ + nt_status = rpccli_srvsvc_NetShareEnumAll(pipe_hnd, talloc_tos(), + pipe_hnd->desthost, + &info_ctr, + preferred_len, + &total_entries, + &resume_handle, + &result); + + /* Was it successful? */ + if (!NT_STATUS_IS_OK(nt_status) || !W_ERROR_IS_OK(result) || + total_entries == 0) { + /* Nope. Go clean up. */ + goto done; + } + + /* For each returned entry... */ + for (i = 0; i < total_entries; i++) { + + /* pull out the share name */ + fstrcpy(name, info_ctr.ctr.ctr1->array[i].name); + + /* pull out the share's comment */ + fstrcpy(comment, info_ctr.ctr.ctr1->array[i].comment); + + /* Get the type value */ + type = info_ctr.ctr.ctr1->array[i].type; + + /* Add this share to the list */ + (*fn)(name, type, comment, state); + } + +done: + /* Close the server service pipe */ + TALLOC_FREE(pipe_hnd); + + /* Tell 'em if it worked */ + return W_ERROR_IS_OK(result) ? 0 : -1; +} + + +/* + * Verify that the options specified in a URL are valid + */ +int +SMBC_check_options(char *server, + char *share, + char *path, + char *options) +{ + DEBUG(4, ("SMBC_check_options(): server='%s' share='%s' " + "path='%s' options='%s'\n", + server, share, path, options)); + + /* No options at all is always ok */ + if (! *options) return 0; + + /* Currently, we don't support any options. */ + return -1; +} + + +SMBCFILE * +SMBC_opendir_ctx(SMBCCTX *context, + const char *fname) +{ + int saved_errno; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *options = NULL; + char *workgroup = NULL; + char *path = NULL; + uint16 mode; + char *p = NULL; + SMBCSRV *srv = NULL; + SMBCFILE *dir = NULL; + struct sockaddr_storage rem_ss; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + DEBUG(4, ("no valid context\n")); + errno = EINVAL + 8192; + TALLOC_FREE(frame); + return NULL; + + } + + if (!fname) { + DEBUG(4, ("no valid fname\n")); + errno = EINVAL + 8193; + TALLOC_FREE(frame); + return NULL; + } + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + &options)) { + DEBUG(4, ("no valid path\n")); + errno = EINVAL + 8194; + TALLOC_FREE(frame); + return NULL; + } + + DEBUG(4, ("parsed path: fname='%s' server='%s' share='%s' " + "path='%s' options='%s'\n", + fname, server, share, path, options)); + + /* Ensure the options are valid */ + if (SMBC_check_options(server, share, path, options)) { + DEBUG(4, ("unacceptable options (%s)\n", options)); + errno = EINVAL + 8195; + TALLOC_FREE(frame); + return NULL; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + } + + dir = SMB_MALLOC_P(SMBCFILE); + + if (!dir) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + + ZERO_STRUCTP(dir); + + dir->cli_fd = 0; + dir->fname = SMB_STRDUP(fname); + dir->srv = NULL; + dir->offset = 0; + dir->file = False; + dir->dir_list = dir->dir_next = dir->dir_end = NULL; + + if (server[0] == (char)0) { + + int i; + int count; + int max_lmb_count; + struct ip_service *ip_list; + struct ip_service server_addr; + struct user_auth_info u_info; + + if (share[0] != (char)0 || path[0] != (char)0) { + + errno = EINVAL + 8196; + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + /* Determine how many local master browsers to query */ + max_lmb_count = (smbc_getOptionBrowseMaxLmbCount(context) == 0 + ? INT_MAX + : smbc_getOptionBrowseMaxLmbCount(context)); + + memset(&u_info, '\0', sizeof(u_info)); + u_info.username = talloc_strdup(frame,user); + u_info.password = talloc_strdup(frame,password); + if (!u_info.username || !u_info.password) { + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + /* + * We have server and share and path empty but options + * requesting that we scan all master browsers for their list + * of workgroups/domains. This implies that we must first try + * broadcast queries to find all master browsers, and if that + * doesn't work, then try our other methods which return only + * a single master browser. + */ + + ip_list = NULL; + if (!NT_STATUS_IS_OK(name_resolve_bcast(MSBROWSE, 1, &ip_list, + &count))) + { + + SAFE_FREE(ip_list); + + if (!find_master_ip(workgroup, &server_addr.ss)) { + + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + errno = ENOENT; + TALLOC_FREE(frame); + return NULL; + } + + ip_list = (struct ip_service *)memdup( + &server_addr, sizeof(server_addr)); + if (ip_list == NULL) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + count = 1; + } + + for (i = 0; i < count && i < max_lmb_count; i++) { + char addr[INET6_ADDRSTRLEN]; + char *wg_ptr = NULL; + struct cli_state *cli = NULL; + + print_sockaddr(addr, sizeof(addr), &ip_list[i].ss); + DEBUG(99, ("Found master browser %d of %d: %s\n", + i+1, MAX(count, max_lmb_count), + addr)); + + cli = get_ipc_connect_master_ip(talloc_tos(), + &ip_list[i], + &u_info, + &wg_ptr); + /* cli == NULL is the master browser refused to talk or + could not be found */ + if (!cli) { + continue; + } + + workgroup = talloc_strdup(frame, wg_ptr); + server = talloc_strdup(frame, cli->desthost); + + cli_shutdown(cli); + + if (!workgroup || !server) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + + DEBUG(4, ("using workgroup %s %s\n", + workgroup, server)); + + /* + * For each returned master browser IP address, get a + * connection to IPC$ on the server if we do not + * already have one, and determine the + * workgroups/domains that it knows about. + */ + + srv = SMBC_server(frame, context, True, server, "IPC$", + &workgroup, &user, &password); + if (!srv) { + continue; + } + + dir->srv = srv; + dir->dir_type = SMBC_WORKGROUP; + + /* Now, list the stuff ... */ + + if (!cli_NetServerEnum(srv->cli, + workgroup, + SV_TYPE_DOMAIN_ENUM, + list_unique_wg_fn, + (void *)dir)) { + continue; + } + } + + SAFE_FREE(ip_list); + } else { + /* + * Server not an empty string ... Check the rest and see what + * gives + */ + if (*share == '\0') { + if (*path != '\0') { + + /* Should not have empty share with path */ + errno = EINVAL + 8197; + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + + } + + /* + * We don't know if <server> is really a server name + * or is a workgroup/domain name. If we already have + * a server structure for it, we'll use it. + * Otherwise, check to see if <server><1D>, + * <server><1B>, or <server><20> translates. We check + * to see if <server> is an IP address first. + */ + + /* + * See if we have an existing server. Do not + * establish a connection if one does not already + * exist. + */ + srv = SMBC_server(frame, context, False, + server, "IPC$", + &workgroup, &user, &password); + + /* + * If no existing server and not an IP addr, look for + * LMB or DMB + */ + if (!srv && + !is_ipaddress(server) && + (resolve_name(server, &rem_ss, 0x1d) || /* LMB */ + resolve_name(server, &rem_ss, 0x1b) )) { /* DMB */ + + fstring buserver; + + dir->dir_type = SMBC_SERVER; + + /* + * Get the backup list ... + */ + if (!name_status_find(server, 0, 0, + &rem_ss, buserver)) { + + DEBUG(0,("Could not get name of " + "local/domain master browser " + "for server %s\n", server)); + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + errno = EPERM; + TALLOC_FREE(frame); + return NULL; + + } + + /* + * Get a connection to IPC$ on the server if + * we do not already have one + */ + srv = SMBC_server(frame, context, True, + buserver, "IPC$", + &workgroup, + &user, &password); + if (!srv) { + DEBUG(0, ("got no contact to IPC$\n")); + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + + } + + dir->srv = srv; + + /* Now, list the servers ... */ + if (!cli_NetServerEnum(srv->cli, server, + 0x0000FFFE, list_fn, + (void *)dir)) { + + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + } else if (srv || + (resolve_name(server, &rem_ss, 0x20))) { + + /* + * If we hadn't found the server, get one now + */ + if (!srv) { + srv = SMBC_server(frame, context, True, + server, "IPC$", + &workgroup, + &user, &password); + } + + if (!srv) { + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + + } + + dir->dir_type = SMBC_FILE_SHARE; + dir->srv = srv; + + /* List the shares ... */ + + if (net_share_enum_rpc( + srv->cli, + list_fn, + (void *) dir) < 0 && + cli_RNetShareEnum( + srv->cli, + list_fn, + (void *)dir) < 0) { + + errno = cli_errno(srv->cli); + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + + } + } else { + /* Neither the workgroup nor server exists */ + errno = ECONNREFUSED; + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + } + else { + /* + * The server and share are specified ... work from + * there ... + */ + char *targetpath; + struct cli_state *targetcli; + + /* We connect to the server and list the directory */ + dir->dir_type = SMBC_FILE_SHARE; + + srv = SMBC_server(frame, context, True, server, share, + &workgroup, &user, &password); + + if (!srv) { + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + dir->srv = srv; + + /* Now, list the files ... */ + + p = path + strlen(path); + path = talloc_asprintf_append(path, "\\*"); + if (!path) { + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + if (!cli_resolve_path(frame, "", srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + TALLOC_FREE(frame); + return NULL; + } + + if (cli_list(targetcli, targetpath, + aDIR | aSYSTEM | aHIDDEN, + dir_list_fn, (void *)dir) < 0) { + + if (dir) { + SAFE_FREE(dir->fname); + SAFE_FREE(dir); + } + saved_errno = SMBC_errno(context, targetcli); + + if (saved_errno == EINVAL) { + /* + * See if they asked to opendir + * something other than a directory. + * If so, the converted error value we + * got would have been EINVAL rather + * than ENOTDIR. + */ + *p = '\0'; /* restore original path */ + + if (SMBC_getatr(context, srv, path, + &mode, NULL, + NULL, NULL, NULL, NULL, + NULL) && + ! IS_DOS_DIR(mode)) { + + /* It is. Correct the error value */ + saved_errno = ENOTDIR; + } + } + + /* + * If there was an error and the server is no + * good any more... + */ + if (cli_is_error(targetcli) && + smbc_getFunctionCheckServer(context)(context, srv)) { + + /* ... then remove it. */ + if (smbc_getFunctionRemoveUnusedServer(context)(context, + srv)) { + /* + * We could not remove the + * server completely, remove + * it from the cache so we + * will not get it again. It + * will be removed when the + * last file/dir is closed. + */ + smbc_getFunctionRemoveCachedServer(context)(context, srv); + } + } + + errno = saved_errno; + TALLOC_FREE(frame); + return NULL; + } + } + + } + + DLIST_ADD(context->internal->files, dir); + TALLOC_FREE(frame); + return dir; + +} + +/* + * Routine to close a directory + */ + +int +SMBC_closedir_ctx(SMBCCTX *context, + SMBCFILE *dir) +{ + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!dir || !SMBC_dlist_contains(context->internal->files, dir)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + } + + remove_dir(dir); /* Clean it up */ + + DLIST_REMOVE(context->internal->files, dir); + + if (dir) { + + SAFE_FREE(dir->fname); + SAFE_FREE(dir); /* Free the space too */ + } + + TALLOC_FREE(frame); + return 0; + +} + +static void +smbc_readdir_internal(SMBCCTX * context, + struct smbc_dirent *dest, + struct smbc_dirent *src, + int max_namebuf_len) +{ + if (smbc_getOptionUrlEncodeReaddirEntries(context)) { + + /* url-encode the name. get back remaining buffer space */ + max_namebuf_len = + SMBC_urlencode(dest->name, src->name, max_namebuf_len); + + /* We now know the name length */ + dest->namelen = strlen(dest->name); + + /* Save the pointer to the beginning of the comment */ + dest->comment = dest->name + dest->namelen + 1; + + /* Copy the comment */ + strncpy(dest->comment, src->comment, max_namebuf_len - 1); + dest->comment[max_namebuf_len - 1] = '\0'; + + /* Save other fields */ + dest->smbc_type = src->smbc_type; + dest->commentlen = strlen(dest->comment); + dest->dirlen = ((dest->comment + dest->commentlen + 1) - + (char *) dest); + } else { + + /* No encoding. Just copy the entry as is. */ + memcpy(dest, src, src->dirlen); + dest->comment = (char *)(&dest->name + src->namelen + 1); + } + +} + +/* + * Routine to get a directory entry + */ + +struct smbc_dirent * +SMBC_readdir_ctx(SMBCCTX *context, + SMBCFILE *dir) +{ + int maxlen; + struct smbc_dirent *dirp, *dirent; + TALLOC_CTX *frame = talloc_stackframe(); + + /* Check that all is ok first ... */ + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + DEBUG(0, ("Invalid context in SMBC_readdir_ctx()\n")); + TALLOC_FREE(frame); + return NULL; + + } + + if (!dir || !SMBC_dlist_contains(context->internal->files, dir)) { + + errno = EBADF; + DEBUG(0, ("Invalid dir in SMBC_readdir_ctx()\n")); + TALLOC_FREE(frame); + return NULL; + + } + + if (dir->file != False) { /* FIXME, should be dir, perhaps */ + + errno = ENOTDIR; + DEBUG(0, ("Found file vs directory in SMBC_readdir_ctx()\n")); + TALLOC_FREE(frame); + return NULL; + + } + + if (!dir->dir_next) { + TALLOC_FREE(frame); + return NULL; + } + + dirent = dir->dir_next->dirent; + if (!dirent) { + + errno = ENOENT; + TALLOC_FREE(frame); + return NULL; + + } + + dirp = (struct smbc_dirent *)context->internal->dirent; + maxlen = (sizeof(context->internal->dirent) - + sizeof(struct smbc_dirent)); + + smbc_readdir_internal(context, dirp, dirent, maxlen); + + dir->dir_next = dir->dir_next->next; + + TALLOC_FREE(frame); + return dirp; +} + +/* + * Routine to get directory entries + */ + +int +SMBC_getdents_ctx(SMBCCTX *context, + SMBCFILE *dir, + struct smbc_dirent *dirp, + int count) +{ + int rem = count; + int reqd; + int maxlen; + char *ndir = (char *)dirp; + struct smbc_dir_list *dirlist; + TALLOC_CTX *frame = talloc_stackframe(); + + /* Check that all is ok first ... */ + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (!dir || !SMBC_dlist_contains(context->internal->files, dir)) { + + errno = EBADF; + TALLOC_FREE(frame); + return -1; + + } + + if (dir->file != False) { /* FIXME, should be dir, perhaps */ + + errno = ENOTDIR; + TALLOC_FREE(frame); + return -1; + + } + + /* + * Now, retrieve the number of entries that will fit in what was passed + * We have to figure out if the info is in the list, or we need to + * send a request to the server to get the info. + */ + + while ((dirlist = dir->dir_next)) { + struct smbc_dirent *dirent; + + if (!dirlist->dirent) { + + errno = ENOENT; /* Bad error */ + TALLOC_FREE(frame); + return -1; + + } + + /* Do urlencoding of next entry, if so selected */ + dirent = (struct smbc_dirent *)context->internal->dirent; + maxlen = (sizeof(context->internal->dirent) - + sizeof(struct smbc_dirent)); + smbc_readdir_internal(context, dirent, + dirlist->dirent, maxlen); + + reqd = dirent->dirlen; + + if (rem < reqd) { + + if (rem < count) { /* We managed to copy something */ + + errno = 0; + TALLOC_FREE(frame); + return count - rem; + + } + else { /* Nothing copied ... */ + + errno = EINVAL; /* Not enough space ... */ + TALLOC_FREE(frame); + return -1; + + } + + } + + memcpy(ndir, dirent, reqd); /* Copy the data in ... */ + + ((struct smbc_dirent *)ndir)->comment = + (char *)(&((struct smbc_dirent *)ndir)->name + + dirent->namelen + + 1); + + ndir += reqd; + + rem -= reqd; + + dir->dir_next = dirlist = dirlist -> next; + } + + TALLOC_FREE(frame); + + if (rem == count) + return 0; + else + return count - rem; + +} + +/* + * Routine to create a directory ... + */ + +int +SMBC_mkdir_ctx(SMBCCTX *context, + const char *fname, + mode_t mode) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_mkdir(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + + } + + /*d_printf(">>>mkdir: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>mkdir: resolved path as %s\n", targetpath);*/ + + if (!cli_mkdir(targetcli, targetpath)) { + + errno = SMBC_errno(context, targetcli); + TALLOC_FREE(frame); + return -1; + + } + + TALLOC_FREE(frame); + return 0; + +} + +/* + * Our list function simply checks to see if a directory is not empty + */ + +static int smbc_rmdir_dirempty = True; + +static void +rmdir_list_fn(const char *mnt, + file_info *finfo, + const char *mask, + void *state) +{ + if (strncmp(finfo->name, ".", 1) != 0 && + strncmp(finfo->name, "..", 2) != 0) { + smbc_rmdir_dirempty = False; + } +} + +/* + * Routine to remove a directory + */ + +int +SMBC_rmdir_ctx(SMBCCTX *context, + const char *fname) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_rmdir(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + + } + + /*d_printf(">>>rmdir: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>rmdir: resolved path as %s\n", targetpath);*/ + + + if (!cli_rmdir(targetcli, targetpath)) { + + errno = SMBC_errno(context, targetcli); + + if (errno == EACCES) { /* Check if the dir empty or not */ + + /* Local storage to avoid buffer overflows */ + char *lpath; + + smbc_rmdir_dirempty = True; /* Make this so ... */ + + lpath = talloc_asprintf(frame, "%s\\*", + targetpath); + if (!lpath) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + + if (cli_list(targetcli, lpath, + aDIR | aSYSTEM | aHIDDEN, + rmdir_list_fn, NULL) < 0) { + + /* Fix errno to ignore latest error ... */ + DEBUG(5, ("smbc_rmdir: " + "cli_list returned an error: %d\n", + SMBC_errno(context, targetcli))); + errno = EACCES; + + } + + if (smbc_rmdir_dirempty) + errno = EACCES; + else + errno = ENOTEMPTY; + + } + + TALLOC_FREE(frame); + return -1; + + } + + TALLOC_FREE(frame); + return 0; + +} + +/* + * Routine to return the current directory position + */ + +off_t +SMBC_telldir_ctx(SMBCCTX *context, + SMBCFILE *dir) +{ + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (!dir || !SMBC_dlist_contains(context->internal->files, dir)) { + + errno = EBADF; + TALLOC_FREE(frame); + return -1; + + } + + if (dir->file != False) { /* FIXME, should be dir, perhaps */ + + errno = ENOTDIR; + TALLOC_FREE(frame); + return -1; + + } + + /* See if we're already at the end. */ + if (dir->dir_next == NULL) { + /* We are. */ + TALLOC_FREE(frame); + return -1; + } + + /* + * We return the pointer here as the offset + */ + TALLOC_FREE(frame); + return (off_t)(long)dir->dir_next->dirent; +} + +/* + * A routine to run down the list and see if the entry is OK + */ + +static struct smbc_dir_list * +check_dir_ent(struct smbc_dir_list *list, + struct smbc_dirent *dirent) +{ + + /* Run down the list looking for what we want */ + + if (dirent) { + + struct smbc_dir_list *tmp = list; + + while (tmp) { + + if (tmp->dirent == dirent) + return tmp; + + tmp = tmp->next; + + } + + } + + return NULL; /* Not found, or an error */ + +} + + +/* + * Routine to seek on a directory + */ + +int +SMBC_lseekdir_ctx(SMBCCTX *context, + SMBCFILE *dir, + off_t offset) +{ + long int l_offset = offset; /* Handle problems of size */ + struct smbc_dirent *dirent = (struct smbc_dirent *)l_offset; + struct smbc_dir_list *list_ent = (struct smbc_dir_list *)NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (dir->file != False) { /* FIXME, should be dir, perhaps */ + + errno = ENOTDIR; + TALLOC_FREE(frame); + return -1; + + } + + /* Now, check what we were passed and see if it is OK ... */ + + if (dirent == NULL) { /* Seek to the begining of the list */ + + dir->dir_next = dir->dir_list; + TALLOC_FREE(frame); + return 0; + + } + + if (offset == -1) { /* Seek to the end of the list */ + dir->dir_next = NULL; + TALLOC_FREE(frame); + return 0; + } + + /* Now, run down the list and make sure that the entry is OK */ + /* This may need to be changed if we change the format of the list */ + + if ((list_ent = check_dir_ent(dir->dir_list, dirent)) == NULL) { + errno = EINVAL; /* Bad entry */ + TALLOC_FREE(frame); + return -1; + } + + dir->dir_next = list_ent; + + TALLOC_FREE(frame); + return 0; +} + +/* + * Routine to fstat a dir + */ + +int +SMBC_fstatdir_ctx(SMBCCTX *context, + SMBCFILE *dir, + struct stat *st) +{ + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + return -1; + } + + /* No code yet ... */ + return 0; +} + +int +SMBC_chmod_ctx(SMBCCTX *context, + const char *fname, + mode_t newmode) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + uint16 mode; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_chmod(%s, 0%3o)\n", fname, newmode)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + mode = 0; + + if (!(newmode & (S_IWUSR | S_IWGRP | S_IWOTH))) mode |= aRONLY; + if ((newmode & S_IXUSR) && lp_map_archive(-1)) mode |= aARCH; + if ((newmode & S_IXGRP) && lp_map_system(-1)) mode |= aSYSTEM; + if ((newmode & S_IXOTH) && lp_map_hidden(-1)) mode |= aHIDDEN; + + if (!cli_setatr(srv->cli, path, mode, 0)) { + errno = SMBC_errno(context, srv->cli); + TALLOC_FREE(frame); + return -1; + } + + TALLOC_FREE(frame); + return 0; +} + +int +SMBC_utimes_ctx(SMBCCTX *context, + const char *fname, + struct timeval *tbuf) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + time_t access_time; + time_t write_time; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (tbuf == NULL) { + access_time = write_time = time(NULL); + } else { + access_time = tbuf[0].tv_sec; + write_time = tbuf[1].tv_sec; + } + + if (DEBUGLVL(4)) { + char *p; + char atimebuf[32]; + char mtimebuf[32]; + + strncpy(atimebuf, ctime(&access_time), sizeof(atimebuf) - 1); + atimebuf[sizeof(atimebuf) - 1] = '\0'; + if ((p = strchr(atimebuf, '\n')) != NULL) { + *p = '\0'; + } + + strncpy(mtimebuf, ctime(&write_time), sizeof(mtimebuf) - 1); + mtimebuf[sizeof(mtimebuf) - 1] = '\0'; + if ((p = strchr(mtimebuf, '\n')) != NULL) { + *p = '\0'; + } + + dbgtext("smbc_utimes(%s, atime = %s mtime = %s)\n", + fname, atimebuf, mtimebuf); + } + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (!SMBC_setatr(context, srv, path, + 0, access_time, write_time, 0, 0)) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_setatr */ + } + + TALLOC_FREE(frame); + return 0; +} + +/* + * Routine to unlink() a file + */ + +int +SMBC_unlink_ctx(SMBCCTX *context, + const char *fname) +{ + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + SMBCSRV *srv = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + TALLOC_FREE(frame); + return -1; /* SMBC_server sets errno */ + + } + + /*d_printf(">>>unlink: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>unlink: resolved path as %s\n", targetpath);*/ + + if (!cli_unlink(targetcli, targetpath)) { + + errno = SMBC_errno(context, targetcli); + + if (errno == EACCES) { /* Check if the file is a directory */ + + int saverr = errno; + SMB_OFF_T size = 0; + uint16 mode = 0; + struct timespec write_time_ts; + struct timespec access_time_ts; + struct timespec change_time_ts; + SMB_INO_T ino = 0; + + if (!SMBC_getatr(context, srv, path, &mode, &size, + NULL, + &access_time_ts, + &write_time_ts, + &change_time_ts, + &ino)) { + + /* Hmmm, bad error ... What? */ + + errno = SMBC_errno(context, targetcli); + TALLOC_FREE(frame); + return -1; + + } + else { + + if (IS_DOS_DIR(mode)) + errno = EISDIR; + else + errno = saverr; /* Restore this */ + + } + } + + TALLOC_FREE(frame); + return -1; + + } + + TALLOC_FREE(frame); + return 0; /* Success ... */ + +} + +/* + * Routine to rename() a file + */ + +int +SMBC_rename_ctx(SMBCCTX *ocontext, + const char *oname, + SMBCCTX *ncontext, + const char *nname) +{ + char *server1 = NULL; + char *share1 = NULL; + char *server2 = NULL; + char *share2 = NULL; + char *user1 = NULL; + char *user2 = NULL; + char *password1 = NULL; + char *password2 = NULL; + char *workgroup = NULL; + char *path1 = NULL; + char *path2 = NULL; + char *targetpath1 = NULL; + char *targetpath2 = NULL; + struct cli_state *targetcli1 = NULL; + struct cli_state *targetcli2 = NULL; + SMBCSRV *srv = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!ocontext || !ncontext || + !ocontext->internal->initialized || + !ncontext->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!oname || !nname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_rename(%s,%s)\n", oname, nname)); + + if (SMBC_parse_path(frame, + ocontext, + oname, + &workgroup, + &server1, + &share1, + &path1, + &user1, + &password1, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user1 || user1[0] == (char)0) { + user1 = talloc_strdup(frame, smbc_getUser(ocontext)); + if (!user1) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + if (SMBC_parse_path(frame, + ncontext, + nname, + NULL, + &server2, + &share2, + &path2, + &user2, + &password2, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user2 || user2[0] == (char)0) { + user2 = talloc_strdup(frame, smbc_getUser(ncontext)); + if (!user2) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + if (strcmp(server1, server2) || strcmp(share1, share2) || + strcmp(user1, user2)) { + /* Can't rename across file systems, or users?? */ + errno = EXDEV; + TALLOC_FREE(frame); + return -1; + } + + srv = SMBC_server(frame, ocontext, True, + server1, share1, &workgroup, &user1, &password1); + if (!srv) { + TALLOC_FREE(frame); + return -1; + + } + + /*d_printf(">>>rename: resolving %s\n", path1);*/ + if (!cli_resolve_path(frame, "", srv->cli, path1, + &targetcli1, &targetpath1)) { + d_printf("Could not resolve %s\n", path1); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>rename: resolved path as %s\n", targetpath1);*/ + /*d_printf(">>>rename: resolving %s\n", path2);*/ + if (!cli_resolve_path(frame, "", srv->cli, path2, + &targetcli2, &targetpath2)) { + d_printf("Could not resolve %s\n", path2); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>rename: resolved path as %s\n", targetpath2);*/ + + if (strcmp(targetcli1->desthost, targetcli2->desthost) || + strcmp(targetcli1->share, targetcli2->share)) + { + /* can't rename across file systems */ + errno = EXDEV; + TALLOC_FREE(frame); + return -1; + } + + if (!cli_rename(targetcli1, targetpath1, targetpath2)) { + int eno = SMBC_errno(ocontext, targetcli1); + + if (eno != EEXIST || + !cli_unlink(targetcli1, targetpath2) || + !cli_rename(targetcli1, targetpath1, targetpath2)) { + + errno = eno; + TALLOC_FREE(frame); + return -1; + + } + } + + TALLOC_FREE(frame); + return 0; /* Success */ +} + diff --git a/source3/libsmb/libsmb_file.c b/source3/libsmb/libsmb_file.c new file mode 100644 index 0000000000..7b287096c2 --- /dev/null +++ b/source3/libsmb/libsmb_file.c @@ -0,0 +1,864 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Routine to open() a file ... + */ + +SMBCFILE * +SMBC_open_ctx(SMBCCTX *context, + const char *fname, + int flags, + mode_t mode) +{ + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + SMBCSRV *srv = NULL; + SMBCFILE *file = NULL; + int fd; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return NULL; + + } + + if (!fname) { + + errno = EINVAL; + TALLOC_FREE(frame); + return NULL; + + } + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return NULL; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + if (errno == EPERM) errno = EACCES; + TALLOC_FREE(frame); + return NULL; /* SMBC_server sets errno */ + } + + /* Hmmm, the test for a directory is suspect here ... FIXME */ + + if (strlen(path) > 0 && path[strlen(path) - 1] == '\\') { + fd = -1; + } else { + file = SMB_MALLOC_P(SMBCFILE); + + if (!file) { + errno = ENOMEM; + TALLOC_FREE(frame); + return NULL; + } + + ZERO_STRUCTP(file); + + /*d_printf(">>>open: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + SAFE_FREE(file); + TALLOC_FREE(frame); + return NULL; + } + /*d_printf(">>>open: resolved %s as %s\n", path, targetpath);*/ + + if ((fd = cli_open(targetcli, targetpath, flags, + context->internal->share_mode)) < 0) { + + /* Handle the error ... */ + + SAFE_FREE(file); + errno = SMBC_errno(context, targetcli); + TALLOC_FREE(frame); + return NULL; + + } + + /* Fill in file struct */ + + file->cli_fd = fd; + file->fname = SMB_STRDUP(fname); + file->srv = srv; + file->offset = 0; + file->file = True; + + DLIST_ADD(context->internal->files, file); + + /* + * If the file was opened in O_APPEND mode, all write + * operations should be appended to the file. To do that, + * though, using this protocol, would require a getattrE() + * call for each and every write, to determine where the end + * of the file is. (There does not appear to be an append flag + * in the protocol.) Rather than add all of that overhead of + * retrieving the current end-of-file offset prior to each + * write operation, we'll assume that most append operations + * will continuously write, so we'll just set the offset to + * the end of the file now and hope that's adequate. + * + * Note to self: If this proves inadequate, and O_APPEND + * should, in some cases, be forced for each write, add a + * field in the context options structure, for + * "strict_append_mode" which would select between the current + * behavior (if FALSE) or issuing a getattrE() prior to each + * write and forcing the write to the end of the file (if + * TRUE). Adding that capability will likely require adding + * an "append" flag into the _SMBCFILE structure to track + * whether a file was opened in O_APPEND mode. -- djl + */ + if (flags & O_APPEND) { + if (SMBC_lseek_ctx(context, file, 0, SEEK_END) < 0) { + (void) SMBC_close_ctx(context, file); + errno = ENXIO; + TALLOC_FREE(frame); + return NULL; + } + } + + TALLOC_FREE(frame); + return file; + + } + + /* Check if opendir needed ... */ + + if (fd == -1) { + int eno = 0; + + eno = SMBC_errno(context, srv->cli); + file = smbc_getFunctionOpendir(context)(context, fname); + if (!file) errno = eno; + TALLOC_FREE(frame); + return file; + + } + + errno = EINVAL; /* FIXME, correct errno ? */ + TALLOC_FREE(frame); + return NULL; + +} + +/* + * Routine to create a file + */ + +SMBCFILE * +SMBC_creat_ctx(SMBCCTX *context, + const char *path, + mode_t mode) +{ + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + return NULL; + + } + + return SMBC_open_ctx(context, path, + O_WRONLY | O_CREAT | O_TRUNC, mode); +} + +/* + * Routine to read() a file ... + */ + +ssize_t +SMBC_read_ctx(SMBCCTX *context, + SMBCFILE *file, + void *buf, + size_t count) +{ + int ret; + char *server = NULL, *share = NULL, *user = NULL, *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + /* + * offset: + * + * Compiler bug (possibly) -- gcc (GCC) 3.3.5 (Debian 1:3.3.5-2) -- + * appears to pass file->offset (which is type off_t) differently than + * a local variable of type off_t. Using local variable "offset" in + * the call to cli_read() instead of file->offset fixes a problem + * retrieving data at an offset greater than 4GB. + */ + off_t offset; + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + DEBUG(4, ("smbc_read(%p, %d)\n", file, (int)count)); + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + + } + + offset = file->offset; + + /* Check that the buffer exists ... */ + + if (buf == NULL) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + /*d_printf(">>>read: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>read: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>fstat: resolved path as %s\n", targetpath);*/ + + ret = cli_read(targetcli, file->cli_fd, (char *)buf, offset, count); + + if (ret < 0) { + + errno = SMBC_errno(context, targetcli); + TALLOC_FREE(frame); + return -1; + + } + + file->offset += ret; + + DEBUG(4, (" --> %d\n", ret)); + + TALLOC_FREE(frame); + return ret; /* Success, ret bytes of data ... */ + +} + +/* + * Routine to write() a file ... + */ + +ssize_t +SMBC_write_ctx(SMBCCTX *context, + SMBCFILE *file, + const void *buf, + size_t count) +{ + int ret; + off_t offset; + char *server = NULL, *share = NULL, *user = NULL, *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + /* First check all pointers before dereferencing them */ + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + } + + /* Check that the buffer exists ... */ + + if (buf == NULL) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + offset = file->offset; /* See "offset" comment in SMBC_read_ctx() */ + + /*d_printf(">>>write: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>write: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>write: resolved path as %s\n", targetpath);*/ + + ret = cli_write(targetcli, file->cli_fd, + 0, (char *)buf, offset, count); + + if (ret <= 0) { + errno = SMBC_errno(context, targetcli); + TALLOC_FREE(frame); + return -1; + + } + + file->offset += ret; + + TALLOC_FREE(frame); + return ret; /* Success, 0 bytes of data ... */ +} + +/* + * Routine to close() a file ... + */ + +int +SMBC_close_ctx(SMBCCTX *context, + SMBCFILE *file) +{ + SMBCSRV *srv; + char *server = NULL, *share = NULL, *user = NULL, *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + } + + /* IS a dir ... */ + if (!file->file) { + TALLOC_FREE(frame); + return smbc_getFunctionClosedir(context)(context, file); + } + + /*d_printf(">>>close: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>close: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>close: resolved path as %s\n", targetpath);*/ + + if (!cli_close(targetcli, file->cli_fd)) { + + DEBUG(3, ("cli_close failed on %s. purging server.\n", + file->fname)); + /* Deallocate slot and remove the server + * from the server cache if unused */ + errno = SMBC_errno(context, targetcli); + srv = file->srv; + DLIST_REMOVE(context->internal->files, file); + SAFE_FREE(file->fname); + SAFE_FREE(file); + smbc_getFunctionRemoveUnusedServer(context)(context, srv); + TALLOC_FREE(frame); + return -1; + + } + + DLIST_REMOVE(context->internal->files, file); + SAFE_FREE(file->fname); + SAFE_FREE(file); + TALLOC_FREE(frame); + + return 0; +} + +/* + * Get info from an SMB server on a file. Use a qpathinfo call first + * and if that fails, use getatr, as Win95 sometimes refuses qpathinfo + */ +bool +SMBC_getatr(SMBCCTX * context, + SMBCSRV *srv, + char *path, + uint16 *mode, + SMB_OFF_T *size, + struct timespec *create_time_ts, + struct timespec *access_time_ts, + struct timespec *write_time_ts, + struct timespec *change_time_ts, + SMB_INO_T *ino) +{ + char *fixedpath = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + time_t write_time; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /* path fixup for . and .. */ + if (strequal(path, ".") || strequal(path, "..")) { + fixedpath = talloc_strdup(frame, "\\"); + if (!fixedpath) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } else { + fixedpath = talloc_strdup(frame, path); + if (!fixedpath) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + trim_string(fixedpath, NULL, "\\.."); + trim_string(fixedpath, NULL, "\\."); + } + DEBUG(4,("SMBC_getatr: sending qpathinfo\n")); + + if (!cli_resolve_path(frame, "", srv->cli, fixedpath, + &targetcli, &targetpath)) { + d_printf("Couldn't resolve %s\n", path); + TALLOC_FREE(frame); + return False; + } + + if (!srv->no_pathinfo2 && + cli_qpathinfo2(targetcli, targetpath, + create_time_ts, + access_time_ts, + write_time_ts, + change_time_ts, + size, mode, ino)) { + TALLOC_FREE(frame); + return True; + } + + /* if this is NT then don't bother with the getatr */ + if (targetcli->capabilities & CAP_NT_SMBS) { + errno = EPERM; + TALLOC_FREE(frame); + return False; + } + + if (cli_getatr(targetcli, targetpath, mode, size, &write_time)) { + + struct timespec w_time_ts; + + w_time_ts = convert_time_t_to_timespec(write_time); + + if (write_time_ts != NULL) { + *write_time_ts = w_time_ts; + } + + if (create_time_ts != NULL) { + *create_time_ts = w_time_ts; + } + + if (access_time_ts != NULL) { + *access_time_ts = w_time_ts; + } + + if (change_time_ts != NULL) { + *change_time_ts = w_time_ts; + } + + srv->no_pathinfo2 = True; + TALLOC_FREE(frame); + return True; + } + + errno = EPERM; + TALLOC_FREE(frame); + return False; + +} + +/* + * Set file info on an SMB server. Use setpathinfo call first. If that + * fails, use setattrE.. + * + * Access and modification time parameters are always used and must be + * provided. Create time, if zero, will be determined from the actual create + * time of the file. If non-zero, the create time will be set as well. + * + * "mode" (attributes) parameter may be set to -1 if it is not to be set. + */ +bool +SMBC_setatr(SMBCCTX * context, SMBCSRV *srv, char *path, + time_t create_time, + time_t access_time, + time_t write_time, + time_t change_time, + uint16 mode) +{ + int fd; + int ret; + TALLOC_CTX *frame = talloc_stackframe(); + + /* + * First, try setpathinfo (if qpathinfo succeeded), for it is the + * modern function for "new code" to be using, and it works given a + * filename rather than requiring that the file be opened to have its + * attributes manipulated. + */ + if (srv->no_pathinfo || + ! cli_setpathinfo(srv->cli, path, + create_time, + access_time, + write_time, + change_time, + mode)) { + + /* + * setpathinfo is not supported; go to plan B. + * + * cli_setatr() does not work on win98, and it also doesn't + * support setting the access time (only the modification + * time), so in all cases, we open the specified file and use + * cli_setattrE() which should work on all OS versions, and + * supports both times. + */ + + /* Don't try {q,set}pathinfo() again, with this server */ + srv->no_pathinfo = True; + + /* Open the file */ + if ((fd = cli_open(srv->cli, path, O_RDWR, DENY_NONE)) < 0) { + + errno = SMBC_errno(context, srv->cli); + TALLOC_FREE(frame); + return -1; + } + + /* Set the new attributes */ + ret = cli_setattrE(srv->cli, fd, + change_time, + access_time, + write_time); + + /* Close the file */ + cli_close(srv->cli, fd); + + /* + * Unfortunately, setattrE() doesn't have a provision for + * setting the access mode (attributes). We'll have to try + * cli_setatr() for that, and with only this parameter, it + * seems to work on win98. + */ + if (ret && mode != (uint16) -1) { + ret = cli_setatr(srv->cli, path, mode, 0); + } + + if (! ret) { + errno = SMBC_errno(context, srv->cli); + TALLOC_FREE(frame); + return False; + } + } + + TALLOC_FREE(frame); + return True; +} + +/* + * A routine to lseek() a file + */ + +off_t +SMBC_lseek_ctx(SMBCCTX *context, + SMBCFILE *file, + off_t offset, + int whence) +{ + SMB_OFF_T size; + char *server = NULL, *share = NULL, *user = NULL, *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + + errno = EBADF; + TALLOC_FREE(frame); + return -1; + + } + + if (!file->file) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; /* Can't lseek a dir ... */ + + } + + switch (whence) { + case SEEK_SET: + file->offset = offset; + break; + + case SEEK_CUR: + file->offset += offset; + break; + + case SEEK_END: + /*d_printf(">>>lseek: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>lseek: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>lseek: resolved path as %s\n", targetpath);*/ + + if (!cli_qfileinfo(targetcli, file->cli_fd, NULL, + &size, NULL, NULL, NULL, NULL, NULL)) + { + SMB_OFF_T b_size = size; + if (!cli_getattrE(targetcli, file->cli_fd, + NULL, &b_size, NULL, NULL, NULL)) + { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } else + size = b_size; + } + file->offset = size + offset; + break; + + default: + errno = EINVAL; + break; + + } + + TALLOC_FREE(frame); + return file->offset; + +} + + +/* + * Routine to truncate a file given by its file descriptor, to a specified size + */ + +int +SMBC_ftruncate_ctx(SMBCCTX *context, + SMBCFILE *file, + off_t length) +{ + SMB_OFF_T size = length; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + } + + if (!file->file) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>fstat: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>fstat: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>fstat: resolved path as %s\n", targetpath);*/ + + if (!cli_ftruncate(targetcli, file->cli_fd, size)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + TALLOC_FREE(frame); + return 0; + +} diff --git a/source3/libsmb/libsmb_misc.c b/source3/libsmb/libsmb_misc.c new file mode 100644 index 0000000000..dd7add5a61 --- /dev/null +++ b/source3/libsmb/libsmb_misc.c @@ -0,0 +1,73 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * check if an element is part of the list. + */ +int +SMBC_dlist_contains(SMBCFILE * list, SMBCFILE *p) +{ + if (!p || !list) return False; + do { + if (p == list) return True; + list = list->next; + } while (list); + return False; +} + + +/* + * Convert an SMB error into a UNIX error ... + */ +int +SMBC_errno(SMBCCTX *context, + struct cli_state *c) +{ + int ret = cli_errno(c); + + if (cli_is_dos_error(c)) { + uint8 eclass; + uint32 ecode; + + cli_dos_error(c, &eclass, &ecode); + + DEBUG(3,("smbc_error %d %d (0x%x) -> %d\n", + (int)eclass, (int)ecode, (int)ecode, ret)); + } else { + NTSTATUS status; + + status = cli_nt_error(c); + + DEBUG(3,("smbc errno %s -> %d\n", + nt_errstr(status), ret)); + } + + return ret; +} + diff --git a/source3/libsmb/libsmb_path.c b/source3/libsmb/libsmb_path.c new file mode 100644 index 0000000000..2c3a5f8866 --- /dev/null +++ b/source3/libsmb/libsmb_path.c @@ -0,0 +1,400 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* Used by urldecode_talloc() */ +static int +hex2int( unsigned int _char ) +{ + if ( _char >= 'A' && _char <='F') + return _char - 'A' + 10; + if ( _char >= 'a' && _char <='f') + return _char - 'a' + 10; + if ( _char >= '0' && _char <='9') + return _char - '0'; + return -1; +} + +/* + * SMBC_urldecode() + * and urldecode_talloc() (internal fn.) + * + * Convert strings of %xx to their single character equivalent. Each 'x' must + * be a valid hexadecimal digit, or that % sequence is left undecoded. + * + * dest may, but need not be, the same pointer as src. + * + * Returns the number of % sequences which could not be converted due to lack + * of two following hexadecimal digits. + */ +static int +urldecode_talloc(TALLOC_CTX *ctx, char **pp_dest, const char *src) +{ + int old_length = strlen(src); + int i = 0; + int err_count = 0; + size_t newlen = 1; + char *p, *dest; + + if (old_length == 0) { + return 0; + } + + *pp_dest = NULL; + for (i = 0; i < old_length; ) { + unsigned char character = src[i++]; + + if (character == '%') { + int a = i+1 < old_length ? hex2int(src[i]) : -1; + int b = i+1 < old_length ? hex2int(src[i+1]) : -1; + + /* Replace valid sequence */ + if (a != -1 && b != -1) { + /* Replace valid %xx sequence with %dd */ + character = (a * 16) + b; + if (character == '\0') { + break; /* Stop at %00 */ + } + i += 2; + } else { + err_count++; + } + } + newlen++; + } + + dest = TALLOC_ARRAY(ctx, char, newlen); + if (!dest) { + return err_count; + } + + err_count = 0; + for (p = dest, i = 0; i < old_length; ) { + unsigned char character = src[i++]; + + if (character == '%') { + int a = i+1 < old_length ? hex2int(src[i]) : -1; + int b = i+1 < old_length ? hex2int(src[i+1]) : -1; + + /* Replace valid sequence */ + if (a != -1 && b != -1) { + /* Replace valid %xx sequence with %dd */ + character = (a * 16) + b; + if (character == '\0') { + break; /* Stop at %00 */ + } + i += 2; + } else { + err_count++; + } + } + *p++ = character; + } + + *p = '\0'; + *pp_dest = dest; + return err_count; +} + +int +SMBC_urldecode(char *dest, + char *src, + size_t max_dest_len) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *pdest; + int ret = urldecode_talloc(frame, &pdest, src); + + if (pdest) { + strlcpy(dest, pdest, max_dest_len); + } + TALLOC_FREE(frame); + return ret; +} + +/* + * SMBC_urlencode() + * + * Convert any characters not specifically allowed in a URL into their %xx + * equivalent. + * + * Returns the remaining buffer length. + */ +int +SMBC_urlencode(char *dest, + char *src, + int max_dest_len) +{ + char hex[] = "0123456789ABCDEF"; + + for (; *src != '\0' && max_dest_len >= 3; src++) { + + if ((*src < '0' && + *src != '-' && + *src != '.') || + (*src > '9' && + *src < 'A') || + (*src > 'Z' && + *src < 'a' && + *src != '_') || + (*src > 'z')) { + *dest++ = '%'; + *dest++ = hex[(*src >> 4) & 0x0f]; + *dest++ = hex[*src & 0x0f]; + max_dest_len -= 3; + } else { + *dest++ = *src; + max_dest_len--; + } + } + + *dest++ = '\0'; + max_dest_len--; + + return max_dest_len; +} + +/* + * Function to parse a path and turn it into components + * + * The general format of an SMB URI is explain in Christopher Hertel's CIFS + * book, at http://ubiqx.org/cifs/Appendix-D.html. We accept a subset of the + * general format ("smb:" only; we do not look for "cifs:"). + * + * + * We accept: + * smb://[[[domain;]user[:password]@]server[/share[/path[/file]]]][?options] + * + * Meaning of URLs: + * + * smb:// Show all workgroups. + * + * The method of locating the list of workgroups varies + * depending upon the setting of the context variable + * context->options.browse_max_lmb_count. This value + * determines the maximum number of local master browsers to + * query for the list of workgroups. In order to ensure that + * a complete list of workgroups is obtained, all master + * browsers must be queried, but if there are many + * workgroups, the time spent querying can begin to add up. + * For small networks (not many workgroups), it is suggested + * that this variable be set to 0, indicating query all local + * master browsers. When the network has many workgroups, a + * reasonable setting for this variable might be around 3. + * + * smb://name/ if name<1D> or name<1B> exists, list servers in + * workgroup, else, if name<20> exists, list all shares + * for server ... + * + * If "options" are provided, this function returns the entire option list as a + * string, for later parsing by the caller. Note that currently, no options + * are supported. + */ + +static const char *smbc_prefix = "smb:"; + +int +SMBC_parse_path(TALLOC_CTX *ctx, + SMBCCTX *context, + const char *fname, + char **pp_workgroup, + char **pp_server, + char **pp_share, + char **pp_path, + char **pp_user, + char **pp_password, + char **pp_options) +{ + char *s; + const char *p; + char *q, *r; + int len; + + /* Ensure these returns are at least valid pointers. */ + *pp_server = talloc_strdup(ctx, ""); + *pp_share = talloc_strdup(ctx, ""); + *pp_path = talloc_strdup(ctx, ""); + *pp_user = talloc_strdup(ctx, ""); + *pp_password = talloc_strdup(ctx, ""); + + if (!*pp_server || !*pp_share || !*pp_path || + !*pp_user || !*pp_password) { + return -1; + } + + /* + * Assume we wont find an authentication domain to parse, so default + * to the workgroup in the provided context. + */ + if (pp_workgroup != NULL) { + *pp_workgroup = + talloc_strdup(ctx, smbc_getWorkgroup(context)); + } + + if (pp_options) { + *pp_options = talloc_strdup(ctx, ""); + } + s = talloc_strdup(ctx, fname); + + /* see if it has the right prefix */ + len = strlen(smbc_prefix); + if (strncmp(s,smbc_prefix,len) || (s[len] != '/' && s[len] != 0)) { + return -1; /* What about no smb: ? */ + } + + p = s + len; + + /* Watch the test below, we are testing to see if we should exit */ + + if (strncmp(p, "//", 2) && strncmp(p, "\\\\", 2)) { + DEBUG(1, ("Invalid path (does not begin with smb://")); + return -1; + } + + p += 2; /* Skip the double slash */ + + /* See if any options were specified */ + if ((q = strrchr(p, '?')) != NULL ) { + /* There are options. Null terminate here and point to them */ + *q++ = '\0'; + + DEBUG(4, ("Found options '%s'", q)); + + /* Copy the options */ + if (*pp_options != NULL) { + TALLOC_FREE(*pp_options); + *pp_options = talloc_strdup(ctx, q); + } + } + + if (*p == '\0') { + goto decoding; + } + + if (*p == '/') { + int wl = strlen(smbc_getWorkgroup(context)); + + if (wl > 16) { + wl = 16; + } + + *pp_server = talloc_strdup(ctx, smbc_getWorkgroup(context)); + if (!*pp_server) { + return -1; + } + *pp_server[wl] = '\0'; + return 0; + } + + /* + * ok, its for us. Now parse out the server, share etc. + * + * However, we want to parse out [[domain;]user[:password]@] if it + * exists ... + */ + + /* check that '@' occurs before '/', if '/' exists at all */ + q = strchr_m(p, '@'); + r = strchr_m(p, '/'); + if (q && (!r || q < r)) { + char *userinfo = NULL; + const char *u; + + next_token_no_ltrim_talloc(ctx, &p, &userinfo, "@"); + if (!userinfo) { + return -1; + } + u = userinfo; + + if (strchr_m(u, ';')) { + char *workgroup; + next_token_no_ltrim_talloc(ctx, &u, &workgroup, ";"); + if (!workgroup) { + return -1; + } + if (pp_workgroup) { + *pp_workgroup = workgroup; + } + } + + if (strchr_m(u, ':')) { + next_token_no_ltrim_talloc(ctx, &u, pp_user, ":"); + if (!*pp_user) { + return -1; + } + *pp_password = talloc_strdup(ctx, u); + if (!*pp_password) { + return -1; + } + } else { + *pp_user = talloc_strdup(ctx, u); + if (!*pp_user) { + return -1; + } + } + } + + if (!next_token_talloc(ctx, &p, pp_server, "/")) { + return -1; + } + + if (*p == (char)0) { + goto decoding; /* That's it ... */ + } + + if (!next_token_talloc(ctx, &p, pp_share, "/")) { + return -1; + } + + /* + * Prepend a leading slash if there's a file path, as required by + * NetApp filers. + */ + if (*p != '\0') { + *pp_path = talloc_asprintf(ctx, + "\\%s", + p); + } else { + *pp_path = talloc_strdup(ctx, ""); + } + if (!*pp_path) { + return -1; + } + string_replace(*pp_path, '/', '\\'); + +decoding: + + (void) urldecode_talloc(ctx, pp_path, *pp_path); + (void) urldecode_talloc(ctx, pp_server, *pp_server); + (void) urldecode_talloc(ctx, pp_share, *pp_share); + (void) urldecode_talloc(ctx, pp_user, *pp_user); + (void) urldecode_talloc(ctx, pp_password, *pp_password); + + return 0; +} + diff --git a/source3/libsmb/libsmb_printjob.c b/source3/libsmb/libsmb_printjob.c new file mode 100644 index 0000000000..c8d7ad039d --- /dev/null +++ b/source3/libsmb/libsmb_printjob.c @@ -0,0 +1,336 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Open a print file to be written to by other calls + */ + +SMBCFILE * +SMBC_open_print_job_ctx(SMBCCTX *context, + const char *fname) +{ + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *path = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return NULL; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return NULL; + } + + DEBUG(4, ("SMBC_open_print_job_ctx(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return NULL; + } + + /* What if the path is empty, or the file exists? */ + + TALLOC_FREE(frame); + return smbc_getFunctionOpen(context)(context, fname, O_WRONLY, 666); +} + +/* + * Routine to print a file on a remote server ... + * + * We open the file, which we assume to be on a remote server, and then + * copy it to a print file on the share specified by printq. + */ + +int +SMBC_print_file_ctx(SMBCCTX *c_file, + const char *fname, + SMBCCTX *c_print, + const char *printq) +{ + SMBCFILE *fid1; + SMBCFILE *fid2; + int bytes; + int saverr; + int tot_bytes = 0; + char buf[4096]; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!c_file || !c_file->internal->initialized || + !c_print || !c_print->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + if (!fname && !printq) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + /* Try to open the file for reading ... */ + + if ((long)(fid1 = smbc_getFunctionOpen(c_file)(c_file, fname, + O_RDONLY, 0666)) < 0) { + DEBUG(3, ("Error, fname=%s, errno=%i\n", fname, errno)); + TALLOC_FREE(frame); + return -1; /* smbc_open sets errno */ + } + + /* Now, try to open the printer file for writing */ + + if ((long)(fid2 = smbc_getFunctionOpenPrintJob(c_print)(c_print, + printq)) < 0) { + + saverr = errno; /* Save errno */ + smbc_getFunctionClose(c_file)(c_file, fid1); + errno = saverr; + TALLOC_FREE(frame); + return -1; + + } + + while ((bytes = smbc_getFunctionRead(c_file)(c_file, fid1, + buf, sizeof(buf))) > 0) { + + tot_bytes += bytes; + + if ((smbc_getFunctionWrite(c_print)(c_print, fid2, + buf, bytes)) < 0) { + + saverr = errno; + smbc_getFunctionClose(c_file)(c_file, fid1); + smbc_getFunctionClose(c_print)(c_print, fid2); + errno = saverr; + + } + + } + + saverr = errno; + + smbc_getFunctionClose(c_file)(c_file, fid1); + smbc_getFunctionClose(c_print)(c_print, fid2); + + if (bytes < 0) { + + errno = saverr; + TALLOC_FREE(frame); + return -1; + + } + + TALLOC_FREE(frame); + return tot_bytes; + +} + +/* + * Routine to list print jobs on a printer share ... + */ + +int +SMBC_list_print_jobs_ctx(SMBCCTX *context, + const char *fname, + smbc_list_print_job_fn fn) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_list_print_jobs(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (cli_print_queue(srv->cli, + (void (*)(struct print_job_info *))fn) < 0) { + errno = SMBC_errno(context, srv->cli); + TALLOC_FREE(frame); + return -1; + } + + TALLOC_FREE(frame); + return 0; + +} + +/* + * Delete a print job from a remote printer share + */ + +int +SMBC_unlink_print_job_ctx(SMBCCTX *context, + const char *fname, + int id) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + int err; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_unlink_print_job(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + + } + + if ((err = cli_printjob_del(srv->cli, id)) != 0) { + + if (err < 0) + errno = SMBC_errno(context, srv->cli); + else if (err == ERRnosuchprintjob) + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + + } + + TALLOC_FREE(frame); + return 0; + +} + diff --git a/source3/libsmb/libsmb_server.c b/source3/libsmb/libsmb_server.c new file mode 100644 index 0000000000..aeec255350 --- /dev/null +++ b/source3/libsmb/libsmb_server.c @@ -0,0 +1,699 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Check a server for being alive and well. + * returns 0 if the server is in shape. Returns 1 on error + * + * Also useable outside libsmbclient to enable external cache + * to do some checks too. + */ +int +SMBC_check_server(SMBCCTX * context, + SMBCSRV * server) +{ + socklen_t size; + struct sockaddr addr; + + size = sizeof(addr); + return (getpeername(server->cli->fd, &addr, &size) == -1); +} + +/* + * Remove a server from the cached server list it's unused. + * On success, 0 is returned. 1 is returned if the server could not be removed. + * + * Also useable outside libsmbclient + */ +int +SMBC_remove_unused_server(SMBCCTX * context, + SMBCSRV * srv) +{ + SMBCFILE * file; + + /* are we being fooled ? */ + if (!context || !context->internal->initialized || !srv) { + return 1; + } + + /* Check all open files/directories for a relation with this server */ + for (file = context->internal->files; file; file = file->next) { + if (file->srv == srv) { + /* Still used */ + DEBUG(3, ("smbc_remove_usused_server: " + "%p still used by %p.\n", + srv, file)); + return 1; + } + } + + DLIST_REMOVE(context->internal->servers, srv); + + cli_shutdown(srv->cli); + srv->cli = NULL; + + DEBUG(3, ("smbc_remove_usused_server: %p removed.\n", srv)); + + smbc_getFunctionRemoveCachedServer(context)(context, srv); + + SAFE_FREE(srv); + return 0; +} + +/**************************************************************** + * Call the auth_fn with fixed size (fstring) buffers. + ***************************************************************/ +void +SMBC_call_auth_fn(TALLOC_CTX *ctx, + SMBCCTX *context, + const char *server, + const char *share, + char **pp_workgroup, + char **pp_username, + char **pp_password) +{ + fstring workgroup; + fstring username; + fstring password; + smbc_get_auth_data_with_context_fn auth_with_context_fn; + + strlcpy(workgroup, *pp_workgroup, sizeof(workgroup)); + strlcpy(username, *pp_username, sizeof(username)); + strlcpy(password, *pp_password, sizeof(password)); + + /* See if there's an authentication with context function provided */ + auth_with_context_fn = smbc_getFunctionAuthDataWithContext(context); + if (auth_with_context_fn) + { + (* auth_with_context_fn)(context, + server, share, + workgroup, sizeof(workgroup), + username, sizeof(username), + password, sizeof(password)); + } + else + { + smbc_getFunctionAuthData(context)(server, share, + workgroup, sizeof(workgroup), + username, sizeof(username), + password, sizeof(password)); + } + + TALLOC_FREE(*pp_workgroup); + TALLOC_FREE(*pp_username); + TALLOC_FREE(*pp_password); + + *pp_workgroup = talloc_strdup(ctx, workgroup); + *pp_username = talloc_strdup(ctx, username); + *pp_password = talloc_strdup(ctx, password); +} + + +void +SMBC_get_auth_data(const char *server, const char *share, + char *workgroup_buf, int workgroup_buf_len, + char *username_buf, int username_buf_len, + char *password_buf, int password_buf_len) +{ + /* Default function just uses provided data. Nothing to do. */ +} + + + +SMBCSRV * +SMBC_find_server(TALLOC_CTX *ctx, + SMBCCTX *context, + const char *server, + const char *share, + char **pp_workgroup, + char **pp_username, + char **pp_password) +{ + SMBCSRV *srv; + int auth_called = 0; + + if (!pp_workgroup || !pp_username || !pp_password) { + return NULL; + } + +check_server_cache: + + srv = smbc_getFunctionGetCachedServer(context)(context, + server, share, + *pp_workgroup, + *pp_username); + + if (!auth_called && !srv && (!*pp_username || !(*pp_username)[0] || + !*pp_password || !(*pp_password)[0])) { + SMBC_call_auth_fn(ctx, context, server, share, + pp_workgroup, pp_username, pp_password); + + /* + * However, smbc_auth_fn may have picked up info relating to + * an existing connection, so try for an existing connection + * again ... + */ + auth_called = 1; + goto check_server_cache; + + } + + if (srv) { + if (smbc_getFunctionCheckServer(context)(context, srv)) { + /* + * This server is no good anymore + * Try to remove it and check for more possible + * servers in the cache + */ + if (smbc_getFunctionRemoveUnusedServer(context)(context, + srv)) { + /* + * We could not remove the server completely, + * remove it from the cache so we will not get + * it again. It will be removed when the last + * file/dir is closed. + */ + smbc_getFunctionRemoveCachedServer(context)(context, + srv); + } + + /* + * Maybe there are more cached connections to this + * server + */ + goto check_server_cache; + } + + return srv; + } + + return NULL; +} + +/* + * Connect to a server, possibly on an existing connection + * + * Here, what we want to do is: If the server and username + * match an existing connection, reuse that, otherwise, establish a + * new connection. + * + * If we have to create a new connection, call the auth_fn to get the + * info we need, unless the username and password were passed in. + */ + +SMBCSRV * +SMBC_server(TALLOC_CTX *ctx, + SMBCCTX *context, + bool connect_if_not_found, + const char *server, + const char *share, + char **pp_workgroup, + char **pp_username, + char **pp_password) +{ + SMBCSRV *srv=NULL; + struct cli_state *c; + struct nmb_name called, calling; + const char *server_n = server; + struct sockaddr_storage ss; + int tried_reverse = 0; + int port_try_first; + int port_try_next; + const char *username_used; + NTSTATUS status; + + zero_addr(&ss); + ZERO_STRUCT(c); + + if (server[0] == 0) { + errno = EPERM; + return NULL; + } + + /* Look for a cached connection */ + srv = SMBC_find_server(ctx, context, server, share, + pp_workgroup, pp_username, pp_password); + + /* + * If we found a connection and we're only allowed one share per + * server... + */ + if (srv && + *share != '\0' && + smbc_getOptionOneSharePerServer(context)) { + + /* + * ... then if there's no current connection to the share, + * connect to it. SMBC_find_server(), or rather the function + * pointed to by context->get_cached_srv_fn which + * was called by SMBC_find_server(), will have issued a tree + * disconnect if the requested share is not the same as the + * one that was already connected. + */ + if (srv->cli->cnum == (uint16) -1) { + /* Ensure we have accurate auth info */ + SMBC_call_auth_fn(ctx, context, server, share, + pp_workgroup, + pp_username, + pp_password); + + if (!*pp_workgroup || !*pp_username || !*pp_password) { + errno = ENOMEM; + cli_shutdown(srv->cli); + srv->cli = NULL; + smbc_getFunctionRemoveCachedServer(context)(context, + srv); + return NULL; + } + + /* + * We don't need to renegotiate encryption + * here as the encryption context is not per + * tid. + */ + + if (!cli_send_tconX(srv->cli, share, "?????", + *pp_password, + strlen(*pp_password)+1)) { + + errno = SMBC_errno(context, srv->cli); + cli_shutdown(srv->cli); + srv->cli = NULL; + smbc_getFunctionRemoveCachedServer(context)(context, + srv); + srv = NULL; + } + + /* + * Regenerate the dev value since it's based on both + * server and share + */ + if (srv) { + srv->dev = (dev_t)(str_checksum(server) ^ + str_checksum(share)); + } + } + } + + /* If we have a connection... */ + if (srv) { + + /* ... then we're done here. Give 'em what they came for. */ + return srv; + } + + /* If we're not asked to connect when a connection doesn't exist... */ + if (! connect_if_not_found) { + /* ... then we're done here. */ + return NULL; + } + + if (!*pp_workgroup || !*pp_username || !*pp_password) { + errno = ENOMEM; + return NULL; + } + + make_nmb_name(&calling, smbc_getNetbiosName(context), 0x0); + make_nmb_name(&called , server, 0x20); + + DEBUG(4,("SMBC_server: server_n=[%s] server=[%s]\n", server_n, server)); + + DEBUG(4,(" -> server_n=[%s] server=[%s]\n", server_n, server)); + +again: + + zero_addr(&ss); + + /* have to open a new connection */ + if ((c = cli_initialise()) == NULL) { + errno = ENOMEM; + return NULL; + } + + if (smbc_getOptionUseKerberos(context)) { + c->use_kerberos = True; + } + + if (smbc_getOptionFallbackAfterKerberos(context)) { + c->fallback_after_kerberos = True; + } + + c->timeout = smbc_getTimeout(context); + + /* + * Force use of port 139 for first try if share is $IPC, empty, or + * null, so browse lists can work + */ + if (share == NULL || *share == '\0' || strcmp(share, "IPC$") == 0) { + port_try_first = 139; + port_try_next = 445; + } else { + port_try_first = 445; + port_try_next = 139; + } + + c->port = port_try_first; + + status = cli_connect(c, server_n, &ss); + if (!NT_STATUS_IS_OK(status)) { + + /* First connection attempt failed. Try alternate port. */ + c->port = port_try_next; + + status = cli_connect(c, server_n, &ss); + if (!NT_STATUS_IS_OK(status)) { + cli_shutdown(c); + errno = ETIMEDOUT; + return NULL; + } + } + + if (!cli_session_request(c, &calling, &called)) { + cli_shutdown(c); + if (strcmp(called.name, "*SMBSERVER")) { + make_nmb_name(&called , "*SMBSERVER", 0x20); + goto again; + } else { /* Try one more time, but ensure we don't loop */ + + /* Only try this if server is an IP address ... */ + + if (is_ipaddress(server) && !tried_reverse) { + fstring remote_name; + struct sockaddr_storage rem_ss; + + if (!interpret_string_addr(&rem_ss, server, + NI_NUMERICHOST)) { + DEBUG(4, ("Could not convert IP address " + "%s to struct sockaddr_storage\n", + server)); + errno = ETIMEDOUT; + return NULL; + } + + tried_reverse++; /* Yuck */ + + if (name_status_find("*", 0, 0, + &rem_ss, remote_name)) { + make_nmb_name(&called, + remote_name, + 0x20); + goto again; + } + } + } + errno = ETIMEDOUT; + return NULL; + } + + DEBUG(4,(" session request ok\n")); + + if (!cli_negprot(c)) { + cli_shutdown(c); + errno = ETIMEDOUT; + return NULL; + } + + username_used = *pp_username; + + if (!NT_STATUS_IS_OK(cli_session_setup(c, username_used, + *pp_password, + strlen(*pp_password), + *pp_password, + strlen(*pp_password), + *pp_workgroup))) { + + /* Failed. Try an anonymous login, if allowed by flags. */ + username_used = ""; + + if (smbc_getOptionNoAutoAnonymousLogin(context) || + !NT_STATUS_IS_OK(cli_session_setup(c, username_used, + *pp_password, 1, + *pp_password, 0, + *pp_workgroup))) { + + cli_shutdown(c); + errno = EPERM; + return NULL; + } + } + + DEBUG(4,(" session setup ok\n")); + + if (!cli_send_tconX(c, share, "?????", + *pp_password, strlen(*pp_password)+1)) { + errno = SMBC_errno(context, c); + cli_shutdown(c); + return NULL; + } + + DEBUG(4,(" tconx ok\n")); + + if (context->internal->smb_encryption_level) { + /* Attempt UNIX smb encryption. */ + if (!NT_STATUS_IS_OK(cli_force_encryption(c, + username_used, + *pp_password, + *pp_workgroup))) { + + /* + * context->smb_encryption_level == 1 + * means don't fail if encryption can't be negotiated, + * == 2 means fail if encryption can't be negotiated. + */ + + DEBUG(4,(" SMB encrypt failed\n")); + + if (context->internal->smb_encryption_level == 2) { + cli_shutdown(c); + errno = EPERM; + return NULL; + } + } + DEBUG(4,(" SMB encrypt ok\n")); + } + + /* + * Ok, we have got a nice connection + * Let's allocate a server structure. + */ + + srv = SMB_MALLOC_P(SMBCSRV); + if (!srv) { + errno = ENOMEM; + goto failed; + } + + ZERO_STRUCTP(srv); + srv->cli = c; + srv->dev = (dev_t)(str_checksum(server) ^ str_checksum(share)); + srv->no_pathinfo = False; + srv->no_pathinfo2 = False; + srv->no_nt_session = False; + + /* now add it to the cache (internal or external) */ + /* Let the cache function set errno if it wants to */ + errno = 0; + if (smbc_getFunctionAddCachedServer(context)(context, srv, + server, share, + *pp_workgroup, + *pp_username)) { + int saved_errno = errno; + DEBUG(3, (" Failed to add server to cache\n")); + errno = saved_errno; + if (errno == 0) { + errno = ENOMEM; + } + goto failed; + } + + DEBUG(2, ("Server connect ok: //%s/%s: %p\n", + server, share, srv)); + + DLIST_ADD(context->internal->servers, srv); + return srv; + +failed: + cli_shutdown(c); + if (!srv) { + return NULL; + } + + SAFE_FREE(srv); + return NULL; +} + +/* + * Connect to a server for getting/setting attributes, possibly on an existing + * connection. This works similarly to SMBC_server(). + */ +SMBCSRV * +SMBC_attr_server(TALLOC_CTX *ctx, + SMBCCTX *context, + const char *server, + const char *share, + char **pp_workgroup, + char **pp_username, + char **pp_password) +{ + int flags; + struct sockaddr_storage ss; + struct cli_state *ipc_cli; + struct rpc_pipe_client *pipe_hnd; + NTSTATUS nt_status; + SMBCSRV *ipc_srv=NULL; + + /* + * See if we've already created this special connection. Reference + * our "special" share name '*IPC$', which is an impossible real share + * name due to the leading asterisk. + */ + ipc_srv = SMBC_find_server(ctx, context, server, "*IPC$", + pp_workgroup, pp_username, pp_password); + if (!ipc_srv) { + + /* We didn't find a cached connection. Get the password */ + if (!*pp_password || (*pp_password)[0] == '\0') { + /* ... then retrieve it now. */ + SMBC_call_auth_fn(ctx, context, server, share, + pp_workgroup, + pp_username, + pp_password); + if (!*pp_workgroup || !*pp_username || !*pp_password) { + errno = ENOMEM; + return NULL; + } + } + + flags = 0; + if (smbc_getOptionUseKerberos(context)) { + flags |= CLI_FULL_CONNECTION_USE_KERBEROS; + } + + zero_addr(&ss); + nt_status = cli_full_connection(&ipc_cli, + global_myname(), server, + &ss, 0, "IPC$", "?????", + *pp_username, + *pp_workgroup, + *pp_password, + flags, + Undefined, NULL); + if (! NT_STATUS_IS_OK(nt_status)) { + DEBUG(1,("cli_full_connection failed! (%s)\n", + nt_errstr(nt_status))); + errno = ENOTSUP; + return NULL; + } + + if (context->internal->smb_encryption_level) { + /* Attempt UNIX smb encryption. */ + if (!NT_STATUS_IS_OK(cli_force_encryption(ipc_cli, + *pp_username, + *pp_password, + *pp_workgroup))) { + + /* + * context->smb_encryption_level == + * 1 means don't fail if encryption can't be + * negotiated, == 2 means fail if encryption + * can't be negotiated. + */ + + DEBUG(4,(" SMB encrypt failed on IPC$\n")); + + if (context->internal->smb_encryption_level == 2) { + cli_shutdown(ipc_cli); + errno = EPERM; + return NULL; + } + } + DEBUG(4,(" SMB encrypt ok on IPC$\n")); + } + + ipc_srv = SMB_MALLOC_P(SMBCSRV); + if (!ipc_srv) { + errno = ENOMEM; + cli_shutdown(ipc_cli); + return NULL; + } + + ZERO_STRUCTP(ipc_srv); + ipc_srv->cli = ipc_cli; + + nt_status = cli_rpc_pipe_open_noauth( + ipc_srv->cli, &ndr_table_lsarpc.syntax_id, &pipe_hnd); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("cli_nt_session_open fail!\n")); + errno = ENOTSUP; + cli_shutdown(ipc_srv->cli); + free(ipc_srv); + return NULL; + } + + /* + * Some systems don't support + * SEC_RIGHTS_MAXIMUM_ALLOWED, but NT sends 0x2000000 + * so we might as well do it too. + */ + + nt_status = rpccli_lsa_open_policy( + pipe_hnd, + talloc_tos(), + True, + GENERIC_EXECUTE_ACCESS, + &ipc_srv->pol); + + if (!NT_STATUS_IS_OK(nt_status)) { + errno = SMBC_errno(context, ipc_srv->cli); + cli_shutdown(ipc_srv->cli); + return NULL; + } + + /* now add it to the cache (internal or external) */ + + errno = 0; /* let cache function set errno if it likes */ + if (smbc_getFunctionAddCachedServer(context)(context, ipc_srv, + server, + "*IPC$", + *pp_workgroup, + *pp_username)) { + DEBUG(3, (" Failed to add server to cache\n")); + if (errno == 0) { + errno = ENOMEM; + } + cli_shutdown(ipc_srv->cli); + free(ipc_srv); + return NULL; + } + + DLIST_ADD(context->internal->servers, ipc_srv); + } + + return ipc_srv; +} diff --git a/source3/libsmb/libsmb_setget.c b/source3/libsmb/libsmb_setget.c new file mode 100644 index 0000000000..d0823bd77e --- /dev/null +++ b/source3/libsmb/libsmb_setget.c @@ -0,0 +1,905 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#define __LIBSMBCLIENT_INTERNAL__ +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/** Get the netbios name used for making connections */ +char * +smbc_getNetbiosName(SMBCCTX *c) +{ + return c->netbios_name; +} + +/** Set the netbios name used for making connections */ +void +smbc_setNetbiosName(SMBCCTX *c, char * netbios_name) +{ + c->netbios_name = netbios_name; +} + +/** Get the workgroup used for making connections */ +char * +smbc_getWorkgroup(SMBCCTX *c) +{ + return c->workgroup; +} + +/** Set the workgroup used for making connections */ +void +smbc_setWorkgroup(SMBCCTX *c, char * workgroup) +{ + c->workgroup = workgroup; +} + +/** Get the username used for making connections */ +char * +smbc_getUser(SMBCCTX *c) +{ + return c->user; +} + +/** Set the username used for making connections */ +void +smbc_setUser(SMBCCTX *c, char * user) +{ + c->user = user; +} + +/** Get the debug level */ +int +smbc_getDebug(SMBCCTX *c) +{ + return c->debug; +} + +/** Set the debug level */ +void +smbc_setDebug(SMBCCTX *c, int debug) +{ + c->debug = debug; + DEBUGLEVEL = debug; +} + +/** + * Get the timeout used for waiting on connections and response data + * (in milliseconds) + */ +int +smbc_getTimeout(SMBCCTX *c) +{ + return c->timeout; +} + +/** + * Set the timeout used for waiting on connections and response data + * (in milliseconds) + */ +void +smbc_setTimeout(SMBCCTX *c, int timeout) +{ + c->timeout = timeout; +} + +/** Get whether to log to standard error instead of standard output */ +smbc_bool +smbc_getOptionDebugToStderr(SMBCCTX *c) +{ + return c->internal->debug_stderr; +} + +/** Set whether to log to standard error instead of standard output */ +void +smbc_setOptionDebugToStderr(SMBCCTX *c, smbc_bool b) +{ + c->internal->debug_stderr = b; +} + +/** + * Get whether to use new-style time attribute names, e.g. WRITE_TIME rather + * than the old-style names such as M_TIME. This allows also setting/getting + * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME + * was supposed to be CHANGE_TIME but was confused and sometimes referred to + * CREATE_TIME.) + */ +smbc_bool +smbc_getOptionFullTimeNames(SMBCCTX *c) +{ + return c->internal->full_time_names; +} + +/** + * Set whether to use new-style time attribute names, e.g. WRITE_TIME rather + * than the old-style names such as M_TIME. This allows also setting/getting + * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME + * was supposed to be CHANGE_TIME but was confused and sometimes referred to + * CREATE_TIME.) + */ +void +smbc_setOptionFullTimeNames(SMBCCTX *c, smbc_bool b) +{ + c->internal->full_time_names = b; +} + +/** + * Get the share mode to use for files opened with SMBC_open_ctx(). The + * default is SMBC_SHAREMODE_DENY_NONE. + */ +smbc_share_mode +smbc_getOptionOpenShareMode(SMBCCTX *c) +{ + return c->internal->share_mode; +} + +/** + * Set the share mode to use for files opened with SMBC_open_ctx(). The + * default is SMBC_SHAREMODE_DENY_NONE. + */ +void +smbc_setOptionOpenShareMode(SMBCCTX *c, smbc_share_mode share_mode) +{ + c->internal->share_mode = share_mode; +} + +/** Retrieve a previously set user data handle */ +void * +smbc_getOptionUserData(SMBCCTX *c) +{ + return c->internal->user_data; +} + +/** Save a user data handle */ +void +smbc_setOptionUserData(SMBCCTX *c, void *user_data) +{ + c->internal->user_data = user_data; +} + +/** Get the encoded value for encryption level. */ +smbc_smb_encrypt_level +smbc_getOptionSmbEncryptionLevel(SMBCCTX *c) +{ + return c->internal->smb_encryption_level; +} + +/** Set the encoded value for encryption level. */ +void +smbc_setOptionSmbEncryptionLevel(SMBCCTX *c, smbc_smb_encrypt_level level) +{ + c->internal->smb_encryption_level = level; +} + +/** + * Get from how many local master browsers should the list of workgroups be + * retrieved. It can take up to 12 minutes or longer after a server becomes a + * local master browser, for it to have the entire browse list (the list of + * workgroups/domains) from an entire network. Since a client never knows + * which local master browser will be found first, the one which is found + * first and used to retrieve a browse list may have an incomplete or empty + * browse list. By requesting the browse list from multiple local master + * browsers, a more complete list can be generated. For small networks (few + * workgroups), it is recommended that this value be set to 0, causing the + * browse lists from all found local master browsers to be retrieved and + * merged. For networks with many workgroups, a suitable value for this + * variable is probably somewhere around 3. (Default: 3). + */ +int +smbc_getOptionBrowseMaxLmbCount(SMBCCTX *c) +{ + return c->options.browse_max_lmb_count; +} + +/** + * Set from how many local master browsers should the list of workgroups be + * retrieved. It can take up to 12 minutes or longer after a server becomes a + * local master browser, for it to have the entire browse list (the list of + * workgroups/domains) from an entire network. Since a client never knows + * which local master browser will be found first, the one which is found + * first and used to retrieve a browse list may have an incomplete or empty + * browse list. By requesting the browse list from multiple local master + * browsers, a more complete list can be generated. For small networks (few + * workgroups), it is recommended that this value be set to 0, causing the + * browse lists from all found local master browsers to be retrieved and + * merged. For networks with many workgroups, a suitable value for this + * variable is probably somewhere around 3. (Default: 3). + */ +void +smbc_setOptionBrowseMaxLmbCount(SMBCCTX *c, int count) +{ + c->options.browse_max_lmb_count = count; +} + +/** + * Get whether to url-encode readdir entries. + * + * There is a difference in the desired return strings from + * smbc_readdir() depending upon whether the filenames are to + * be displayed to the user, or whether they are to be + * appended to the path name passed to smbc_opendir() to call + * a further smbc_ function (e.g. open the file with + * smbc_open()). In the former case, the filename should be + * in "human readable" form. In the latter case, the smbc_ + * functions expect a URL which must be url-encoded. Those + * functions decode the URL. If, for example, smbc_readdir() + * returned a file name of "abc%20def.txt", passing a path + * with this file name attached to smbc_open() would cause + * smbc_open to attempt to open the file "abc def.txt" since + * the %20 is decoded into a space. + * + * Set this option to True if the names returned by + * smbc_readdir() should be url-encoded such that they can be + * passed back to another smbc_ call. Set it to False if the + * names returned by smbc_readdir() are to be presented to the + * user. + * + * For backwards compatibility, this option defaults to False. + */ +smbc_bool +smbc_getOptionUrlEncodeReaddirEntries(SMBCCTX *c) +{ + return c->options.urlencode_readdir_entries; +} + +/** + * Set whether to url-encode readdir entries. + * + * There is a difference in the desired return strings from + * smbc_readdir() depending upon whether the filenames are to + * be displayed to the user, or whether they are to be + * appended to the path name passed to smbc_opendir() to call + * a further smbc_ function (e.g. open the file with + * smbc_open()). In the former case, the filename should be + * in "human readable" form. In the latter case, the smbc_ + * functions expect a URL which must be url-encoded. Those + * functions decode the URL. If, for example, smbc_readdir() + * returned a file name of "abc%20def.txt", passing a path + * with this file name attached to smbc_open() would cause + * smbc_open to attempt to open the file "abc def.txt" since + * the %20 is decoded into a space. + * + * Set this option to True if the names returned by + * smbc_readdir() should be url-encoded such that they can be + * passed back to another smbc_ call. Set it to False if the + * names returned by smbc_readdir() are to be presented to the + * user. + * + * For backwards compatibility, this option defaults to False. + */ +void +smbc_setOptionUrlEncodeReaddirEntries(SMBCCTX *c, smbc_bool b) +{ + c->options.urlencode_readdir_entries = b; +} + +/** + * Get whether to use the same connection for all shares on a server. + * + * Some Windows versions appear to have a limit to the number + * of concurrent SESSIONs and/or TREE CONNECTions. In + * one-shot programs (i.e. the program runs and then quickly + * ends, thereby shutting down all connections), it is + * probably reasonable to establish a new connection for each + * share. In long-running applications, the limitation can be + * avoided by using only a single connection to each server, + * and issuing a new TREE CONNECT when the share is accessed. + */ +smbc_bool +smbc_getOptionOneSharePerServer(SMBCCTX *c) +{ + return c->options.one_share_per_server; +} + +/** + * Set whether to use the same connection for all shares on a server. + * + * Some Windows versions appear to have a limit to the number + * of concurrent SESSIONs and/or TREE CONNECTions. In + * one-shot programs (i.e. the program runs and then quickly + * ends, thereby shutting down all connections), it is + * probably reasonable to establish a new connection for each + * share. In long-running applications, the limitation can be + * avoided by using only a single connection to each server, + * and issuing a new TREE CONNECT when the share is accessed. + */ +void +smbc_setOptionOneSharePerServer(SMBCCTX *c, smbc_bool b) +{ + c->options.one_share_per_server = b; +} + +/** Get whether to enable use of kerberos */ +smbc_bool +smbc_getOptionUseKerberos(SMBCCTX *c) +{ + return c->flags & SMB_CTX_FLAG_USE_KERBEROS ? True : False; +} + +/** Set whether to enable use of kerberos */ +void +smbc_setOptionUseKerberos(SMBCCTX *c, smbc_bool b) +{ + if (b) { + c->flags |= SMB_CTX_FLAG_USE_KERBEROS; + } else { + c->flags &= ~SMB_CTX_FLAG_USE_KERBEROS; + } +} + +/** Get whether to fallback after kerberos */ +smbc_bool +smbc_getOptionFallbackAfterKerberos(SMBCCTX *c) +{ + return c->flags & SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS ? True : False; +} + +/** Set whether to fallback after kerberos */ +void +smbc_setOptionFallbackAfterKerberos(SMBCCTX *c, smbc_bool b) +{ + if (b) { + c->flags |= SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS; + } else { + c->flags &= ~SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS; + } +} + +/** Get whether to automatically select anonymous login */ +smbc_bool +smbc_getOptionNoAutoAnonymousLogin(SMBCCTX *c) +{ + return c->flags & SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON ? True : False; +} + +/** Set whether to automatically select anonymous login */ +void +smbc_setOptionNoAutoAnonymousLogin(SMBCCTX *c, smbc_bool b) +{ + if (b) { + c->flags |= SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON; + } else { + c->flags &= ~SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON; + } +} + +/** Get the function for obtaining authentication data */ +smbc_get_auth_data_fn +smbc_getFunctionAuthData(SMBCCTX *c) +{ + return c->callbacks.auth_fn; +} + +/** Set the function for obtaining authentication data */ +void +smbc_setFunctionAuthData(SMBCCTX *c, smbc_get_auth_data_fn fn) +{ + c->internal->auth_fn_with_context = NULL; + c->callbacks.auth_fn = fn; +} + +/** Get the new-style authentication function which includes the context. */ +smbc_get_auth_data_with_context_fn +smbc_getFunctionAuthDataWithContext(SMBCCTX *c) +{ + return c->internal->auth_fn_with_context; +} + +/** Set the new-style authentication function which includes the context. */ +void +smbc_setFunctionAuthDataWithContext(SMBCCTX *c, + smbc_get_auth_data_with_context_fn fn) +{ + c->callbacks.auth_fn = NULL; + c->internal->auth_fn_with_context = fn; +} + +/** Get the function for checking if a server is still good */ +smbc_check_server_fn +smbc_getFunctionCheckServer(SMBCCTX *c) +{ + return c->callbacks.check_server_fn; +} + +/** Set the function for checking if a server is still good */ +void +smbc_setFunctionCheckServer(SMBCCTX *c, smbc_check_server_fn fn) +{ + c->callbacks.check_server_fn = fn; +} + +/** Get the function for removing a server if unused */ +smbc_remove_unused_server_fn +smbc_getFunctionRemoveUnusedServer(SMBCCTX *c) +{ + return c->callbacks.remove_unused_server_fn; +} + +/** Set the function for removing a server if unused */ +void +smbc_setFunctionRemoveUnusedServer(SMBCCTX *c, + smbc_remove_unused_server_fn fn) +{ + c->callbacks.remove_unused_server_fn = fn; +} + +/** Get the function for adding a cached server */ +smbc_add_cached_srv_fn +smbc_getFunctionAddCachedServer(SMBCCTX *c) +{ + return c->callbacks.add_cached_srv_fn; +} + +/** Set the function for adding a cached server */ +void +smbc_setFunctionAddCachedServer(SMBCCTX *c, smbc_add_cached_srv_fn fn) +{ + c->callbacks.add_cached_srv_fn = fn; +} + +/** Get the function for server cache lookup */ +smbc_get_cached_srv_fn +smbc_getFunctionGetCachedServer(SMBCCTX *c) +{ + return c->callbacks.get_cached_srv_fn; +} + +/** Set the function for server cache lookup */ +void +smbc_setFunctionGetCachedServer(SMBCCTX *c, smbc_get_cached_srv_fn fn) +{ + c->callbacks.get_cached_srv_fn = fn; +} + +/** Get the function for server cache removal */ +smbc_remove_cached_srv_fn +smbc_getFunctionRemoveCachedServer(SMBCCTX *c) +{ + return c->callbacks.remove_cached_srv_fn; +} + +/** Set the function for server cache removal */ +void +smbc_setFunctionRemoveCachedServer(SMBCCTX *c, + smbc_remove_cached_srv_fn fn) +{ + c->callbacks.remove_cached_srv_fn = fn; +} + +/** + * Get the function for server cache purging. This function tries to + * remove all cached servers (e.g. on disconnect) + */ +smbc_purge_cached_fn +smbc_getFunctionPurgeCachedServers(SMBCCTX *c) +{ + return c->callbacks.purge_cached_fn; +} + +/** Set the function to store private data of the server cache */ +void smbc_setServerCacheData(SMBCCTX *c, struct smbc_server_cache * cache) +{ + c->internal->server_cache = cache; +} + +/** Get the function to store private data of the server cache */ +struct smbc_server_cache * smbc_getServerCacheData(SMBCCTX *c) +{ + return c->internal->server_cache; +} + + +/** + * Set the function for server cache purging. This function tries to + * remove all cached servers (e.g. on disconnect) + */ +void +smbc_setFunctionPurgeCachedServers(SMBCCTX *c, smbc_purge_cached_fn fn) +{ + c->callbacks.purge_cached_fn = fn; +} + +/** + * Callable functions for files. + */ + +smbc_open_fn +smbc_getFunctionOpen(SMBCCTX *c) +{ + return c->open; +} + +void +smbc_setFunctionOpen(SMBCCTX *c, smbc_open_fn fn) +{ + c->open = fn; +} + +smbc_creat_fn +smbc_getFunctionCreat(SMBCCTX *c) +{ + return c->creat; +} + +void +smbc_setFunctionCreat(SMBCCTX *c, smbc_creat_fn fn) +{ + c->creat = fn; +} + +smbc_read_fn +smbc_getFunctionRead(SMBCCTX *c) +{ + return c->read; +} + +void +smbc_setFunctionRead(SMBCCTX *c, smbc_read_fn fn) +{ + c->read = fn; +} + +smbc_write_fn +smbc_getFunctionWrite(SMBCCTX *c) +{ + return c->write; +} + +void +smbc_setFunctionWrite(SMBCCTX *c, smbc_write_fn fn) +{ + c->write = fn; +} + +smbc_unlink_fn +smbc_getFunctionUnlink(SMBCCTX *c) +{ + return c->unlink; +} + +void +smbc_setFunctionUnlink(SMBCCTX *c, smbc_unlink_fn fn) +{ + c->unlink = fn; +} + +smbc_rename_fn +smbc_getFunctionRename(SMBCCTX *c) +{ + return c->rename; +} + +void +smbc_setFunctionRename(SMBCCTX *c, smbc_rename_fn fn) +{ + c->rename = fn; +} + +smbc_lseek_fn +smbc_getFunctionLseek(SMBCCTX *c) +{ + return c->lseek; +} + +void +smbc_setFunctionLseek(SMBCCTX *c, smbc_lseek_fn fn) +{ + c->lseek = fn; +} + +smbc_stat_fn +smbc_getFunctionStat(SMBCCTX *c) +{ + return c->stat; +} + +void +smbc_setFunctionStat(SMBCCTX *c, smbc_stat_fn fn) +{ + c->stat = fn; +} + +smbc_fstat_fn +smbc_getFunctionFstat(SMBCCTX *c) +{ + return c->fstat; +} + +void +smbc_setFunctionFstat(SMBCCTX *c, smbc_fstat_fn fn) +{ + c->fstat = fn; +} + +smbc_ftruncate_fn +smbc_getFunctionFtruncate(SMBCCTX *c) +{ + return c->internal->posix_emu.ftruncate_fn; +} + +void +smbc_setFunctionFtruncate(SMBCCTX *c, smbc_ftruncate_fn fn) +{ + c->internal->posix_emu.ftruncate_fn = fn; +} + +smbc_close_fn +smbc_getFunctionClose(SMBCCTX *c) +{ + return c->close_fn; +} + +void +smbc_setFunctionClose(SMBCCTX *c, smbc_close_fn fn) +{ + c->close_fn = fn; +} + + +/** + * Callable functions for directories. + */ + +smbc_opendir_fn +smbc_getFunctionOpendir(SMBCCTX *c) +{ + return c->opendir; +} + +void +smbc_setFunctionOpendir(SMBCCTX *c, smbc_opendir_fn fn) +{ + c->opendir = fn; +} + +smbc_closedir_fn +smbc_getFunctionClosedir(SMBCCTX *c) +{ + return c->closedir; +} + +void +smbc_setFunctionClosedir(SMBCCTX *c, smbc_closedir_fn fn) +{ + c->closedir = fn; +} + +smbc_readdir_fn +smbc_getFunctionReaddir(SMBCCTX *c) +{ + return c->readdir; +} + +void +smbc_setFunctionReaddir(SMBCCTX *c, smbc_readdir_fn fn) +{ + c->readdir = fn; +} + +smbc_getdents_fn +smbc_getFunctionGetdents(SMBCCTX *c) +{ + return c->getdents; +} + +void +smbc_setFunctionGetdents(SMBCCTX *c, smbc_getdents_fn fn) +{ + c->getdents = fn; +} + +smbc_mkdir_fn +smbc_getFunctionMkdir(SMBCCTX *c) +{ + return c->mkdir; +} + +void +smbc_setFunctionMkdir(SMBCCTX *c, smbc_mkdir_fn fn) +{ + c->mkdir = fn; +} + +smbc_rmdir_fn +smbc_getFunctionRmdir(SMBCCTX *c) +{ + return c->rmdir; +} + +void +smbc_setFunctionRmdir(SMBCCTX *c, smbc_rmdir_fn fn) +{ + c->rmdir = fn; +} + +smbc_telldir_fn +smbc_getFunctionTelldir(SMBCCTX *c) +{ + return c->telldir; +} + +void +smbc_setFunctionTelldir(SMBCCTX *c, smbc_telldir_fn fn) +{ + c->telldir = fn; +} + +smbc_lseekdir_fn +smbc_getFunctionLseekdir(SMBCCTX *c) +{ + return c->lseekdir; +} + +void +smbc_setFunctionLseekdir(SMBCCTX *c, smbc_lseekdir_fn fn) +{ + c->lseekdir = fn; +} + +smbc_fstatdir_fn +smbc_getFunctionFstatdir(SMBCCTX *c) +{ + return c->fstatdir; +} + +void +smbc_setFunctionFstatdir(SMBCCTX *c, smbc_fstatdir_fn fn) +{ + c->fstatdir = fn; +} + + +/** + * Callable functions applicable to both files and directories. + */ + +smbc_chmod_fn +smbc_getFunctionChmod(SMBCCTX *c) +{ + return c->chmod; +} + +void +smbc_setFunctionChmod(SMBCCTX *c, smbc_chmod_fn fn) +{ + c->chmod = fn; +} + +smbc_utimes_fn +smbc_getFunctionUtimes(SMBCCTX *c) +{ + return c->utimes; +} + +void +smbc_setFunctionUtimes(SMBCCTX *c, smbc_utimes_fn fn) +{ + c->utimes = fn; +} + +smbc_setxattr_fn +smbc_getFunctionSetxattr(SMBCCTX *c) +{ + return c->setxattr; +} + +void +smbc_setFunctionSetxattr(SMBCCTX *c, smbc_setxattr_fn fn) +{ + c->setxattr = fn; +} + +smbc_getxattr_fn +smbc_getFunctionGetxattr(SMBCCTX *c) +{ + return c->getxattr; +} + +void +smbc_setFunctionGetxattr(SMBCCTX *c, smbc_getxattr_fn fn) +{ + c->getxattr = fn; +} + +smbc_removexattr_fn +smbc_getFunctionRemovexattr(SMBCCTX *c) +{ + return c->removexattr; +} + +void +smbc_setFunctionRemovexattr(SMBCCTX *c, smbc_removexattr_fn fn) +{ + c->removexattr = fn; +} + +smbc_listxattr_fn +smbc_getFunctionListxattr(SMBCCTX *c) +{ + return c->listxattr; +} + +void +smbc_setFunctionListxattr(SMBCCTX *c, smbc_listxattr_fn fn) +{ + c->listxattr = fn; +} + + +/** + * Callable functions related to printing + */ + +smbc_print_file_fn +smbc_getFunctionPrintFile(SMBCCTX *c) +{ + return c->print_file; +} + +void +smbc_setFunctionPrintFile(SMBCCTX *c, smbc_print_file_fn fn) +{ + c->print_file = fn; +} + +smbc_open_print_job_fn +smbc_getFunctionOpenPrintJob(SMBCCTX *c) +{ + return c->open_print_job; +} + +void +smbc_setFunctionOpenPrintJob(SMBCCTX *c, + smbc_open_print_job_fn fn) +{ + c->open_print_job = fn; +} + +smbc_list_print_jobs_fn +smbc_getFunctionListPrintJobs(SMBCCTX *c) +{ + return c->list_print_jobs; +} + +void +smbc_setFunctionListPrintJobs(SMBCCTX *c, + smbc_list_print_jobs_fn fn) +{ + c->list_print_jobs = fn; +} + +smbc_unlink_print_job_fn +smbc_getFunctionUnlinkPrintJob(SMBCCTX *c) +{ + return c->unlink_print_job; +} + +void +smbc_setFunctionUnlinkPrintJob(SMBCCTX *c, + smbc_unlink_print_job_fn fn) +{ + c->unlink_print_job = fn; +} + diff --git a/source3/libsmb/libsmb_stat.c b/source3/libsmb/libsmb_stat.c new file mode 100644 index 0000000000..27546f687e --- /dev/null +++ b/source3/libsmb/libsmb_stat.c @@ -0,0 +1,302 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Generate an inode number from file name for those things that need it + */ + +static ino_t +generate_inode(SMBCCTX *context, + const char *name) +{ + if (!context || !context->internal->initialized) { + + errno = EINVAL; + return -1; + + } + + if (!*name) return 2; /* FIXME, why 2 ??? */ + return (ino_t)str_checksum(name); + +} + +/* + * Routine to put basic stat info into a stat structure ... Used by stat and + * fstat below. + */ + +static int +setup_stat(SMBCCTX *context, + struct stat *st, + char *fname, + SMB_OFF_T size, + int mode) +{ + TALLOC_CTX *frame = talloc_stackframe(); + + st->st_mode = 0; + + if (IS_DOS_DIR(mode)) { + st->st_mode = SMBC_DIR_MODE; + } else { + st->st_mode = SMBC_FILE_MODE; + } + + if (IS_DOS_ARCHIVE(mode)) st->st_mode |= S_IXUSR; + if (IS_DOS_SYSTEM(mode)) st->st_mode |= S_IXGRP; + if (IS_DOS_HIDDEN(mode)) st->st_mode |= S_IXOTH; + if (!IS_DOS_READONLY(mode)) st->st_mode |= S_IWUSR; + + st->st_size = size; +#ifdef HAVE_STAT_ST_BLKSIZE + st->st_blksize = 512; +#endif +#ifdef HAVE_STAT_ST_BLOCKS + st->st_blocks = (size+511)/512; +#endif +#ifdef HAVE_STRUCT_STAT_ST_RDEV + st->st_rdev = 0; +#endif + st->st_uid = getuid(); + st->st_gid = getgid(); + + if (IS_DOS_DIR(mode)) { + st->st_nlink = 2; + } else { + st->st_nlink = 1; + } + + if (st->st_ino == 0) { + st->st_ino = generate_inode(context, fname); + } + + TALLOC_FREE(frame); + return True; /* FIXME: Is this needed ? */ + +} + +/* + * Routine to stat a file given a name + */ + +int +SMBC_stat_ctx(SMBCCTX *context, + const char *fname, + struct stat *st) +{ + SMBCSRV *srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + struct timespec write_time_ts; + struct timespec access_time_ts; + struct timespec change_time_ts; + SMB_OFF_T size = 0; + uint16 mode = 0; + SMB_INO_T ino = 0; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_stat(%s)\n", fname)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (!SMBC_getatr(context, srv, path, &mode, &size, + NULL, + &access_time_ts, + &write_time_ts, + &change_time_ts, + &ino)) { + errno = SMBC_errno(context, srv->cli); + TALLOC_FREE(frame); + return -1; + } + + st->st_ino = ino; + + setup_stat(context, st, (char *) fname, size, mode); + + set_atimespec(st, access_time_ts); + set_ctimespec(st, change_time_ts); + set_mtimespec(st, write_time_ts); + st->st_dev = srv->dev; + + TALLOC_FREE(frame); + return 0; + +} + +/* + * Routine to stat a file given an fd + */ + +int +SMBC_fstat_ctx(SMBCCTX *context, + SMBCFILE *file, + struct stat *st) +{ + struct timespec change_time_ts; + struct timespec access_time_ts; + struct timespec write_time_ts; + SMB_OFF_T size; + uint16 mode; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *path = NULL; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + SMB_INO_T ino = 0; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!file || !SMBC_dlist_contains(context->internal->files, file)) { + errno = EBADF; + TALLOC_FREE(frame); + return -1; + } + + if (!file->file) { + TALLOC_FREE(frame); + return smbc_getFunctionFstatdir(context)(context, file, st); + } + + /*d_printf(">>>fstat: parsing %s\n", file->fname);*/ + if (SMBC_parse_path(frame, + context, + file->fname, + NULL, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + /*d_printf(">>>fstat: resolving %s\n", path);*/ + if (!cli_resolve_path(frame, "", file->srv->cli, path, + &targetcli, &targetpath)) { + d_printf("Could not resolve %s\n", path); + TALLOC_FREE(frame); + return -1; + } + /*d_printf(">>>fstat: resolved path as %s\n", targetpath);*/ + + if (!cli_qfileinfo(targetcli, file->cli_fd, &mode, &size, + NULL, + &access_time_ts, + &write_time_ts, + &change_time_ts, + &ino)) { + + time_t change_time, access_time, write_time; + + if (!cli_getattrE(targetcli, file->cli_fd, &mode, &size, + &change_time, &access_time, &write_time)) { + + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + change_time_ts = convert_time_t_to_timespec(change_time); + access_time_ts = convert_time_t_to_timespec(access_time); + write_time_ts = convert_time_t_to_timespec(write_time); + } + + st->st_ino = ino; + + setup_stat(context, st, file->fname, size, mode); + + set_atimespec(st, access_time_ts); + set_ctimespec(st, change_time_ts); + set_mtimespec(st, write_time_ts); + st->st_dev = file->srv->dev; + + TALLOC_FREE(frame); + return 0; + +} diff --git a/source3/libsmb/libsmb_xattr.c b/source3/libsmb/libsmb_xattr.c new file mode 100644 index 0000000000..f1b3d1415e --- /dev/null +++ b/source3/libsmb/libsmb_xattr.c @@ -0,0 +1,2323 @@ +/* + Unix SMB/Netbios implementation. + SMB client library implementation + Copyright (C) Andrew Tridgell 1998 + Copyright (C) Richard Sharpe 2000, 2002 + Copyright (C) John Terpstra 2000 + Copyright (C) Tom Jansen (Ninja ISD) 2002 + Copyright (C) Derrell Lipman 2003-2008 + Copyright (C) Jeremy Allison 2007, 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libsmbclient.h" +#include "libsmb_internal.h" + + +/* + * Find an lsa pipe handle associated with a cli struct. + */ +static struct rpc_pipe_client * +find_lsa_pipe_hnd(struct cli_state *ipc_cli) +{ + struct rpc_pipe_client *pipe_hnd; + + for (pipe_hnd = ipc_cli->pipe_list; + pipe_hnd; + pipe_hnd = pipe_hnd->next) { + + if (ndr_syntax_id_equal(&pipe_hnd->abstract_syntax, + &ndr_table_lsarpc.syntax_id)) { + return pipe_hnd; + } + } + + return NULL; +} + +/* + * Sort ACEs according to the documentation at + * http://support.microsoft.com/kb/269175, at least as far as it defines the + * order. + */ + +static int +ace_compare(SEC_ACE *ace1, + SEC_ACE *ace2) +{ + bool b1; + bool b2; + + /* If the ACEs are equal, we have nothing more to do. */ + if (sec_ace_equal(ace1, ace2)) { + return 0; + } + + /* Inherited follow non-inherited */ + b1 = ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0); + b2 = ((ace2->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + /* + * What shall we do with AUDITs and ALARMs? It's undefined. We'll + * sort them after DENY and ALLOW. + */ + b1 = (ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED && + ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT && + ace1->type != SEC_ACE_TYPE_ACCESS_DENIED && + ace1->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT); + b2 = (ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED && + ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT && + ace2->type != SEC_ACE_TYPE_ACCESS_DENIED && + ace2->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + /* Allowed ACEs follow denied ACEs */ + b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED || + ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT); + b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED || + ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + /* + * ACEs applying to an entity's object follow those applying to the + * entity itself + */ + b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT || + ace1->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT); + b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT || + ace2->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + /* + * If we get this far, the ACEs are similar as far as the + * characteristics we typically care about (those defined by the + * referenced MS document). We'll now sort by characteristics that + * just seems reasonable. + */ + + if (ace1->type != ace2->type) { + return ace2->type - ace1->type; + } + + if (sid_compare(&ace1->trustee, &ace2->trustee)) { + return sid_compare(&ace1->trustee, &ace2->trustee); + } + + if (ace1->flags != ace2->flags) { + return ace1->flags - ace2->flags; + } + + if (ace1->access_mask != ace2->access_mask) { + return ace1->access_mask - ace2->access_mask; + } + + if (ace1->size != ace2->size) { + return ace1->size - ace2->size; + } + + return memcmp(ace1, ace2, sizeof(SEC_ACE)); +} + + +static void +sort_acl(SEC_ACL *the_acl) +{ + uint32 i; + if (!the_acl) return; + + qsort(the_acl->aces, the_acl->num_aces, sizeof(the_acl->aces[0]), + QSORT_CAST ace_compare); + + for (i=1;i<the_acl->num_aces;) { + if (sec_ace_equal(&the_acl->aces[i-1], &the_acl->aces[i])) { + int j; + for (j=i; j<the_acl->num_aces-1; j++) { + the_acl->aces[j] = the_acl->aces[j+1]; + } + the_acl->num_aces--; + } else { + i++; + } + } +} + +/* convert a SID to a string, either numeric or username/group */ +static void +convert_sid_to_string(struct cli_state *ipc_cli, + POLICY_HND *pol, + fstring str, + bool numeric, + DOM_SID *sid) +{ + char **domains = NULL; + char **names = NULL; + enum lsa_SidType *types = NULL; + struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli); + TALLOC_CTX *ctx; + + sid_to_fstring(str, sid); + + if (numeric) { + return; /* no lookup desired */ + } + + if (!pipe_hnd) { + return; + } + + /* Ask LSA to convert the sid to a name */ + + ctx = talloc_stackframe(); + + if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_sids(pipe_hnd, ctx, + pol, 1, sid, &domains, + &names, &types)) || + !domains || !domains[0] || !names || !names[0]) { + TALLOC_FREE(ctx); + return; + } + + TALLOC_FREE(ctx); + /* Converted OK */ + + slprintf(str, sizeof(fstring) - 1, "%s%s%s", + domains[0], lp_winbind_separator(), + names[0]); +} + +/* convert a string to a SID, either numeric or username/group */ +static bool +convert_string_to_sid(struct cli_state *ipc_cli, + POLICY_HND *pol, + bool numeric, + DOM_SID *sid, + const char *str) +{ + enum lsa_SidType *types = NULL; + DOM_SID *sids = NULL; + bool result = True; + TALLOC_CTX *ctx = NULL; + struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli); + + if (!pipe_hnd) { + return False; + } + + if (numeric) { + if (strncmp(str, "S-", 2) == 0) { + return string_to_sid(sid, str); + } + + result = False; + goto done; + } + + ctx = talloc_stackframe(); + if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_names(pipe_hnd, ctx, + pol, 1, &str, + NULL, 1, &sids, + &types))) { + result = False; + goto done; + } + + sid_copy(sid, &sids[0]); +done: + + TALLOC_FREE(ctx); + return result; +} + + +/* parse an ACE in the same format as print_ace() */ +static bool +parse_ace(struct cli_state *ipc_cli, + POLICY_HND *pol, + SEC_ACE *ace, + bool numeric, + char *str) +{ + char *p; + const char *cp; + char *tok; + unsigned int atype; + unsigned int aflags; + unsigned int amask; + DOM_SID sid; + SEC_ACCESS mask; + const struct perm_value *v; + struct perm_value { + const char *perm; + uint32 mask; + }; + TALLOC_CTX *frame = talloc_stackframe(); + + /* These values discovered by inspection */ + static const struct perm_value special_values[] = { + { "R", 0x00120089 }, + { "W", 0x00120116 }, + { "X", 0x001200a0 }, + { "D", 0x00010000 }, + { "P", 0x00040000 }, + { "O", 0x00080000 }, + { NULL, 0 }, + }; + + static const struct perm_value standard_values[] = { + { "READ", 0x001200a9 }, + { "CHANGE", 0x001301bf }, + { "FULL", 0x001f01ff }, + { NULL, 0 }, + }; + + + ZERO_STRUCTP(ace); + p = strchr_m(str,':'); + if (!p) { + TALLOC_FREE(frame); + return False; + } + *p = '\0'; + p++; + /* Try to parse numeric form */ + + if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 && + convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) { + goto done; + } + + /* Try to parse text form */ + + if (!convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) { + TALLOC_FREE(frame); + return false; + } + + cp = p; + if (!next_token_talloc(frame, &cp, &tok, "/")) { + TALLOC_FREE(frame); + return false; + } + + if (StrnCaseCmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) { + atype = SEC_ACE_TYPE_ACCESS_ALLOWED; + } else if (StrnCaseCmp(tok, "DENIED", strlen("DENIED")) == 0) { + atype = SEC_ACE_TYPE_ACCESS_DENIED; + } else { + TALLOC_FREE(frame); + return false; + } + + /* Only numeric form accepted for flags at present */ + + if (!(next_token_talloc(frame, &cp, &tok, "/") && + sscanf(tok, "%i", &aflags))) { + TALLOC_FREE(frame); + return false; + } + + if (!next_token_talloc(frame, &cp, &tok, "/")) { + TALLOC_FREE(frame); + return false; + } + + if (strncmp(tok, "0x", 2) == 0) { + if (sscanf(tok, "%i", &amask) != 1) { + TALLOC_FREE(frame); + return false; + } + goto done; + } + + for (v = standard_values; v->perm; v++) { + if (strcmp(tok, v->perm) == 0) { + amask = v->mask; + goto done; + } + } + + p = tok; + + while(*p) { + bool found = False; + + for (v = special_values; v->perm; v++) { + if (v->perm[0] == *p) { + amask |= v->mask; + found = True; + } + } + + if (!found) { + TALLOC_FREE(frame); + return false; + } + p++; + } + + if (*p) { + TALLOC_FREE(frame); + return false; + } + +done: + mask = amask; + init_sec_ace(ace, &sid, atype, mask, aflags); + TALLOC_FREE(frame); + return true; +} + +/* add an ACE to a list of ACEs in a SEC_ACL */ +static bool +add_ace(SEC_ACL **the_acl, + SEC_ACE *ace, + TALLOC_CTX *ctx) +{ + SEC_ACL *newacl; + SEC_ACE *aces; + + if (! *the_acl) { + (*the_acl) = make_sec_acl(ctx, 3, 1, ace); + return True; + } + + if ((aces = SMB_CALLOC_ARRAY(SEC_ACE, + 1+(*the_acl)->num_aces)) == NULL) { + return False; + } + memcpy(aces, (*the_acl)->aces, (*the_acl)->num_aces * sizeof(SEC_ACE)); + memcpy(aces+(*the_acl)->num_aces, ace, sizeof(SEC_ACE)); + newacl = make_sec_acl(ctx, (*the_acl)->revision, + 1+(*the_acl)->num_aces, aces); + SAFE_FREE(aces); + (*the_acl) = newacl; + return True; +} + + +/* parse a ascii version of a security descriptor */ +static SEC_DESC * +sec_desc_parse(TALLOC_CTX *ctx, + struct cli_state *ipc_cli, + POLICY_HND *pol, + bool numeric, + char *str) +{ + const char *p = str; + char *tok; + SEC_DESC *ret = NULL; + size_t sd_size; + DOM_SID *group_sid=NULL; + DOM_SID *owner_sid=NULL; + SEC_ACL *dacl=NULL; + int revision=1; + + while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) { + + if (StrnCaseCmp(tok,"REVISION:", 9) == 0) { + revision = strtol(tok+9, NULL, 16); + continue; + } + + if (StrnCaseCmp(tok,"OWNER:", 6) == 0) { + if (owner_sid) { + DEBUG(5,("OWNER specified more than once!\n")); + goto done; + } + owner_sid = SMB_CALLOC_ARRAY(DOM_SID, 1); + if (!owner_sid || + !convert_string_to_sid(ipc_cli, pol, + numeric, + owner_sid, tok+6)) { + DEBUG(5, ("Failed to parse owner sid\n")); + goto done; + } + continue; + } + + if (StrnCaseCmp(tok,"OWNER+:", 7) == 0) { + if (owner_sid) { + DEBUG(5,("OWNER specified more than once!\n")); + goto done; + } + owner_sid = SMB_CALLOC_ARRAY(DOM_SID, 1); + if (!owner_sid || + !convert_string_to_sid(ipc_cli, pol, + False, + owner_sid, tok+7)) { + DEBUG(5, ("Failed to parse owner sid\n")); + goto done; + } + continue; + } + + if (StrnCaseCmp(tok,"GROUP:", 6) == 0) { + if (group_sid) { + DEBUG(5,("GROUP specified more than once!\n")); + goto done; + } + group_sid = SMB_CALLOC_ARRAY(DOM_SID, 1); + if (!group_sid || + !convert_string_to_sid(ipc_cli, pol, + numeric, + group_sid, tok+6)) { + DEBUG(5, ("Failed to parse group sid\n")); + goto done; + } + continue; + } + + if (StrnCaseCmp(tok,"GROUP+:", 7) == 0) { + if (group_sid) { + DEBUG(5,("GROUP specified more than once!\n")); + goto done; + } + group_sid = SMB_CALLOC_ARRAY(DOM_SID, 1); + if (!group_sid || + !convert_string_to_sid(ipc_cli, pol, + False, + group_sid, tok+6)) { + DEBUG(5, ("Failed to parse group sid\n")); + goto done; + } + continue; + } + + if (StrnCaseCmp(tok,"ACL:", 4) == 0) { + SEC_ACE ace; + if (!parse_ace(ipc_cli, pol, &ace, numeric, tok+4)) { + DEBUG(5, ("Failed to parse ACL %s\n", tok)); + goto done; + } + if(!add_ace(&dacl, &ace, ctx)) { + DEBUG(5, ("Failed to add ACL %s\n", tok)); + goto done; + } + continue; + } + + if (StrnCaseCmp(tok,"ACL+:", 5) == 0) { + SEC_ACE ace; + if (!parse_ace(ipc_cli, pol, &ace, False, tok+5)) { + DEBUG(5, ("Failed to parse ACL %s\n", tok)); + goto done; + } + if(!add_ace(&dacl, &ace, ctx)) { + DEBUG(5, ("Failed to add ACL %s\n", tok)); + goto done; + } + continue; + } + + DEBUG(5, ("Failed to parse security descriptor\n")); + goto done; + } + + ret = make_sec_desc(ctx, revision, SEC_DESC_SELF_RELATIVE, + owner_sid, group_sid, NULL, dacl, &sd_size); + +done: + SAFE_FREE(group_sid); + SAFE_FREE(owner_sid); + + return ret; +} + + +/* Obtain the current dos attributes */ +static DOS_ATTR_DESC * +dos_attr_query(SMBCCTX *context, + TALLOC_CTX *ctx, + const char *filename, + SMBCSRV *srv) +{ + struct timespec create_time_ts; + struct timespec write_time_ts; + struct timespec access_time_ts; + struct timespec change_time_ts; + SMB_OFF_T size = 0; + uint16 mode = 0; + SMB_INO_T inode = 0; + DOS_ATTR_DESC *ret; + + ret = TALLOC_P(ctx, DOS_ATTR_DESC); + if (!ret) { + errno = ENOMEM; + return NULL; + } + + /* Obtain the DOS attributes */ + if (!SMBC_getatr(context, srv, CONST_DISCARD(char *, filename), + &mode, &size, + &create_time_ts, + &access_time_ts, + &write_time_ts, + &change_time_ts, + &inode)) { + errno = SMBC_errno(context, srv->cli); + DEBUG(5, ("dos_attr_query Failed to query old attributes\n")); + return NULL; + } + + ret->mode = mode; + ret->size = size; + ret->create_time = convert_timespec_to_time_t(create_time_ts); + ret->access_time = convert_timespec_to_time_t(access_time_ts); + ret->write_time = convert_timespec_to_time_t(write_time_ts); + ret->change_time = convert_timespec_to_time_t(change_time_ts); + ret->inode = inode; + + return ret; +} + + +/* parse a ascii version of a security descriptor */ +static void +dos_attr_parse(SMBCCTX *context, + DOS_ATTR_DESC *dad, + SMBCSRV *srv, + char *str) +{ + int n; + const char *p = str; + char *tok = NULL; + TALLOC_CTX *frame = NULL; + struct { + const char * create_time_attr; + const char * access_time_attr; + const char * write_time_attr; + const char * change_time_attr; + } attr_strings; + + /* Determine whether to use old-style or new-style attribute names */ + if (context->internal->full_time_names) { + /* new-style names */ + attr_strings.create_time_attr = "CREATE_TIME"; + attr_strings.access_time_attr = "ACCESS_TIME"; + attr_strings.write_time_attr = "WRITE_TIME"; + attr_strings.change_time_attr = "CHANGE_TIME"; + } else { + /* old-style names */ + attr_strings.create_time_attr = NULL; + attr_strings.access_time_attr = "A_TIME"; + attr_strings.write_time_attr = "M_TIME"; + attr_strings.change_time_attr = "C_TIME"; + } + + /* if this is to set the entire ACL... */ + if (*str == '*') { + /* ... then increment past the first colon if there is one */ + if ((p = strchr(str, ':')) != NULL) { + ++p; + } else { + p = str; + } + } + + frame = talloc_stackframe(); + while (next_token_talloc(frame, &p, &tok, "\t,\r\n")) { + if (StrnCaseCmp(tok, "MODE:", 5) == 0) { + long request = strtol(tok+5, NULL, 16); + if (request == 0) { + dad->mode = (request | + (IS_DOS_DIR(dad->mode) + ? FILE_ATTRIBUTE_DIRECTORY + : FILE_ATTRIBUTE_NORMAL)); + } else { + dad->mode = request; + } + continue; + } + + if (StrnCaseCmp(tok, "SIZE:", 5) == 0) { + dad->size = (SMB_OFF_T)atof(tok+5); + continue; + } + + n = strlen(attr_strings.access_time_attr); + if (StrnCaseCmp(tok, attr_strings.access_time_attr, n) == 0) { + dad->access_time = (time_t)strtol(tok+n+1, NULL, 10); + continue; + } + + n = strlen(attr_strings.change_time_attr); + if (StrnCaseCmp(tok, attr_strings.change_time_attr, n) == 0) { + dad->change_time = (time_t)strtol(tok+n+1, NULL, 10); + continue; + } + + n = strlen(attr_strings.write_time_attr); + if (StrnCaseCmp(tok, attr_strings.write_time_attr, n) == 0) { + dad->write_time = (time_t)strtol(tok+n+1, NULL, 10); + continue; + } + + if (attr_strings.create_time_attr != NULL) { + n = strlen(attr_strings.create_time_attr); + if (StrnCaseCmp(tok, attr_strings.create_time_attr, + n) == 0) { + dad->create_time = (time_t)strtol(tok+n+1, + NULL, 10); + continue; + } + } + + if (StrnCaseCmp(tok, "INODE:", 6) == 0) { + dad->inode = (SMB_INO_T)atof(tok+6); + continue; + } + } + TALLOC_FREE(frame); +} + +/***************************************************** + Retrieve the acls for a file. +*******************************************************/ + +static int +cacl_get(SMBCCTX *context, + TALLOC_CTX *ctx, + SMBCSRV *srv, + struct cli_state *ipc_cli, + POLICY_HND *pol, + char *filename, + char *attr_name, + char *buf, + int bufsize) +{ + uint32 i; + int n = 0; + int n_used; + bool all; + bool all_nt; + bool all_nt_acls; + bool all_dos; + bool some_nt; + bool some_dos; + bool exclude_nt_revision = False; + bool exclude_nt_owner = False; + bool exclude_nt_group = False; + bool exclude_nt_acl = False; + bool exclude_dos_mode = False; + bool exclude_dos_size = False; + bool exclude_dos_create_time = False; + bool exclude_dos_access_time = False; + bool exclude_dos_write_time = False; + bool exclude_dos_change_time = False; + bool exclude_dos_inode = False; + bool numeric = True; + bool determine_size = (bufsize == 0); + int fnum = -1; + SEC_DESC *sd; + fstring sidstr; + fstring name_sandbox; + char *name; + char *pExclude; + char *p; + struct timespec create_time_ts; + struct timespec write_time_ts; + struct timespec access_time_ts; + struct timespec change_time_ts; + time_t create_time = (time_t)0; + time_t write_time = (time_t)0; + time_t access_time = (time_t)0; + time_t change_time = (time_t)0; + SMB_OFF_T size = 0; + uint16 mode = 0; + SMB_INO_T ino = 0; + struct cli_state *cli = srv->cli; + struct { + const char * create_time_attr; + const char * access_time_attr; + const char * write_time_attr; + const char * change_time_attr; + } attr_strings; + struct { + const char * create_time_attr; + const char * access_time_attr; + const char * write_time_attr; + const char * change_time_attr; + } excl_attr_strings; + + /* Determine whether to use old-style or new-style attribute names */ + if (context->internal->full_time_names) { + /* new-style names */ + attr_strings.create_time_attr = "CREATE_TIME"; + attr_strings.access_time_attr = "ACCESS_TIME"; + attr_strings.write_time_attr = "WRITE_TIME"; + attr_strings.change_time_attr = "CHANGE_TIME"; + + excl_attr_strings.create_time_attr = "CREATE_TIME"; + excl_attr_strings.access_time_attr = "ACCESS_TIME"; + excl_attr_strings.write_time_attr = "WRITE_TIME"; + excl_attr_strings.change_time_attr = "CHANGE_TIME"; + } else { + /* old-style names */ + attr_strings.create_time_attr = NULL; + attr_strings.access_time_attr = "A_TIME"; + attr_strings.write_time_attr = "M_TIME"; + attr_strings.change_time_attr = "C_TIME"; + + excl_attr_strings.create_time_attr = NULL; + excl_attr_strings.access_time_attr = "dos_attr.A_TIME"; + excl_attr_strings.write_time_attr = "dos_attr.M_TIME"; + excl_attr_strings.change_time_attr = "dos_attr.C_TIME"; + } + + /* Copy name so we can strip off exclusions (if any are specified) */ + strncpy(name_sandbox, attr_name, sizeof(name_sandbox) - 1); + + /* Ensure name is null terminated */ + name_sandbox[sizeof(name_sandbox) - 1] = '\0'; + + /* Play in the sandbox */ + name = name_sandbox; + + /* If there are any exclusions, point to them and mask them from name */ + if ((pExclude = strchr(name, '!')) != NULL) + { + *pExclude++ = '\0'; + } + + all = (StrnCaseCmp(name, "system.*", 8) == 0); + all_nt = (StrnCaseCmp(name, "system.nt_sec_desc.*", 20) == 0); + all_nt_acls = (StrnCaseCmp(name, "system.nt_sec_desc.acl.*", 24) == 0); + all_dos = (StrnCaseCmp(name, "system.dos_attr.*", 17) == 0); + some_nt = (StrnCaseCmp(name, "system.nt_sec_desc.", 19) == 0); + some_dos = (StrnCaseCmp(name, "system.dos_attr.", 16) == 0); + numeric = (* (name + strlen(name) - 1) != '+'); + + /* Look for exclusions from "all" requests */ + if (all || all_nt || all_dos) { + + /* Exclusions are delimited by '!' */ + for (; + pExclude != NULL; + pExclude = (p == NULL ? NULL : p + 1)) { + + /* Find end of this exclusion name */ + if ((p = strchr(pExclude, '!')) != NULL) + { + *p = '\0'; + } + + /* Which exclusion name is this? */ + if (StrCaseCmp(pExclude, + "nt_sec_desc.revision") == 0) { + exclude_nt_revision = True; + } + else if (StrCaseCmp(pExclude, + "nt_sec_desc.owner") == 0) { + exclude_nt_owner = True; + } + else if (StrCaseCmp(pExclude, + "nt_sec_desc.group") == 0) { + exclude_nt_group = True; + } + else if (StrCaseCmp(pExclude, + "nt_sec_desc.acl") == 0) { + exclude_nt_acl = True; + } + else if (StrCaseCmp(pExclude, + "dos_attr.mode") == 0) { + exclude_dos_mode = True; + } + else if (StrCaseCmp(pExclude, + "dos_attr.size") == 0) { + exclude_dos_size = True; + } + else if (excl_attr_strings.create_time_attr != NULL && + StrCaseCmp(pExclude, + excl_attr_strings.change_time_attr) == 0) { + exclude_dos_create_time = True; + } + else if (StrCaseCmp(pExclude, + excl_attr_strings.access_time_attr) == 0) { + exclude_dos_access_time = True; + } + else if (StrCaseCmp(pExclude, + excl_attr_strings.write_time_attr) == 0) { + exclude_dos_write_time = True; + } + else if (StrCaseCmp(pExclude, + excl_attr_strings.change_time_attr) == 0) { + exclude_dos_change_time = True; + } + else if (StrCaseCmp(pExclude, "dos_attr.inode") == 0) { + exclude_dos_inode = True; + } + else { + DEBUG(5, ("cacl_get received unknown exclusion: %s\n", + pExclude)); + errno = ENOATTR; + return -1; + } + } + } + + n_used = 0; + + /* + * If we are (possibly) talking to an NT or new system and some NT + * attributes have been requested... + */ + if (ipc_cli && (all || some_nt || all_nt_acls)) { + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + + /* Point to the portion after "system.nt_sec_desc." */ + name += 19; /* if (all) this will be invalid but unused */ + + if (!cli_resolve_path(ctx, "", cli, filename, + &targetcli, &targetpath)) { + DEBUG(5, ("cacl_get Could not resolve %s\n", + filename)); + errno = ENOENT; + return -1; + } + + /* ... then obtain any NT attributes which were requested */ + fnum = cli_nt_create(targetcli, targetpath, CREATE_ACCESS_READ); + + if (fnum == -1) { + DEBUG(5, ("cacl_get failed to open %s: %s\n", + targetpath, cli_errstr(targetcli))); + errno = 0; + return -1; + } + + sd = cli_query_secdesc(targetcli, fnum, ctx); + + if (!sd) { + DEBUG(5, + ("cacl_get Failed to query old descriptor\n")); + errno = 0; + return -1; + } + + cli_close(targetcli, fnum); + + if (! exclude_nt_revision) { + if (all || all_nt) { + if (determine_size) { + p = talloc_asprintf(ctx, + "REVISION:%d", + sd->revision); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "REVISION:%d", + sd->revision); + } + } else if (StrCaseCmp(name, "revision") == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%d", + sd->revision); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, "%d", + sd->revision); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_nt_owner) { + /* Get owner and group sid */ + if (sd->owner_sid) { + convert_sid_to_string(ipc_cli, pol, + sidstr, + numeric, + sd->owner_sid); + } else { + fstrcpy(sidstr, ""); + } + + if (all || all_nt) { + if (determine_size) { + p = talloc_asprintf(ctx, ",OWNER:%s", + sidstr); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else if (sidstr[0] != '\0') { + n = snprintf(buf, bufsize, + ",OWNER:%s", sidstr); + } + } else if (StrnCaseCmp(name, "owner", 5) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%s", sidstr); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, "%s", + sidstr); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_nt_group) { + if (sd->group_sid) { + convert_sid_to_string(ipc_cli, pol, + sidstr, numeric, + sd->group_sid); + } else { + fstrcpy(sidstr, ""); + } + + if (all || all_nt) { + if (determine_size) { + p = talloc_asprintf(ctx, ",GROUP:%s", + sidstr); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else if (sidstr[0] != '\0') { + n = snprintf(buf, bufsize, + ",GROUP:%s", sidstr); + } + } else if (StrnCaseCmp(name, "group", 5) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%s", sidstr); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%s", sidstr); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_nt_acl) { + /* Add aces to value buffer */ + for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) { + + SEC_ACE *ace = &sd->dacl->aces[i]; + convert_sid_to_string(ipc_cli, pol, + sidstr, numeric, + &ace->trustee); + + if (all || all_nt) { + if (determine_size) { + p = talloc_asprintf( + ctx, + ",ACL:" + "%s:%d/%d/0x%08x", + sidstr, + ace->type, + ace->flags, + ace->access_mask); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf( + buf, bufsize, + ",ACL:%s:%d/%d/0x%08x", + sidstr, + ace->type, + ace->flags, + ace->access_mask); + } + } else if ((StrnCaseCmp(name, "acl", 3) == 0 && + StrCaseCmp(name+3, sidstr) == 0) || + (StrnCaseCmp(name, "acl+", 4) == 0 && + StrCaseCmp(name+4, sidstr) == 0)) { + if (determine_size) { + p = talloc_asprintf( + ctx, + "%d/%d/0x%08x", + ace->type, + ace->flags, + ace->access_mask); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%d/%d/0x%08x", + ace->type, + ace->flags, + ace->access_mask); + } + } else if (all_nt_acls) { + if (determine_size) { + p = talloc_asprintf( + ctx, + "%s%s:%d/%d/0x%08x", + i ? "," : "", + sidstr, + ace->type, + ace->flags, + ace->access_mask); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%s%s:%d/%d/0x%08x", + i ? "," : "", + sidstr, + ace->type, + ace->flags, + ace->access_mask); + } + } + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + } + + /* Restore name pointer to its original value */ + name -= 19; + } + + if (all || some_dos) { + /* Point to the portion after "system.dos_attr." */ + name += 16; /* if (all) this will be invalid but unused */ + + /* Obtain the DOS attributes */ + if (!SMBC_getatr(context, srv, filename, &mode, &size, + &create_time_ts, + &access_time_ts, + &write_time_ts, + &change_time_ts, + &ino)) { + + errno = SMBC_errno(context, srv->cli); + return -1; + + } + + create_time = convert_timespec_to_time_t(create_time_ts); + access_time = convert_timespec_to_time_t(access_time_ts); + write_time = convert_timespec_to_time_t(write_time_ts); + change_time = convert_timespec_to_time_t(change_time_ts); + + if (! exclude_dos_mode) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf(ctx, + "%sMODE:0x%x", + (ipc_cli && + (all || some_nt) + ? "," + : ""), + mode); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%sMODE:0x%x", + (ipc_cli && + (all || some_nt) + ? "," + : ""), + mode); + } + } else if (StrCaseCmp(name, "mode") == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "0x%x", mode); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "0x%x", mode); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_size) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf( + ctx, + ",SIZE:%.0f", + (double)size); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",SIZE:%.0f", + (double)size); + } + } else if (StrCaseCmp(name, "size") == 0) { + if (determine_size) { + p = talloc_asprintf( + ctx, + "%.0f", + (double)size); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%.0f", + (double)size); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_create_time && + attr_strings.create_time_attr != NULL) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf(ctx, + ",%s:%lu", + attr_strings.create_time_attr, + create_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",%s:%lu", + attr_strings.create_time_attr, + create_time); + } + } else if (StrCaseCmp(name, attr_strings.create_time_attr) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%lu", create_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%lu", create_time); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_access_time) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf(ctx, + ",%s:%lu", + attr_strings.access_time_attr, + access_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",%s:%lu", + attr_strings.access_time_attr, + access_time); + } + } else if (StrCaseCmp(name, attr_strings.access_time_attr) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%lu", access_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%lu", access_time); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_write_time) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf(ctx, + ",%s:%lu", + attr_strings.write_time_attr, + write_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",%s:%lu", + attr_strings.write_time_attr, + write_time); + } + } else if (StrCaseCmp(name, attr_strings.write_time_attr) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%lu", write_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%lu", write_time); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_change_time) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf(ctx, + ",%s:%lu", + attr_strings.change_time_attr, + change_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",%s:%lu", + attr_strings.change_time_attr, + change_time); + } + } else if (StrCaseCmp(name, attr_strings.change_time_attr) == 0) { + if (determine_size) { + p = talloc_asprintf(ctx, "%lu", change_time); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%lu", change_time); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + if (! exclude_dos_inode) { + if (all || all_dos) { + if (determine_size) { + p = talloc_asprintf( + ctx, + ",INODE:%.0f", + (double)ino); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + ",INODE:%.0f", + (double) ino); + } + } else if (StrCaseCmp(name, "inode") == 0) { + if (determine_size) { + p = talloc_asprintf( + ctx, + "%.0f", + (double) ino); + if (!p) { + errno = ENOMEM; + return -1; + } + n = strlen(p); + } else { + n = snprintf(buf, bufsize, + "%.0f", + (double) ino); + } + } + + if (!determine_size && n > bufsize) { + errno = ERANGE; + return -1; + } + buf += n; + n_used += n; + bufsize -= n; + n = 0; + } + + /* Restore name pointer to its original value */ + name -= 16; + } + + if (n_used == 0) { + errno = ENOATTR; + return -1; + } + + return n_used; +} + +/***************************************************** +set the ACLs on a file given an ascii description +*******************************************************/ +static int +cacl_set(TALLOC_CTX *ctx, + struct cli_state *cli, + struct cli_state *ipc_cli, + POLICY_HND *pol, + const char *filename, + const char *the_acl, + int mode, + int flags) +{ + int fnum; + int err = 0; + SEC_DESC *sd = NULL, *old; + SEC_ACL *dacl = NULL; + DOM_SID *owner_sid = NULL; + DOM_SID *group_sid = NULL; + uint32 i, j; + size_t sd_size; + int ret = 0; + char *p; + bool numeric = True; + char *targetpath = NULL; + struct cli_state *targetcli = NULL; + + /* the_acl will be null for REMOVE_ALL operations */ + if (the_acl) { + numeric = ((p = strchr(the_acl, ':')) != NULL && + p > the_acl && + p[-1] != '+'); + + /* if this is to set the entire ACL... */ + if (*the_acl == '*') { + /* ... then increment past the first colon */ + the_acl = p + 1; + } + + sd = sec_desc_parse(ctx, ipc_cli, pol, numeric, + CONST_DISCARD(char *, the_acl)); + + if (!sd) { + errno = EINVAL; + return -1; + } + } + + /* SMBC_XATTR_MODE_REMOVE_ALL is the only caller + that doesn't deref sd */ + + if (!sd && (mode != SMBC_XATTR_MODE_REMOVE_ALL)) { + errno = EINVAL; + return -1; + } + + if (!cli_resolve_path(ctx, "", cli, filename, + &targetcli, &targetpath)) { + DEBUG(5,("cacl_set: Could not resolve %s\n", filename)); + errno = ENOENT; + return -1; + } + + /* The desired access below is the only one I could find that works + with NT4, W2KP and Samba */ + + fnum = cli_nt_create(targetcli, targetpath, CREATE_ACCESS_READ); + + if (fnum == -1) { + DEBUG(5, ("cacl_set failed to open %s: %s\n", + targetpath, cli_errstr(targetcli))); + errno = 0; + return -1; + } + + old = cli_query_secdesc(targetcli, fnum, ctx); + + if (!old) { + DEBUG(5, ("cacl_set Failed to query old descriptor\n")); + errno = 0; + return -1; + } + + cli_close(targetcli, fnum); + + switch (mode) { + case SMBC_XATTR_MODE_REMOVE_ALL: + old->dacl->num_aces = 0; + dacl = old->dacl; + break; + + case SMBC_XATTR_MODE_REMOVE: + for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) { + bool found = False; + + for (j=0;old->dacl && j<old->dacl->num_aces;j++) { + if (sec_ace_equal(&sd->dacl->aces[i], + &old->dacl->aces[j])) { + uint32 k; + for (k=j; k<old->dacl->num_aces-1;k++) { + old->dacl->aces[k] = + old->dacl->aces[k+1]; + } + old->dacl->num_aces--; + found = True; + dacl = old->dacl; + break; + } + } + + if (!found) { + err = ENOATTR; + ret = -1; + goto failed; + } + } + break; + + case SMBC_XATTR_MODE_ADD: + for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) { + bool found = False; + + for (j=0;old->dacl && j<old->dacl->num_aces;j++) { + if (sid_equal(&sd->dacl->aces[i].trustee, + &old->dacl->aces[j].trustee)) { + if (!(flags & SMBC_XATTR_FLAG_CREATE)) { + err = EEXIST; + ret = -1; + goto failed; + } + old->dacl->aces[j] = sd->dacl->aces[i]; + ret = -1; + found = True; + } + } + + if (!found && (flags & SMBC_XATTR_FLAG_REPLACE)) { + err = ENOATTR; + ret = -1; + goto failed; + } + + for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) { + add_ace(&old->dacl, &sd->dacl->aces[i], ctx); + } + } + dacl = old->dacl; + break; + + case SMBC_XATTR_MODE_SET: + old = sd; + owner_sid = old->owner_sid; + group_sid = old->group_sid; + dacl = old->dacl; + break; + + case SMBC_XATTR_MODE_CHOWN: + owner_sid = sd->owner_sid; + break; + + case SMBC_XATTR_MODE_CHGRP: + group_sid = sd->group_sid; + break; + } + + /* Denied ACE entries must come before allowed ones */ + sort_acl(old->dacl); + + /* Create new security descriptor and set it */ + sd = make_sec_desc(ctx, old->revision, SEC_DESC_SELF_RELATIVE, + owner_sid, group_sid, NULL, dacl, &sd_size); + + fnum = cli_nt_create(targetcli, targetpath, + WRITE_DAC_ACCESS | WRITE_OWNER_ACCESS); + + if (fnum == -1) { + DEBUG(5, ("cacl_set failed to open %s: %s\n", + targetpath, cli_errstr(targetcli))); + errno = 0; + return -1; + } + + if (!cli_set_secdesc(targetcli, fnum, sd)) { + DEBUG(5, ("ERROR: secdesc set failed: %s\n", + cli_errstr(targetcli))); + ret = -1; + } + + /* Clean up */ + +failed: + cli_close(targetcli, fnum); + + if (err != 0) { + errno = err; + } + + return ret; +} + + +int +SMBC_setxattr_ctx(SMBCCTX *context, + const char *fname, + const char *name, + const void *value, + size_t size, + int flags) +{ + int ret; + int ret2; + SMBCSRV *srv = NULL; + SMBCSRV *ipc_srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + DOS_ATTR_DESC *dad = NULL; + struct { + const char * create_time_attr; + const char * access_time_attr; + const char * write_time_attr; + const char * change_time_attr; + } attr_strings; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_setxattr(%s, %s, %.*s)\n", + fname, name, (int) size, (const char*)value)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (! srv->no_nt_session) { + ipc_srv = SMBC_attr_server(frame, context, server, share, + &workgroup, &user, &password); + if (! ipc_srv) { + srv->no_nt_session = True; + } + } else { + ipc_srv = NULL; + } + + /* + * Are they asking to set the entire set of known attributes? + */ + if (StrCaseCmp(name, "system.*") == 0 || + StrCaseCmp(name, "system.*+") == 0) { + /* Yup. */ + char *namevalue = + talloc_asprintf(talloc_tos(), "%s:%s", + name+7, (const char *) value); + if (! namevalue) { + errno = ENOMEM; + ret = -1; + TALLOC_FREE(frame); + return -1; + } + + if (ipc_srv) { + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + namevalue, + (*namevalue == '*' + ? SMBC_XATTR_MODE_SET + : SMBC_XATTR_MODE_ADD), + flags); + } else { + ret = 0; + } + + /* get a DOS Attribute Descriptor with current attributes */ + dad = dos_attr_query(context, talloc_tos(), path, srv); + if (dad) { + /* Overwrite old with new, using what was provided */ + dos_attr_parse(context, dad, srv, namevalue); + + /* Set the new DOS attributes */ + if (! SMBC_setatr(context, srv, path, + dad->create_time, + dad->access_time, + dad->write_time, + dad->change_time, + dad->mode)) { + + /* cause failure if NT failed too */ + dad = NULL; + } + } + + /* we only fail if both NT and DOS sets failed */ + if (ret < 0 && ! dad) { + ret = -1; /* in case dad was null */ + } + else { + ret = 0; + } + + TALLOC_FREE(frame); + return ret; + } + + /* + * Are they asking to set an access control element or to set + * the entire access control list? + */ + if (StrCaseCmp(name, "system.nt_sec_desc.*") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.*+") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.revision") == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl", 22) == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl+", 23) == 0) { + + /* Yup. */ + char *namevalue = + talloc_asprintf(talloc_tos(), "%s:%s", + name+19, (const char *) value); + + if (! ipc_srv) { + ret = -1; /* errno set by SMBC_server() */ + } + else if (! namevalue) { + errno = ENOMEM; + ret = -1; + } else { + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + namevalue, + (*namevalue == '*' + ? SMBC_XATTR_MODE_SET + : SMBC_XATTR_MODE_ADD), + flags); + } + TALLOC_FREE(frame); + return ret; + } + + /* + * Are they asking to set the owner? + */ + if (StrCaseCmp(name, "system.nt_sec_desc.owner") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.owner+") == 0) { + + /* Yup. */ + char *namevalue = + talloc_asprintf(talloc_tos(), "%s:%s", + name+19, (const char *) value); + + if (! ipc_srv) { + ret = -1; /* errno set by SMBC_server() */ + } + else if (! namevalue) { + errno = ENOMEM; + ret = -1; + } else { + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + namevalue, SMBC_XATTR_MODE_CHOWN, 0); + } + TALLOC_FREE(frame); + return ret; + } + + /* + * Are they asking to set the group? + */ + if (StrCaseCmp(name, "system.nt_sec_desc.group") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.group+") == 0) { + + /* Yup. */ + char *namevalue = + talloc_asprintf(talloc_tos(), "%s:%s", + name+19, (const char *) value); + + if (! ipc_srv) { + /* errno set by SMBC_server() */ + ret = -1; + } + else if (! namevalue) { + errno = ENOMEM; + ret = -1; + } else { + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + namevalue, SMBC_XATTR_MODE_CHGRP, 0); + } + TALLOC_FREE(frame); + return ret; + } + + /* Determine whether to use old-style or new-style attribute names */ + if (context->internal->full_time_names) { + /* new-style names */ + attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME"; + attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME"; + attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME"; + attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME"; + } else { + /* old-style names */ + attr_strings.create_time_attr = NULL; + attr_strings.access_time_attr = "system.dos_attr.A_TIME"; + attr_strings.write_time_attr = "system.dos_attr.M_TIME"; + attr_strings.change_time_attr = "system.dos_attr.C_TIME"; + } + + /* + * Are they asking to set a DOS attribute? + */ + if (StrCaseCmp(name, "system.dos_attr.*") == 0 || + StrCaseCmp(name, "system.dos_attr.mode") == 0 || + (attr_strings.create_time_attr != NULL && + StrCaseCmp(name, attr_strings.create_time_attr) == 0) || + StrCaseCmp(name, attr_strings.access_time_attr) == 0 || + StrCaseCmp(name, attr_strings.write_time_attr) == 0 || + StrCaseCmp(name, attr_strings.change_time_attr) == 0) { + + /* get a DOS Attribute Descriptor with current attributes */ + dad = dos_attr_query(context, talloc_tos(), path, srv); + if (dad) { + char *namevalue = + talloc_asprintf(talloc_tos(), "%s:%s", + name+16, (const char *) value); + if (! namevalue) { + errno = ENOMEM; + ret = -1; + } else { + /* Overwrite old with provided new params */ + dos_attr_parse(context, dad, srv, namevalue); + + /* Set the new DOS attributes */ + ret2 = SMBC_setatr(context, srv, path, + dad->create_time, + dad->access_time, + dad->write_time, + dad->change_time, + dad->mode); + + /* ret2 has True (success) / False (failure) */ + if (ret2) { + ret = 0; + } else { + ret = -1; + } + } + } else { + ret = -1; + } + + TALLOC_FREE(frame); + return ret; + } + + /* Unsupported attribute name */ + errno = EINVAL; + TALLOC_FREE(frame); + return -1; +} + +int +SMBC_getxattr_ctx(SMBCCTX *context, + const char *fname, + const char *name, + const void *value, + size_t size) +{ + int ret; + SMBCSRV *srv = NULL; + SMBCSRV *ipc_srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + struct { + const char * create_time_attr; + const char * access_time_attr; + const char * write_time_attr; + const char * change_time_attr; + } attr_strings; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_getxattr(%s, %s)\n", fname, name)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (! srv->no_nt_session) { + ipc_srv = SMBC_attr_server(frame, context, server, share, + &workgroup, &user, &password); + if (! ipc_srv) { + srv->no_nt_session = True; + } + } else { + ipc_srv = NULL; + } + + /* Determine whether to use old-style or new-style attribute names */ + if (context->internal->full_time_names) { + /* new-style names */ + attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME"; + attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME"; + attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME"; + attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME"; + } else { + /* old-style names */ + attr_strings.create_time_attr = NULL; + attr_strings.access_time_attr = "system.dos_attr.A_TIME"; + attr_strings.write_time_attr = "system.dos_attr.M_TIME"; + attr_strings.change_time_attr = "system.dos_attr.C_TIME"; + } + + /* Are they requesting a supported attribute? */ + if (StrCaseCmp(name, "system.*") == 0 || + StrnCaseCmp(name, "system.*!", 9) == 0 || + StrCaseCmp(name, "system.*+") == 0 || + StrnCaseCmp(name, "system.*+!", 10) == 0 || + StrCaseCmp(name, "system.nt_sec_desc.*") == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.*!", 21) == 0 || + StrCaseCmp(name, "system.nt_sec_desc.*+") == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.*+!", 22) == 0 || + StrCaseCmp(name, "system.nt_sec_desc.revision") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.owner") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.owner+") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.group") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.group+") == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl", 22) == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl+", 23) == 0 || + StrCaseCmp(name, "system.dos_attr.*") == 0 || + StrnCaseCmp(name, "system.dos_attr.*!", 18) == 0 || + StrCaseCmp(name, "system.dos_attr.mode") == 0 || + StrCaseCmp(name, "system.dos_attr.size") == 0 || + (attr_strings.create_time_attr != NULL && + StrCaseCmp(name, attr_strings.create_time_attr) == 0) || + StrCaseCmp(name, attr_strings.access_time_attr) == 0 || + StrCaseCmp(name, attr_strings.write_time_attr) == 0 || + StrCaseCmp(name, attr_strings.change_time_attr) == 0 || + StrCaseCmp(name, "system.dos_attr.inode") == 0) { + + /* Yup. */ + ret = cacl_get(context, talloc_tos(), srv, + ipc_srv == NULL ? NULL : ipc_srv->cli, + &ipc_srv->pol, path, + CONST_DISCARD(char *, name), + CONST_DISCARD(char *, value), size); + if (ret < 0 && errno == 0) { + errno = SMBC_errno(context, srv->cli); + } + TALLOC_FREE(frame); + return ret; + } + + /* Unsupported attribute name */ + errno = EINVAL; + TALLOC_FREE(frame); + return -1; +} + + +int +SMBC_removexattr_ctx(SMBCCTX *context, + const char *fname, + const char *name) +{ + int ret; + SMBCSRV *srv = NULL; + SMBCSRV *ipc_srv = NULL; + char *server = NULL; + char *share = NULL; + char *user = NULL; + char *password = NULL; + char *workgroup = NULL; + char *path = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!context || !context->internal->initialized) { + + errno = EINVAL; /* Best I can think of ... */ + TALLOC_FREE(frame); + return -1; + } + + if (!fname) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + DEBUG(4, ("smbc_removexattr(%s, %s)\n", fname, name)); + + if (SMBC_parse_path(frame, + context, + fname, + &workgroup, + &server, + &share, + &path, + &user, + &password, + NULL)) { + errno = EINVAL; + TALLOC_FREE(frame); + return -1; + } + + if (!user || user[0] == (char)0) { + user = talloc_strdup(frame, smbc_getUser(context)); + if (!user) { + errno = ENOMEM; + TALLOC_FREE(frame); + return -1; + } + } + + srv = SMBC_server(frame, context, True, + server, share, &workgroup, &user, &password); + if (!srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_server */ + } + + if (! srv->no_nt_session) { + ipc_srv = SMBC_attr_server(frame, context, server, share, + &workgroup, &user, &password); + if (! ipc_srv) { + srv->no_nt_session = True; + } + } else { + ipc_srv = NULL; + } + + if (! ipc_srv) { + TALLOC_FREE(frame); + return -1; /* errno set by SMBC_attr_server */ + } + + /* Are they asking to set the entire ACL? */ + if (StrCaseCmp(name, "system.nt_sec_desc.*") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.*+") == 0) { + + /* Yup. */ + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + NULL, SMBC_XATTR_MODE_REMOVE_ALL, 0); + TALLOC_FREE(frame); + return ret; + } + + /* + * Are they asking to remove one or more spceific security descriptor + * attributes? + */ + if (StrCaseCmp(name, "system.nt_sec_desc.revision") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.owner") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.owner+") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.group") == 0 || + StrCaseCmp(name, "system.nt_sec_desc.group+") == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl", 22) == 0 || + StrnCaseCmp(name, "system.nt_sec_desc.acl+", 23) == 0) { + + /* Yup. */ + ret = cacl_set(talloc_tos(), srv->cli, + ipc_srv->cli, &ipc_srv->pol, path, + name + 19, SMBC_XATTR_MODE_REMOVE, 0); + TALLOC_FREE(frame); + return ret; + } + + /* Unsupported attribute name */ + errno = EINVAL; + TALLOC_FREE(frame); + return -1; +} + +int +SMBC_listxattr_ctx(SMBCCTX *context, + const char *fname, + char *list, + size_t size) +{ + /* + * This isn't quite what listxattr() is supposed to do. This returns + * the complete set of attribute names, always, rather than only those + * attribute names which actually exist for a file. Hmmm... + */ + size_t retsize; + const char supported_old[] = + "system.*\0" + "system.*+\0" + "system.nt_sec_desc.revision\0" + "system.nt_sec_desc.owner\0" + "system.nt_sec_desc.owner+\0" + "system.nt_sec_desc.group\0" + "system.nt_sec_desc.group+\0" + "system.nt_sec_desc.acl.*\0" + "system.nt_sec_desc.acl\0" + "system.nt_sec_desc.acl+\0" + "system.nt_sec_desc.*\0" + "system.nt_sec_desc.*+\0" + "system.dos_attr.*\0" + "system.dos_attr.mode\0" + "system.dos_attr.c_time\0" + "system.dos_attr.a_time\0" + "system.dos_attr.m_time\0" + ; + const char supported_new[] = + "system.*\0" + "system.*+\0" + "system.nt_sec_desc.revision\0" + "system.nt_sec_desc.owner\0" + "system.nt_sec_desc.owner+\0" + "system.nt_sec_desc.group\0" + "system.nt_sec_desc.group+\0" + "system.nt_sec_desc.acl.*\0" + "system.nt_sec_desc.acl\0" + "system.nt_sec_desc.acl+\0" + "system.nt_sec_desc.*\0" + "system.nt_sec_desc.*+\0" + "system.dos_attr.*\0" + "system.dos_attr.mode\0" + "system.dos_attr.create_time\0" + "system.dos_attr.access_time\0" + "system.dos_attr.write_time\0" + "system.dos_attr.change_time\0" + ; + const char * supported; + + if (context->internal->full_time_names) { + supported = supported_new; + retsize = sizeof(supported_new); + } else { + supported = supported_old; + retsize = sizeof(supported_old); + } + + if (size == 0) { + return retsize; + } + + if (retsize > size) { + errno = ERANGE; + return -1; + } + + /* this can't be strcpy() because there are embedded null characters */ + memcpy(list, supported, retsize); + return retsize; +} diff --git a/source3/libsmb/namecache.c b/source3/libsmb/namecache.c new file mode 100644 index 0000000000..ba706e5ee2 --- /dev/null +++ b/source3/libsmb/namecache.c @@ -0,0 +1,394 @@ +/* + Unix SMB/CIFS implementation. + + NetBIOS name cache module on top of gencache mechanism. + + Copyright (C) Tim Potter 2002 + Copyright (C) Rafal Szczesniak 2002 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#define NBTKEY_FMT "NBT/%s#%02X" + +/** + * Initialise namecache system. Function calls gencache + * initialisation function to perform necessary actions + * + * @return true upon successful initialisation of the cache or + * false on failure + **/ + +bool namecache_enable(void) +{ + /* + * Check if name caching disabled by setting the name cache + * timeout to zero. + */ + + if (lp_name_cache_timeout() == 0) { + DEBUG(5, ("namecache_enable: disabling netbios name cache\n")); + return False; + } + + /* Init namecache by calling gencache initialisation */ + + if (!gencache_init()) { + DEBUG(2, ("namecache_enable: " + "Couldn't initialise namecache on top of gencache.\n")); + return False; + } + + /* I leave it for now, though I don't think we really + * need this (mimir, 27.09.2002) */ + DEBUG(5, ("namecache_enable: enabling netbios namecache, timeout %d " + "seconds\n", lp_name_cache_timeout())); + + return True; +} + +/** + * Shutdown namecache. Routine calls gencache close function + * to safely close gencache file. + * + * @return true upon successful shutdown of the cache or + * false on failure + **/ + +bool namecache_shutdown(void) +{ + if (!gencache_shutdown()) { + DEBUG(2, ("namecache_shutdown: " + "Couldn't close namecache on top of gencache.\n")); + return False; + } + + DEBUG(5, ("namecache_shutdown: " + "netbios namecache closed successfully.\n")); + return True; +} + +/** + * Generates a key for netbios name lookups on basis of + * netbios name and type. + * The caller must free returned key string when finished. + * + * @param name netbios name string (case insensitive) + * @param name_type netbios type of the name being looked up + * + * @return string consisted of uppercased name and appended + * type number + */ + +static char* namecache_key(const char *name, + int name_type) +{ + char *keystr; + asprintf_strupper_m(&keystr, NBTKEY_FMT, name, name_type); + + return keystr; +} + +/** + * Store a name(s) in the name cache + * + * @param name netbios names array + * @param name_type integer netbios name type + * @param num_names number of names being stored + * @param ip_list array of in_addr structures containing + * ip addresses being stored + **/ + +bool namecache_store(const char *name, + int name_type, + int num_names, + struct ip_service *ip_list) +{ + time_t expiry; + char *key, *value_string; + int i; + bool ret; + + /* + * we use gecache call to avoid annoying debug messages about + * initialised namecache again and again... + */ + if (!gencache_init()) { + return False; + } + + if (name_type > 255) { + return False; /* Don't store non-real name types. */ + } + + if ( DEBUGLEVEL >= 5 ) { + TALLOC_CTX *ctx = talloc_stackframe(); + char *addr = NULL; + + DEBUG(5, ("namecache_store: storing %d address%s for %s#%02x: ", + num_names, num_names == 1 ? "": "es", name, name_type)); + + for (i = 0; i < num_names; i++) { + addr = print_canonical_sockaddr(ctx, + &ip_list[i].ss); + if (!addr) { + continue; + } + DEBUGADD(5, ("%s%s", addr, + (i == (num_names - 1) ? "" : ","))); + + } + DEBUGADD(5, ("\n")); + TALLOC_FREE(ctx); + } + + key = namecache_key(name, name_type); + if (!key) { + return False; + } + + expiry = time(NULL) + lp_name_cache_timeout(); + + /* + * Generate string representation of ip addresses list + * First, store the number of ip addresses and then + * place each single ip + */ + if (!ipstr_list_make(&value_string, ip_list, num_names)) { + SAFE_FREE(key); + SAFE_FREE(value_string); + return false; + } + + /* set the entry */ + ret = gencache_set(key, value_string, expiry); + SAFE_FREE(key); + SAFE_FREE(value_string); + return ret; +} + +/** + * Look up a name in the cache. + * + * @param name netbios name to look up for + * @param name_type netbios name type of @param name + * @param ip_list mallocated list of IP addresses if found in the cache, + * NULL otherwise + * @param num_names number of entries found + * + * @return true upon successful fetch or + * false if name isn't found in the cache or has expired + **/ + +bool namecache_fetch(const char *name, + int name_type, + struct ip_service **ip_list, + int *num_names) +{ + char *key, *value; + time_t timeout; + + /* exit now if null pointers were passed as they're required further */ + if (!ip_list || !num_names) { + return False; + } + + if (!gencache_init()) { + return False; + } + + if (name_type > 255) { + return False; /* Don't fetch non-real name types. */ + } + + *num_names = 0; + + /* + * Use gencache interface - lookup the key + */ + key = namecache_key(name, name_type); + if (!key) { + return False; + } + + if (!gencache_get(key, &value, &timeout)) { + DEBUG(5, ("no entry for %s#%02X found.\n", name, name_type)); + SAFE_FREE(key); + return False; + } else { + DEBUG(5, ("name %s#%02X found.\n", name, name_type)); + } + + /* + * Split up the stored value into the list of IP adresses + */ + *num_names = ipstr_list_parse(value, ip_list); + + SAFE_FREE(key); + SAFE_FREE(value); + + return *num_names > 0; /* true only if some ip has been fetched */ +} + +/** + * Remove a namecache entry. Needed for site support. + * + **/ + +bool namecache_delete(const char *name, int name_type) +{ + bool ret; + char *key; + + if (!gencache_init()) + return False; + + if (name_type > 255) { + return False; /* Don't fetch non-real name types. */ + } + + key = namecache_key(name, name_type); + if (!key) { + return False; + } + ret = gencache_del(key); + SAFE_FREE(key); + return ret; +} + +/** + * Delete single namecache entry. Look at the + * gencache_iterate definition. + * + **/ + +static void flush_netbios_name(const char *key, + const char *value, + time_t timeout, + void *dptr) +{ + gencache_del(key); + DEBUG(5, ("Deleting entry %s\n", key)); +} + +/** + * Flush all names from the name cache. + * It's done by gencache_iterate() + * + * @return true upon successful deletion or + * false in case of an error + **/ + +void namecache_flush(void) +{ + if (!gencache_init()) { + return; + } + + /* + * iterate through each NBT cache's entry and flush it + * by flush_netbios_name function + */ + gencache_iterate(flush_netbios_name, NULL, "NBT/*"); + DEBUG(5, ("Namecache flushed\n")); +} + +/* Construct a name status record key. */ + +static char *namecache_status_record_key(const char *name, + int name_type1, + int name_type2, + const struct sockaddr_storage *keyip) +{ + char addr[INET6_ADDRSTRLEN]; + char *keystr; + + print_sockaddr(addr, sizeof(addr), keyip); + asprintf_strupper_m(&keystr, "NBT/%s#%02X.%02X.%s", name, + name_type1, name_type2, addr); + return keystr; +} + +/* Store a name status record. */ + +bool namecache_status_store(const char *keyname, int keyname_type, + int name_type, const struct sockaddr_storage *keyip, + const char *srvname) +{ + char *key; + time_t expiry; + bool ret; + + if (!gencache_init()) { + return False; + } + + key = namecache_status_record_key(keyname, keyname_type, + name_type, keyip); + if (!key) + return False; + + expiry = time(NULL) + lp_name_cache_timeout(); + ret = gencache_set(key, srvname, expiry); + + if (ret) { + DEBUG(5, ("namecache_status_store: entry %s -> %s\n", + key, srvname )); + } else { + DEBUG(5, ("namecache_status_store: entry %s store failed.\n", + key )); + } + + SAFE_FREE(key); + return ret; +} + +/* Fetch a name status record. */ + +bool namecache_status_fetch(const char *keyname, + int keyname_type, + int name_type, + const struct sockaddr_storage *keyip, + char *srvname_out) +{ + char *key = NULL; + char *value = NULL; + time_t timeout; + + if (!gencache_init()) + return False; + + key = namecache_status_record_key(keyname, keyname_type, + name_type, keyip); + if (!key) + return False; + + if (!gencache_get(key, &value, &timeout)) { + DEBUG(5, ("namecache_status_fetch: no entry for %s found.\n", + key)); + SAFE_FREE(key); + return False; + } else { + DEBUG(5, ("namecache_status_fetch: key %s -> %s\n", + key, value )); + } + + strlcpy(srvname_out, value, 16); + SAFE_FREE(key); + SAFE_FREE(value); + return True; +} diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c new file mode 100644 index 0000000000..24d7ee1a9c --- /dev/null +++ b/source3/libsmb/namequery.c @@ -0,0 +1,2145 @@ +/* + Unix SMB/CIFS implementation. + name query routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* nmbd.c sets this to True. */ +bool global_in_nmbd = False; + +/**************************** + * SERVER AFFINITY ROUTINES * + ****************************/ + + /* Server affinity is the concept of preferring the last domain + controller with whom you had a successful conversation */ + +/**************************************************************************** +****************************************************************************/ +#define SAFKEY_FMT "SAF/DOMAIN/%s" +#define SAF_TTL 900 + +static char *saf_key(const char *domain) +{ + char *keystr; + + asprintf_strupper_m(&keystr, SAFKEY_FMT, domain); + + return keystr; +} + +/**************************************************************************** +****************************************************************************/ + +bool saf_store( const char *domain, const char *servername ) +{ + char *key; + time_t expire; + bool ret = False; + + if ( !domain || !servername ) { + DEBUG(2,("saf_store: " + "Refusing to store empty domain or servername!\n")); + return False; + } + + if ( (strlen(domain) == 0) || (strlen(servername) == 0) ) { + DEBUG(0,("saf_store: " + "refusing to store 0 length domain or servername!\n")); + return False; + } + + if ( !gencache_init() ) + return False; + + key = saf_key( domain ); + expire = time( NULL ) + SAF_TTL; + + DEBUG(10,("saf_store: domain = [%s], server = [%s], expire = [%u]\n", + domain, servername, (unsigned int)expire )); + + ret = gencache_set( key, servername, expire ); + + SAFE_FREE( key ); + + return ret; +} + +bool saf_delete( const char *domain ) +{ + char *key; + bool ret = False; + + if ( !domain ) { + DEBUG(2,("saf_delete: Refusing to delete empty domain\n")); + return False; + } + + if ( !gencache_init() ) + return False; + + key = saf_key(domain); + ret = gencache_del(key); + + if (ret) { + DEBUG(10,("saf_delete: domain = [%s]\n", domain )); + } + + SAFE_FREE( key ); + + return ret; +} + +/**************************************************************************** +****************************************************************************/ + +char *saf_fetch( const char *domain ) +{ + char *server = NULL; + time_t timeout; + bool ret = False; + char *key = NULL; + + if ( !domain || strlen(domain) == 0) { + DEBUG(2,("saf_fetch: Empty domain name!\n")); + return NULL; + } + + if ( !gencache_init() ) + return False; + + key = saf_key( domain ); + + ret = gencache_get( key, &server, &timeout ); + + SAFE_FREE( key ); + + if ( !ret ) { + DEBUG(5,("saf_fetch: failed to find server for \"%s\" domain\n", + domain )); + } else { + DEBUG(5,("saf_fetch: Returning \"%s\" for \"%s\" domain\n", + server, domain )); + } + + return server; +} + +/**************************************************************************** + Generate a random trn_id. +****************************************************************************/ + +static int generate_trn_id(void) +{ + uint16 id; + + generate_random_buffer((uint8 *)&id, sizeof(id)); + + return id % (unsigned)0x7FFF; +} + +/**************************************************************************** + Parse a node status response into an array of structures. +****************************************************************************/ + +static NODE_STATUS_STRUCT *parse_node_status(char *p, + int *num_names, + struct node_status_extra *extra) +{ + NODE_STATUS_STRUCT *ret; + int i; + + *num_names = CVAL(p,0); + + if (*num_names == 0) + return NULL; + + ret = SMB_MALLOC_ARRAY(NODE_STATUS_STRUCT,*num_names); + if (!ret) + return NULL; + + p++; + for (i=0;i< *num_names;i++) { + StrnCpy(ret[i].name,p,15); + trim_char(ret[i].name,'\0',' '); + ret[i].type = CVAL(p,15); + ret[i].flags = p[16]; + p += 18; + DEBUG(10, ("%s#%02x: flags = 0x%02x\n", ret[i].name, + ret[i].type, ret[i].flags)); + } + /* + * Also, pick up the MAC address ... + */ + if (extra) { + memcpy(&extra->mac_addr, p, 6); /* Fill in the mac addr */ + } + return ret; +} + + +/**************************************************************************** + Do a NBT node status query on an open socket and return an array of + structures holding the returned names or NULL if the query failed. +**************************************************************************/ + +NODE_STATUS_STRUCT *node_status_query(int fd, + struct nmb_name *name, + const struct sockaddr_storage *to_ss, + int *num_names, + struct node_status_extra *extra) +{ + bool found=False; + int retries = 2; + int retry_time = 2000; + struct timeval tval; + struct packet_struct p; + struct packet_struct *p2; + struct nmb_packet *nmb = &p.packet.nmb; + NODE_STATUS_STRUCT *ret; + + ZERO_STRUCT(p); + + if (to_ss->ss_family != AF_INET) { + /* Can't do node status to IPv6 */ + return NULL; + } + nmb->header.name_trn_id = generate_trn_id(); + nmb->header.opcode = 0; + nmb->header.response = false; + nmb->header.nm_flags.bcast = false; + nmb->header.nm_flags.recursion_available = false; + nmb->header.nm_flags.recursion_desired = false; + nmb->header.nm_flags.trunc = false; + nmb->header.nm_flags.authoritative = false; + nmb->header.rcode = 0; + nmb->header.qdcount = 1; + nmb->header.ancount = 0; + nmb->header.nscount = 0; + nmb->header.arcount = 0; + nmb->question.question_name = *name; + nmb->question.question_type = 0x21; + nmb->question.question_class = 0x1; + + p.ip = ((const struct sockaddr_in *)to_ss)->sin_addr; + p.port = NMB_PORT; + p.fd = fd; + p.timestamp = time(NULL); + p.packet_type = NMB_PACKET; + + GetTimeOfDay(&tval); + + if (!send_packet(&p)) + return NULL; + + retries--; + + while (1) { + struct timeval tval2; + GetTimeOfDay(&tval2); + if (TvalDiff(&tval,&tval2) > retry_time) { + if (!retries) + break; + if (!found && !send_packet(&p)) + return NULL; + GetTimeOfDay(&tval); + retries--; + } + + if ((p2=receive_nmb_packet(fd,90,nmb->header.name_trn_id))) { + struct nmb_packet *nmb2 = &p2->packet.nmb; + debug_nmb_packet(p2); + + if (nmb2->header.opcode != 0 || + nmb2->header.nm_flags.bcast || + nmb2->header.rcode || + !nmb2->header.ancount || + nmb2->answers->rr_type != 0x21) { + /* XXXX what do we do with this? could be a + redirect, but we'll discard it for the + moment */ + free_packet(p2); + continue; + } + + ret = parse_node_status(&nmb2->answers->rdata[0], + num_names, extra); + free_packet(p2); + return ret; + } + } + + return NULL; +} + +/**************************************************************************** + Find the first type XX name in a node status reply - used for finding + a servers name given its IP. Return the matched name in *name. +**************************************************************************/ + +bool name_status_find(const char *q_name, + int q_type, + int type, + const struct sockaddr_storage *to_ss, + fstring name) +{ + char addr[INET6_ADDRSTRLEN]; + struct sockaddr_storage ss; + NODE_STATUS_STRUCT *status = NULL; + struct nmb_name nname; + int count, i; + int sock; + bool result = false; + + if (lp_disable_netbios()) { + DEBUG(5,("name_status_find(%s#%02x): netbios is disabled\n", + q_name, q_type)); + return False; + } + + print_sockaddr(addr, sizeof(addr), to_ss); + + DEBUG(10, ("name_status_find: looking up %s#%02x at %s\n", q_name, + q_type, addr)); + + /* Check the cache first. */ + + if (namecache_status_fetch(q_name, q_type, type, to_ss, name)) { + return True; + } + + if (to_ss->ss_family != AF_INET) { + /* Can't do node status to IPv6 */ + return false; + } + + if (!interpret_string_addr(&ss, lp_socket_address(), + AI_NUMERICHOST|AI_PASSIVE)) { + zero_addr(&ss); + } + + sock = open_socket_in(SOCK_DGRAM, 0, 3, &ss, True); + if (sock == -1) + goto done; + + /* W2K PDC's seem not to respond to '*'#0. JRA */ + make_nmb_name(&nname, q_name, q_type); + status = node_status_query(sock, &nname, to_ss, &count, NULL); + close(sock); + if (!status) + goto done; + + for (i=0;i<count;i++) { + if (status[i].type == type) + break; + } + if (i == count) + goto done; + + pull_ascii_nstring(name, sizeof(fstring), status[i].name); + + /* Store the result in the cache. */ + /* but don't store an entry for 0x1c names here. Here we have + a single host and DOMAIN<0x1c> names should be a list of hosts */ + + if ( q_type != 0x1c ) { + namecache_status_store(q_name, q_type, type, to_ss, name); + } + + result = true; + + done: + SAFE_FREE(status); + + DEBUG(10, ("name_status_find: name %sfound", result ? "" : "not ")); + + if (result) + DEBUGADD(10, (", name %s ip address is %s", name, addr)); + + DEBUG(10, ("\n")); + + return result; +} + +/* + comparison function used by sort_addr_list +*/ + +static int addr_compare(const struct sockaddr_storage *ss1, + const struct sockaddr_storage *ss2) +{ + int max_bits1=0, max_bits2=0; + int num_interfaces = iface_count(); + int i; + + /* Sort IPv6 addresses first. */ + if (ss1->ss_family != ss2->ss_family) { + if (ss2->ss_family == AF_INET) { + return -1; + } else { + return 1; + } + } + + /* Here we know both addresses are of the same + * family. */ + + for (i=0;i<num_interfaces;i++) { + const struct sockaddr_storage *pss = iface_n_bcast(i); + unsigned char *p_ss1 = NULL; + unsigned char *p_ss2 = NULL; + unsigned char *p_if = NULL; + size_t len = 0; + int bits1, bits2; + + if (pss->ss_family != ss1->ss_family) { + /* Ignore interfaces of the wrong type. */ + continue; + } + if (pss->ss_family == AF_INET) { + p_if = (unsigned char *) + &((const struct sockaddr_in *)pss)->sin_addr; + p_ss1 = (unsigned char *) + &((const struct sockaddr_in *)ss1)->sin_addr; + p_ss2 = (unsigned char *) + &((const struct sockaddr_in *)ss2)->sin_addr; + len = 4; + } +#if defined(HAVE_IPV6) + if (pss->ss_family == AF_INET6) { + p_if = (unsigned char *) + &((const struct sockaddr_in6 *)pss)->sin6_addr; + p_ss1 = (unsigned char *) + &((const struct sockaddr_in6 *)ss1)->sin6_addr; + p_ss2 = (unsigned char *) + &((const struct sockaddr_in6 *)ss2)->sin6_addr; + len = 16; + } +#endif + if (!p_ss1 || !p_ss2 || !p_if || len == 0) { + continue; + } + bits1 = matching_len_bits(p_ss1, p_if, len); + bits2 = matching_len_bits(p_ss2, p_if, len); + max_bits1 = MAX(bits1, max_bits1); + max_bits2 = MAX(bits2, max_bits2); + } + + /* Bias towards directly reachable IPs */ + if (iface_local(ss1)) { + if (ss1->ss_family == AF_INET) { + max_bits1 += 32; + } else { + max_bits1 += 128; + } + } + if (iface_local(ss2)) { + if (ss2->ss_family == AF_INET) { + max_bits2 += 32; + } else { + max_bits2 += 128; + } + } + return max_bits2 - max_bits1; +} + +/******************************************************************* + compare 2 ldap IPs by nearness to our interfaces - used in qsort +*******************************************************************/ + +int ip_service_compare(struct ip_service *ss1, struct ip_service *ss2) +{ + int result; + + if ((result = addr_compare(&ss1->ss, &ss2->ss)) != 0) { + return result; + } + + if (ss1->port > ss2->port) { + return 1; + } + + if (ss1->port < ss2->port) { + return -1; + } + + return 0; +} + +/* + sort an IP list so that names that are close to one of our interfaces + are at the top. This prevents the problem where a WINS server returns an IP + that is not reachable from our subnet as the first match +*/ + +static void sort_addr_list(struct sockaddr_storage *sslist, int count) +{ + if (count <= 1) { + return; + } + + qsort(sslist, count, sizeof(struct sockaddr_storage), + QSORT_CAST addr_compare); +} + +static void sort_service_list(struct ip_service *servlist, int count) +{ + if (count <= 1) { + return; + } + + qsort(servlist, count, sizeof(struct ip_service), + QSORT_CAST ip_service_compare); +} + +/********************************************************************** + Remove any duplicate address/port pairs in the list + *********************************************************************/ + +static int remove_duplicate_addrs2(struct ip_service *iplist, int count ) +{ + int i, j; + + DEBUG(10,("remove_duplicate_addrs2: " + "looking for duplicate address/port pairs\n")); + + /* one loop to remove duplicates */ + for ( i=0; i<count; i++ ) { + if ( is_zero_addr(&iplist[i].ss)) { + continue; + } + + for ( j=i+1; j<count; j++ ) { + if (addr_equal(&iplist[i].ss, &iplist[j].ss) && + iplist[i].port == iplist[j].port) { + zero_addr(&iplist[j].ss); + } + } + } + + /* one loop to clean up any holes we left */ + /* first ip should never be a zero_ip() */ + for (i = 0; i<count; ) { + if (is_zero_addr(&iplist[i].ss) ) { + if (i != count-1) { + memmove(&iplist[i], &iplist[i+1], + (count - i - 1)*sizeof(iplist[i])); + } + count--; + continue; + } + i++; + } + + return count; +} + +/**************************************************************************** + Do a netbios name query to find someones IP. + Returns an array of IP addresses or NULL if none. + *count will be set to the number of addresses returned. + *timed_out is set if we failed by timing out +****************************************************************************/ + +struct sockaddr_storage *name_query(int fd, + const char *name, + int name_type, + bool bcast, + bool recurse, + const struct sockaddr_storage *to_ss, + int *count, + int *flags, + bool *timed_out) +{ + bool found=false; + int i, retries = 3; + int retry_time = bcast?250:2000; + struct timeval tval; + struct packet_struct p; + struct packet_struct *p2; + struct nmb_packet *nmb = &p.packet.nmb; + struct sockaddr_storage *ss_list = NULL; + + if (lp_disable_netbios()) { + DEBUG(5,("name_query(%s#%02x): netbios is disabled\n", + name, name_type)); + return NULL; + } + + if (to_ss->ss_family != AF_INET) { + return NULL; + } + + if (timed_out) { + *timed_out = false; + } + + memset((char *)&p,'\0',sizeof(p)); + (*count) = 0; + (*flags) = 0; + + nmb->header.name_trn_id = generate_trn_id(); + nmb->header.opcode = 0; + nmb->header.response = false; + nmb->header.nm_flags.bcast = bcast; + nmb->header.nm_flags.recursion_available = false; + nmb->header.nm_flags.recursion_desired = recurse; + nmb->header.nm_flags.trunc = false; + nmb->header.nm_flags.authoritative = false; + nmb->header.rcode = 0; + nmb->header.qdcount = 1; + nmb->header.ancount = 0; + nmb->header.nscount = 0; + nmb->header.arcount = 0; + + make_nmb_name(&nmb->question.question_name,name,name_type); + + nmb->question.question_type = 0x20; + nmb->question.question_class = 0x1; + + p.ip = ((struct sockaddr_in *)to_ss)->sin_addr; + p.port = NMB_PORT; + p.fd = fd; + p.timestamp = time(NULL); + p.packet_type = NMB_PACKET; + + GetTimeOfDay(&tval); + + if (!send_packet(&p)) + return NULL; + + retries--; + + while (1) { + struct timeval tval2; + + GetTimeOfDay(&tval2); + if (TvalDiff(&tval,&tval2) > retry_time) { + if (!retries) + break; + if (!found && !send_packet(&p)) + return NULL; + GetTimeOfDay(&tval); + retries--; + } + + if ((p2=receive_nmb_packet(fd,90,nmb->header.name_trn_id))) { + struct nmb_packet *nmb2 = &p2->packet.nmb; + debug_nmb_packet(p2); + + /* If we get a Negative Name Query Response from a WINS + * server, we should report it and give up. + */ + if( 0 == nmb2->header.opcode /* A query response */ + && !(bcast) /* from a WINS server */ + && nmb2->header.rcode /* Error returned */ + ) { + + if( DEBUGLVL( 3 ) ) { + /* Only executed if DEBUGLEVEL >= 3 */ + dbgtext( "Negative name query " + "response, rcode 0x%02x: ", + nmb2->header.rcode ); + switch( nmb2->header.rcode ) { + case 0x01: + dbgtext( "Request " + "was invalidly formatted.\n" ); + break; + case 0x02: + dbgtext( "Problem with NBNS, " + "cannot process name.\n"); + break; + case 0x03: + dbgtext( "The name requested " + "does not exist.\n" ); + break; + case 0x04: + dbgtext( "Unsupported request " + "error.\n" ); + break; + case 0x05: + dbgtext( "Query refused " + "error.\n" ); + break; + default: + dbgtext( "Unrecognized error " + "code.\n" ); + break; + } + } + free_packet(p2); + return( NULL ); + } + + if (nmb2->header.opcode != 0 || + nmb2->header.nm_flags.bcast || + nmb2->header.rcode || + !nmb2->header.ancount) { + /* + * XXXX what do we do with this? Could be a + * redirect, but we'll discard it for the + * moment. + */ + free_packet(p2); + continue; + } + + ss_list = SMB_REALLOC_ARRAY(ss_list, + struct sockaddr_storage, + (*count) + + nmb2->answers->rdlength/6); + + if (!ss_list) { + DEBUG(0,("name_query: Realloc failed.\n")); + free_packet(p2); + return NULL; + } + + DEBUG(2,("Got a positive name query response " + "from %s ( ", + inet_ntoa(p2->ip))); + + for (i=0;i<nmb2->answers->rdlength/6;i++) { + struct in_addr ip; + putip((char *)&ip,&nmb2->answers->rdata[2+i*6]); + in_addr_to_sockaddr_storage(&ss_list[(*count)], + ip); + DEBUGADD(2,("%s ",inet_ntoa(ip))); + (*count)++; + } + DEBUGADD(2,(")\n")); + + found=true; + retries=0; + /* We add the flags back ... */ + if (nmb2->header.response) + (*flags) |= NM_FLAGS_RS; + if (nmb2->header.nm_flags.authoritative) + (*flags) |= NM_FLAGS_AA; + if (nmb2->header.nm_flags.trunc) + (*flags) |= NM_FLAGS_TC; + if (nmb2->header.nm_flags.recursion_desired) + (*flags) |= NM_FLAGS_RD; + if (nmb2->header.nm_flags.recursion_available) + (*flags) |= NM_FLAGS_RA; + if (nmb2->header.nm_flags.bcast) + (*flags) |= NM_FLAGS_B; + free_packet(p2); + /* + * If we're doing a unicast lookup we only + * expect one reply. Don't wait the full 2 + * seconds if we got one. JRA. + */ + if(!bcast && found) + break; + } + } + + /* only set timed_out if we didn't fund what we where looking for*/ + + if ( !found && timed_out ) { + *timed_out = true; + } + + /* sort the ip list so we choose close servers first if possible */ + sort_addr_list(ss_list, *count); + + return ss_list; +} + +/******************************************************** + Start parsing the lmhosts file. +*********************************************************/ + +XFILE *startlmhosts(const char *fname) +{ + XFILE *fp = x_fopen(fname,O_RDONLY, 0); + if (!fp) { + DEBUG(4,("startlmhosts: Can't open lmhosts file %s. " + "Error was %s\n", + fname, strerror(errno))); + return NULL; + } + return fp; +} + +/******************************************************** + Parse the next line in the lmhosts file. +*********************************************************/ + +bool getlmhostsent(TALLOC_CTX *ctx, XFILE *fp, char **pp_name, int *name_type, + struct sockaddr_storage *pss) +{ + char line[1024]; + + *pp_name = NULL; + + while(!x_feof(fp) && !x_ferror(fp)) { + char *ip = NULL; + char *flags = NULL; + char *extra = NULL; + char *name = NULL; + const char *ptr; + char *ptr1 = NULL; + int count = 0; + + *name_type = -1; + + if (!fgets_slash(line,sizeof(line),fp)) { + continue; + } + + if (*line == '#') { + continue; + } + + ptr = line; + + if (next_token_talloc(ctx, &ptr, &ip, NULL)) + ++count; + if (next_token_talloc(ctx, &ptr, &name, NULL)) + ++count; + if (next_token_talloc(ctx, &ptr, &flags, NULL)) + ++count; + if (next_token_talloc(ctx, &ptr, &extra, NULL)) + ++count; + + if (count <= 0) + continue; + + if (count > 0 && count < 2) { + DEBUG(0,("getlmhostsent: Ill formed hosts line [%s]\n", + line)); + continue; + } + + if (count >= 4) { + DEBUG(0,("getlmhostsent: too many columns " + "in lmhosts file (obsolete syntax)\n")); + continue; + } + + if (!flags) { + flags = talloc_strdup(ctx, ""); + if (!flags) { + continue; + } + } + + DEBUG(4, ("getlmhostsent: lmhost entry: %s %s %s\n", + ip, name, flags)); + + if (strchr_m(flags,'G') || strchr_m(flags,'S')) { + DEBUG(0,("getlmhostsent: group flag " + "in lmhosts ignored (obsolete)\n")); + continue; + } + + if (!interpret_string_addr(pss, ip, AI_NUMERICHOST)) { + DEBUG(0,("getlmhostsent: invalid address " + "%s.\n", ip)); + } + + /* Extra feature. If the name ends in '#XX', + * where XX is a hex number, then only add that name type. */ + if((ptr1 = strchr_m(name, '#')) != NULL) { + char *endptr; + ptr1++; + + *name_type = (int)strtol(ptr1, &endptr, 16); + if(!*ptr1 || (endptr == ptr1)) { + DEBUG(0,("getlmhostsent: invalid name " + "%s containing '#'.\n", name)); + continue; + } + + *(--ptr1) = '\0'; /* Truncate at the '#' */ + } + + *pp_name = talloc_strdup(ctx, name); + if (!*pp_name) { + return false; + } + return true; + } + + return false; +} + +/******************************************************** + Finish parsing the lmhosts file. +*********************************************************/ + +void endlmhosts(XFILE *fp) +{ + x_fclose(fp); +} + +/******************************************************** + convert an array if struct sockaddr_storage to struct ip_service + return false on failure. Port is set to PORT_NONE; +*********************************************************/ + +static bool convert_ss2service(struct ip_service **return_iplist, + const struct sockaddr_storage *ss_list, + int count) +{ + int i; + + if ( count==0 || !ss_list ) + return False; + + /* copy the ip address; port will be PORT_NONE */ + if ((*return_iplist = SMB_MALLOC_ARRAY(struct ip_service, count)) == + NULL) { + DEBUG(0,("convert_ip2service: malloc failed " + "for %d enetries!\n", count )); + return False; + } + + for ( i=0; i<count; i++ ) { + (*return_iplist)[i].ss = ss_list[i]; + (*return_iplist)[i].port = PORT_NONE; + } + + return true; +} + +/******************************************************** + Resolve via "bcast" method. +*********************************************************/ + +NTSTATUS name_resolve_bcast(const char *name, + int name_type, + struct ip_service **return_iplist, + int *return_count) +{ + int sock, i; + int num_interfaces = iface_count(); + struct sockaddr_storage *ss_list; + struct sockaddr_storage ss; + NTSTATUS status; + + if (lp_disable_netbios()) { + DEBUG(5,("name_resolve_bcast(%s#%02x): netbios is disabled\n", + name, name_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + *return_iplist = NULL; + *return_count = 0; + + /* + * "bcast" means do a broadcast lookup on all the local interfaces. + */ + + DEBUG(3,("name_resolve_bcast: Attempting broadcast lookup " + "for name %s<0x%x>\n", name, name_type)); + + if (!interpret_string_addr(&ss, lp_socket_address(), + AI_NUMERICHOST|AI_PASSIVE)) { + zero_addr(&ss); + } + + sock = open_socket_in( SOCK_DGRAM, 0, 3, &ss, true ); + if (sock == -1) { + return NT_STATUS_UNSUCCESSFUL; + } + + set_socket_options(sock,"SO_BROADCAST"); + /* + * Lookup the name on all the interfaces, return on + * the first successful match. + */ + for( i = num_interfaces-1; i >= 0; i--) { + const struct sockaddr_storage *pss = iface_n_bcast(i); + int flags; + + /* Done this way to fix compiler error on IRIX 5.x */ + if (!pss) { + continue; + } + ss_list = name_query(sock, name, name_type, true, + true, pss, return_count, &flags, NULL); + if (ss_list) { + goto success; + } + } + + /* failed - no response */ + + close(sock); + return NT_STATUS_UNSUCCESSFUL; + +success: + + status = NT_STATUS_OK; + if (!convert_ss2service(return_iplist, ss_list, *return_count) ) + status = NT_STATUS_INVALID_PARAMETER; + + SAFE_FREE(ss_list); + close(sock); + return status; +} + +/******************************************************** + Resolve via "wins" method. +*********************************************************/ + +NTSTATUS resolve_wins(const char *name, + int name_type, + struct ip_service **return_iplist, + int *return_count) +{ + int sock, t, i; + char **wins_tags; + struct sockaddr_storage src_ss, *ss_list = NULL; + struct in_addr src_ip; + NTSTATUS status; + + if (lp_disable_netbios()) { + DEBUG(5,("resolve_wins(%s#%02x): netbios is disabled\n", + name, name_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + *return_iplist = NULL; + *return_count = 0; + + DEBUG(3,("resolve_wins: Attempting wins lookup for name %s<0x%x>\n", + name, name_type)); + + if (wins_srv_count() < 1) { + DEBUG(3,("resolve_wins: WINS server resolution selected " + "and no WINS servers listed.\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* we try a lookup on each of the WINS tags in turn */ + wins_tags = wins_srv_tags(); + + if (!wins_tags) { + /* huh? no tags?? give up in disgust */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* the address we will be sending from */ + if (!interpret_string_addr(&src_ss, lp_socket_address(), + AI_NUMERICHOST|AI_PASSIVE)) { + zero_addr(&src_ss); + } + + if (src_ss.ss_family != AF_INET) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &src_ss); + DEBUG(3,("resolve_wins: cannot receive WINS replies " + "on IPv6 address %s\n", + addr)); + wins_srv_tags_free(wins_tags); + return NT_STATUS_INVALID_PARAMETER; + } + + src_ip = ((struct sockaddr_in *)&src_ss)->sin_addr; + + /* in the worst case we will try every wins server with every + tag! */ + for (t=0; wins_tags && wins_tags[t]; t++) { + int srv_count = wins_srv_count_tag(wins_tags[t]); + for (i=0; i<srv_count; i++) { + struct sockaddr_storage wins_ss; + struct in_addr wins_ip; + int flags; + bool timed_out; + + wins_ip = wins_srv_ip_tag(wins_tags[t], src_ip); + + if (global_in_nmbd && ismyip_v4(wins_ip)) { + /* yikes! we'll loop forever */ + continue; + } + + /* skip any that have been unresponsive lately */ + if (wins_srv_is_dead(wins_ip, src_ip)) { + continue; + } + + DEBUG(3,("resolve_wins: using WINS server %s " + "and tag '%s'\n", + inet_ntoa(wins_ip), wins_tags[t])); + + sock = open_socket_in(SOCK_DGRAM, 0, 3, &src_ss, true); + if (sock == -1) { + continue; + } + + in_addr_to_sockaddr_storage(&wins_ss, wins_ip); + ss_list = name_query(sock, + name, + name_type, + false, + true, + &wins_ss, + return_count, + &flags, + &timed_out); + + /* exit loop if we got a list of addresses */ + + if (ss_list) + goto success; + + close(sock); + + if (timed_out) { + /* Timed out wating for WINS server to respond. + * Mark it dead. */ + wins_srv_died(wins_ip, src_ip); + } else { + /* The name definately isn't in this + group of WINS servers. + goto the next group */ + break; + } + } + } + + wins_srv_tags_free(wins_tags); + return NT_STATUS_NO_LOGON_SERVERS; + +success: + + status = NT_STATUS_OK; + if (!convert_ss2service(return_iplist, ss_list, *return_count)) + status = NT_STATUS_INVALID_PARAMETER; + + SAFE_FREE(ss_list); + wins_srv_tags_free(wins_tags); + close(sock); + + return status; +} + +/******************************************************** + Resolve via "lmhosts" method. +*********************************************************/ + +static NTSTATUS resolve_lmhosts(const char *name, int name_type, + struct ip_service **return_iplist, + int *return_count) +{ + /* + * "lmhosts" means parse the local lmhosts file. + */ + + XFILE *fp; + char *lmhost_name = NULL; + int name_type2; + struct sockaddr_storage return_ss; + NTSTATUS status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + TALLOC_CTX *ctx = NULL; + + *return_iplist = NULL; + *return_count = 0; + + DEBUG(3,("resolve_lmhosts: " + "Attempting lmhosts lookup for name %s<0x%x>\n", + name, name_type)); + + fp = startlmhosts(get_dyn_LMHOSTSFILE()); + + if ( fp == NULL ) + return NT_STATUS_NO_SUCH_FILE; + + ctx = talloc_init("resolve_lmhosts"); + if (!ctx) { + endlmhosts(fp); + return NT_STATUS_NO_MEMORY; + } + + while (getlmhostsent(ctx, fp, &lmhost_name, &name_type2, &return_ss)) { + + if (!strequal(name, lmhost_name)) { + TALLOC_FREE(lmhost_name); + continue; + } + + if ((name_type2 != -1) && (name_type != name_type2)) { + TALLOC_FREE(lmhost_name); + continue; + } + + *return_iplist = SMB_REALLOC_ARRAY((*return_iplist), + struct ip_service, + (*return_count)+1); + + if ((*return_iplist) == NULL) { + TALLOC_FREE(ctx); + endlmhosts(fp); + DEBUG(3,("resolve_lmhosts: malloc fail !\n")); + return NT_STATUS_NO_MEMORY; + } + + (*return_iplist)[*return_count].ss = return_ss; + (*return_iplist)[*return_count].port = PORT_NONE; + *return_count += 1; + + /* we found something */ + status = NT_STATUS_OK; + + /* Multiple names only for DC lookup */ + if (name_type != 0x1c) + break; + } + + TALLOC_FREE(ctx); + endlmhosts(fp); + return status; +} + + +/******************************************************** + Resolve via "hosts" method. +*********************************************************/ + +static NTSTATUS resolve_hosts(const char *name, int name_type, + struct ip_service **return_iplist, + int *return_count) +{ + /* + * "host" means do a localhost, or dns lookup. + */ + struct addrinfo hints; + struct addrinfo *ailist = NULL; + struct addrinfo *res = NULL; + int ret = -1; + int i = 0; + + if ( name_type != 0x20 && name_type != 0x0) { + DEBUG(5, ("resolve_hosts: not appropriate " + "for name type <0x%x>\n", + name_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + *return_iplist = NULL; + *return_count = 0; + + DEBUG(3,("resolve_hosts: Attempting host lookup for name %s<0x%x>\n", + name, name_type)); + + ZERO_STRUCT(hints); + /* By default make sure it supports TCP. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + +#if !defined(HAVE_IPV6) + /* Unless we have IPv6, we really only want IPv4 addresses back. */ + hints.ai_family = AF_INET; +#endif + + ret = getaddrinfo(name, + NULL, + &hints, + &ailist); + if (ret) { + DEBUG(3,("resolve_hosts: getaddrinfo failed for name %s [%s]\n", + name, + gai_strerror(ret) )); + } + + for (res = ailist; res; res = res->ai_next) { + struct sockaddr_storage ss; + + if (!res->ai_addr || res->ai_addrlen == 0) { + continue; + } + + ZERO_STRUCT(ss); + memcpy(&ss, res->ai_addr, res->ai_addrlen); + + *return_count += 1; + + *return_iplist = SMB_REALLOC_ARRAY(*return_iplist, + struct ip_service, + *return_count); + if (!*return_iplist) { + DEBUG(3,("resolve_hosts: malloc fail !\n")); + freeaddrinfo(ailist); + return NT_STATUS_NO_MEMORY; + } + (*return_iplist)[i].ss = ss; + (*return_iplist)[i].port = PORT_NONE; + i++; + } + if (ailist) { + freeaddrinfo(ailist); + } + if (*return_count) { + return NT_STATUS_OK; + } + return NT_STATUS_UNSUCCESSFUL; +} + +/******************************************************** + Resolve via "ADS" method. +*********************************************************/ + +static NTSTATUS resolve_ads(const char *name, + int name_type, + const char *sitename, + struct ip_service **return_iplist, + int *return_count) +{ + int i, j; + NTSTATUS status; + TALLOC_CTX *ctx; + struct dns_rr_srv *dcs = NULL; + int numdcs = 0; + int numaddrs = 0; + + if ((name_type != 0x1c) && (name_type != KDC_NAME_TYPE) && + (name_type != 0x1b)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if ( (ctx = talloc_init("resolve_ads")) == NULL ) { + DEBUG(0,("resolve_ads: talloc_init() failed!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* The DNS code needs fixing to find IPv6 addresses... JRA. */ + + switch (name_type) { + case 0x1b: + DEBUG(5,("resolve_ads: Attempting to resolve " + "PDC for %s using DNS\n", name)); + status = ads_dns_query_pdc(ctx, name, &dcs, &numdcs); + break; + + case 0x1c: + DEBUG(5,("resolve_ads: Attempting to resolve " + "DCs for %s using DNS\n", name)); + status = ads_dns_query_dcs(ctx, name, sitename, &dcs, + &numdcs); + break; + case KDC_NAME_TYPE: + DEBUG(5,("resolve_ads: Attempting to resolve " + "KDCs for %s using DNS\n", name)); + status = ads_dns_query_kdcs(ctx, name, sitename, &dcs, + &numdcs); + break; + default: + status = NT_STATUS_INVALID_PARAMETER; + break; + } + + if ( !NT_STATUS_IS_OK( status ) ) { + talloc_destroy(ctx); + return status; + } + + for (i=0;i<numdcs;i++) { + numaddrs += MAX(dcs[i].num_ips,1); + } + + if ((*return_iplist = SMB_MALLOC_ARRAY(struct ip_service, numaddrs)) == + NULL ) { + DEBUG(0,("resolve_ads: malloc failed for %d entries\n", + numaddrs )); + talloc_destroy(ctx); + return NT_STATUS_NO_MEMORY; + } + + /* now unroll the list of IP addresses */ + + *return_count = 0; + i = 0; + j = 0; + while ( i < numdcs && (*return_count<numaddrs) ) { + struct ip_service *r = &(*return_iplist)[*return_count]; + + r->port = dcs[i].port; + + /* If we don't have an IP list for a name, lookup it up */ + + if (!dcs[i].ss_s) { + interpret_string_addr(&r->ss, dcs[i].hostname, 0); + i++; + j = 0; + } else { + /* use the IP addresses from the SRV sresponse */ + + if ( j >= dcs[i].num_ips ) { + i++; + j = 0; + continue; + } + + r->ss = dcs[i].ss_s[j]; + j++; + } + + /* make sure it is a valid IP. I considered checking the + * negative connection cache, but this is the wrong place + * for it. Maybe only as a hack. After think about it, if + * all of the IP addresses returned from DNS are dead, what + * hope does a netbios name lookup have ? The standard reason + * for falling back to netbios lookups is that our DNS server + * doesn't know anything about the DC's -- jerry */ + + if (!is_zero_addr(&r->ss)) { + (*return_count)++; + } + } + + talloc_destroy(ctx); + return NT_STATUS_OK; +} + +/******************************************************************* + Internal interface to resolve a name into an IP address. + Use this function if the string is either an IP address, DNS + or host name or NetBIOS name. This uses the name switch in the + smb.conf to determine the order of name resolution. + + Added support for ip addr/port to support ADS ldap servers. + the only place we currently care about the port is in the + resolve_hosts() when looking up DC's via SRV RR entries in DNS +**********************************************************************/ + +NTSTATUS internal_resolve_name(const char *name, + int name_type, + const char *sitename, + struct ip_service **return_iplist, + int *return_count, + const char *resolve_order) +{ + char *tok; + const char *ptr; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + int i; + TALLOC_CTX *frame = NULL; + + *return_iplist = NULL; + *return_count = 0; + + DEBUG(10, ("internal_resolve_name: looking up %s#%x (sitename %s)\n", + name, name_type, sitename ? sitename : NULL)); + + if (is_ipaddress(name)) { + if ((*return_iplist = SMB_MALLOC_P(struct ip_service)) == + NULL) { + DEBUG(0,("internal_resolve_name: malloc fail !\n")); + return NT_STATUS_NO_MEMORY; + } + + /* ignore the port here */ + (*return_iplist)->port = PORT_NONE; + + /* if it's in the form of an IP address then get the lib to interpret it */ + if (!interpret_string_addr(&(*return_iplist)->ss, + name, AI_NUMERICHOST)) { + DEBUG(1,("internal_resolve_name: interpret_string_addr " + "failed on %s\n", + name)); + SAFE_FREE(*return_iplist); + return NT_STATUS_INVALID_PARAMETER; + } + *return_count = 1; + return NT_STATUS_OK; + } + + /* Check name cache */ + + if (namecache_fetch(name, name_type, return_iplist, return_count)) { + /* This could be a negative response */ + if (*return_count > 0) { + return NT_STATUS_OK; + } else { + return NT_STATUS_UNSUCCESSFUL; + } + } + + /* set the name resolution order */ + + if (strcmp( resolve_order, "NULL") == 0) { + DEBUG(8,("internal_resolve_name: all lookups disabled\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!resolve_order[0]) { + ptr = "host"; + } else { + ptr = resolve_order; + } + + /* iterate through the name resolution backends */ + + frame = talloc_stackframe(); + while (next_token_talloc(frame, &ptr, &tok, LIST_SEP)) { + if((strequal(tok, "host") || strequal(tok, "hosts"))) { + status = resolve_hosts(name, name_type, return_iplist, + return_count); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + } else if(strequal( tok, "kdc")) { + /* deal with KDC_NAME_TYPE names here. + * This will result in a SRV record lookup */ + status = resolve_ads(name, KDC_NAME_TYPE, sitename, + return_iplist, return_count); + if (NT_STATUS_IS_OK(status)) { + /* Ensure we don't namecache + * this with the KDC port. */ + name_type = KDC_NAME_TYPE; + goto done; + } + } else if(strequal( tok, "ads")) { + /* deal with 0x1c and 0x1b names here. + * This will result in a SRV record lookup */ + status = resolve_ads(name, name_type, sitename, + return_iplist, return_count); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + } else if(strequal( tok, "lmhosts")) { + status = resolve_lmhosts(name, name_type, + return_iplist, return_count); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + } else if(strequal( tok, "wins")) { + /* don't resolve 1D via WINS */ + if (name_type != 0x1D) { + status = resolve_wins(name, name_type, + return_iplist, + return_count); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + } + } else if(strequal( tok, "bcast")) { + status = name_resolve_bcast(name, name_type, + return_iplist, + return_count); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + } else { + DEBUG(0,("resolve_name: unknown name switch type %s\n", + tok)); + } + } + + /* All of the resolve_* functions above have returned false. */ + + TALLOC_FREE(frame); + SAFE_FREE(*return_iplist); + *return_count = 0; + + return NT_STATUS_UNSUCCESSFUL; + + done: + + /* Remove duplicate entries. Some queries, notably #1c (domain + controllers) return the PDC in iplist[0] and then all domain + controllers including the PDC in iplist[1..n]. Iterating over + the iplist when the PDC is down will cause two sets of timeouts. */ + + if ( *return_count ) { + *return_count = remove_duplicate_addrs2(*return_iplist, + *return_count ); + } + + /* Save in name cache */ + if ( DEBUGLEVEL >= 100 ) { + for (i = 0; i < *return_count && DEBUGLEVEL == 100; i++) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &(*return_iplist)[i].ss); + DEBUG(100, ("Storing name %s of type %d (%s:%d)\n", + name, + name_type, + addr, + (*return_iplist)[i].port)); + } + } + + namecache_store(name, name_type, *return_count, *return_iplist); + + /* Display some debugging info */ + + if ( DEBUGLEVEL >= 10 ) { + DEBUG(10, ("internal_resolve_name: returning %d addresses: ", + *return_count)); + + for (i = 0; i < *return_count; i++) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &(*return_iplist)[i].ss); + DEBUGADD(10, ("%s:%d ", + addr, + (*return_iplist)[i].port)); + } + DEBUG(10, ("\n")); + } + + TALLOC_FREE(frame); + return status; +} + +/******************************************************** + Internal interface to resolve a name into one IP address. + Use this function if the string is either an IP address, DNS + or host name or NetBIOS name. This uses the name switch in the + smb.conf to determine the order of name resolution. +*********************************************************/ + +bool resolve_name(const char *name, + struct sockaddr_storage *return_ss, + int name_type) +{ + struct ip_service *ss_list = NULL; + char *sitename = NULL; + int count = 0; + + if (is_ipaddress(name)) { + return interpret_string_addr(return_ss, name, AI_NUMERICHOST); + } + + sitename = sitename_fetch(lp_realm()); /* wild guess */ + + if (NT_STATUS_IS_OK(internal_resolve_name(name, name_type, sitename, + &ss_list, &count, + lp_name_resolve_order()))) { + int i; + + /* only return valid addresses for TCP connections */ + for (i=0; i<count; i++) { + if (!is_zero_addr(&ss_list[i].ss) && + !is_broadcast_addr(&ss_list[i].ss)) { + *return_ss = ss_list[i].ss; + SAFE_FREE(ss_list); + SAFE_FREE(sitename); + return True; + } + } + } + + SAFE_FREE(ss_list); + SAFE_FREE(sitename); + return False; +} + +/******************************************************** + Internal interface to resolve a name into a list of IP addresses. + Use this function if the string is either an IP address, DNS + or host name or NetBIOS name. This uses the name switch in the + smb.conf to determine the order of name resolution. +*********************************************************/ + +NTSTATUS resolve_name_list(TALLOC_CTX *ctx, + const char *name, + int name_type, + struct sockaddr_storage **return_ss_arr, + unsigned int *p_num_entries) +{ + struct ip_service *ss_list = NULL; + char *sitename = NULL; + int count = 0; + int i; + unsigned int num_entries; + NTSTATUS status; + + *p_num_entries = 0; + *return_ss_arr = NULL; + + if (is_ipaddress(name)) { + *return_ss_arr = TALLOC_P(ctx, struct sockaddr_storage); + if (!*return_ss_arr) { + return NT_STATUS_NO_MEMORY; + } + if (!interpret_string_addr(*return_ss_arr, name, AI_NUMERICHOST)) { + TALLOC_FREE(*return_ss_arr); + return NT_STATUS_BAD_NETWORK_NAME; + } + *p_num_entries = 1; + return NT_STATUS_OK; + } + + sitename = sitename_fetch(lp_realm()); /* wild guess */ + + status = internal_resolve_name(name, name_type, sitename, + &ss_list, &count, + lp_name_resolve_order()); + SAFE_FREE(sitename); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* only return valid addresses for TCP connections */ + for (i=0, num_entries = 0; i<count; i++) { + if (!is_zero_addr(&ss_list[i].ss) && + !is_broadcast_addr(&ss_list[i].ss)) { + num_entries++; + } + } + if (num_entries == 0) { + SAFE_FREE(ss_list); + return NT_STATUS_BAD_NETWORK_NAME; + } + + *return_ss_arr = TALLOC_ARRAY(ctx, + struct sockaddr_storage, + num_entries); + if (!(*return_ss_arr)) { + SAFE_FREE(ss_list); + return NT_STATUS_NO_MEMORY; + } + + for (i=0, num_entries = 0; i<count; i++) { + if (!is_zero_addr(&ss_list[i].ss) && + !is_broadcast_addr(&ss_list[i].ss)) { + (*return_ss_arr)[num_entries++] = ss_list[i].ss; + } + } + + status = NT_STATUS_OK; + *p_num_entries = num_entries; + + SAFE_FREE(ss_list); + return NT_STATUS_OK; +} + +/******************************************************** + Find the IP address of the master browser or DMB for a workgroup. +*********************************************************/ + +bool find_master_ip(const char *group, struct sockaddr_storage *master_ss) +{ + struct ip_service *ip_list = NULL; + int count = 0; + NTSTATUS status; + + if (lp_disable_netbios()) { + DEBUG(5,("find_master_ip(%s): netbios is disabled\n", group)); + return false; + } + + status = internal_resolve_name(group, 0x1D, NULL, &ip_list, &count, + lp_name_resolve_order()); + if (NT_STATUS_IS_OK(status)) { + *master_ss = ip_list[0].ss; + SAFE_FREE(ip_list); + return true; + } + + status = internal_resolve_name(group, 0x1B, NULL, &ip_list, &count, + lp_name_resolve_order()); + if (NT_STATUS_IS_OK(status)) { + *master_ss = ip_list[0].ss; + SAFE_FREE(ip_list); + return true; + } + + SAFE_FREE(ip_list); + return false; +} + +/******************************************************** + Get the IP address list of the primary domain controller + for a domain. +*********************************************************/ + +bool get_pdc_ip(const char *domain, struct sockaddr_storage *pss) +{ + struct ip_service *ip_list = NULL; + int count = 0; + NTSTATUS status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + + /* Look up #1B name */ + + if (lp_security() == SEC_ADS) { + status = internal_resolve_name(domain, 0x1b, NULL, &ip_list, + &count, "ads"); + } + + if (!NT_STATUS_IS_OK(status) || count == 0) { + status = internal_resolve_name(domain, 0x1b, NULL, &ip_list, + &count, + lp_name_resolve_order()); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + } + + /* if we get more than 1 IP back we have to assume it is a + multi-homed PDC and not a mess up */ + + if ( count > 1 ) { + DEBUG(6,("get_pdc_ip: PDC has %d IP addresses!\n", count)); + sort_service_list(ip_list, count); + } + + *pss = ip_list[0].ss; + SAFE_FREE(ip_list); + return true; +} + +/* Private enum type for lookups. */ + +enum dc_lookup_type { DC_NORMAL_LOOKUP, DC_ADS_ONLY, DC_KDC_ONLY }; + +/******************************************************** + Get the IP address list of the domain controllers for + a domain. +*********************************************************/ + +static NTSTATUS get_dc_list(const char *domain, + const char *sitename, + struct ip_service **ip_list, + int *count, + enum dc_lookup_type lookup_type, + bool *ordered) +{ + char *resolve_order = NULL; + char *saf_servername = NULL; + char *pserver = NULL; + const char *p; + char *port_str = NULL; + int port; + char *name; + int num_addresses = 0; + int local_count, i, j; + struct ip_service *return_iplist = NULL; + struct ip_service *auto_ip_list = NULL; + bool done_auto_lookup = false; + int auto_count = 0; + NTSTATUS status; + TALLOC_CTX *ctx = talloc_init("get_dc_list"); + + *ip_list = NULL; + *count = 0; + + if (!ctx) { + return NT_STATUS_NO_MEMORY; + } + + *ordered = False; + + /* if we are restricted to solely using DNS for looking + up a domain controller, make sure that host lookups + are enabled for the 'name resolve order'. If host lookups + are disabled and ads_only is True, then set the string to + NULL. */ + + resolve_order = talloc_strdup(ctx, lp_name_resolve_order()); + if (!resolve_order) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + strlower_m(resolve_order); + if (lookup_type == DC_ADS_ONLY) { + if (strstr( resolve_order, "host")) { + resolve_order = talloc_strdup(ctx, "ads"); + + /* DNS SRV lookups used by the ads resolver + are already sorted by priority and weight */ + *ordered = true; + } else { + resolve_order = talloc_strdup(ctx, "NULL"); + } + } else if (lookup_type == DC_KDC_ONLY) { + /* DNS SRV lookups used by the ads/kdc resolver + are already sorted by priority and weight */ + *ordered = true; + resolve_order = talloc_strdup(ctx, "kdc"); + } + if (!resolve_order) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* fetch the server we have affinity for. Add the + 'password server' list to a search for our domain controllers */ + + saf_servername = saf_fetch( domain); + + if (strequal(domain, lp_workgroup()) || strequal(domain, lp_realm())) { + pserver = talloc_asprintf(NULL, "%s, %s", + saf_servername ? saf_servername : "", + lp_passwordserver()); + } else { + pserver = talloc_asprintf(NULL, "%s, *", + saf_servername ? saf_servername : ""); + } + + SAFE_FREE(saf_servername); + if (!pserver) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* if we are starting from scratch, just lookup DOMAIN<0x1c> */ + + if (!*pserver ) { + DEBUG(10,("get_dc_list: no preferred domain controllers.\n")); + status = internal_resolve_name(domain, 0x1C, sitename, ip_list, + count, resolve_order); + goto out; + } + + DEBUG(3,("get_dc_list: preferred server list: \"%s\"\n", pserver )); + + /* + * if '*' appears in the "password server" list then add + * an auto lookup to the list of manually configured + * DC's. If any DC is listed by name, then the list should be + * considered to be ordered + */ + + p = pserver; + while (next_token_talloc(ctx, &p, &name, LIST_SEP)) { + if (!done_auto_lookup && strequal(name, "*")) { + status = internal_resolve_name(domain, 0x1C, sitename, + &auto_ip_list, + &auto_count, + resolve_order); + if (NT_STATUS_IS_OK(status)) { + num_addresses += auto_count; + } + done_auto_lookup = true; + DEBUG(8,("Adding %d DC's from auto lookup\n", + auto_count)); + } else { + num_addresses++; + } + } + + /* if we have no addresses and haven't done the auto lookup, then + just return the list of DC's. Or maybe we just failed. */ + + if ((num_addresses == 0)) { + if (done_auto_lookup) { + DEBUG(4,("get_dc_list: no servers found\n")); + status = NT_STATUS_NO_LOGON_SERVERS; + goto out; + } + status = internal_resolve_name(domain, 0x1C, sitename, ip_list, + count, resolve_order); + goto out; + } + + if ((return_iplist = SMB_MALLOC_ARRAY(struct ip_service, + num_addresses)) == NULL) { + DEBUG(3,("get_dc_list: malloc fail !\n")); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + p = pserver; + local_count = 0; + + /* fill in the return list now with real IP's */ + + while ((local_count<num_addresses) && + next_token_talloc(ctx, &p, &name, LIST_SEP)) { + struct sockaddr_storage name_ss; + + /* copy any addersses from the auto lookup */ + + if (strequal(name, "*")) { + for (j=0; j<auto_count; j++) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, + sizeof(addr), + &auto_ip_list[j].ss); + /* Check for and don't copy any + * known bad DC IP's. */ + if(!NT_STATUS_IS_OK(check_negative_conn_cache( + domain, + addr))) { + DEBUG(5,("get_dc_list: " + "negative entry %s removed " + "from DC list\n", + addr)); + continue; + } + return_iplist[local_count].ss = + auto_ip_list[j].ss; + return_iplist[local_count].port = + auto_ip_list[j].port; + local_count++; + } + continue; + } + + /* added support for address:port syntax for ads + * (not that I think anyone will ever run the LDAP + * server in an AD domain on something other than + * port 389 */ + + port = (lp_security() == SEC_ADS) ? LDAP_PORT : PORT_NONE; + if ((port_str=strchr(name, ':')) != NULL) { + *port_str = '\0'; + port_str++; + port = atoi(port_str); + } + + /* explicit lookup; resolve_name() will + * handle names & IP addresses */ + if (resolve_name( name, &name_ss, 0x20 )) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, + sizeof(addr), + &name_ss); + + /* Check for and don't copy any known bad DC IP's. */ + if( !NT_STATUS_IS_OK(check_negative_conn_cache(domain, + addr)) ) { + DEBUG(5,("get_dc_list: negative entry %s " + "removed from DC list\n", + name )); + continue; + } + + return_iplist[local_count].ss = name_ss; + return_iplist[local_count].port = port; + local_count++; + *ordered = true; + } + } + + /* need to remove duplicates in the list if we have any + explicit password servers */ + + if (local_count) { + local_count = remove_duplicate_addrs2(return_iplist, + local_count ); + } + + if ( DEBUGLEVEL >= 4 ) { + DEBUG(4,("get_dc_list: returning %d ip addresses " + "in an %sordered list\n", + local_count, + *ordered ? "":"un")); + DEBUG(4,("get_dc_list: ")); + for ( i=0; i<local_count; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, + sizeof(addr), + &return_iplist[i].ss); + DEBUGADD(4,("%s:%d ", addr, return_iplist[i].port )); + } + DEBUGADD(4,("\n")); + } + + *ip_list = return_iplist; + *count = local_count; + + status = ( *count != 0 ? NT_STATUS_OK : NT_STATUS_NO_LOGON_SERVERS ); + + out: + + if (!NT_STATUS_IS_OK(status)) { + SAFE_FREE(return_iplist); + *ip_list = NULL; + *count = 0; + } + + SAFE_FREE(auto_ip_list); + TALLOC_FREE(ctx); + return status; +} + +/********************************************************************* + Small wrapper function to get the DC list and sort it if neccessary. +*********************************************************************/ + +NTSTATUS get_sorted_dc_list( const char *domain, + const char *sitename, + struct ip_service **ip_list, + int *count, + bool ads_only ) +{ + bool ordered; + NTSTATUS status; + enum dc_lookup_type lookup_type = DC_NORMAL_LOOKUP; + + *ip_list = NULL; + *count = 0; + + DEBUG(8,("get_sorted_dc_list: attempting lookup " + "for name %s (sitename %s) using [%s]\n", + domain, + sitename ? sitename : "NULL", + (ads_only ? "ads" : lp_name_resolve_order()))); + + if (ads_only) { + lookup_type = DC_ADS_ONLY; + } + + status = get_dc_list(domain, sitename, ip_list, + count, lookup_type, &ordered); + if (!NT_STATUS_IS_OK(status)) { + SAFE_FREE(*ip_list); + *count = 0; + return status; + } + + /* only sort if we don't already have an ordered list */ + if (!ordered) { + sort_service_list(*ip_list, *count); + } + + return NT_STATUS_OK; +} + +/********************************************************************* + Get the KDC list - re-use all the logic in get_dc_list. +*********************************************************************/ + +NTSTATUS get_kdc_list( const char *realm, + const char *sitename, + struct ip_service **ip_list, + int *count) +{ + bool ordered; + NTSTATUS status; + + *count = 0; + *ip_list = NULL; + + status = get_dc_list(realm, sitename, ip_list, + count, DC_KDC_ONLY, &ordered); + + if (!NT_STATUS_IS_OK(status)) { + SAFE_FREE(*ip_list); + *count = 0; + return status; + } + + /* only sort if we don't already have an ordered list */ + if ( !ordered ) { + sort_service_list(*ip_list, *count); + } + + return NT_STATUS_OK; +} diff --git a/source3/libsmb/namequery_dc.c b/source3/libsmb/namequery_dc.c new file mode 100644 index 0000000000..d080f8f0b7 --- /dev/null +++ b/source3/libsmb/namequery_dc.c @@ -0,0 +1,247 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Gerald Carter 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" + +/********************************************************************** + Is this our primary domain ? +**********************************************************************/ + +#ifdef HAVE_KRB5 +static bool is_our_primary_domain(const char *domain) +{ + int role = lp_server_role(); + + if ((role == ROLE_DOMAIN_MEMBER) && strequal(lp_workgroup(), domain)) { + return True; + } else if (strequal(get_global_sam_name(), domain)) { + return True; + } + return False; +} +#endif + +/************************************************************************** + Find the name and IP address for a server in the realm/domain + *************************************************************************/ + +static bool ads_dc_name(const char *domain, + const char *realm, + struct sockaddr_storage *dc_ss, + fstring srv_name) +{ + ADS_STRUCT *ads; + char *sitename; + int i; + char addr[INET6_ADDRSTRLEN]; + + if (!realm && strequal(domain, lp_workgroup())) { + realm = lp_realm(); + } + + sitename = sitename_fetch(realm); + + /* Try this 3 times then give up. */ + for( i =0 ; i < 3; i++) { + ads = ads_init(realm, domain, NULL); + if (!ads) { + SAFE_FREE(sitename); + return False; + } + + DEBUG(4,("ads_dc_name: domain=%s\n", domain)); + +#ifdef HAVE_ADS + /* we don't need to bind, just connect */ + ads->auth.flags |= ADS_AUTH_NO_BIND; + ads_connect(ads); +#endif + + if (!ads->config.realm) { + SAFE_FREE(sitename); + ads_destroy(&ads); + return False; + } + + /* Now we've found a server, see if our sitename + has changed. If so, we need to re-do the DNS query + to ensure we only find servers in our site. */ + + if (stored_sitename_changed(realm, sitename)) { + SAFE_FREE(sitename); + sitename = sitename_fetch(realm); + ads_destroy(&ads); + /* Ensure we don't cache the DC we just connected to. */ + namecache_delete(realm, 0x1C); + namecache_delete(domain, 0x1C); + continue; + } + +#ifdef HAVE_KRB5 + if (is_our_primary_domain(domain) && (ads->config.flags & NBT_SERVER_KDC)) { + if (ads_closest_dc(ads)) { + /* We're going to use this KDC for this realm/domain. + If we are using sites, then force the krb5 libs + to use this KDC. */ + + create_local_private_krb5_conf_for_domain(realm, + domain, + sitename, + &ads->ldap.ss); + } else { + create_local_private_krb5_conf_for_domain(realm, + domain, + NULL, + &ads->ldap.ss); + } + } +#endif + break; + } + + if (i == 3) { + DEBUG(1,("ads_dc_name: sitename (now \"%s\") keeps changing ???\n", + sitename ? sitename : "")); + SAFE_FREE(sitename); + return False; + } + + SAFE_FREE(sitename); + + fstrcpy(srv_name, ads->config.ldap_server_name); + strupper_m(srv_name); +#ifdef HAVE_ADS + *dc_ss = ads->ldap.ss; +#else + zero_addr(dc_ss); +#endif + ads_destroy(&ads); + + print_sockaddr(addr, sizeof(addr), dc_ss); + DEBUG(4,("ads_dc_name: using server='%s' IP=%s\n", + srv_name, addr)); + + return True; +} + +/**************************************************************************** + Utility function to return the name of a DC. The name is guaranteed to be + valid since we have already done a name_status_find on it + ***************************************************************************/ + +static bool rpc_dc_name(const char *domain, + fstring srv_name, + struct sockaddr_storage *ss_out) +{ + struct ip_service *ip_list = NULL; + struct sockaddr_storage dc_ss; + int count, i; + NTSTATUS result; + char addr[INET6_ADDRSTRLEN]; + + /* get a list of all domain controllers */ + + if (!NT_STATUS_IS_OK(get_sorted_dc_list(domain, NULL, &ip_list, &count, + False))) { + DEBUG(3, ("Could not look up dc's for domain %s\n", domain)); + return False; + } + + /* Remove the entry we've already failed with (should be the PDC). */ + + for (i = 0; i < count; i++) { + if (is_zero_addr(&ip_list[i].ss)) + continue; + + if (name_status_find(domain, 0x1c, 0x20, &ip_list[i].ss, srv_name)) { + result = check_negative_conn_cache( domain, srv_name ); + if ( NT_STATUS_IS_OK(result) ) { + dc_ss = ip_list[i].ss; + goto done; + } + } + } + + SAFE_FREE(ip_list); + + /* No-one to talk to )-: */ + return False; /* Boo-hoo */ + + done: + /* We have the netbios name and IP address of a domain controller. + Ideally we should sent a SAMLOGON request to determine whether + the DC is alive and kicking. If we can catch a dead DC before + performing a cli_connect() we can avoid a 30-second timeout. */ + + print_sockaddr(addr, sizeof(addr), &dc_ss); + DEBUG(3, ("rpc_dc_name: Returning DC %s (%s) for domain %s\n", srv_name, + addr, domain)); + + *ss_out = dc_ss; + SAFE_FREE(ip_list); + + return True; +} + +/********************************************************************** + wrapper around ads and rpc methods of finds DC's +**********************************************************************/ + +bool get_dc_name(const char *domain, + const char *realm, + fstring srv_name, + struct sockaddr_storage *ss_out) +{ + struct sockaddr_storage dc_ss; + bool ret; + bool our_domain = False; + + zero_addr(&dc_ss); + + ret = False; + + if ( strequal(lp_workgroup(), domain) || strequal(lp_realm(), realm) ) + our_domain = True; + + /* always try to obey what the admin specified in smb.conf + (for the local domain) */ + + if ( (our_domain && lp_security()==SEC_ADS) || realm ) { + ret = ads_dc_name(domain, realm, &dc_ss, srv_name); + } + + if (!domain) { + /* if we have only the realm we can't do anything else */ + return False; + } + + if (!ret) { + /* fall back on rpc methods if the ADS methods fail */ + ret = rpc_dc_name(domain, srv_name, &dc_ss); + } + + *ss_out = dc_ss; + + return ret; +} diff --git a/source3/libsmb/nmblib.c b/source3/libsmb/nmblib.c new file mode 100644 index 0000000000..bfe5e7b97b --- /dev/null +++ b/source3/libsmb/nmblib.c @@ -0,0 +1,1400 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios library routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" + +int num_good_sends = 0; +int num_good_receives = 0; + +static const struct opcode_names { + const char *nmb_opcode_name; + int opcode; +} nmb_header_opcode_names[] = { + {"Query", 0 }, + {"Registration", 5 }, + {"Release", 6 }, + {"WACK", 7 }, + {"Refresh", 8 }, + {"Refresh(altcode)", 9 }, + {"Multi-homed Registration", 15 }, + {0, -1 } +}; + +/**************************************************************************** + Lookup a nmb opcode name. +****************************************************************************/ + +static const char *lookup_opcode_name( int opcode ) +{ + const struct opcode_names *op_namep; + int i; + + for(i = 0; nmb_header_opcode_names[i].nmb_opcode_name != 0; i++) { + op_namep = &nmb_header_opcode_names[i]; + if(opcode == op_namep->opcode) + return op_namep->nmb_opcode_name; + } + return "<unknown opcode>"; +} + +/**************************************************************************** + Print out a res_rec structure. +****************************************************************************/ + +static void debug_nmb_res_rec(struct res_rec *res, const char *hdr) +{ + int i, j; + + DEBUGADD( 4, ( " %s: nmb_name=%s rr_type=%d rr_class=%d ttl=%d\n", + hdr, + nmb_namestr(&res->rr_name), + res->rr_type, + res->rr_class, + res->ttl ) ); + + if( res->rdlength == 0 || res->rdata == NULL ) + return; + + for (i = 0; i < res->rdlength; i+= MAX_NETBIOSNAME_LEN) { + DEBUGADD(4, (" %s %3x char ", hdr, i)); + + for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) { + unsigned char x = res->rdata[i+j]; + if (x < 32 || x > 127) + x = '.'; + + if (i+j >= res->rdlength) + break; + DEBUGADD(4, ("%c", x)); + } + + DEBUGADD(4, (" hex ")); + + for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) { + if (i+j >= res->rdlength) + break; + DEBUGADD(4, ("%02X", (unsigned char)res->rdata[i+j])); + } + + DEBUGADD(4, ("\n")); + } +} + +/**************************************************************************** + Process a nmb packet. +****************************************************************************/ + +void debug_nmb_packet(struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + + if( DEBUGLVL( 4 ) ) { + dbgtext( "nmb packet from %s(%d) header: id=%d " + "opcode=%s(%d) response=%s\n", + inet_ntoa(p->ip), p->port, + nmb->header.name_trn_id, + lookup_opcode_name(nmb->header.opcode), + nmb->header.opcode, + BOOLSTR(nmb->header.response) ); + dbgtext( " header: flags: bcast=%s rec_avail=%s " + "rec_des=%s trunc=%s auth=%s\n", + BOOLSTR(nmb->header.nm_flags.bcast), + BOOLSTR(nmb->header.nm_flags.recursion_available), + BOOLSTR(nmb->header.nm_flags.recursion_desired), + BOOLSTR(nmb->header.nm_flags.trunc), + BOOLSTR(nmb->header.nm_flags.authoritative) ); + dbgtext( " header: rcode=%d qdcount=%d ancount=%d " + "nscount=%d arcount=%d\n", + nmb->header.rcode, + nmb->header.qdcount, + nmb->header.ancount, + nmb->header.nscount, + nmb->header.arcount ); + } + + if (nmb->header.qdcount) { + DEBUGADD( 4, ( " question: q_name=%s q_type=%d q_class=%d\n", + nmb_namestr(&nmb->question.question_name), + nmb->question.question_type, + nmb->question.question_class) ); + } + + if (nmb->answers && nmb->header.ancount) { + debug_nmb_res_rec(nmb->answers,"answers"); + } + if (nmb->nsrecs && nmb->header.nscount) { + debug_nmb_res_rec(nmb->nsrecs,"nsrecs"); + } + if (nmb->additional && nmb->header.arcount) { + debug_nmb_res_rec(nmb->additional,"additional"); + } +} + +/******************************************************************* + Handle "compressed" name pointers. +******************************************************************/ + +static bool handle_name_ptrs(unsigned char *ubuf,int *offset,int length, + bool *got_pointer,int *ret) +{ + int loop_count=0; + + while ((ubuf[*offset] & 0xC0) == 0xC0) { + if (!*got_pointer) + (*ret) += 2; + (*got_pointer)=True; + (*offset) = ((ubuf[*offset] & ~0xC0)<<8) | ubuf[(*offset)+1]; + if (loop_count++ == 10 || + (*offset) < 0 || (*offset)>(length-2)) { + return False; + } + } + return True; +} + +/******************************************************************* + Parse a nmb name from "compressed" format to something readable + return the space taken by the name, or 0 if the name is invalid +******************************************************************/ + +static int parse_nmb_name(char *inbuf,int ofs,int length, struct nmb_name *name) +{ + int m,n=0; + unsigned char *ubuf = (unsigned char *)inbuf; + int ret = 0; + bool got_pointer=False; + int loop_count=0; + int offset = ofs; + + if (length - offset < 2) + return(0); + + /* handle initial name pointers */ + if (!handle_name_ptrs(ubuf,&offset,length,&got_pointer,&ret)) + return(0); + + m = ubuf[offset]; + + if (!m) + return(0); + if ((m & 0xC0) || offset+m+2 > length) + return(0); + + memset((char *)name,'\0',sizeof(*name)); + + /* the "compressed" part */ + if (!got_pointer) + ret += m + 2; + offset++; + while (m > 0) { + unsigned char c1,c2; + c1 = ubuf[offset++]-'A'; + c2 = ubuf[offset++]-'A'; + if ((c1 & 0xF0) || (c2 & 0xF0) || (n > sizeof(name->name)-1)) + return(0); + name->name[n++] = (c1<<4) | c2; + m -= 2; + } + name->name[n] = 0; + + if (n==MAX_NETBIOSNAME_LEN) { + /* parse out the name type, its always + * in the 16th byte of the name */ + name->name_type = ((unsigned char)name->name[15]) & 0xff; + + /* remove trailing spaces */ + name->name[15] = 0; + n = 14; + while (n && name->name[n]==' ') + name->name[n--] = 0; + } + + /* now the domain parts (if any) */ + n = 0; + while (ubuf[offset]) { + /* we can have pointers within the domain part as well */ + if (!handle_name_ptrs(ubuf,&offset,length,&got_pointer,&ret)) + return(0); + + m = ubuf[offset]; + /* + * Don't allow null domain parts. + */ + if (!m) + return(0); + if (!got_pointer) + ret += m+1; + if (n) + name->scope[n++] = '.'; + if (m+2+offset>length || n+m+1>sizeof(name->scope)) + return(0); + offset++; + while (m--) + name->scope[n++] = (char)ubuf[offset++]; + + /* + * Watch for malicious loops. + */ + if (loop_count++ == 10) + return 0; + } + name->scope[n++] = 0; + + return(ret); +} + +/**************************************************************************** + Put a netbios name, padding(s) and a name type into a 16 character buffer. + name is already in DOS charset. + [15 bytes name + padding][1 byte name type]. +****************************************************************************/ + +void put_name(char *dest, const char *name, int pad, unsigned int name_type) +{ + size_t len = strlen(name); + + memcpy(dest, name, (len < MAX_NETBIOSNAME_LEN) ? + len : MAX_NETBIOSNAME_LEN - 1); + if (len < MAX_NETBIOSNAME_LEN - 1) { + memset(dest + len, pad, MAX_NETBIOSNAME_LEN - 1 - len); + } + dest[MAX_NETBIOSNAME_LEN - 1] = name_type; +} + +/******************************************************************* + Put a compressed nmb name into a buffer. Return the length of the + compressed name. + + Compressed names are really weird. The "compression" doubles the + size. The idea is that it also means that compressed names conform + to the doman name system. See RFC1002. + + If buf == NULL this is a length calculation. +******************************************************************/ + +static int put_nmb_name(char *buf,int offset,struct nmb_name *name) +{ + int ret,m; + nstring buf1; + char *p; + + if (strcmp(name->name,"*") == 0) { + /* special case for wildcard name */ + put_name(buf1, "*", '\0', name->name_type); + } else { + put_name(buf1, name->name, ' ', name->name_type); + } + + if (buf) { + buf[offset] = 0x20; + } + + ret = 34; + + for (m=0;m<MAX_NETBIOSNAME_LEN;m++) { + if (buf) { + buf[offset+1+2*m] = 'A' + ((buf1[m]>>4)&0xF); + buf[offset+2+2*m] = 'A' + (buf1[m]&0xF); + } + } + offset += 33; + + if (buf) { + buf[offset] = 0; + } + + if (name->scope[0]) { + /* XXXX this scope handling needs testing */ + ret += strlen(name->scope) + 1; + if (buf) { + safe_strcpy(&buf[offset+1],name->scope, + sizeof(name->scope)); + + p = &buf[offset+1]; + while ((p = strchr_m(p,'.'))) { + buf[offset] = PTR_DIFF(p,&buf[offset+1]); + offset += (buf[offset] + 1); + p = &buf[offset+1]; + } + buf[offset] = strlen(&buf[offset+1]); + } + } + + return ret; +} + +/******************************************************************* + Useful for debugging messages. +******************************************************************/ + +char *nmb_namestr(const struct nmb_name *n) +{ + fstring name; + char *result; + + pull_ascii_fstring(name, n->name); + if (!n->scope[0]) + result = talloc_asprintf(talloc_tos(), "%s<%02x>", name, + n->name_type); + else + result = talloc_asprintf(talloc_tos(), "%s<%02x>.%s", name, + n->name_type, n->scope); + + SMB_ASSERT(result != NULL); + return result; +} + +/******************************************************************* + Allocate and parse some resource records. +******************************************************************/ + +static bool parse_alloc_res_rec(char *inbuf,int *offset,int length, + struct res_rec **recs, int count) +{ + int i; + + *recs = SMB_MALLOC_ARRAY(struct res_rec, count); + if (!*recs) + return(False); + + memset((char *)*recs,'\0',sizeof(**recs)*count); + + for (i=0;i<count;i++) { + int l = parse_nmb_name(inbuf,*offset,length, + &(*recs)[i].rr_name); + (*offset) += l; + if (!l || (*offset)+10 > length) { + SAFE_FREE(*recs); + return(False); + } + (*recs)[i].rr_type = RSVAL(inbuf,(*offset)); + (*recs)[i].rr_class = RSVAL(inbuf,(*offset)+2); + (*recs)[i].ttl = RIVAL(inbuf,(*offset)+4); + (*recs)[i].rdlength = RSVAL(inbuf,(*offset)+8); + (*offset) += 10; + if ((*recs)[i].rdlength>sizeof((*recs)[i].rdata) || + (*offset)+(*recs)[i].rdlength > length) { + SAFE_FREE(*recs); + return(False); + } + memcpy((*recs)[i].rdata,inbuf+(*offset),(*recs)[i].rdlength); + (*offset) += (*recs)[i].rdlength; + } + return(True); +} + +/******************************************************************* + Put a resource record into a packet. + If buf == NULL this is a length calculation. +******************************************************************/ + +static int put_res_rec(char *buf,int offset,struct res_rec *recs,int count) +{ + int ret=0; + int i; + + for (i=0;i<count;i++) { + int l = put_nmb_name(buf,offset,&recs[i].rr_name); + offset += l; + ret += l; + if (buf) { + RSSVAL(buf,offset,recs[i].rr_type); + RSSVAL(buf,offset+2,recs[i].rr_class); + RSIVAL(buf,offset+4,recs[i].ttl); + RSSVAL(buf,offset+8,recs[i].rdlength); + memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength); + } + offset += 10+recs[i].rdlength; + ret += 10+recs[i].rdlength; + } + + return ret; +} + +/******************************************************************* + Put a compressed name pointer record into a packet. + If buf == NULL this is a length calculation. +******************************************************************/ + +static int put_compressed_name_ptr(unsigned char *buf, + int offset, + struct res_rec *rec, + int ptr_offset) +{ + int ret=0; + if (buf) { + buf[offset] = (0xC0 | ((ptr_offset >> 8) & 0xFF)); + buf[offset+1] = (ptr_offset & 0xFF); + } + offset += 2; + ret += 2; + if (buf) { + RSSVAL(buf,offset,rec->rr_type); + RSSVAL(buf,offset+2,rec->rr_class); + RSIVAL(buf,offset+4,rec->ttl); + RSSVAL(buf,offset+8,rec->rdlength); + memcpy(buf+offset+10,rec->rdata,rec->rdlength); + } + offset += 10+rec->rdlength; + ret += 10+rec->rdlength; + + return ret; +} + +/******************************************************************* + Parse a dgram packet. Return False if the packet can't be parsed + or is invalid for some reason, True otherwise. + + This is documented in section 4.4.1 of RFC1002. +******************************************************************/ + +static bool parse_dgram(char *inbuf,int length,struct dgram_packet *dgram) +{ + int offset; + int flags; + + memset((char *)dgram,'\0',sizeof(*dgram)); + + if (length < 14) + return(False); + + dgram->header.msg_type = CVAL(inbuf,0); + flags = CVAL(inbuf,1); + dgram->header.flags.node_type = (enum node_type)((flags>>2)&3); + if (flags & 1) + dgram->header.flags.more = True; + if (flags & 2) + dgram->header.flags.first = True; + dgram->header.dgm_id = RSVAL(inbuf,2); + putip((char *)&dgram->header.source_ip,inbuf+4); + dgram->header.source_port = RSVAL(inbuf,8); + dgram->header.dgm_length = RSVAL(inbuf,10); + dgram->header.packet_offset = RSVAL(inbuf,12); + + offset = 14; + + if (dgram->header.msg_type == 0x10 || + dgram->header.msg_type == 0x11 || + dgram->header.msg_type == 0x12) { + offset += parse_nmb_name(inbuf,offset,length, + &dgram->source_name); + offset += parse_nmb_name(inbuf,offset,length, + &dgram->dest_name); + } + + if (offset >= length || (length-offset > sizeof(dgram->data))) + return(False); + + dgram->datasize = length-offset; + memcpy(dgram->data,inbuf+offset,dgram->datasize); + + /* Paranioa. Ensure the last 2 bytes in the dgram buffer are + zero. This should be true anyway, just enforce it for + paranioa sake. JRA. */ + SMB_ASSERT(dgram->datasize <= (sizeof(dgram->data)-2)); + memset(&dgram->data[sizeof(dgram->data)-2], '\0', 2); + + return(True); +} + +/******************************************************************* + Parse a nmb packet. Return False if the packet can't be parsed + or is invalid for some reason, True otherwise. +******************************************************************/ + +static bool parse_nmb(char *inbuf,int length,struct nmb_packet *nmb) +{ + int nm_flags,offset; + + memset((char *)nmb,'\0',sizeof(*nmb)); + + if (length < 12) + return(False); + + /* parse the header */ + nmb->header.name_trn_id = RSVAL(inbuf,0); + + DEBUG(10,("parse_nmb: packet id = %d\n", nmb->header.name_trn_id)); + + nmb->header.opcode = (CVAL(inbuf,2) >> 3) & 0xF; + nmb->header.response = ((CVAL(inbuf,2)>>7)&1)?True:False; + nm_flags = ((CVAL(inbuf,2) & 0x7) << 4) + (CVAL(inbuf,3)>>4); + nmb->header.nm_flags.bcast = (nm_flags&1)?True:False; + nmb->header.nm_flags.recursion_available = (nm_flags&8)?True:False; + nmb->header.nm_flags.recursion_desired = (nm_flags&0x10)?True:False; + nmb->header.nm_flags.trunc = (nm_flags&0x20)?True:False; + nmb->header.nm_flags.authoritative = (nm_flags&0x40)?True:False; + nmb->header.rcode = CVAL(inbuf,3) & 0xF; + nmb->header.qdcount = RSVAL(inbuf,4); + nmb->header.ancount = RSVAL(inbuf,6); + nmb->header.nscount = RSVAL(inbuf,8); + nmb->header.arcount = RSVAL(inbuf,10); + + if (nmb->header.qdcount) { + offset = parse_nmb_name(inbuf,12,length, + &nmb->question.question_name); + if (!offset) + return(False); + + if (length - (12+offset) < 4) + return(False); + nmb->question.question_type = RSVAL(inbuf,12+offset); + nmb->question.question_class = RSVAL(inbuf,12+offset+2); + + offset += 12+4; + } else { + offset = 12; + } + + /* and any resource records */ + if (nmb->header.ancount && + !parse_alloc_res_rec(inbuf,&offset,length,&nmb->answers, + nmb->header.ancount)) + return(False); + + if (nmb->header.nscount && + !parse_alloc_res_rec(inbuf,&offset,length,&nmb->nsrecs, + nmb->header.nscount)) + return(False); + + if (nmb->header.arcount && + !parse_alloc_res_rec(inbuf,&offset,length, + &nmb->additional, nmb->header.arcount)) + return(False); + + return(True); +} + +/******************************************************************* + 'Copy constructor' for an nmb packet. +******************************************************************/ + +static struct packet_struct *copy_nmb_packet(struct packet_struct *packet) +{ + struct nmb_packet *nmb; + struct nmb_packet *copy_nmb; + struct packet_struct *pkt_copy; + + if(( pkt_copy = SMB_MALLOC_P(struct packet_struct)) == NULL) { + DEBUG(0,("copy_nmb_packet: malloc fail.\n")); + return NULL; + } + + /* Structure copy of entire thing. */ + + *pkt_copy = *packet; + + /* Ensure this copy is not locked. */ + pkt_copy->locked = False; + + /* Ensure this copy has no resource records. */ + nmb = &packet->packet.nmb; + copy_nmb = &pkt_copy->packet.nmb; + + copy_nmb->answers = NULL; + copy_nmb->nsrecs = NULL; + copy_nmb->additional = NULL; + + /* Now copy any resource records. */ + + if (nmb->answers) { + if((copy_nmb->answers = SMB_MALLOC_ARRAY( + struct res_rec,nmb->header.ancount)) == NULL) + goto free_and_exit; + memcpy((char *)copy_nmb->answers, (char *)nmb->answers, + nmb->header.ancount * sizeof(struct res_rec)); + } + if (nmb->nsrecs) { + if((copy_nmb->nsrecs = SMB_MALLOC_ARRAY( + struct res_rec, nmb->header.nscount)) == NULL) + goto free_and_exit; + memcpy((char *)copy_nmb->nsrecs, (char *)nmb->nsrecs, + nmb->header.nscount * sizeof(struct res_rec)); + } + if (nmb->additional) { + if((copy_nmb->additional = SMB_MALLOC_ARRAY( + struct res_rec, nmb->header.arcount)) == NULL) + goto free_and_exit; + memcpy((char *)copy_nmb->additional, (char *)nmb->additional, + nmb->header.arcount * sizeof(struct res_rec)); + } + + return pkt_copy; + + free_and_exit: + + SAFE_FREE(copy_nmb->answers); + SAFE_FREE(copy_nmb->nsrecs); + SAFE_FREE(copy_nmb->additional); + SAFE_FREE(pkt_copy); + + DEBUG(0,("copy_nmb_packet: malloc fail in resource records.\n")); + return NULL; +} + +/******************************************************************* + 'Copy constructor' for a dgram packet. +******************************************************************/ + +static struct packet_struct *copy_dgram_packet(struct packet_struct *packet) +{ + struct packet_struct *pkt_copy; + + if(( pkt_copy = SMB_MALLOC_P(struct packet_struct)) == NULL) { + DEBUG(0,("copy_dgram_packet: malloc fail.\n")); + return NULL; + } + + /* Structure copy of entire thing. */ + + *pkt_copy = *packet; + + /* Ensure this copy is not locked. */ + pkt_copy->locked = False; + + /* There are no additional pointers in a dgram packet, + we are finished. */ + return pkt_copy; +} + +/******************************************************************* + 'Copy constructor' for a generic packet. +******************************************************************/ + +struct packet_struct *copy_packet(struct packet_struct *packet) +{ + if(packet->packet_type == NMB_PACKET) + return copy_nmb_packet(packet); + else if (packet->packet_type == DGRAM_PACKET) + return copy_dgram_packet(packet); + return NULL; +} + +/******************************************************************* + Free up any resources associated with an nmb packet. +******************************************************************/ + +static void free_nmb_packet(struct nmb_packet *nmb) +{ + SAFE_FREE(nmb->answers); + SAFE_FREE(nmb->nsrecs); + SAFE_FREE(nmb->additional); +} + +/******************************************************************* + Free up any resources associated with a dgram packet. +******************************************************************/ + +static void free_dgram_packet(struct dgram_packet *nmb) +{ + /* We have nothing to do for a dgram packet. */ +} + +/******************************************************************* + Free up any resources associated with a packet. +******************************************************************/ + +void free_packet(struct packet_struct *packet) +{ + if (packet->locked) + return; + if (packet->packet_type == NMB_PACKET) + free_nmb_packet(&packet->packet.nmb); + else if (packet->packet_type == DGRAM_PACKET) + free_dgram_packet(&packet->packet.dgram); + ZERO_STRUCTPN(packet); + SAFE_FREE(packet); +} + +/******************************************************************* + Parse a packet buffer into a packet structure. +******************************************************************/ + +struct packet_struct *parse_packet(char *buf,int length, + enum packet_type packet_type, + struct in_addr ip, + int port) +{ + struct packet_struct *p; + bool ok=False; + + p = SMB_MALLOC_P(struct packet_struct); + if (!p) + return(NULL); + + ZERO_STRUCTP(p); /* initialize for possible padding */ + + p->next = NULL; + p->prev = NULL; + p->ip = ip; + p->port = port; + p->locked = False; + p->timestamp = time(NULL); + p->packet_type = packet_type; + + switch (packet_type) { + case NMB_PACKET: + ok = parse_nmb(buf,length,&p->packet.nmb); + break; + + case DGRAM_PACKET: + ok = parse_dgram(buf,length,&p->packet.dgram); + break; + } + + if (!ok) { + free_packet(p); + return NULL; + } + + return p; +} + +/******************************************************************* + Read a packet from a socket and parse it, returning a packet ready + to be used or put on the queue. This assumes a UDP socket. +******************************************************************/ + +struct packet_struct *read_packet(int fd,enum packet_type packet_type) +{ + struct packet_struct *packet; + struct sockaddr_storage sa; + struct sockaddr_in *si = (struct sockaddr_in *)&sa; + char buf[MAX_DGRAM_SIZE]; + int length; + + length = read_udp_v4_socket(fd,buf,sizeof(buf),&sa); + if (length < MIN_DGRAM_SIZE || sa.ss_family != AF_INET) { + return NULL; + } + + packet = parse_packet(buf, + length, + packet_type, + si->sin_addr, + ntohs(si->sin_port)); + if (!packet) + return NULL; + + packet->fd = fd; + + num_good_receives++; + + DEBUG(5,("Received a packet of len %d from (%s) port %d\n", + length, inet_ntoa(packet->ip), packet->port ) ); + + return(packet); +} + +/******************************************************************* + Send a udp packet on a already open socket. +******************************************************************/ + +static bool send_udp(int fd,char *buf,int len,struct in_addr ip,int port) +{ + bool ret = False; + int i; + struct sockaddr_in sock_out; + + /* set the address and port */ + memset((char *)&sock_out,'\0',sizeof(sock_out)); + putip((char *)&sock_out.sin_addr,(char *)&ip); + sock_out.sin_port = htons( port ); + sock_out.sin_family = AF_INET; + + DEBUG( 5, ( "Sending a packet of len %d to (%s) on port %d\n", + len, inet_ntoa(ip), port ) ); + + /* + * Patch to fix asynch error notifications from Linux kernel. + */ + + for (i = 0; i < 5; i++) { + ret = (sendto(fd,buf,len,0,(struct sockaddr *)&sock_out, + sizeof(sock_out)) >= 0); + if (ret || errno != ECONNREFUSED) + break; + } + + if (!ret) + DEBUG(0,("Packet send failed to %s(%d) ERRNO=%s\n", + inet_ntoa(ip),port,strerror(errno))); + + if (ret) + num_good_sends++; + + return(ret); +} + +/******************************************************************* + Build a dgram packet ready for sending. + If buf == NULL this is a length calculation. +******************************************************************/ + +static int build_dgram(char *buf, size_t len, struct dgram_packet *dgram) +{ + unsigned char *ubuf = (unsigned char *)buf; + int offset=0; + + /* put in the header */ + if (buf) { + ubuf[0] = dgram->header.msg_type; + ubuf[1] = (((int)dgram->header.flags.node_type)<<2); + if (dgram->header.flags.more) + ubuf[1] |= 1; + if (dgram->header.flags.first) + ubuf[1] |= 2; + RSSVAL(ubuf,2,dgram->header.dgm_id); + putip(ubuf+4,(char *)&dgram->header.source_ip); + RSSVAL(ubuf,8,dgram->header.source_port); + RSSVAL(ubuf,12,dgram->header.packet_offset); + } + + offset = 14; + + if (dgram->header.msg_type == 0x10 || + dgram->header.msg_type == 0x11 || + dgram->header.msg_type == 0x12) { + offset += put_nmb_name((char *)ubuf,offset,&dgram->source_name); + offset += put_nmb_name((char *)ubuf,offset,&dgram->dest_name); + } + + if (buf) { + memcpy(ubuf+offset,dgram->data,dgram->datasize); + } + offset += dgram->datasize; + + /* automatically set the dgm_length + * NOTE: RFC1002 says the dgm_length does *not* + * include the fourteen-byte header. crh + */ + dgram->header.dgm_length = (offset - 14); + if (buf) { + RSSVAL(ubuf,10,dgram->header.dgm_length); + } + + return offset; +} + +/******************************************************************* + Build a nmb name +*******************************************************************/ + +void make_nmb_name( struct nmb_name *n, const char *name, int type) +{ + fstring unix_name; + memset( (char *)n, '\0', sizeof(struct nmb_name) ); + fstrcpy(unix_name, name); + strupper_m(unix_name); + push_ascii(n->name, unix_name, sizeof(n->name), STR_TERMINATE); + n->name_type = (unsigned int)type & 0xFF; + push_ascii(n->scope, global_scope(), 64, STR_TERMINATE); +} + +/******************************************************************* + Compare two nmb names +******************************************************************/ + +bool nmb_name_equal(struct nmb_name *n1, struct nmb_name *n2) +{ + return ((n1->name_type == n2->name_type) && + strequal(n1->name ,n2->name ) && + strequal(n1->scope,n2->scope)); +} + +/******************************************************************* + Build a nmb packet ready for sending. + If buf == NULL this is a length calculation. +******************************************************************/ + +static int build_nmb(char *buf, size_t len, struct nmb_packet *nmb) +{ + unsigned char *ubuf = (unsigned char *)buf; + int offset=0; + + if (len && len < 12) { + return 0; + } + + /* put in the header */ + if (buf) { + RSSVAL(ubuf,offset,nmb->header.name_trn_id); + ubuf[offset+2] = (nmb->header.opcode & 0xF) << 3; + if (nmb->header.response) + ubuf[offset+2] |= (1<<7); + if (nmb->header.nm_flags.authoritative && + nmb->header.response) + ubuf[offset+2] |= 0x4; + if (nmb->header.nm_flags.trunc) + ubuf[offset+2] |= 0x2; + if (nmb->header.nm_flags.recursion_desired) + ubuf[offset+2] |= 0x1; + if (nmb->header.nm_flags.recursion_available && + nmb->header.response) + ubuf[offset+3] |= 0x80; + if (nmb->header.nm_flags.bcast) + ubuf[offset+3] |= 0x10; + ubuf[offset+3] |= (nmb->header.rcode & 0xF); + + RSSVAL(ubuf,offset+4,nmb->header.qdcount); + RSSVAL(ubuf,offset+6,nmb->header.ancount); + RSSVAL(ubuf,offset+8,nmb->header.nscount); + RSSVAL(ubuf,offset+10,nmb->header.arcount); + } + + offset += 12; + if (nmb->header.qdcount) { + /* XXXX this doesn't handle a qdcount of > 1 */ + if (len) { + /* Length check. */ + int extra = put_nmb_name(NULL,offset, + &nmb->question.question_name); + if (offset + extra > len) { + return 0; + } + } + offset += put_nmb_name((char *)ubuf,offset, + &nmb->question.question_name); + if (buf) { + RSSVAL(ubuf,offset,nmb->question.question_type); + RSSVAL(ubuf,offset+2,nmb->question.question_class); + } + offset += 4; + } + + if (nmb->header.ancount) { + if (len) { + /* Length check. */ + int extra = put_res_rec(NULL,offset,nmb->answers, + nmb->header.ancount); + if (offset + extra > len) { + return 0; + } + } + offset += put_res_rec((char *)ubuf,offset,nmb->answers, + nmb->header.ancount); + } + + if (nmb->header.nscount) { + if (len) { + /* Length check. */ + int extra = put_res_rec(NULL,offset,nmb->nsrecs, + nmb->header.nscount); + if (offset + extra > len) { + return 0; + } + } + offset += put_res_rec((char *)ubuf,offset,nmb->nsrecs, + nmb->header.nscount); + } + + /* + * The spec says we must put compressed name pointers + * in the following outgoing packets : + * NAME_REGISTRATION_REQUEST, NAME_REFRESH_REQUEST, + * NAME_RELEASE_REQUEST. + */ + + if((nmb->header.response == False) && + ((nmb->header.opcode == NMB_NAME_REG_OPCODE) || + (nmb->header.opcode == NMB_NAME_RELEASE_OPCODE) || + (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) || + (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9) || + (nmb->header.opcode == NMB_NAME_MULTIHOMED_REG_OPCODE)) && + (nmb->header.arcount == 1)) { + + if (len) { + /* Length check. */ + int extra = put_compressed_name_ptr(NULL,offset, + nmb->additional,12); + if (offset + extra > len) { + return 0; + } + } + offset += put_compressed_name_ptr(ubuf,offset, + nmb->additional,12); + } else if (nmb->header.arcount) { + if (len) { + /* Length check. */ + int extra = put_res_rec(NULL,offset,nmb->additional, + nmb->header.arcount); + if (offset + extra > len) { + return 0; + } + } + offset += put_res_rec((char *)ubuf,offset,nmb->additional, + nmb->header.arcount); + } + return offset; +} + +/******************************************************************* + Linearise a packet. +******************************************************************/ + +int build_packet(char *buf, size_t buflen, struct packet_struct *p) +{ + int len = 0; + + switch (p->packet_type) { + case NMB_PACKET: + len = build_nmb(buf,buflen,&p->packet.nmb); + break; + + case DGRAM_PACKET: + len = build_dgram(buf,buflen,&p->packet.dgram); + break; + } + + return len; +} + +/******************************************************************* + Send a packet_struct. +******************************************************************/ + +bool send_packet(struct packet_struct *p) +{ + char buf[1024]; + int len=0; + + memset(buf,'\0',sizeof(buf)); + + len = build_packet(buf, sizeof(buf), p); + + if (!len) + return(False); + + return(send_udp(p->fd,buf,len,p->ip,p->port)); +} + +/**************************************************************************** + Receive a packet with timeout on a open UDP filedescriptor. + The timeout is in milliseconds +***************************************************************************/ + +struct packet_struct *receive_packet(int fd,enum packet_type type,int t) +{ + fd_set fds; + struct timeval timeout; + int ret; + + FD_ZERO(&fds); + FD_SET(fd,&fds); + timeout.tv_sec = t/1000; + timeout.tv_usec = 1000*(t%1000); + + if ((ret = sys_select_intr(fd+1,&fds,NULL,NULL,&timeout)) == -1) { + /* errno should be EBADF or EINVAL. */ + DEBUG(0,("select returned -1, errno = %s (%d)\n", + strerror(errno), errno)); + return NULL; + } + + if (ret == 0) /* timeout */ + return NULL; + + if (FD_ISSET(fd,&fds)) + return(read_packet(fd,type)); + + return(NULL); +} + +/**************************************************************************** + Receive a UDP/137 packet either via UDP or from the unexpected packet + queue. The packet must be a reply packet and have the specified trn_id. + The timeout is in milliseconds. +***************************************************************************/ + +struct packet_struct *receive_nmb_packet(int fd, int t, int trn_id) +{ + struct packet_struct *p; + + p = receive_packet(fd, NMB_PACKET, t); + + if (p && p->packet.nmb.header.response && + p->packet.nmb.header.name_trn_id == trn_id) { + return p; + } + if (p) + free_packet(p); + + /* try the unexpected packet queue */ + return receive_unexpected(NMB_PACKET, trn_id, NULL); +} + +/**************************************************************************** + Receive a UDP/138 packet either via UDP or from the unexpected packet + queue. The packet must be a reply packet and have the specified mailslot name + The timeout is in milliseconds. +***************************************************************************/ + +struct packet_struct *receive_dgram_packet(int fd, int t, + const char *mailslot_name) +{ + struct packet_struct *p; + + p = receive_packet(fd, DGRAM_PACKET, t); + + if (p && match_mailslot_name(p, mailslot_name)) { + return p; + } + if (p) + free_packet(p); + + /* try the unexpected packet queue */ + return receive_unexpected(DGRAM_PACKET, 0, mailslot_name); +} + +/**************************************************************************** + See if a datagram has the right mailslot name. +***************************************************************************/ + +bool match_mailslot_name(struct packet_struct *p, const char *mailslot_name) +{ + struct dgram_packet *dgram = &p->packet.dgram; + char *buf; + + buf = &dgram->data[0]; + buf -= 4; + + buf = smb_buf(buf); + + if (memcmp(buf, mailslot_name, strlen(mailslot_name)+1) == 0) { + return True; + } + + return False; +} + +/**************************************************************************** + Return the number of bits that match between two len character buffers +***************************************************************************/ + +int matching_len_bits(unsigned char *p1, unsigned char *p2, size_t len) +{ + size_t i, j; + int ret = 0; + for (i=0; i<len; i++) { + if (p1[i] != p2[i]) + break; + ret += 8; + } + + if (i==len) + return ret; + + for (j=0; j<8; j++) { + if ((p1[i] & (1<<(7-j))) != (p2[i] & (1<<(7-j)))) + break; + ret++; + } + + return ret; +} + +static unsigned char sort_ip[4]; + +/**************************************************************************** + Compare two query reply records. +***************************************************************************/ + +static int name_query_comp(unsigned char *p1, unsigned char *p2) +{ + return matching_len_bits(p2+2, sort_ip, 4) - + matching_len_bits(p1+2, sort_ip, 4); +} + +/**************************************************************************** + Sort a set of 6 byte name query response records so that the IPs that + have the most leading bits in common with the specified address come first. +***************************************************************************/ + +void sort_query_replies(char *data, int n, struct in_addr ip) +{ + if (n <= 1) + return; + + putip(sort_ip, (char *)&ip); + + qsort(data, n, 6, QSORT_CAST name_query_comp); +} + +/**************************************************************************** + Interpret the weird netbios "name" into a unix fstring. Return the name type. +****************************************************************************/ + +static int name_interpret(char *in, fstring name) +{ + int ret; + int len = (*in++) / 2; + fstring out_string; + char *out = out_string; + + *out=0; + + if (len > 30 || len<1) + return(0); + + while (len--) { + if (in[0] < 'A' || in[0] > 'P' || in[1] < 'A' || in[1] > 'P') { + *out = 0; + return(0); + } + *out = ((in[0]-'A')<<4) + (in[1]-'A'); + in += 2; + out++; + } + ret = out[-1]; + out[-1] = 0; + +#ifdef NETBIOS_SCOPE + /* Handle any scope names */ + while(*in) { + *out++ = '.'; /* Scope names are separated by periods */ + len = *(unsigned char *)in++; + StrnCpy(out, in, len); + out += len; + *out=0; + in += len; + } +#endif + pull_ascii_fstring(name, out_string); + + return(ret); +} + +/**************************************************************************** + Mangle a name into netbios format. + Note: <Out> must be (33 + strlen(scope) + 2) bytes long, at minimum. +****************************************************************************/ + +int name_mangle( char *In, char *Out, char name_type ) +{ + int i; + int len; + nstring buf; + char *p = Out; + + /* Safely copy the input string, In, into buf[]. */ + if (strcmp(In,"*") == 0) + put_name(buf, "*", '\0', 0x00); + else { + /* We use an fstring here as mb dos names can expend x3 when + going to utf8. */ + fstring buf_unix; + nstring buf_dos; + + pull_ascii_fstring(buf_unix, In); + strupper_m(buf_unix); + + push_ascii_nstring(buf_dos, buf_unix); + put_name(buf, buf_dos, ' ', name_type); + } + + /* Place the length of the first field into the output buffer. */ + p[0] = 32; + p++; + + /* Now convert the name to the rfc1001/1002 format. */ + for( i = 0; i < MAX_NETBIOSNAME_LEN; i++ ) { + p[i*2] = ( (buf[i] >> 4) & 0x000F ) + 'A'; + p[(i*2)+1] = (buf[i] & 0x000F) + 'A'; + } + p += 32; + p[0] = '\0'; + + /* Add the scope string. */ + for( i = 0, len = 0; *(global_scope()) != '\0'; i++, len++ ) { + switch( (global_scope())[i] ) { + case '\0': + p[0] = len; + if( len > 0 ) + p[len+1] = 0; + return( name_len(Out) ); + case '.': + p[0] = len; + p += (len + 1); + len = -1; + break; + default: + p[len+1] = (global_scope())[i]; + break; + } + } + + return( name_len(Out) ); +} + +/**************************************************************************** + Find a pointer to a netbios name. +****************************************************************************/ + +static char *name_ptr(char *buf,int ofs) +{ + unsigned char c = *(unsigned char *)(buf+ofs); + + if ((c & 0xC0) == 0xC0) { + uint16 l = RSVAL(buf, ofs) & 0x3FFF; + DEBUG(5,("name ptr to pos %d from %d is %s\n",l,ofs,buf+l)); + return(buf + l); + } else { + return(buf+ofs); + } +} + +/**************************************************************************** + Extract a netbios name from a buf (into a unix string) return name type. +****************************************************************************/ + +int name_extract(char *buf,int ofs, fstring name) +{ + char *p = name_ptr(buf,ofs); + int d = PTR_DIFF(p,buf+ofs); + + name[0] = '\0'; + if (d < -50 || d > 50) + return(0); + return(name_interpret(p,name)); +} + +/**************************************************************************** + Return the total storage length of a mangled name. +****************************************************************************/ + +int name_len(char *s1) +{ + /* NOTE: this argument _must_ be unsigned */ + unsigned char *s = (unsigned char *)s1; + int len; + + /* If the two high bits of the byte are set, return 2. */ + if (0xC0 == (*s & 0xC0)) + return(2); + + /* Add up the length bytes. */ + for (len = 1; (*s); s += (*s) + 1) { + len += *s + 1; + SMB_ASSERT(len < 80); + } + + return(len); +} diff --git a/source3/libsmb/nterr.c b/source3/libsmb/nterr.c new file mode 100644 index 0000000000..fc6340342f --- /dev/null +++ b/source3/libsmb/nterr.c @@ -0,0 +1,759 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Luke Kenneth Casson Leighton 1997-2001. + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NT error codes. please read nterr.h */ + +#include "includes.h" + +typedef struct +{ + const char *nt_errstr; + NTSTATUS nt_errcode; +} nt_err_code_struct; + +static const nt_err_code_struct nt_errs[] = +{ + { "NT_STATUS_OK", NT_STATUS_OK }, + { "NT_STATUS_UNSUCCESSFUL", NT_STATUS_UNSUCCESSFUL }, + { "NT_STATUS_NOT_IMPLEMENTED", NT_STATUS_NOT_IMPLEMENTED }, + { "NT_STATUS_INVALID_INFO_CLASS", NT_STATUS_INVALID_INFO_CLASS }, + { "NT_STATUS_INFO_LENGTH_MISMATCH", NT_STATUS_INFO_LENGTH_MISMATCH }, + { "NT_STATUS_ACCESS_VIOLATION", NT_STATUS_ACCESS_VIOLATION }, + { "STATUS_BUFFER_OVERFLOW", STATUS_BUFFER_OVERFLOW }, + { "NT_STATUS_IN_PAGE_ERROR", NT_STATUS_IN_PAGE_ERROR }, + { "NT_STATUS_PAGEFILE_QUOTA", NT_STATUS_PAGEFILE_QUOTA }, + { "NT_STATUS_INVALID_HANDLE", NT_STATUS_INVALID_HANDLE }, + { "NT_STATUS_BAD_INITIAL_STACK", NT_STATUS_BAD_INITIAL_STACK }, + { "NT_STATUS_BAD_INITIAL_PC", NT_STATUS_BAD_INITIAL_PC }, + { "NT_STATUS_INVALID_CID", NT_STATUS_INVALID_CID }, + { "NT_STATUS_TIMER_NOT_CANCELED", NT_STATUS_TIMER_NOT_CANCELED }, + { "NT_STATUS_INVALID_PARAMETER", NT_STATUS_INVALID_PARAMETER }, + { "NT_STATUS_NO_SUCH_DEVICE", NT_STATUS_NO_SUCH_DEVICE }, + { "NT_STATUS_NO_SUCH_FILE", NT_STATUS_NO_SUCH_FILE }, + { "NT_STATUS_INVALID_DEVICE_REQUEST", NT_STATUS_INVALID_DEVICE_REQUEST }, + { "NT_STATUS_END_OF_FILE", NT_STATUS_END_OF_FILE }, + { "NT_STATUS_WRONG_VOLUME", NT_STATUS_WRONG_VOLUME }, + { "NT_STATUS_NO_MEDIA_IN_DEVICE", NT_STATUS_NO_MEDIA_IN_DEVICE }, + { "NT_STATUS_UNRECOGNIZED_MEDIA", NT_STATUS_UNRECOGNIZED_MEDIA }, + { "NT_STATUS_NONEXISTENT_SECTOR", NT_STATUS_NONEXISTENT_SECTOR }, + { "NT_STATUS_MORE_PROCESSING_REQUIRED", NT_STATUS_MORE_PROCESSING_REQUIRED }, + { "NT_STATUS_NO_MEMORY", NT_STATUS_NO_MEMORY }, + { "NT_STATUS_CONFLICTING_ADDRESSES", NT_STATUS_CONFLICTING_ADDRESSES }, + { "NT_STATUS_NOT_MAPPED_VIEW", NT_STATUS_NOT_MAPPED_VIEW }, + { "NT_STATUS_UNABLE_TO_FREE_VM", NT_STATUS_UNABLE_TO_FREE_VM }, + { "NT_STATUS_UNABLE_TO_DELETE_SECTION", NT_STATUS_UNABLE_TO_DELETE_SECTION }, + { "NT_STATUS_INVALID_SYSTEM_SERVICE", NT_STATUS_INVALID_SYSTEM_SERVICE }, + { "NT_STATUS_ILLEGAL_INSTRUCTION", NT_STATUS_ILLEGAL_INSTRUCTION }, + { "NT_STATUS_INVALID_LOCK_SEQUENCE", NT_STATUS_INVALID_LOCK_SEQUENCE }, + { "NT_STATUS_INVALID_VIEW_SIZE", NT_STATUS_INVALID_VIEW_SIZE }, + { "NT_STATUS_INVALID_FILE_FOR_SECTION", NT_STATUS_INVALID_FILE_FOR_SECTION }, + { "NT_STATUS_ALREADY_COMMITTED", NT_STATUS_ALREADY_COMMITTED }, + { "NT_STATUS_ACCESS_DENIED", NT_STATUS_ACCESS_DENIED }, + { "NT_STATUS_BUFFER_TOO_SMALL", NT_STATUS_BUFFER_TOO_SMALL }, + { "NT_STATUS_OBJECT_TYPE_MISMATCH", NT_STATUS_OBJECT_TYPE_MISMATCH }, + { "NT_STATUS_NONCONTINUABLE_EXCEPTION", NT_STATUS_NONCONTINUABLE_EXCEPTION }, + { "NT_STATUS_INVALID_DISPOSITION", NT_STATUS_INVALID_DISPOSITION }, + { "NT_STATUS_UNWIND", NT_STATUS_UNWIND }, + { "NT_STATUS_BAD_STACK", NT_STATUS_BAD_STACK }, + { "NT_STATUS_INVALID_UNWIND_TARGET", NT_STATUS_INVALID_UNWIND_TARGET }, + { "NT_STATUS_NOT_LOCKED", NT_STATUS_NOT_LOCKED }, + { "NT_STATUS_PARITY_ERROR", NT_STATUS_PARITY_ERROR }, + { "NT_STATUS_UNABLE_TO_DECOMMIT_VM", NT_STATUS_UNABLE_TO_DECOMMIT_VM }, + { "NT_STATUS_NOT_COMMITTED", NT_STATUS_NOT_COMMITTED }, + { "NT_STATUS_INVALID_PORT_ATTRIBUTES", NT_STATUS_INVALID_PORT_ATTRIBUTES }, + { "NT_STATUS_PORT_MESSAGE_TOO_LONG", NT_STATUS_PORT_MESSAGE_TOO_LONG }, + { "NT_STATUS_INVALID_PARAMETER_MIX", NT_STATUS_INVALID_PARAMETER_MIX }, + { "NT_STATUS_INVALID_QUOTA_LOWER", NT_STATUS_INVALID_QUOTA_LOWER }, + { "NT_STATUS_DISK_CORRUPT_ERROR", NT_STATUS_DISK_CORRUPT_ERROR }, + { "NT_STATUS_OBJECT_NAME_INVALID", NT_STATUS_OBJECT_NAME_INVALID }, + { "NT_STATUS_OBJECT_NAME_NOT_FOUND", NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { "NT_STATUS_OBJECT_NAME_COLLISION", NT_STATUS_OBJECT_NAME_COLLISION }, + { "NT_STATUS_HANDLE_NOT_WAITABLE", NT_STATUS_HANDLE_NOT_WAITABLE }, + { "NT_STATUS_PORT_DISCONNECTED", NT_STATUS_PORT_DISCONNECTED }, + { "NT_STATUS_DEVICE_ALREADY_ATTACHED", NT_STATUS_DEVICE_ALREADY_ATTACHED }, + { "NT_STATUS_OBJECT_PATH_INVALID", NT_STATUS_OBJECT_PATH_INVALID }, + { "NT_STATUS_OBJECT_PATH_NOT_FOUND", NT_STATUS_OBJECT_PATH_NOT_FOUND }, + { "NT_STATUS_OBJECT_PATH_SYNTAX_BAD", NT_STATUS_OBJECT_PATH_SYNTAX_BAD }, + { "NT_STATUS_DATA_OVERRUN", NT_STATUS_DATA_OVERRUN }, + { "NT_STATUS_DATA_LATE_ERROR", NT_STATUS_DATA_LATE_ERROR }, + { "NT_STATUS_DATA_ERROR", NT_STATUS_DATA_ERROR }, + { "NT_STATUS_CRC_ERROR", NT_STATUS_CRC_ERROR }, + { "NT_STATUS_SECTION_TOO_BIG", NT_STATUS_SECTION_TOO_BIG }, + { "NT_STATUS_PORT_CONNECTION_REFUSED", NT_STATUS_PORT_CONNECTION_REFUSED }, + { "NT_STATUS_INVALID_PORT_HANDLE", NT_STATUS_INVALID_PORT_HANDLE }, + { "NT_STATUS_SHARING_VIOLATION", NT_STATUS_SHARING_VIOLATION }, + { "NT_STATUS_QUOTA_EXCEEDED", NT_STATUS_QUOTA_EXCEEDED }, + { "NT_STATUS_INVALID_PAGE_PROTECTION", NT_STATUS_INVALID_PAGE_PROTECTION }, + { "NT_STATUS_MUTANT_NOT_OWNED", NT_STATUS_MUTANT_NOT_OWNED }, + { "NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED", NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED }, + { "NT_STATUS_PORT_ALREADY_SET", NT_STATUS_PORT_ALREADY_SET }, + { "NT_STATUS_SECTION_NOT_IMAGE", NT_STATUS_SECTION_NOT_IMAGE }, + { "NT_STATUS_SUSPEND_COUNT_EXCEEDED", NT_STATUS_SUSPEND_COUNT_EXCEEDED }, + { "NT_STATUS_THREAD_IS_TERMINATING", NT_STATUS_THREAD_IS_TERMINATING }, + { "NT_STATUS_BAD_WORKING_SET_LIMIT", NT_STATUS_BAD_WORKING_SET_LIMIT }, + { "NT_STATUS_INCOMPATIBLE_FILE_MAP", NT_STATUS_INCOMPATIBLE_FILE_MAP }, + { "NT_STATUS_SECTION_PROTECTION", NT_STATUS_SECTION_PROTECTION }, + { "NT_STATUS_EAS_NOT_SUPPORTED", NT_STATUS_EAS_NOT_SUPPORTED }, + { "NT_STATUS_EA_TOO_LARGE", NT_STATUS_EA_TOO_LARGE }, + { "NT_STATUS_NONEXISTENT_EA_ENTRY", NT_STATUS_NONEXISTENT_EA_ENTRY }, + { "NT_STATUS_NO_EAS_ON_FILE", NT_STATUS_NO_EAS_ON_FILE }, + { "NT_STATUS_EA_CORRUPT_ERROR", NT_STATUS_EA_CORRUPT_ERROR }, + { "NT_STATUS_FILE_LOCK_CONFLICT", NT_STATUS_FILE_LOCK_CONFLICT }, + { "NT_STATUS_LOCK_NOT_GRANTED", NT_STATUS_LOCK_NOT_GRANTED }, + { "NT_STATUS_DELETE_PENDING", NT_STATUS_DELETE_PENDING }, + { "NT_STATUS_CTL_FILE_NOT_SUPPORTED", NT_STATUS_CTL_FILE_NOT_SUPPORTED }, + { "NT_STATUS_UNKNOWN_REVISION", NT_STATUS_UNKNOWN_REVISION }, + { "NT_STATUS_REVISION_MISMATCH", NT_STATUS_REVISION_MISMATCH }, + { "NT_STATUS_INVALID_OWNER", NT_STATUS_INVALID_OWNER }, + { "NT_STATUS_INVALID_PRIMARY_GROUP", NT_STATUS_INVALID_PRIMARY_GROUP }, + { "NT_STATUS_NO_IMPERSONATION_TOKEN", NT_STATUS_NO_IMPERSONATION_TOKEN }, + { "NT_STATUS_CANT_DISABLE_MANDATORY", NT_STATUS_CANT_DISABLE_MANDATORY }, + { "NT_STATUS_NO_LOGON_SERVERS", NT_STATUS_NO_LOGON_SERVERS }, + { "NT_STATUS_NO_SUCH_LOGON_SESSION", NT_STATUS_NO_SUCH_LOGON_SESSION }, + { "NT_STATUS_NO_SUCH_PRIVILEGE", NT_STATUS_NO_SUCH_PRIVILEGE }, + { "NT_STATUS_PRIVILEGE_NOT_HELD", NT_STATUS_PRIVILEGE_NOT_HELD }, + { "NT_STATUS_INVALID_ACCOUNT_NAME", NT_STATUS_INVALID_ACCOUNT_NAME }, + { "NT_STATUS_USER_EXISTS", NT_STATUS_USER_EXISTS }, + { "NT_STATUS_NO_SUCH_USER", NT_STATUS_NO_SUCH_USER }, + { "NT_STATUS_GROUP_EXISTS", NT_STATUS_GROUP_EXISTS }, + { "NT_STATUS_NO_SUCH_GROUP", NT_STATUS_NO_SUCH_GROUP }, + { "NT_STATUS_MEMBER_IN_GROUP", NT_STATUS_MEMBER_IN_GROUP }, + { "NT_STATUS_MEMBER_NOT_IN_GROUP", NT_STATUS_MEMBER_NOT_IN_GROUP }, + { "NT_STATUS_LAST_ADMIN", NT_STATUS_LAST_ADMIN }, + { "NT_STATUS_WRONG_PASSWORD", NT_STATUS_WRONG_PASSWORD }, + { "NT_STATUS_ILL_FORMED_PASSWORD", NT_STATUS_ILL_FORMED_PASSWORD }, + { "NT_STATUS_PASSWORD_RESTRICTION", NT_STATUS_PASSWORD_RESTRICTION }, + { "NT_STATUS_LOGON_FAILURE", NT_STATUS_LOGON_FAILURE }, + { "NT_STATUS_ACCOUNT_RESTRICTION", NT_STATUS_ACCOUNT_RESTRICTION }, + { "NT_STATUS_INVALID_LOGON_HOURS", NT_STATUS_INVALID_LOGON_HOURS }, + { "NT_STATUS_INVALID_WORKSTATION", NT_STATUS_INVALID_WORKSTATION }, + { "NT_STATUS_PASSWORD_EXPIRED", NT_STATUS_PASSWORD_EXPIRED }, + { "NT_STATUS_ACCOUNT_DISABLED", NT_STATUS_ACCOUNT_DISABLED }, + { "NT_STATUS_NONE_MAPPED", NT_STATUS_NONE_MAPPED }, + { "NT_STATUS_TOO_MANY_LUIDS_REQUESTED", NT_STATUS_TOO_MANY_LUIDS_REQUESTED }, + { "NT_STATUS_LUIDS_EXHAUSTED", NT_STATUS_LUIDS_EXHAUSTED }, + { "NT_STATUS_INVALID_SUB_AUTHORITY", NT_STATUS_INVALID_SUB_AUTHORITY }, + { "NT_STATUS_INVALID_ACL", NT_STATUS_INVALID_ACL }, + { "NT_STATUS_INVALID_SID", NT_STATUS_INVALID_SID }, + { "NT_STATUS_INVALID_SECURITY_DESCR", NT_STATUS_INVALID_SECURITY_DESCR }, + { "NT_STATUS_PROCEDURE_NOT_FOUND", NT_STATUS_PROCEDURE_NOT_FOUND }, + { "NT_STATUS_INVALID_IMAGE_FORMAT", NT_STATUS_INVALID_IMAGE_FORMAT }, + { "NT_STATUS_NO_TOKEN", NT_STATUS_NO_TOKEN }, + { "NT_STATUS_BAD_INHERITANCE_ACL", NT_STATUS_BAD_INHERITANCE_ACL }, + { "NT_STATUS_RANGE_NOT_LOCKED", NT_STATUS_RANGE_NOT_LOCKED }, + { "NT_STATUS_DISK_FULL", NT_STATUS_DISK_FULL }, + { "NT_STATUS_SERVER_DISABLED", NT_STATUS_SERVER_DISABLED }, + { "NT_STATUS_SERVER_NOT_DISABLED", NT_STATUS_SERVER_NOT_DISABLED }, + { "NT_STATUS_TOO_MANY_GUIDS_REQUESTED", NT_STATUS_TOO_MANY_GUIDS_REQUESTED }, + { "NT_STATUS_GUIDS_EXHAUSTED", NT_STATUS_GUIDS_EXHAUSTED }, + { "NT_STATUS_INVALID_ID_AUTHORITY", NT_STATUS_INVALID_ID_AUTHORITY }, + { "NT_STATUS_AGENTS_EXHAUSTED", NT_STATUS_AGENTS_EXHAUSTED }, + { "NT_STATUS_INVALID_VOLUME_LABEL", NT_STATUS_INVALID_VOLUME_LABEL }, + { "NT_STATUS_SECTION_NOT_EXTENDED", NT_STATUS_SECTION_NOT_EXTENDED }, + { "NT_STATUS_NOT_MAPPED_DATA", NT_STATUS_NOT_MAPPED_DATA }, + { "NT_STATUS_RESOURCE_DATA_NOT_FOUND", NT_STATUS_RESOURCE_DATA_NOT_FOUND }, + { "NT_STATUS_RESOURCE_TYPE_NOT_FOUND", NT_STATUS_RESOURCE_TYPE_NOT_FOUND }, + { "NT_STATUS_RESOURCE_NAME_NOT_FOUND", NT_STATUS_RESOURCE_NAME_NOT_FOUND }, + { "NT_STATUS_ARRAY_BOUNDS_EXCEEDED", NT_STATUS_ARRAY_BOUNDS_EXCEEDED }, + { "NT_STATUS_FLOAT_DENORMAL_OPERAND", NT_STATUS_FLOAT_DENORMAL_OPERAND }, + { "NT_STATUS_FLOAT_DIVIDE_BY_ZERO", NT_STATUS_FLOAT_DIVIDE_BY_ZERO }, + { "NT_STATUS_FLOAT_INEXACT_RESULT", NT_STATUS_FLOAT_INEXACT_RESULT }, + { "NT_STATUS_FLOAT_INVALID_OPERATION", NT_STATUS_FLOAT_INVALID_OPERATION }, + { "NT_STATUS_FLOAT_OVERFLOW", NT_STATUS_FLOAT_OVERFLOW }, + { "NT_STATUS_FLOAT_STACK_CHECK", NT_STATUS_FLOAT_STACK_CHECK }, + { "NT_STATUS_FLOAT_UNDERFLOW", NT_STATUS_FLOAT_UNDERFLOW }, + { "NT_STATUS_INTEGER_DIVIDE_BY_ZERO", NT_STATUS_INTEGER_DIVIDE_BY_ZERO }, + { "NT_STATUS_INTEGER_OVERFLOW", NT_STATUS_INTEGER_OVERFLOW }, + { "NT_STATUS_PRIVILEGED_INSTRUCTION", NT_STATUS_PRIVILEGED_INSTRUCTION }, + { "NT_STATUS_TOO_MANY_PAGING_FILES", NT_STATUS_TOO_MANY_PAGING_FILES }, + { "NT_STATUS_FILE_INVALID", NT_STATUS_FILE_INVALID }, + { "NT_STATUS_ALLOTTED_SPACE_EXCEEDED", NT_STATUS_ALLOTTED_SPACE_EXCEEDED }, + { "NT_STATUS_INSUFFICIENT_RESOURCES", NT_STATUS_INSUFFICIENT_RESOURCES }, + { "NT_STATUS_DFS_EXIT_PATH_FOUND", NT_STATUS_DFS_EXIT_PATH_FOUND }, + { "NT_STATUS_DEVICE_DATA_ERROR", NT_STATUS_DEVICE_DATA_ERROR }, + { "NT_STATUS_DEVICE_NOT_CONNECTED", NT_STATUS_DEVICE_NOT_CONNECTED }, + { "NT_STATUS_DEVICE_POWER_FAILURE", NT_STATUS_DEVICE_POWER_FAILURE }, + { "NT_STATUS_FREE_VM_NOT_AT_BASE", NT_STATUS_FREE_VM_NOT_AT_BASE }, + { "NT_STATUS_MEMORY_NOT_ALLOCATED", NT_STATUS_MEMORY_NOT_ALLOCATED }, + { "NT_STATUS_WORKING_SET_QUOTA", NT_STATUS_WORKING_SET_QUOTA }, + { "NT_STATUS_MEDIA_WRITE_PROTECTED", NT_STATUS_MEDIA_WRITE_PROTECTED }, + { "NT_STATUS_DEVICE_NOT_READY", NT_STATUS_DEVICE_NOT_READY }, + { "NT_STATUS_INVALID_GROUP_ATTRIBUTES", NT_STATUS_INVALID_GROUP_ATTRIBUTES }, + { "NT_STATUS_BAD_IMPERSONATION_LEVEL", NT_STATUS_BAD_IMPERSONATION_LEVEL }, + { "NT_STATUS_CANT_OPEN_ANONYMOUS", NT_STATUS_CANT_OPEN_ANONYMOUS }, + { "NT_STATUS_BAD_VALIDATION_CLASS", NT_STATUS_BAD_VALIDATION_CLASS }, + { "NT_STATUS_BAD_TOKEN_TYPE", NT_STATUS_BAD_TOKEN_TYPE }, + { "NT_STATUS_BAD_MASTER_BOOT_RECORD", NT_STATUS_BAD_MASTER_BOOT_RECORD }, + { "NT_STATUS_INSTRUCTION_MISALIGNMENT", NT_STATUS_INSTRUCTION_MISALIGNMENT }, + { "NT_STATUS_INSTANCE_NOT_AVAILABLE", NT_STATUS_INSTANCE_NOT_AVAILABLE }, + { "NT_STATUS_PIPE_NOT_AVAILABLE", NT_STATUS_PIPE_NOT_AVAILABLE }, + { "NT_STATUS_INVALID_PIPE_STATE", NT_STATUS_INVALID_PIPE_STATE }, + { "NT_STATUS_PIPE_BUSY", NT_STATUS_PIPE_BUSY }, + { "NT_STATUS_ILLEGAL_FUNCTION", NT_STATUS_ILLEGAL_FUNCTION }, + { "NT_STATUS_PIPE_DISCONNECTED", NT_STATUS_PIPE_DISCONNECTED }, + { "NT_STATUS_PIPE_CLOSING", NT_STATUS_PIPE_CLOSING }, + { "NT_STATUS_PIPE_CONNECTED", NT_STATUS_PIPE_CONNECTED }, + { "NT_STATUS_PIPE_LISTENING", NT_STATUS_PIPE_LISTENING }, + { "NT_STATUS_INVALID_READ_MODE", NT_STATUS_INVALID_READ_MODE }, + { "NT_STATUS_IO_TIMEOUT", NT_STATUS_IO_TIMEOUT }, + { "NT_STATUS_FILE_FORCED_CLOSED", NT_STATUS_FILE_FORCED_CLOSED }, + { "NT_STATUS_PROFILING_NOT_STARTED", NT_STATUS_PROFILING_NOT_STARTED }, + { "NT_STATUS_PROFILING_NOT_STOPPED", NT_STATUS_PROFILING_NOT_STOPPED }, + { "NT_STATUS_COULD_NOT_INTERPRET", NT_STATUS_COULD_NOT_INTERPRET }, + { "NT_STATUS_FILE_IS_A_DIRECTORY", NT_STATUS_FILE_IS_A_DIRECTORY }, + { "NT_STATUS_NOT_SUPPORTED", NT_STATUS_NOT_SUPPORTED }, + { "NT_STATUS_REMOTE_NOT_LISTENING", NT_STATUS_REMOTE_NOT_LISTENING }, + { "NT_STATUS_DUPLICATE_NAME", NT_STATUS_DUPLICATE_NAME }, + { "NT_STATUS_BAD_NETWORK_PATH", NT_STATUS_BAD_NETWORK_PATH }, + { "NT_STATUS_NETWORK_BUSY", NT_STATUS_NETWORK_BUSY }, + { "NT_STATUS_DEVICE_DOES_NOT_EXIST", NT_STATUS_DEVICE_DOES_NOT_EXIST }, + { "NT_STATUS_TOO_MANY_COMMANDS", NT_STATUS_TOO_MANY_COMMANDS }, + { "NT_STATUS_ADAPTER_HARDWARE_ERROR", NT_STATUS_ADAPTER_HARDWARE_ERROR }, + { "NT_STATUS_INVALID_NETWORK_RESPONSE", NT_STATUS_INVALID_NETWORK_RESPONSE }, + { "NT_STATUS_UNEXPECTED_NETWORK_ERROR", NT_STATUS_UNEXPECTED_NETWORK_ERROR }, + { "NT_STATUS_BAD_REMOTE_ADAPTER", NT_STATUS_BAD_REMOTE_ADAPTER }, + { "NT_STATUS_PRINT_QUEUE_FULL", NT_STATUS_PRINT_QUEUE_FULL }, + { "NT_STATUS_NO_SPOOL_SPACE", NT_STATUS_NO_SPOOL_SPACE }, + { "NT_STATUS_PRINT_CANCELLED", NT_STATUS_PRINT_CANCELLED }, + { "NT_STATUS_NETWORK_NAME_DELETED", NT_STATUS_NETWORK_NAME_DELETED }, + { "NT_STATUS_NETWORK_ACCESS_DENIED", NT_STATUS_NETWORK_ACCESS_DENIED }, + { "NT_STATUS_BAD_DEVICE_TYPE", NT_STATUS_BAD_DEVICE_TYPE }, + { "NT_STATUS_BAD_NETWORK_NAME", NT_STATUS_BAD_NETWORK_NAME }, + { "NT_STATUS_TOO_MANY_NAMES", NT_STATUS_TOO_MANY_NAMES }, + { "NT_STATUS_TOO_MANY_SESSIONS", NT_STATUS_TOO_MANY_SESSIONS }, + { "NT_STATUS_SHARING_PAUSED", NT_STATUS_SHARING_PAUSED }, + { "NT_STATUS_REQUEST_NOT_ACCEPTED", NT_STATUS_REQUEST_NOT_ACCEPTED }, + { "NT_STATUS_REDIRECTOR_PAUSED", NT_STATUS_REDIRECTOR_PAUSED }, + { "NT_STATUS_NET_WRITE_FAULT", NT_STATUS_NET_WRITE_FAULT }, + { "NT_STATUS_PROFILING_AT_LIMIT", NT_STATUS_PROFILING_AT_LIMIT }, + { "NT_STATUS_NOT_SAME_DEVICE", NT_STATUS_NOT_SAME_DEVICE }, + { "NT_STATUS_FILE_RENAMED", NT_STATUS_FILE_RENAMED }, + { "NT_STATUS_VIRTUAL_CIRCUIT_CLOSED", NT_STATUS_VIRTUAL_CIRCUIT_CLOSED }, + { "NT_STATUS_NO_SECURITY_ON_OBJECT", NT_STATUS_NO_SECURITY_ON_OBJECT }, + { "NT_STATUS_CANT_WAIT", NT_STATUS_CANT_WAIT }, + { "NT_STATUS_PIPE_EMPTY", NT_STATUS_PIPE_EMPTY }, + { "NT_STATUS_CANT_ACCESS_DOMAIN_INFO", NT_STATUS_CANT_ACCESS_DOMAIN_INFO }, + { "NT_STATUS_CANT_TERMINATE_SELF", NT_STATUS_CANT_TERMINATE_SELF }, + { "NT_STATUS_INVALID_SERVER_STATE", NT_STATUS_INVALID_SERVER_STATE }, + { "NT_STATUS_INVALID_DOMAIN_STATE", NT_STATUS_INVALID_DOMAIN_STATE }, + { "NT_STATUS_INVALID_DOMAIN_ROLE", NT_STATUS_INVALID_DOMAIN_ROLE }, + { "NT_STATUS_NO_SUCH_DOMAIN", NT_STATUS_NO_SUCH_DOMAIN }, + { "NT_STATUS_DOMAIN_EXISTS", NT_STATUS_DOMAIN_EXISTS }, + { "NT_STATUS_DOMAIN_LIMIT_EXCEEDED", NT_STATUS_DOMAIN_LIMIT_EXCEEDED }, + { "NT_STATUS_OPLOCK_NOT_GRANTED", NT_STATUS_OPLOCK_NOT_GRANTED }, + { "NT_STATUS_INVALID_OPLOCK_PROTOCOL", NT_STATUS_INVALID_OPLOCK_PROTOCOL }, + { "NT_STATUS_INTERNAL_DB_CORRUPTION", NT_STATUS_INTERNAL_DB_CORRUPTION }, + { "NT_STATUS_INTERNAL_ERROR", NT_STATUS_INTERNAL_ERROR }, + { "NT_STATUS_GENERIC_NOT_MAPPED", NT_STATUS_GENERIC_NOT_MAPPED }, + { "NT_STATUS_BAD_DESCRIPTOR_FORMAT", NT_STATUS_BAD_DESCRIPTOR_FORMAT }, + { "NT_STATUS_INVALID_USER_BUFFER", NT_STATUS_INVALID_USER_BUFFER }, + { "NT_STATUS_UNEXPECTED_IO_ERROR", NT_STATUS_UNEXPECTED_IO_ERROR }, + { "NT_STATUS_UNEXPECTED_MM_CREATE_ERR", NT_STATUS_UNEXPECTED_MM_CREATE_ERR }, + { "NT_STATUS_UNEXPECTED_MM_MAP_ERROR", NT_STATUS_UNEXPECTED_MM_MAP_ERROR }, + { "NT_STATUS_UNEXPECTED_MM_EXTEND_ERR", NT_STATUS_UNEXPECTED_MM_EXTEND_ERR }, + { "NT_STATUS_NOT_LOGON_PROCESS", NT_STATUS_NOT_LOGON_PROCESS }, + { "NT_STATUS_LOGON_SESSION_EXISTS", NT_STATUS_LOGON_SESSION_EXISTS }, + { "NT_STATUS_INVALID_PARAMETER_1", NT_STATUS_INVALID_PARAMETER_1 }, + { "NT_STATUS_INVALID_PARAMETER_2", NT_STATUS_INVALID_PARAMETER_2 }, + { "NT_STATUS_INVALID_PARAMETER_3", NT_STATUS_INVALID_PARAMETER_3 }, + { "NT_STATUS_INVALID_PARAMETER_4", NT_STATUS_INVALID_PARAMETER_4 }, + { "NT_STATUS_INVALID_PARAMETER_5", NT_STATUS_INVALID_PARAMETER_5 }, + { "NT_STATUS_INVALID_PARAMETER_6", NT_STATUS_INVALID_PARAMETER_6 }, + { "NT_STATUS_INVALID_PARAMETER_7", NT_STATUS_INVALID_PARAMETER_7 }, + { "NT_STATUS_INVALID_PARAMETER_8", NT_STATUS_INVALID_PARAMETER_8 }, + { "NT_STATUS_INVALID_PARAMETER_9", NT_STATUS_INVALID_PARAMETER_9 }, + { "NT_STATUS_INVALID_PARAMETER_10", NT_STATUS_INVALID_PARAMETER_10 }, + { "NT_STATUS_INVALID_PARAMETER_11", NT_STATUS_INVALID_PARAMETER_11 }, + { "NT_STATUS_INVALID_PARAMETER_12", NT_STATUS_INVALID_PARAMETER_12 }, + { "NT_STATUS_REDIRECTOR_NOT_STARTED", NT_STATUS_REDIRECTOR_NOT_STARTED }, + { "NT_STATUS_REDIRECTOR_STARTED", NT_STATUS_REDIRECTOR_STARTED }, + { "NT_STATUS_STACK_OVERFLOW", NT_STATUS_STACK_OVERFLOW }, + { "NT_STATUS_NO_SUCH_PACKAGE", NT_STATUS_NO_SUCH_PACKAGE }, + { "NT_STATUS_BAD_FUNCTION_TABLE", NT_STATUS_BAD_FUNCTION_TABLE }, + { "NT_STATUS_DIRECTORY_NOT_EMPTY", NT_STATUS_DIRECTORY_NOT_EMPTY }, + { "NT_STATUS_FILE_CORRUPT_ERROR", NT_STATUS_FILE_CORRUPT_ERROR }, + { "NT_STATUS_NOT_A_DIRECTORY", NT_STATUS_NOT_A_DIRECTORY }, + { "NT_STATUS_BAD_LOGON_SESSION_STATE", NT_STATUS_BAD_LOGON_SESSION_STATE }, + { "NT_STATUS_LOGON_SESSION_COLLISION", NT_STATUS_LOGON_SESSION_COLLISION }, + { "NT_STATUS_NAME_TOO_LONG", NT_STATUS_NAME_TOO_LONG }, + { "NT_STATUS_FILES_OPEN", NT_STATUS_FILES_OPEN }, + { "NT_STATUS_CONNECTION_IN_USE", NT_STATUS_CONNECTION_IN_USE }, + { "NT_STATUS_MESSAGE_NOT_FOUND", NT_STATUS_MESSAGE_NOT_FOUND }, + { "NT_STATUS_PROCESS_IS_TERMINATING", NT_STATUS_PROCESS_IS_TERMINATING }, + { "NT_STATUS_INVALID_LOGON_TYPE", NT_STATUS_INVALID_LOGON_TYPE }, + { "NT_STATUS_NO_GUID_TRANSLATION", NT_STATUS_NO_GUID_TRANSLATION }, + { "NT_STATUS_CANNOT_IMPERSONATE", NT_STATUS_CANNOT_IMPERSONATE }, + { "NT_STATUS_IMAGE_ALREADY_LOADED", NT_STATUS_IMAGE_ALREADY_LOADED }, + { "NT_STATUS_ABIOS_NOT_PRESENT", NT_STATUS_ABIOS_NOT_PRESENT }, + { "NT_STATUS_ABIOS_LID_NOT_EXIST", NT_STATUS_ABIOS_LID_NOT_EXIST }, + { "NT_STATUS_ABIOS_LID_ALREADY_OWNED", NT_STATUS_ABIOS_LID_ALREADY_OWNED }, + { "NT_STATUS_ABIOS_NOT_LID_OWNER", NT_STATUS_ABIOS_NOT_LID_OWNER }, + { "NT_STATUS_ABIOS_INVALID_COMMAND", NT_STATUS_ABIOS_INVALID_COMMAND }, + { "NT_STATUS_ABIOS_INVALID_LID", NT_STATUS_ABIOS_INVALID_LID }, + { "NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE", NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE }, + { "NT_STATUS_ABIOS_INVALID_SELECTOR", NT_STATUS_ABIOS_INVALID_SELECTOR }, + { "NT_STATUS_NO_LDT", NT_STATUS_NO_LDT }, + { "NT_STATUS_INVALID_LDT_SIZE", NT_STATUS_INVALID_LDT_SIZE }, + { "NT_STATUS_INVALID_LDT_OFFSET", NT_STATUS_INVALID_LDT_OFFSET }, + { "NT_STATUS_INVALID_LDT_DESCRIPTOR", NT_STATUS_INVALID_LDT_DESCRIPTOR }, + { "NT_STATUS_INVALID_IMAGE_NE_FORMAT", NT_STATUS_INVALID_IMAGE_NE_FORMAT }, + { "NT_STATUS_RXACT_INVALID_STATE", NT_STATUS_RXACT_INVALID_STATE }, + { "NT_STATUS_RXACT_COMMIT_FAILURE", NT_STATUS_RXACT_COMMIT_FAILURE }, + { "NT_STATUS_MAPPED_FILE_SIZE_ZERO", NT_STATUS_MAPPED_FILE_SIZE_ZERO }, + { "NT_STATUS_TOO_MANY_OPENED_FILES", NT_STATUS_TOO_MANY_OPENED_FILES }, + { "NT_STATUS_CANCELLED", NT_STATUS_CANCELLED }, + { "NT_STATUS_CANNOT_DELETE", NT_STATUS_CANNOT_DELETE }, + { "NT_STATUS_INVALID_COMPUTER_NAME", NT_STATUS_INVALID_COMPUTER_NAME }, + { "NT_STATUS_FILE_DELETED", NT_STATUS_FILE_DELETED }, + { "NT_STATUS_SPECIAL_ACCOUNT", NT_STATUS_SPECIAL_ACCOUNT }, + { "NT_STATUS_SPECIAL_GROUP", NT_STATUS_SPECIAL_GROUP }, + { "NT_STATUS_SPECIAL_USER", NT_STATUS_SPECIAL_USER }, + { "NT_STATUS_MEMBERS_PRIMARY_GROUP", NT_STATUS_MEMBERS_PRIMARY_GROUP }, + { "NT_STATUS_FILE_CLOSED", NT_STATUS_FILE_CLOSED }, + { "NT_STATUS_TOO_MANY_THREADS", NT_STATUS_TOO_MANY_THREADS }, + { "NT_STATUS_THREAD_NOT_IN_PROCESS", NT_STATUS_THREAD_NOT_IN_PROCESS }, + { "NT_STATUS_TOKEN_ALREADY_IN_USE", NT_STATUS_TOKEN_ALREADY_IN_USE }, + { "NT_STATUS_PAGEFILE_QUOTA_EXCEEDED", NT_STATUS_PAGEFILE_QUOTA_EXCEEDED }, + { "NT_STATUS_COMMITMENT_LIMIT", NT_STATUS_COMMITMENT_LIMIT }, + { "NT_STATUS_INVALID_IMAGE_LE_FORMAT", NT_STATUS_INVALID_IMAGE_LE_FORMAT }, + { "NT_STATUS_INVALID_IMAGE_NOT_MZ", NT_STATUS_INVALID_IMAGE_NOT_MZ }, + { "NT_STATUS_INVALID_IMAGE_PROTECT", NT_STATUS_INVALID_IMAGE_PROTECT }, + { "NT_STATUS_INVALID_IMAGE_WIN_16", NT_STATUS_INVALID_IMAGE_WIN_16 }, + { "NT_STATUS_LOGON_SERVER_CONFLICT", NT_STATUS_LOGON_SERVER_CONFLICT }, + { "NT_STATUS_TIME_DIFFERENCE_AT_DC", NT_STATUS_TIME_DIFFERENCE_AT_DC }, + { "NT_STATUS_SYNCHRONIZATION_REQUIRED", NT_STATUS_SYNCHRONIZATION_REQUIRED }, + { "NT_STATUS_DLL_NOT_FOUND", NT_STATUS_DLL_NOT_FOUND }, + { "NT_STATUS_OPEN_FAILED", NT_STATUS_OPEN_FAILED }, + { "NT_STATUS_IO_PRIVILEGE_FAILED", NT_STATUS_IO_PRIVILEGE_FAILED }, + { "NT_STATUS_ORDINAL_NOT_FOUND", NT_STATUS_ORDINAL_NOT_FOUND }, + { "NT_STATUS_ENTRYPOINT_NOT_FOUND", NT_STATUS_ENTRYPOINT_NOT_FOUND }, + { "NT_STATUS_CONTROL_C_EXIT", NT_STATUS_CONTROL_C_EXIT }, + { "NT_STATUS_LOCAL_DISCONNECT", NT_STATUS_LOCAL_DISCONNECT }, + { "NT_STATUS_REMOTE_DISCONNECT", NT_STATUS_REMOTE_DISCONNECT }, + { "NT_STATUS_REMOTE_RESOURCES", NT_STATUS_REMOTE_RESOURCES }, + { "NT_STATUS_LINK_FAILED", NT_STATUS_LINK_FAILED }, + { "NT_STATUS_LINK_TIMEOUT", NT_STATUS_LINK_TIMEOUT }, + { "NT_STATUS_INVALID_CONNECTION", NT_STATUS_INVALID_CONNECTION }, + { "NT_STATUS_INVALID_ADDRESS", NT_STATUS_INVALID_ADDRESS }, + { "NT_STATUS_DLL_INIT_FAILED", NT_STATUS_DLL_INIT_FAILED }, + { "NT_STATUS_MISSING_SYSTEMFILE", NT_STATUS_MISSING_SYSTEMFILE }, + { "NT_STATUS_UNHANDLED_EXCEPTION", NT_STATUS_UNHANDLED_EXCEPTION }, + { "NT_STATUS_APP_INIT_FAILURE", NT_STATUS_APP_INIT_FAILURE }, + { "NT_STATUS_PAGEFILE_CREATE_FAILED", NT_STATUS_PAGEFILE_CREATE_FAILED }, + { "NT_STATUS_NO_PAGEFILE", NT_STATUS_NO_PAGEFILE }, + { "NT_STATUS_INVALID_LEVEL", NT_STATUS_INVALID_LEVEL }, + { "NT_STATUS_WRONG_PASSWORD_CORE", NT_STATUS_WRONG_PASSWORD_CORE }, + { "NT_STATUS_ILLEGAL_FLOAT_CONTEXT", NT_STATUS_ILLEGAL_FLOAT_CONTEXT }, + { "NT_STATUS_PIPE_BROKEN", NT_STATUS_PIPE_BROKEN }, + { "NT_STATUS_REGISTRY_CORRUPT", NT_STATUS_REGISTRY_CORRUPT }, + { "NT_STATUS_REGISTRY_IO_FAILED", NT_STATUS_REGISTRY_IO_FAILED }, + { "NT_STATUS_NO_EVENT_PAIR", NT_STATUS_NO_EVENT_PAIR }, + { "NT_STATUS_UNRECOGNIZED_VOLUME", NT_STATUS_UNRECOGNIZED_VOLUME }, + { "NT_STATUS_SERIAL_NO_DEVICE_INITED", NT_STATUS_SERIAL_NO_DEVICE_INITED }, + { "NT_STATUS_NO_SUCH_ALIAS", NT_STATUS_NO_SUCH_ALIAS }, + { "NT_STATUS_MEMBER_NOT_IN_ALIAS", NT_STATUS_MEMBER_NOT_IN_ALIAS }, + { "NT_STATUS_MEMBER_IN_ALIAS", NT_STATUS_MEMBER_IN_ALIAS }, + { "NT_STATUS_ALIAS_EXISTS", NT_STATUS_ALIAS_EXISTS }, + { "NT_STATUS_LOGON_NOT_GRANTED", NT_STATUS_LOGON_NOT_GRANTED }, + { "NT_STATUS_TOO_MANY_SECRETS", NT_STATUS_TOO_MANY_SECRETS }, + { "NT_STATUS_SECRET_TOO_LONG", NT_STATUS_SECRET_TOO_LONG }, + { "NT_STATUS_INTERNAL_DB_ERROR", NT_STATUS_INTERNAL_DB_ERROR }, + { "NT_STATUS_FULLSCREEN_MODE", NT_STATUS_FULLSCREEN_MODE }, + { "NT_STATUS_TOO_MANY_CONTEXT_IDS", NT_STATUS_TOO_MANY_CONTEXT_IDS }, + { "NT_STATUS_LOGON_TYPE_NOT_GRANTED", NT_STATUS_LOGON_TYPE_NOT_GRANTED }, + { "NT_STATUS_NOT_REGISTRY_FILE", NT_STATUS_NOT_REGISTRY_FILE }, + { "NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED", NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED }, + { "NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR", NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR }, + { "NT_STATUS_FT_MISSING_MEMBER", NT_STATUS_FT_MISSING_MEMBER }, + { "NT_STATUS_ILL_FORMED_SERVICE_ENTRY", NT_STATUS_ILL_FORMED_SERVICE_ENTRY }, + { "NT_STATUS_ILLEGAL_CHARACTER", NT_STATUS_ILLEGAL_CHARACTER }, + { "NT_STATUS_UNMAPPABLE_CHARACTER", NT_STATUS_UNMAPPABLE_CHARACTER }, + { "NT_STATUS_UNDEFINED_CHARACTER", NT_STATUS_UNDEFINED_CHARACTER }, + { "NT_STATUS_FLOPPY_VOLUME", NT_STATUS_FLOPPY_VOLUME }, + { "NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND", NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND }, + { "NT_STATUS_FLOPPY_WRONG_CYLINDER", NT_STATUS_FLOPPY_WRONG_CYLINDER }, + { "NT_STATUS_FLOPPY_UNKNOWN_ERROR", NT_STATUS_FLOPPY_UNKNOWN_ERROR }, + { "NT_STATUS_FLOPPY_BAD_REGISTERS", NT_STATUS_FLOPPY_BAD_REGISTERS }, + { "NT_STATUS_DISK_RECALIBRATE_FAILED", NT_STATUS_DISK_RECALIBRATE_FAILED }, + { "NT_STATUS_DISK_OPERATION_FAILED", NT_STATUS_DISK_OPERATION_FAILED }, + { "NT_STATUS_DISK_RESET_FAILED", NT_STATUS_DISK_RESET_FAILED }, + { "NT_STATUS_SHARED_IRQ_BUSY", NT_STATUS_SHARED_IRQ_BUSY }, + { "NT_STATUS_FT_ORPHANING", NT_STATUS_FT_ORPHANING }, + { "NT_STATUS_PARTITION_FAILURE", NT_STATUS_PARTITION_FAILURE }, + { "NT_STATUS_INVALID_BLOCK_LENGTH", NT_STATUS_INVALID_BLOCK_LENGTH }, + { "NT_STATUS_DEVICE_NOT_PARTITIONED", NT_STATUS_DEVICE_NOT_PARTITIONED }, + { "NT_STATUS_UNABLE_TO_LOCK_MEDIA", NT_STATUS_UNABLE_TO_LOCK_MEDIA }, + { "NT_STATUS_UNABLE_TO_UNLOAD_MEDIA", NT_STATUS_UNABLE_TO_UNLOAD_MEDIA }, + { "NT_STATUS_EOM_OVERFLOW", NT_STATUS_EOM_OVERFLOW }, + { "NT_STATUS_NO_MEDIA", NT_STATUS_NO_MEDIA }, + { "NT_STATUS_NO_SUCH_MEMBER", NT_STATUS_NO_SUCH_MEMBER }, + { "NT_STATUS_INVALID_MEMBER", NT_STATUS_INVALID_MEMBER }, + { "NT_STATUS_KEY_DELETED", NT_STATUS_KEY_DELETED }, + { "NT_STATUS_NO_LOG_SPACE", NT_STATUS_NO_LOG_SPACE }, + { "NT_STATUS_TOO_MANY_SIDS", NT_STATUS_TOO_MANY_SIDS }, + { "NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED", NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED }, + { "NT_STATUS_KEY_HAS_CHILDREN", NT_STATUS_KEY_HAS_CHILDREN }, + { "NT_STATUS_CHILD_MUST_BE_VOLATILE", NT_STATUS_CHILD_MUST_BE_VOLATILE }, + { "NT_STATUS_DEVICE_CONFIGURATION_ERROR", NT_STATUS_DEVICE_CONFIGURATION_ERROR }, + { "NT_STATUS_DRIVER_INTERNAL_ERROR", NT_STATUS_DRIVER_INTERNAL_ERROR }, + { "NT_STATUS_INVALID_DEVICE_STATE", NT_STATUS_INVALID_DEVICE_STATE }, + { "NT_STATUS_IO_DEVICE_ERROR", NT_STATUS_IO_DEVICE_ERROR }, + { "NT_STATUS_DEVICE_PROTOCOL_ERROR", NT_STATUS_DEVICE_PROTOCOL_ERROR }, + { "NT_STATUS_BACKUP_CONTROLLER", NT_STATUS_BACKUP_CONTROLLER }, + { "NT_STATUS_LOG_FILE_FULL", NT_STATUS_LOG_FILE_FULL }, + { "NT_STATUS_TOO_LATE", NT_STATUS_TOO_LATE }, + { "NT_STATUS_NO_TRUST_LSA_SECRET", NT_STATUS_NO_TRUST_LSA_SECRET }, + { "NT_STATUS_NO_TRUST_SAM_ACCOUNT", NT_STATUS_NO_TRUST_SAM_ACCOUNT }, + { "NT_STATUS_TRUSTED_DOMAIN_FAILURE", NT_STATUS_TRUSTED_DOMAIN_FAILURE }, + { "NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE", NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE }, + { "NT_STATUS_EVENTLOG_FILE_CORRUPT", NT_STATUS_EVENTLOG_FILE_CORRUPT }, + { "NT_STATUS_EVENTLOG_CANT_START", NT_STATUS_EVENTLOG_CANT_START }, + { "NT_STATUS_TRUST_FAILURE", NT_STATUS_TRUST_FAILURE }, + { "NT_STATUS_MUTANT_LIMIT_EXCEEDED", NT_STATUS_MUTANT_LIMIT_EXCEEDED }, + { "NT_STATUS_NETLOGON_NOT_STARTED", NT_STATUS_NETLOGON_NOT_STARTED }, + { "NT_STATUS_ACCOUNT_EXPIRED", NT_STATUS_ACCOUNT_EXPIRED }, + { "NT_STATUS_POSSIBLE_DEADLOCK", NT_STATUS_POSSIBLE_DEADLOCK }, + { "NT_STATUS_NETWORK_CREDENTIAL_CONFLICT", NT_STATUS_NETWORK_CREDENTIAL_CONFLICT }, + { "NT_STATUS_REMOTE_SESSION_LIMIT", NT_STATUS_REMOTE_SESSION_LIMIT }, + { "NT_STATUS_EVENTLOG_FILE_CHANGED", NT_STATUS_EVENTLOG_FILE_CHANGED }, + { "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT", NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT }, + { "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT", NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT }, + { "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT", NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT }, + { "NT_STATUS_DOMAIN_TRUST_INCONSISTENT", NT_STATUS_DOMAIN_TRUST_INCONSISTENT }, + { "NT_STATUS_FS_DRIVER_REQUIRED", NT_STATUS_FS_DRIVER_REQUIRED }, + { "NT_STATUS_NO_USER_SESSION_KEY", NT_STATUS_NO_USER_SESSION_KEY }, + { "NT_STATUS_USER_SESSION_DELETED", NT_STATUS_USER_SESSION_DELETED }, + { "NT_STATUS_RESOURCE_LANG_NOT_FOUND", NT_STATUS_RESOURCE_LANG_NOT_FOUND }, + { "NT_STATUS_INSUFF_SERVER_RESOURCES", NT_STATUS_INSUFF_SERVER_RESOURCES }, + { "NT_STATUS_INVALID_BUFFER_SIZE", NT_STATUS_INVALID_BUFFER_SIZE }, + { "NT_STATUS_INVALID_ADDRESS_COMPONENT", NT_STATUS_INVALID_ADDRESS_COMPONENT }, + { "NT_STATUS_INVALID_ADDRESS_WILDCARD", NT_STATUS_INVALID_ADDRESS_WILDCARD }, + { "NT_STATUS_TOO_MANY_ADDRESSES", NT_STATUS_TOO_MANY_ADDRESSES }, + { "NT_STATUS_ADDRESS_ALREADY_EXISTS", NT_STATUS_ADDRESS_ALREADY_EXISTS }, + { "NT_STATUS_ADDRESS_CLOSED", NT_STATUS_ADDRESS_CLOSED }, + { "NT_STATUS_CONNECTION_DISCONNECTED", NT_STATUS_CONNECTION_DISCONNECTED }, + { "NT_STATUS_CONNECTION_RESET", NT_STATUS_CONNECTION_RESET }, + { "NT_STATUS_TOO_MANY_NODES", NT_STATUS_TOO_MANY_NODES }, + { "NT_STATUS_TRANSACTION_ABORTED", NT_STATUS_TRANSACTION_ABORTED }, + { "NT_STATUS_TRANSACTION_TIMED_OUT", NT_STATUS_TRANSACTION_TIMED_OUT }, + { "NT_STATUS_TRANSACTION_NO_RELEASE", NT_STATUS_TRANSACTION_NO_RELEASE }, + { "NT_STATUS_TRANSACTION_NO_MATCH", NT_STATUS_TRANSACTION_NO_MATCH }, + { "NT_STATUS_TRANSACTION_RESPONDED", NT_STATUS_TRANSACTION_RESPONDED }, + { "NT_STATUS_TRANSACTION_INVALID_ID", NT_STATUS_TRANSACTION_INVALID_ID }, + { "NT_STATUS_TRANSACTION_INVALID_TYPE", NT_STATUS_TRANSACTION_INVALID_TYPE }, + { "NT_STATUS_NOT_SERVER_SESSION", NT_STATUS_NOT_SERVER_SESSION }, + { "NT_STATUS_NOT_CLIENT_SESSION", NT_STATUS_NOT_CLIENT_SESSION }, + { "NT_STATUS_CANNOT_LOAD_REGISTRY_FILE", NT_STATUS_CANNOT_LOAD_REGISTRY_FILE }, + { "NT_STATUS_DEBUG_ATTACH_FAILED", NT_STATUS_DEBUG_ATTACH_FAILED }, + { "NT_STATUS_SYSTEM_PROCESS_TERMINATED", NT_STATUS_SYSTEM_PROCESS_TERMINATED }, + { "NT_STATUS_DATA_NOT_ACCEPTED", NT_STATUS_DATA_NOT_ACCEPTED }, + { "NT_STATUS_NO_BROWSER_SERVERS_FOUND", NT_STATUS_NO_BROWSER_SERVERS_FOUND }, + { "NT_STATUS_VDM_HARD_ERROR", NT_STATUS_VDM_HARD_ERROR }, + { "NT_STATUS_DRIVER_CANCEL_TIMEOUT", NT_STATUS_DRIVER_CANCEL_TIMEOUT }, + { "NT_STATUS_REPLY_MESSAGE_MISMATCH", NT_STATUS_REPLY_MESSAGE_MISMATCH }, + { "NT_STATUS_MAPPED_ALIGNMENT", NT_STATUS_MAPPED_ALIGNMENT }, + { "NT_STATUS_IMAGE_CHECKSUM_MISMATCH", NT_STATUS_IMAGE_CHECKSUM_MISMATCH }, + { "NT_STATUS_LOST_WRITEBEHIND_DATA", NT_STATUS_LOST_WRITEBEHIND_DATA }, + { "NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID", NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID }, + { "NT_STATUS_PASSWORD_MUST_CHANGE", NT_STATUS_PASSWORD_MUST_CHANGE }, + { "NT_STATUS_NOT_FOUND", NT_STATUS_NOT_FOUND }, + { "NT_STATUS_NOT_TINY_STREAM", NT_STATUS_NOT_TINY_STREAM }, + { "NT_STATUS_RECOVERY_FAILURE", NT_STATUS_RECOVERY_FAILURE }, + { "NT_STATUS_STACK_OVERFLOW_READ", NT_STATUS_STACK_OVERFLOW_READ }, + { "NT_STATUS_FAIL_CHECK", NT_STATUS_FAIL_CHECK }, + { "NT_STATUS_DUPLICATE_OBJECTID", NT_STATUS_DUPLICATE_OBJECTID }, + { "NT_STATUS_OBJECTID_EXISTS", NT_STATUS_OBJECTID_EXISTS }, + { "NT_STATUS_CONVERT_TO_LARGE", NT_STATUS_CONVERT_TO_LARGE }, + { "NT_STATUS_RETRY", NT_STATUS_RETRY }, + { "NT_STATUS_FOUND_OUT_OF_SCOPE", NT_STATUS_FOUND_OUT_OF_SCOPE }, + { "NT_STATUS_ALLOCATE_BUCKET", NT_STATUS_ALLOCATE_BUCKET }, + { "NT_STATUS_PROPSET_NOT_FOUND", NT_STATUS_PROPSET_NOT_FOUND }, + { "NT_STATUS_MARSHALL_OVERFLOW", NT_STATUS_MARSHALL_OVERFLOW }, + { "NT_STATUS_INVALID_VARIANT", NT_STATUS_INVALID_VARIANT }, + { "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND }, + { "NT_STATUS_ACCOUNT_LOCKED_OUT", NT_STATUS_ACCOUNT_LOCKED_OUT }, + { "NT_STATUS_HANDLE_NOT_CLOSABLE", NT_STATUS_HANDLE_NOT_CLOSABLE }, + { "NT_STATUS_CONNECTION_REFUSED", NT_STATUS_CONNECTION_REFUSED }, + { "NT_STATUS_GRACEFUL_DISCONNECT", NT_STATUS_GRACEFUL_DISCONNECT }, + { "NT_STATUS_ADDRESS_ALREADY_ASSOCIATED", NT_STATUS_ADDRESS_ALREADY_ASSOCIATED }, + { "NT_STATUS_ADDRESS_NOT_ASSOCIATED", NT_STATUS_ADDRESS_NOT_ASSOCIATED }, + { "NT_STATUS_CONNECTION_INVALID", NT_STATUS_CONNECTION_INVALID }, + { "NT_STATUS_CONNECTION_ACTIVE", NT_STATUS_CONNECTION_ACTIVE }, + { "NT_STATUS_NETWORK_UNREACHABLE", NT_STATUS_NETWORK_UNREACHABLE }, + { "NT_STATUS_HOST_UNREACHABLE", NT_STATUS_HOST_UNREACHABLE }, + { "NT_STATUS_PROTOCOL_UNREACHABLE", NT_STATUS_PROTOCOL_UNREACHABLE }, + { "NT_STATUS_PORT_UNREACHABLE", NT_STATUS_PORT_UNREACHABLE }, + { "NT_STATUS_REQUEST_ABORTED", NT_STATUS_REQUEST_ABORTED }, + { "NT_STATUS_CONNECTION_ABORTED", NT_STATUS_CONNECTION_ABORTED }, + { "NT_STATUS_BAD_COMPRESSION_BUFFER", NT_STATUS_BAD_COMPRESSION_BUFFER }, + { "NT_STATUS_USER_MAPPED_FILE", NT_STATUS_USER_MAPPED_FILE }, + { "NT_STATUS_AUDIT_FAILED", NT_STATUS_AUDIT_FAILED }, + { "NT_STATUS_TIMER_RESOLUTION_NOT_SET", NT_STATUS_TIMER_RESOLUTION_NOT_SET }, + { "NT_STATUS_CONNECTION_COUNT_LIMIT", NT_STATUS_CONNECTION_COUNT_LIMIT }, + { "NT_STATUS_LOGIN_TIME_RESTRICTION", NT_STATUS_LOGIN_TIME_RESTRICTION }, + { "NT_STATUS_LOGIN_WKSTA_RESTRICTION", NT_STATUS_LOGIN_WKSTA_RESTRICTION }, + { "NT_STATUS_IMAGE_MP_UP_MISMATCH", NT_STATUS_IMAGE_MP_UP_MISMATCH }, + { "NT_STATUS_INSUFFICIENT_LOGON_INFO", NT_STATUS_INSUFFICIENT_LOGON_INFO }, + { "NT_STATUS_BAD_DLL_ENTRYPOINT", NT_STATUS_BAD_DLL_ENTRYPOINT }, + { "NT_STATUS_BAD_SERVICE_ENTRYPOINT", NT_STATUS_BAD_SERVICE_ENTRYPOINT }, + { "NT_STATUS_LPC_REPLY_LOST", NT_STATUS_LPC_REPLY_LOST }, + { "NT_STATUS_IP_ADDRESS_CONFLICT1", NT_STATUS_IP_ADDRESS_CONFLICT1 }, + { "NT_STATUS_IP_ADDRESS_CONFLICT2", NT_STATUS_IP_ADDRESS_CONFLICT2 }, + { "NT_STATUS_REGISTRY_QUOTA_LIMIT", NT_STATUS_REGISTRY_QUOTA_LIMIT }, + { "NT_STATUS_PATH_NOT_COVERED", NT_STATUS_PATH_NOT_COVERED }, + { "NT_STATUS_NO_CALLBACK_ACTIVE", NT_STATUS_NO_CALLBACK_ACTIVE }, + { "NT_STATUS_LICENSE_QUOTA_EXCEEDED", NT_STATUS_LICENSE_QUOTA_EXCEEDED }, + { "NT_STATUS_PWD_TOO_SHORT", NT_STATUS_PWD_TOO_SHORT }, + { "NT_STATUS_PWD_TOO_RECENT", NT_STATUS_PWD_TOO_RECENT }, + { "NT_STATUS_PWD_HISTORY_CONFLICT", NT_STATUS_PWD_HISTORY_CONFLICT }, + { "NT_STATUS_PLUGPLAY_NO_DEVICE", NT_STATUS_PLUGPLAY_NO_DEVICE }, + { "NT_STATUS_UNSUPPORTED_COMPRESSION", NT_STATUS_UNSUPPORTED_COMPRESSION }, + { "NT_STATUS_INVALID_HW_PROFILE", NT_STATUS_INVALID_HW_PROFILE }, + { "NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH", NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH }, + { "NT_STATUS_DRIVER_ORDINAL_NOT_FOUND", NT_STATUS_DRIVER_ORDINAL_NOT_FOUND }, + { "NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND", NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND }, + { "NT_STATUS_RESOURCE_NOT_OWNED", NT_STATUS_RESOURCE_NOT_OWNED }, + { "NT_STATUS_TOO_MANY_LINKS", NT_STATUS_TOO_MANY_LINKS }, + { "NT_STATUS_QUOTA_LIST_INCONSISTENT", NT_STATUS_QUOTA_LIST_INCONSISTENT }, + { "NT_STATUS_FILE_IS_OFFLINE", NT_STATUS_FILE_IS_OFFLINE }, + { "NT_STATUS_DS_NO_MORE_RIDS", NT_STATUS_DS_NO_MORE_RIDS }, + { "NT_STATUS_NOT_A_REPARSE_POINT", NT_STATUS_NOT_A_REPARSE_POINT }, + { "NT_STATUS_DOWNGRADE_DETECTED", NT_STATUS_DOWNGRADE_DETECTED }, + { "NT_STATUS_NO_MORE_ENTRIES", NT_STATUS_NO_MORE_ENTRIES }, + { "STATUS_MORE_ENTRIES", STATUS_MORE_ENTRIES }, + { "STATUS_SOME_UNMAPPED", STATUS_SOME_UNMAPPED }, + { "STATUS_NO_MORE_FILES", STATUS_NO_MORE_FILES }, + { "NT_STATUS_RPC_CANNOT_SUPPORT", NT_STATUS_RPC_CANNOT_SUPPORT }, + { NULL, NT_STATUS(0) } +}; + +/* These need sorting..... */ + +nt_err_code_struct nt_err_desc[] = +{ + { "Success", NT_STATUS_OK }, + { "Undetermined error", NT_STATUS_UNSUCCESSFUL }, + { "Access denied", NT_STATUS_ACCESS_DENIED }, + { "Account locked out", NT_STATUS_ACCOUNT_LOCKED_OUT }, + { "Must change password", NT_STATUS_PASSWORD_MUST_CHANGE }, + { "Password is too short", NT_STATUS_PWD_TOO_SHORT }, + { "Password is too recent", NT_STATUS_PWD_TOO_RECENT }, + { "Password history conflict", NT_STATUS_PWD_HISTORY_CONFLICT }, + { "No logon servers", NT_STATUS_NO_LOGON_SERVERS }, + { "Improperly formed account name", NT_STATUS_INVALID_ACCOUNT_NAME }, + { "User exists", NT_STATUS_USER_EXISTS }, + { "No such user", NT_STATUS_NO_SUCH_USER }, + { "Group exists", NT_STATUS_GROUP_EXISTS }, + { "No such group", NT_STATUS_NO_SUCH_GROUP }, + { "Member not in group", NT_STATUS_MEMBER_NOT_IN_GROUP }, + { "Wrong Password", NT_STATUS_WRONG_PASSWORD }, + { "Ill formed password", NT_STATUS_ILL_FORMED_PASSWORD }, + { "Password restriction", NT_STATUS_PASSWORD_RESTRICTION }, + { "Logon failure", NT_STATUS_LOGON_FAILURE }, + { "Account restriction", NT_STATUS_ACCOUNT_RESTRICTION }, + { "Invalid logon hours", NT_STATUS_INVALID_LOGON_HOURS }, + { "Invalid workstation", NT_STATUS_INVALID_WORKSTATION }, + { "Password expired", NT_STATUS_PASSWORD_EXPIRED }, + { "Account disabled", NT_STATUS_ACCOUNT_DISABLED }, + { "Memory allocation error", NT_STATUS_NO_MEMORY }, + { "No domain controllers located", NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND }, + { "Named pipe not available", NT_STATUS_PIPE_NOT_AVAILABLE }, + { "Not implemented", NT_STATUS_NOT_IMPLEMENTED }, + { "Invalid information class", NT_STATUS_INVALID_INFO_CLASS }, + { "Information length mismatch", NT_STATUS_INFO_LENGTH_MISMATCH }, + { "Access violation", NT_STATUS_ACCESS_VIOLATION }, + { "Invalid handle", NT_STATUS_INVALID_HANDLE }, + { "Invalid parameter", NT_STATUS_INVALID_PARAMETER }, + { "No memory", NT_STATUS_NO_MEMORY }, + { "Buffer too small", NT_STATUS_BUFFER_TOO_SMALL }, + { "Revision mismatch", NT_STATUS_REVISION_MISMATCH }, + { "No such logon session", NT_STATUS_NO_SUCH_LOGON_SESSION }, + { "No such privilege", NT_STATUS_NO_SUCH_PRIVILEGE }, + { "Procedure not found", NT_STATUS_PROCEDURE_NOT_FOUND }, + { "Server disabled", NT_STATUS_SERVER_DISABLED }, + { "Invalid pipe state", NT_STATUS_INVALID_PIPE_STATE }, + { "Named pipe busy", NT_STATUS_PIPE_BUSY }, + { "Illegal function", NT_STATUS_ILLEGAL_FUNCTION }, + { "Named pipe dicconnected", NT_STATUS_PIPE_DISCONNECTED }, + { "Named pipe closing", NT_STATUS_PIPE_CLOSING }, + { "Remote host not listening", NT_STATUS_REMOTE_NOT_LISTENING }, + { "Duplicate name on network", NT_STATUS_DUPLICATE_NAME }, + { "Print queue is full", NT_STATUS_PRINT_QUEUE_FULL }, + { "No print spool space available", NT_STATUS_NO_SPOOL_SPACE }, + { "The network name cannot be found", NT_STATUS_BAD_NETWORK_NAME }, + { "The connection was refused", NT_STATUS_CONNECTION_REFUSED }, + { "Too many names", NT_STATUS_TOO_MANY_NAMES }, + { "Too many sessions", NT_STATUS_TOO_MANY_SESSIONS }, + { "Invalid server state", NT_STATUS_INVALID_SERVER_STATE }, + { "Invalid domain state", NT_STATUS_INVALID_DOMAIN_STATE }, + { "Invalid domain role", NT_STATUS_INVALID_DOMAIN_ROLE }, + { "No such domain", NT_STATUS_NO_SUCH_DOMAIN }, + { "Domain exists", NT_STATUS_DOMAIN_EXISTS }, + { "Domain limit exceeded", NT_STATUS_DOMAIN_LIMIT_EXCEEDED }, + { "Bad logon session state", NT_STATUS_BAD_LOGON_SESSION_STATE }, + { "Logon session collision", NT_STATUS_LOGON_SESSION_COLLISION }, + { "Invalid logon type", NT_STATUS_INVALID_LOGON_TYPE }, + { "Cancelled", NT_STATUS_CANCELLED }, + { "Invalid computer name", NT_STATUS_INVALID_COMPUTER_NAME }, + { "Logon server conflict", NT_STATUS_LOGON_SERVER_CONFLICT }, + { "Time difference at domain controller", NT_STATUS_TIME_DIFFERENCE_AT_DC }, + { "Pipe broken", NT_STATUS_PIPE_BROKEN }, + { "Registry corrupt", NT_STATUS_REGISTRY_CORRUPT }, + { "Too many secrets", NT_STATUS_TOO_MANY_SECRETS }, + { "Too many SIDs", NT_STATUS_TOO_MANY_SIDS }, + { "Lanmanager cross encryption required", NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED }, + { "Log file full", NT_STATUS_LOG_FILE_FULL }, + { "No trusted LSA secret", NT_STATUS_NO_TRUST_LSA_SECRET }, + { "No trusted SAM account", NT_STATUS_NO_TRUST_SAM_ACCOUNT }, + { "Trusted domain failure", NT_STATUS_TRUSTED_DOMAIN_FAILURE }, + { "Trust relationship failure", NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE }, + { "Trust failure", NT_STATUS_TRUST_FAILURE }, + { "Netlogon service not started", NT_STATUS_NETLOGON_NOT_STARTED }, + { "Account expired", NT_STATUS_ACCOUNT_EXPIRED }, + { "Network credential conflict", NT_STATUS_NETWORK_CREDENTIAL_CONFLICT }, + { "Remote session limit", NT_STATUS_REMOTE_SESSION_LIMIT }, + { "No logon interdomain trust account", NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT }, + { "No logon workstation trust account", NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT }, + { "No logon server trust account", NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT }, + { "Domain trust inconsistent", NT_STATUS_DOMAIN_TRUST_INCONSISTENT }, + { "No user session key available", NT_STATUS_NO_USER_SESSION_KEY }, + { "User session deleted", NT_STATUS_USER_SESSION_DELETED }, + { "Insufficient server resources", NT_STATUS_INSUFF_SERVER_RESOURCES }, + { "Insufficient logon information", NT_STATUS_INSUFFICIENT_LOGON_INFO }, + + { "License quota exceeded", NT_STATUS_LICENSE_QUOTA_EXCEEDED }, + { "No more files", STATUS_NO_MORE_FILES }, + + { NULL, NT_STATUS(0) } +}; + +/***************************************************************************** + Returns an NT error message. not amazingly helpful, but better than a number. + *****************************************************************************/ + +const char *nt_errstr(NTSTATUS nt_code) +{ + int idx = 0; + char *result; + +#ifdef HAVE_LDAP + if (NT_STATUS_TYPE(nt_code) == NT_STATUS_TYPE_LDAP) { + return ldap_err2string(NT_STATUS_LDAP_CODE(nt_code)); + } +#endif + + while (nt_errs[idx].nt_errstr != NULL) { + if (NT_STATUS_EQUAL(nt_errs[idx].nt_errcode, nt_code)) { + return nt_errs[idx].nt_errstr; + } + idx++; + } + + result = talloc_asprintf(talloc_tos(), "NT code 0x%08x", + NT_STATUS_V(nt_code)); + SMB_ASSERT(result != NULL); + return result; +} + +/************************************************************************ + Print friendler version fo NT error code + ***********************************************************************/ + +const char *get_friendly_nt_error_msg(NTSTATUS nt_code) +{ + int idx = 0; + + while (nt_err_desc[idx].nt_errstr != NULL) { + if (NT_STATUS_V(nt_err_desc[idx].nt_errcode) == NT_STATUS_V(nt_code)) { + return nt_err_desc[idx].nt_errstr; + } + idx++; + } + + /* fall back to NT_STATUS_XXX string */ + + return nt_errstr(nt_code); +} + +/***************************************************************************** + Returns an NT_STATUS constant as a string for inclusion in autogen C code. + *****************************************************************************/ + +const char *get_nt_error_c_code(NTSTATUS nt_code) +{ + char *result; + int idx = 0; + + while (nt_errs[idx].nt_errstr != NULL) { + if (NT_STATUS_V(nt_errs[idx].nt_errcode) == + NT_STATUS_V(nt_code)) { + return nt_errs[idx].nt_errstr; + } + idx++; + } + + result = talloc_asprintf(talloc_tos(), "NT_STATUS(0x%08x)", + NT_STATUS_V(nt_code)); + SMB_ASSERT(result); + return result; +} + +/***************************************************************************** + Returns the NT_STATUS constant matching the string supplied (as an NTSTATUS) + *****************************************************************************/ + +NTSTATUS nt_status_string_to_code(char *nt_status_str) +{ + int idx = 0; + + while (nt_errs[idx].nt_errstr != NULL) { + if (strcmp(nt_errs[idx].nt_errstr, nt_status_str) == 0) { + return nt_errs[idx].nt_errcode; + } + idx++; + } + return NT_STATUS_UNSUCCESSFUL; +} + +/** + * Squash an NT_STATUS in line with security requirements. + * In an attempt to avoid giving the whole game away when users + * are authenticating, NT replaces both NT_STATUS_NO_SUCH_USER and + * NT_STATUS_WRONG_PASSWORD with NT_STATUS_LOGON_FAILURE in certain situations + * (session setups in particular). + * + * @param nt_status NTSTATUS input for squashing. + * @return the 'squashed' nt_status + **/ + +NTSTATUS nt_status_squash(NTSTATUS nt_status) +{ + if NT_STATUS_IS_OK(nt_status) { + return nt_status; + } else if NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) { + /* Match WinXP and don't give the game away */ + return NT_STATUS_LOGON_FAILURE; + + } else if NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD) { + /* Match WinXP and don't give the game away */ + return NT_STATUS_LOGON_FAILURE; + } else { + return nt_status; + } +} diff --git a/source3/libsmb/ntlm_check.c b/source3/libsmb/ntlm_check.c new file mode 100644 index 0000000000..ae10d7373d --- /dev/null +++ b/source3/libsmb/ntlm_check.c @@ -0,0 +1,463 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Tridgell 1992-2000 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + Copyright (C) Andrew Bartlett 2001-2003 + Copyright (C) Gerald Carter 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/**************************************************************************** + Core of smb password checking routine. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv1(const DATA_BLOB *nt_response, + const uchar *part_passwd, + const DATA_BLOB *sec_blob, + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uchar p24[24]; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false ! */ + return False; + } + + if (sec_blob->length != 8) { + DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect challenge size (%lu)\n", + (unsigned long)sec_blob->length)); + return False; + } + + if (nt_response->length != 24) { + DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect password length (%lu)\n", + (unsigned long)nt_response->length)); + return False; + } + + SMBOWFencrypt(part_passwd, sec_blob->data, p24); + if (user_sess_key != NULL) { + *user_sess_key = data_blob(NULL, 16); + SMBsesskeygen_ntv1(part_passwd, NULL, user_sess_key->data); + } + + +#ifdef DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, nt_response->data, nt_response->length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, p24, 24); +#endif + return (memcmp(p24, nt_response->data, 24) == 0); +} + +/**************************************************************************** + Core of smb password checking routine. (NTLMv2, LMv2) + Note: The same code works with both NTLMv2 and LMv2. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv2(const DATA_BLOB *ntv2_response, + const uchar *part_passwd, + const DATA_BLOB *sec_blob, + const char *user, const char *domain, + bool upper_case_domain, /* should the domain be transformed into upper case? */ + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uchar kr[16]; + uchar value_from_encryption[16]; + uchar client_response[16]; + DATA_BLOB client_key_data; + bool res; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always False */ + return False; + } + + if (sec_blob->length != 8) { + DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect challenge size (%lu)\n", + (unsigned long)sec_blob->length)); + return False; + } + + if (ntv2_response->length < 24) { + /* We MUST have more than 16 bytes, or the stuff below will go + crazy. No known implementation sends less than the 24 bytes + for LMv2, let alone NTLMv2. */ + DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect password length (%lu)\n", + (unsigned long)ntv2_response->length)); + return False; + } + + client_key_data = data_blob(ntv2_response->data+16, ntv2_response->length-16); + /* + todo: should we be checking this for anything? We can't for LMv2, + but for NTLMv2 it is meant to contain the current time etc. + */ + + memcpy(client_response, ntv2_response->data, sizeof(client_response)); + + if (!ntv2_owf_gen(part_passwd, user, domain, upper_case_domain, kr)) { + return False; + } + + SMBOWFencrypt_ntv2(kr, sec_blob, &client_key_data, value_from_encryption); + if (user_sess_key != NULL) { + *user_sess_key = data_blob(NULL, 16); + SMBsesskeygen_ntv2(kr, value_from_encryption, user_sess_key->data); + } + +#if DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, ntv2_response->data, ntv2_response->length); + DEBUGADD(100,("Variable data from client was |\n")); + dump_data(100, client_key_data.data, client_key_data.length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, value_from_encryption, 16); +#endif + data_blob_clear_free(&client_key_data); + res = (memcmp(value_from_encryption, client_response, 16) == 0); + if ((!res) && (user_sess_key != NULL)) + data_blob_clear_free(user_sess_key); + return res; +} + +/** + * Check a challenge-response password against the value of the NT or + * LM password hash. + * + * @param mem_ctx talloc context + * @param challenge 8-byte challenge. If all zero, forces plaintext comparison + * @param nt_response 'unicode' NT response to the challenge, or unicode password + * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param nt_pw MD4 unicode password from our passdb or similar + * @param lm_pw LANMAN ASCII password from our passdb or similar + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_response, + const DATA_BLOB *nt_response, + const DATA_BLOB *lm_interactive_pwd, + const DATA_BLOB *nt_interactive_pwd, + const char *username, + const char *client_username, + const char *client_domain, + const uint8 *lm_pw, const uint8 *nt_pw, + DATA_BLOB *user_sess_key, + DATA_BLOB *lm_sess_key) +{ + unsigned char zeros[8]; + + ZERO_STRUCT(zeros); + + if (nt_pw == NULL) { + DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n", + username)); + } + + if (nt_interactive_pwd && nt_interactive_pwd->length && nt_pw) { + if (nt_interactive_pwd->length != 16) { + DEBUG(3,("ntlm_password_check: Interactive logon: Invalid NT password length (%d) supplied for user %s\n", (int)nt_interactive_pwd->length, + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (memcmp(nt_interactive_pwd->data, nt_pw, 16) == 0) { + if (user_sess_key) { + *user_sess_key = data_blob(NULL, 16); + SMBsesskeygen_ntv1(nt_pw, NULL, user_sess_key->data); + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: Interactive logon: NT password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + } else if (lm_interactive_pwd && lm_interactive_pwd->length && lm_pw) { + if (lm_interactive_pwd->length != 16) { + DEBUG(3,("ntlm_password_check: Interactive logon: Invalid LANMAN password length (%d) supplied for user %s\n", (int)lm_interactive_pwd->length, + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (!lp_lanman_auth()) { + DEBUG(3,("ntlm_password_check: Interactive logon: only LANMAN password supplied for user %s, and LM passwords are disabled!\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (memcmp(lm_interactive_pwd->data, lm_pw, 16) == 0) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: Interactive logon: LANMAN password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } + + /* Check for cleartext netlogon. Used by Exchange 5.5. */ + if (challenge->length == sizeof(zeros) && + (memcmp(challenge->data, zeros, challenge->length) == 0 )) { + + DEBUG(4,("ntlm_password_check: checking plaintext passwords for user %s\n", + username)); + if (nt_pw && nt_response->length) { + unsigned char pwhash[16]; + mdfour(pwhash, nt_response->data, nt_response->length); + if (memcmp(pwhash, nt_pw, sizeof(pwhash)) == 0) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NT (Unicode) plaintext password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + } else if (!lp_lanman_auth()) { + DEBUG(3,("ntlm_password_check: (plaintext password check) LANMAN passwords NOT PERMITTED for user %s\n", + username)); + + } else if (lm_pw && lm_response->length) { + uchar dospwd[14]; + uchar p16[16]; + ZERO_STRUCT(dospwd); + + memcpy(dospwd, lm_response->data, MIN(lm_response->length, sizeof(dospwd))); + /* Only the fisrt 14 chars are considered, password need not be null terminated. */ + + /* we *might* need to upper-case the string here */ + E_P16((const unsigned char *)dospwd, p16); + + if (memcmp(p16, lm_pw, sizeof(p16)) == 0) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: LANMAN (ASCII) plaintext password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } else { + DEBUG(3, ("Plaintext authentication for user %s attempted, but neither NT nor LM passwords available\n", username)); + return NT_STATUS_WRONG_PASSWORD; + } + } + + if (nt_response->length != 0 && nt_response->length < 24) { + DEBUG(2,("ntlm_password_check: invalid NT password length (%lu) for user %s\n", + (unsigned long)nt_response->length, username)); + } + + if (nt_response->length >= 24 && nt_pw) { + if (nt_response->length > 24) { + /* We have the NT MD4 hash challenge available - see if we can + use it + */ + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with domain [%s]\n", client_domain)); + if (smb_pwd_check_ntlmv2( nt_response, + nt_pw, challenge, + client_username, + client_domain, + False, + user_sess_key)) { + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with uppercased version of domain [%s]\n", client_domain)); + if (smb_pwd_check_ntlmv2( nt_response, + nt_pw, challenge, + client_username, + client_domain, + True, + user_sess_key)) { + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2( nt_response, + nt_pw, challenge, + client_username, + "", + False, + user_sess_key)) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NTLMv2 password check failed\n")); + return NT_STATUS_WRONG_PASSWORD; + } + } + + if (lp_ntlm_auth()) { + /* We have the NT MD4 hash challenge available - see if we can + use it (ie. does it exist in the smbpasswd file). + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password\n")); + if (smb_pwd_check_ntlmv1(nt_response, + nt_pw, challenge, + user_sess_key)) { + /* The LM session key for this response is not very secure, + so use it only if we otherwise allow LM authentication */ + + if (lp_lanman_auth() && lm_pw) { + uint8 first_8_lm_hash[16]; + memcpy(first_8_lm_hash, lm_pw, 8); + memset(first_8_lm_hash + 8, '\0', 8); + if (lm_sess_key) { + *lm_sess_key = data_blob(first_8_lm_hash, 16); + } + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NT MD4 password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } else { + DEBUG(2,("ntlm_password_check: NTLMv1 passwords NOT PERMITTED for user %s\n", + username)); + /* no return, becouse we might pick up LMv2 in the LM field */ + } + } + + if (lm_response->length == 0) { + DEBUG(3,("ntlm_password_check: NEITHER LanMan nor NT password supplied for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (lm_response->length < 24) { + DEBUG(2,("ntlm_password_check: invalid LanMan password length (%lu) for user %s\n", + (unsigned long)nt_response->length, username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (!lp_lanman_auth()) { + DEBUG(3,("ntlm_password_check: Lanman passwords NOT PERMITTED for user %s\n", + username)); + } else if (!lm_pw) { + DEBUG(3,("ntlm_password_check: NO LanMan password set for user %s (and no NT password supplied)\n", + username)); + } else { + DEBUG(4,("ntlm_password_check: Checking LM password\n")); + if (smb_pwd_check_ntlmv1(lm_response, + lm_pw, challenge, + NULL)) { + uint8 first_8_lm_hash[16]; + memcpy(first_8_lm_hash, lm_pw, 8); + memset(first_8_lm_hash + 8, '\0', 8); + if (user_sess_key) { + *user_sess_key = data_blob(first_8_lm_hash, 16); + } + + if (lm_sess_key) { + *lm_sess_key = data_blob(first_8_lm_hash, 16); + } + return NT_STATUS_OK; + } + } + + if (!nt_pw) { + DEBUG(4,("ntlm_password_check: LM password check failed for user, no NT password %s\n",username)); + return NT_STATUS_WRONG_PASSWORD; + } + + /* This is for 'LMv2' authentication. almost NTLMv2 but limited to 24 bytes. + - related to Win9X, legacy NAS pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking LMv2 password with domain %s\n", client_domain)); + if (smb_pwd_check_ntlmv2( lm_response, + nt_pw, challenge, + client_username, + client_domain, + False, + NULL)) { + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password with upper-cased version of domain %s\n", client_domain)); + if (smb_pwd_check_ntlmv2( lm_response, + nt_pw, challenge, + client_username, + client_domain, + True, + NULL)) { + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2( lm_response, + nt_pw, challenge, + client_username, + "", + False, + NULL)) { + return NT_STATUS_OK; + } + + /* Apparently NT accepts NT responses in the LM field + - I think this is related to Win9X pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password in LM field\n")); + if (lp_ntlm_auth()) { + if (smb_pwd_check_ntlmv1(lm_response, + nt_pw, challenge, + NULL)) { + /* The session key for this response is still very odd. + It not very secure, so use it only if we otherwise + allow LM authentication */ + + if (lp_lanman_auth() && lm_pw) { + uint8 first_8_lm_hash[16]; + memcpy(first_8_lm_hash, lm_pw, 8); + memset(first_8_lm_hash + 8, '\0', 8); + if (user_sess_key) { + *user_sess_key = data_blob(first_8_lm_hash, 16); + } + + if (lm_sess_key) { + *lm_sess_key = data_blob(first_8_lm_hash, 16); + } + } + return NT_STATUS_OK; + } + DEBUG(3,("ntlm_password_check: LM password, NT MD4 password in LM field and LMv2 failed for user %s\n",username)); + } else { + DEBUG(3,("ntlm_password_check: LM password and LMv2 failed for user %s, and NT MD4 password in LM field not permitted\n",username)); + } + return NT_STATUS_WRONG_PASSWORD; +} + diff --git a/source3/libsmb/ntlmssp.c b/source3/libsmb/ntlmssp.c new file mode 100644 index 0000000000..a0e54ce769 --- /dev/null +++ b/source3/libsmb/ntlmssp.c @@ -0,0 +1,1251 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, server side + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2001-2003 + Copyright (C) Andrew Bartlett 2005 (Updated from gensec). + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +static NTSTATUS ntlmssp_client_initial(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB reply, DATA_BLOB *next_request); +static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB in, DATA_BLOB *out); +static NTSTATUS ntlmssp_client_challenge(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB reply, DATA_BLOB *next_request); +static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB request, DATA_BLOB *reply); + +/** + * Callbacks for NTLMSSP - for both client and server operating modes + * + */ + +static const struct ntlmssp_callbacks { + enum NTLMSSP_ROLE role; + enum NTLM_MESSAGE_TYPE ntlmssp_command; + NTSTATUS (*fn)(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB in, DATA_BLOB *out); +} ntlmssp_callbacks[] = { + {NTLMSSP_CLIENT, NTLMSSP_INITIAL, ntlmssp_client_initial}, + {NTLMSSP_SERVER, NTLMSSP_NEGOTIATE, ntlmssp_server_negotiate}, + {NTLMSSP_CLIENT, NTLMSSP_CHALLENGE, ntlmssp_client_challenge}, + {NTLMSSP_SERVER, NTLMSSP_AUTH, ntlmssp_server_auth}, + {NTLMSSP_CLIENT, NTLMSSP_UNKNOWN, NULL}, + {NTLMSSP_SERVER, NTLMSSP_UNKNOWN, NULL} +}; + + +/** + * Print out the NTLMSSP flags for debugging + * @param neg_flags The flags from the packet + */ + +void debug_ntlmssp_flags(uint32 neg_flags) +{ + DEBUG(3,("Got NTLMSSP neg_flags=0x%08x\n", neg_flags)); + + if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_UNICODE\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_OEM) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_OEM\n")); + if (neg_flags & NTLMSSP_REQUEST_TARGET) + DEBUGADD(4, (" NTLMSSP_REQUEST_TARGET\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_SIGN) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SIGN\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_SEAL) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SEAL\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM_STYLE) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_DATAGRAM_STYLE\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_LM_KEY\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NETWARE) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NETWARE\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NTLM) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_ALWAYS_SIGN\n")); + if (neg_flags & NTLMSSP_CHAL_ACCEPT_RESPONSE) + DEBUGADD(4, (" NTLMSSP_CHAL_ACCEPT_RESPONSE\n")); + if (neg_flags & NTLMSSP_CHAL_NON_NT_SESSION_KEY) + DEBUGADD(4, (" NTLMSSP_CHAL_NON_NT_SESSION_KEY\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NTLM2) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM2\n")); + if (neg_flags & NTLMSSP_CHAL_TARGET_INFO) + DEBUGADD(4, (" NTLMSSP_CHAL_TARGET_INFO\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_VERSION) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_VERSION\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_128) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_128\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_KEY_EXCH\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_56) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_56\n")); +} + +/** + * Default challenge generation code. + * + */ + +static const uint8 *get_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + static uchar chal[8]; + generate_random_buffer(chal, sizeof(chal)); + + return chal; +} + +/** + * Default 'we can set the challenge to anything we like' implementation + * + */ + +static bool may_set_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + return True; +} + +/** + * Default 'we can set the challenge to anything we like' implementation + * + * Does not actually do anything, as the value is always in the structure anyway. + * + */ + +static NTSTATUS set_challenge(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *challenge) +{ + SMB_ASSERT(challenge->length == 8); + return NT_STATUS_OK; +} + +/** + * Set a username on an NTLMSSP context - ensures it is talloc()ed + * + */ + +NTSTATUS ntlmssp_set_username(NTLMSSP_STATE *ntlmssp_state, const char *user) +{ + ntlmssp_state->user = talloc_strdup(ntlmssp_state->mem_ctx, user ? user : "" ); + if (!ntlmssp_state->user) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Store NT and LM hashes on an NTLMSSP context - ensures they are talloc()ed + * + */ +NTSTATUS ntlmssp_set_hashes(NTLMSSP_STATE *ntlmssp_state, + const unsigned char lm_hash[16], + const unsigned char nt_hash[16]) +{ + ntlmssp_state->lm_hash = (unsigned char *) + TALLOC_MEMDUP(ntlmssp_state->mem_ctx, lm_hash, 16); + ntlmssp_state->nt_hash = (unsigned char *) + TALLOC_MEMDUP(ntlmssp_state->mem_ctx, nt_hash, 16); + if (!ntlmssp_state->lm_hash || !ntlmssp_state->nt_hash) { + TALLOC_FREE(ntlmssp_state->lm_hash); + TALLOC_FREE(ntlmssp_state->nt_hash); + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Converts a password to the hashes on an NTLMSSP context. + * + */ +NTSTATUS ntlmssp_set_password(NTLMSSP_STATE *ntlmssp_state, const char *password) +{ + if (!password) { + ntlmssp_state->lm_hash = NULL; + ntlmssp_state->nt_hash = NULL; + } else { + unsigned char lm_hash[16]; + unsigned char nt_hash[16]; + + E_deshash(password, lm_hash); + E_md4hash(password, nt_hash); + return ntlmssp_set_hashes(ntlmssp_state, lm_hash, nt_hash); + } + return NT_STATUS_OK; +} + +/** + * Set a domain on an NTLMSSP context - ensures it is talloc()ed + * + */ +NTSTATUS ntlmssp_set_domain(NTLMSSP_STATE *ntlmssp_state, const char *domain) +{ + ntlmssp_state->domain = talloc_strdup(ntlmssp_state->mem_ctx, domain ? domain : "" ); + if (!ntlmssp_state->domain) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set a workstation on an NTLMSSP context - ensures it is talloc()ed + * + */ +NTSTATUS ntlmssp_set_workstation(NTLMSSP_STATE *ntlmssp_state, const char *workstation) +{ + ntlmssp_state->workstation = talloc_strdup(ntlmssp_state->mem_ctx, workstation); + if (!ntlmssp_state->workstation) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Store a DATA_BLOB containing an NTLMSSP response, for use later. + * This copies the data blob + */ + +NTSTATUS ntlmssp_store_response(NTLMSSP_STATE *ntlmssp_state, + DATA_BLOB response) +{ + ntlmssp_state->stored_response = data_blob_talloc(ntlmssp_state->mem_ctx, + response.data, response.length); + return NT_STATUS_OK; +} + +/** + * Request features for the NTLMSSP negotiation + * + * @param ntlmssp_state NTLMSSP state + * @param feature_list List of space seperated features requested from NTLMSSP. + */ +void ntlmssp_want_feature_list(NTLMSSP_STATE *ntlmssp_state, char *feature_list) +{ + /* + * We need to set this to allow a later SetPassword + * via the SAMR pipe to succeed. Strange.... We could + * also add NTLMSSP_NEGOTIATE_SEAL here. JRA. + */ + if (in_list("NTLMSSP_FEATURE_SESSION_KEY", feature_list, True)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (in_list("NTLMSSP_FEATURE_SIGN", feature_list, True)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if(in_list("NTLMSSP_FEATURE_SEAL", feature_list, True)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } +} + +/** + * Request a feature for the NTLMSSP negotiation + * + * @param ntlmssp_state NTLMSSP state + * @param feature Bit flag specifying the requested feature + */ +void ntlmssp_want_feature(NTLMSSP_STATE *ntlmssp_state, uint32 feature) +{ + /* As per JRA's comment above */ + if (feature & NTLMSSP_FEATURE_SESSION_KEY) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (feature & NTLMSSP_FEATURE_SIGN) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (feature & NTLMSSP_FEATURE_SEAL) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } +} + +/** + * Next state function for the NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State + * @param in The packet in from the NTLMSSP partner, as a DATA_BLOB + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors, NT_STATUS_MORE_PROCESSING_REQUIRED or NT_STATUS_OK. + */ + +NTSTATUS ntlmssp_update(NTLMSSP_STATE *ntlmssp_state, + const DATA_BLOB in, DATA_BLOB *out) +{ + DATA_BLOB input; + uint32 ntlmssp_command; + int i; + + if (ntlmssp_state->expected_state == NTLMSSP_DONE) { + /* Called update after negotiations finished. */ + DEBUG(1, ("Called NTLMSSP after state machine was 'done'\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + *out = data_blob_null; + + if (!in.length && ntlmssp_state->stored_response.length) { + input = ntlmssp_state->stored_response; + + /* we only want to read the stored response once - overwrite it */ + ntlmssp_state->stored_response = data_blob_null; + } else { + input = in; + } + + if (!input.length) { + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + ntlmssp_command = NTLMSSP_INITIAL; + break; + case NTLMSSP_SERVER: + /* 'datagram' mode - no neg packet */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + break; + } + } else { + if (!msrpc_parse(&input, "Cd", + "NTLMSSP", + &ntlmssp_command)) { + DEBUG(1, ("Failed to parse NTLMSSP packet, could not extract NTLMSSP command\n")); + dump_data(2, input.data, input.length); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (ntlmssp_command != ntlmssp_state->expected_state) { + DEBUG(1, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, ntlmssp_state->expected_state)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; ntlmssp_callbacks[i].fn; i++) { + if (ntlmssp_callbacks[i].role == ntlmssp_state->role + && ntlmssp_callbacks[i].ntlmssp_command == ntlmssp_command) { + return ntlmssp_callbacks[i].fn(ntlmssp_state, input, out); + } + } + + DEBUG(1, ("failed to find NTLMSSP callback for NTLMSSP mode %u, command %u\n", + ntlmssp_state->role, ntlmssp_command)); + + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * End an NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State, free()ed by this function + */ + +void ntlmssp_end(NTLMSSP_STATE **ntlmssp_state) +{ + TALLOC_CTX *mem_ctx = (*ntlmssp_state)->mem_ctx; + + (*ntlmssp_state)->ref_count--; + + if ((*ntlmssp_state)->ref_count == 0) { + data_blob_free(&(*ntlmssp_state)->chal); + data_blob_free(&(*ntlmssp_state)->lm_resp); + data_blob_free(&(*ntlmssp_state)->nt_resp); + + talloc_destroy(mem_ctx); + } + + *ntlmssp_state = NULL; + return; +} + +/** + * Determine correct target name flags for reply, given server role + * and negotiated flags + * + * @param ntlmssp_state NTLMSSP State + * @param neg_flags The flags from the packet + * @param chal_flags The flags to be set in the reply packet + * @return The 'target name' string. + */ + +static const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32 neg_flags, uint32 *chal_flags) +{ + if (neg_flags & NTLMSSP_REQUEST_TARGET) { + *chal_flags |= NTLMSSP_CHAL_TARGET_INFO; + *chal_flags |= NTLMSSP_REQUEST_TARGET; + if (ntlmssp_state->server_role == ROLE_STANDALONE) { + *chal_flags |= NTLMSSP_TARGET_TYPE_SERVER; + return ntlmssp_state->get_global_myname(); + } else { + *chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN; + return ntlmssp_state->get_domain(); + }; + } else { + return ""; + } +} + +static void ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32 neg_flags, bool allow_lm) { + if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = True; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = False; + } + + if ((neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) && allow_lm) { + /* other end forcing us to use LM */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + ntlmssp_state->use_ntlmv2 = False; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_NTLM2)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_128)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_56)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_56; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_SEAL)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + + /* Woop Woop - unknown flag for Windows compatibility... + What does this really do ? JRA. */ + if (!(neg_flags & NTLMSSP_NEGOTIATE_VERSION)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_VERSION; + } + + if ((neg_flags & NTLMSSP_REQUEST_TARGET)) { + ntlmssp_state->neg_flags |= NTLMSSP_REQUEST_TARGET; + } +} + +/** + Weaken NTLMSSP keys to cope with down-level clients and servers. + + We probably should have some parameters to control this, but as + it only occours for LM_KEY connections, and this is controlled + by the client lanman auth/lanman auth parameters, it isn't too bad. +*/ + +DATA_BLOB ntlmssp_weaken_keys(NTLMSSP_STATE *ntlmssp_state, TALLOC_CTX *mem_ctx) +{ + DATA_BLOB weakened_key = data_blob_talloc(mem_ctx, + ntlmssp_state->session_key.data, + ntlmssp_state->session_key.length); + + /* Nothing to weaken. We certainly don't want to 'extend' the length... */ + if (weakened_key.length < 16) { + /* perhaps there was no key? */ + return weakened_key; + } + + /* Key weakening not performed on the master key for NTLM2 + and does not occour for NTLM1. Therefore we only need + to do this for the LM_KEY. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) { + /* LM key doesn't support 128 bit crypto, so this is + * the best we can do. If you negotiate 128 bit, but + * not 56, you end up with 40 bit... */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weakened_key.data[7] = 0xa0; + } else { /* forty bits */ + weakened_key.data[5] = 0xe5; + weakened_key.data[6] = 0x38; + weakened_key.data[7] = 0xb0; + } + weakened_key.length = 8; + } + return weakened_key; +} + +/** + * Next state function for the Negotiate packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB + * @param request The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or MORE_PROCESSING_REQUIRED if a reply is sent. + */ + +static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB request, DATA_BLOB *reply) +{ + DATA_BLOB struct_blob; + const char *dnsname; + char *dnsdomname = NULL; + uint32 neg_flags = 0; + uint32 ntlmssp_command, chal_flags; + const uint8 *cryptkey; + const char *target_name; + + /* parse the NTLMSSP packet */ +#if 0 + file_save("ntlmssp_negotiate.dat", request.data, request.length); +#endif + + if (request.length) { + if ((request.length < 16) || !msrpc_parse(&request, "Cdd", + "NTLMSSP", + &ntlmssp_command, + &neg_flags)) { + DEBUG(1, ("ntlmssp_server_negotiate: failed to parse NTLMSSP Negotiate of length %u\n", + (unsigned int)request.length)); + dump_data(2, request.data, request.length); + return NT_STATUS_INVALID_PARAMETER; + } + debug_ntlmssp_flags(neg_flags); + } + + ntlmssp_handle_neg_flags(ntlmssp_state, neg_flags, lp_lanman_auth()); + + /* Ask our caller what challenge they would like in the packet */ + cryptkey = ntlmssp_state->get_challenge(ntlmssp_state); + + /* Check if we may set the challenge */ + if (!ntlmssp_state->may_set_challenge(ntlmssp_state)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + /* The flags we send back are not just the negotiated flags, + * they are also 'what is in this packet'. Therfore, we + * operate on 'chal_flags' from here on + */ + + chal_flags = ntlmssp_state->neg_flags; + + /* get the right name to fill in as 'target' */ + target_name = ntlmssp_target_name(ntlmssp_state, + neg_flags, &chal_flags); + if (target_name == NULL) + return NT_STATUS_INVALID_PARAMETER; + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state->mem_ctx, cryptkey, 8); + ntlmssp_state->internal_chal = data_blob_talloc(ntlmssp_state->mem_ctx, cryptkey, 8); + + /* This should be a 'netbios domain -> DNS domain' mapping */ + dnsdomname = get_mydnsdomname(ntlmssp_state->mem_ctx); + if (!dnsdomname) { + dnsdomname = talloc_strdup(ntlmssp_state->mem_ctx, ""); + } + if (!dnsdomname) { + return NT_STATUS_NO_MEMORY; + } + strlower_m(dnsdomname); + + dnsname = get_mydnsfullname(); + if (!dnsname) { + dnsname = ""; + } + + /* This creates the 'blob' of names that appears at the end of the packet */ + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) + { + msrpc_gen(&struct_blob, "aaaaa", + NTLMSSP_NAME_TYPE_DOMAIN, target_name, + NTLMSSP_NAME_TYPE_SERVER, ntlmssp_state->get_global_myname(), + NTLMSSP_NAME_TYPE_DOMAIN_DNS, dnsdomname, + NTLMSSP_NAME_TYPE_SERVER_DNS, dnsname, + 0, ""); + } else { + struct_blob = data_blob_null; + } + + { + /* Marshel the packet in the right format, be it unicode or ASCII */ + const char *gen_string; + if (ntlmssp_state->unicode) { + gen_string = "CdUdbddB"; + } else { + gen_string = "CdAdbddB"; + } + + msrpc_gen(reply, gen_string, + "NTLMSSP", + NTLMSSP_CHALLENGE, + target_name, + chal_flags, + cryptkey, 8, + 0, 0, + struct_blob.data, struct_blob.length); + } + + data_blob_free(&struct_blob); + + ntlmssp_state->expected_state = NTLMSSP_AUTH; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Authenticate packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB + * @param request The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB request, DATA_BLOB *reply) +{ + DATA_BLOB encrypted_session_key = data_blob_null; + DATA_BLOB user_session_key = data_blob_null; + DATA_BLOB lm_session_key = data_blob_null; + DATA_BLOB session_key = data_blob_null; + uint32 ntlmssp_command, auth_flags; + NTSTATUS nt_status = NT_STATUS_OK; + + /* used by NTLM2 */ + bool doing_ntlm2 = False; + + uchar session_nonce[16]; + uchar session_nonce_hash[16]; + + const char *parse_string; + char *domain = NULL; + char *user = NULL; + char *workstation = NULL; + + /* parse the NTLMSSP packet */ + *reply = data_blob_null; + +#if 0 + file_save("ntlmssp_auth.dat", request.data, request.length); +#endif + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBd"; + } else { + parse_string = "CdBBAAABd"; + } + + data_blob_free(&ntlmssp_state->lm_resp); + data_blob_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->user = NULL; + ntlmssp_state->domain = NULL; + ntlmssp_state->workstation = NULL; + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(&request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &domain, + &user, + &workstation, + &encrypted_session_key, + &auth_flags)) { + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + data_blob_free(&encrypted_session_key); + auth_flags = 0; + + /* Try again with a shorter string (Win9X truncates this packet) */ + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUU"; + } else { + parse_string = "CdBBAAA"; + } + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(&request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &domain, + &user, + &workstation)) { + DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP (tried both formats):\n")); + dump_data(2, request.data, request.length); + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (auth_flags) + ntlmssp_handle_neg_flags(ntlmssp_state, auth_flags, lp_lanman_auth()); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, domain))) { + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + data_blob_free(&encrypted_session_key); + return nt_status; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, user))) { + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + data_blob_free(&encrypted_session_key); + return nt_status; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_workstation(ntlmssp_state, workstation))) { + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + data_blob_free(&encrypted_session_key); + return nt_status; + } + + SAFE_FREE(domain); + SAFE_FREE(user); + SAFE_FREE(workstation); + + DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n", + ntlmssp_state->user, ntlmssp_state->domain, ntlmssp_state->workstation, (unsigned long)ntlmssp_state->lm_resp.length, (unsigned long)ntlmssp_state->nt_resp.length)); + +#if 0 + file_save("nthash1.dat", &ntlmssp_state->nt_resp.data, &ntlmssp_state->nt_resp.length); + file_save("lmhash1.dat", &ntlmssp_state->lm_resp.data, &ntlmssp_state->lm_resp.length); +#endif + + /* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a + client challenge + + However, the NTLM2 flag may still be set for the real NTLMv2 logins, be careful. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (ntlmssp_state->nt_resp.length == 24 && ntlmssp_state->lm_resp.length == 24) { + struct MD5Context md5_session_nonce_ctx; + SMB_ASSERT(ntlmssp_state->internal_chal.data && ntlmssp_state->internal_chal.length == 8); + + doing_ntlm2 = True; + + memcpy(session_nonce, ntlmssp_state->internal_chal.data, 8); + memcpy(&session_nonce[8], ntlmssp_state->lm_resp.data, 8); + + MD5Init(&md5_session_nonce_ctx); + MD5Update(&md5_session_nonce_ctx, session_nonce, 16); + MD5Final(session_nonce_hash, &md5_session_nonce_ctx); + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state->mem_ctx, session_nonce_hash, 8); + + /* LM response is no longer useful */ + data_blob_free(&ntlmssp_state->lm_resp); + + /* We changed the effective challenge - set it */ + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_state->set_challenge(ntlmssp_state, &ntlmssp_state->chal))) { + data_blob_free(&encrypted_session_key); + return nt_status; + } + + /* LM Key is incompatible. */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + } + + /* + * Note we don't check here for NTLMv2 auth settings. If NTLMv2 auth + * is required (by "ntlm auth = no" and "lm auth = no" being set in the + * smb.conf file) and no NTLMv2 response was sent then the password check + * will fail here. JRA. + */ + + /* Finally, actually ask if the password is OK */ + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_state->check_password(ntlmssp_state, + &user_session_key, &lm_session_key))) { + data_blob_free(&encrypted_session_key); + return nt_status; + } + + dump_data_pw("NT session key:\n", user_session_key.data, user_session_key.length); + dump_data_pw("LM first-8:\n", lm_session_key.data, lm_session_key.length); + + /* Handle the different session key derivation for NTLM2 */ + if (doing_ntlm2) { + if (user_session_key.data && user_session_key.length == 16) { + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 16); + hmac_md5(user_session_key.data, session_nonce, + sizeof(session_nonce), session_key.data); + DEBUG(10,("ntlmssp_server_auth: Created NTLM2 session key.\n")); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM2 session key.\n")); + session_key = data_blob_null; + } + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) { + if (lm_session_key.data && lm_session_key.length >= 8) { + if (ntlmssp_state->lm_resp.data && ntlmssp_state->lm_resp.length == 24) { + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + SMBsesskeygen_lm_sess_key(lm_session_key.data, ntlmssp_state->lm_resp.data, + session_key.data); + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + } else { + uint8 zeros[24]; + ZERO_STRUCT(zeros); + session_key = data_blob_talloc( + ntlmssp_state->mem_ctx, NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + SMBsesskeygen_lm_sess_key( + lm_session_key.data, zeros, + session_key.data); + } + dump_data_pw("LM session key:\n", session_key.data, + session_key.length); + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM session key.\n")); + session_key = data_blob_null; + } + } else if (user_session_key.data) { + session_key = user_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified nt session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + } else if (lm_session_key.data) { + session_key = lm_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified lm session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create unmodified session key.\n")); + session_key = data_blob_null; + } + + /* With KEY_EXCH, the client supplies the proposed session key, + but encrypts it with the long-term key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + if (!encrypted_session_key.data || encrypted_session_key.length != 16) { + data_blob_free(&encrypted_session_key); + DEBUG(1, ("Client-supplied KEY_EXCH session key was of invalid length (%u)!\n", + (unsigned int)encrypted_session_key.length)); + return NT_STATUS_INVALID_PARAMETER; + } else if (!session_key.data || session_key.length != 16) { + DEBUG(5, ("server session key is invalid (len == %u), cannot do KEY_EXCH!\n", + (unsigned int)session_key.length)); + ntlmssp_state->session_key = session_key; + } else { + dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length); + SamOEMhash(encrypted_session_key.data, + session_key.data, + encrypted_session_key.length); + ntlmssp_state->session_key = data_blob_talloc(ntlmssp_state->mem_ctx, + encrypted_session_key.data, + encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, + encrypted_session_key.length); + } + } else { + ntlmssp_state->session_key = session_key; + } + + if (!NT_STATUS_IS_OK(nt_status)) { + ntlmssp_state->session_key = data_blob_null; + } else if (ntlmssp_state->session_key.length) { + nt_status = ntlmssp_sign_init(ntlmssp_state); + } + + data_blob_free(&encrypted_session_key); + + /* Only one authentication allowed per server state. */ + ntlmssp_state->expected_state = NTLMSSP_DONE; + + return nt_status; +} + +/** + * Create an NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State, allocated by this function + */ + +NTSTATUS ntlmssp_server_start(NTLMSSP_STATE **ntlmssp_state) +{ + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_init("NTLMSSP context"); + + *ntlmssp_state = TALLOC_ZERO_P(mem_ctx, NTLMSSP_STATE); + if (!*ntlmssp_state) { + DEBUG(0,("ntlmssp_server_start: talloc failed!\n")); + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + (*ntlmssp_state)->role = NTLMSSP_SERVER; + + (*ntlmssp_state)->mem_ctx = mem_ctx; + (*ntlmssp_state)->get_challenge = get_challenge; + (*ntlmssp_state)->set_challenge = set_challenge; + (*ntlmssp_state)->may_set_challenge = may_set_challenge; + + (*ntlmssp_state)->get_global_myname = global_myname; + (*ntlmssp_state)->get_domain = lp_workgroup; + (*ntlmssp_state)->server_role = ROLE_DOMAIN_MEMBER; /* a good default */ + + (*ntlmssp_state)->expected_state = NTLMSSP_NEGOTIATE; + + (*ntlmssp_state)->ref_count = 1; + + (*ntlmssp_state)->neg_flags = + NTLMSSP_NEGOTIATE_128 | + NTLMSSP_NEGOTIATE_56 | + NTLMSSP_NEGOTIATE_VERSION | + NTLMSSP_NEGOTIATE_ALWAYS_SIGN | + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_NTLM2 | + NTLMSSP_NEGOTIATE_KEY_EXCH | + NTLMSSP_NEGOTIATE_SIGN | + NTLMSSP_NEGOTIATE_SEAL; + + return NT_STATUS_OK; +} + +/********************************************************************* + Client side NTLMSSP +*********************************************************************/ + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB. reply.data must be NULL + * @param request The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_client_initial(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB reply, DATA_BLOB *next_request) +{ + if (ntlmssp_state->unicode) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + } else { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + } + + if (ntlmssp_state->use_ntlmv2) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + /* generate the ntlmssp negotiate packet */ + msrpc_gen(next_request, "CddAA", + "NTLMSSP", + NTLMSSP_NEGOTIATE, + ntlmssp_state->neg_flags, + ntlmssp_state->get_domain(), + ntlmssp_state->get_global_myname()); + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB. reply.data must be NULL + * @param request The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_client_challenge(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB reply, DATA_BLOB *next_request) +{ + uint32 chal_flags, ntlmssp_command, unkn1, unkn2; + DATA_BLOB server_domain_blob; + DATA_BLOB challenge_blob; + DATA_BLOB struct_blob = data_blob_null; + char *server_domain; + const char *chal_parse_string; + const char *auth_gen_string; + DATA_BLOB lm_response = data_blob_null; + DATA_BLOB nt_response = data_blob_null; + DATA_BLOB session_key = data_blob_null; + DATA_BLOB encrypted_session_key = data_blob_null; + NTSTATUS nt_status = NT_STATUS_OK; + + if (!msrpc_parse(&reply, "CdBd", + "NTLMSSP", + &ntlmssp_command, + &server_domain_blob, + &chal_flags)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n")); + dump_data(2, reply.data, reply.length); + + return NT_STATUS_INVALID_PARAMETER; + } + + data_blob_free(&server_domain_blob); + + DEBUG(3, ("Got challenge flags:\n")); + debug_ntlmssp_flags(chal_flags); + + ntlmssp_handle_neg_flags(ntlmssp_state, chal_flags, lp_client_lanman_auth()); + + if (ntlmssp_state->unicode) { + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) { + chal_parse_string = "CdUdbddB"; + } else { + chal_parse_string = "CdUdbdd"; + } + auth_gen_string = "CdBBUUUBd"; + } else { + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) { + chal_parse_string = "CdAdbddB"; + } else { + chal_parse_string = "CdAdbdd"; + } + + auth_gen_string = "CdBBAAABd"; + } + + DEBUG(3, ("NTLMSSP: Set final flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (!msrpc_parse(&reply, chal_parse_string, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8, + &unkn1, &unkn2, + &struct_blob)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n")); + dump_data(2, reply.data, reply.length); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->server_domain = talloc_strdup(ntlmssp_state->mem_ctx, + server_domain); + + SAFE_FREE(server_domain); + if (challenge_blob.length != 8) { + data_blob_free(&struct_blob); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->nt_hash || !ntlmssp_state->lm_hash) { + uchar zeros[16]; + /* do nothing - blobs are zero length */ + + ZERO_STRUCT(zeros); + + /* session key is all zeros */ + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, zeros, 16); + + /* not doing NLTM2 without a password */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } else if (ntlmssp_state->use_ntlmv2) { + + if (!struct_blob.length) { + /* be lazy, match win2k - we can't do NTLMv2 without it */ + DEBUG(1, ("Server did not provide 'target information', required for NTLMv2\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: if the remote server is standalone, then we should replace 'domain' + with the server name as supplied above */ + + if (!SMBNTLMv2encrypt_hash(ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->nt_hash, &challenge_blob, + &struct_blob, + &lm_response, &nt_response, &session_key)) { + data_blob_free(&challenge_blob); + data_blob_free(&struct_blob); + return NT_STATUS_NO_MEMORY; + } + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + struct MD5Context md5_session_nonce_ctx; + uchar session_nonce[16]; + uchar session_nonce_hash[16]; + uchar user_session_key[16]; + + lm_response = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 24); + generate_random_buffer(lm_response.data, 8); + memset(lm_response.data+8, 0, 16); + + memcpy(session_nonce, challenge_blob.data, 8); + memcpy(&session_nonce[8], lm_response.data, 8); + + MD5Init(&md5_session_nonce_ctx); + MD5Update(&md5_session_nonce_ctx, challenge_blob.data, 8); + MD5Update(&md5_session_nonce_ctx, lm_response.data, 8); + MD5Final(session_nonce_hash, &md5_session_nonce_ctx); + + DEBUG(5, ("NTLMSSP challenge set by NTLM2\n")); + DEBUG(5, ("challenge is: \n")); + dump_data(5, session_nonce_hash, 8); + + nt_response = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 24); + SMBNTencrypt_hash(ntlmssp_state->nt_hash, + session_nonce_hash, + nt_response.data); + + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 16); + + SMBsesskeygen_ntv1(ntlmssp_state->nt_hash, NULL, user_session_key); + hmac_md5(user_session_key, session_nonce, sizeof(session_nonce), session_key.data); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + } else { + /* lanman auth is insecure, it may be disabled */ + if (lp_client_lanman_auth()) { + lm_response = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 24); + SMBencrypt_hash(ntlmssp_state->lm_hash,challenge_blob.data, + lm_response.data); + } + + nt_response = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 24); + SMBNTencrypt_hash(ntlmssp_state->nt_hash,challenge_blob.data, + nt_response.data); + + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, NULL, 16); + if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + && lp_client_lanman_auth()) { + SMBsesskeygen_lm_sess_key(ntlmssp_state->lm_hash, lm_response.data, + session_key.data); + dump_data_pw("LM session key\n", session_key.data, session_key.length); + } else { + SMBsesskeygen_ntv1(ntlmssp_state->nt_hash, NULL, session_key.data); + dump_data_pw("NT session key:\n", session_key.data, session_key.length); + } + } + data_blob_free(&struct_blob); + + /* Key exchange encryptes a new client-generated session key with + the password-derived key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + /* Make up a new session key */ + uint8 client_session_key[16]; + generate_random_buffer(client_session_key, sizeof(client_session_key)); + + /* Encrypt the new session key with the old one */ + encrypted_session_key = data_blob(client_session_key, sizeof(client_session_key)); + dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, encrypted_session_key.length); + SamOEMhash(encrypted_session_key.data, session_key.data, encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length); + + /* Mark the new session key as the 'real' session key */ + data_blob_free(&session_key); + session_key = data_blob_talloc(ntlmssp_state->mem_ctx, client_session_key, sizeof(client_session_key)); + } + + /* this generates the actual auth packet */ + if (!msrpc_gen(next_request, auth_gen_string, + "NTLMSSP", + NTLMSSP_AUTH, + lm_response.data, lm_response.length, + nt_response.data, nt_response.length, + ntlmssp_state->domain, + ntlmssp_state->user, + ntlmssp_state->get_global_myname(), + encrypted_session_key.data, encrypted_session_key.length, + ntlmssp_state->neg_flags)) { + + return NT_STATUS_NO_MEMORY; + } + + data_blob_free(&encrypted_session_key); + + data_blob_free(&ntlmssp_state->chal); + + ntlmssp_state->session_key = session_key; + + ntlmssp_state->chal = challenge_blob; + ntlmssp_state->lm_resp = lm_response; + ntlmssp_state->nt_resp = nt_response; + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_sign_init(ntlmssp_state))) { + DEBUG(1, ("Could not setup NTLMSSP signing/sealing system (error was: %s)\n", nt_errstr(nt_status))); + } + + return nt_status; +} + +NTSTATUS ntlmssp_client_start(NTLMSSP_STATE **ntlmssp_state) +{ + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_init("NTLMSSP Client context"); + + *ntlmssp_state = TALLOC_ZERO_P(mem_ctx, NTLMSSP_STATE); + if (!*ntlmssp_state) { + DEBUG(0,("ntlmssp_client_start: talloc failed!\n")); + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + (*ntlmssp_state)->role = NTLMSSP_CLIENT; + + (*ntlmssp_state)->mem_ctx = mem_ctx; + + (*ntlmssp_state)->get_global_myname = global_myname; + (*ntlmssp_state)->get_domain = lp_workgroup; + + (*ntlmssp_state)->unicode = True; + + (*ntlmssp_state)->use_ntlmv2 = lp_client_ntlmv2_auth(); + + (*ntlmssp_state)->expected_state = NTLMSSP_INITIAL; + + (*ntlmssp_state)->ref_count = 1; + + (*ntlmssp_state)->neg_flags = + NTLMSSP_NEGOTIATE_128 | + NTLMSSP_NEGOTIATE_ALWAYS_SIGN | + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_NTLM2 | + NTLMSSP_NEGOTIATE_KEY_EXCH | + NTLMSSP_REQUEST_TARGET; + + return NT_STATUS_OK; +} diff --git a/source3/libsmb/ntlmssp_parse.c b/source3/libsmb/ntlmssp_parse.c new file mode 100644 index 0000000000..70377cba7d --- /dev/null +++ b/source3/libsmb/ntlmssp_parse.c @@ -0,0 +1,384 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5/SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + Copyright (C) Andrew Bartlett 2002-2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* + this is a tiny msrpc packet generator. I am only using this to + avoid tying this code to a particular varient of our rpc code. This + generator is not general enough for all our rpc needs, its just + enough for the spnego/ntlmssp code + + format specifiers are: + + U = unicode string (input is unix string) + a = address (input is char *unix_string) + (1 byte type, 1 byte length, unicode/ASCII string, all inline) + A = ASCII string (input is unix string) + B = data blob (pointer + length) + b = data blob in header (pointer + length) + D + d = word (4 bytes) + C = constant ascii string + */ +bool msrpc_gen(DATA_BLOB *blob, + const char *format, ...) +{ + int i, n; + va_list ap; + char *s; + uint8 *b; + int head_size=0, data_size=0; + int head_ofs, data_ofs; + + /* first scan the format to work out the header and body size */ + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + s = va_arg(ap, char *); + head_size += 8; + data_size += str_charnum(s) * 2; + break; + case 'A': + s = va_arg(ap, char *); + head_size += 8; + data_size += str_ascii_charnum(s); + break; + case 'a': + n = va_arg(ap, int); + s = va_arg(ap, char *); + data_size += (str_charnum(s) * 2) + 4; + break; + case 'B': + b = va_arg(ap, uint8 *); + head_size += 8; + data_size += va_arg(ap, int); + break; + case 'b': + b = va_arg(ap, uint8 *); + head_size += va_arg(ap, int); + break; + case 'd': + n = va_arg(ap, int); + head_size += 4; + break; + case 'C': + s = va_arg(ap, char *); + head_size += str_charnum(s) + 1; + break; + } + } + va_end(ap); + + /* allocate the space, then scan the format + * again to fill in the values */ + + *blob = data_blob(NULL, head_size + data_size); + + head_ofs = 0; + data_ofs = head_size; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + s = va_arg(ap, char *); + n = str_charnum(s); + SSVAL(blob->data, head_ofs, n*2); head_ofs += 2; + SSVAL(blob->data, head_ofs, n*2); head_ofs += 2; + SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; + push_string(NULL, blob->data+data_ofs, + s, n*2, STR_UNICODE|STR_NOALIGN); + data_ofs += n*2; + break; + case 'A': + s = va_arg(ap, char *); + n = str_ascii_charnum(s); + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; + push_string(NULL, blob->data+data_ofs, + s, n, STR_ASCII|STR_NOALIGN); + data_ofs += n; + break; + case 'a': + n = va_arg(ap, int); + SSVAL(blob->data, data_ofs, n); data_ofs += 2; + s = va_arg(ap, char *); + n = str_charnum(s); + SSVAL(blob->data, data_ofs, n*2); data_ofs += 2; + if (0 < n) { + push_string(NULL, blob->data+data_ofs, s, n*2, + STR_UNICODE|STR_NOALIGN); + } + data_ofs += n*2; + break; + + case 'B': + b = va_arg(ap, uint8 *); + n = va_arg(ap, int); + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; + if (n && b) /* don't follow null pointers... */ + memcpy(blob->data+data_ofs, b, n); + data_ofs += n; + break; + case 'd': + n = va_arg(ap, int); + SIVAL(blob->data, head_ofs, n); head_ofs += 4; + break; + case 'b': + b = va_arg(ap, uint8 *); + n = va_arg(ap, int); + memcpy(blob->data + head_ofs, b, n); + head_ofs += n; + break; + case 'C': + s = va_arg(ap, char *); + n = str_charnum(s) + 1; + head_ofs += push_string(NULL, blob->data+head_ofs, s, n, + STR_ASCII|STR_TERMINATE); + break; + } + } + va_end(ap); + + return true; +} + + +/* a helpful macro to avoid running over the end of our blob */ +#define NEED_DATA(amount) \ +if ((head_ofs + amount) > blob->length) { \ + va_end(ap); \ + return False; \ +} + +/* + this is a tiny msrpc packet parser. This the the partner of msrpc_gen + + format specifiers are: + + U = unicode string (output is unix string) + A = ascii string + B = data blob + b = data blob in header + d = word (4 bytes) + C = constant ascii string + */ + +bool msrpc_parse(const DATA_BLOB *blob, + const char *format, ...) +{ + int i; + va_list ap; + char **ps, *s; + DATA_BLOB *b; + size_t head_ofs = 0; + uint16 len1, len2; + uint32 ptr; + uint32 *v; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + ps = va_arg(ap, char **); + if (len1 == 0 && len2 == 0) { + *ps = smb_xstrdup(""); + } else { + /* make sure its in the right format + * be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || + (ptr + len1 < len1) || + (ptr + len1 > blob->length)) { + va_end(ap); + return false; + } + if (len1 & 1) { + /* if odd length and unicode */ + va_end(ap); + return false; + } + if (blob->data + ptr < + (uint8 *)(unsigned long)ptr || + blob->data + ptr < blob->data) { + va_end(ap); + return false; + } + + if (0 < len1) { + char *p = NULL; + pull_string_talloc(talloc_tos(), + NULL, + 0, + &p, + blob->data + ptr, + len1, + STR_UNICODE|STR_NOALIGN); + if (p) { + (*ps) = smb_xstrdup(p); + TALLOC_FREE(p); + } else { + (*ps) = smb_xstrdup(""); + } + } else { + (*ps) = smb_xstrdup(""); + } + } + break; + case 'A': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + ps = va_arg(ap, char **); + /* make sure its in the right format - be strict */ + if (len1 == 0 && len2 == 0) { + *ps = smb_xstrdup(""); + } else { + if ((len1 != len2) || (ptr + len1 < ptr) || + (ptr + len1 < len1) || + (ptr + len1 > blob->length)) { + va_end(ap); + return false; + } + + if (blob->data + ptr < + (uint8 *)(unsigned long)ptr || + blob->data + ptr < blob->data) { + va_end(ap); + return false; + } + + if (0 < len1) { + char *p = NULL; + pull_string_talloc(talloc_tos(), + NULL, + 0, + &p, + blob->data + ptr, + len1, + STR_ASCII|STR_NOALIGN); + if (p) { + (*ps) = smb_xstrdup(p); + TALLOC_FREE(p); + } else { + (*ps) = smb_xstrdup(""); + } + } else { + (*ps) = smb_xstrdup(""); + } + } + break; + case 'B': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + b = (DATA_BLOB *)va_arg(ap, void *); + if (len1 == 0 && len2 == 0) { + *b = data_blob_null; + } else { + /* make sure its in the right format + * be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || + (ptr + len1 < len1) || + (ptr + len1 > blob->length)) { + va_end(ap); + return false; + } + + if (blob->data + ptr < + (uint8 *)(unsigned long)ptr || + blob->data + ptr < blob->data) { + va_end(ap); + return false; + } + + *b = data_blob(blob->data + ptr, len1); + } + break; + case 'b': + b = (DATA_BLOB *)va_arg(ap, void *); + len1 = va_arg(ap, unsigned); + /* make sure its in the right format - be strict */ + NEED_DATA(len1); + if (blob->data + head_ofs < (uint8 *)head_ofs || + blob->data + head_ofs < blob->data) { + va_end(ap); + return false; + } + + *b = data_blob(blob->data + head_ofs, len1); + head_ofs += len1; + break; + case 'd': + v = va_arg(ap, uint32 *); + NEED_DATA(4); + *v = IVAL(blob->data, head_ofs); head_ofs += 4; + break; + case 'C': + s = va_arg(ap, char *); + + if (blob->data + head_ofs < (uint8 *)head_ofs || + blob->data + head_ofs < blob->data) { + va_end(ap); + return false; + } + + { + char *p = NULL; + size_t ret = pull_string_talloc(talloc_tos(), + NULL, + 0, + &p, + blob->data+head_ofs, + blob->length - head_ofs, + STR_ASCII|STR_TERMINATE); + if (ret == (size_t)-1 || p == NULL) { + va_end(ap); + return false; + } + head_ofs += ret; + if (strcmp(s, p) != 0) { + TALLOC_FREE(p); + va_end(ap); + return false; + } + TALLOC_FREE(p); + } + break; + } + } + va_end(ap); + + return True; +} diff --git a/source3/libsmb/ntlmssp_sign.c b/source3/libsmb/ntlmssp_sign.c new file mode 100644 index 0000000000..8413c8066b --- /dev/null +++ b/source3/libsmb/ntlmssp_sign.c @@ -0,0 +1,468 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Andrew Bartlett 2003-2005 + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +#define CLI_SIGN "session key to client-to-server signing key magic constant" +#define CLI_SEAL "session key to client-to-server sealing key magic constant" +#define SRV_SIGN "session key to server-to-client signing key magic constant" +#define SRV_SEAL "session key to server-to-client sealing key magic constant" + +/** + * Some notes on then NTLM2 code: + * + * NTLM2 is a AEAD system. This means that the data encrypted is not + * all the data that is signed. In DCE-RPC case, the headers of the + * DCE-RPC packets are also signed. This prevents some of the + * fun-and-games one might have by changing them. + * + */ + +static void calc_ntlmv2_key(unsigned char subkey[16], + DATA_BLOB session_key, + const char *constant) +{ + struct MD5Context ctx3; + MD5Init(&ctx3); + MD5Update(&ctx3, session_key.data, session_key.length); + MD5Update(&ctx3, (const unsigned char *)constant, strlen(constant)+1); + MD5Final(subkey, &ctx3); +} + +enum ntlmssp_direction { + NTLMSSP_SEND, + NTLMSSP_RECEIVE +}; + +static NTSTATUS ntlmssp_make_packet_signature(NTLMSSP_STATE *ntlmssp_state, + const uchar *data, size_t length, + const uchar *whole_pdu, size_t pdu_length, + enum ntlmssp_direction direction, + DATA_BLOB *sig, + bool encrypt_sig) +{ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + HMACMD5Context ctx; + uchar seq_num[4]; + uchar digest[16]; + + *sig = data_blob(NULL, NTLMSSP_SIG_SIZE); + if (!sig->data) { + return NT_STATUS_NO_MEMORY; + } + + switch (direction) { + case NTLMSSP_SEND: + DEBUG(100,("ntlmssp_make_packet_signature: SEND seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->ntlm2_send_seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->ntlm2_send_seq_num); + ntlmssp_state->ntlm2_send_seq_num++; + hmac_md5_init_limK_to_64(ntlmssp_state->send_sign_key, 16, &ctx); + break; + case NTLMSSP_RECEIVE: + + DEBUG(100,("ntlmssp_make_packet_signature: RECV seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->ntlm2_recv_seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->ntlm2_recv_seq_num); + ntlmssp_state->ntlm2_recv_seq_num++; + hmac_md5_init_limK_to_64(ntlmssp_state->recv_sign_key, 16, &ctx); + break; + } + + dump_data_pw("pdu data ", whole_pdu, pdu_length); + + hmac_md5_update(seq_num, 4, &ctx); + hmac_md5_update(whole_pdu, pdu_length, &ctx); + hmac_md5_final(digest, &ctx); + + if (encrypt_sig && (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + switch (direction) { + case NTLMSSP_SEND: + smb_arc4_crypt(ntlmssp_state->send_seal_arc4_state, digest, 8); + break; + case NTLMSSP_RECEIVE: + smb_arc4_crypt(ntlmssp_state->recv_seal_arc4_state, digest, 8); + break; + } + } + + SIVAL(sig->data, 0, NTLMSSP_SIGN_VERSION); + memcpy(sig->data + 4, digest, 8); + memcpy(sig->data + 12, seq_num, 4); + + dump_data_pw("ntlmssp v2 sig ", sig->data, sig->length); + + } else { + uint32 crc; + crc = crc32_calc_buffer((const char *)data, length); + if (!msrpc_gen(sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, ntlmssp_state->ntlmv1_seq_num)) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->ntlmv1_seq_num++; + + dump_data_pw("ntlmssp hash:\n", ntlmssp_state->ntlmv1_arc4_state, + sizeof(ntlmssp_state->ntlmv1_arc4_state)); + smb_arc4_crypt(ntlmssp_state->ntlmv1_arc4_state, sig->data+4, sig->length-4); + } + return NT_STATUS_OK; +} + +NTSTATUS ntlmssp_sign_packet(NTLMSSP_STATE *ntlmssp_state, + const uchar *data, size_t length, + const uchar *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + NTSTATUS nt_status; + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + DEBUG(3, ("NTLMSSP Signing not negotiated - cannot sign packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check sign packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, True); + + return nt_status; +} + +/** + * Check the signature of an incoming packet + * @note caller *must* check that the signature is the size it expects + * + */ + +NTSTATUS ntlmssp_check_packet(NTLMSSP_STATE *ntlmssp_state, + const uchar *data, size_t length, + const uchar *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + DATA_BLOB local_sig; + NTSTATUS nt_status; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check packet signature\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (sig->length < 8) { + DEBUG(0, ("NTLMSSP packet check failed due to short signature (%lu bytes)!\n", + (unsigned long)sig->length)); + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + data, length, + whole_pdu, pdu_length, + NTLMSSP_RECEIVE, &local_sig, True); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("NTLMSSP packet check failed with %s\n", nt_errstr(nt_status))); + data_blob_free(&local_sig); + return nt_status; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (local_sig.length != sig->length || + memcmp(local_sig.data, sig->data, sig->length) != 0) { + DEBUG(5, ("BAD SIG NTLM2: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature!\n")); + data_blob_free(&local_sig); + return NT_STATUS_ACCESS_DENIED; + } + } else { + if (local_sig.length != sig->length || + memcmp(local_sig.data + 8, sig->data + 8, sig->length - 8) != 0) { + DEBUG(5, ("BAD SIG NTLM1: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM1 packet check failed due to invalid signature!\n")); + data_blob_free(&local_sig); + return NT_STATUS_ACCESS_DENIED; + } + } + dump_data_pw("checked ntlmssp signature\n", sig->data, sig->length); + DEBUG(10,("ntlmssp_check_packet: NTLMSSP signature OK !\n")); + + data_blob_free(&local_sig); + return NT_STATUS_OK; +} + +/** + * Seal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_seal_packet(NTLMSSP_STATE *ntlmssp_state, + uchar *data, size_t length, + uchar *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL)) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot seal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_seal_data: seal\n")); + dump_data_pw("ntlmssp clear data\n", data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + /* The order of these two operations matters - we must first seal the packet, + then seal the sequence number - this is becouse the send_seal_hash is not + constant, but is is rather updated with each iteration */ + NTSTATUS nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, False); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + smb_arc4_crypt(ntlmssp_state->send_seal_arc4_state, data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + smb_arc4_crypt(ntlmssp_state->send_seal_arc4_state, sig->data+4, 8); + } + } else { + uint32 crc; + crc = crc32_calc_buffer((const char *)data, length); + if (!msrpc_gen(sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, ntlmssp_state->ntlmv1_seq_num)) { + return NT_STATUS_NO_MEMORY; + } + + /* The order of these two operations matters - we must first seal the packet, + then seal the sequence number - this is becouse the ntlmv1_arc4_state is not + constant, but is is rather updated with each iteration */ + + dump_data_pw("ntlmv1 arc4 state:\n", ntlmssp_state->ntlmv1_arc4_state, + sizeof(ntlmssp_state->ntlmv1_arc4_state)); + smb_arc4_crypt(ntlmssp_state->ntlmv1_arc4_state, data, length); + + dump_data_pw("ntlmv1 arc4 state:\n", ntlmssp_state->ntlmv1_arc4_state, + sizeof(ntlmssp_state->ntlmv1_arc4_state)); + + smb_arc4_crypt(ntlmssp_state->ntlmv1_arc4_state, sig->data+4, sig->length-4); + + ntlmssp_state->ntlmv1_seq_num++; + } + dump_data_pw("ntlmssp signature\n", sig->data, sig->length); + dump_data_pw("ntlmssp sealed data\n", data, length); + + return NT_STATUS_OK; +} + +/** + * Unseal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_unseal_packet(NTLMSSP_STATE *ntlmssp_state, + uchar *data, size_t length, + uchar *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot unseal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_unseal_packet: seal\n")); + dump_data_pw("ntlmssp sealed data\n", data, length); + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + /* First unseal the data. */ + smb_arc4_crypt(ntlmssp_state->recv_seal_arc4_state, data, length); + dump_data_pw("ntlmv2 clear data\n", data, length); + } else { + smb_arc4_crypt(ntlmssp_state->ntlmv1_arc4_state, data, length); + dump_data_pw("ntlmv1 clear data\n", data, length); + } + return ntlmssp_check_packet(ntlmssp_state, data, length, whole_pdu, pdu_length, sig); +} + +/** + Initialise the state for NTLMSSP signing. +*/ +NTSTATUS ntlmssp_sign_init(NTLMSSP_STATE *ntlmssp_state) +{ + unsigned char p24[24]; + TALLOC_CTX *mem_ctx; + ZERO_STRUCT(p24); + + mem_ctx = talloc_init("weak_keys"); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(3, ("NTLMSSP Sign/Seal - Initialising with flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (ntlmssp_state->session_key.length < 8) { + TALLOC_FREE(mem_ctx); + DEBUG(3, ("NO session key, cannot intialise signing\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + DATA_BLOB weak_session_key = ntlmssp_state->session_key; + const char *send_sign_const; + const char *send_seal_const; + const char *recv_sign_const; + const char *recv_seal_const; + + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + send_sign_const = CLI_SIGN; + send_seal_const = CLI_SEAL; + recv_sign_const = SRV_SIGN; + recv_seal_const = SRV_SEAL; + break; + case NTLMSSP_SERVER: + send_sign_const = SRV_SIGN; + send_seal_const = SRV_SEAL; + recv_sign_const = CLI_SIGN; + recv_seal_const = CLI_SEAL; + break; + default: + TALLOC_FREE(mem_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + /** + Weaken NTLMSSP keys to cope with down-level clients, servers and export restrictions. + We probably should have some parameters to control this, once we get NTLM2 working. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + ; + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key.length = 7; + } else { /* forty bits */ + weak_session_key.length = 5; + } + + dump_data_pw("NTLMSSP weakend master key:\n", + weak_session_key.data, + weak_session_key.length); + + /* SEND: sign key */ + calc_ntlmv2_key(ntlmssp_state->send_sign_key, + ntlmssp_state->session_key, send_sign_const); + dump_data_pw("NTLMSSP send sign key:\n", + ntlmssp_state->send_sign_key, 16); + + /* SEND: seal ARCFOUR pad */ + calc_ntlmv2_key(ntlmssp_state->send_seal_key, + weak_session_key, send_seal_const); + dump_data_pw("NTLMSSP send seal key:\n", + ntlmssp_state->send_seal_key, 16); + + smb_arc4_init(ntlmssp_state->send_seal_arc4_state, + ntlmssp_state->send_seal_key, 16); + + dump_data_pw("NTLMSSP send seal arc4 state:\n", + ntlmssp_state->send_seal_arc4_state, + sizeof(ntlmssp_state->send_seal_arc4_state)); + + /* RECV: sign key */ + calc_ntlmv2_key(ntlmssp_state->recv_sign_key, + ntlmssp_state->session_key, recv_sign_const); + dump_data_pw("NTLMSSP recv send sign key:\n", + ntlmssp_state->recv_sign_key, 16); + + /* RECV: seal ARCFOUR pad */ + calc_ntlmv2_key(ntlmssp_state->recv_seal_key, + weak_session_key, recv_seal_const); + + dump_data_pw("NTLMSSP recv seal key:\n", + ntlmssp_state->recv_seal_key, 16); + + smb_arc4_init(ntlmssp_state->recv_seal_arc4_state, + ntlmssp_state->recv_seal_key, 16); + + dump_data_pw("NTLMSSP recv seal arc4 state:\n", + ntlmssp_state->recv_seal_arc4_state, + sizeof(ntlmssp_state->recv_seal_arc4_state)); + + ntlmssp_state->ntlm2_send_seq_num = 0; + ntlmssp_state->ntlm2_recv_seq_num = 0; + + + } else { +#if 0 + /* Hmmm. Shouldn't we also weaken keys for ntlmv1 ? JRA. */ + + DATA_BLOB weak_session_key = ntlmssp_state->session_key; + /** + Weaken NTLMSSP keys to cope with down-level clients, servers and export restrictions. + We probably should have some parameters to control this, once we get NTLM2 working. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + ; + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key.length = 6; + } else { /* forty bits */ + weak_session_key.length = 5; + } + dump_data_pw("NTLMSSP weakend master key:\n", + weak_session_key.data, + weak_session_key.length); +#endif + + DATA_BLOB weak_session_key = ntlmssp_weaken_keys(ntlmssp_state, mem_ctx); + + DEBUG(5, ("NTLMSSP Sign/Seal - using NTLM1\n")); + + smb_arc4_init(ntlmssp_state->ntlmv1_arc4_state, + weak_session_key.data, weak_session_key.length); + + dump_data_pw("NTLMv1 arc4 state:\n", ntlmssp_state->ntlmv1_arc4_state, + sizeof(ntlmssp_state->ntlmv1_arc4_state)); + + ntlmssp_state->ntlmv1_seq_num = 0; + } + + TALLOC_FREE(mem_ctx); + return NT_STATUS_OK; +} diff --git a/source3/libsmb/passchange.c b/source3/libsmb/passchange.c new file mode 100644 index 0000000000..4c76234e0c --- /dev/null +++ b/source3/libsmb/passchange.c @@ -0,0 +1,257 @@ +/* + Unix SMB/CIFS implementation. + SMB client password change routine + Copyright (C) Andrew Tridgell 1994-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/************************************************************* + Change a password on a remote machine using IPC calls. +*************************************************************/ + +NTSTATUS remote_password_change(const char *remote_machine, const char *user_name, + const char *old_passwd, const char *new_passwd, + char **err_str) +{ + struct nmb_name calling, called; + struct cli_state *cli; + struct rpc_pipe_client *pipe_hnd; + struct sockaddr_storage ss; + + NTSTATUS result; + bool pass_must_change = False; + + *err_str = NULL; + + if(!resolve_name( remote_machine, &ss, 0x20)) { + asprintf(err_str, "Unable to find an IP address for machine " + "%s.\n", remote_machine); + return NT_STATUS_UNSUCCESSFUL; + } + + cli = cli_initialise(); + if (!cli) { + return NT_STATUS_NO_MEMORY; + } + + result = cli_connect(cli, remote_machine, &ss); + if (!NT_STATUS_IS_OK(result)) { + asprintf(err_str, "Unable to connect to SMB server on " + "machine %s. Error was : %s.\n", + remote_machine, nt_errstr(result)); + cli_shutdown(cli); + return result; + } + + make_nmb_name(&calling, global_myname() , 0x0); + make_nmb_name(&called , remote_machine, 0x20); + + if (!cli_session_request(cli, &calling, &called)) { + asprintf(err_str, "machine %s rejected the session setup. " + "Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } + + cli->protocol = PROTOCOL_NT1; + + if (!cli_negprot(cli)) { + asprintf(err_str, "machine %s rejected the negotiate " + "protocol. Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } + + /* Given things like SMB signing, restrict anonymous and the like, + try an authenticated connection first */ + result = cli_session_setup(cli, user_name, + old_passwd, strlen(old_passwd)+1, + old_passwd, strlen(old_passwd)+1, ""); + + if (!NT_STATUS_IS_OK(result)) { + + /* Password must change or Password expired are the only valid + * error conditions here from where we can proceed, the rest like + * account locked out or logon failure will lead to errors later + * anyway */ + + if (!NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) && + !NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED)) { + asprintf(err_str, "Could not connect to machine %s: " + "%s\n", remote_machine, cli_errstr(cli)); + cli_shutdown(cli); + return result; + } + + pass_must_change = True; + + /* + * We should connect as the anonymous user here, in case + * the server has "must change password" checked... + * Thanks to <Nicholas.S.Jenkins@cdc.com> for this fix. + */ + + result = cli_session_setup(cli, "", "", 0, "", 0, ""); + + if (!NT_STATUS_IS_OK(result)) { + asprintf(err_str, "machine %s rejected the session " + "setup. Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + cli_shutdown(cli); + return result; + } + + cli_init_creds(cli, "", "", NULL); + } else { + cli_init_creds(cli, user_name, "", old_passwd); + } + + if (!cli_send_tconX(cli, "IPC$", "IPC", "", 1)) { + asprintf(err_str, "machine %s rejected the tconX on the IPC$ " + "share. Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } + + /* Try not to give the password away too easily */ + + if (!pass_must_change) { + result = cli_rpc_pipe_open_ntlmssp(cli, + &ndr_table_samr.syntax_id, + PIPE_AUTH_LEVEL_PRIVACY, + "", /* what domain... ? */ + user_name, + old_passwd, + &pipe_hnd); + } else { + /* + * If the user password must be changed the ntlmssp bind will + * fail the same way as the session setup above did. The + * difference ist that with a pipe bind we don't get a good + * error message, the result will be that the rpc call below + * will just fail. So we do it anonymously, there's no other + * way. + */ + result = cli_rpc_pipe_open_noauth( + cli, &ndr_table_samr.syntax_id, &pipe_hnd); + } + + if (!NT_STATUS_IS_OK(result)) { + if (lp_client_lanman_auth()) { + /* Use the old RAP method. */ + if (!cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) { + asprintf(err_str, "machine %s rejected the " + "password change: Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } + } else { + asprintf(err_str, "SAMR connection to machine %s " + "failed. Error was %s, but LANMAN password " + "changed are disabled\n", + nt_errstr(result), remote_machine); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } + } + + result = rpccli_samr_chgpasswd_user2(pipe_hnd, talloc_tos(), + user_name, new_passwd, old_passwd); + if (NT_STATUS_IS_OK(result)) { + /* Great - it all worked! */ + cli_shutdown(cli); + return NT_STATUS_OK; + + } else if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) + || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) { + /* it failed, but for reasons such as wrong password, too short etc ... */ + + asprintf(err_str, "machine %s rejected the password change: " + "Error was : %s.\n", + remote_machine, get_friendly_nt_error_msg(result)); + cli_shutdown(cli); + return result; + } + + /* OK, that failed, so try again... */ + TALLOC_FREE(pipe_hnd); + + /* Try anonymous NTLMSSP... */ + cli_init_creds(cli, "", "", NULL); + + result = NT_STATUS_UNSUCCESSFUL; + + /* OK, this is ugly, but... try an anonymous pipe. */ + result = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr.syntax_id, + &pipe_hnd); + + if ( NT_STATUS_IS_OK(result) && + (NT_STATUS_IS_OK(result = rpccli_samr_chgpasswd_user2( + pipe_hnd, talloc_tos(), user_name, + new_passwd, old_passwd)))) { + /* Great - it all worked! */ + cli_shutdown(cli); + return NT_STATUS_OK; + } else { + if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) + || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) { + /* it failed, but again it was due to things like new password too short */ + + asprintf(err_str, "machine %s rejected the " + "(anonymous) password change: Error was : " + "%s.\n", remote_machine, + get_friendly_nt_error_msg(result)); + cli_shutdown(cli); + return result; + } + + /* We have failed to change the user's password, and we think the server + just might not support SAMR password changes, so fall back */ + + if (lp_client_lanman_auth()) { + /* Use the old RAP method. */ + if (cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) { + /* SAMR failed, but the old LanMan protocol worked! */ + + cli_shutdown(cli); + return NT_STATUS_OK; + } + asprintf(err_str, "machine %s rejected the password " + "change: Error was : %s.\n", + remote_machine, cli_errstr(cli) ); + result = cli_nt_error(cli); + cli_shutdown(cli); + return result; + } else { + asprintf(err_str, "SAMR connection to machine %s " + "failed. Error was %s, but LANMAN password " + "changed are disabled\n", + nt_errstr(result), remote_machine); + cli_shutdown(cli); + return NT_STATUS_UNSUCCESSFUL; + } + } +} diff --git a/source3/libsmb/pwd_cache.c b/source3/libsmb/pwd_cache.c new file mode 100644 index 0000000000..071e729e8c --- /dev/null +++ b/source3/libsmb/pwd_cache.c @@ -0,0 +1,61 @@ +/* + Unix SMB/CIFS implementation. + Password cacheing. obfuscation is planned + Copyright (C) Luke Kenneth Casson Leighton 1996-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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/**************************************************************************** + Initialises a password structure. +****************************************************************************/ + +static void pwd_init(struct pwd_info *pwd) +{ + memset((char *)pwd->password , '\0', sizeof(pwd->password )); + + pwd->null_pwd = True; /* safest option... */ +} + +/**************************************************************************** + Stores a cleartext password. +****************************************************************************/ + +void pwd_set_cleartext(struct pwd_info *pwd, const char *clr) +{ + pwd_init(pwd); + if (clr) { + fstrcpy(pwd->password, clr); + pwd->null_pwd = False; + } else { + pwd->null_pwd = True; + } + + pwd->cleartext = True; +} + +/**************************************************************************** + Gets a cleartext password. +****************************************************************************/ + +void pwd_get_cleartext(struct pwd_info *pwd, fstring clr) +{ + if (pwd->cleartext) + fstrcpy(clr, pwd->password); + else + clr[0] = 0; + +} diff --git a/source3/libsmb/samlogon_cache.c b/source3/libsmb/samlogon_cache.c new file mode 100644 index 0000000000..4abe5bb6de --- /dev/null +++ b/source3/libsmb/samlogon_cache.c @@ -0,0 +1,258 @@ +/* + Unix SMB/CIFS implementation. + Net_sam_logon info3 helpers + Copyright (C) Alexander Bokovoy 2002. + Copyright (C) Andrew Bartlett 2002. + Copyright (C) Gerald Carter 2003. + Copyright (C) Tim Potter 2003. + Copyright (C) Guenther Deschner 2008. + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#define NETSAMLOGON_TDB "netsamlogon_cache.tdb" + +static TDB_CONTEXT *netsamlogon_tdb = NULL; + +/*********************************************************************** + open the tdb + ***********************************************************************/ + +bool netsamlogon_cache_init(void) +{ + if (!netsamlogon_tdb) { + netsamlogon_tdb = tdb_open_log(lock_path(NETSAMLOGON_TDB), 0, + TDB_DEFAULT, O_RDWR | O_CREAT, 0600); + } + + return (netsamlogon_tdb != NULL); +} + + +/*********************************************************************** + Shutdown samlogon_cache database +***********************************************************************/ + +bool netsamlogon_cache_shutdown(void) +{ + if (netsamlogon_tdb) { + return (tdb_close(netsamlogon_tdb) == 0); + } + + return true; +} + +/*********************************************************************** + Clear cache getpwnam and getgroups entries from the winbindd cache +***********************************************************************/ + +void netsamlogon_clear_cached_user(struct netr_SamInfo3 *info3) +{ + DOM_SID user_sid; + fstring keystr, tmp; + + if (!info3) { + return; + } + + if (!netsamlogon_cache_init()) { + DEBUG(0,("netsamlogon_clear_cached_user: cannot open " + "%s for write!\n", + NETSAMLOGON_TDB)); + return; + } + sid_copy(&user_sid, info3->base.domain_sid); + sid_append_rid(&user_sid, info3->base.rid); + + /* Prepare key as DOMAIN-SID/USER-RID string */ + slprintf(keystr, sizeof(keystr), "%s", sid_to_fstring(tmp, &user_sid)); + + DEBUG(10,("netsamlogon_clear_cached_user: SID [%s]\n", keystr)); + + tdb_delete_bystring(netsamlogon_tdb, keystr); +} + +/*********************************************************************** + Store a netr_SamInfo3 structure in a tdb for later user + username should be in UTF-8 format +***********************************************************************/ + +bool netsamlogon_cache_store(const char *username, struct netr_SamInfo3 *info3) +{ + TDB_DATA data; + fstring keystr, tmp; + bool result = false; + DOM_SID user_sid; + time_t t = time(NULL); + TALLOC_CTX *mem_ctx; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct netsamlogoncache_entry r; + + if (!info3) { + return false; + } + + if (!netsamlogon_cache_init()) { + DEBUG(0,("netsamlogon_cache_store: cannot open %s for write!\n", + NETSAMLOGON_TDB)); + return false; + } + + sid_copy(&user_sid, info3->base.domain_sid); + sid_append_rid(&user_sid, info3->base.rid); + + /* Prepare key as DOMAIN-SID/USER-RID string */ + slprintf(keystr, sizeof(keystr), "%s", sid_to_fstring(tmp, &user_sid)); + + DEBUG(10,("netsamlogon_cache_store: SID [%s]\n", keystr)); + + /* Prepare data */ + + if (!(mem_ctx = TALLOC_P( NULL, int))) { + DEBUG(0,("netsamlogon_cache_store: talloc() failed!\n")); + return false; + } + + /* only Samba fills in the username, not sure why NT doesn't */ + /* so we fill it in since winbindd_getpwnam() makes use of it */ + + if (!info3->base.account_name.string) { + info3->base.account_name.string = talloc_strdup(info3, username); + } + + r.timestamp = t; + r.info3 = *info3; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netsamlogoncache_entry, &r); + } + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &r, + (ndr_push_flags_fn_t)ndr_push_netsamlogoncache_entry); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0,("netsamlogon_cache_store: failed to push entry to cache\n")); + TALLOC_FREE(mem_ctx); + return false; + } + + data.dsize = blob.length; + data.dptr = blob.data; + + if (tdb_store_bystring(netsamlogon_tdb, keystr, data, TDB_REPLACE) != -1) { + result = true; + } + + TALLOC_FREE(mem_ctx); + + return result; +} + +/*********************************************************************** + Retrieves a netr_SamInfo3 structure from a tdb. Caller must + free the user_info struct (malloc()'d memory) +***********************************************************************/ + +struct netr_SamInfo3 *netsamlogon_cache_get(TALLOC_CTX *mem_ctx, const DOM_SID *user_sid) +{ + struct netr_SamInfo3 *info3 = NULL; + TDB_DATA data; + fstring keystr, tmp; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + struct netsamlogoncache_entry r; + + if (!netsamlogon_cache_init()) { + DEBUG(0,("netsamlogon_cache_get: cannot open %s for write!\n", + NETSAMLOGON_TDB)); + return false; + } + + /* Prepare key as DOMAIN-SID/USER-RID string */ + slprintf(keystr, sizeof(keystr), "%s", sid_to_fstring(tmp, user_sid)); + DEBUG(10,("netsamlogon_cache_get: SID [%s]\n", keystr)); + data = tdb_fetch_bystring( netsamlogon_tdb, keystr ); + + if (!data.dptr) { + return NULL; + } + + info3 = TALLOC_ZERO_P(mem_ctx, struct netr_SamInfo3); + if (!info3) { + goto done; + } + + blob = data_blob_const(data.dptr, data.dsize); + + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r, + (ndr_pull_flags_fn_t)ndr_pull_netsamlogoncache_entry); + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netsamlogoncache_entry, &r); + } + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0,("netsamlogon_cache_get: failed to pull entry from cache\n")); + tdb_delete(netsamlogon_tdb, data); + TALLOC_FREE(info3); + goto done; + } + + info3 = (struct netr_SamInfo3 *)talloc_memdup(mem_ctx, &r.info3, + sizeof(r.info3)); + + done: + SAFE_FREE(data.dptr); + + return info3; + +#if 0 /* The netsamlogon cache needs to hang around. Something about + this feels wrong, but it is the only way we can get all of the + groups. The old universal groups cache didn't expire either. + --jerry */ + { + time_t now = time(NULL); + uint32 time_diff; + + /* is the entry expired? */ + time_diff = now - t; + + if ( (time_diff < 0 ) || (time_diff > lp_winbind_cache_time()) ) { + DEBUG(10,("netsamlogon_cache_get: cache entry expired \n")); + tdb_delete( netsamlogon_tdb, key ); + TALLOC_FREE( user ); + } + } +#endif +} + +bool netsamlogon_cache_have(const DOM_SID *user_sid) +{ + TALLOC_CTX *mem_ctx = talloc_init("netsamlogon_cache_have"); + struct netr_SamInfo3 *info3 = NULL; + bool result; + + if (!mem_ctx) + return False; + + info3 = netsamlogon_cache_get(mem_ctx, user_sid); + + result = (info3 != NULL); + + talloc_destroy(mem_ctx); + + return result; +} diff --git a/source3/libsmb/smb_seal.c b/source3/libsmb/smb_seal.c new file mode 100644 index 0000000000..a81ae9afd5 --- /dev/null +++ b/source3/libsmb/smb_seal.c @@ -0,0 +1,497 @@ +/* + Unix SMB/CIFS implementation. + SMB Transport encryption (sealing) code. + Copyright (C) Jeremy Allison 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/****************************************************************************** + Pull out the encryption context for this packet. 0 means global context. +******************************************************************************/ + +NTSTATUS get_enc_ctx_num(const uint8_t *buf, uint16 *p_enc_ctx_num) +{ + if (smb_len(buf) < 8) { + return NT_STATUS_INVALID_BUFFER_SIZE; + } + + if (buf[4] == 0xFF) { + if (buf[5] == 'S' && buf [6] == 'M' && buf[7] == 'B') { + /* Not an encrypted buffer. */ + return NT_STATUS_NOT_FOUND; + } + if (buf[5] == 'E') { + *p_enc_ctx_num = SVAL(buf,6); + return NT_STATUS_OK; + } + } + return NT_STATUS_INVALID_NETWORK_RESPONSE; +} + +/****************************************************************************** + Generic code for client and server. + Is encryption turned on ? +******************************************************************************/ + +bool common_encryption_on(struct smb_trans_enc_state *es) +{ + return ((es != NULL) && es->enc_on); +} + +/****************************************************************************** + Generic code for client and server. + NTLM decrypt an incoming buffer. + Abartlett tells me that SSPI puts the signature first before the encrypted + output, so cope with the same for compatibility. +******************************************************************************/ + +NTSTATUS common_ntlm_decrypt_buffer(NTLMSSP_STATE *ntlmssp_state, char *buf) +{ + NTSTATUS status; + size_t buf_len = smb_len(buf) + 4; /* Don't forget the 4 length bytes. */ + size_t data_len; + char *inbuf; + DATA_BLOB sig; + + if (buf_len < 8 + NTLMSSP_SIG_SIZE) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + inbuf = (char *)smb_xmemdup(buf, buf_len); + + /* Adjust for the signature. */ + data_len = buf_len - 8 - NTLMSSP_SIG_SIZE; + + /* Point at the signature. */ + sig = data_blob_const(inbuf+8, NTLMSSP_SIG_SIZE); + + status = ntlmssp_unseal_packet(ntlmssp_state, + (unsigned char *)inbuf + 8 + NTLMSSP_SIG_SIZE, /* 4 byte len + 0xFF 'E' <enc> <ctx> */ + data_len, + (unsigned char *)inbuf + 8 + NTLMSSP_SIG_SIZE, + data_len, + &sig); + + if (!NT_STATUS_IS_OK(status)) { + SAFE_FREE(inbuf); + return status; + } + + memcpy(buf + 8, inbuf + 8 + NTLMSSP_SIG_SIZE, data_len); + + /* Reset the length and overwrite the header. */ + smb_setlen(buf,data_len + 4); + + SAFE_FREE(inbuf); + return NT_STATUS_OK; +} + +/****************************************************************************** + Generic code for client and server. + NTLM encrypt an outgoing buffer. Return the encrypted pointer in ppbuf_out. + Abartlett tells me that SSPI puts the signature first before the encrypted + output, so do the same for compatibility. +******************************************************************************/ + +NTSTATUS common_ntlm_encrypt_buffer(NTLMSSP_STATE *ntlmssp_state, + uint16 enc_ctx_num, + char *buf, + char **ppbuf_out) +{ + NTSTATUS status; + char *buf_out; + size_t data_len = smb_len(buf) - 4; /* Ignore the 0xFF SMB bytes. */ + DATA_BLOB sig; + + *ppbuf_out = NULL; + + if (data_len == 0) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + /* + * We know smb_len can't return a value > 128k, so no int overflow + * check needed. + */ + + buf_out = SMB_XMALLOC_ARRAY(char, 8 + NTLMSSP_SIG_SIZE + data_len); + + /* Copy the data from the original buffer. */ + + memcpy(buf_out + 8 + NTLMSSP_SIG_SIZE, buf + 8, data_len); + + smb_set_enclen(buf_out, smb_len(buf) + NTLMSSP_SIG_SIZE, enc_ctx_num); + + sig = data_blob(NULL, NTLMSSP_SIG_SIZE); + + status = ntlmssp_seal_packet(ntlmssp_state, + (unsigned char *)buf_out + 8 + NTLMSSP_SIG_SIZE, /* 4 byte len + 0xFF 'S' <enc> <ctx> */ + data_len, + (unsigned char *)buf_out + 8 + NTLMSSP_SIG_SIZE, + data_len, + &sig); + + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&sig); + SAFE_FREE(buf_out); + return status; + } + + /* First 16 data bytes are signature for SSPI compatibility. */ + memcpy(buf_out + 8, sig.data, NTLMSSP_SIG_SIZE); + *ppbuf_out = buf_out; + return NT_STATUS_OK; +} + +/****************************************************************************** + Generic code for client and server. + gss-api decrypt an incoming buffer. We insist that the size of the + unwrapped buffer must be smaller or identical to the incoming buffer. +******************************************************************************/ + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) +static NTSTATUS common_gss_decrypt_buffer(struct smb_tran_enc_state_gss *gss_state, char *buf) +{ + gss_ctx_id_t gss_ctx = gss_state->gss_ctx; + OM_uint32 ret = 0; + OM_uint32 minor = 0; + int flags_got = 0; + gss_buffer_desc in_buf, out_buf; + size_t buf_len = smb_len(buf) + 4; /* Don't forget the 4 length bytes. */ + + if (buf_len < 8) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + in_buf.value = buf + 8; + in_buf.length = buf_len - 8; + + ret = gss_unwrap(&minor, + gss_ctx, + &in_buf, + &out_buf, + &flags_got, /* did we get sign+seal ? */ + (gss_qop_t *) NULL); + + if (ret != GSS_S_COMPLETE) { + ADS_STATUS adss = ADS_ERROR_GSS(ret, minor); + DEBUG(0,("common_gss_encrypt_buffer: gss_unwrap failed. Error %s\n", + ads_errstr(adss) )); + return map_nt_error_from_gss(ret, minor); + } + + if (out_buf.length > in_buf.length) { + DEBUG(0,("common_gss_encrypt_buffer: gss_unwrap size (%u) too large (%u) !\n", + (unsigned int)out_buf.length, + (unsigned int)in_buf.length )); + gss_release_buffer(&minor, &out_buf); + return NT_STATUS_INVALID_PARAMETER; + } + + memcpy(buf + 8, out_buf.value, out_buf.length); + /* Reset the length and overwrite the header. */ + smb_setlen(buf, out_buf.length + 4); + + gss_release_buffer(&minor, &out_buf); + return NT_STATUS_OK; +} + +/****************************************************************************** + Generic code for client and server. + gss-api encrypt an outgoing buffer. Return the alloced encrypted pointer in buf_out. +******************************************************************************/ + +static NTSTATUS common_gss_encrypt_buffer(struct smb_tran_enc_state_gss *gss_state, + uint16 enc_ctx_num, + char *buf, + char **ppbuf_out) +{ + gss_ctx_id_t gss_ctx = gss_state->gss_ctx; + OM_uint32 ret = 0; + OM_uint32 minor = 0; + int flags_got = 0; + gss_buffer_desc in_buf, out_buf; + size_t buf_len = smb_len(buf) + 4; /* Don't forget the 4 length bytes. */ + + *ppbuf_out = NULL; + + if (buf_len < 8) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + in_buf.value = buf + 8; + in_buf.length = buf_len - 8; + + ret = gss_wrap(&minor, + gss_ctx, + true, /* we want sign+seal. */ + GSS_C_QOP_DEFAULT, + &in_buf, + &flags_got, /* did we get sign+seal ? */ + &out_buf); + + if (ret != GSS_S_COMPLETE) { + ADS_STATUS adss = ADS_ERROR_GSS(ret, minor); + DEBUG(0,("common_gss_encrypt_buffer: gss_wrap failed. Error %s\n", + ads_errstr(adss) )); + return map_nt_error_from_gss(ret, minor); + } + + if (!flags_got) { + /* Sign+seal not supported. */ + gss_release_buffer(&minor, &out_buf); + return NT_STATUS_NOT_SUPPORTED; + } + + /* Ya see - this is why I *hate* gss-api. I don't + * want to have to malloc another buffer of the + * same size + 8 bytes just to get a continuous + * header + buffer, but gss won't let me pass in + * a pre-allocated buffer. Bastards (and you know + * who you are....). I might fix this by + * going to "encrypt_and_send" passing in a file + * descriptor and doing scatter-gather write with + * TCP cork on Linux. But I shouldn't have to + * bother :-*(. JRA. + */ + + *ppbuf_out = (char *)SMB_MALLOC(out_buf.length + 8); /* We know this can't wrap. */ + if (!*ppbuf_out) { + gss_release_buffer(&minor, &out_buf); + return NT_STATUS_NO_MEMORY; + } + + memcpy(*ppbuf_out+8, out_buf.value, out_buf.length); + smb_set_enclen(*ppbuf_out, out_buf.length + 4, enc_ctx_num); + + gss_release_buffer(&minor, &out_buf); + return NT_STATUS_OK; +} +#endif + +/****************************************************************************** + Generic code for client and server. + Encrypt an outgoing buffer. Return the alloced encrypted pointer in buf_out. +******************************************************************************/ + +NTSTATUS common_encrypt_buffer(struct smb_trans_enc_state *es, char *buffer, char **buf_out) +{ + if (!common_encryption_on(es)) { + /* Not encrypting. */ + *buf_out = buffer; + return NT_STATUS_OK; + } + + switch (es->smb_enc_type) { + case SMB_TRANS_ENC_NTLM: + return common_ntlm_encrypt_buffer(es->s.ntlmssp_state, es->enc_ctx_num, buffer, buf_out); +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + case SMB_TRANS_ENC_GSS: + return common_gss_encrypt_buffer(es->s.gss_state, es->enc_ctx_num, buffer, buf_out); +#endif + default: + return NT_STATUS_NOT_SUPPORTED; + } +} + +/****************************************************************************** + Generic code for client and server. + Decrypt an incoming SMB buffer. Replaces the data within it. + New data must be less than or equal to the current length. +******************************************************************************/ + +NTSTATUS common_decrypt_buffer(struct smb_trans_enc_state *es, char *buf) +{ + if (!common_encryption_on(es)) { + /* Not decrypting. */ + return NT_STATUS_OK; + } + + switch (es->smb_enc_type) { + case SMB_TRANS_ENC_NTLM: + return common_ntlm_decrypt_buffer(es->s.ntlmssp_state, buf); +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + case SMB_TRANS_ENC_GSS: + return common_gss_decrypt_buffer(es->s.gss_state, buf); +#endif + default: + return NT_STATUS_NOT_SUPPORTED; + } +} + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) +/****************************************************************************** + Shutdown a gss encryption state. +******************************************************************************/ + +static void common_free_gss_state(struct smb_tran_enc_state_gss **pp_gss_state) +{ + OM_uint32 minor = 0; + struct smb_tran_enc_state_gss *gss_state = *pp_gss_state; + + if (gss_state->creds != GSS_C_NO_CREDENTIAL) { + gss_release_cred(&minor, &gss_state->creds); + } + if (gss_state->gss_ctx != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&minor, &gss_state->gss_ctx, NULL); + } + SAFE_FREE(*pp_gss_state); +} +#endif + +/****************************************************************************** + Shutdown an encryption state. +******************************************************************************/ + +void common_free_encryption_state(struct smb_trans_enc_state **pp_es) +{ + struct smb_trans_enc_state *es = *pp_es; + + if (es == NULL) { + return; + } + + if (es->smb_enc_type == SMB_TRANS_ENC_NTLM) { + if (es->s.ntlmssp_state) { + ntlmssp_end(&es->s.ntlmssp_state); + } + } +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + if (es->smb_enc_type == SMB_TRANS_ENC_GSS) { + /* Free the gss context handle. */ + if (es->s.gss_state) { + common_free_gss_state(&es->s.gss_state); + } + } +#endif + SAFE_FREE(es); + *pp_es = NULL; +} + +/****************************************************************************** + Free an encryption-allocated buffer. +******************************************************************************/ + +void common_free_enc_buffer(struct smb_trans_enc_state *es, char *buf) +{ + if (!common_encryption_on(es)) { + return; + } + + if (es->smb_enc_type == SMB_TRANS_ENC_NTLM) { + SAFE_FREE(buf); + return; + } + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5) + if (es->smb_enc_type == SMB_TRANS_ENC_GSS) { + OM_uint32 min; + gss_buffer_desc rel_buf; + rel_buf.value = buf; + rel_buf.length = smb_len(buf) + 4; + gss_release_buffer(&min, &rel_buf); + } +#endif +} + +/****************************************************************************** + Client side encryption. +******************************************************************************/ + +/****************************************************************************** + Is client encryption on ? +******************************************************************************/ + +bool cli_encryption_on(struct cli_state *cli) +{ + /* If we supported multiple encrytion contexts + * here we'd look up based on tid. + */ + return common_encryption_on(cli->trans_enc_state); +} + +/****************************************************************************** + Shutdown a client encryption state. +******************************************************************************/ + +void cli_free_encryption_context(struct cli_state *cli) +{ + common_free_encryption_state(&cli->trans_enc_state); +} + +/****************************************************************************** + Free an encryption-allocated buffer. +******************************************************************************/ + +void cli_free_enc_buffer(struct cli_state *cli, char *buf) +{ + /* We know this is an smb buffer, and we + * didn't malloc, only copy, for a keepalive, + * so ignore non-session messages. */ + + if(CVAL(buf,0)) { + return; + } + + /* If we supported multiple encrytion contexts + * here we'd look up based on tid. + */ + common_free_enc_buffer(cli->trans_enc_state, buf); +} + +/****************************************************************************** + Decrypt an incoming buffer. +******************************************************************************/ + +NTSTATUS cli_decrypt_message(struct cli_state *cli) +{ + NTSTATUS status; + uint16 enc_ctx_num; + + /* Ignore non-session messages. */ + if(CVAL(cli->inbuf,0)) { + return NT_STATUS_OK; + } + + status = get_enc_ctx_num((const uint8_t *)cli->inbuf, &enc_ctx_num); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (enc_ctx_num != cli->trans_enc_state->enc_ctx_num) { + return NT_STATUS_INVALID_HANDLE; + } + + return common_decrypt_buffer(cli->trans_enc_state, cli->inbuf); +} + +/****************************************************************************** + Encrypt an outgoing buffer. Return the encrypted pointer in buf_out. +******************************************************************************/ + +NTSTATUS cli_encrypt_message(struct cli_state *cli, char *buf, char **buf_out) +{ + /* Ignore non-session messages. */ + if (CVAL(buf,0)) { + return NT_STATUS_OK; + } + + /* If we supported multiple encrytion contexts + * here we'd look up based on tid. + */ + return common_encrypt_buffer(cli->trans_enc_state, buf, buf_out); +} diff --git a/source3/libsmb/smb_share_modes.c b/source3/libsmb/smb_share_modes.c new file mode 100644 index 0000000000..16b3b10925 --- /dev/null +++ b/source3/libsmb/smb_share_modes.c @@ -0,0 +1,520 @@ +/* + Samba share mode database library external interface library. + Used by non-Samba products needing access to the Samba share mode db. + + Copyright (C) Jeremy Allison 2005 - 2006 + + sharemodes_procid functions (C) Copyright (C) Volker Lendecke 2005 + + ** NOTE! The following LGPL license applies to this module only. + ** This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "smb_share_modes.h" + +/* Database context handle. */ +struct smbdb_ctx { + TDB_CONTEXT *smb_tdb; +}; + +/* Remove the paranoid malloc checker. */ +#ifdef malloc +#undef malloc +#endif + +int smb_create_share_mode_entry_ex(struct smbdb_ctx *db_ctx, uint64_t dev, + uint64_t ino, const struct smb_share_mode_entry *new_entry, + const char *sharepath, const char *filename); + +static bool sharemodes_procid_equal(const struct server_id *p1, const struct server_id *p2) +{ + return (p1->pid == p2->pid); +} + +static pid_t sharemodes_procid_to_pid(const struct server_id *proc) +{ + return proc->pid; +} + +/* + * open/close sharemode database. + */ + +struct smbdb_ctx *smb_share_mode_db_open(const char *db_path) +{ + struct smbdb_ctx *smb_db = (struct smbdb_ctx *)malloc(sizeof(struct smbdb_ctx)); + + if (!smb_db) { + return NULL; + } + + memset(smb_db, '\0', sizeof(struct smbdb_ctx)); + + smb_db->smb_tdb = tdb_open(db_path, + 0, TDB_DEFAULT|TDB_CLEAR_IF_FIRST, + O_RDWR|O_CREAT, + 0644); + + if (!smb_db->smb_tdb) { + free(smb_db); + return NULL; + } + + /* Should check that this is the correct version.... */ + return smb_db; +} + +/* key and data records in the tdb locking database */ +struct locking_key { + SMB_DEV_T dev; + SMB_INO_T inode; +}; + +int smb_share_mode_db_close(struct smbdb_ctx *db_ctx) +{ + int ret = tdb_close(db_ctx->smb_tdb); + free(db_ctx); + return ret; +} + +static TDB_DATA get_locking_key(uint64_t dev, uint64_t ino) +{ + static struct locking_key lk; + TDB_DATA ld; + + memset(&lk, '\0', sizeof(struct locking_key)); + lk.dev = (SMB_DEV_T)dev; + lk.inode = (SMB_INO_T)ino; + ld.dptr = (uint8 *)&lk; + ld.dsize = sizeof(lk); + return ld; +} + +/* + * lock/unlock entry in sharemode database. + */ + +int smb_lock_share_mode_entry(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino) +{ + return tdb_chainlock(db_ctx->smb_tdb, get_locking_key(dev, ino)); +} + +int smb_unlock_share_mode_entry(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino) +{ + return tdb_chainunlock(db_ctx->smb_tdb, get_locking_key(dev, ino)); +} + +/* + * Check if an external smb_share_mode_entry and an internal share_mode entry match. + */ + +static int share_mode_entry_equal(const struct smb_share_mode_entry *e_entry, + const struct share_mode_entry *entry) +{ + return (sharemodes_procid_equal(&e_entry->pid, &entry->pid) && + e_entry->file_id == (uint32_t)entry->share_file_id && + e_entry->open_time.tv_sec == entry->time.tv_sec && + e_entry->open_time.tv_usec == entry->time.tv_usec && + e_entry->share_access == (uint32_t)entry->share_access && + e_entry->access_mask == (uint32_t)entry->access_mask && + e_entry->dev == entry->id.devid && + e_entry->ino == entry->id.inode); +} + +/* + * Create an internal Samba share_mode entry from an external smb_share_mode_entry. + */ + +static void create_share_mode_entry(struct share_mode_entry *out, + const struct smb_share_mode_entry *in) +{ + memset(out, '\0', sizeof(struct share_mode_entry)); + + out->pid = in->pid; + out->share_file_id = (unsigned long)in->file_id; + out->time.tv_sec = in->open_time.tv_sec; + out->time.tv_usec = in->open_time.tv_usec; + out->share_access = in->share_access; + out->access_mask = in->access_mask; + out->id.devid = in->dev; + out->id.inode = in->ino; + out->uid = (uint32)geteuid(); + out->flags = 0; +} + +/* + * Return the current share mode list for an open file. + * This uses similar (but simplified) logic to locking/locking.c + */ + +int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino, + struct smb_share_mode_entry **pp_list, + unsigned char *p_delete_on_close) +{ + TDB_DATA db_data; + struct smb_share_mode_entry *list = NULL; + int num_share_modes = 0; + struct locking_data *ld = NULL; /* internal samba db state. */ + struct share_mode_entry *shares = NULL; + size_t i; + int list_num; + + *pp_list = NULL; + *p_delete_on_close = 0; + + db_data = tdb_fetch(db_ctx->smb_tdb, get_locking_key(dev, ino)); + if (!db_data.dptr) { + return 0; + } + + ld = (struct locking_data *)db_data.dptr; + num_share_modes = ld->u.s.num_share_mode_entries; + + if (!num_share_modes) { + free(db_data.dptr); + return 0; + } + + list = (struct smb_share_mode_entry *)malloc(sizeof(struct smb_share_mode_entry)*num_share_modes); + if (!list) { + free(db_data.dptr); + return -1; + } + + memset(list, '\0', num_share_modes * sizeof(struct smb_share_mode_entry)); + + shares = (struct share_mode_entry *)(db_data.dptr + sizeof(struct locking_data)); + + list_num = 0; + for (i = 0; i < num_share_modes; i++) { + struct share_mode_entry *share = &shares[i]; + struct smb_share_mode_entry *sme = &list[list_num]; + struct server_id pid = share->pid; + + /* Check this process really exists. */ + if (kill(sharemodes_procid_to_pid(&pid), 0) == -1 && (errno == ESRCH)) { + continue; /* No longer exists. */ + } + + /* Ignore deferred open entries. */ + if (share->op_type == DEFERRED_OPEN_ENTRY) { + continue; + } + + /* Copy into the external list. */ + sme->dev = share->id.devid; + sme->ino = share->id.inode; + sme->share_access = (uint32_t)share->share_access; + sme->access_mask = (uint32_t)share->access_mask; + sme->open_time.tv_sec = share->time.tv_sec; + sme->open_time.tv_usec = share->time.tv_usec; + sme->file_id = (uint32_t)share->share_file_id; + sme->pid = share->pid; + list_num++; + } + + if (list_num == 0) { + free(db_data.dptr); + free(list); + return 0; + } + + *p_delete_on_close = ld->u.s.delete_on_close; + *pp_list = list; + free(db_data.dptr); + return list_num; +} + +/* + * Create an entry in the Samba share mode db. + */ + +int smb_create_share_mode_entry_ex(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino, + const struct smb_share_mode_entry *new_entry, + const char *sharepath, /* Must be absolute utf8 path. */ + const char *filename) /* Must be relative utf8 path. */ +{ + TDB_DATA db_data; + TDB_DATA locking_key = get_locking_key(dev, ino); + int orig_num_share_modes = 0; + struct locking_data *ld = NULL; /* internal samba db state. */ + struct share_mode_entry *shares = NULL; + uint8 *new_data_p = NULL; + size_t new_data_size = 0; + + db_data = tdb_fetch(db_ctx->smb_tdb, locking_key); + if (!db_data.dptr) { + /* We must create the entry. */ + db_data.dptr = (uint8 *)malloc( + sizeof(struct locking_data) + + sizeof(struct share_mode_entry) + + strlen(sharepath) + 1 + + strlen(filename) + 1); + if (!db_data.dptr) { + return -1; + } + ld = (struct locking_data *)db_data.dptr; + memset(ld, '\0', sizeof(struct locking_data)); + ld->u.s.num_share_mode_entries = 1; + ld->u.s.delete_on_close = 0; + ld->u.s.delete_token_size = 0; + shares = (struct share_mode_entry *)(db_data.dptr + sizeof(struct locking_data)); + create_share_mode_entry(shares, new_entry); + + memcpy(db_data.dptr + sizeof(struct locking_data) + sizeof(struct share_mode_entry), + sharepath, + strlen(sharepath) + 1); + memcpy(db_data.dptr + sizeof(struct locking_data) + sizeof(struct share_mode_entry) + + strlen(sharepath) + 1, + filename, + strlen(filename) + 1); + + db_data.dsize = sizeof(struct locking_data) + sizeof(struct share_mode_entry) + + strlen(sharepath) + 1 + + strlen(filename) + 1; + if (tdb_store(db_ctx->smb_tdb, locking_key, db_data, TDB_INSERT) == -1) { + free(db_data.dptr); + return -1; + } + free(db_data.dptr); + return 0; + } + + /* Entry exists, we must add a new entry. */ + new_data_p = (uint8 *)malloc( + db_data.dsize + sizeof(struct share_mode_entry)); + if (!new_data_p) { + free(db_data.dptr); + return -1; + } + + ld = (struct locking_data *)db_data.dptr; + orig_num_share_modes = ld->u.s.num_share_mode_entries; + + /* Copy the original data. */ + memcpy(new_data_p, db_data.dptr, sizeof(struct locking_data) + (orig_num_share_modes * sizeof(struct share_mode_entry))); + + /* Add in the new share mode */ + shares = (struct share_mode_entry *)(new_data_p + sizeof(struct locking_data) + + (orig_num_share_modes * sizeof(struct share_mode_entry))); + + create_share_mode_entry(shares, new_entry); + + ld = (struct locking_data *)new_data_p; + ld->u.s.num_share_mode_entries++; + + /* Append the original delete_token and filenames. */ + memcpy(new_data_p + sizeof(struct locking_data) + (ld->u.s.num_share_mode_entries * sizeof(struct share_mode_entry)), + db_data.dptr + sizeof(struct locking_data) + (orig_num_share_modes * sizeof(struct share_mode_entry)), + db_data.dsize - sizeof(struct locking_data) - (orig_num_share_modes * sizeof(struct share_mode_entry))); + + new_data_size = db_data.dsize + sizeof(struct share_mode_entry); + + free(db_data.dptr); + + db_data.dptr = new_data_p; + db_data.dsize = new_data_size; + + if (tdb_store(db_ctx->smb_tdb, locking_key, db_data, TDB_REPLACE) == -1) { + free(db_data.dptr); + return -1; + } + free(db_data.dptr); + return 0; +} + +/* + * Create an entry in the Samba share mode db. Original interface - doesn't + * Distinguish between share path and filename. Fudge this by using a + * sharepath of / and a relative filename of (filename+1). + */ + +int smb_create_share_mode_entry(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino, + const struct smb_share_mode_entry *new_entry, + const char *filename) /* Must be absolute utf8 path. */ +{ + if (*filename != '/') { + abort(); + } + return smb_create_share_mode_entry_ex(db_ctx, dev, ino, new_entry, + "/", &filename[1]); +} + +int smb_delete_share_mode_entry(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino, + const struct smb_share_mode_entry *del_entry) +{ + TDB_DATA db_data; + TDB_DATA locking_key = get_locking_key(dev, ino); + int orig_num_share_modes = 0; + struct locking_data *ld = NULL; /* internal samba db state. */ + struct share_mode_entry *shares = NULL; + uint8 *new_data_p = NULL; + size_t remaining_size = 0; + size_t i, num_share_modes; + const uint8 *remaining_ptr = NULL; + + db_data = tdb_fetch(db_ctx->smb_tdb, locking_key); + if (!db_data.dptr) { + return -1; /* Error - missing entry ! */ + } + + ld = (struct locking_data *)db_data.dptr; + orig_num_share_modes = ld->u.s.num_share_mode_entries; + shares = (struct share_mode_entry *)(db_data.dptr + sizeof(struct locking_data)); + + if (orig_num_share_modes == 1) { + /* Only one entry - better be ours... */ + if (!share_mode_entry_equal(del_entry, shares)) { + /* Error ! We can't delete someone else's entry ! */ + free(db_data.dptr); + return -1; + } + /* It's ours - just remove the entire record. */ + free(db_data.dptr); + return tdb_delete(db_ctx->smb_tdb, locking_key); + } + + /* More than one - allocate a new record minus the one we'll delete. */ + new_data_p = (uint8 *)malloc( + db_data.dsize - sizeof(struct share_mode_entry)); + if (!new_data_p) { + free(db_data.dptr); + return -1; + } + + /* Copy the header. */ + memcpy(new_data_p, db_data.dptr, sizeof(struct locking_data)); + + num_share_modes = 0; + for (i = 0; i < orig_num_share_modes; i++) { + struct share_mode_entry *share = &shares[i]; + struct server_id pid = share->pid; + + /* Check this process really exists. */ + if (kill(sharemodes_procid_to_pid(&pid), 0) == -1 && (errno == ESRCH)) { + continue; /* No longer exists. */ + } + + if (share_mode_entry_equal(del_entry, share)) { + continue; /* This is our delete taget. */ + } + + memcpy(new_data_p + sizeof(struct locking_data) + + (num_share_modes * sizeof(struct share_mode_entry)), + share, sizeof(struct share_mode_entry) ); + + num_share_modes++; + } + + if (num_share_modes == 0) { + /* None left after pruning. Delete record. */ + free(db_data.dptr); + free(new_data_p); + return tdb_delete(db_ctx->smb_tdb, locking_key); + } + + /* Copy any delete token plus the terminating filenames. */ + remaining_ptr = db_data.dptr + sizeof(struct locking_data) + (orig_num_share_modes * sizeof(struct share_mode_entry)); + remaining_size = db_data.dsize - (remaining_ptr - db_data.dptr); + + memcpy(new_data_p + sizeof(struct locking_data) + (num_share_modes * sizeof(struct share_mode_entry)), + remaining_ptr, + remaining_size); + + free(db_data.dptr); + + db_data.dptr = new_data_p; + + /* Re-save smaller record. */ + ld = (struct locking_data *)db_data.dptr; + ld->u.s.num_share_mode_entries = num_share_modes; + + db_data.dsize = sizeof(struct locking_data) + (num_share_modes * sizeof(struct share_mode_entry)) + remaining_size; + + if (tdb_store(db_ctx->smb_tdb, locking_key, db_data, TDB_REPLACE) == -1) { + free(db_data.dptr); + return -1; + } + free(db_data.dptr); + return 0; +} + +int smb_change_share_mode_entry(struct smbdb_ctx *db_ctx, + uint64_t dev, + uint64_t ino, + const struct smb_share_mode_entry *set_entry, + const struct smb_share_mode_entry *new_entry) +{ + TDB_DATA db_data; + TDB_DATA locking_key = get_locking_key(dev, ino); + int num_share_modes = 0; + struct locking_data *ld = NULL; /* internal samba db state. */ + struct share_mode_entry *shares = NULL; + size_t i; + int found_entry = 0; + + db_data = tdb_fetch(db_ctx->smb_tdb, locking_key); + if (!db_data.dptr) { + return -1; /* Error - missing entry ! */ + } + + ld = (struct locking_data *)db_data.dptr; + num_share_modes = ld->u.s.num_share_mode_entries; + shares = (struct share_mode_entry *)(db_data.dptr + sizeof(struct locking_data)); + + for (i = 0; i < num_share_modes; i++) { + struct share_mode_entry *share = &shares[i]; + struct server_id pid = share->pid; + + /* Check this process really exists. */ + if (kill(sharemodes_procid_to_pid(&pid), 0) == -1 && (errno == ESRCH)) { + continue; /* No longer exists. */ + } + + if (share_mode_entry_equal(set_entry, share)) { + create_share_mode_entry(share, new_entry); + found_entry = 1; + break; + } + } + + if (!found_entry) { + free(db_data.dptr); + return -1; + } + + /* Save modified data. */ + if (tdb_store(db_ctx->smb_tdb, locking_key, db_data, TDB_REPLACE) == -1) { + free(db_data.dptr); + return -1; + } + free(db_data.dptr); + return 0; +} diff --git a/source3/libsmb/smb_signing.c b/source3/libsmb/smb_signing.c new file mode 100644 index 0000000000..ea1eb05cfb --- /dev/null +++ b/source3/libsmb/smb_signing.c @@ -0,0 +1,1004 @@ +/* + Unix SMB/CIFS implementation. + SMB Signing Code + Copyright (C) Jeremy Allison 2003. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* Lookup a packet's MID (multiplex id) and figure out it's sequence number */ +struct outstanding_packet_lookup { + struct outstanding_packet_lookup *prev, *next; + uint16 mid; + uint32 reply_seq_num; + bool can_delete; /* Set to False in trans state. */ +}; + +struct smb_basic_signing_context { + DATA_BLOB mac_key; + uint32 send_seq_num; + struct outstanding_packet_lookup *outstanding_packet_list; +}; + +static bool store_sequence_for_reply(struct outstanding_packet_lookup **list, + uint16 mid, uint32 reply_seq_num) +{ + struct outstanding_packet_lookup *t; + + /* Ensure we only add a mid once. */ + for (t = *list; t; t = t->next) { + if (t->mid == mid) { + return False; + } + } + + t = SMB_XMALLOC_P(struct outstanding_packet_lookup); + ZERO_STRUCTP(t); + + t->mid = mid; + t->reply_seq_num = reply_seq_num; + t->can_delete = True; + + /* + * Add to the *start* of the list not the end of the list. + * This ensures that the *last* send sequence with this mid + * is returned by preference. + * This can happen if the mid wraps and one of the early + * mid numbers didn't get a reply and is still lurking on + * the list. JRA. Found by Fran Fabrizio <fran@cis.uab.edu>. + */ + + DLIST_ADD(*list, t); + DEBUG(10,("store_sequence_for_reply: stored seq = %u mid = %u\n", + (unsigned int)reply_seq_num, (unsigned int)mid )); + return True; +} + +static bool get_sequence_for_reply(struct outstanding_packet_lookup **list, + uint16 mid, uint32 *reply_seq_num) +{ + struct outstanding_packet_lookup *t; + + for (t = *list; t; t = t->next) { + if (t->mid == mid) { + *reply_seq_num = t->reply_seq_num; + DEBUG(10,("get_sequence_for_reply: found seq = %u mid = %u\n", + (unsigned int)t->reply_seq_num, (unsigned int)t->mid )); + if (t->can_delete) { + DLIST_REMOVE(*list, t); + SAFE_FREE(t); + } + return True; + } + } + return False; +} + +static bool set_sequence_can_delete_flag(struct outstanding_packet_lookup **list, uint16 mid, bool can_delete_entry) +{ + struct outstanding_packet_lookup *t; + + for (t = *list; t; t = t->next) { + if (t->mid == mid) { + t->can_delete = can_delete_entry; + return True; + } + } + return False; +} + +/*********************************************************** + SMB signing - Common code before we set a new signing implementation +************************************************************/ + +static bool cli_set_smb_signing_common(struct cli_state *cli) +{ + if (!cli->sign_info.allow_smb_signing) { + return False; + } + + if (!cli->sign_info.negotiated_smb_signing + && !cli->sign_info.mandatory_signing) { + return False; + } + + if (cli->sign_info.doing_signing) { + return False; + } + + if (cli->sign_info.free_signing_context) + cli->sign_info.free_signing_context(&cli->sign_info); + + /* These calls are INCOMPATIBLE with SMB signing */ + cli->readbraw_supported = False; + cli->writebraw_supported = False; + + return True; +} + +/*********************************************************** + SMB signing - Common code for 'real' implementations +************************************************************/ + +static bool set_smb_signing_real_common(struct smb_sign_info *si) +{ + if (si->mandatory_signing) { + DEBUG(5, ("Mandatory SMB signing enabled!\n")); + } + + si->doing_signing = True; + DEBUG(5, ("SMB signing enabled!\n")); + + return True; +} + +static void mark_packet_signed(char *outbuf) +{ + uint16 flags2; + flags2 = SVAL(outbuf,smb_flg2); + flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES; + SSVAL(outbuf,smb_flg2, flags2); +} + +/*********************************************************** + SMB signing - NULL implementation - calculate a MAC to send. +************************************************************/ + +static void null_sign_outgoing_message(char *outbuf, struct smb_sign_info *si) +{ + /* we can't zero out the sig, as we might be trying to send a + session request - which is NBT-level, not SMB level and doesn't + have the field */ + return; +} + +/*********************************************************** + SMB signing - NULL implementation - check a MAC sent by server. +************************************************************/ + +static bool null_check_incoming_message(const char *inbuf, + struct smb_sign_info *si, + bool must_be_ok) +{ + return True; +} + +/*********************************************************** + SMB signing - NULL implementation - free signing context +************************************************************/ + +static void null_free_signing_context(struct smb_sign_info *si) +{ + return; +} + +/** + SMB signing - NULL implementation - setup the MAC key. + + @note Used as an initialisation only - it will not correctly + shut down a real signing mechanism +*/ + +static bool null_set_signing(struct smb_sign_info *si) +{ + si->signing_context = NULL; + + si->sign_outgoing_message = null_sign_outgoing_message; + si->check_incoming_message = null_check_incoming_message; + si->free_signing_context = null_free_signing_context; + + return True; +} + +/** + * Free the signing context + */ + +static void free_signing_context(struct smb_sign_info *si) +{ + if (si->free_signing_context) { + si->free_signing_context(si); + si->signing_context = NULL; + } + + null_set_signing(si); +} + + +static bool signing_good(const char *inbuf, struct smb_sign_info *si, + bool good, uint32 seq, bool must_be_ok) +{ + if (good) { + + if (!si->doing_signing) { + si->doing_signing = True; + } + + if (!si->seen_valid) { + si->seen_valid = True; + } + + } else { + if (!si->mandatory_signing && !si->seen_valid) { + + if (!must_be_ok) { + return True; + } + /* Non-mandatory signing - just turn off if this is the first bad packet.. */ + DEBUG(5, ("srv_check_incoming_message: signing negotiated but not required and peer\n" + "isn't sending correct signatures. Turning off.\n")); + si->negotiated_smb_signing = False; + si->allow_smb_signing = False; + si->doing_signing = False; + free_signing_context(si); + return True; + } else if (!must_be_ok) { + /* This packet is known to be unsigned */ + return True; + } else { + /* Mandatory signing or bad packet after signing started - fail and disconnect. */ + if (seq) + DEBUG(0, ("signing_good: BAD SIG: seq %u\n", (unsigned int)seq)); + return False; + } + } + return True; +} + +/*********************************************************** + SMB signing - Simple implementation - calculate a MAC on the packet +************************************************************/ + +static void simple_packet_signature(struct smb_basic_signing_context *data, + const uchar *buf, uint32 seq_number, + unsigned char calc_md5_mac[16]) +{ + const size_t offset_end_of_sig = (smb_ss_field + 8); + unsigned char sequence_buf[8]; + struct MD5Context md5_ctx; +#if 0 + /* JRA - apparently this is incorrect. */ + unsigned char key_buf[16]; +#endif + + /* + * Firstly put the sequence number into the first 4 bytes. + * and zero out the next 4 bytes. + * + * We do this here, to avoid modifying the packet. + */ + + DEBUG(10,("simple_packet_signature: sequence number %u\n", seq_number )); + + SIVAL(sequence_buf, 0, seq_number); + SIVAL(sequence_buf, 4, 0); + + /* Calculate the 16 byte MAC - but don't alter the data in the + incoming packet. + + This makes for a bit of fussing about, but it's not too bad. + */ + MD5Init(&md5_ctx); + + /* intialise with the key */ + MD5Update(&md5_ctx, data->mac_key.data, data->mac_key.length); +#if 0 + /* JRA - apparently this is incorrect. */ + /* NB. When making and verifying SMB signatures, Windows apparently + zero-pads the key to 128 bits if it isn't long enough. + From Nalin Dahyabhai <nalin@redhat.com> */ + if (data->mac_key.length < sizeof(key_buf)) { + memset(key_buf, 0, sizeof(key_buf)); + MD5Update(&md5_ctx, key_buf, sizeof(key_buf) - data->mac_key.length); + } +#endif + + /* copy in the first bit of the SMB header */ + MD5Update(&md5_ctx, buf + 4, smb_ss_field - 4); + + /* copy in the sequence number, instead of the signature */ + MD5Update(&md5_ctx, sequence_buf, sizeof(sequence_buf)); + + /* copy in the rest of the packet in, skipping the signature */ + MD5Update(&md5_ctx, buf + offset_end_of_sig, + smb_len(buf) - (offset_end_of_sig - 4)); + + /* calculate the MD5 sig */ + MD5Final(calc_md5_mac, &md5_ctx); +} + + +/*********************************************************** + SMB signing - Client implementation - send the MAC. +************************************************************/ + +static void client_sign_outgoing_message(char *outbuf, struct smb_sign_info *si) +{ + unsigned char calc_md5_mac[16]; + struct smb_basic_signing_context *data = + (struct smb_basic_signing_context *)si->signing_context; + + if (!si->doing_signing) + return; + + /* JRA Paranioa test - we should be able to get rid of this... */ + if (smb_len(outbuf) < (smb_ss_field + 8 - 4)) { + DEBUG(1, ("client_sign_outgoing_message: Logic error. Can't check signature on short packet! smb_len = %u\n", + smb_len(outbuf) )); + abort(); + } + + /* mark the packet as signed - BEFORE we sign it...*/ + mark_packet_signed(outbuf); + + simple_packet_signature(data, (const unsigned char *)outbuf, + data->send_seq_num, calc_md5_mac); + + DEBUG(10, ("client_sign_outgoing_message: sent SMB signature of\n")); + dump_data(10, calc_md5_mac, 8); + + memcpy(&outbuf[smb_ss_field], calc_md5_mac, 8); + +/* cli->outbuf[smb_ss_field+2]=0; + Uncomment this to test if the remote server actually verifies signatures...*/ + + /* Instead of re-introducing the trans_info_conect we + used to have here, we use the fact that during a + SMBtrans/SMBtrans2/SMBnttrans send that the mid stays + constant. This means that calling store_sequence_for_reply() + will return False for all trans secondaries, as the mid is already + on the stored sequence list. As the send_seqence_number must + remain constant for all primary+secondary trans sends, we + only increment the send sequence number when we successfully + add a new entry to the outstanding sequence list. This means + I can isolate the fix here rather than re-adding the trans + signing on/off calls in libsmb/clitrans2.c JRA. + */ + + if (store_sequence_for_reply(&data->outstanding_packet_list, SVAL(outbuf,smb_mid), data->send_seq_num + 1)) { + data->send_seq_num += 2; + } +} + +/*********************************************************** + SMB signing - Client implementation - check a MAC sent by server. +************************************************************/ + +static bool client_check_incoming_message(const char *inbuf, + struct smb_sign_info *si, + bool must_be_ok) +{ + bool good; + uint32 reply_seq_number; + unsigned char calc_md5_mac[16]; + unsigned char *server_sent_mac; + + struct smb_basic_signing_context *data = + (struct smb_basic_signing_context *)si->signing_context; + + if (!si->doing_signing) + return True; + + if (smb_len(inbuf) < (smb_ss_field + 8 - 4)) { + DEBUG(1, ("client_check_incoming_message: Can't check signature on short packet! smb_len = %u\n", smb_len(inbuf))); + return False; + } + + if (!get_sequence_for_reply(&data->outstanding_packet_list, SVAL(inbuf, smb_mid), &reply_seq_number)) { + DEBUG(1, ("client_check_incoming_message: received message " + "with mid %u with no matching send record.\n", (unsigned int)SVAL(inbuf, smb_mid) )); + return False; + } + + simple_packet_signature(data, (const unsigned char *)inbuf, + reply_seq_number, calc_md5_mac); + + server_sent_mac = (unsigned char *)&inbuf[smb_ss_field]; + good = (memcmp(server_sent_mac, calc_md5_mac, 8) == 0); + + if (!good) { + DEBUG(5, ("client_check_incoming_message: BAD SIG: wanted SMB signature of\n")); + dump_data(5, calc_md5_mac, 8); + + DEBUG(5, ("client_check_incoming_message: BAD SIG: got SMB signature of\n")); + dump_data(5, server_sent_mac, 8); +#if 1 /* JRATEST */ + { + int i; + for (i = -5; i < 5; i++) { + simple_packet_signature(data, (const unsigned char *)inbuf, reply_seq_number+i, calc_md5_mac); + if (memcmp(server_sent_mac, calc_md5_mac, 8) == 0) { + DEBUG(0,("client_check_incoming_message: out of seq. seq num %u matches. \ +We were expecting seq %u\n", reply_seq_number+i, reply_seq_number )); + break; + } + } + } +#endif /* JRATEST */ + + } else { + DEBUG(10, ("client_check_incoming_message: seq %u: got good SMB signature of\n", (unsigned int)reply_seq_number)); + dump_data(10, server_sent_mac, 8); + } + return signing_good(inbuf, si, good, reply_seq_number, must_be_ok); +} + +/*********************************************************** + SMB signing - Simple implementation - free signing context +************************************************************/ + +static void simple_free_signing_context(struct smb_sign_info *si) +{ + struct smb_basic_signing_context *data = + (struct smb_basic_signing_context *)si->signing_context; + struct outstanding_packet_lookup *list; + struct outstanding_packet_lookup *next; + + for (list = data->outstanding_packet_list; list; list = next) { + next = list->next; + DLIST_REMOVE(data->outstanding_packet_list, list); + SAFE_FREE(list); + } + + data_blob_free(&data->mac_key); + + SAFE_FREE(si->signing_context); + + return; +} + +/*********************************************************** + SMB signing - Simple implementation - setup the MAC key. +************************************************************/ + +bool cli_simple_set_signing(struct cli_state *cli, + const DATA_BLOB user_session_key, + const DATA_BLOB response) +{ + struct smb_basic_signing_context *data; + + if (!user_session_key.length) + return False; + + if (!cli_set_smb_signing_common(cli)) { + return False; + } + + if (!set_smb_signing_real_common(&cli->sign_info)) { + return False; + } + + data = SMB_XMALLOC_P(struct smb_basic_signing_context); + memset(data, '\0', sizeof(*data)); + + cli->sign_info.signing_context = data; + + data->mac_key = data_blob(NULL, response.length + user_session_key.length); + + memcpy(&data->mac_key.data[0], user_session_key.data, user_session_key.length); + + DEBUG(10, ("cli_simple_set_signing: user_session_key\n")); + dump_data(10, user_session_key.data, user_session_key.length); + + if (response.length) { + memcpy(&data->mac_key.data[user_session_key.length],response.data, response.length); + DEBUG(10, ("cli_simple_set_signing: response_data\n")); + dump_data(10, response.data, response.length); + } else { + DEBUG(10, ("cli_simple_set_signing: NULL response_data\n")); + } + + dump_data_pw("MAC ssession key is:\n", data->mac_key.data, data->mac_key.length); + + /* Initialise the sequence number */ + data->send_seq_num = 0; + + /* Initialise the list of outstanding packets */ + data->outstanding_packet_list = NULL; + + cli->sign_info.sign_outgoing_message = client_sign_outgoing_message; + cli->sign_info.check_incoming_message = client_check_incoming_message; + cli->sign_info.free_signing_context = simple_free_signing_context; + + return True; +} + +/*********************************************************** + SMB signing - TEMP implementation - calculate a MAC to send. +************************************************************/ + +static void temp_sign_outgoing_message(char *outbuf, struct smb_sign_info *si) +{ + /* mark the packet as signed - BEFORE we sign it...*/ + mark_packet_signed(outbuf); + + /* I wonder what BSRSPYL stands for - but this is what MS + actually sends! */ + memcpy(&outbuf[smb_ss_field], "BSRSPYL ", 8); + return; +} + +/*********************************************************** + SMB signing - TEMP implementation - check a MAC sent by server. +************************************************************/ + +static bool temp_check_incoming_message(const char *inbuf, + struct smb_sign_info *si, bool foo) +{ + return True; +} + +/*********************************************************** + SMB signing - TEMP implementation - free signing context +************************************************************/ + +static void temp_free_signing_context(struct smb_sign_info *si) +{ + return; +} + +/*********************************************************** + SMB signing - NULL implementation - setup the MAC key. +************************************************************/ + +bool cli_null_set_signing(struct cli_state *cli) +{ + return null_set_signing(&cli->sign_info); +} + +/*********************************************************** + SMB signing - temp implementation - setup the MAC key. +************************************************************/ + +bool cli_temp_set_signing(struct cli_state *cli) +{ + if (!cli_set_smb_signing_common(cli)) { + return False; + } + + cli->sign_info.signing_context = NULL; + + cli->sign_info.sign_outgoing_message = temp_sign_outgoing_message; + cli->sign_info.check_incoming_message = temp_check_incoming_message; + cli->sign_info.free_signing_context = temp_free_signing_context; + + return True; +} + +void cli_free_signing_context(struct cli_state *cli) +{ + free_signing_context(&cli->sign_info); +} + +/** + * Sign a packet with the current mechanism + */ + +void cli_calculate_sign_mac(struct cli_state *cli, char *buf) +{ + cli->sign_info.sign_outgoing_message(buf, &cli->sign_info); +} + +/** + * Check a packet with the current mechanism + * @return False if we had an established signing connection + * which had a bad checksum, True otherwise. + */ + +bool cli_check_sign_mac(struct cli_state *cli, char *buf) +{ + if (!cli->sign_info.check_incoming_message(buf, &cli->sign_info, True)) { + free_signing_context(&cli->sign_info); + return False; + } + return True; +} + +/*********************************************************** + Enter trans/trans2/nttrans state. +************************************************************/ + +bool client_set_trans_sign_state_on(struct cli_state *cli, uint16 mid) +{ + struct smb_sign_info *si = &cli->sign_info; + struct smb_basic_signing_context *data = (struct smb_basic_signing_context *)si->signing_context; + + if (!si->doing_signing) { + return True; + } + + if (!data) { + return False; + } + + if (!set_sequence_can_delete_flag(&data->outstanding_packet_list, mid, False)) { + return False; + } + + return True; +} + +/*********************************************************** + Leave trans/trans2/nttrans state. +************************************************************/ + +bool client_set_trans_sign_state_off(struct cli_state *cli, uint16 mid) +{ + uint32 reply_seq_num; + struct smb_sign_info *si = &cli->sign_info; + struct smb_basic_signing_context *data = (struct smb_basic_signing_context *)si->signing_context; + + if (!si->doing_signing) { + return True; + } + + if (!data) { + return False; + } + + if (!set_sequence_can_delete_flag(&data->outstanding_packet_list, mid, True)) { + return False; + } + + /* Now delete the stored mid entry. */ + if (!get_sequence_for_reply(&data->outstanding_packet_list, mid, &reply_seq_num)) { + return False; + } + + return True; +} + +/*********************************************************** + Is client signing on ? +************************************************************/ + +bool client_is_signing_on(struct cli_state *cli) +{ + struct smb_sign_info *si = &cli->sign_info; + return si->doing_signing; +} + +/*********************************************************** + SMB signing - Server implementation - send the MAC. +************************************************************/ + +static void srv_sign_outgoing_message(char *outbuf, struct smb_sign_info *si) +{ + unsigned char calc_md5_mac[16]; + struct smb_basic_signing_context *data = + (struct smb_basic_signing_context *)si->signing_context; + uint32 send_seq_number = data->send_seq_num-1; + uint16 mid; + + if (!si->doing_signing) { + return; + } + + /* JRA Paranioa test - we should be able to get rid of this... */ + if (smb_len(outbuf) < (smb_ss_field + 8 - 4)) { + DEBUG(1, ("srv_sign_outgoing_message: Logic error. Can't send signature on short packet! smb_len = %u\n", + smb_len(outbuf) )); + abort(); + } + + /* mark the packet as signed - BEFORE we sign it...*/ + mark_packet_signed(outbuf); + + mid = SVAL(outbuf, smb_mid); + + /* See if this is a reply for a deferred packet. */ + get_sequence_for_reply(&data->outstanding_packet_list, mid, &send_seq_number); + + simple_packet_signature(data, (const unsigned char *)outbuf, send_seq_number, calc_md5_mac); + + DEBUG(10, ("srv_sign_outgoing_message: seq %u: sent SMB signature of\n", (unsigned int)send_seq_number)); + dump_data(10, calc_md5_mac, 8); + + memcpy(&outbuf[smb_ss_field], calc_md5_mac, 8); + +/* cli->outbuf[smb_ss_field+2]=0; + Uncomment this to test if the remote client actually verifies signatures...*/ +} + +/*********************************************************** + SMB signing - Server implementation - check a MAC sent by server. +************************************************************/ + +static bool srv_check_incoming_message(const char *inbuf, + struct smb_sign_info *si, + bool must_be_ok) +{ + bool good; + struct smb_basic_signing_context *data = + (struct smb_basic_signing_context *)si->signing_context; + uint32 reply_seq_number = data->send_seq_num; + uint32 saved_seq; + unsigned char calc_md5_mac[16]; + unsigned char *server_sent_mac; + + if (!si->doing_signing) + return True; + + if (smb_len(inbuf) < (smb_ss_field + 8 - 4)) { + DEBUG(1, ("srv_check_incoming_message: Can't check signature on short packet! smb_len = %u\n", smb_len(inbuf))); + return False; + } + + /* We always increment the sequence number. */ + data->send_seq_num += 2; + + saved_seq = reply_seq_number; + simple_packet_signature(data, (const unsigned char *)inbuf, reply_seq_number, calc_md5_mac); + + server_sent_mac = (unsigned char *)&inbuf[smb_ss_field]; + good = (memcmp(server_sent_mac, calc_md5_mac, 8) == 0); + + if (!good) { + + if (saved_seq) { + DEBUG(0, ("srv_check_incoming_message: BAD SIG: seq %u wanted SMB signature of\n", + (unsigned int)saved_seq)); + dump_data(5, calc_md5_mac, 8); + + DEBUG(0, ("srv_check_incoming_message: BAD SIG: seq %u got SMB signature of\n", + (unsigned int)reply_seq_number)); + dump_data(5, server_sent_mac, 8); + } + +#if 1 /* JRATEST */ + { + int i; + reply_seq_number -= 5; + for (i = 0; i < 10; i++, reply_seq_number++) { + simple_packet_signature(data, (const unsigned char *)inbuf, reply_seq_number, calc_md5_mac); + if (memcmp(server_sent_mac, calc_md5_mac, 8) == 0) { + DEBUG(0,("srv_check_incoming_message: out of seq. seq num %u matches. \ +We were expecting seq %u\n", reply_seq_number, saved_seq )); + break; + } + } + } +#endif /* JRATEST */ + + } else { + DEBUG(10, ("srv_check_incoming_message: seq %u: (current is %u) got good SMB signature of\n", (unsigned int)reply_seq_number, (unsigned int)data->send_seq_num)); + dump_data(10, server_sent_mac, 8); + } + + return (signing_good(inbuf, si, good, saved_seq, must_be_ok)); +} + +/*********************************************************** + SMB signing - server API's. +************************************************************/ + +static struct smb_sign_info srv_sign_info = { + null_sign_outgoing_message, + null_check_incoming_message, + null_free_signing_context, + NULL, + False, + False, + False, + False +}; + +/*********************************************************** + Turn signing off or on for oplock break code. +************************************************************/ + +bool srv_oplock_set_signing(bool onoff) +{ + bool ret = srv_sign_info.doing_signing; + srv_sign_info.doing_signing = onoff; + return ret; +} + +/*********************************************************** + Called to validate an incoming packet from the client. +************************************************************/ + +bool srv_check_sign_mac(const char *inbuf, bool must_be_ok) +{ + /* Check if it's a non-session message. */ + if(CVAL(inbuf,0)) { + return True; + } + + return srv_sign_info.check_incoming_message(inbuf, &srv_sign_info, must_be_ok); +} + +/*********************************************************** + Called to sign an outgoing packet to the client. +************************************************************/ + +void srv_calculate_sign_mac(char *outbuf) +{ + /* Check if it's a non-session message. */ + if(CVAL(outbuf,0)) { + return; + } + + srv_sign_info.sign_outgoing_message(outbuf, &srv_sign_info); +} + +/*********************************************************** + Called by server to defer an outgoing packet. +************************************************************/ + +void srv_defer_sign_response(uint16 mid) +{ + struct smb_basic_signing_context *data; + + if (!srv_sign_info.doing_signing) + return; + + data = (struct smb_basic_signing_context *)srv_sign_info.signing_context; + + if (!data) + return; + + /* + * Ensure we only store this mid reply once... + */ + + store_sequence_for_reply(&data->outstanding_packet_list, mid, + data->send_seq_num-1); +} + +/*********************************************************** + Called to remove sequence records when a deferred packet is + cancelled by mid. This should never find one.... +************************************************************/ + +void srv_cancel_sign_response(uint16 mid) +{ + struct smb_basic_signing_context *data; + uint32 dummy_seq; + + if (!srv_sign_info.doing_signing) + return; + + data = (struct smb_basic_signing_context *)srv_sign_info.signing_context; + + if (!data) + return; + + DEBUG(10,("srv_cancel_sign_response: for mid %u\n", (unsigned int)mid )); + + while (get_sequence_for_reply(&data->outstanding_packet_list, mid, &dummy_seq)) + ; + + /* cancel doesn't send a reply so doesn't burn a sequence number. */ + data->send_seq_num -= 1; +} + +/*********************************************************** + Called by server negprot when signing has been negotiated. +************************************************************/ + +void srv_set_signing_negotiated(void) +{ + srv_sign_info.allow_smb_signing = True; + srv_sign_info.negotiated_smb_signing = True; + if (lp_server_signing() == Required) + srv_sign_info.mandatory_signing = True; + + srv_sign_info.sign_outgoing_message = temp_sign_outgoing_message; + srv_sign_info.check_incoming_message = temp_check_incoming_message; + srv_sign_info.free_signing_context = temp_free_signing_context; +} + +/*********************************************************** + Returns whether signing is active. We can't use sendfile or raw + reads/writes if it is. +************************************************************/ + +bool srv_is_signing_active(void) +{ + return srv_sign_info.doing_signing; +} + + +/*********************************************************** + Returns whether signing is negotiated. We can't use it unless it was + in the negprot. +************************************************************/ + +bool srv_is_signing_negotiated(void) +{ + return srv_sign_info.negotiated_smb_signing; +} + +/*********************************************************** + Returns whether signing is actually happening +************************************************************/ + +bool srv_signing_started(void) +{ + struct smb_basic_signing_context *data; + + if (!srv_sign_info.doing_signing) { + return False; + } + + data = (struct smb_basic_signing_context *)srv_sign_info.signing_context; + if (!data) + return False; + + if (data->send_seq_num == 0) { + return False; + } + + return True; +} + +/*********************************************************** + Turn on signing from this packet onwards. +************************************************************/ + +void srv_set_signing(const DATA_BLOB user_session_key, const DATA_BLOB response) +{ + struct smb_basic_signing_context *data; + + if (!user_session_key.length) + return; + + if (!srv_sign_info.negotiated_smb_signing && !srv_sign_info.mandatory_signing) { + DEBUG(5,("srv_set_signing: signing negotiated = %u, mandatory_signing = %u. Not allowing smb signing.\n", + (unsigned int)srv_sign_info.negotiated_smb_signing, + (unsigned int)srv_sign_info.mandatory_signing )); + return; + } + + /* Once we've turned on, ignore any more sessionsetups. */ + if (srv_sign_info.doing_signing) { + return; + } + + if (srv_sign_info.free_signing_context) + srv_sign_info.free_signing_context(&srv_sign_info); + + srv_sign_info.doing_signing = True; + + data = SMB_XMALLOC_P(struct smb_basic_signing_context); + memset(data, '\0', sizeof(*data)); + + srv_sign_info.signing_context = data; + + data->mac_key = data_blob(NULL, response.length + user_session_key.length); + + memcpy(&data->mac_key.data[0], user_session_key.data, user_session_key.length); + if (response.length) + memcpy(&data->mac_key.data[user_session_key.length],response.data, response.length); + + dump_data_pw("MAC ssession key is:\n", data->mac_key.data, data->mac_key.length); + + DEBUG(3,("srv_set_signing: turning on SMB signing: signing negotiated = %s, mandatory_signing = %s.\n", + BOOLSTR(srv_sign_info.negotiated_smb_signing), + BOOLSTR(srv_sign_info.mandatory_signing) )); + + /* Initialise the sequence number */ + data->send_seq_num = 0; + + /* Initialise the list of outstanding packets */ + data->outstanding_packet_list = NULL; + + srv_sign_info.sign_outgoing_message = srv_sign_outgoing_message; + srv_sign_info.check_incoming_message = srv_check_incoming_message; + srv_sign_info.free_signing_context = simple_free_signing_context; +} diff --git a/source3/libsmb/smbdes.c b/source3/libsmb/smbdes.c new file mode 100644 index 0000000000..98d5cd05b7 --- /dev/null +++ b/source3/libsmb/smbdes.c @@ -0,0 +1,420 @@ +/* + Unix SMB/CIFS implementation. + + a partial implementation of DES designed for use in the + SMB authentication protocol + + Copyright (C) Andrew Tridgell 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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* NOTES: + + This code makes no attempt to be fast! In fact, it is a very + slow implementation + + This code is NOT a complete DES implementation. It implements only + the minimum necessary for SMB authentication, as used by all SMB + products (including every copy of Microsoft Windows95 ever sold) + + In particular, it can only do a unchained forward DES pass. This + means it is not possible to use this code for encryption/decryption + of data, instead it is only useful as a "hash" algorithm. + + There is no entry point into this code that allows normal DES operation. + + I believe this means that this code does not come under ITAR + regulations but this is NOT a legal opinion. If you are concerned + about the applicability of ITAR regulations to this code then you + should confirm it for yourself (and maybe let me know if you come + up with a different answer to the one above) +*/ + + +#define uchar unsigned char + +static const uchar perm1[56] = {57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4}; + +static const uchar perm2[48] = {14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32}; + +static const uchar perm3[64] = {58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7}; + +static const uchar perm4[48] = { 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1}; + +static const uchar perm5[32] = { 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25}; + + +static const uchar perm6[64] ={ 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25}; + + +static const uchar sc[16] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}; + +static const uchar sbox[8][4][16] = { + {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7}, + {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8}, + {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0}, + {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}}, + + {{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10}, + {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5}, + {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15}, + {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}}, + + {{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8}, + {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1}, + {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7}, + {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}}, + + {{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15}, + {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9}, + {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4}, + {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}}, + + {{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9}, + {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6}, + {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14}, + {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}}, + + {{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11}, + {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8}, + {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6}, + {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}}, + + {{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1}, + {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6}, + {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2}, + {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}}, + + {{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7}, + {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2}, + {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8}, + {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}}}; + +static void permute(char *out, const char *in, const uchar *p, int n) +{ + int i; + for (i=0;i<n;i++) + out[i] = in[p[i]-1]; +} + +static void lshift(char *d, int count, int n) +{ + char out[64]; + int i; + for (i=0;i<n;i++) + out[i] = d[(i+count)%n]; + for (i=0;i<n;i++) + d[i] = out[i]; +} + +static void concat(char *out, char *in1, char *in2, int l1, int l2) +{ + while (l1--) + *out++ = *in1++; + while (l2--) + *out++ = *in2++; +} + +static void x_or(char *out, char *in1, char *in2, int n) +{ + int i; + for (i=0;i<n;i++) + out[i] = in1[i] ^ in2[i]; +} + +static void dohash(char *out, char *in, char *key, int forw) +{ + int i, j, k; + char pk1[56]; + char c[28]; + char d[28]; + char cd[56]; + char ki[16][48]; + char pd1[64]; + char l[32], r[32]; + char rl[64]; + + permute(pk1, key, perm1, 56); + + for (i=0;i<28;i++) + c[i] = pk1[i]; + for (i=0;i<28;i++) + d[i] = pk1[i+28]; + + for (i=0;i<16;i++) { + lshift(c, sc[i], 28); + lshift(d, sc[i], 28); + + concat(cd, c, d, 28, 28); + permute(ki[i], cd, perm2, 48); + } + + permute(pd1, in, perm3, 64); + + for (j=0;j<32;j++) { + l[j] = pd1[j]; + r[j] = pd1[j+32]; + } + + for (i=0;i<16;i++) { + char er[48]; + char erk[48]; + char b[8][6]; + char cb[32]; + char pcb[32]; + char r2[32]; + + permute(er, r, perm4, 48); + + x_or(erk, er, ki[forw ? i : 15 - i], 48); + + for (j=0;j<8;j++) + for (k=0;k<6;k++) + b[j][k] = erk[j*6 + k]; + + for (j=0;j<8;j++) { + int m, n; + m = (b[j][0]<<1) | b[j][5]; + + n = (b[j][1]<<3) | (b[j][2]<<2) | (b[j][3]<<1) | b[j][4]; + + for (k=0;k<4;k++) + b[j][k] = (sbox[j][m][n] & (1<<(3-k)))?1:0; + } + + for (j=0;j<8;j++) + for (k=0;k<4;k++) + cb[j*4+k] = b[j][k]; + permute(pcb, cb, perm5, 32); + + x_or(r2, l, pcb, 32); + + for (j=0;j<32;j++) + l[j] = r[j]; + + for (j=0;j<32;j++) + r[j] = r2[j]; + } + + concat(rl, r, l, 32, 32); + + permute(out, rl, perm6, 64); +} + +/* Convert a 7 byte string to an 8 byte key. */ +static void str_to_key(const unsigned char str[7], unsigned char key[8]) +{ + int i; + + key[0] = str[0]>>1; + key[1] = ((str[0]&0x01)<<6) | (str[1]>>2); + key[2] = ((str[1]&0x03)<<5) | (str[2]>>3); + key[3] = ((str[2]&0x07)<<4) | (str[3]>>4); + key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5); + key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6); + key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7); + key[7] = str[6]&0x7F; + for (i=0;i<8;i++) { + key[i] = (key[i]<<1); + } +} + + +void des_crypt56(unsigned char *out, const unsigned char *in, const unsigned char *key, int forw) +{ + int i; + char outb[64]; + char inb[64]; + char keyb[64]; + unsigned char key2[8]; + + str_to_key(key, key2); + + for (i=0;i<64;i++) { + inb[i] = (in[i/8] & (1<<(7-(i%8)))) ? 1 : 0; + keyb[i] = (key2[i/8] & (1<<(7-(i%8)))) ? 1 : 0; + outb[i] = 0; + } + + dohash(outb, inb, keyb, forw); + + for (i=0;i<8;i++) { + out[i] = 0; + } + + for (i=0;i<64;i++) { + if (outb[i]) + out[i/8] |= (1<<(7-(i%8))); + } +} + +void E_P16(const unsigned char *p14,unsigned char *p16) +{ + unsigned char sp8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; + des_crypt56(p16, sp8, p14, 1); + des_crypt56(p16+8, sp8, p14+7, 1); +} + +void E_P24(const unsigned char *p21, const unsigned char *c8, unsigned char *p24) +{ + des_crypt56(p24, c8, p21, 1); + des_crypt56(p24+8, c8, p21+7, 1); + des_crypt56(p24+16, c8, p21+14, 1); +} + +void D_P16(const unsigned char *p14, const unsigned char *in, unsigned char *out) +{ + des_crypt56(out, in, p14, 0); + des_crypt56(out+8, in+8, p14+7, 0); +} + +void E_old_pw_hash( unsigned char *p14, const unsigned char *in, unsigned char *out) +{ + des_crypt56(out, in, p14, 1); + des_crypt56(out+8, in+8, p14+7, 1); +} + +/* forward des encryption with a 128 bit key */ +void des_crypt128(unsigned char out[8], const unsigned char in[8], const unsigned char key[16]) +{ + unsigned char buf[8]; + + des_crypt56(buf, in, key, 1); + des_crypt56(out, buf, key+9, 1); +} + +/* forward des encryption with a 64 bit key */ +void des_crypt64(unsigned char out[8], const unsigned char in[8], const unsigned char key[8]) +{ + unsigned char buf[8]; + unsigned char key2[8]; + + memset(key2,'\0',8); + des_crypt56(buf, in, key, 1); + key2[0] = key[7]; + des_crypt56(out, buf, key2, 1); +} + +/* des encryption with a 112 bit (14 byte) key */ +/* Note that if the forw is 1, and key is actually 8 bytes of key, followed by 6 bytes of zeros, + this is identical to des_crypt64(). JRA. */ + +void des_crypt112(unsigned char out[8], const unsigned char in[8], const unsigned char key[14], int forw) +{ + unsigned char buf[8]; + des_crypt56(buf, in, key, forw); + des_crypt56(out, buf, key+7, forw); +} + +void cred_hash3(unsigned char *out, const unsigned char *in, const unsigned char *key, int forw) +{ + unsigned char key2[8]; + + memset(key2,'\0',8); + des_crypt56(out, in, key, forw); + key2[0] = key[7]; + des_crypt56(out + 8, in + 8, key2, forw); +} + +/* des encryption of a 16 byte lump of data with a 112 bit key */ +/* Note that if the key is actually 8 bytes of key, followed by 6 bytes of zeros, + this is identical to cred_hash3(). JRA. */ + +void des_crypt112_16(unsigned char out[16], unsigned char in[16], const unsigned char key[14], int forw) +{ + des_crypt56(out, in, key, forw); + des_crypt56(out + 8, in + 8, key+7, forw); +} + +/***************************************************************** + arc4 crypt/decrypt with a 16 byte key. +*****************************************************************/ + +void SamOEMhash( unsigned char *data, const unsigned char key[16], size_t len) +{ + unsigned char arc4_state[258]; + + smb_arc4_init(arc4_state, key, 16); + smb_arc4_crypt(arc4_state, data, len); +} + +void SamOEMhashBlob( unsigned char *data, size_t len, DATA_BLOB *key) +{ + unsigned char arc4_state[258]; + + smb_arc4_init(arc4_state, key->data, key->length); + smb_arc4_crypt(arc4_state, data, len); +} + +/* Decode a sam password hash into a password. The password hash is the + same method used to store passwords in the NT registry. The DES key + used is based on the RID of the user. */ + +void sam_pwd_hash(unsigned int rid, const uchar *in, uchar *out, int forw) +{ + uchar s[14]; + + s[0] = s[4] = s[8] = s[12] = (uchar)(rid & 0xFF); + s[1] = s[5] = s[9] = s[13] = (uchar)((rid >> 8) & 0xFF); + s[2] = s[6] = s[10] = (uchar)((rid >> 16) & 0xFF); + s[3] = s[7] = s[11] = (uchar)((rid >> 24) & 0xFF); + + des_crypt56(out, in, s, forw); + des_crypt56(out+8, in+8, s+7, forw); +} diff --git a/source3/libsmb/smbencrypt.c b/source3/libsmb/smbencrypt.c new file mode 100644 index 0000000000..0742976635 --- /dev/null +++ b/source3/libsmb/smbencrypt.c @@ -0,0 +1,898 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1998 + Modified by Jeremy Allison 1995. + Copyright (C) Jeremy Allison 1995-2000. + Copyright (C) Luke Kennethc Casson Leighton 1996-2000. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "byteorder.h" + +void SMBencrypt_hash(const uchar lm_hash[16], const uchar *c8, uchar p24[24]) +{ + uchar p21[21]; + + memset(p21,'\0',21); + memcpy(p21, lm_hash, 16); + + SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBencrypt_hash: lm#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif +} + +/* + This implements the X/Open SMB password encryption + It takes a password ('unix' string), a 8 byte "crypt key" + and puts 24 bytes of encrypted password into p24 + + Returns False if password must have been truncated to create LM hash +*/ + +bool SMBencrypt(const char *passwd, const uchar *c8, uchar p24[24]) +{ + bool ret; + uchar lm_hash[16]; + + ret = E_deshash(passwd, lm_hash); + SMBencrypt_hash(lm_hash, c8, p24); + return ret; +} + +/** + * Creates the MD4 Hash of the users password in NT UNICODE. + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with md4, caller allocated 16 byte buffer + */ + +void E_md4hash(const char *passwd, uchar p16[16]) +{ + int len; + smb_ucs2_t wpwd[129]; + + /* Password must be converted to NT unicode - null terminated. */ + push_ucs2(NULL, wpwd, (const char *)passwd, 256, STR_UNICODE|STR_NOALIGN|STR_TERMINATE); + /* Calculate length in bytes */ + len = strlen_w(wpwd) * sizeof(int16); + + mdfour(p16, (unsigned char *)wpwd, len); + ZERO_STRUCT(wpwd); +} + +/** + * Creates the MD5 Hash of a combination of 16 byte salt and 16 byte NT hash. + * @param 16 byte salt. + * @param 16 byte NT hash. + * @param 16 byte return hashed with md5, caller allocated 16 byte buffer + */ + +void E_md5hash(const uchar salt[16], const uchar nthash[16], uchar hash_out[16]) +{ + struct MD5Context tctx; + uchar array[32]; + + memset(hash_out, '\0', 16); + memcpy(array, salt, 16); + memcpy(&array[16], nthash, 16); + MD5Init(&tctx); + MD5Update(&tctx, array, 32); + MD5Final(hash_out, &tctx); +} + +/** + * Creates the DES forward-only Hash of the users password in DOS ASCII charset + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with DES, caller allocated 16 byte buffer + * @return False if password was > 14 characters, and therefore may be incorrect, otherwise True + * @note p16 is filled in regardless + */ + +bool E_deshash(const char *passwd, uchar p16[16]) +{ + bool ret = True; + fstring dospwd; + ZERO_STRUCT(dospwd); + + /* Password must be converted to DOS charset - null terminated, uppercase. */ + push_ascii(dospwd, passwd, sizeof(dospwd), STR_UPPER|STR_TERMINATE); + + /* Only the fisrt 14 chars are considered, password need not be null terminated. */ + E_P16((const unsigned char *)dospwd, p16); + + if (strlen(dospwd) > 14) { + ret = False; + } + + ZERO_STRUCT(dospwd); + + return ret; +} + +/** + * Creates the MD4 and DES (LM) Hash of the users password. + * MD4 is of the NT Unicode, DES is of the DOS UPPERCASE password. + * @param passwd password in 'unix' charset. + * @param nt_p16 return password hashed with md4, caller allocated 16 byte buffer + * @param p16 return password hashed with des, caller allocated 16 byte buffer + */ + +/* Does both the NT and LM owfs of a user's password */ +void nt_lm_owf_gen(const char *pwd, uchar nt_p16[16], uchar p16[16]) +{ + /* Calculate the MD4 hash (NT compatible) of the password */ + memset(nt_p16, '\0', 16); + E_md4hash(pwd, nt_p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, nt#\n")); + dump_data(120, (uint8 *)pwd, strlen(pwd)); + dump_data(100, nt_p16, 16); +#endif + + E_deshash(pwd, (uchar *)p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, lm#\n")); + dump_data(120, (uint8 *)pwd, strlen(pwd)); + dump_data(100, p16, 16); +#endif +} + +/* Does both the NTLMv2 owfs of a user's password */ +bool ntv2_owf_gen(const uchar owf[16], + const char *user_in, const char *domain_in, + bool upper_case_domain, /* Transform the domain into UPPER case */ + uchar kr_buf[16]) +{ + smb_ucs2_t *user; + smb_ucs2_t *domain; + + size_t user_byte_len; + size_t domain_byte_len; + + HMACMD5Context ctx; + + if (!push_ucs2_allocate(&user, user_in, &user_byte_len)) { + DEBUG(0, ("push_uss2_allocate() for user failed: %s\n", + strerror(errno))); + return False; + } + + if (!push_ucs2_allocate(&domain, domain_in, &domain_byte_len)) { + DEBUG(0, ("push_uss2_allocate() for domain failed: %s\n", + strerror(errno))); + SAFE_FREE(user); + return False; + } + + strupper_w(user); + + if (upper_case_domain) + strupper_w(domain); + + SMB_ASSERT(user_byte_len >= 2); + SMB_ASSERT(domain_byte_len >= 2); + + /* We don't want null termination */ + user_byte_len = user_byte_len - 2; + domain_byte_len = domain_byte_len - 2; + + hmac_md5_init_limK_to_64(owf, 16, &ctx); + hmac_md5_update((const unsigned char *)user, user_byte_len, &ctx); + hmac_md5_update((const unsigned char *)domain, domain_byte_len, &ctx); + hmac_md5_final(kr_buf, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("ntv2_owf_gen: user, domain, owfkey, kr\n")); + dump_data(100, (uint8 *)user, user_byte_len); + dump_data(100, (uint8 *)domain, domain_byte_len); + dump_data(100, (uint8 *)owf, 16); + dump_data(100, (uint8 *)kr_buf, 16); +#endif + + SAFE_FREE(user); + SAFE_FREE(domain); + return True; +} + +/* Does the des encryption from the NT or LM MD4 hash. */ +void SMBOWFencrypt(const uchar passwd[16], const uchar *c8, uchar p24[24]) +{ + uchar p21[21]; + + ZERO_STRUCT(p21); + + memcpy(p21, passwd, 16); + E_P24(p21, c8, p24); +} + +/* Does the des encryption from the FIRST 8 BYTES of the NT or LM MD4 hash. */ +void NTLMSSPOWFencrypt(const uchar passwd[8], const uchar *ntlmchalresp, uchar p24[24]) +{ + uchar p21[21]; + + memset(p21,'\0',21); + memcpy(p21, passwd, 8); + memset(p21 + 8, 0xbd, 8); + + E_P24(p21, ntlmchalresp, p24); +#ifdef DEBUG_PASSWORD + DEBUG(100,("NTLMSSPOWFencrypt: p21, c8, p24\n")); + dump_data(100, p21, 21); + dump_data(100, ntlmchalresp, 8); + dump_data(100, p24, 24); +#endif +} + + +/* Does the des encryption. */ + +void SMBNTencrypt_hash(const uchar nt_hash[16], uchar *c8, uchar *p24) +{ + uchar p21[21]; + + memset(p21,'\0',21); + memcpy(p21, nt_hash, 16); + SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBNTencrypt: nt#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif +} + +/* Does the NT MD4 hash then des encryption. Plaintext version of the above. */ + +void SMBNTencrypt(const char *passwd, uchar *c8, uchar *p24) +{ + uchar nt_hash[16]; + E_md4hash(passwd, nt_hash); + SMBNTencrypt_hash(nt_hash, c8, p24); +} + +/* Does the md5 encryption from the Key Response for NTLMv2. */ +void SMBOWFencrypt_ntv2(const uchar kr[16], + const DATA_BLOB *srv_chal, + const DATA_BLOB *cli_chal, + uchar resp_buf[16]) +{ + HMACMD5Context ctx; + + hmac_md5_init_limK_to_64(kr, 16, &ctx); + hmac_md5_update(srv_chal->data, srv_chal->length, &ctx); + hmac_md5_update(cli_chal->data, cli_chal->length, &ctx); + hmac_md5_final(resp_buf, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBOWFencrypt_ntv2: srv_chal, cli_chal, resp_buf\n")); + dump_data(100, srv_chal->data, srv_chal->length); + dump_data(100, cli_chal->data, cli_chal->length); + dump_data(100, resp_buf, 16); +#endif +} + +void SMBsesskeygen_ntv2(const uchar kr[16], + const uchar * nt_resp, uint8 sess_key[16]) +{ + /* a very nice, 128 bit, variable session key */ + + HMACMD5Context ctx; + + hmac_md5_init_limK_to_64(kr, 16, &ctx); + hmac_md5_update(nt_resp, 16, &ctx); + hmac_md5_final((unsigned char *)sess_key, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv2:\n")); + dump_data(100, sess_key, 16); +#endif +} + +void SMBsesskeygen_ntv1(const uchar kr[16], + const uchar * nt_resp, uint8 sess_key[16]) +{ + /* yes, this session key does not change - yes, this + is a problem - but it is 128 bits */ + + mdfour((unsigned char *)sess_key, kr, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv1:\n")); + dump_data(100, sess_key, 16); +#endif +} + +void SMBsesskeygen_lm_sess_key(const uchar lm_hash[16], + const uchar lm_resp[24], /* only uses 8 */ + uint8 sess_key[16]) +{ + uchar p24[24]; + uchar partial_lm_hash[16]; + + memcpy(partial_lm_hash, lm_hash, 8); + memset(partial_lm_hash + 8, 0xbd, 8); + + SMBOWFencrypt(partial_lm_hash, lm_resp, p24); + + memcpy(sess_key, p24, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_lmv1_jerry:\n")); + dump_data(100, sess_key, 16); +#endif +} + +DATA_BLOB NTLMv2_generate_names_blob(const char *hostname, + const char *domain) +{ + DATA_BLOB names_blob = data_blob_null; + + msrpc_gen(&names_blob, "aaa", + NTLMSSP_NAME_TYPE_DOMAIN, domain, + NTLMSSP_NAME_TYPE_SERVER, hostname, + 0, ""); + return names_blob; +} + +static DATA_BLOB NTLMv2_generate_client_data(const DATA_BLOB *names_blob) +{ + uchar client_chal[8]; + DATA_BLOB response = data_blob_null; + char long_date[8]; + + generate_random_buffer(client_chal, sizeof(client_chal)); + + put_long_date(long_date, time(NULL)); + + /* See http://www.ubiqx.org/cifs/SMB.html#SMB.8.5 */ + + msrpc_gen(&response, "ddbbdb", + 0x00000101, /* Header */ + 0, /* 'Reserved' */ + long_date, 8, /* Timestamp */ + client_chal, 8, /* client challenge */ + 0, /* Unknown */ + names_blob->data, names_blob->length); /* End of name list */ + + return response; +} + +static DATA_BLOB NTLMv2_generate_response(const uchar ntlm_v2_hash[16], + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob) +{ + uchar ntlmv2_response[16]; + DATA_BLOB ntlmv2_client_data; + DATA_BLOB final_response; + + /* NTLMv2 */ + /* generate some data to pass into the response function - including + the hostname and domain name of the server */ + ntlmv2_client_data = NTLMv2_generate_client_data(names_blob); + + /* Given that data, and the challenge from the server, generate a response */ + SMBOWFencrypt_ntv2(ntlm_v2_hash, server_chal, &ntlmv2_client_data, ntlmv2_response); + + final_response = data_blob(NULL, sizeof(ntlmv2_response) + ntlmv2_client_data.length); + + memcpy(final_response.data, ntlmv2_response, sizeof(ntlmv2_response)); + + memcpy(final_response.data+sizeof(ntlmv2_response), + ntlmv2_client_data.data, ntlmv2_client_data.length); + + data_blob_free(&ntlmv2_client_data); + + return final_response; +} + +static DATA_BLOB LMv2_generate_response(const uchar ntlm_v2_hash[16], + const DATA_BLOB *server_chal) +{ + uchar lmv2_response[16]; + DATA_BLOB lmv2_client_data = data_blob(NULL, 8); + DATA_BLOB final_response = data_blob(NULL, 24); + + /* LMv2 */ + /* client-supplied random data */ + generate_random_buffer(lmv2_client_data.data, lmv2_client_data.length); + + /* Given that data, and the challenge from the server, generate a response */ + SMBOWFencrypt_ntv2(ntlm_v2_hash, server_chal, &lmv2_client_data, lmv2_response); + memcpy(final_response.data, lmv2_response, sizeof(lmv2_response)); + + /* after the first 16 bytes is the random data we generated above, + so the server can verify us with it */ + memcpy(final_response.data+sizeof(lmv2_response), + lmv2_client_data.data, lmv2_client_data.length); + + data_blob_free(&lmv2_client_data); + + return final_response; +} + +bool SMBNTLMv2encrypt_hash(const char *user, const char *domain, const uchar nt_hash[16], + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *user_session_key) +{ + uchar ntlm_v2_hash[16]; + + /* We don't use the NT# directly. Instead we use it mashed up with + the username and domain. + This prevents username swapping during the auth exchange + */ + if (!ntv2_owf_gen(nt_hash, user, domain, False, ntlm_v2_hash)) { + return False; + } + + if (nt_response) { + *nt_response = NTLMv2_generate_response(ntlm_v2_hash, server_chal, + names_blob); + if (user_session_key) { + *user_session_key = data_blob(NULL, 16); + + /* The NTLMv2 calculations also provide a session key, for signing etc later */ + /* use only the first 16 bytes of nt_response for session key */ + SMBsesskeygen_ntv2(ntlm_v2_hash, nt_response->data, user_session_key->data); + } + } + + /* LMv2 */ + + if (lm_response) { + *lm_response = LMv2_generate_response(ntlm_v2_hash, server_chal); + } + + return True; +} + +/* Plaintext version of the above. */ + +bool SMBNTLMv2encrypt(const char *user, const char *domain, const char *password, + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *user_session_key) +{ + uchar nt_hash[16]; + E_md4hash(password, nt_hash); + + return SMBNTLMv2encrypt_hash(user, domain, nt_hash, + server_chal, + names_blob, + lm_response, nt_response, + user_session_key); +} + +/*********************************************************** + encode a password buffer with a unicode password. The buffer + is filled with random data to make it harder to attack. +************************************************************/ +bool encode_pw_buffer(uint8 buffer[516], const char *password, int string_flags) +{ + uchar new_pw[512]; + size_t new_pw_len; + + /* the incoming buffer can be any alignment. */ + string_flags |= STR_NOALIGN; + + new_pw_len = push_string(NULL, new_pw, + password, + sizeof(new_pw), string_flags); + + memcpy(&buffer[512 - new_pw_len], new_pw, new_pw_len); + + generate_random_buffer(buffer, 512 - new_pw_len); + + /* + * The length of the new password is in the last 4 bytes of + * the data buffer. + */ + SIVAL(buffer, 512, new_pw_len); + ZERO_STRUCT(new_pw); + return True; +} + + +/*********************************************************** + decode a password buffer + *new_pw_len is the length in bytes of the possibly mulitbyte + returned password including termination. +************************************************************/ + +bool decode_pw_buffer(TALLOC_CTX *ctx, + uint8 in_buffer[516], + char **pp_new_pwrd, + uint32 *new_pw_len, + int string_flags) +{ + int byte_len=0; + + *pp_new_pwrd = NULL; + *new_pw_len = 0; + + /* the incoming buffer can be any alignment. */ + string_flags |= STR_NOALIGN; + + /* + Warning !!! : This function is called from some rpc call. + The password IN the buffer may be a UNICODE string. + The password IN new_pwrd is an ASCII string + If you reuse that code somewhere else check first. + */ + + /* The length of the new password is in the last 4 bytes of the data buffer. */ + + byte_len = IVAL(in_buffer, 512); + +#ifdef DEBUG_PASSWORD + dump_data(100, in_buffer, 516); +#endif + + /* Password cannot be longer than the size of the password buffer */ + if ( (byte_len < 0) || (byte_len > 512)) { + DEBUG(0, ("decode_pw_buffer: incorrect password length (%d).\n", byte_len)); + DEBUG(0, ("decode_pw_buffer: check that 'encrypt passwords = yes'\n")); + return false; + } + + /* decode into the return buffer. */ + *new_pw_len = pull_string_talloc(ctx, + NULL, + 0, + pp_new_pwrd, + &in_buffer[512 - byte_len], + byte_len, + string_flags); + + if (!*pp_new_pwrd || *new_pw_len == 0) { + DEBUG(0, ("decode_pw_buffer: pull_string_talloc failed\n")); + return false; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100,("decode_pw_buffer: new_pwrd: ")); + dump_data(100, (uint8 *)*pp_new_pwrd, *new_pw_len); + DEBUG(100,("multibyte len:%d\n", *new_pw_len)); + DEBUG(100,("original char len:%d\n", byte_len/2)); +#endif + + return true; +} + +/*********************************************************** + Decode an arc4 encrypted password change buffer. +************************************************************/ + +void encode_or_decode_arc4_passwd_buffer(unsigned char pw_buf[532], const DATA_BLOB *psession_key) +{ + struct MD5Context tctx; + unsigned char key_out[16]; + + /* Confounder is last 16 bytes. */ + + MD5Init(&tctx); + MD5Update(&tctx, &pw_buf[516], 16); + MD5Update(&tctx, psession_key->data, psession_key->length); + MD5Final(key_out, &tctx); + /* arc4 with key_out. */ + SamOEMhash(pw_buf, key_out, 516); +} + +/*********************************************************** + Encrypt/Decrypt used for LSA secrets and trusted domain + passwords. +************************************************************/ + +void sess_crypt_blob(DATA_BLOB *out, const DATA_BLOB *in, const DATA_BLOB *session_key, int forward) +{ + int i, k; + + for (i=0,k=0; + i<in->length; + i += 8, k += 7) { + uint8 bin[8], bout[8], key[7]; + + memset(bin, 0, 8); + memcpy(bin, &in->data[i], MIN(8, in->length-i)); + + if (k + 7 > session_key->length) { + k = (session_key->length - k); + } + memcpy(key, &session_key->data[k], 7); + + des_crypt56(bout, bin, key, forward?1:0); + + memcpy(&out->data[i], bout, MIN(8, in->length-i)); + } +} + +/* Decrypts password-blob with session-key + * @param nt_hash NT hash for the session key + * @param data_in DATA_BLOB encrypted password + * + * Returns cleartext password in CH_UNIX + * Caller must free the returned string + */ + +char *decrypt_trustdom_secret(uint8_t nt_hash[16], DATA_BLOB *data_in) +{ + DATA_BLOB data_out, sess_key; + uint32_t length; + uint32_t version; + fstring cleartextpwd; + + if (!data_in || !nt_hash) + return NULL; + + /* hashed twice with md4 */ + mdfour(nt_hash, nt_hash, 16); + + /* 16-Byte session-key */ + sess_key = data_blob(nt_hash, 16); + if (sess_key.data == NULL) + return NULL; + + data_out = data_blob(NULL, data_in->length); + if (data_out.data == NULL) + return NULL; + + /* decrypt with des3 */ + sess_crypt_blob(&data_out, data_in, &sess_key, 0); + + /* 4 Byte length, 4 Byte version */ + length = IVAL(data_out.data, 0); + version = IVAL(data_out.data, 4); + + if (length > data_in->length - 8) { + DEBUG(0,("decrypt_trustdom_secret: invalid length (%d)\n", length)); + return NULL; + } + + if (version != 1) { + DEBUG(0,("decrypt_trustdom_secret: unknown version number (%d)\n", version)); + return NULL; + } + + rpcstr_pull(cleartextpwd, data_out.data + 8, sizeof(fstring), length, 0 ); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("decrypt_trustdom_secret: length is: %d, version is: %d, password is: %s\n", + length, version, cleartextpwd)); +#endif + + data_blob_free(&data_out); + data_blob_free(&sess_key); + + return SMB_STRDUP(cleartextpwd); + +} + +/* encode a wkssvc_PasswordBuffer: + * + * similar to samr_CryptPasswordEx. Different: 8byte confounder (instead of + * 16byte), confounder in front of the 516 byte buffer (instead of after that + * buffer), calling MD5Update() first with session_key and then with confounder + * (vice versa in samr) - Guenther */ + +void encode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + const char *pwd, + DATA_BLOB *session_key, + struct wkssvc_PasswordBuffer **pwd_buf) +{ + uint8_t buffer[516]; + struct MD5Context ctx; + struct wkssvc_PasswordBuffer *my_pwd_buf = NULL; + DATA_BLOB confounded_session_key; + int confounder_len = 8; + uint8_t confounder[8]; + + my_pwd_buf = talloc_zero(mem_ctx, struct wkssvc_PasswordBuffer); + if (!my_pwd_buf) { + return; + } + + confounded_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + encode_pw_buffer(buffer, pwd, STR_UNICODE); + + generate_random_buffer((uint8_t *)confounder, confounder_len); + + MD5Init(&ctx); + MD5Update(&ctx, session_key->data, session_key->length); + MD5Update(&ctx, confounder, confounder_len); + MD5Final(confounded_session_key.data, &ctx); + + SamOEMhashBlob(buffer, 516, &confounded_session_key); + + memcpy(&my_pwd_buf->data[0], confounder, confounder_len); + memcpy(&my_pwd_buf->data[8], buffer, 516); + + data_blob_free(&confounded_session_key); + + *pwd_buf = my_pwd_buf; +} + +WERROR decode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + struct wkssvc_PasswordBuffer *pwd_buf, + DATA_BLOB *session_key, + char **pwd) +{ + uint8_t buffer[516]; + struct MD5Context ctx; + uint32_t pwd_len; + + DATA_BLOB confounded_session_key; + + int confounder_len = 8; + uint8_t confounder[8]; + + *pwd = NULL; + + if (!pwd_buf) { + return WERR_BAD_PASSWORD; + } + + if (session_key->length != 16) { + DEBUG(10,("invalid session key\n")); + return WERR_BAD_PASSWORD; + } + + confounded_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + memcpy(&confounder, &pwd_buf->data[0], confounder_len); + memcpy(&buffer, &pwd_buf->data[8], 516); + + MD5Init(&ctx); + MD5Update(&ctx, session_key->data, session_key->length); + MD5Update(&ctx, confounder, confounder_len); + MD5Final(confounded_session_key.data, &ctx); + + SamOEMhashBlob(buffer, 516, &confounded_session_key); + + if (!decode_pw_buffer(mem_ctx, buffer, pwd, &pwd_len, STR_UNICODE)) { + data_blob_free(&confounded_session_key); + return WERR_BAD_PASSWORD; + } + + data_blob_free(&confounded_session_key); + + return WERR_OK; +} + +DATA_BLOB decrypt_drsuapi_blob(TALLOC_CTX *mem_ctx, + const DATA_BLOB *session_key, + bool rcrypt, + uint32_t rid, + const DATA_BLOB *buffer) +{ + DATA_BLOB confounder; + DATA_BLOB enc_buffer; + + struct MD5Context md5; + uint8_t _enc_key[16]; + DATA_BLOB enc_key; + + DATA_BLOB dec_buffer; + + uint32_t crc32_given; + uint32_t crc32_calc; + DATA_BLOB checked_buffer; + + DATA_BLOB plain_buffer; + + /* + * the combination "c[3] s[1] e[1] d[0]..." + * was successful!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + + /* + * the first 16 bytes at the beginning are the confounder + * followed by the 4 byte crc32 checksum + */ + if (buffer->length < 20) { + return data_blob_const(NULL, 0); + } + confounder = data_blob_const(buffer->data, 16); + enc_buffer = data_blob_const(buffer->data + 16, buffer->length - 16); + + /* + * build the encryption key md5 over the session key followed + * by the confounder + * + * here the gensec session key is used and + * not the dcerpc ncacn_ip_tcp "SystemLibraryDTC" key! + */ + enc_key = data_blob_const(_enc_key, sizeof(_enc_key)); + MD5Init(&md5); + MD5Update(&md5, session_key->data, session_key->length); + MD5Update(&md5, confounder.data, confounder.length); + MD5Final(enc_key.data, &md5); + + /* + * copy the encrypted buffer part and + * decrypt it using the created encryption key using arcfour + */ + dec_buffer = data_blob_talloc(mem_ctx, enc_buffer.data, enc_buffer.length); + if (!dec_buffer.data) { + return data_blob_const(NULL, 0); + } + SamOEMhashBlob(dec_buffer.data, dec_buffer.length, &enc_key); + + /* + * the first 4 byte are the crc32 checksum + * of the remaining bytes + */ + crc32_given = IVAL(dec_buffer.data, 0); + crc32_calc = crc32_calc_buffer((const char *)dec_buffer.data + 4 , dec_buffer.length - 4); + if (crc32_given != crc32_calc) { + DEBUG(1,("CRC32: given[0x%08X] calc[0x%08X]\n", + crc32_given, crc32_calc)); + return data_blob_const(NULL, 0); + } + checked_buffer = data_blob_talloc(mem_ctx, dec_buffer.data + 4, dec_buffer.length - 4); + if (!checked_buffer.data) { + return data_blob_const(NULL, 0); + } + + /* + * some attributes seem to be in a usable form after this decryption + * (supplementalCredentials, priorValue, currentValue, trustAuthOutgoing, + * trustAuthIncoming, initialAuthOutgoing, initialAuthIncoming) + * At least supplementalCredentials contains plaintext + * like "Primary:Kerberos" (in unicode form) + * + * some attributes seem to have some additional encryption + * dBCSPwd, unicodePwd, ntPwdHistory, lmPwdHistory + * + * it's the sam_rid_crypt() function, as the value is constant, + * so it doesn't depend on sessionkeys. + */ + if (rcrypt) { + uint32_t i, num_hashes; + + if ((checked_buffer.length % 16) != 0) { + return data_blob_const(NULL, 0); + } + + plain_buffer = data_blob_talloc(mem_ctx, checked_buffer.data, checked_buffer.length); + if (!plain_buffer.data) { + return data_blob_const(NULL, 0); + } + + num_hashes = plain_buffer.length / 16; + for (i = 0; i < num_hashes; i++) { + uint32_t offset = i * 16; + sam_pwd_hash(rid, checked_buffer.data + offset, plain_buffer.data + offset, 0); + } + } else { + plain_buffer = checked_buffer; + } + + return plain_buffer; +} + + diff --git a/source3/libsmb/smberr.c b/source3/libsmb/smberr.c new file mode 100644 index 0000000000..f4a13983f0 --- /dev/null +++ b/source3/libsmb/smberr.c @@ -0,0 +1,266 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Andrew Tridgell 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 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +/* error code stuff - put together by Merik Karman + merik@blackadder.dsh.oz.au */ + + +/* There is a big list of error codes and their meanings at: + + http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/errlist_7oz7.asp + + and if you don't like MSDN try: + + http://www.siris.gr/computers/library/error.htm + +*/ + +typedef const struct +{ + const char *name; + int code; + const char *message; +} err_code_struct; + +/* Dos Error Messages */ +err_code_struct dos_msgs[] = { + {"ERRbadfunc",ERRbadfunc,"Invalid function."}, + {"ERRbadfile",ERRbadfile,"File not found."}, + {"ERRbadpath",ERRbadpath,"Directory invalid."}, + {"ERRnofids",ERRnofids,"No file descriptors available"}, + {"ERRnoaccess",ERRnoaccess,"Access denied."}, + {"ERRbadfid",ERRbadfid,"Invalid file handle."}, + {"ERRbadmcb",ERRbadmcb,"Memory control blocks destroyed."}, + {"ERRnomem",ERRnomem,"Insufficient server memory to perform the requested function."}, + {"ERRbadmem",ERRbadmem,"Invalid memory block address."}, + {"ERRbadenv",ERRbadenv,"Invalid environment."}, + {"ERRbadformat",11,"Invalid format."}, + {"ERRbadaccess",ERRbadaccess,"Invalid open mode."}, + {"ERRbaddata",ERRbaddata,"Invalid data."}, + {"ERRres",ERRres,"reserved."}, + {"ERRbaddrive",ERRbaddrive,"Invalid drive specified."}, + {"ERRremcd",ERRremcd,"A Delete Directory request attempted to remove the server's current directory."}, + {"ERRdiffdevice",ERRdiffdevice,"Not same device."}, + {"ERRnofiles",ERRnofiles,"A File Search command can find no more files matching the specified criteria."}, + {"ERRbadshare",ERRbadshare,"The sharing mode specified for an Open conflicts with existing FIDs on the file."}, + {"ERRlock",ERRlock,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."}, + {"ERRunsup", ERRunsup, "The operation is unsupported"}, + {"ERRnosuchshare", ERRnosuchshare, "You specified an invalid share name"}, + {"ERRfilexists",ERRfilexists,"The file named in a Create Directory, Make New File or Link request already exists."}, + {"ERRinvalidname",ERRinvalidname, "Invalid name"}, + {"ERRbadpipe",ERRbadpipe,"Pipe invalid."}, + {"ERRpipebusy",ERRpipebusy,"All instances of the requested pipe are busy."}, + {"ERRpipeclosing",ERRpipeclosing,"Pipe close in progress."}, + {"ERRnotconnected",ERRnotconnected,"No process on other end of pipe."}, + {"ERRmoredata",ERRmoredata,"There is more data to be returned."}, + {"ERRinvgroup",ERRinvgroup,"Invalid workgroup (try the -W option)"}, + {"ERRlogonfailure",ERRlogonfailure,"Logon failure"}, + {"ERRdiskfull",ERRdiskfull,"Disk full"}, + {"ERRgeneral",ERRgeneral, "General failure"}, + {"ERRbaddirectory", ERRbaddirectory, "Bad directory name"}, + {"ERRunknownlevel",ERRunknownlevel, "Unknown info level"}, + {NULL,-1,NULL}}; + +/* Server Error Messages */ +err_code_struct server_msgs[] = { + {"ERRerror",1,"Non-specific error code."}, + {"ERRbadpw",2,"Bad password - name/password pair in a Tree Connect or Session Setup are invalid."}, + {"ERRbadtype",3,"reserved."}, + {"ERRaccess",4,"The requester does not have the necessary access rights within the specified context for the requested function. The context is defined by the TID or the UID."}, + {"ERRinvnid",5,"The tree ID (TID) specified in a command was invalid."}, + {"ERRinvnetname",6,"Invalid network name in tree connect."}, + {"ERRinvdevice",7,"Invalid device - printer request made to non-printer connection or non-printer request made to printer connection."}, + {"ERRqfull",49,"Print queue full (files) -- returned by open print file."}, + {"ERRqtoobig",50,"Print queue full -- no space."}, + {"ERRqeof",51,"EOF on print queue dump."}, + {"ERRinvpfid",52,"Invalid print file FID."}, + {"ERRsmbcmd",64,"The server did not recognize the command received."}, + {"ERRsrverror",65,"The server encountered an internal error, e.g., system file unavailable."}, + {"ERRfilespecs",67,"The file handle (FID) and pathname parameters contained an invalid combination of values."}, + {"ERRreserved",68,"reserved."}, + {"ERRbadpermits",69,"The access permissions specified for a file or directory are not a valid combination. The server cannot set the requested attribute."}, + {"ERRreserved",70,"reserved."}, + {"ERRsetattrmode",71,"The attribute mode in the Set File Attribute request is invalid."}, + {"ERRpaused",81,"Server is paused."}, + {"ERRmsgoff",82,"Not receiving messages."}, + {"ERRnoroom",83,"No room to buffer message."}, + {"ERRrmuns",87,"Too many remote user names."}, + {"ERRtimeout",88,"Operation timed out."}, + {"ERRnoresource",89,"No resources currently available for request."}, + {"ERRtoomanyuids",90,"Too many UIDs active on this session."}, + {"ERRbaduid",91,"The UID is not known as a valid ID on this session."}, + {"ERRusempx",250,"Temp unable to support Raw, use MPX mode."}, + {"ERRusestd",251,"Temp unable to support Raw, use standard read/write."}, + {"ERRcontmpx",252,"Continue in MPX mode."}, + {"ERRreserved",253,"reserved."}, + {"ERRreserved",254,"reserved."}, + {"ERRnosupport",0xFFFF,"Function not supported."}, + {NULL,-1,NULL}}; + +/* Hard Error Messages */ +err_code_struct hard_msgs[] = { + {"ERRnowrite",19,"Attempt to write on write-protected diskette."}, + {"ERRbadunit",20,"Unknown unit."}, + {"ERRnotready",21,"Drive not ready."}, + {"ERRbadcmd",22,"Unknown command."}, + {"ERRdata",23,"Data error (CRC)."}, + {"ERRbadreq",24,"Bad request structure length."}, + {"ERRseek",25 ,"Seek error."}, + {"ERRbadmedia",26,"Unknown media type."}, + {"ERRbadsector",27,"Sector not found."}, + {"ERRnopaper",28,"Printer out of paper."}, + {"ERRwrite",29,"Write fault."}, + {"ERRread",30,"Read fault."}, + {"ERRgeneral",31,"General failure."}, + {"ERRbadshare",32,"An open conflicts with an existing open."}, + {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."}, + {"ERRwrongdisk",34,"The wrong disk was found in a drive."}, + {"ERRFCBUnavail",35,"No FCBs are available to process request."}, + {"ERRsharebufexc",36,"A sharing buffer has been exceeded."}, + {NULL,-1,NULL}}; + + +const struct +{ + int code; + const char *e_class; + err_code_struct *err_msgs; +} err_classes[] = { + {0,"SUCCESS",NULL}, + {0x01,"ERRDOS",dos_msgs}, + {0x02,"ERRSRV",server_msgs}, + {0x03,"ERRHRD",hard_msgs}, + {0x04,"ERRXOS",NULL}, + {0xE1,"ERRRMX1",NULL}, + {0xE2,"ERRRMX2",NULL}, + {0xE3,"ERRRMX3",NULL}, + {0xFF,"ERRCMD",NULL}, + {-1,NULL,NULL}}; + + +/**************************************************************************** +return a SMB error name from a class and code +****************************************************************************/ +const char *smb_dos_err_name(uint8 e_class, uint16 num) +{ + char *result; + int i,j; + + for (i=0;err_classes[i].e_class;i++) + if (err_classes[i].code == e_class) { + if (err_classes[i].err_msgs) { + err_code_struct *err = err_classes[i].err_msgs; + for (j=0;err[j].name;j++) + if (num == err[j].code) { + return err[j].name; + } + } + result = talloc_asprintf(talloc_tos(), "%d", num); + SMB_ASSERT(result != NULL); + return result; + } + + result = talloc_asprintf(talloc_tos(), "Error: Unknown error class " + "(%d,%d)", e_class,num); + SMB_ASSERT(result != NULL); + return result; +} + +/* Return a string for a DOS error */ + +const char *get_dos_error_msg(WERROR result) +{ + uint16 errnum; + + errnum = W_ERROR_V(result); + + return smb_dos_err_name(ERRDOS, errnum); +} + +/**************************************************************************** +return a SMB error class name as a string. +****************************************************************************/ +const char *smb_dos_err_class(uint8 e_class) +{ + char *result; + int i; + + for (i=0;err_classes[i].e_class;i++) { + if (err_classes[i].code == e_class) { + return err_classes[i].e_class; + } + } + + result = talloc_asprintf(talloc_tos(), "Error: Unknown class (%d)", + e_class); + SMB_ASSERT(result != NULL); + return result; +} + +/**************************************************************************** +return a SMB string from an SMB buffer +****************************************************************************/ +char *smb_dos_errstr(char *inbuf) +{ + char *result; + int e_class = CVAL(inbuf,smb_rcls); + int num = SVAL(inbuf,smb_err); + int i,j; + + for (i=0;err_classes[i].e_class;i++) + if (err_classes[i].code == e_class) { + if (err_classes[i].err_msgs) { + err_code_struct *err = err_classes[i].err_msgs; + for (j=0;err[j].name;j++) + if (num == err[j].code) { + if (DEBUGLEVEL > 0) + result = talloc_asprintf( + talloc_tos(), "%s - %s (%s)", + err_classes[i].e_class, + err[j].name,err[j].message); + else + result = talloc_asprintf( + talloc_tos(), "%s - %s", + err_classes[i].e_class, + err[j].name); + goto done; + } + } + + result = talloc_asprintf(talloc_tos(), "%s - %d", + err_classes[i].e_class, num); + goto done; + } + + result = talloc_asprintf(talloc_tos(), "Error: Unknown error (%d,%d)", + e_class, num); + done: + SMB_ASSERT(result != NULL); + return result; +} + +/***************************************************************************** +map a unix errno to a win32 error + *****************************************************************************/ +WERROR map_werror_from_unix(int error) +{ + NTSTATUS status = map_nt_error_from_unix(error); + return ntstatus_to_werror(status); +} diff --git a/source3/libsmb/spnego.c b/source3/libsmb/spnego.c new file mode 100644 index 0000000000..57b2d8060b --- /dev/null +++ b/source3/libsmb/spnego.c @@ -0,0 +1,346 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static bool read_negTokenInit(ASN1_DATA *asn1, negTokenInit_t *token) +{ + ZERO_STRUCTP(token); + + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { + int i; + + switch (asn1->data[asn1->ofs]) { + /* Read mechTypes */ + case ASN1_CONTEXT(0): + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + token->mechTypes = SMB_MALLOC_P(const char *); + for (i = 0; !asn1->has_error && + 0 < asn1_tag_remaining(asn1); i++) { + char *p_oid = NULL; + token->mechTypes = + SMB_REALLOC_ARRAY(token->mechTypes, const char *, i + 2); + if (!token->mechTypes) { + asn1->has_error = True; + return False; + } + asn1_read_OID(asn1, &p_oid); + token->mechTypes[i] = p_oid; + } + token->mechTypes[i] = NULL; + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + break; + /* Read reqFlags */ + case ASN1_CONTEXT(1): + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_read_Integer(asn1, &token->reqFlags); + token->reqFlags |= SPNEGO_REQ_FLAG; + asn1_end_tag(asn1); + break; + /* Read mechToken */ + case ASN1_CONTEXT(2): + asn1_start_tag(asn1, ASN1_CONTEXT(2)); + asn1_read_OctetString(asn1, &token->mechToken); + asn1_end_tag(asn1); + break; + /* Read mecListMIC */ + case ASN1_CONTEXT(3): + asn1_start_tag(asn1, ASN1_CONTEXT(3)); + if (asn1->data[asn1->ofs] == ASN1_OCTET_STRING) { + asn1_read_OctetString(asn1, + &token->mechListMIC); + } else { + /* RFC 2478 says we have an Octet String here, + but W2k sends something different... */ + char *mechListMIC; + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_read_GeneralString(asn1, &mechListMIC); + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + token->mechListMIC = + data_blob(mechListMIC, strlen(mechListMIC)); + SAFE_FREE(mechListMIC); + } + asn1_end_tag(asn1); + break; + default: + asn1->has_error = True; + break; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static bool write_negTokenInit(ASN1_DATA *asn1, negTokenInit_t *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + /* Write mechTypes */ + if (token->mechTypes && *token->mechTypes) { + int i; + + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + for (i = 0; token->mechTypes[i]; i++) { + asn1_write_OID(asn1, token->mechTypes[i]); + } + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + } + + /* write reqFlags */ + if (token->reqFlags & SPNEGO_REQ_FLAG) { + int flags = token->reqFlags & ~SPNEGO_REQ_FLAG; + + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_Integer(asn1, flags); + asn1_pop_tag(asn1); + } + + /* write mechToken */ + if (token->mechToken.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(2)); + asn1_write_OctetString(asn1, token->mechToken.data, + token->mechToken.length); + asn1_pop_tag(asn1); + } + + /* write mechListMIC */ + if (token->mechListMIC.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(3)); +#if 0 + /* This is what RFC 2478 says ... */ + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); +#else + /* ... but unfortunately this is what Windows + sends/expects */ + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_GENERAL_STRING); + asn1_write(asn1, token->mechListMIC.data, + token->mechListMIC.length); + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); +#endif + asn1_pop_tag(asn1); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +static bool read_negTokenTarg(ASN1_DATA *asn1, negTokenTarg_t *token) +{ + ZERO_STRUCTP(token); + + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { + switch (asn1->data[asn1->ofs]) { + case ASN1_CONTEXT(0): + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_ENUMERATED); + asn1_read_uint8(asn1, &token->negResult); + asn1_end_tag(asn1); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(1): + asn1_start_tag(asn1, ASN1_CONTEXT(1)); + asn1_read_OID(asn1, &token->supportedMech); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(2): + asn1_start_tag(asn1, ASN1_CONTEXT(2)); + asn1_read_OctetString(asn1, &token->responseToken); + asn1_end_tag(asn1); + break; + case ASN1_CONTEXT(3): + asn1_start_tag(asn1, ASN1_CONTEXT(3)); + asn1_read_OctetString(asn1, &token->mechListMIC); + asn1_end_tag(asn1); + break; + default: + asn1->has_error = True; + break; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static bool write_negTokenTarg(ASN1_DATA *asn1, negTokenTarg_t *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_write_enumerated(asn1, token->negResult); + asn1_pop_tag(asn1); + + if (token->supportedMech) { + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_OID(asn1, token->supportedMech); + asn1_pop_tag(asn1); + } + + if (token->responseToken.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(2)); + asn1_write_OctetString(asn1, token->responseToken.data, + token->responseToken.length); + asn1_pop_tag(asn1); + } + + if (token->mechListMIC.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(3)); + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); + asn1_pop_tag(asn1); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +ssize_t read_spnego_data(DATA_BLOB data, SPNEGO_DATA *token) +{ + ASN1_DATA asn1; + ssize_t ret = -1; + + ZERO_STRUCTP(token); + ZERO_STRUCT(asn1); + asn1_load(&asn1, data); + + switch (asn1.data[asn1.ofs]) { + case ASN1_APPLICATION(0): + asn1_start_tag(&asn1, ASN1_APPLICATION(0)); + asn1_check_OID(&asn1, OID_SPNEGO); + if (read_negTokenInit(&asn1, &token->negTokenInit)) { + token->type = SPNEGO_NEG_TOKEN_INIT; + } + asn1_end_tag(&asn1); + break; + case ASN1_CONTEXT(1): + if (read_negTokenTarg(&asn1, &token->negTokenTarg)) { + token->type = SPNEGO_NEG_TOKEN_TARG; + } + break; + default: + break; + } + + if (!asn1.has_error) ret = asn1.ofs; + asn1_free(&asn1); + + return ret; +} + +ssize_t write_spnego_data(DATA_BLOB *blob, SPNEGO_DATA *spnego) +{ + ASN1_DATA asn1; + ssize_t ret = -1; + + ZERO_STRUCT(asn1); + + switch (spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + asn1_push_tag(&asn1, ASN1_APPLICATION(0)); + asn1_write_OID(&asn1, OID_SPNEGO); + write_negTokenInit(&asn1, &spnego->negTokenInit); + asn1_pop_tag(&asn1); + break; + case SPNEGO_NEG_TOKEN_TARG: + write_negTokenTarg(&asn1, &spnego->negTokenTarg); + break; + default: + asn1.has_error = True; + break; + } + + if (!asn1.has_error) { + *blob = data_blob(asn1.data, asn1.length); + ret = asn1.ofs; + } + asn1_free(&asn1); + + return ret; +} + +bool free_spnego_data(SPNEGO_DATA *spnego) +{ + bool ret = True; + + if (!spnego) goto out; + + switch(spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + if (spnego->negTokenInit.mechTypes) { + int i; + for (i = 0; spnego->negTokenInit.mechTypes[i]; i++) { + free(CONST_DISCARD(char *,spnego->negTokenInit.mechTypes[i])); + } + free(spnego->negTokenInit.mechTypes); + } + data_blob_free(&spnego->negTokenInit.mechToken); + data_blob_free(&spnego->negTokenInit.mechListMIC); + break; + case SPNEGO_NEG_TOKEN_TARG: + if (spnego->negTokenTarg.supportedMech) { + free(spnego->negTokenTarg.supportedMech); + } + data_blob_free(&spnego->negTokenTarg.responseToken); + data_blob_free(&spnego->negTokenTarg.mechListMIC); + break; + default: + ret = False; + break; + } + ZERO_STRUCTP(spnego); +out: + return ret; +} diff --git a/source3/libsmb/trustdom_cache.c b/source3/libsmb/trustdom_cache.c new file mode 100644 index 0000000000..6755de3814 --- /dev/null +++ b/source3/libsmb/trustdom_cache.c @@ -0,0 +1,338 @@ +/* + Unix SMB/CIFS implementation. + + Trusted domain names cache on top of gencache. + + Copyright (C) Rafal Szczesniak 2002 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_ALL /* there's no proper class yet */ + +#define TDOMKEY_FMT "TDOM/%s" +#define TDOMTSKEY "TDOMCACHE/TIMESTAMP" + + +/** + * @file trustdom_cache.c + * + * Implementation of trusted domain names cache useful when + * samba acts as domain member server. In such case, caching + * domain names currently trusted gives a performance gain + * because there's no need to query PDC each time we need + * list of trusted domains + **/ + + +/** + * Initialise trustdom name caching system. Call gencache + * initialisation routine to perform necessary activities. + * + * @return true upon successful cache initialisation or + * false if cache init failed + **/ + +bool trustdom_cache_enable(void) +{ + /* Init trustdom cache by calling gencache initialisation */ + if (!gencache_init()) { + DEBUG(2, ("trustdomcache_enable: Couldn't initialise trustdom cache on top of gencache.\n")); + return False; + } + + return True; +} + + +/** + * Shutdown trustdom name caching system. Calls gencache + * shutdown function. + * + * @return true upon successful cache close or + * false if it failed + **/ + +bool trustdom_cache_shutdown(void) +{ + /* Close trustdom cache by calling gencache shutdown */ + if (!gencache_shutdown()) { + DEBUG(2, ("trustdomcache_shutdown: Couldn't shutdown trustdom cache on top of gencache.\n")); + return False; + } + + return True; +} + + +/** + * Form up trustdom name key. It is based only + * on domain name now. + * + * @param name trusted domain name + * @return cache key for use in gencache mechanism + **/ + +static char* trustdom_cache_key(const char* name) +{ + char* keystr = NULL; + asprintf_strupper_m(&keystr, TDOMKEY_FMT, name); + + return keystr; +} + + +/** + * Store trusted domain in gencache as the domain name (key) + * and trusted domain's SID (value) + * + * @param name trusted domain name + * @param alt_name alternative trusted domain name (used in ADS domains) + * @param sid trusted domain's SID + * @param timeout cache entry expiration time + * @return true upon successful value storing or + * false if store attempt failed + **/ + +bool trustdom_cache_store(char* name, char* alt_name, const DOM_SID *sid, + time_t timeout) +{ + char *key, *alt_key; + fstring sid_string; + bool ret; + + /* + * we use gecache call to avoid annoying debug messages + * about initialised trustdom + */ + if (!gencache_init()) + return False; + + DEBUG(5, ("trustdom_store: storing SID %s of domain %s\n", + sid_string_dbg(sid), name)); + + key = trustdom_cache_key(name); + alt_key = alt_name ? trustdom_cache_key(alt_name) : NULL; + + /* Generate string representation domain SID */ + sid_to_fstring(sid_string, sid); + + /* + * try to put the names in the cache + */ + if (alt_key) { + ret = gencache_set(alt_key, sid_string, timeout); + if ( ret ) { + ret = gencache_set(key, sid_string, timeout); + } + SAFE_FREE(alt_key); + SAFE_FREE(key); + return ret; + } + + ret = gencache_set(key, sid_string, timeout); + SAFE_FREE(key); + return ret; +} + + +/** + * Fetch trusted domain's SID from the gencache. + * This routine can also be used to check whether given + * domain is currently trusted one. + * + * @param name trusted domain name + * @param sid trusted domain's SID to be returned + * @return true if entry is found or + * false if has expired/doesn't exist + **/ + +bool trustdom_cache_fetch(const char* name, DOM_SID* sid) +{ + char *key = NULL, *value = NULL; + time_t timeout; + + /* init the cache */ + if (!gencache_init()) + return False; + + /* exit now if null pointers were passed as they're required further */ + if (!sid) + return False; + + /* prepare a key and get the value */ + key = trustdom_cache_key(name); + if (!key) + return False; + + if (!gencache_get(key, &value, &timeout)) { + DEBUG(5, ("no entry for trusted domain %s found.\n", name)); + SAFE_FREE(key); + return False; + } else { + SAFE_FREE(key); + DEBUG(5, ("trusted domain %s found (%s)\n", name, value)); + } + + /* convert sid string representation into DOM_SID structure */ + if(! string_to_sid(sid, value)) { + sid = NULL; + SAFE_FREE(value); + return False; + } + + SAFE_FREE(value); + return True; +} + + +/******************************************************************* + fetch the timestamp from the last update +*******************************************************************/ + +uint32 trustdom_cache_fetch_timestamp( void ) +{ + char *value = NULL; + time_t timeout; + uint32 timestamp; + + /* init the cache */ + if (!gencache_init()) + return False; + + if (!gencache_get(TDOMTSKEY, &value, &timeout)) { + DEBUG(5, ("no timestamp for trusted domain cache located.\n")); + SAFE_FREE(value); + return 0; + } + + timestamp = atoi(value); + + SAFE_FREE(value); + return timestamp; +} + +/******************************************************************* + store the timestamp from the last update +*******************************************************************/ + +bool trustdom_cache_store_timestamp( uint32 t, time_t timeout ) +{ + fstring value; + + /* init the cache */ + if (!gencache_init()) + return False; + + fstr_sprintf(value, "%d", t ); + + if (!gencache_set(TDOMTSKEY, value, timeout)) { + DEBUG(5, ("failed to set timestamp for trustdom_cache\n")); + return False; + } + + return True; +} + + +/** + * Delete single trustdom entry. Look at the + * gencache_iterate definition. + * + **/ + +static void flush_trustdom_name(const char* key, const char *value, time_t timeout, void* dptr) +{ + gencache_del(key); + DEBUG(5, ("Deleting entry %s\n", key)); +} + + +/** + * Flush all the trusted domains entries from the cache. + **/ + +void trustdom_cache_flush(void) +{ + if (!gencache_init()) + return; + + /* + * iterate through each TDOM cache's entry and flush it + * by flush_trustdom_name function + */ + gencache_iterate(flush_trustdom_name, NULL, trustdom_cache_key("*")); + DEBUG(5, ("Trusted domains cache flushed\n")); +} + +/******************************************************************** + update the trustdom_cache if needed +********************************************************************/ +#define TRUSTDOM_UPDATE_INTERVAL 600 + +void update_trustdom_cache( void ) +{ + char **domain_names; + DOM_SID *dom_sids; + uint32 num_domains; + uint32 last_check; + int time_diff; + TALLOC_CTX *mem_ctx = NULL; + time_t now = time(NULL); + int i; + + /* get the timestamp. We have to initialise it if the last timestamp == 0 */ + if ( (last_check = trustdom_cache_fetch_timestamp()) == 0 ) + trustdom_cache_store_timestamp(0, now+TRUSTDOM_UPDATE_INTERVAL); + + time_diff = (int) (now - last_check); + + if ( (time_diff > 0) && (time_diff < TRUSTDOM_UPDATE_INTERVAL) ) { + DEBUG(10,("update_trustdom_cache: not time to update trustdom_cache yet\n")); + return; + } + + /* note that we don't lock the timestamp. This prevents this + smbd from blocking all other smbd daemons while we + enumerate the trusted domains */ + trustdom_cache_store_timestamp(now, now+TRUSTDOM_UPDATE_INTERVAL); + + if ( !(mem_ctx = talloc_init("update_trustdom_cache")) ) { + DEBUG(0,("update_trustdom_cache: talloc_init() failed!\n")); + goto done; + } + + /* get the domains and store them */ + + if ( enumerate_domain_trusts(mem_ctx, lp_workgroup(), &domain_names, + &num_domains, &dom_sids)) { + for ( i=0; i<num_domains; i++ ) { + trustdom_cache_store( domain_names[i], NULL, &dom_sids[i], + now+TRUSTDOM_UPDATE_INTERVAL); + } + } else { + /* we failed to fetch the list of trusted domains - restore the old + timestamp */ + trustdom_cache_store_timestamp(last_check, + last_check+TRUSTDOM_UPDATE_INTERVAL); + } + +done: + talloc_destroy( mem_ctx ); + + return; +} diff --git a/source3/libsmb/trusts_util.c b/source3/libsmb/trusts_util.c new file mode 100644 index 0000000000..08a49930b4 --- /dev/null +++ b/source3/libsmb/trusts_util.c @@ -0,0 +1,283 @@ +/* + * Unix SMB/CIFS implementation. + * Routines to operate on various trust relationships + * Copyright (C) Andrew Bartlett 2001 + * Copyright (C) Rafal Szczesniak 2003 + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +/********************************************************* + Change the domain password on the PDC. + + Just changes the password betwen the two values specified. + + Caller must have the cli connected to the netlogon pipe + already. +**********************************************************/ + +static NTSTATUS just_change_the_password(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx, + const unsigned char orig_trust_passwd_hash[16], + const char *new_trust_pwd_cleartext, + const unsigned char new_trust_passwd_hash[16], + uint32 sec_channel_type) +{ + NTSTATUS result; + uint32_t neg_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + + result = rpccli_netlogon_setup_creds(cli, + cli->desthost, /* server name */ + lp_workgroup(), /* domain */ + global_myname(), /* client name */ + global_myname(), /* machine account name */ + orig_trust_passwd_hash, + sec_channel_type, + &neg_flags); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3,("just_change_the_password: unable to setup creds (%s)!\n", + nt_errstr(result))); + return result; + } + + if (neg_flags & NETLOGON_NEG_PASSWORD_SET2) { + + struct netr_Authenticator clnt_creds, srv_cred; + struct netr_CryptPassword new_password; + struct samr_CryptPassword password_buf; + + netlogon_creds_client_step(cli->dc, &clnt_creds); + + encode_pw_buffer(password_buf.data, new_trust_pwd_cleartext, STR_UNICODE); + + SamOEMhash(password_buf.data, cli->dc->sess_key, 516); + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + result = rpccli_netr_ServerPasswordSet2(cli, mem_ctx, + cli->dc->remote_machine, + cli->dc->mach_acct, + sec_channel_type, + global_myname(), + &clnt_creds, + &srv_cred, + &new_password); + + /* Always check returned credentials. */ + if (!netlogon_creds_client_check(cli->dc, &srv_cred.cred)) { + DEBUG(0,("rpccli_netr_ServerPasswordSet2: " + "credentials chain check failed\n")); + return NT_STATUS_ACCESS_DENIED; + } + + } else { + + struct netr_Authenticator clnt_creds, srv_cred; + struct samr_Password new_password; + + netlogon_creds_client_step(cli->dc, &clnt_creds); + + cred_hash3(new_password.hash, + new_trust_passwd_hash, + cli->dc->sess_key, 1); + + result = rpccli_netr_ServerPasswordSet(cli, mem_ctx, + cli->dc->remote_machine, + cli->dc->mach_acct, + sec_channel_type, + global_myname(), + &clnt_creds, + &srv_cred, + &new_password); + + /* Always check returned credentials. */ + if (!netlogon_creds_client_check(cli->dc, &srv_cred.cred)) { + DEBUG(0,("rpccli_netr_ServerPasswordSet: " + "credentials chain check failed\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("just_change_the_password: unable to change password (%s)!\n", + nt_errstr(result))); + } + return result; +} + +/********************************************************* + Change the domain password on the PDC. + Store the password ourselves, but use the supplied password + Caller must have already setup the connection to the NETLOGON pipe +**********************************************************/ + +NTSTATUS trust_pw_change_and_store_it(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx, + const char *domain, + unsigned char orig_trust_passwd_hash[16], + uint32 sec_channel_type) +{ + unsigned char new_trust_passwd_hash[16]; + char *new_trust_passwd; + char *str; + NTSTATUS nt_status; + + /* Create a random machine account password */ + str = generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH); + + if ((new_trust_passwd = talloc_strdup(mem_ctx, str)) == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + return NT_STATUS_NO_MEMORY; + } + + E_md4hash(new_trust_passwd, new_trust_passwd_hash); + + nt_status = just_change_the_password(cli, mem_ctx, + orig_trust_passwd_hash, + new_trust_passwd, + new_trust_passwd_hash, + sec_channel_type); + + if (NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("%s : trust_pw_change_and_store_it: Changed password.\n", + current_timestring(debug_ctx(), False))); + /* + * Return the result of trying to write the new password + * back into the trust account file. + */ + if (!secrets_store_machine_password(new_trust_passwd, domain, sec_channel_type)) { + nt_status = NT_STATUS_UNSUCCESSFUL; + } + } + + return nt_status; +} + +/********************************************************* + Change the domain password on the PDC. + Do most of the legwork ourselfs. Caller must have + already setup the connection to the NETLOGON pipe +**********************************************************/ + +NTSTATUS trust_pw_find_change_and_store_it(struct rpc_pipe_client *cli, + TALLOC_CTX *mem_ctx, + const char *domain) +{ + unsigned char old_trust_passwd_hash[16]; + uint32 sec_channel_type = 0; + + if (!secrets_fetch_trust_account_password(domain, + old_trust_passwd_hash, + NULL, &sec_channel_type)) { + DEBUG(0, ("could not fetch domain secrets for domain %s!\n", domain)); + return NT_STATUS_UNSUCCESSFUL; + } + + return trust_pw_change_and_store_it(cli, mem_ctx, domain, + old_trust_passwd_hash, + sec_channel_type); +} + +/********************************************************************* + Enumerate the list of trusted domains from a DC +*********************************************************************/ + +bool enumerate_domain_trusts( TALLOC_CTX *mem_ctx, const char *domain, + char ***domain_names, uint32 *num_domains, + DOM_SID **sids ) +{ + POLICY_HND pol; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + fstring dc_name; + struct sockaddr_storage dc_ss; + uint32 enum_ctx = 0; + struct cli_state *cli = NULL; + struct rpc_pipe_client *lsa_pipe; + bool retry; + struct lsa_DomainList dom_list; + int i; + + *domain_names = NULL; + *num_domains = 0; + *sids = NULL; + + /* lookup a DC first */ + + if ( !get_dc_name(domain, NULL, dc_name, &dc_ss) ) { + DEBUG(3,("enumerate_domain_trusts: can't locate a DC for domain %s\n", + domain)); + return False; + } + + /* setup the anonymous connection */ + + result = cli_full_connection( &cli, global_myname(), dc_name, &dc_ss, 0, "IPC$", "IPC", + "", "", "", 0, Undefined, &retry); + if ( !NT_STATUS_IS_OK(result) ) + goto done; + + /* open the LSARPC_PIPE */ + + result = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc.syntax_id, + &lsa_pipe); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + /* get a handle */ + + result = rpccli_lsa_open_policy(lsa_pipe, mem_ctx, True, + LSA_POLICY_VIEW_LOCAL_INFORMATION, &pol); + if ( !NT_STATUS_IS_OK(result) ) + goto done; + + /* Lookup list of trusted domains */ + + result = rpccli_lsa_EnumTrustDom(lsa_pipe, mem_ctx, + &pol, + &enum_ctx, + &dom_list, + (uint32_t)-1); + if ( !NT_STATUS_IS_OK(result) ) + goto done; + + *num_domains = dom_list.count; + + *domain_names = TALLOC_ZERO_ARRAY(mem_ctx, char *, *num_domains); + if (!*domain_names) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + *sids = TALLOC_ZERO_ARRAY(mem_ctx, DOM_SID, *num_domains); + if (!*sids) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + for (i=0; i< *num_domains; i++) { + (*domain_names)[i] = CONST_DISCARD(char *, dom_list.domains[i].name.string); + (*sids)[i] = *dom_list.domains[i].sid; + } + +done: + /* cleanup */ + if (cli) { + DEBUG(10,("enumerate_domain_trusts: shutting down connection...\n")); + cli_shutdown( cli ); + } + + return NT_STATUS_IS_OK(result); +} diff --git a/source3/libsmb/unexpected.c b/source3/libsmb/unexpected.c new file mode 100644 index 0000000000..df4d2119e2 --- /dev/null +++ b/source3/libsmb/unexpected.c @@ -0,0 +1,202 @@ +/* + Unix SMB/CIFS implementation. + handle unexpected packets + Copyright (C) Andrew Tridgell 2000 + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" + +static TDB_CONTEXT *tdbd = NULL; + +/* the key type used in the unexpected packet database */ +struct unexpected_key { + enum packet_type packet_type; + time_t timestamp; + int count; +}; + +/**************************************************************************** + All unexpected packets are passed in here, to be stored in a unexpected + packet database. This allows nmblookup and other tools to receive packets + erroneously sent to the wrong port by broken MS systems. +**************************************************************************/ + +void unexpected_packet(struct packet_struct *p) +{ + static int count; + TDB_DATA kbuf, dbuf; + struct unexpected_key key; + char buf[1024]; + int len=0; + uint32_t enc_ip; + + if (!tdbd) { + tdbd = tdb_open_log(lock_path("unexpected.tdb"), 0, + TDB_CLEAR_IF_FIRST|TDB_DEFAULT, + O_RDWR | O_CREAT, 0644); + if (!tdbd) { + DEBUG(0,("Failed to open unexpected.tdb\n")); + return; + } + } + + memset(buf,'\0',sizeof(buf)); + + /* Encode the ip addr and port. */ + enc_ip = ntohl(p->ip.s_addr); + SIVAL(buf,0,enc_ip); + SSVAL(buf,4,p->port); + + len = build_packet(&buf[6], sizeof(buf)-6, p) + 6; + + ZERO_STRUCT(key); /* needed for potential alignment */ + + key.packet_type = p->packet_type; + key.timestamp = p->timestamp; + key.count = count++; + + kbuf.dptr = (uint8_t *)&key; + kbuf.dsize = sizeof(key); + dbuf.dptr = (uint8_t *)buf; + dbuf.dsize = len; + + tdb_store(tdbd, kbuf, dbuf, TDB_REPLACE); +} + + +static time_t lastt; + +/**************************************************************************** + Delete the record if it is too old. +**************************************************************************/ + +static int traverse_fn(TDB_CONTEXT *ttdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct unexpected_key key; + + if (kbuf.dsize != sizeof(key)) { + tdb_delete(ttdb, kbuf); + } + + memcpy(&key, kbuf.dptr, sizeof(key)); + + if (lastt - key.timestamp > NMBD_UNEXPECTED_TIMEOUT) { + tdb_delete(ttdb, kbuf); + } + + return 0; +} + + +/**************************************************************************** + Delete all old unexpected packets. +**************************************************************************/ + +void clear_unexpected(time_t t) +{ + if (!tdbd) return; + + if ((lastt != 0) && (t < lastt + NMBD_UNEXPECTED_TIMEOUT)) + return; + + lastt = t; + + tdb_traverse(tdbd, traverse_fn, NULL); +} + +struct receive_unexpected_state { + struct packet_struct *matched_packet; + int match_id; + enum packet_type match_type; + const char *match_name; +}; + +/**************************************************************************** + tdb traversal fn to find a matching 137 packet. +**************************************************************************/ + +static int traverse_match(TDB_CONTEXT *ttdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *private_data) +{ + struct receive_unexpected_state *state = + (struct receive_unexpected_state *)private_data; + struct unexpected_key key; + struct in_addr ip; + uint32_t enc_ip; + int port; + struct packet_struct *p; + + if (kbuf.dsize != sizeof(key)) { + return 0; + } + + memcpy(&key, kbuf.dptr, sizeof(key)); + + if (key.packet_type != state->match_type) return 0; + + if (dbuf.dsize < 6) { + return 0; + } + + /* Decode the ip addr and port. */ + enc_ip = IVAL(dbuf.dptr,0); + ip.s_addr = htonl(enc_ip); + port = SVAL(dbuf.dptr,4); + + p = parse_packet((char *)&dbuf.dptr[6], + dbuf.dsize-6, + state->match_type, + ip, + port); + + if ((state->match_type == NMB_PACKET && + p->packet.nmb.header.name_trn_id == state->match_id) || + (state->match_type == DGRAM_PACKET && + match_mailslot_name(p, state->match_name))) { + state->matched_packet = p; + return -1; + } + + free_packet(p); + + return 0; +} + +/**************************************************************************** + Check for a particular packet in the unexpected packet queue. +**************************************************************************/ + +struct packet_struct *receive_unexpected(enum packet_type packet_type, int id, + const char *mailslot_name) +{ + TDB_CONTEXT *tdb2; + struct receive_unexpected_state state; + + tdb2 = tdb_open_log(lock_path("unexpected.tdb"), 0, 0, O_RDONLY, 0); + if (!tdb2) return NULL; + + state.matched_packet = NULL; + state.match_id = id; + state.match_type = packet_type; + state.match_name = mailslot_name; + + tdb_traverse(tdb2, traverse_match, &state); + + tdb_close(tdb2); + + return state.matched_packet; +} |