Esempio n. 1
0
/*
 *  	get the vps and put them in perl hash
 *  	If one VP have multiple values it is added as array_ref
 *  	Example for this is Cisco-AVPair that holds multiple values.
 *  	Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'}
 */
static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, HV *rad_hv,
			   const char *hash_name, const char *list_name)
{
	VALUE_PAIR *vp;

	hv_undef(rad_hv);

	fr_cursor_t cursor;

	RINDENT();
	fr_pair_list_sort(vps, fr_pair_cmp_by_da_tag);
	for (vp = fr_cursor_init(&cursor, vps);
	     vp;
	     vp = fr_cursor_next(&cursor)) {
		VALUE_PAIR *next;

		char const *name;
		char namebuf[256];

		/*
		 *	Tagged attributes are added to the hash with name
		 *	<attribute>:<tag>, others just use the normal attribute
		 *	name as the key.
		 */
		if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) {
			snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag);
			name = namebuf;
		} else {
			name = vp->da->name;
		}

		/*
		 *	We've sorted by type, then tag, so attributes of the
		 *	same type/tag should follow on from each other.
		 */
		if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) {
			int i = 0;
			AV *av;

			av = newAV();
			perl_vp_to_svpvn_element(request, av, vp, &i, hash_name, list_name);
			do {
				perl_vp_to_svpvn_element(request, av, next, &i, hash_name, list_name);
				fr_cursor_next(&cursor);
			} while ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next));
			(void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0);

			continue;
		}

		/*
		 *	It's a normal single valued attribute
		 */
		switch (vp->vp_type) {
		case FR_TYPE_STRING:
			RDEBUG2("$%s{'%s'} = &%s:%s -> '%pV'", hash_name, vp->da->name, list_name,
			       vp->da->name, &vp->data);
			(void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->vp_length), 0);
			break;

		case FR_TYPE_OCTETS:
			RDEBUG2("$%s{'%s'} = &%s:%s -> %pV", hash_name, vp->da->name, list_name,
			       vp->da->name, &vp->data);
			(void)hv_store(rad_hv, name, strlen(name),
				       newSVpvn((char const *)vp->vp_octets, vp->vp_length), 0);
			break;

		default:
		{
			char buffer[1024];
			size_t len;

			len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0');
			RDEBUG2("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name,
			       list_name, vp->da->name, buffer);
			(void)hv_store(rad_hv, name, strlen(name),
				       newSVpvn(buffer, truncate_len(len, sizeof(buffer))), 0);
		}
			break;
		}
	}
	REXDENT();
}
Esempio n. 2
0
/*
 *	Receive one packet, maybe.
 */
static int recv_one_packet(int wait_time)
{
	fd_set		set;
	struct timeval  tv;
	rc_request_t	*request;
	RADIUS_PACKET	*reply, **packet_p;
	volatile int max_fd;

	/* And wait for reply, timing out as necessary */
	FD_ZERO(&set);

	max_fd = fr_packet_list_fd_set(pl, &set);
	if (max_fd < 0) exit(1); /* no sockets to listen on! */

	tv.tv_sec = (wait_time <= 0) ? 0 : wait_time;
	tv.tv_usec = 0;

	/*
	 *	No packet was received.
	 */
	if (select(max_fd, &set, NULL, NULL, &tv) <= 0) return 0;

	/*
	 *	Look for the packet.
	 */
	reply = fr_packet_list_recv(pl, &set);
	if (!reply) {
		ERROR("Received bad packet");
#ifdef WITH_TCP
		/*
		 *	If the packet is bad, we close the socket.
		 *	I'm not sure how to do that now, so we just
		 *	die...
		 */
		if (proto) exit(1);
#endif
		return -1;	/* bad packet */
	}

	/*
	 *	We don't use udpfromto.  So if we bind to "*", we want
	 *	to find replies sent to 192.0.2.4.  Therefore, we
	 *	force all replies to have the one address we know
	 *	about, no matter what real address they were sent to.
	 *
	 *	This only works if were not using any of the
	 *	Packet-* attributes, or running with 'auto'.
	 */
	reply->dst_ipaddr = client_ipaddr;
	reply->dst_port = client_port;

#ifdef WITH_TCP

	/*
	 *	TCP sockets don't use recvmsg(), and thus don't get
	 *	the source IP/port.  However, since they're TCP, we
	 *	know what the source IP/port is, because that's where
	 *	we connected to.
	 */
	if (ipproto == IPPROTO_TCP) {
		reply->src_ipaddr = server_ipaddr;
		reply->src_port = server_port;
	}
#endif

	packet_p = fr_packet_list_find_byreply(pl, reply);
	if (!packet_p) {
		ERROR("Received reply to request we did not send. (id=%d socket %d)",
		      reply->id, reply->sockfd);
		rad_free(&reply);
		return -1;	/* got reply to packet we didn't send */
	}
	request = fr_packet2myptr(rc_request_t, packet, packet_p);

	/*
	 *	Fails the signature validation: not a real reply.
	 *	FIXME: Silently drop it and listen for another packet.
	 */
	if (rad_verify(reply, request->packet, secret) < 0) {
		REDEBUG("Reply verification failed");
		stats.lost++;
		goto packet_done; /* shared secret is incorrect */
	}

	if (print_filename) {
		RDEBUG("%s response code %d", request->files->packets, reply->code);
	}

	deallocate_id(request);
	request->reply = reply;
	reply = NULL;

	/*
	 *	If this fails, we're out of memory.
	 */
	if (rad_decode(request->reply, request->packet, secret) != 0) {
		REDEBUG("Reply decode failed");
		stats.lost++;
		goto packet_done;
	}

	fr_packet_header_print(fr_log_fp, request->reply, true);
	if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, request->reply->vps);

	/*
	 *	Increment counters...
	 */
	switch (request->reply->code) {
	case PW_CODE_ACCESS_ACCEPT:
	case PW_CODE_ACCOUNTING_RESPONSE:
	case PW_CODE_COA_ACK:
	case PW_CODE_DISCONNECT_ACK:
		stats.accepted++;
		break;

	case PW_CODE_ACCESS_CHALLENGE:
		break;

	default:
		stats.rejected++;
	}

	/*
	 *	If we had an expected response code, check to see if the
	 *	packet matched that.
	 */
	if (request->reply->code != request->filter_code) {
		if (is_radius_code(request->reply->code)) {
			REDEBUG("%s: Expected %s got %s", request->name, fr_packet_codes[request->filter_code],
				fr_packet_codes[request->reply->code]);
		} else {
			REDEBUG("%s: Expected %u got %i", request->name, request->filter_code,
				request->reply->code);
		}
		stats.failed++;
	/*
	 *	Check if the contents of the packet matched the filter
	 */
	} else if (!request->filter) {
		stats.passed++;
	} else {
		VALUE_PAIR const *failed[2];

		fr_pair_list_sort(&request->reply->vps, fr_pair_cmp_by_da_tag);
		if (fr_pair_validate(failed, request->filter, request->reply->vps)) {
			RDEBUG("%s: Response passed filter", request->name);
			stats.passed++;
		} else {
			fr_pair_validate_debug(request, failed);
			REDEBUG("%s: Response for failed filter", request->name);
			stats.failed++;
		}
	}

	if (request->resend == resend_count) {
		request->done = true;
	}

packet_done:
	rad_free(&request->reply);
	rad_free(&reply);	/* may be NULL */

	return 0;
}
Esempio n. 3
0
/*
 *  	get the vps and put them in perl hash
 *  	If one VP have multiple values it is added as array_ref
 *  	Example for this is Cisco-AVPair that holds multiple values.
 *  	Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'}
 */
static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, HV *rad_hv,
			   const char *hash_name, const char *list_name)
{
	VALUE_PAIR *vp;
	char *tbuff;
	size_t tbufflen = 1024;

	hv_undef(rad_hv);

	vp_cursor_t cursor;

	/*
	 *	Find out how much room to allocate.
	 */
	for (vp = fr_cursor_init(&cursor, vps);
	     vp;
	     vp = fr_cursor_next(&cursor)) {
		if (((vp->length * 2) + 3) > tbufflen) {
			tbufflen = (vp->vp_length * 2) + 3;
		}
	}
	tbuff = talloc_array(request, char, tbufflen);

	RINDENT();
	fr_pair_list_sort(vps, fr_pair_cmp_by_da_tag);
	for (vp = fr_cursor_init(&cursor, vps);
	     vp;
	     vp = fr_cursor_next(&cursor)) {
	     	VALUE_PAIR *next;
	     	char const *name;
		size_t len;
		char namebuf[256];

		/*
		 *	Tagged attributes are added to the hash with name
		 *	<attribute>:<tag>, others just use the normal attribute
		 *	name as the key.
		 */
		if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) {
			snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag);
			name = namebuf;
		} else {
			name = vp->da->name;
		}

		/*
		 *	We've sorted by type, then tag, so attributes of the
		 *	same type/tag should follow on from each other.
		 */
		if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) {
			int i = 0;
			AV *av;

			av = newAV();

			perl_vp_to_svpvn_element(request, av, vp, &i, hash_name, list_name);
			do {
				perl_vp_to_svpvn_element(request, av, next, &i, hash_name, list_name);
				fr_cursor_next(&cursor);
			} while ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next));
			(void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0);

			continue;
		}

		/*
		 *	It's a normal single valued attribute
		 */
		switch (vp->da->type) {
		case PW_TYPE_STRING:
			RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name, list_name,
			       vp->da->name, vp->vp_strvalue);
			(void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->vp_length), 0);
			break;

		default:
			len = vp_prints_value(tbuff, tbufflen, vp, 0);
			RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name,
			       list_name, vp->da->name, tbuff);
			(void)hv_store(rad_hv, name, strlen(name),
				       newSVpvn(tbuff, truncate_len(len, tbufflen)), 0);
			break;
		}
	}
	REXDENT();

	talloc_free(tbuff);
}
Esempio n. 4
0
/*
 *	Initialize a radclient data structure and add it to
 *	the global linked list.
 */
static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files)
{
	FILE *packets, *filters = NULL;

	vp_cursor_t cursor;
	VALUE_PAIR *vp;
	rc_request_t *request;
	bool packets_done = false;
	uint64_t num = 0;

	assert(files->packets != NULL);

	/*
	 *	Determine where to read the VP's from.
	 */
	if (strcmp(files->packets, "-") != 0) {
		packets = fopen(files->packets, "r");
		if (!packets) {
			ERROR("Error opening %s: %s", files->packets, strerror(errno));
			return 0;
		}

		/*
		 *	Read in the pairs representing the expected response.
		 */
		if (files->filters) {
			filters = fopen(files->filters, "r");
			if (!filters) {
				ERROR("Error opening %s: %s", files->filters, strerror(errno));
				fclose(packets);
				return 0;
			}
		}
	} else {
		packets = stdin;
	}


	/*
	 *	Loop until the file is done.
	 */
	do {
		/*
		 *	Allocate it.
		 */
		request = talloc_zero(ctx, rc_request_t);
		if (!request) {
			ERROR("Out of memory");
			goto error;
		}

		request->packet = rad_alloc(request, true);
		if (!request->packet) {
			ERROR("Out of memory");
			goto error;
		}

#ifdef WITH_TCP
		request->packet->src_ipaddr = client_ipaddr;
		request->packet->src_port = client_port;
		request->packet->dst_ipaddr = server_ipaddr;
		request->packet->dst_port = server_port;
		request->packet->proto = ipproto;
#endif

		request->files = files;
		request->packet->id = -1; /* allocate when sending */
		request->num = num++;

		/*
		 *	Read the request VP's.
		 */
		if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, packets, &packets_done) < 0) {
			char const *input;

			if ((files->packets[0] == '-') && (files->packets[1] == '\0')) {
				input = "stdin";
			} else {
				input = files->packets;
			}

			REDEBUG("Error parsing \"%s\"", input);
			goto error;
		}

		/*
		 *	Skip empty entries
		 */
		if (!request->packet->vps) {
			talloc_free(request);
			continue;
		}

		/*
		 *	Read in filter VP's.
		 */
		if (filters) {
			bool filters_done;

			if (fr_pair_list_afrom_file(request, &request->filter, filters, &filters_done) < 0) {
				REDEBUG("Error parsing \"%s\"", files->filters);
				goto error;
			}

			if (filters_done && !packets_done) {
				REDEBUG("Differing number of packets/filters in %s:%s "
				        "(too many requests))", files->packets, files->filters);
				goto error;
			}

			if (!filters_done && packets_done) {
				REDEBUG("Differing number of packets/filters in %s:%s "
				        "(too many filters))", files->packets, files->filters);
				goto error;
			}

			/*
			 *	xlat expansions aren't supported here
			 */
			for (vp = fr_cursor_init(&cursor, &request->filter);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				if (vp->type == VT_XLAT) {
					vp->type = VT_DATA;
					vp->vp_strvalue = vp->xlat;
					vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
				}

				if (vp->da->vendor == 0 ) switch (vp->da->attr) {
				case PW_RESPONSE_PACKET_TYPE:
				case PW_PACKET_TYPE:
					fr_cursor_remove(&cursor);	/* so we don't break the filter */
					request->filter_code = vp->vp_integer;
					talloc_free(vp);

				default:
					break;
				}
			}

			/*
			 *	This allows efficient list comparisons later
			 */
			fr_pair_list_sort(&request->filter, fr_pair_cmp_by_da_tag);
		}

		/*
		 *	Process special attributes
		 */
		for (vp = fr_cursor_init(&cursor, &request->packet->vps);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			/*
			 *	Double quoted strings get marked up as xlat expansions,
			 *	but we don't support that in request.
			 */
			if (vp->type == VT_XLAT) {
				vp->type = VT_DATA;
				vp->vp_strvalue = vp->xlat;
				vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
			}

			if (!vp->da->vendor) switch (vp->da->attr) {
			default:
				break;

			/*
			 *	Allow it to set the packet type in
			 *	the attributes read from the file.
			 */
			case PW_PACKET_TYPE:
				request->packet->code = vp->vp_integer;
				break;

			case PW_RESPONSE_PACKET_TYPE:
				request->filter_code = vp->vp_integer;
				break;

			case PW_PACKET_DST_PORT:
				request->packet->dst_port = (vp->vp_integer & 0xffff);
				break;

			case PW_PACKET_DST_IP_ADDRESS:
				request->packet->dst_ipaddr.af = AF_INET;
				request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
				request->packet->dst_ipaddr.prefix = 32;
				break;

			case PW_PACKET_DST_IPV6_ADDRESS:
				request->packet->dst_ipaddr.af = AF_INET6;
				request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
				request->packet->dst_ipaddr.prefix = 128;
				break;

			case PW_PACKET_SRC_PORT:
				if ((vp->vp_integer < 1024) ||
				    (vp->vp_integer > 65535)) {
					ERROR("Invalid value '%u' for Packet-Src-Port", vp->vp_integer);
					goto error;
				}
				request->packet->src_port = (vp->vp_integer & 0xffff);
				break;

			case PW_PACKET_SRC_IP_ADDRESS:
				request->packet->src_ipaddr.af = AF_INET;
				request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
				request->packet->src_ipaddr.prefix = 32;
				break;

			case PW_PACKET_SRC_IPV6_ADDRESS:
				request->packet->src_ipaddr.af = AF_INET6;
				request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
				request->packet->src_ipaddr.prefix = 128;
				break;

			case PW_DIGEST_REALM:
			case PW_DIGEST_NONCE:
			case PW_DIGEST_METHOD:
			case PW_DIGEST_URI:
			case PW_DIGEST_QOP:
			case PW_DIGEST_ALGORITHM:
			case PW_DIGEST_BODY_DIGEST:
			case PW_DIGEST_CNONCE:
			case PW_DIGEST_NONCE_COUNT:
			case PW_DIGEST_USER_NAME:
			/* overlapping! */
			{
				DICT_ATTR const *da;
				uint8_t *p, *q;

				p = talloc_array(vp, uint8_t, vp->vp_length + 2);

				memcpy(p + 2, vp->vp_octets, vp->vp_length);
				p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
				vp->vp_length += 2;
				p[1] = vp->vp_length;

				da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
				if (!da) {
					ERROR("Out of memory");
					goto error;
				}
				vp->da = da;

				/*
				 *	Re-do fr_pair_value_memsteal ourselves,
				 *	because we play games with
				 *	vp->da, and fr_pair_value_memsteal goes
				 *	to GREAT lengths to sanitize
				 *	and fix and change and
				 *	double-check the various
				 *	fields.
				 */
				memcpy(&q, &vp->vp_octets, sizeof(q));
				talloc_free(q);

				vp->vp_octets = talloc_steal(vp, p);
				vp->type = VT_DATA;

				VERIFY_VP(vp);
			}
				break;

				/*
				 *	Cache this for later.
				 */
			case PW_CLEARTEXT_PASSWORD:
				request->password = vp;
				break;

			/*
			 *	Keep a copy of the the password attribute.
			 */
			case PW_CHAP_PASSWORD:
				/*
				 *	If it's already hex, do nothing.
				 */
				if ((vp->vp_length == 17) &&
				    (already_hex(vp))) break;

				/*
				 *	CHAP-Password is octets, so it may not be zero terminated.
				 */
				request->password = fr_pair_make(request->packet, &request->packet->vps, "Cleartext-Password",
							     "", T_OP_EQ);
				fr_pair_value_bstrncpy(request->password, vp->vp_strvalue, vp->vp_length);
				break;

			case PW_USER_PASSWORD:
			case PW_MS_CHAP_PASSWORD:
				request->password = fr_pair_make(request->packet, &request->packet->vps, "Cleartext-Password",
							     vp->vp_strvalue, T_OP_EQ);
				break;

			case PW_RADCLIENT_TEST_NAME:
				request->name = vp->vp_strvalue;
				break;
			}
		} /* loop over the VP's we read in */

		/*
		 *	Use the default set on the command line
		 */
		if (request->packet->code == PW_CODE_UNDEFINED) request->packet->code = packet_code;

		/*
		 *	Default to the filename
		 */
		if (!request->name) request->name = request->files->packets;

		/*
		 *	Automatically set the response code from the request code
		 *	(if one wasn't already set).
		 */
		if (request->filter_code == PW_CODE_UNDEFINED) {
			switch (request->packet->code) {
			case PW_CODE_ACCESS_REQUEST:
				request->filter_code = PW_CODE_ACCESS_ACCEPT;
				break;

			case PW_CODE_ACCOUNTING_REQUEST:
				request->filter_code = PW_CODE_ACCOUNTING_RESPONSE;
				break;

			case PW_CODE_COA_REQUEST:
				request->filter_code = PW_CODE_COA_ACK;
				break;

			case PW_CODE_DISCONNECT_REQUEST:
				request->filter_code = PW_CODE_DISCONNECT_ACK;
				break;

			case PW_CODE_STATUS_SERVER:
				switch (radclient_get_code(request->packet->dst_port)) {
				case PW_CODE_ACCESS_REQUEST:
					request->filter_code = PW_CODE_ACCESS_ACCEPT;
					break;

				case PW_CODE_ACCOUNTING_REQUEST:
					request->filter_code = PW_CODE_ACCOUNTING_RESPONSE;
					break;

				default:
					REDEBUG("Can't determine expected response to Status-Server request, specify "
					        "a well known RADIUS port, or add a Response-Packet-Type attribute "
					        "to the request of filter");
					goto error;
				}
				break;

			case PW_CODE_UNDEFINED:
				REDEBUG("Both Packet-Type and Response-Packet-Type undefined, specify at least one, "
					"or a well known RADIUS port");
				goto error;

			default:
				REDEBUG("Can't determine expected Response-Packet-Type for Packet-Type %i",
					request->packet->code);
				goto error;
			}
		/*
		 *	Automatically set the request code from the response code
		 *	(if one wasn't already set).
		 */
		} else if (request->packet->code == PW_CODE_UNDEFINED) {
			switch (request->filter_code) {
			case PW_CODE_ACCESS_ACCEPT:
			case PW_CODE_ACCESS_REJECT:
				request->packet->code = PW_CODE_ACCESS_REQUEST;
				break;

			case PW_CODE_ACCOUNTING_RESPONSE:
				request->packet->code = PW_CODE_ACCOUNTING_REQUEST;
				break;

			case PW_CODE_DISCONNECT_ACK:
			case PW_CODE_DISCONNECT_NAK:
				request->packet->code = PW_CODE_DISCONNECT_REQUEST;
				break;

			case PW_CODE_COA_ACK:
			case PW_CODE_COA_NAK:
				request->packet->code = PW_CODE_COA_REQUEST;
				break;

			default:
				REDEBUG("Can't determine expected Packet-Type for Response-Packet-Type %i",
					request->filter_code);
				goto error;
			}
		}

		/*
		 *	Automatically set the dst port (if one wasn't already set).
		 */
		if (request->packet->dst_port == 0) {
			radclient_get_port(request->packet->code, &request->packet->dst_port);
			if (request->packet->dst_port == 0) {
				REDEBUG("Can't determine destination port");
				goto error;
			}
		}

		/*
		 *	Add it to the tail of the list.
		 */
		if (!request_head) {
			assert(rc_request_tail == NULL);
			request_head = request;
			request->prev = NULL;
		} else {
			assert(rc_request_tail->next == NULL);
			rc_request_tail->next = request;
			request->prev = rc_request_tail;
		}
		rc_request_tail = request;
		request->next = NULL;

		/*
		 *	Set the destructor so it removes itself from the
		 *	request list when freed. We don't set this until
		 *	the packet is actually in the list, else we trigger
		 *	the asserts in the free callback.
		 */
		talloc_set_destructor(request, _rc_request_free);
	} while (!packets_done); /* loop until the file is done. */

	if (packets != stdin) fclose(packets);
	if (filters) fclose(filters);

	/*
	 *	And we're done.
	 */
	return 1;

error:
	talloc_free(request);

	if (packets != stdin) fclose(packets);
	if (filters) fclose(filters);

	return 0;
}
Esempio n. 5
0
ssize_t fr_dhcpv4_encode(uint8_t *buffer, size_t buflen, dhcp_packet_t *original, int code, uint32_t xid, VALUE_PAIR *vps)
{
	uint8_t		*p;
	fr_cursor_t	cursor;
	VALUE_PAIR	*vp;
	uint32_t	lvalue;
	uint16_t	svalue;
	size_t		dhcp_size;
	ssize_t		len;

	p = buffer;

	if (buflen < DEFAULT_PACKET_SIZE) return -1;

	/*
	 *	@todo: Make this work again.
	 */
#if 0
	mms = DEFAULT_PACKET_SIZE; /* maximum message size */

	/*
	 *	Clients can request a LARGER size, but not a
	 *	smaller one.  They also cannot request a size
	 *	larger than MTU.
	 */

	/* DHCP-DHCP-Maximum-Msg-Size */
	vp = fr_pair_find_by_da(vps, attr_dhcp_dhcp_maximum_msg_size, TAG_ANY);
	if (vp && (vp->vp_uint32 > mms)) {
		mms = vp->vp_uint32;

		if (mms > MAX_PACKET_SIZE) mms = MAX_PACKET_SIZE;
	}
#endif

	vp = fr_pair_find_by_da(vps, attr_dhcp_opcode, TAG_ANY);
	if (vp) {
		*p++ = vp->vp_uint32 & 0xff;
	} else {
		*p++ = 1;	/* client message */
	}

	/* DHCP-Hardware-Type */
	vp = fr_pair_find_by_da(vps, attr_dhcp_hardware_type, TAG_ANY);
	if (vp) {
		*p = vp->vp_uint8;

	} else if (original) {
		*p = original->htype;

	} /* else leave it unset */
	p += 1;

	/* DHCP-Hardware-Address-len */
	vp = fr_pair_find_by_da(vps, attr_dhcp_hardware_address_length, TAG_ANY);
	if (vp) {
		*p = vp->vp_uint8;

	} else if (original) {
		*p = original->hlen;

	} /* else leave it unset */
	p += 1;

	/* DHCP-Hop-Count */
	vp = fr_pair_find_by_da(vps, attr_dhcp_hop_count, TAG_ANY);
	if (vp) {
		*p = vp->vp_uint8;

	} else if (original) {
		*p = original->hops;

	} /* else leave it unset */
	p++;

	/* DHCP-Transaction-Id */
	lvalue = htonl(xid);
	memcpy(p, &lvalue, 4);
	p += 4;

	/* DHCP-Number-of-Seconds */
	vp = fr_pair_find_by_da(vps, attr_dhcp_number_of_seconds, TAG_ANY);
	if (vp) {
		svalue = htons(vp->vp_uint16);
		memcpy(p, &svalue, 2);
	}
	p += 2;

	/* DHCP-Flags */
	vp = fr_pair_find_by_da(vps, attr_dhcp_flags, TAG_ANY);
	if (vp) {
		svalue = htons(vp->vp_uint16);
		memcpy(p, &svalue, 2);
	}
	p += 2;

	/* DHCP-Client-IP-Address */
	vp = fr_pair_find_by_da(vps, attr_dhcp_client_ip_address, TAG_ANY);
	if (vp) memcpy(p, &vp->vp_ipv4addr, 4);
	p += 4;

	/* DHCP-Your-IP-address */
	vp = fr_pair_find_by_da(vps, attr_dhcp_your_ip_address, TAG_ANY);
	if (vp) {
		lvalue = vp->vp_ipv4addr;
	} else {
		lvalue = htonl(INADDR_ANY);
	}
	memcpy(p, &lvalue, 4);
	p += 4;

	/* DHCP-Server-IP-Address */
	vp = fr_pair_find_by_da(vps, attr_dhcp_server_ip_address, TAG_ANY);
	if (vp) {
		lvalue = vp->vp_ipv4addr;
	} else {
		lvalue = htonl(INADDR_ANY);
	}
	memcpy(p, &lvalue, 4);
	p += 4;

	/*
	 *	DHCP-Gateway-IP-Address
	 */
	vp = fr_pair_find_by_da(vps, attr_dhcp_gateway_ip_address, TAG_ANY);
	if (vp) {
		lvalue = vp->vp_ipv4addr;
		memcpy(p, &lvalue, 4);

	} else if (original) {	/* copy whatever value was in the original */
		memcpy(p, &original->giaddr, sizeof(original->giaddr));

	} else {
		lvalue = htonl(INADDR_ANY);
		memcpy(p, &lvalue, 4);
	}
	p += 4;

	/* DHCP-Client-Hardware-Address */
	if ((vp = fr_pair_find_by_da(vps, attr_dhcp_client_hardware_address, TAG_ANY))) {
		if (vp->vp_type == FR_TYPE_ETHERNET) {
			/*
			 *	Ensure that we mark the packet as being Ethernet.
			 */
			buffer[1] = 1;	/* Hardware address type = Ethernet */
			buffer[2] = 6;	/* Hardware address length = 6 */

			memcpy(p, vp->vp_ether, sizeof(vp->vp_ether));
		} /* else ignore it */

	} else if (original) {	/* copy whatever value was in the original */
		memcpy(p, &original->chaddr[0], sizeof(original->chaddr));

	}
	p += DHCP_CHADDR_LEN;

	/* DHCP-Server-Host-Name */
	if ((vp = fr_pair_find_by_da(vps, attr_dhcp_server_host_name, TAG_ANY))) {
		if (vp->vp_length > DHCP_SNAME_LEN) {
			memcpy(p, vp->vp_strvalue, DHCP_SNAME_LEN);
		} else {
			memcpy(p, vp->vp_strvalue, vp->vp_length);
		}
	}
	p += DHCP_SNAME_LEN;

	/*
	 *	Copy over DHCP-Boot-Filename.
	 *
	 *	FIXME: This copy should be delayed until AFTER the options
	 *	have been processed.  If there are too many options for
	 *	the packet, then they go into the sname && filename fields.
	 *	When that happens, the boot filename is passed as an option,
	 *	instead of being placed verbatim in the filename field.
	 */

	/* DHCP-Boot-Filename */
	vp = fr_pair_find_by_da(vps, attr_dhcp_boot_filename, TAG_ANY);
	if (vp) {
		if (vp->vp_length > DHCP_FILE_LEN) {
			memcpy(p, vp->vp_strvalue, DHCP_FILE_LEN);
		} else {
			memcpy(p, vp->vp_strvalue, vp->vp_length);
		}
	}
	p += DHCP_FILE_LEN;

	/* DHCP magic number */
	lvalue = htonl(DHCP_OPTION_MAGIC_NUMBER);
	memcpy(p, &lvalue, 4);
	p += 4;

	p[0] = FR_DHCP_MESSAGE_TYPE;
	p[1] = 1;
	p[2] = code;
	p += 3;

	/*
	 *  Pre-sort attributes into contiguous blocks so that fr_dhcpv4_encode_option
	 *  operates correctly. This changes the order of the list, but never mind...
	 */
	fr_pair_list_sort(&vps, fr_dhcpv4_attr_cmp);
	fr_cursor_init(&cursor, &vps);

	/*
	 *  Each call to fr_dhcpv4_encode_option will encode one complete DHCP option,
	 *  and sub options.
	 */
	while ((vp = fr_cursor_current(&cursor))) {
		len = fr_dhcpv4_encode_option(p, buflen - (p - buffer),
					      &cursor, &(fr_dhcpv4_ctx_t){ .root = fr_dict_root(dict_dhcpv4) });
		if (len < 0) break;
		p += len;
	};