#include "includes.h"
#include "../librpc/ndr/libndr.h"
#include "ndr_dns_utils.h"


/**
  push a dns/nbt string list to the wire
*/
enum ndr_err_code ndr_push_dns_string_list(struct ndr_push *ndr,
					   struct ndr_token_list *string_list,
					   ndr_flags_type ndr_flags,
					   const char *s,
					   bool is_nbt)
{
	const char *start = s;
	bool use_compression;
	size_t max_length;
	if (is_nbt) {
		use_compression = true;
		/*
		 * Max length is longer in NBT/Wins, because Windows counts
		 * the semi-decompressed size of the netbios name (16 bytes)
		 * rather than the wire size of 32, which is what you'd expect
		 * if it followed RFC1002 (it uses the short form in
		 * [MS-WINSRA]). In other words the maximum size of the
		 * "scope" is 237, not 221.
		 *
		 * We make the size limit slightly larger than 255 + 16,
		 * because the 237 scope limit is already enforced in the
		 * winsserver code with a specific return value; bailing out
		 * here would muck with that.
		 */
		max_length = 274;
	} else {
		use_compression = !(ndr->flags & LIBNDR_FLAG_NO_COMPRESSION);
		max_length = 255;
	}

	if (!(ndr_flags & NDR_SCALARS)) {
		return NDR_ERR_SUCCESS;
	}

	while (s && *s) {
		enum ndr_err_code ndr_err;
		char *compname;
		size_t complen;
		uint32_t offset;

		if (use_compression) {
			/* see if we have pushed the remaining string already,
			 * if so we use a label pointer to this string
			 */
			ndr_err = ndr_token_peek_cmp_fn(string_list,
							s,
							&offset,
							(comparison_fn_t)
								strcmp);
			if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
				uint8_t b[2];

				if (offset > 0x3FFF) {
					return ndr_push_error(ndr, NDR_ERR_STRING,
							      "offset for dns string " \
							      "label pointer " \
							      "%"PRIu32"[%08"PRIX32"] > 0x00003FFF",
							      offset, offset);
				}

				b[0] = 0xC0 | (offset>>8);
				b[1] = (offset & 0xFF);

				return ndr_push_bytes(ndr, b, 2);
			}
		}

		complen = strcspn(s, ".");

		/* the length must fit into 6 bits (i.e. <= 63) */
		if (complen > 0x3F) {
			return ndr_push_error(ndr, NDR_ERR_STRING,
					      "component length %zu[%08zX] > " \
					      "0x0000003F",
					      complen,
					      complen);
		}

		if (complen == 0 && s[complen] == '.') {
			return ndr_push_error(ndr, NDR_ERR_STRING,
					      "component length is 0 "
					      "(consecutive dots)");
		}

		if (is_nbt && s[complen] == '.' && s[complen + 1] == '\0') {
			/* nbt names are sometimes usernames, and we need to
			 * keep a trailing dot to ensure it is byte-identical,
			 * (not just semantically identical given DNS
			 * semantics). */
			complen++;
		}

		compname = talloc_asprintf(ndr, "%c%*.*s",
						(unsigned char)complen,
						(unsigned char)complen,
						(unsigned char)complen, s);
		NDR_ERR_HAVE_NO_MEMORY(compname);

		/* remember the current component + the rest of the string
		 * so it can be reused later
		 */
		if (use_compression) {
			NDR_CHECK(ndr_token_store(ndr, string_list, s,
						  ndr->offset));
		}

		/* push just this component into the blob */
		NDR_CHECK(ndr_push_bytes(ndr, (const uint8_t *)compname,
					 complen+1));
		talloc_free(compname);

		s += complen;
		if (*s == '.') {
			s++;
		}
		if (s - start > max_length) {
			return ndr_push_error(ndr, NDR_ERR_STRING,
					      "name > %zu characters long",
					      max_length);
		}
	}

	/* if we reach the end of the string and have pushed the last component
	 * without using a label pointer, we need to terminate the string
	 */
	return ndr_push_bytes(ndr, (const uint8_t *)"", 1);
}
