diff options
Diffstat (limited to 'usr/src/lib/smbsrv/libmlrpc/common/ndr_process.c')
-rw-r--r-- | usr/src/lib/smbsrv/libmlrpc/common/ndr_process.c | 2028 |
1 files changed, 2028 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libmlrpc/common/ndr_process.c b/usr/src/lib/smbsrv/libmlrpc/common/ndr_process.c new file mode 100644 index 0000000000..b64ada2955 --- /dev/null +++ b/usr/src/lib/smbsrv/libmlrpc/common/ndr_process.c @@ -0,0 +1,2028 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Network Data Representation (NDR) is a compatible subset of the DCE RPC + * and MSRPC NDR. NDR is used to move parameters consisting of + * complicated trees of data constructs between an RPC client and server. + */ + +#include <sys/byteorder.h> +#include <strings.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include <smbsrv/libsmb.h> +#include <smbsrv/string.h> +#include <smbsrv/ndr.h> + +#define NDR_STRING_MAX 256 + +#define NDR_IS_UNION(T) \ + (((T)->type_flags & NDR_F_TYPEOP_MASK) == NDR_F_UNION) +#define NDR_IS_STRING(T) \ + (((T)->type_flags & NDR_F_TYPEOP_MASK) == NDR_F_STRING) + +extern struct ndr_typeinfo ndt_s_wchar; + +/* + * The following synopsis describes the terms TOP-MOST, OUTER and INNER. + * + * Each parameter (call arguments and return values) is a TOP-MOST item. + * A TOP-MOST item consists of one or more OUTER items. An OUTER item + * consists of one or more INNER items. There are important differences + * between each kind, which, primarily, have to do with the allocation + * of memory to contain data structures and the order of processing. + * + * This is most easily demonstrated with a short example. + * Consider these structures: + * + * struct top_param { + * long level; + * struct list * head; + * long count; + * }; + * + * struct list { + * struct list * next; + * char * str; // a string + * }; + * + * Now, consider an instance tree like this: + * + * +---------+ +-------+ +-------+ + * |top_param| +--->|list #1| +--->|list #2| + * +---------+ | +-------+ | +-------+ + * | level | | | next ----+ | next --->(NULL) + * | head ----+ | str -->"foo" | str -->"bar" + * | count | | flag | | flag | + * +---------+ +-------+ +-------+ + * + * The DCE(MS)/RPC Stub Data encoding for the tree is the following. + * The vertical bars (|) indicate OUTER construct boundaries. + * + * +-----+----------------------+----------------------+-----+-----+-----+ + * |level|#1.next #1.str #1.flag|#2.next #2.str #2.flag|"bar"|"foo"|count| + * +-----+----------------------+----------------------+-----+-----+-----+ + * level |<----------------------- head -------------------------->|count + * TOP TOP TOP + * + * Here's what to notice: + * + * - The members of the TOP-MOST construct are scattered through the Stub + * Data in the order they occur. This example shows a TOP-MOST construct + * consisting of atomic types (pointers and integers). A construct + * (struct) within the TOP-MOST construct would be contiguous and not + * scattered. + * + * - The members of OUTER constructs are contiguous, which allows for + * non-copied relocated (fixed-up) data structures at the packet's + * destination. We don't do fix-ups here. The pointers within the + * OUTER constructs are processed depth-first in the order that they + * occur. If they were processed breadth first, the sequence would + * be #1,"foo",#2,"bar". This is tricky because OUTER constructs may + * be variable length, and pointers are often encountered before the + * size(s) is known. + * + * - The INNER constructs are simply the members of an OUTER construct. + * + * For comparison, consider how ONC RPC would handle the same tree of + * data. ONC requires very little buffering, while DCE requires enough + * buffer space for the entire message. ONC does atom-by-atom depth-first + * (de)serialization and copy, while DCE allows for constructs to be + * "fixed-up" (relocated) in place at the destination. The packet data + * for the same tree processed by ONC RPC would look like this: + * + * +---------------------------------------------------------------------+ + * |level #1.next #2.next #2.str "bar" #2.flag #1.str "foo" #1.flag count| + * +---------------------------------------------------------------------+ + * TOP #1 #2 #2 bar #2 #1 foo #1 TOP + * + * More details about each TOP-MOST, OUTER, and INNER constructs appear + * throughout this source file near where such constructs are processed. + * + * NDR_REFERENCE + * + * The primary object for NDR is the struct ndr_reference. + * + * An ndr_reference indicates the local datum (i.e. native "C" data + * format), and the element within the Stub Data (contained within the + * RPC PDU (protocol data unit). An ndr_reference also indicates, + * largely as a debugging aid, something about the type of the + * element/datum, and the enclosing construct for the element. The + * ndr_reference's are typically allocated on the stack as locals, + * and the chain of ndr_reference.enclosing references is in reverse + * order of the call graph. + * + * The ndr_reference.datum is a pointer to the local memory that + * contains/receives the value. The ndr_reference.pdu_offset indicates + * where in the Stub Data the value is to be stored/retrieved. + * + * The ndr_reference also contains various parameters to the NDR + * process, such as ndr_reference.size_is, which indicates the size + * of variable length data, or ndr_reference.switch_is, which + * indicates the arm of a union to use. + * + * QUEUE OF OUTER REFERENCES + * + * Some OUTER constructs are variable size. Sometimes (often) we don't + * know the size of the OUTER construct until after pointers have been + * encountered. Hence, we can not begin processing the referent of the + * pointer until after the referring OUTER construct is completely + * processed, i.e. we don't know where to find/put the referent in the + * Stub Data until we know the size of all its predecessors. + * + * This is managed using the queue of OUTER references. The queue is + * anchored in mlndr_stream.outer_queue_head. At any time, + * mlndr_stream.outer_queue_tailp indicates where to put the + * ndr_reference for the next encountered pointer. + * + * Refer to the example above as we illustrate the queue here. In these + * illustrations, the queue entries are not the data structures themselves. + * Rather, they are ndr_reference entries which **refer** to the data + * structures in both the PDU and local memory. + * + * During some point in the processing, the queue looks like this: + * + * outer_current -------v + * outer_queue_head --> list#1 --0 + * outer_queue_tailp ---------& + * + * When the pointer #1.next is encountered, and entry is added to the + * queue, + * + * outer_current -------v + * outer_queue_head --> list#1 --> list#2 --0 + * outer_queue_tailp --------------------& + * + * and the members of #1 continue to be processed, which encounters + * #1.str: + * + * outer_current -------v + * outer_queue_head --> list#1 --> list#2 --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * Upon the completion of list#1, the processing continues by moving + * to mlndr_stream.outer_current->next, and the tail is set to this + * outer member: + * + * outer_current ------------------v + * outer_queue_head --> list#1 --> list#2 --> "foo" --0 + * outer_queue_tailp --------------------& + * + * Space for list#2 is allocated, either in the Stub Data or of local + * memory. When #2.next is encountered, it is found to be the null + * pointer and no reference is added to the queue. When #2.str is + * encountered, it is found to be valid, and a reference is added: + * + * outer_current ------------------v + * outer_queue_head --> list#1 --> list#2 --> "bar" --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * Processing continues in a similar fashion with the string "bar", + * which is variable-length. At this point, memory for "bar" may be + * malloc()ed during NDR_M_OP_UNMARSHALL: + * + * outer_current -----------------------------v + * outer_queue_head --> list#1 --> list#2 --> "bar" --> "foo" --0 + * outer_queue_tailp ------------------------------& + * + * And finishes on string "foo". Notice that because "bar" is a + * variable length string, and we don't know the PDU offset for "foo" + * until we reach this point. + * + * When the queue is drained (current->next==0), processing continues + * with the next TOP-MOST member. + * + * The queue of OUTER constructs manages the variable-length semantics + * of OUTER constructs and satisfies the depth-first requirement. + * We allow the queue to linger until the entire TOP-MOST structure is + * processed as an aid to debugging. + */ + +static struct ndr_reference *mlndr_enter_outer_queue(struct ndr_reference *); +extern int mlndr__ulong(struct ndr_reference *); + +/* + * TOP-MOST ELEMENTS + * + * This is fundamentally the first OUTER construct of the parameter, + * possibly followed by more OUTER constructs due to pointers. The + * datum (local memory) for TOP-MOST constructs (structs) is allocated + * by the caller of NDR. + * + * After the element is transferred, the outer_queue is drained. + * + * All we have to do is add an entry to the outer_queue for this + * top-most member, and commence the outer_queue processing. + */ +int +mlndo_process(struct mlndr_stream *mlnds, struct ndr_typeinfo *ti, + char *datum) +{ + struct ndr_reference myref; + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.datum = datum; + myref.name = "PROCESS"; + myref.ti = ti; + + return (mlndr_topmost(&myref)); +} + +int +mlndo_operation(struct mlndr_stream *mlnds, struct ndr_typeinfo *ti, + int opnum, char *datum) +{ + struct ndr_reference myref; + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.datum = datum; + myref.name = "OPERATION"; + myref.ti = ti; + myref.inner_flags = NDR_F_SWITCH_IS; + myref.switch_is = opnum; + + if (ti->type_flags != NDR_F_INTERFACE) { + NDR_SET_ERROR(&myref, NDR_ERR_NOT_AN_INTERFACE); + return (0); + } + + return ((*ti->ndr_func)(&myref)); +} + +int +mlndr_params(struct ndr_reference *params_ref) +{ + struct ndr_typeinfo *ti = params_ref->ti; + + if (ti->type_flags == NDR_F_OPERATION) + return (*ti->ndr_func) (params_ref); + else + return (mlndr_topmost(params_ref)); +} + +int +mlndr_topmost(struct ndr_reference *top_ref) +{ + struct mlndr_stream *mlnds; + struct ndr_typeinfo *ti; + struct ndr_reference *outer_ref = 0; + int is_varlen; + int is_string; + int error; + int rc; + unsigned n_fixed; + int params; + + assert(top_ref); + assert(top_ref->stream); + assert(top_ref->ti); + + mlnds = top_ref->stream; + ti = top_ref->ti; + + is_varlen = ti->pdu_size_variable_part; + is_string = NDR_IS_STRING(ti); + + assert(mlnds->outer_queue_tailp && !*mlnds->outer_queue_tailp); + assert(!mlnds->outer_current); + + params = top_ref->inner_flags & NDR_F_PARAMS_MASK; + + switch (params) { + case NDR_F_NONE: + case NDR_F_SWITCH_IS: + if (is_string || is_varlen) { + error = NDR_ERR_TOPMOST_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + n_fixed = ti->pdu_size_fixed_part; + break; + + case NDR_F_SIZE_IS: + error = NDR_ERR_TOPMOST_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + + case NDR_F_DIMENSION_IS: + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + n_fixed = ti->pdu_size_fixed_part * top_ref->dimension_is; + break; + + case NDR_F_IS_POINTER: + case NDR_F_IS_POINTER+NDR_F_SIZE_IS: + n_fixed = 4; + break; + + case NDR_F_IS_REFERENCE: + case NDR_F_IS_REFERENCE+NDR_F_SIZE_IS: + n_fixed = 0; + break; + + default: + error = NDR_ERR_OUTER_PARAMS_BAD; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + + outer_ref = mlndr_enter_outer_queue(top_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* + * Hand-craft the first OUTER construct and directly call + * mlndr_inner(). Then, run the outer_queue. We do this + * because mlndr_outer() wants to malloc() memory for + * the construct, and we already have the memory. + */ + + /* move the flags, etc, around again, undoes enter_outer_queue() */ + outer_ref->inner_flags = top_ref->inner_flags; + outer_ref->outer_flags = 0; + outer_ref->datum = top_ref->datum; + + /* All outer constructs start on a mod4 (longword) boundary */ + if (!mlndr_outer_align(outer_ref)) + return (0); /* error already set */ + + /* Regardless of what it is, this is where it starts */ + outer_ref->pdu_offset = mlnds->pdu_scan_offset; + + rc = mlndr_outer_grow(outer_ref, n_fixed); + if (!rc) + return (0); /* error already set */ + + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_fixed; + + /* set-up outer_current, as though run_outer_queue() was doing it */ + mlnds->outer_current = outer_ref; + mlnds->outer_queue_tailp = &mlnds->outer_current->next; + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + /* do the topmost member */ + rc = mlndr_inner(outer_ref); + if (!rc) + return (0); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + /* advance, as though run_outer_queue() was doing it */ + mlnds->outer_current = mlnds->outer_current->next; + return (mlndr_run_outer_queue(mlnds)); +} + +static struct ndr_reference * +mlndr_enter_outer_queue(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + struct ndr_reference *outer_ref; + + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + outer_ref = (struct ndr_reference *) + MLNDS_MALLOC(mlnds, sizeof (*outer_ref), arg_ref); + if (!outer_ref) { + NDR_SET_ERROR(arg_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + + *outer_ref = *arg_ref; + + /* move advice in inner_flags to outer_flags */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->inner_flags = 0; + outer_ref->enclosing = mlnds->outer_current; + outer_ref->backptr = 0; + outer_ref->datum = 0; + + assert(mlnds->outer_queue_tailp); + + outer_ref->next = *mlnds->outer_queue_tailp; + *mlnds->outer_queue_tailp = outer_ref; + mlnds->outer_queue_tailp = &outer_ref->next; + return (outer_ref); +} + +int +mlndr_run_outer_queue(struct mlndr_stream *mlnds) +{ + while (mlnds->outer_current) { + mlnds->outer_queue_tailp = &mlnds->outer_current->next; + + if (!mlndr_outer(mlnds->outer_current)) + return (0); + + mlnds->outer_current = mlnds->outer_current->next; + } + + return (1); +} + +/* + * OUTER CONSTRUCTS + * + * OUTER constructs are where the real work is, which stems from the + * variable-length potential. + * + * DCE(MS)/RPC VARIABLE LENGTH -- CONFORMANT, VARYING, VARYING/CONFORMANT + * + * DCE(MS)/RPC provides for three forms of variable length: CONFORMANT, + * VARYING, and VARYING/CONFORMANT. + * + * What makes this so tough is that the variable-length array may be well + * encapsulated within the outer construct. Further, because DCE(MS)/RPC + * tries to keep the constructs contiguous in the data stream, the sizing + * information precedes the entire OUTER construct. The sizing information + * must be used at the appropriate time, which can be after many, many, + * many fixed-length elements. During IDL type analysis, we know in + * advance constructs that encapsulate variable-length constructs. So, + * we know when we have a sizing header and when we don't. The actual + * semantics of the header are largely deferred. + * + * Currently, VARYING constructs are not implemented but they are described + * here in case they have to be implemented in the future. Similarly, + * DCE(MS)/RPC provides for multi-dimensional arrays, which are currently + * not implemented. Only one-dimensional, variable-length arrays are + * supported. + * + * CONFORMANT CONSTRUCTS -- VARIABLE LENGTH ARRAYS START THE SHOW + * + * All variable-length values are arrays. These arrays may be embedded + * well within another construct. However, a variable-length construct + * may ONLY appear as the last member of an enclosing construct. Example: + * + * struct credentials { + * ulong uid, gid; + * ulong n_gids; + * [size_is(n_gids)] + * ulong gids[*]; // variable-length. + * }; + * + * CONFORMANT constructs have a dynamic size in local memory and in the + * PDU. The CONFORMANT quality is indicated by the [size_is()] advice. + * CONFORMANT constructs have the following header: + * + * struct conformant_header { + * ulong size_is; + * }; + * + * (Multi-dimensional CONFORMANT arrays have a similar header for each + * dimension - not implemented). + * + * Example CONFORMANT construct: + * + * struct user { + * char * name; + * struct credentials cred; // see above + * }; + * + * Consider the data tree: + * + * +--------+ + * | user | + * +--------+ + * | name ----> "fred" (the string is a different OUTER) + * | uid | + * | gid | + * | n_gids | for example, 3 + * | gids[0]| + * | gids[1]| + * | gids[2]| + * +--------+ + * + * The OUTER construct in the Stub Data would be: + * + * +---+---------+---------------------------------------------+ + * |pad|size_is=3 name uid gid n_gids=3 gids[0] gids[1] gids[2]| + * +---+---------+---------------------------------------------+ + * szing hdr|user |<-------------- user.cred ------------>| + * |<--- fixed-size ---->|<----- conformant ---->| + * + * The ndr_typeinfo for struct user will have: + * pdu_fixed_size_part = 16 four long words (name uid gid n_gids) + * pdu_variable_size_part = 4 per element, sizeof gids[0] + * + * VARYING CONSTRUCTS -- NOT IMPLEMENTED + * + * VARYING constructs have the following header: + * + * struct varying_header { + * ulong first_is; + * ulong length_is; + * }; + * + * This indicates which interval of an array is significant. + * Non-intersecting elements of the array are undefined and usually + * zero-filled. The first_is parameter for C arrays is always 0 for + * the first element. + * + * N.B. Constructs may contain one CONFORMANT element, which is always + * last, but may contain many VARYING elements, which can be anywhere. + * + * VARYING CONFORMANT constructs have the sizing headers arranged like + * this: + * + * struct conformant_header all_conformant[N_CONFORMANT_DIM]; + * struct varying_header all_varying[N_VARYING_ELEMS_AND_DIMS]; + * + * The sizing header is immediately followed by the values for the + * construct. Again, we don't support more than one dimension and + * we don't support VARYING constructs at this time. + * + * A good example of a VARYING/CONFORMANT data structure is the UNIX + * directory entry: + * + * struct dirent { + * ushort reclen; + * ushort namlen; + * ulong inum; + * [size_is(reclen-8) length_is(namlen+1)] // -(2+2+4), +1 for NUL + * uchar name[*]; + * }; + * + * + * STRINGS ARE A SPECIAL CASE + * + * Strings are handled specially. MS/RPC uses VARYING/CONFORMANT structures + * for strings. This is a simple one-dimensional variable-length array, + * typically with its last element all zeroes. We handle strings with the + * header: + * + * struct string_header { + * ulong size_is; + * ulong first_is; // always 0 + * ulong length_is; // always same as size_is + * }; + * + * If general support for VARYING and VARYING/CONFORMANT mechanisms is + * implemented, we probably won't need the strings special case. + */ +int +mlndr_outer(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int error = NDR_ERR_OUTER_PARAMS_BAD; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + NDR_TATTLE(outer_ref, "--OUTER--"); + + /* All outer constructs start on a mod4 (longword) boundary */ + if (!mlndr_outer_align(outer_ref)) + return (0); /* error already set */ + + /* Regardless of what it is, this is where it starts */ + outer_ref->pdu_offset = mlnds->pdu_scan_offset; + + if (is_union) { + error = NDR_ERR_OUTER_UNION_ILLEGAL; + NDR_SET_ERROR(outer_ref, error); + return (0); + } + + switch (params) { + case NDR_F_NONE: + if (is_string) + return (mlndr_outer_string(outer_ref)); + if (is_varlen) + return (mlndr_outer_conformant_construct(outer_ref)); + + return (mlndr_outer_fixed(outer_ref)); + break; + + case NDR_F_SIZE_IS: + case NDR_F_DIMENSION_IS: + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + break; + } + + if (params == NDR_F_SIZE_IS) + return (mlndr_outer_conformant_array(outer_ref)); + else + return (mlndr_outer_fixed_array(outer_ref)); + break; + + default: + error = NDR_ERR_OUTER_PARAMS_BAD; + break; + } + + /* + * If we get here, something is wrong. Most likely, + * the params flags do not match. + */ + NDR_SET_ERROR(outer_ref, error); + return (0); +} + +int +mlndr_outer_fixed(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_NONE); + + /* no header for this */ + n_hdr = 0; + + /* fixed part -- exactly one of these */ + n_fixed = ti->pdu_size_fixed_part; + assert(n_fixed > 0); + + /* variable part -- exactly none of these */ + n_variable = 0; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "FIXED-VALUE"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_NONE; + + myref.pdu_offset = outer_ref->pdu_offset; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_fixed_array(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_DIMENSION_IS); + + /* no header for this */ + n_hdr = 0; + + /* fixed part -- exactly dimension_is of these */ + n_fixed = ti->pdu_size_fixed_part * outer_ref->dimension_is; + assert(n_fixed > 0); + + /* variable part -- exactly none of these */ + n_variable = 0; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "FIXED-ARRAY"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_DIMENSION_IS; + myref.dimension_is = outer_ref->dimension_is; + + myref.pdu_offset = outer_ref->pdu_offset; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_conformant_array(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(!is_varlen && !is_string && !is_union); + assert(params == NDR_F_SIZE_IS); + + /* conformant header for this */ + n_hdr = 4; + + /* fixed part -- exactly none of these */ + n_fixed = 0; + + /* variable part -- exactly size_of of these */ + /* notice that it is the **fixed** size of the ti */ + n_variable = ti->pdu_size_fixed_part * outer_ref->size_is; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + size_is = outer_ref->size_is; + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + rc = mlndr_outer_peek_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + if (size_is != outer_ref->size_is) { + NDR_SET_ERROR(outer_ref, + NDR_ERR_SIZE_IS_MISMATCH_PDU); + return (0); + } + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "CONFORMANT-ARRAY"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_SIZE_IS; + myref.size_is = outer_ref->size_is; + + myref.inner_flags = NDR_F_DIMENSION_IS; /* convenient */ + myref.dimension_is = outer_ref->size_is; /* convenient */ + + myref.pdu_offset = outer_ref->pdu_offset + 4; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + outer_ref->type_flags = NDR_F_NONE; + outer_ref->inner_flags = NDR_F_NONE; + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_conformant_construct(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(is_varlen && !is_string && !is_union); + assert(params == NDR_F_NONE); + + /* conformant header for this */ + n_hdr = 4; + + /* fixed part -- exactly one of these */ + n_fixed = ti->pdu_size_fixed_part; + + /* variable part -- exactly size_of of these */ + n_variable = 0; /* 0 for the moment */ + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + /* For the moment, grow enough for the fixed-size part */ + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + /* + * We don't know the size yet. We have to wait for + * it. Proceed with the fixed-size part, and await + * the call to mlndr_size_is(). + */ + size_is = 0; + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + valp = outer_ref->datum; + assert(valp); + if (outer_ref->backptr) { + assert(valp == *outer_ref->backptr); + } + break; + + case NDR_M_OP_UNMARSHALL: + /* + * We know the size of the variable part because + * of the CONFORMANT header. We will verify + * the header against the [size_is(X)] advice + * later when mlndr_size_is() is called. + */ + rc = mlndr_outer_peek_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + + /* recalculate metrics */ + n_variable = size_is * ti->pdu_size_variable_part; + n_pdu_total = n_hdr + n_fixed + n_variable; + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + outer_ref->size_is = size_is; /* verified later */ + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "CONFORMANT-CONSTRUCT"; + myref.outer_flags = NDR_F_NONE; + myref.inner_flags = NDR_F_NONE; + myref.size_is = outer_ref->size_is; + + myref.pdu_offset = outer_ref->pdu_offset + 4; + outer_ref->pdu_end_offset = outer_ref->pdu_offset + n_pdu_total; + + outer_ref->type_flags = NDR_F_SIZE_IS; /* indicate pending */ + outer_ref->inner_flags = NDR_F_NONE; /* indicate pending */ + + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + + if (outer_ref->inner_flags != NDR_F_SIZE_IS) { + NDR_SET_ERROR(&myref, NDR_ERR_SIZE_IS_MISMATCH_AFTER); + return (0); + } + + return (1); +} + +int +mlndr_size_is(struct ndr_reference *ref) +{ + struct mlndr_stream *mlnds = ref->stream; + struct ndr_reference *outer_ref = mlnds->outer_current; + struct ndr_typeinfo *ti = outer_ref->ti; + unsigned long size_is; + int rc; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_pdu_total; + + assert(ref->inner_flags & NDR_F_SIZE_IS); + size_is = ref->size_is; + + if (outer_ref->type_flags != NDR_F_SIZE_IS) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_UNEXPECTED); + return (0); + } + + if (outer_ref->inner_flags & NDR_F_SIZE_IS) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_DUPLICATED); + return (0); + } + + /* repeat metrics, see mlndr_conformant_construct() above */ + n_hdr = 4; + n_fixed = ti->pdu_size_fixed_part; + n_variable = size_is * ti->pdu_size_variable_part; + n_pdu_total = n_hdr + n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + /* + * We have to set the sizing header and extend + * the size of the PDU (already done). + */ + rc = mlndr_outer_poke_sizing(outer_ref, 0, &size_is); + if (!rc) + return (0); /* error already set */ + break; + + case NDR_M_OP_UNMARSHALL: + /* + * Allocation done during mlndr_conformant_construct(). + * All we are doing here is verifying that the + * intended size (ref->size_is) matches the sizing header. + */ + if (size_is != outer_ref->size_is) { + NDR_SET_ERROR(ref, NDR_ERR_SIZE_IS_MISMATCH_PDU); + return (0); + } + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + outer_ref->inner_flags |= NDR_F_SIZE_IS; + outer_ref->size_is = ref->size_is; + return (1); +} + +int +mlndr_outer_string(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + struct ndr_typeinfo *ti = outer_ref->ti; + struct ndr_reference myref; + char *valp = NULL; + unsigned is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int is_string = NDR_IS_STRING(ti); + int rc; + unsigned n_zeroes; + unsigned ix; + unsigned long size_is; + unsigned long first_is; + unsigned long length_is; + unsigned n_hdr; + unsigned n_fixed; + unsigned n_variable; + unsigned n_alloc; + unsigned n_pdu_total; + int params; + + params = outer_ref->outer_flags & NDR_F_PARAMS_MASK; + + assert(is_varlen && is_string && !is_union); + assert(params == NDR_F_NONE); + + /* string header for this: size_is first_is length_is */ + n_hdr = 12; + + /* fixed part -- exactly none of these */ + n_fixed = 0; + + if (!mlndr_outer_grow(outer_ref, n_hdr)) + return (0); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + valp = outer_ref->datum; + assert(valp); + + if (outer_ref->backptr) + assert(valp == *outer_ref->backptr); + + if (ti == &ndt_s_wchar) { + /* + * size_is is the number of characters in the string, + * including the null. We assume valp is UTF-8 encoded. + * We can use mts_wcequiv_strlen for ASCII, extended + * ASCII or Unicode (UCS-2). + */ + size_is = (mts_wcequiv_strlen(valp) / + sizeof (mts_wchar_t)) + 1; + + if (size_is > NDR_STRING_MAX) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRLEN); + return (0); + } + } else { + valp = outer_ref->datum; + n_zeroes = 0; + for (ix = 0; ix < 1024; ix++) { + if (valp[ix] == 0) { + n_zeroes++; + if (n_zeroes >= is_varlen && + ix % is_varlen == 0) { + break; + } + } else { + n_zeroes = 0; + } + } + if (ix >= 1024) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRLEN); + return (0); + } + size_is = ix+1; + } + + first_is = 0; + + if (mlnds->flags & MLNDS_F_NOTERM) + length_is = size_is - 1; + else + length_is = size_is; + + if (!mlndr_outer_poke_sizing(outer_ref, 0, &size_is) || + !mlndr_outer_poke_sizing(outer_ref, 4, &first_is) || + !mlndr_outer_poke_sizing(outer_ref, 8, &length_is)) + return (0); /* error already set */ + break; + + case NDR_M_OP_UNMARSHALL: + if (!mlndr_outer_peek_sizing(outer_ref, 0, &size_is) || + !mlndr_outer_peek_sizing(outer_ref, 4, &first_is) || + !mlndr_outer_peek_sizing(outer_ref, 8, &length_is)) + return (0); /* error already set */ + + /* + * In addition to the first_is check, we used to check that + * size_is or size_is-1 was equal to length_is but Windows95 + * doesn't conform to this "rule" (see variable part below). + * The srvmgr tool for Windows95 sent the following values + * for a path string: + * + * size_is = 261 (0x105) + * first_is = 0 + * length_is = 53 (0x35) + * + * The length_is was correct (for the given path) but the + * size_is was the maximum path length rather than being + * related to length_is. + */ + if (first_is != 0) { + NDR_SET_ERROR(outer_ref, NDR_ERR_STRING_SIZING); + return (0); + } + + if (ti == &ndt_s_wchar) { + /* + * Decoding Unicode to UTF-8; we need to allow + * for the maximum possible char size. It would + * be nice to use mbequiv_strlen but the string + * may not be null terminated. + */ + n_alloc = (size_is + 1) * MTS_MB_CHAR_MAX; + } else { + n_alloc = (size_is + 1) * is_varlen; + } + + valp = MLNDS_MALLOC(mlnds, n_alloc, outer_ref); + if (!valp) { + NDR_SET_ERROR(outer_ref, NDR_ERR_MALLOC_FAILED); + return (0); + } + + bzero(valp, (size_is+1) * is_varlen); + + if (outer_ref->backptr) + *outer_ref->backptr = valp; + outer_ref->datum = valp; + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + /* + * Variable part - exactly length_is of these. + * + * Usually, length_is is same as size_is and includes nul. + * Some protocols use length_is = size_is-1, and length_is does + * not include the nul (which is more consistent with DCE spec). + * If the length_is is 0, there is no data following the + * sizing header, regardless of size_is. + */ + n_variable = length_is * is_varlen; + + /* sum them up to determine the PDU space required */ + n_pdu_total = n_hdr + n_fixed + n_variable; + + /* similar sum to determine how much local memory is required */ + n_alloc = n_fixed + n_variable; + + rc = mlndr_outer_grow(outer_ref, n_pdu_total); + if (!rc) + return (rc); /* error already set */ + + if (length_is > 0) { + bzero(&myref, sizeof (myref)); + myref.stream = mlnds; + myref.enclosing = outer_ref; + myref.ti = outer_ref->ti; + myref.datum = outer_ref->datum; + myref.name = "OUTER-STRING"; + myref.outer_flags = NDR_F_IS_STRING; + myref.inner_flags = NDR_F_NONE; + + /* + * Set up size_is and strlen_is for mlndr_s_wchar. + */ + myref.size_is = size_is; + myref.strlen_is = length_is; + } + + myref.pdu_offset = outer_ref->pdu_offset + 12; + + /* + * Don't try to decode empty strings. + */ + if ((size_is == 0) && (first_is == 0) && (length_is == 0)) { + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); + } + + if ((size_is != 0) && (length_is != 0)) { + rc = mlndr_inner(&myref); + if (!rc) + return (rc); /* error already set */ + } + + mlnds->pdu_scan_offset = outer_ref->pdu_end_offset; + return (1); +} + +int +mlndr_outer_peek_sizing(struct ndr_reference *outer_ref, unsigned offset, + unsigned long *sizing_p) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_offset; + int rc; + + pdu_offset = outer_ref->pdu_offset + offset; + + if (pdu_offset < mlnds->outer_current->pdu_offset || + pdu_offset > mlnds->outer_current->pdu_end_offset || + pdu_offset+4 > mlnds->outer_current->pdu_end_offset) { + NDR_SET_ERROR(outer_ref, NDR_ERR_BOUNDS_CHECK); + return (0); + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + NDR_SET_ERROR(outer_ref, NDR_ERR_UNIMPLEMENTED); + return (0); + + case NDR_M_OP_UNMARSHALL: + rc = MLNDS_GET_PDU(mlnds, pdu_offset, 4, (char *)sizing_p, + mlnds->swap, outer_ref); + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +int +mlndr_outer_poke_sizing(struct ndr_reference *outer_ref, unsigned offset, + unsigned long *sizing_p) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_offset; + int rc; + + pdu_offset = outer_ref->pdu_offset + offset; + + if (pdu_offset < mlnds->outer_current->pdu_offset || + pdu_offset > mlnds->outer_current->pdu_end_offset || + pdu_offset+4 > mlnds->outer_current->pdu_end_offset) { + NDR_SET_ERROR(outer_ref, NDR_ERR_BOUNDS_CHECK); + return (0); + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PUT_PDU(mlnds, pdu_offset, 4, (char *)sizing_p, + mlnds->swap, outer_ref); + break; + + case NDR_M_OP_UNMARSHALL: + NDR_SET_ERROR(outer_ref, NDR_ERR_UNIMPLEMENTED); + return (0); + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +/* + * All OUTER constructs begin on a mod4 (dword) boundary - except + * for the ones that don't: some MSRPC calls appear to use word or + * packed alignment. Strings appear to be dword aligned. + */ +int +mlndr_outer_align(struct ndr_reference *outer_ref) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + int rc; + unsigned n_pad; + unsigned align; + + if (outer_ref->packed_alignment && outer_ref->ti != &ndt_s_wchar) { + align = outer_ref->ti->alignment; + n_pad = ((align + 1) - mlnds->pdu_scan_offset) & align; + } else { + n_pad = (4 - mlnds->pdu_scan_offset) & 3; + } + + if (n_pad == 0) + return (1); /* already aligned, often the case */ + + if (!mlndr_outer_grow(outer_ref, n_pad)) + return (0); /* error already set */ + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PAD_PDU(mlnds, + mlnds->pdu_scan_offset, n_pad, outer_ref); + if (!rc) { + NDR_SET_ERROR(outer_ref, NDR_ERR_PAD_FAILED); + return (0); + } + break; + + case NDR_M_OP_UNMARSHALL: + break; + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + mlnds->pdu_scan_offset += n_pad; + return (1); +} + +int +mlndr_outer_grow(struct ndr_reference *outer_ref, unsigned n_total) +{ + struct mlndr_stream *mlnds = outer_ref->stream; + unsigned long pdu_want_size; + int rc, is_ok = 0; + + pdu_want_size = mlnds->pdu_scan_offset + n_total; + + if (pdu_want_size <= mlnds->pdu_max_size) { + is_ok = 1; + } + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + if (is_ok) + break; + rc = MLNDS_GROW_PDU(mlnds, pdu_want_size, outer_ref); + if (!rc) { + NDR_SET_ERROR(outer_ref, NDR_ERR_GROW_FAILED); + return (0); + } + break; + + case NDR_M_OP_UNMARSHALL: + if (is_ok) + break; + NDR_SET_ERROR(outer_ref, NDR_ERR_UNDERFLOW); + return (0); + + default: + NDR_SET_ERROR(outer_ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + if (mlnds->pdu_size < pdu_want_size) + mlnds->pdu_size = pdu_want_size; + + outer_ref->pdu_end_offset = pdu_want_size; + return (1); +} + +/* + * INNER ELEMENTS + * + * The local datum (arg_ref->datum) already exists, there is no need to + * malloc() it. The datum should point at a member of a structure. + * + * For the most part, mlndr_inner() and its helpers are just a sanity + * check. The underlying ti->ndr_func() could be called immediately + * for non-pointer elements. For the sake of robustness, we detect + * run-time errors here. Most of the situations this protects against + * have already been checked by the IDL compiler. This is also a + * common point for processing of all data, and so is a convenient + * place to work from for debugging. + */ +int +mlndr_inner(struct ndr_reference *arg_ref) +{ + struct ndr_typeinfo *ti = arg_ref->ti; + int is_varlen = ti->pdu_size_variable_part; + int is_union = NDR_IS_UNION(ti); + int error = NDR_ERR_INNER_PARAMS_BAD; + int params; + + params = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + + switch (params) { + case NDR_F_NONE: + if (is_union) { + error = NDR_ERR_SWITCH_VALUE_MISSING; + break; + } + return (*ti->ndr_func)(arg_ref); + break; + + case NDR_F_SIZE_IS: + case NDR_F_DIMENSION_IS: + case NDR_F_IS_POINTER+NDR_F_SIZE_IS: /* pointer to something */ + case NDR_F_IS_REFERENCE+NDR_F_SIZE_IS: /* pointer to something */ + if (is_varlen) { + error = NDR_ERR_ARRAY_VARLEN_ILLEGAL; + break; + } + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + if (params & NDR_F_IS_POINTER) + return (mlndr_inner_pointer(arg_ref)); + else if (params & NDR_F_IS_REFERENCE) + return (mlndr_inner_reference(arg_ref)); + else + return (mlndr_inner_array(arg_ref)); + break; + + case NDR_F_IS_POINTER: /* type is pointer to one something */ + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + return (mlndr_inner_pointer(arg_ref)); + break; + + case NDR_F_IS_REFERENCE: /* type is pointer to one something */ + if (is_union) { + error = NDR_ERR_ARRAY_UNION_ILLEGAL; + break; + } + return (mlndr_inner_reference(arg_ref)); + break; + + case NDR_F_SWITCH_IS: + if (!is_union) { + error = NDR_ERR_SWITCH_VALUE_ILLEGAL; + break; + } + return (*ti->ndr_func)(arg_ref); + break; + + default: + error = NDR_ERR_INNER_PARAMS_BAD; + break; + } + + /* + * If we get here, something is wrong. Most likely, + * the params flags do not match + */ + NDR_SET_ERROR(arg_ref, error); + return (0); +} + +int +mlndr_inner_pointer(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + char **valpp = (char **)arg_ref->datum; + struct ndr_reference *outer_ref; + + if (!mlndr__ulong(arg_ref)) + return (0); /* error */ + if (!*valpp) + return (1); /* NULL pointer */ + + outer_ref = mlndr_enter_outer_queue(arg_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* move advice in inner_flags to outer_flags sans pointer */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->outer_flags &= ~NDR_F_IS_POINTER; +#ifdef NDR_INNER_NOT_YET + outer_ref->outer_flags |= NDR_F_BACKPTR; + if (outer_ref->outer_flags & NDR_F_SIZE_IS) { + outer_ref->outer_flags |= NDR_F_ARRAY+NDR_F_CONFORMANT; + } +#endif /* NDR_INNER_NOT_YET */ + + outer_ref->backptr = valpp; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + outer_ref->datum = *valpp; + break; + + case NDR_M_OP_UNMARSHALL: + /* + * This is probably wrong if the application allocated + * memory in advance. Indicate no value for now. + * ONC RPC handles this case. + */ + *valpp = 0; + outer_ref->datum = 0; + break; + } + + return (1); /* pointer dereference scheduled */ +} + +int +mlndr_inner_reference(struct ndr_reference *arg_ref) +{ + struct mlndr_stream *mlnds = arg_ref->stream; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + char **valpp = (char **)arg_ref->datum; + struct ndr_reference *outer_ref; + + outer_ref = mlndr_enter_outer_queue(arg_ref); + if (!outer_ref) + return (0); /* error already set */ + + /* move advice in inner_flags to outer_flags sans pointer */ + outer_ref->outer_flags = arg_ref->inner_flags & NDR_F_PARAMS_MASK; + outer_ref->outer_flags &= ~NDR_F_IS_REFERENCE; +#ifdef NDR_INNER_REF_NOT_YET + outer_ref->outer_flags |= NDR_F_BACKPTR; + if (outer_ref->outer_flags & NDR_F_SIZE_IS) { + outer_ref->outer_flags |= NDR_F_ARRAY+NDR_F_CONFORMANT; + } +#endif /* NDR_INNER_REF_NOT_YET */ + + outer_ref->backptr = valpp; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + outer_ref->datum = *valpp; + break; + + case NDR_M_OP_UNMARSHALL: + /* + * This is probably wrong if the application allocated + * memory in advance. Indicate no value for now. + * ONC RPC handles this case. + */ + *valpp = 0; + outer_ref->datum = 0; + break; + } + + return (1); /* pointer dereference scheduled */ +} + +int +mlndr_inner_array(struct ndr_reference *encl_ref) +{ + struct ndr_typeinfo *ti = encl_ref->ti; + struct ndr_reference myref; + unsigned long pdu_offset = encl_ref->pdu_offset; + unsigned long n_elem; + unsigned long i; + char name[30]; + + if (encl_ref->inner_flags & NDR_F_SIZE_IS) { + /* now is the time to check/set size */ + if (!mlndr_size_is(encl_ref)) + return (0); /* error already set */ + n_elem = encl_ref->size_is; + } else { + assert(encl_ref->inner_flags & NDR_F_DIMENSION_IS); + n_elem = encl_ref->dimension_is; + } + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = ti; + myref.inner_flags = NDR_F_NONE; + + for (i = 0; i < n_elem; i++) { + (void) sprintf(name, "[%lu]", i); + myref.name = name; + myref.pdu_offset = pdu_offset + i * ti->pdu_size_fixed_part; + myref.datum = encl_ref->datum + i * ti->c_size_fixed_part; + + if (!mlndr_inner(&myref)) + return (0); + } + + return (1); +} + + +/* + * BASIC TYPES + */ +#define MAKE_BASIC_TYPE_BASE(TYPE, SIZE) \ + extern int mlndr_##TYPE(struct ndr_reference *encl_ref); \ + struct ndr_typeinfo ndt_##TYPE = { \ + 1, /* NDR version */ \ + (SIZE)-1, /* alignment */ \ + NDR_F_NONE, /* flags */ \ + mlndr_##TYPE, /* ndr_func */ \ + SIZE, /* pdu_size_fixed_part */ \ + 0, /* pdu_size_variable_part */ \ + SIZE, /* c_size_fixed_part */ \ + 0, /* c_size_variable_part */ \ + }; \ + int mlndr_##TYPE(struct ndr_reference *ref) { \ + return (mlndr_basic_integer(ref, SIZE)); \ +} + +#define MAKE_BASIC_TYPE_STRING(TYPE, SIZE) \ + extern int mlndr_s##TYPE(struct ndr_reference *encl_ref); \ + struct ndr_typeinfo ndt_s##TYPE = { \ + 1, /* NDR version */ \ + (SIZE)-1, /* alignment */ \ + NDR_F_STRING, /* flags */ \ + mlndr_s##TYPE, /* ndr_func */ \ + 0, /* pdu_size_fixed_part */ \ + SIZE, /* pdu_size_variable_part */ \ + 0, /* c_size_fixed_part */ \ + SIZE, /* c_size_variable_part */ \ + }; \ + int mlndr_s##TYPE(struct ndr_reference *ref) { \ + return (mlndr_string_basic_integer(ref, &ndt_##TYPE)); \ +} + +#define MAKE_BASIC_TYPE(TYPE, SIZE) \ + MAKE_BASIC_TYPE_BASE(TYPE, SIZE) \ + MAKE_BASIC_TYPE_STRING(TYPE, SIZE) + +extern int +mlndr_basic_integer(struct ndr_reference *ref, unsigned size); + +extern int +mlndr_string_basic_integer(struct ndr_reference *encl_ref, + struct ndr_typeinfo *type_under); + + +MAKE_BASIC_TYPE(_char, 1) +MAKE_BASIC_TYPE(_uchar, 1) +MAKE_BASIC_TYPE(_short, 2) +MAKE_BASIC_TYPE(_ushort, 2) +MAKE_BASIC_TYPE(_long, 4) +MAKE_BASIC_TYPE(_ulong, 4) + +MAKE_BASIC_TYPE_BASE(_wchar, 2) + +int +mlndr_basic_integer(struct ndr_reference *ref, unsigned size) +{ + struct mlndr_stream *mlnds = ref->stream; + char *valp = (char *)ref->datum; + int rc; + + switch (mlnds->m_op) { + case NDR_M_OP_MARSHALL: + rc = MLNDS_PUT_PDU(mlnds, ref->pdu_offset, size, + valp, mlnds->swap, ref); + break; + + case NDR_M_OP_UNMARSHALL: + rc = MLNDS_GET_PDU(mlnds, ref->pdu_offset, size, + valp, mlnds->swap, ref); + break; + + default: + NDR_SET_ERROR(ref, NDR_ERR_M_OP_INVALID); + return (0); + } + + return (rc); +} + +int +mlndr_string_basic_integer(struct ndr_reference *encl_ref, + struct ndr_typeinfo *type_under) +{ + unsigned long pdu_offset = encl_ref->pdu_offset; + unsigned size = type_under->pdu_size_fixed_part; + char *valp; + struct ndr_reference myref; + unsigned long i; + long sense = 0; + char name[30]; + + assert(size != 0); + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = type_under; + myref.inner_flags = NDR_F_NONE; + myref.name = name; + + for (i = 0; i < NDR_STRING_MAX; i++) { + (void) sprintf(name, "[%lu]", i); + myref.pdu_offset = pdu_offset + i * size; + valp = encl_ref->datum + i * size; + myref.datum = valp; + + if (!mlndr_inner(&myref)) + return (0); + + switch (size) { + case 1: sense = *valp; break; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + case 2: sense = *(short *)valp; break; + /*LINTED E_BAD_PTR_CAST_ALIGN*/ + case 4: sense = *(long *)valp; break; + } + + if (!sense) + break; + } + + return (1); +} + + +extern int mlndr_s_wchar(struct ndr_reference *encl_ref); +struct ndr_typeinfo ndt_s_wchar = { + 1, /* NDR version */ + 2-1, /* alignment */ + NDR_F_STRING, /* flags */ + mlndr_s_wchar, /* ndr_func */ + 0, /* pdu_size_fixed_part */ + 2, /* pdu_size_variable_part */ + 0, /* c_size_fixed_part */ + 1, /* c_size_variable_part */ +}; + + +/* + * Hand coded wchar function because all strings are transported + * as wide characters. During NDR_M_OP_MARSHALL, we convert from + * multi-byte to wide characters. During NDR_M_OP_UNMARSHALL, we + * convert from wide characters to multi-byte. + * + * It appeared that NT would sometimes leave a spurious character + * in the data stream before the null wide_char, which would get + * included in the string decode because we processed until the + * null character. It now looks like NT does not always terminate + * RPC Unicode strings and the terminating null is a side effect + * of field alignment. So now we rely on the strlen_is (set up in + * mlndr_outer_string) of the enclosing reference. This may or may + * not include the null but it doesn't matter, the algorithm will + * get it right. + */ +int +mlndr_s_wchar(struct ndr_reference *encl_ref) +{ + struct mlndr_stream *mlnds = encl_ref->stream; + unsigned short wide_char; + char *valp; + struct ndr_reference myref; + unsigned long i; + char name[30]; + int count; + int char_count = 0; + + if (mlnds->m_op == NDR_M_OP_UNMARSHALL) { + /* + * To avoid problems with zero length strings + * we can just null terminate here and be done. + */ + if (encl_ref->strlen_is == 0) { + encl_ref->datum[0] = '\0'; + return (1); + } + } + + bzero(&myref, sizeof (myref)); + myref.enclosing = encl_ref; + myref.stream = encl_ref->stream; + myref.packed_alignment = 0; + myref.ti = &ndt__wchar; + myref.inner_flags = NDR_F_NONE; + myref.datum = (char *)&wide_char; + myref.name = name; + myref.pdu_offset = encl_ref->pdu_offset; + + valp = encl_ref->datum; + count = 0; + + for (i = 0; i < NDR_STRING_MAX; i++) { + (void) sprintf(name, "[%lu]", i); + + if (mlnds->m_op == NDR_M_OP_MARSHALL) { + count = mts_mbtowc((mts_wchar_t *)&wide_char, valp, + MTS_MB_CHAR_MAX); + if (count < 0) { + return (0); + } else if (count == 0) { + if (encl_ref->strlen_is != encl_ref->size_is) + break; + + /* + * If the input char is 0, mbtowc + * returns 0 without setting wide_char. + * Set wide_char to 0 and a count of 1. + */ + wide_char = *valp; + count = 1; + } + } + + if (!mlndr_inner(&myref)) + return (0); + + if (mlnds->m_op == NDR_M_OP_UNMARSHALL) { + count = mts_wctomb(valp, wide_char); + + if ((++char_count) == encl_ref->strlen_is) { + valp += count; + *valp = '\0'; + break; + } + } + + if (!wide_char) + break; + + myref.pdu_offset += sizeof (wide_char); + valp += count; + } + + return (1); +} + +/* + * Converts a multibyte character string to a little-endian, wide-char + * string. No more than nwchars wide characters are stored. + * A terminating null wide character is appended if there is room. + * + * Returns the number of wide characters converted, not counting + * any terminating null wide character. Returns -1 if an invalid + * multibyte character is encountered. + */ +size_t +ndr_mbstowcs(struct mlndr_stream *mlnds, mts_wchar_t *wcs, const char *mbs, + size_t nwchars) +{ + mts_wchar_t *start = wcs; + int nbytes; + + while (nwchars--) { + nbytes = ndr_mbtowc(mlnds, wcs, mbs, MTS_MB_CHAR_MAX); + if (nbytes < 0) { + *wcs = 0; + return ((size_t)-1); + } + + if (*mbs == 0) + break; + + ++wcs; + mbs += nbytes; + } + + return (wcs - start); +} + +/* + * Converts a multibyte character to a little-endian, wide-char, which + * is stored in wcharp. Up to nbytes bytes are examined. + * + * If mbchar is valid, returns the number of bytes processed in mbchar. + * If mbchar is invalid, returns -1. See also mts_mbtowc(). + */ +/*ARGSUSED*/ +int +ndr_mbtowc(struct mlndr_stream *mlnds, mts_wchar_t *wcharp, + const char *mbchar, size_t nbytes) +{ + int rc; + + if ((rc = mts_mbtowc(wcharp, mbchar, nbytes)) < 0) + return (rc); + +#ifdef _BIG_ENDIAN + if (mlnds == NULL || NDR_MODE_MATCH(mlnds, NDR_MODE_RETURN_SEND)) + *wcharp = BSWAP_16(*wcharp); +#endif + + return (rc); +} |