Files

1944 lines
59 KiB
C

/* -*- mode:c; tab-width:8; c-basic-offset:8; indent-tabs-mode:nil; -*- */
/*
Copyright (C) 2018 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
This program 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 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef STDC_HEADERS
#include <stddef.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_UNISTD_H
#include <sys/unistd.h>
#endif
#include "portable-endian.h"
#include <errno.h>
#include "compat.h"
#include <stdio.h>
#include "smb2.h"
#include "libsmb2.h"
#include "libsmb2-dcerpc.h"
#include "libsmb2-raw.h"
#include "libsmb2-private.h"
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)(void *)( (char *)__mptr - offsetof(type,member) );})
struct dcerpc_deferred_pointer {
dcerpc_coder coder;
void *ptr;
};
#define MAX_DEFERRED_PTR 1024
#define NDR32_UUID 0x8a885d04, 0x1ceb, 0x11c9, {0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60}
#define NDR64_UUID 0x71710533, 0xbeba, 0x4937, {0x83, 0x19, 0xb5, 0xdb, 0xef, 0x9c, 0xcc, 0x36}
/*
* NDR64 is only supported for LITTLE_ENDIAN encodings:
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/b1af93c7-f988-4a1a-ac74-063179942f32
*/
p_syntax_id_t ndr32_syntax = {
{NDR32_UUID}, 2, 0
};
p_syntax_id_t ndr64_syntax = {
{NDR64_UUID}, 1, 0
};
struct dcerpc_context {
struct smb2_context *smb2;
const char *path;
p_syntax_id_t *syntax;
smb2_file_id file_id;
uint8_t tctx_id; /* 0:NDR32 1:NDR64 */
uint8_t packed_drep[4];
uint32_t call_id;
};
struct dcerpc_header {
uint8_t rpc_vers;
uint8_t rpc_vers_minor;
uint8_t PTYPE;
uint8_t pfc_flags;
uint8_t packed_drep[4];
uint16_t frag_length;
uint16_t auth_length;
uint32_t call_id;
};
struct p_cont_elem_t {
uint16_t p_cont_id;
uint8_t n_transfer_syn; /* number of items */
uint8_t reserved; /* alignment pad, m.b.z. */
p_syntax_id_t *abstract_syntax; /* transfer syntax list */
p_syntax_id_t **transfer_syntaxes;
};
struct dcerpc_bind_pdu {
uint16_t max_xmit_frag;
uint16_t max_recv_frag;
uint32_t assoc_group_id;
/* presentation context list */
uint8_t n_context_elem; /* number of items */
//u_int8 reserved; /* alignment pad, m.b.z. */
//u_short reserved2; /* alignment pad, m.b.z. */
struct p_cont_elem_t *p_cont_elem;
//p_cont_elem_t [size_is(n_cont_elem)] p_cont_elem[];
//p_syntax_id_t *abstract_syntax;
};
#define ACK_RESULT_ACCEPTANCE 0
#define ACK_RESULT_USER_REJECTION 1
#define ACK_RESULT_PROVIDER_REJECTION 2
#define ACK_REASON_REASON_NOT_SPECIFIED 0
#define ACK_REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED 1
#define ACK_REASON_PROPOSED_TRANSFER_SYNTAXES_NOT_SUPPORTED 2
#define ACK_REASON_PROTOCOL_VERSION_NOT_SUPPORTED 4
struct dcerpc_bind_context_results {
uint16_t ack_result;
uint16_t ack_reason;
dcerpc_uuid_t uuid;
uint32_t syntax_version;
};
#define MAX_ACK_RESULTS 4
struct dcerpc_bind_ack_pdu {
uint16_t max_xmit_frag;
uint16_t max_recv_frag;
uint32_t assoc_group_id;
/* presentation context list */
uint8_t num_results;
struct dcerpc_bind_context_results results[MAX_ACK_RESULTS];
};
struct dcerpc_request_pdu {
uint32_t alloc_hint;
uint16_t context_id;
uint16_t opnum;
/* optional field for request, only present if the PFC_OBJECT_UUID
* field is non-zero */
/* dcerpc_uuid_t object; 24:16 object UID */
/* stub data, 8-octet aligned
.
.
. */
};
struct dcerpc_response_pdu {
uint32_t alloc_hint;
uint16_t context_id;
uint8_t cancel_count;
uint8_t reserved;
/* stub data, 8-octet aligned
.
.
. */
};
/* PDU Types */
#define PDU_TYPE_REQUEST 0
#define PDU_TYPE_PING 1
#define PDU_TYPE_RESPONSE 2
#define PDU_TYPE_FAULT 3
#define PDU_TYPE_WORKING 4
#define PDU_TYPE_NOCALL 5
#define PDU_TYPE_REJECT 6
#define PDU_TYPE_ACK 7
#define PDU_TYPE_CL_CANCEL 8
#define PDU_TYPE_FACK 9
#define PDU_TYPE_CANCEL_ACK 10
#define PDU_TYPE_BIND 11
#define PDU_TYPE_BIND_ACK 12
#define PDU_TYPE_BIND_NAK 13
#define PDU_TYPE_ALTER_CONTEXT 14
#define PDU_TYPE_ALTER_CONTEXT_RESP 15
#define PDU_TYPE_SHUTDOWN 17
#define PDU_TYPE_CO_CANCEL 18
#define PDU_TYPE_ORPHANED 19
/* Flags */
#define PFC_FIRST_FRAG 0x01
#define PFC_LAST_FRAG 0x02
#define PFC_PENDING_CANCEL 0x04
#define PFC_RESERVED_1 0x08
#define PFC_CONC_MPX 0x10
#define PFC_DID_NOT_EXECUTE 0x20
#define PFC_MAYBE 0x40
#define PFC_OBJECT_UUID 0x80
#define NSE_BUF_SIZE 128*1024
struct dcerpc_cb_data {
struct dcerpc_context *dce;
dcerpc_cb cb;
void *cb_data;
};
struct dcerpc_pdu {
struct dcerpc_header hdr;
union {
struct dcerpc_bind_pdu bind;
struct dcerpc_bind_ack_pdu bind_ack;
struct dcerpc_request_pdu req;
struct dcerpc_response_pdu rsp;
};
/* optional authentication verifier */
/* following fields present iff auth_length != 0 */
#if 0
auth_verifier_co_t auth_verifier;
#endif
struct dcerpc_context *dce;
dcerpc_cb cb;
void *cb_data;
dcerpc_coder coder;
int decode_size;
void *payload;
int top_level;
uint64_t ptr_id;
int cur_ptr;
int max_ptr;
struct dcerpc_deferred_pointer ptrs[MAX_DEFERRED_PTR];
int direction;
/* All items are parsed twice, first to handle the conformance
* fields and a second time to handle the data itself.
* During the first run we also check what the maximum alignment
* of the fields are.
*/
int is_conformance_run;
int max_alignment;
};
int
dcerpc_set_uint8(struct dcerpc_context *ctx, struct smb2_iovec *iov,
int *offset, uint8_t value)
{
if (*offset + sizeof(uint8_t) > iov->len) {
return -1;
}
*(uint8_t *)(iov->buf + *offset) = value;
*offset += 1;
return 0;
}
static int
dcerpc_set_uint16(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint16_t value)
{
*offset = (*offset + 1) & ~1;
if (*offset + sizeof(uint16_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
*(uint16_t *)(void *)(iov->buf + *offset) = htobe16(value);
} else {
*(uint16_t *)(void *)(iov->buf + *offset) = htole16(value);
}
*offset += 2;
return 0;
}
static int
dcerpc_set_uint32(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint32_t value)
{
*offset = (*offset + 3) & ~3;
if (*offset + sizeof(uint32_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
*(uint32_t *)(void *)(iov->buf + *offset) = htobe32(value);
} else {
*(uint32_t *)(void *)(iov->buf + *offset) = htole32(value);
}
*offset += 4;
return 0;
}
static int
dcerpc_set_uint64(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint64_t value)
{
*offset = (*offset + 7) & ~7;
if (*offset + sizeof(uint64_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
*(uint64_t *)(void *)(iov->buf + *offset) = htobe64(value);
} else {
*(uint64_t *)(void *)(iov->buf + *offset) = htole64(value);
}
*offset += 8;
return 0;
}
static int
dcerpc_get_uint8(struct dcerpc_context *ctx, struct smb2_iovec *iov,
int *offset, uint8_t *value)
{
if (*offset + sizeof(uint8_t) > iov->len) {
return -1;
}
*value = iov->buf[*offset];
*offset += 1;
return 0;
}
static int
dcerpc_get_uint16(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint16_t *value)
{
uint16_t val;
*offset = (*offset + 1) & ~1;
if (*offset + sizeof(uint16_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
val = be16toh(*(uint16_t *)(void *)(iov->buf + *offset));
} else {
val = le16toh(*(uint16_t *)(void *)(iov->buf + *offset));
}
*value = val;
*offset += 2;
return 0;
}
static int
dcerpc_get_uint32(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint32_t *value)
{
uint32_t val;
*offset = (*offset + 3) & ~3;
if (*offset + sizeof(uint32_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
val = be32toh(*(uint32_t *)(void *)(iov->buf + *offset));
} else {
val = le32toh(*(uint32_t *)(void *)(iov->buf + *offset));
}
*value = val;
*offset += 4;
return 0;
}
static int
dcerpc_get_uint64(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, uint64_t *value)
{
uint64_t val;
*offset = (*offset + 7) & ~7;
if (*offset + sizeof(uint64_t) > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
val = be64toh(*(uint64_t *)(void *)(iov->buf + *offset));
} else {
val = le64toh(*(uint64_t *)(void *)(iov->buf + *offset));
}
*value = val;
*offset += 8;
return 0;
}
int
dcerpc_uint64_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
if (pdu->is_conformance_run) {
if (pdu->max_alignment < 8) {
pdu->max_alignment = 8;
}
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_get_uint64(ctx, pdu, iov, offset, ptr);
} else {
return dcerpc_set_uint64(ctx, pdu, iov, offset, *(uint32_t *)ptr);
}
}
struct smb2_context *
dcerpc_get_smb2_context(struct dcerpc_context *dce)
{
return dce->smb2;
}
void *
dcerpc_get_pdu_payload(struct dcerpc_pdu *pdu)
{
return pdu->payload;
}
struct dcerpc_context *
dcerpc_create_context(struct smb2_context *smb2)
{
struct dcerpc_context *ctx;
ctx = calloc(1, sizeof(struct dcerpc_context));
if (ctx == NULL) {
smb2_set_error(smb2, "Failed to allocate dcercp context.");
return NULL;
}
ctx->smb2 = smb2;
ctx->packed_drep[0] |= DCERPC_DR_LITTLE_ENDIAN;
return ctx;
}
int
dcerpc_connect_context_async(struct dcerpc_context *dce, const char *path,
p_syntax_id_t *syntax,
dcerpc_cb cb, void *cb_data)
{
dce->call_id = 2;
dce->path = strdup(path);
if (dce->path == NULL) {
smb2_set_error(dce->smb2, "Failed to allocate path for "
"dcercp context.");
return -ENOMEM;
}
dce->syntax = syntax;
dce->packed_drep[0] = DCERPC_DR_ASCII;
if (!dce->smb2->endianness) {
dce->packed_drep[0] |= DCERPC_DR_LITTLE_ENDIAN;
}
if (dcerpc_open_async(dce, cb, cb_data) != 0) {
return -1;
}
return 0;
}
void
dcerpc_destroy_context(struct dcerpc_context *dce)
{
if (dce == NULL) {
return;
}
free(discard_const(dce->path));
free(dce);
}
void
dcerpc_free_pdu(struct dcerpc_context *dce, struct dcerpc_pdu *pdu)
{
if (pdu == NULL) {
return;
}
if (pdu->payload) {
smb2_free_data(dce->smb2, pdu->payload);
}
free(pdu);
}
struct dcerpc_pdu *
dcerpc_allocate_pdu(struct dcerpc_context *dce, int direction, int payload_size)
{
struct dcerpc_pdu *pdu;
pdu = calloc(1, sizeof(struct dcerpc_pdu));
if (pdu == NULL) {
smb2_set_error(dce->smb2, "Failed to allocate DCERPC PDU");
return NULL;
}
pdu->dce = dce;
pdu->hdr.call_id = dce->call_id++;
pdu->direction = direction;
pdu->top_level = 1;
pdu->payload = smb2_alloc_init(dce->smb2, payload_size);
if (pdu->payload == NULL) {
smb2_set_error(dce->smb2, "Failed to allocate PDU Payload");
dcerpc_free_pdu(dce, pdu);
return NULL;
}
return pdu;
}
static void
dcerpc_add_deferred_pointer(struct dcerpc_context *ctx,
struct dcerpc_pdu *pdu,
dcerpc_coder coder, void *ptr)
{
pdu->ptrs[pdu->max_ptr].coder = coder;
pdu->ptrs[pdu->max_ptr].ptr = ptr;
pdu->max_ptr++;
}
int
dcerpc_do_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov,
int *offset, void *ptr,
dcerpc_coder coder)
{
pdu->max_alignment = 1;
pdu->is_conformance_run = 1;
if (coder(ctx, pdu, iov, offset, ptr)) {
return -1;
}
*offset = (*offset + (pdu->max_alignment - 1)) & ~(pdu->max_alignment - 1);
pdu->is_conformance_run = 0;
if (coder(ctx, pdu, iov, offset, ptr)) {
return -1;
}
return 0;
}
static int
dcerpc_process_deferred_pointers(struct dcerpc_context *ctx,
struct dcerpc_pdu *pdu,
struct smb2_iovec *iov,
int *offset)
{
struct dcerpc_deferred_pointer *dp;
int idx;
while (pdu->cur_ptr != pdu->max_ptr) {
idx = pdu->cur_ptr++;
dp = &pdu->ptrs[idx];
if (dcerpc_do_coder(ctx, pdu, iov, offset, dp->ptr, dp->coder)) {
return -1;
}
}
return 0;
}
int
dcerpc_uint32_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
if (pdu->is_conformance_run) {
if (pdu->max_alignment < 4) {
pdu->max_alignment = 4;
}
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_get_uint32(ctx, pdu, iov, offset, ptr);
} else {
return dcerpc_set_uint32(ctx, pdu, iov, offset, *(uint32_t *)ptr);
}
}
int
dcerpc_uint16_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
if (pdu->is_conformance_run) {
if (pdu->max_alignment < 2) {
pdu->max_alignment = 2;
}
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_get_uint16(ctx, pdu, iov, offset, ptr);
} else {
return dcerpc_set_uint16(ctx, pdu, iov, offset, *(uint16_t *)ptr);
}
}
int
dcerpc_uint8_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
if (pdu->is_conformance_run) {
if (pdu->max_alignment < 1) {
pdu->max_alignment = 1;
}
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_get_uint8(ctx, iov, offset, ptr);
} else {
return dcerpc_set_uint8(ctx, iov, offset, *(uint8_t *)ptr);
}
return 0;
}
/* Encode words that vary in size depending on the transport syntax */
int
dcerpc_uint3264_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
uint32_t u32 = 0;
uint64_t val = *(uint64_t *)ptr;
if (pdu->is_conformance_run) {
if (ctx->tctx_id) {
if (pdu->max_alignment < 8) {
pdu->max_alignment = 8;
}
} else {
if (pdu->max_alignment < 4) {
pdu->max_alignment = 4;
}
}
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
if (ctx->tctx_id) {
if (dcerpc_get_uint64(ctx, pdu, iov, offset, ptr)) {
return -1;
}
} else {
if (dcerpc_get_uint32(ctx, pdu, iov, offset, &u32)) {
return -1;
}
*(uint64_t *)ptr = u32;
}
} else {
if (ctx->tctx_id) {
if (dcerpc_set_uint64(ctx, pdu, iov, offset, val)) {
return -1;
}
} else {
if (dcerpc_set_uint32(ctx, pdu, iov, offset, (uint32_t)val)) {
return -1;
}
}
}
return 0;
}
int
dcerpc_conformance_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr)
{
uint32_t u32 = 0;
uint64_t val = *(uint64_t *)ptr;
if (!pdu->is_conformance_run) {
return 0;
}
if (pdu->direction == DCERPC_DECODE) {
if (ctx->tctx_id) {
if (dcerpc_get_uint64(ctx, pdu, iov, offset, ptr)) {
return -1;
}
} else {
if (dcerpc_get_uint32(ctx, pdu, iov, offset, &u32)) {
return -1;
}
*(uint64_t *)ptr = u32;
}
} else {
if (ctx->tctx_id) {
if (dcerpc_set_uint64(ctx, pdu, iov, offset, val)) {
return -1;
}
} else {
if (dcerpc_set_uint32(ctx, pdu, iov, offset, val)) {
return -1;
}
}
}
return 0;
}
#define RPTR 0x5270747272747052
#define UPTR 0x5570747272747055
static int
dcerpc_encode_ptr(struct dcerpc_context *dce, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov,
int *offset, void *ptr,
enum ptr_type type, dcerpc_coder coder)
{
int top_level = pdu->top_level;
uint64_t val;
if (pdu->is_conformance_run) {
if (dce->tctx_id) {
if (pdu->max_alignment < 8) {
pdu->max_alignment = 8;
}
} else {
if (pdu->max_alignment < 4) {
pdu->max_alignment = 4;
}
}
return 0;
}
switch (type) {
case PTR_REF:
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_do_coder(dce, pdu, iov, offset, ptr, coder)) {
return -1;
}
pdu->top_level = top_level;
goto out;
}
val = RPTR;
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &val)) {
return -1;
}
dcerpc_add_deferred_pointer(dce, pdu, (dcerpc_coder)coder, ptr);
break;
case PTR_FULL:
if (ptr == NULL) {
val = 0;
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &val)) {
return -1;
}
goto out;
}
pdu->ptr_id++;
val = pdu->ptr_id;
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &val)) {
return -1;
}
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_do_coder(dce, pdu, iov, offset, ptr, coder)) {
return -1;
}
pdu->top_level = top_level;
} else {
dcerpc_add_deferred_pointer(dce, pdu, (dcerpc_coder)coder, ptr);
}
break;
case PTR_UNIQUE:
if (ptr == NULL) {
val = 0;
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &val)) {
return -1;
}
goto out;
}
val = UPTR;
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &val)) {
return -1;
}
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_do_coder(dce, pdu, iov, offset, ptr, coder)) {
return -1;
}
pdu->top_level = top_level;
} else {
dcerpc_add_deferred_pointer(dce, pdu, (dcerpc_coder)coder, ptr);
}
break;
}
out:
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_process_deferred_pointers(dce, pdu, iov, offset)) {
return -1;
}
pdu->top_level = top_level;
}
return 0;
}
/* TODO conformance split
* during the conformance run we need to do the alignment in all the
coders, even for the coders that do not have any conformance data.
*/
static int
dcerpc_decode_ptr(struct dcerpc_context *dce, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr,
enum ptr_type type, dcerpc_coder coder)
{
int top_level = pdu->top_level;
uint64_t p;
if (pdu->is_conformance_run) {
if (!(type==PTR_REF && pdu->top_level)) {
if (dce->tctx_id) {
if (pdu->max_alignment < 8) {
pdu->max_alignment = 8;
}
} else {
if (pdu->max_alignment < 4) {
pdu->max_alignment = 4;
}
}
}
return 0;
}
switch (type) {
case PTR_REF:
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_do_coder(dce, pdu, iov, offset, ptr, coder)) {
return -1;
}
pdu->top_level = top_level;
goto out;
}
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &p)) {
return -1;
}
dcerpc_add_deferred_pointer(dce, pdu, (dcerpc_coder)coder, ptr);
break;
case PTR_UNIQUE:
if (dcerpc_uint3264_coder(dce, pdu, iov, offset, &p)) {
return -1;
}
if (p == 0 || ptr == NULL) {
return 0;
}
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_do_coder(dce, pdu, iov, offset, ptr, coder)) {
return -1;
}
pdu->top_level = top_level;
} else {
dcerpc_add_deferred_pointer(dce, pdu, (dcerpc_coder)coder, ptr);
}
break;
case PTR_FULL:
/* not implemented yet */
break;
}
out:
if (pdu->top_level) {
pdu->top_level = 0;
if (dcerpc_process_deferred_pointers(dce, pdu, iov, offset)) {
return -1;
}
pdu->top_level = top_level;
}
return 0;
}
int
dcerpc_carray_coder(struct dcerpc_context *ctx,
struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr, int elem_size, dcerpc_coder coder)
{
struct dcerpc_carray *carray = ptr;
int i;
uint64_t p;
/* Conformance */
p = carray->max_count;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &p)) {
return -1;
}
carray->max_count = p;
if (dcerpc_pdu_direction(pdu) == DCERPC_DECODE) {
if (carray->max_count && carray->data == NULL) {
carray->data = smb2_alloc_data(
dcerpc_get_smb2_context(ctx),
dcerpc_get_pdu_payload(pdu),
carray->max_count * elem_size);
if (carray->data == NULL) {
return -1;
}
}
}
/* Data */
for (i = 0; i < p; i++) {
if (coder(ctx, pdu, iov, offset, &carray->data[i * elem_size])) {
return -1;
}
}
return 0;
}
int
dcerpc_ptr_coder(struct dcerpc_context *dce, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset, void *ptr,
enum ptr_type type, dcerpc_coder coder)
{
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_decode_ptr(dce, pdu, iov, offset, ptr,
type, coder);
} else {
return dcerpc_encode_ptr(dce, pdu, iov, offset, ptr,
type, coder);
}
}
static int
dcerpc_encode_utf16(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr, int nult)
{
struct dcerpc_utf16 *s = ptr;
int i;
uint64_t val;
uint16_t zero = 0;
/* Conformance part */
if (pdu->is_conformance_run) {
if (s->utf8 == NULL) {
s->utf8 = "";
}
s->utf16 = smb2_utf8_to_utf16(s->utf8);
if (s->utf16 == NULL) {
return -1;
}
if (nult) {
val = s->utf16->len + 1;
} else {
val = s->utf16->len;
}
s->actual_count = (uint32_t)val;
if (!nult) {
if (val & 0x01) val++;
}
s->max_count = (uint32_t)val;
s->offset = 0;
val = s->max_count;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
val = s->offset;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
val = s->actual_count;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
if (pdu->max_alignment < 2) {
pdu->max_alignment = 2;
}
return 0;
}
/* Data part */
for (i = 0; i < s->utf16->len; i++) {
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &s->utf16->val[i])) {
return -1;
}
}
if (nult) {
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &zero)) {
return -1;
}
}
free(s->utf16);
return 0;
}
static int
dcerpc_decode_utf16(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr, int nult)
{
struct dcerpc_utf16 *s = ptr;
uint64_t val; /* Any fundament of this? */
char *str;
const char *tmp;
/* Conformance part */
if (pdu->is_conformance_run) {
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
s->max_count = (uint32_t)val;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
s->offset = (uint32_t)val;
if (dcerpc_conformance_coder(ctx, pdu, iov, offset, &val)) {
return -1;
}
s->actual_count = (uint32_t)val;
if (pdu->max_alignment < 2) {
pdu->max_alignment = 2;
}
return 0;
}
/* Data part */
if (*offset + s->actual_count * 2 > iov->len) {
return -1;
}
if (!(pdu->hdr.packed_drep[0] & DCERPC_DR_LITTLE_ENDIAN)) {
int i, o;
uint16_t v;
for (i = 0; i < s->actual_count; i++) {
o = *offset + i *2;
if (dcerpc_get_uint16(ctx, pdu, iov, &o, &v)) {
return -1;
}
*(uint16_t *)(void *)&iov->buf[*offset + i * 2] = v;
}
}
tmp = smb2_utf16_to_utf8((uint16_t *)(void *)(&iov->buf[*offset]), (size_t)s->actual_count);
*offset += (int)s->actual_count * 2;
str = smb2_alloc_data(ctx->smb2, pdu->payload, strlen(tmp) + 1);
if (str == NULL) {
free(discard_const(tmp));
return -1;
}
strcat(str, tmp);
free(discard_const(tmp));
s->utf8 = str;
return 0;
}
/* Handle \0 terminated utf16 strings */
/* ptr is char ** */
int
dcerpc_utf16z_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr)
{
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_decode_utf16(ctx, pdu, iov, offset, ptr, 1);
} else {
return dcerpc_encode_utf16(ctx, pdu, iov, offset, ptr, 1);
}
}
/* Handle utf16 strings that are NOT \0 terminated */
int
dcerpc_utf16_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr)
{
if (pdu->direction == DCERPC_DECODE) {
return dcerpc_decode_utf16(ctx, pdu, iov, offset, ptr, 0);
} else {
return dcerpc_encode_utf16(ctx, pdu, iov, offset, ptr, 0);
}
}
int
dcerpc_header_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
struct dcerpc_header *hdr)
{
/* Major Version */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->rpc_vers)) {
return -1;
}
/* Minor Version */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->rpc_vers_minor)) {
return -1;
}
/* Packet Type */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->PTYPE)) {
return -1;
}
/* Flags */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->pfc_flags)) {
return -1;
}
/* Data Representation */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->packed_drep[0])) {
return -1;
}
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->packed_drep[1])) {
return -1;
}
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->packed_drep[2])) {
return -1;
}
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &hdr->packed_drep[3])) {
return -1;
}
/* Fragment len */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &hdr->frag_length)) {
return -1;
}
/* Auth len */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &hdr->auth_length)) {
return -1;
}
/* Call id */
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &hdr->call_id)) {
return -1;
}
return 0;
}
int
dcerpc_uuid_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
dcerpc_uuid_t *uuid)
{
int i;
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &uuid->v1)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &uuid->v2)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &uuid->v3)) {
return -1;
}
for (i = 0; i < 8; i++) {
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &uuid->v4[i])) {
return -1;
}
}
return 0;
}
/**********************
* typedef struct ndr_context_handle {
* unsigned32 context_handle_attributes;
* dcerpc_uuid_t context_handle_uuid;
* } ndr_context_handle;
**********************/
int
dcerpc_context_handle_coder(struct dcerpc_context *dce,
struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset,
void *ptr)
{
struct ndr_context_handle *handle = ptr;
if (dcerpc_uint32_coder(dce, pdu, iov, offset, &handle->context_handle_attributes)) {
return -1;
}
if (dcerpc_uuid_coder(dce, pdu, iov, offset,
&handle->context_handle_uuid)) {
return -1;
}
return 0;
}
static int
dcerpc_bind_coder(struct dcerpc_context *ctx,
struct dcerpc_pdu *pdu,
struct dcerpc_bind_pdu *bind,
struct smb2_iovec *iov, int *offset)
{
int oo, i, j;
uint16_t v;
/* Max Xmit Frag */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind->max_xmit_frag)) {
return -1;
}
/* Max Recv Frag */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind->max_recv_frag)) {
return -1;
}
/* Association Group */
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &bind->assoc_group_id)) {
return -1;
}
/* Number Of Context Items */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &bind->n_context_elem)) {
return -1;
}
*offset += 3;
//qqq TODO allocate p_cont_elem on decode
for (i = 0; i < bind->n_context_elem; i++) {
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].p_cont_id)) {
return -1;
}
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].n_transfer_syn)) {
return -1;
}
*offset += 1;
/* Abstract Syntax */
//qqq TODO allocate abstract_syntax on decode
if (dcerpc_uuid_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].abstract_syntax->uuid)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].abstract_syntax->vers)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].abstract_syntax->vers_minor)) {
return -1;
}
//qqq TODO allocate transfer_syntaxes on decode
for (j = 0; j < pdu->bind.p_cont_elem[i].n_transfer_syn; j++) {
if (dcerpc_uuid_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].transfer_syntaxes[j]->uuid)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].transfer_syntaxes[j]->vers)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &pdu->bind.p_cont_elem[i].transfer_syntaxes[j]->vers_minor)) {
return -1;
}
}
}
/* Fixup fragment length */
oo = 8;
v = *offset;
if (dcerpc_uint16_coder(ctx, pdu, iov, &oo, &v)) {
return -1;
}
return 0;
}
static int
dcerpc_request_coder(struct dcerpc_context *ctx,
struct dcerpc_pdu *pdu,
struct dcerpc_request_pdu *req,
struct smb2_iovec *iov, int *offset)
{
/* Alloc Hint */
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &req->alloc_hint)) {
return -1;
}
/* Context ID */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &req->context_id)) {
return -1;
}
/* Opnum */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &req->opnum)) {
return -1;
}
return 0;
}
static int
dcerpc_bind_ack_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct dcerpc_bind_ack_pdu *bind_ack,
struct smb2_iovec *iov, int *offset)
{
int i;
uint16_t sec_addr_len;
/* Max Xmit Frag */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind_ack->max_xmit_frag)) {
return -1;
}
/* Max Recv Frag */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind_ack->max_recv_frag)) {
return -1;
}
/* Association Group */
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &bind_ack->assoc_group_id)) {
return -1;
}
/* Secondary Address Length */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &sec_addr_len)) {
return -1;
}
/* Skip the secondary address and realign to 32bit */
/* TODO: we need to handle the encode case.
* it is something like "\\PIPE\\srvsvc"
*/
*offset += sec_addr_len;
*offset = (*offset + 3) & ~3;
/* Number Of Results */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &bind_ack->num_results)) {
return -1;
}
*offset += 3;
for (i = 0; i < bind_ack->num_results; i++) {
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind_ack->results[i].ack_result)) {
return -1;
}
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &bind_ack->results[i].ack_reason)) {
return -1;
}
if (dcerpc_uuid_coder(ctx, pdu, iov, offset,
&bind_ack->results[i].uuid)) {
return -1;
}
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &bind_ack->results[i].syntax_version)) {
return -1;
}
}
return 0;
}
static int
dcerpc_response_coder(struct dcerpc_context *ctx,
struct dcerpc_response_pdu *rsp,
struct smb2_iovec *iov, int *offset)
{
#ifndef _MSC_VER
struct dcerpc_pdu *pdu = container_of(rsp, struct dcerpc_pdu, rsp);
#else
const char* __mptr = (const char*)rsp;
struct dcerpc_pdu *pdu = (struct dcerpc_pdu*)((char *)__mptr - offsetof(struct dcerpc_pdu, rsp));
#endif /* !_MSC_VER */
if (*offset < 0) {
return -1;
}
/* Alloc Hint */
if (dcerpc_uint32_coder(ctx, pdu, iov, offset, &rsp->alloc_hint)) {
return -1;
}
if (rsp->alloc_hint > 16*1024*1024) {
smb2_set_error(ctx->smb2, "DCERPC RESPONSE alloc_hint out "
"of range.");
return -1;
}
/* Context Id */
if (dcerpc_uint16_coder(ctx, pdu, iov, offset, &rsp->context_id)) {
return -1;
}
/* Cancel Count */
if (dcerpc_uint8_coder(ctx, pdu, iov, offset, &rsp->cancel_count)) {
return -1;
}
*offset += 1;
/* decode the blob */
pdu->top_level = 1;
if (pdu->coder(ctx, pdu, iov, offset, pdu->payload) < 0) {
return -1;
}
*offset += rsp->alloc_hint;
return 0;
}
static int
dcerpc_pdu_coder(struct dcerpc_context *ctx, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov, int *offset)
{
if (dcerpc_header_coder(ctx, pdu, iov, offset, &pdu->hdr)) {
return -1;
}
switch (pdu->hdr.PTYPE) {
case PDU_TYPE_BIND:
if (dcerpc_bind_coder(ctx, pdu, &pdu->bind, iov, offset)) {
return -1;
}
break;
case PDU_TYPE_BIND_ACK:
if (dcerpc_bind_ack_coder(ctx, pdu, &pdu->bind_ack, iov, offset)) {
return -1;
}
break;
case PDU_TYPE_REQUEST:
if (dcerpc_request_coder(ctx, pdu, &pdu->req, iov, offset)) {
return -1;
}
break;
case PDU_TYPE_RESPONSE:
if (dcerpc_response_coder(ctx, &pdu->rsp, iov, offset)) {
return -1;
}
break;
default:
smb2_set_error(ctx->smb2, "DCERPC No decoder for PDU type %d",
pdu->hdr.PTYPE);
return -1;
}
return 0;
}
static void
dce_unfragment_ioctl(struct dcerpc_context *dce, struct dcerpc_pdu *pdu,
struct smb2_iovec *iov)
{
int offset = 0;
int unfragment_len;
struct dcerpc_header hdr, next_hdr;
struct smb2_iovec tmpiov _U_;
int o;
o = 0;
if (dcerpc_header_coder(dce, pdu, iov, &o, &hdr)) {
return;
}
if (hdr.rpc_vers != 5 || hdr.rpc_vers_minor != 0 ||
hdr.PTYPE != PDU_TYPE_RESPONSE) {
return;
}
if (hdr.pfc_flags & PFC_LAST_FRAG) {
return;
}
offset += hdr.frag_length;
unfragment_len = hdr.frag_length;
do {
/* We must have at least a DCERPC header plus a
* RESPONSE header
*/
if (iov->len - offset < 24) {
return;
}
tmpiov.buf = iov->buf + offset;
tmpiov.len = iov->len - offset;
o = 0;
if (dcerpc_header_coder(dce, pdu, &tmpiov, &o, &next_hdr)) {
return;
}
memmove(iov->buf + unfragment_len, iov->buf + offset + 24,
next_hdr.frag_length - 24);
unfragment_len += next_hdr.frag_length - 24;
offset += next_hdr.frag_length;
hdr.frag_length += next_hdr.frag_length;
if (next_hdr.pfc_flags & PFC_LAST_FRAG) {
hdr.pfc_flags |= PFC_LAST_FRAG;
}
o = 0;
if (dcerpc_header_coder(dce, pdu, iov, &o, &hdr)) {
return;
}
} while (!(next_hdr.pfc_flags & PFC_LAST_FRAG));
iov->len = unfragment_len;
}
static void
dcerpc_send_pdu_cb_and_free(struct dcerpc_context *dce, struct dcerpc_pdu *pdu,
int status, void *command_data)
{
dcerpc_cb pdu_cb = pdu->cb;
void *pdu_cb_data = pdu->cb_data;
dcerpc_free_pdu(dce, pdu);
pdu_cb(dce, status, command_data, pdu_cb_data);
}
static void
dcerpc_call_cb(struct smb2_context *smb2, int status,
void *command_data, void *private_data)
{
struct dcerpc_pdu *pdu = private_data;
struct dcerpc_context *dce = pdu->dce;
struct smb2_iovec iov _U_;
struct smb2_ioctl_reply *rep = command_data;
void *payload;
int offset = 0;
pdu->direction = DCERPC_DECODE;
if (status != SMB2_STATUS_SUCCESS) {
dcerpc_send_pdu_cb_and_free(dce, pdu, -nterror_to_errno(status), NULL);
return;
}
smb2_free_data(dce->smb2, pdu->payload);
pdu->payload = NULL;
pdu->payload = smb2_alloc_init(dce->smb2, pdu->decode_size);
if (pdu->payload == NULL) {
dcerpc_send_pdu_cb_and_free(dce, pdu, -ENOMEM, NULL);
return;
}
iov.buf = rep->output;
iov.len = rep->output_count;
iov.free = NULL;
dce_unfragment_ioctl(dce, pdu, &iov);
if (dcerpc_pdu_coder(dce, pdu, &iov, &offset)) {
smb2_free_data(dce->smb2, rep->output);
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
smb2_free_data(dce->smb2, rep->output);
if (pdu->hdr.PTYPE != PDU_TYPE_RESPONSE) {
smb2_set_error(dce->smb2, "DCERPC response was not a RESPONSE");
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
payload = pdu->payload;
pdu->payload = NULL;
dcerpc_send_pdu_cb_and_free(dce, pdu, 0, payload);
}
int
dcerpc_call_async(struct dcerpc_context *dce,
int opnum,
dcerpc_coder req_coder, void *req,
dcerpc_coder rep_coder, int decode_size,
dcerpc_cb cb, void *cb_data)
{
struct dcerpc_pdu *pdu;
struct smb2_pdu *smb2_pdu;
struct smb2_ioctl_request smb2_req;
struct smb2_iovec iov;
int offset = 0, o;
uint32_t v;
pdu = dcerpc_allocate_pdu(dce, DCERPC_ENCODE, NSE_BUF_SIZE);
if (pdu == NULL) {
return -ENOMEM;
}
pdu->hdr.rpc_vers = 5;
pdu->hdr.rpc_vers_minor = 0;
pdu->hdr.PTYPE = PDU_TYPE_REQUEST;
pdu->hdr.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
pdu->hdr.packed_drep[0] = dce->packed_drep[0];
pdu->hdr.frag_length = 0;
pdu->hdr.auth_length = 0;
pdu->req.alloc_hint = 0;
pdu->req.context_id = dce->tctx_id;
pdu->req.opnum = opnum;
pdu->coder = rep_coder;
pdu->decode_size = decode_size;
pdu->cb = cb;
pdu->cb_data = cb_data;
iov.buf = pdu->payload;
iov.len = NSE_BUF_SIZE;
iov.free = NULL;
if (dcerpc_pdu_coder(dce, pdu, &iov, &offset)) {
dcerpc_free_pdu(dce, pdu);
return -ENOMEM;
}
/* encode the blob */
pdu->top_level = 1;
if (req_coder(dce, pdu, &iov, &offset, req)) {
return -1;
}
iov.len = offset;
/* Fixup frag_length and alloc_hint */
o = 8;
if (dcerpc_set_uint16(dce, pdu, &iov, &o, offset)) {
return -1;
}
o = 16;
v = offset - 24;
if (dcerpc_uint32_coder(dce, pdu, &iov, &o, &v)) {
return -1;
}
memset(&smb2_req, 0, sizeof(struct smb2_ioctl_request));
smb2_req.ctl_code = SMB2_FSCTL_PIPE_TRANSCEIVE;
memcpy(smb2_req.file_id, dce->file_id, SMB2_FD_SIZE);
smb2_req.input_count = (uint32_t)iov.len;
smb2_req.input = iov.buf;
smb2_req.flags = SMB2_0_IOCTL_IS_FSCTL;
smb2_pdu = smb2_cmd_ioctl_async(dce->smb2, &smb2_req, dcerpc_call_cb, pdu);
if (smb2_pdu == NULL) {
dcerpc_free_pdu(dce, pdu);
return -ENOMEM;
}
smb2_queue_pdu(dce->smb2, smb2_pdu);
return 0;
}
static void
dcerpc_bind_cb(struct dcerpc_context *dce, int status,
void *command_data, void *cb_data)
{
struct dcerpc_cb_data *data = cb_data;
if (status != SMB2_STATUS_SUCCESS) {
data->cb(dce, status, NULL, data->cb_data);
free(data);
return;
}
data->cb(dce, 0, NULL, data->cb_data);
free(data);
}
static void
smb2_bind_cb(struct smb2_context *smb2, int status,
void *command_data, void *private_data)
{
struct dcerpc_pdu *pdu = private_data;
struct dcerpc_context *dce = pdu->dce;
struct smb2_iovec iov _U_;
struct smb2_ioctl_reply *rep = command_data;
int i;
int offset = 0;
pdu->direction = DCERPC_DECODE;
if (status != SMB2_STATUS_SUCCESS) {
dcerpc_send_pdu_cb_and_free(dce, pdu, -nterror_to_errno(status), NULL);
return;
}
iov.buf = rep->output;
iov.len = rep->output_count;
iov.free = NULL;
if (dcerpc_pdu_coder(dce, pdu, &iov, &offset)) {
smb2_free_data(dce->smb2, rep->output);
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
smb2_free_data(dce->smb2, rep->output);
if (pdu->hdr.PTYPE != PDU_TYPE_BIND_ACK) {
smb2_set_error(dce->smb2, "DCERPC response was not a BIND_ACK");
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
if (pdu->bind_ack.num_results < 1) {
smb2_set_error(smb2, "No results in BIND ACK");
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
for (i = 0; i < pdu->bind_ack.num_results; i++) {
if (pdu->bind_ack.results[i].ack_result !=
ACK_RESULT_ACCEPTANCE) {
continue;
}
switch (smb2->ndr) {
case 0:
dce->tctx_id = i;
break;
case 1:
dce->tctx_id = 0;
break;
case 2:
dce->tctx_id = 1;
break;
}
break;
}
if (i == pdu->bind_ack.num_results) {
smb2_set_error(smb2, "Bind rejected all contexts");
dcerpc_send_pdu_cb_and_free(dce, pdu, -EINVAL, NULL);
return;
}
dcerpc_send_pdu_cb_and_free(dce, pdu, 0, NULL);
}
static int
dcerpc_bind_async(struct dcerpc_context *dce, dcerpc_cb cb,
void *cb_data)
{
struct dcerpc_pdu *pdu;
struct smb2_pdu *smb2_pdu;
struct smb2_ioctl_request req;
struct smb2_iovec iov _U_;
int offset = 0;
struct p_cont_elem_t *pce;
pdu = dcerpc_allocate_pdu(dce, DCERPC_ENCODE, NSE_BUF_SIZE);
if (pdu == NULL) {
return -ENOMEM;
}
pdu->hdr.rpc_vers = 5;
pdu->hdr.rpc_vers_minor = 0;
pdu->hdr.PTYPE = PDU_TYPE_BIND;
pdu->hdr.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
pdu->hdr.packed_drep[0] = dce->packed_drep[0];
pdu->hdr.frag_length = 0;
pdu->hdr.auth_length = 0;
pdu->bind.max_xmit_frag = 32768;
pdu->bind.max_recv_frag = 32768;
pdu->bind.assoc_group_id = 0;
pdu->bind.n_context_elem = dce->smb2->ndr ? 1 : 2;
pdu->bind.p_cont_elem = smb2_alloc_data(dce->smb2, pdu->payload,
pdu->bind.n_context_elem * sizeof(struct p_cont_elem_t));
pce = pdu->bind.p_cont_elem;
if (dce->smb2->ndr == 0 || dce->smb2->ndr == 1) {
pce->p_cont_id = 0;
pce->n_transfer_syn = 1;
pce->abstract_syntax = dce->syntax;
pce->transfer_syntaxes = smb2_alloc_data(
dce->smb2, pdu->payload,
pce->n_transfer_syn * sizeof(struct p_cont_elem_t *));
pce->transfer_syntaxes[0] = &ndr32_syntax;
pce++;
}
if (dce->smb2->ndr == 0 || dce->smb2->ndr == 2) {
pce->p_cont_id = 1;
pce->n_transfer_syn = 1;
pce->abstract_syntax = dce->syntax;
pce->transfer_syntaxes = smb2_alloc_data(
dce->smb2, pdu->payload,
pce->n_transfer_syn * sizeof(struct p_cont_elem_t *));
pce->transfer_syntaxes[0] = &ndr64_syntax;
}
pdu->cb = cb;
pdu->cb_data = cb_data;
iov.buf = pdu->payload;
iov.len = NSE_BUF_SIZE;
iov.free = NULL;
if (dcerpc_pdu_coder(dce, pdu, &iov, &offset)) {
dcerpc_free_pdu(dce, pdu);
return -ENOMEM;
}
iov.len = offset;
memset(&req, 0, sizeof(struct smb2_ioctl_request));
req.ctl_code = SMB2_FSCTL_PIPE_TRANSCEIVE;
memcpy(req.file_id, dce->file_id, SMB2_FD_SIZE);
req.input_count = (uint32_t)iov.len;
req.input = iov.buf;
req.flags = SMB2_0_IOCTL_IS_FSCTL;
smb2_pdu = smb2_cmd_ioctl_async(dce->smb2, &req, smb2_bind_cb, pdu);
if (smb2_pdu == NULL) {
dcerpc_free_pdu(dce, pdu);
return -ENOMEM;
}
smb2_queue_pdu(dce->smb2, smb2_pdu);
return 0;
}
static void
smb2_open_cb(struct smb2_context *smb2, int status,
void *command_data, void *private_data)
{
struct dcerpc_cb_data *data = private_data;
struct smb2_create_reply *rep = command_data;
struct dcerpc_context *dce = data->dce;
if (status != SMB2_STATUS_SUCCESS) {
data->cb(dce, -nterror_to_errno(status),
NULL, data->cb_data);
free(data);
return;
}
memcpy(dce->file_id, rep->file_id, SMB2_FD_SIZE);
status = dcerpc_bind_async(dce, dcerpc_bind_cb, data);
if (status) {
data->cb(dce, status, NULL, data->cb_data);
free(data);
return;
}
return;
}
int
dcerpc_open_async(struct dcerpc_context *dce, dcerpc_cb cb,
void *cb_data)
{
struct smb2_create_request req;
struct smb2_pdu *pdu;
struct dcerpc_cb_data *data;
data = calloc(1, sizeof(struct dcerpc_cb_data));
if (data == NULL) {
smb2_set_error(dce->smb2, "Failed to allocate dcerpc callback "
"data");
return -ENOMEM;
}
data->dce = dce;
data->cb = cb;
data->cb_data = cb_data;
memset(&req, 0, sizeof(struct smb2_create_request));
req.requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
req.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION;
req.desired_access = SMB2_FILE_READ_DATA |
SMB2_FILE_WRITE_DATA |
SMB2_FILE_APPEND_DATA |
SMB2_FILE_READ_EA |
SMB2_FILE_READ_ATTRIBUTES |
SMB2_FILE_WRITE_EA |
SMB2_FILE_WRITE_ATTRIBUTES |
SMB2_READ_CONTROL |
SMB2_SYNCHRONIZE;
req.file_attributes = 0;
req.share_access = SMB2_FILE_SHARE_READ |
SMB2_FILE_SHARE_WRITE |
SMB2_FILE_SHARE_DELETE;
req.create_disposition = SMB2_FILE_OPEN;
req.create_options = 0;
req.name = dce->path;
pdu = smb2_cmd_create_async(dce->smb2, &req, smb2_open_cb, data);
if (pdu == NULL) {
free(data);
return -ENOMEM;
}
smb2_queue_pdu(dce->smb2, pdu);
return 0;
}
const char *
dcerpc_get_error(struct dcerpc_context *dce)
{
return smb2_get_error(dcerpc_get_smb2_context(dce));
}
void
dcerpc_free_data(struct dcerpc_context *dce, void *data)
{
smb2_free_data(dcerpc_get_smb2_context(dce), data);
}
int
dcerpc_pdu_direction(struct dcerpc_pdu *pdu)
{
return pdu->direction;
}
int
dcerpc_align_3264(struct dcerpc_context *ctx, int offset)
{
if (offset < 0) {
return offset;
}
if (ctx->tctx_id) {
offset = (offset + 7) & ~7;
} else {
offset = (offset + 3) & ~3;
}
return offset;
}
/* Used for testing. Override/force the transfer syntax. */
void dcerpc_set_tctx(struct dcerpc_context *ctx, int tctx)
{
ctx->tctx_id = tctx;
}
/* Used for testing. Override/force the transfer syntax. */
void dcerpc_set_endian(struct dcerpc_pdu *pdu, int little_endian)
{
if (little_endian) {
pdu->hdr.packed_drep[0] |= DCERPC_DR_LITTLE_ENDIAN;
} else {
pdu->hdr.packed_drep[0] &= ~DCERPC_DR_LITTLE_ENDIAN;
}
}
int dcerpc_get_cr(struct dcerpc_pdu *pdu)
{
return pdu->is_conformance_run;
}