File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / http / servlet / http_servlet_cookieauth.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 4 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>

#include <netinet/in.h>

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <syslog.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <pthread.h>

#include <openssl/ssl.h>
#include <openssl/md5.h>

#include "structs/structs.h"
#include "structs/type/array.h"
#include "structs/type/boolean.h"
#include "structs/type/data.h"
#include "structs/type/int.h"
#include "structs/type/null.h"
#include "structs/type/string.h"
#include "structs/type/struct.h"
#include "structs/type/time.h"
#include "structs/xml.h"

#include "http/http_defs.h"
#include "http/http_server.h"
#include "http/http_servlet.h"
#include "http/servlet/redirect.h"
#include "http/servlet/cookieauth.h"

#include "sys/alog.h"
#include "io/string_fp.h"
#include "util/rsa_util.h"
#include "util/typed_mem.h"
#include "debug/debug.h"

#ifndef __FreeBSD__
#define __printflike(x,y)
#endif

/*
 * Ref: http://home.netscape.com/newsref/std/cookie_spec.html
 */

#define MEM_TYPE		"http_servlet_cookieauth"
#define DATA_MEM_TYPE		"http_servlet_cookieauth.data"
#define COOKIE_TIME_FMT		"%a, %d-%b-%Y %T GMT"
#define COOKIE_LINGER_TIME	(30 * 60)		/* 30 minutes */

/* Per-servlet private info */
struct cookieauth_private {
	struct http_servlet	*redirect;	/* private redirect servlet */
	http_servlet_cookieauth_reqd_t
				*authreqd;	/* checks if auth required */
	void			*arg;		/* argument for authreqd() */
	void			(*destroy)(void *);	/* destructor for arg */
	char			*privkey;	/* rsa private key file */
	char			*cookiename;	/* name of cookie */
	struct structs_data	id;		/* unique system id */
};

/* Structure of the cookie data */
struct cookieauth {
	char			*username;	/* login username */
	char			*path;		/* cookie path */
	char			*domain;	/* cookie domain */
	u_char			secure;		/* cookie 'secure' bit */
	u_char			session_only;	/* this browser session only */
	time_t			timestamp;	/* time cookie was set */
	time_t			expire;		/* expiration time, or zero */
	u_int32_t		linger;		/* max linger time, or zero */
	struct structs_data	id;		/* unique system id */
	struct structs_data	sig;		/* rsa signature */
};

/* Internal functions */
static http_servlet_run_t	http_servlet_cookieauth_run;
static http_servlet_destroy_t	http_servlet_cookieauth_destroy;

static int	http_servlet_cookieauth_get(const char *privkey,
			const struct structs_data *id, const char *cookiename,
			struct http_request *req, struct cookieauth *auth);
static int	http_servlet_cookieauth_md5(const struct cookieauth *auth,
			u_char *md5);

#if PDEL_DEBUG
static void	dump_data(const void *data, u_int plen, const char *fmt, ...)
			__printflike(3, 4);
#endif

/* Internal variables */
static const struct structs_type authcookie_data_type
	= STRUCTS_DATA_TYPE(NULL, DATA_MEM_TYPE);

static const struct structs_field cookieauth_fields[] = {
	STRUCTS_STRUCT_FIELD(cookieauth, username, &structs_type_string),
	STRUCTS_STRUCT_FIELD(cookieauth, path, &structs_type_string_null),
	STRUCTS_STRUCT_FIELD(cookieauth, domain, &structs_type_string_null),
	STRUCTS_STRUCT_FIELD(cookieauth, secure, &structs_type_boolean_char),
	STRUCTS_STRUCT_FIELD(cookieauth,
	    session_only, &structs_type_boolean_char),
	STRUCTS_STRUCT_FIELD(cookieauth, timestamp, &structs_type_time_gmt),
	STRUCTS_STRUCT_FIELD(cookieauth, expire, &structs_type_time_gmt),
	STRUCTS_STRUCT_FIELD(cookieauth, linger, &structs_type_uint32),
	STRUCTS_STRUCT_FIELD(cookieauth, id, &authcookie_data_type),
	STRUCTS_STRUCT_FIELD(cookieauth, sig, &authcookie_data_type),
	STRUCTS_STRUCT_FIELD_END
};
static const struct structs_type cookieauth_type
	= STRUCTS_STRUCT_TYPE(cookieauth, &cookieauth_fields);

/*
 * Create a new cookieauth servlet.
 */
struct http_servlet *
http_servlet_cookieauth_create(const char *redirect, int append,
	http_servlet_cookieauth_reqd_t *authreqd, void *arg,
	void (*destroy)(void *), const char *privkey,
	const void *id, size_t idlen, const char *cookiename)
{
	struct http_servlet *servlet;
	struct cookieauth_private *priv;
	struct structs_data id_data;
	const char *s;

	/* Validate cookiename */
	for (s = cookiename; *s != '\0'; s++) {
		if (!isgraph((u_char)*s) || strchr(",;=", *s) != NULL)
			break;
	}
	if (s == cookiename || *s != '\0') {
		errno = EINVAL;
		return (NULL);
	}

	/* Create new servlet */
	if ((servlet = MALLOC(MEM_TYPE, sizeof(*servlet))) == NULL)
		return (NULL);
	memset(servlet, 0, sizeof(*servlet));
	servlet->run = http_servlet_cookieauth_run;
	servlet->destroy = http_servlet_cookieauth_destroy;

	/* Set up private info */
	if ((priv = MALLOC(MEM_TYPE, sizeof(*priv))) == NULL)
		goto fail;
	memset(priv, 0, sizeof(*priv));
	if ((priv->redirect
	    = http_servlet_redirect_create(redirect, append)) == NULL)
		goto fail;
	priv->authreqd = authreqd;
	priv->arg = arg;
	priv->destroy = destroy;
	if ((priv->privkey = STRDUP(MEM_TYPE, privkey)) == NULL)
		goto fail;
	if ((priv->cookiename = STRDUP(MEM_TYPE, cookiename)) == NULL)
		goto fail;
	id_data.data = (u_char *)id;
	id_data.length = idlen;
	if (structs_get(&authcookie_data_type, NULL, &id_data, &priv->id) == -1)
		goto fail;
	servlet->arg = priv;

	/* Done */
	return (servlet);

fail:
	/* Clean up after failure */
	if (priv != NULL) {
		structs_free(&authcookie_data_type, NULL, &priv->id);
		FREE(MEM_TYPE, priv->cookiename);
		FREE(MEM_TYPE, priv->privkey);
		http_server_destroy_servlet(&priv->redirect);
		FREE(MEM_TYPE, priv);
	}
	FREE(MEM_TYPE, servlet);
	return (NULL);
}

/*
 * Execute cookie authorization servlet.
 */
static int
http_servlet_cookieauth_run(struct http_servlet *servlet,
	struct http_request *req, struct http_response *resp)
{
	struct cookieauth_private *const priv = servlet->arg;
	struct cookieauth auth;

	/* Always allow access to the logon page */
	if (priv->authreqd != NULL && !(*priv->authreqd)(priv->arg, req))
		goto allow;

	/* Get valid authorization structure, if there is one */
	if (http_servlet_cookieauth_get(priv->privkey,
	    &priv->id, priv->cookiename, req, &auth) == -1) {

		/* Invalid authorization -> redirect to logon page */
		if (errno == EACCES) {
			return ((*priv->redirect->run)(priv->redirect,
			    req, resp));
		}

		/* Other errors -> generate server error */
		http_response_send_errno_error(resp);
		return (1);
	}

	/* Update cookie for linger timer */
	if (auth.linger != 0) {
		(void)http_servlet_cookieauth_login(resp, priv->privkey,
		    auth.username, auth.linger, auth.expire, auth.session_only,
		    priv->id.data, priv->id.length, priv->cookiename, auth.path,
		    auth.domain, auth.secure);
	}

	/* Free authorization info */
	structs_free(&cookieauth_type, NULL, &auth);

allow:
	/* Allow request to continue */
	return (0);
}

/*
 * Destroy an auth servlet.
 */
static void
http_servlet_cookieauth_destroy(struct http_servlet *servlet)
{
	struct cookieauth_private *const priv = servlet->arg;

	if (priv->destroy != NULL)
		(*priv->destroy)(priv->arg);
	structs_free(&authcookie_data_type, NULL, &priv->id);
	FREE(MEM_TYPE, priv->privkey);
	FREE(MEM_TYPE, priv->cookiename);
	http_server_destroy_servlet(&priv->redirect);
	FREE(MEM_TYPE, priv);
	FREE(MEM_TYPE, servlet);
}

/*
 * Add a cookie that will cause the servlet to not redirect.
 */
int
http_servlet_cookieauth_login(struct http_response *resp,
	const char *privkey, const char *username, u_int max_linger,
	time_t expire, int session_only, const u_char *id, size_t idlen,
	const char *cookiename, const char *path, const char *domain,
	int secure)
{
	struct structs_data data;	/* binary encoding of "auth" */
	struct cookieauth auth;		/* authorization info struct */
	u_char md5[MD5_DIGEST_LENGTH];
	u_char sigbuf[1024];
	char ebuf[128];
	int siglen;
	char *hval;
	FILE *sb;

	/* Build auth structure */
	if (structs_init(&cookieauth_type, NULL, &auth) == -1)
		return (-1);
	if (structs_set_string(&cookieauth_type,
	    "username", username, &auth, ebuf, sizeof(ebuf)) == -1) {
		alogf(LOG_ERR, "%s: %s", "structs_set_string", ebuf);
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}
	if (path != NULL
	    && structs_set_string(&cookieauth_type,
	      "path", path, &auth, ebuf, sizeof(ebuf)) == -1) {
		alogf(LOG_ERR, "%s: %s", "structs_set_string", ebuf);
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}
	if (domain != NULL
	    && structs_set_string(&cookieauth_type,
	      "domain", domain, &auth, ebuf, sizeof(ebuf)) == -1) {
		alogf(LOG_ERR, "%s: %s", "structs_set_string", ebuf);
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}
	auth.secure = !!secure;
	auth.session_only = !!session_only;
	auth.timestamp = time(NULL);
	auth.linger = max_linger;
	auth.expire = expire;
	data.data = (u_char *)id;
	data.length = idlen;
	if (structs_get(&authcookie_data_type, NULL, &data, &auth.id) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_get");
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}

	/* Add RSA signature */
	if (http_servlet_cookieauth_md5(&auth, md5) == -1) {
		alogf(LOG_ERR, "%s: %m", "http_servlet_cookieauth_md5");
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}
	if ((siglen = rsa_util_sign(privkey,
	    md5, sigbuf, sizeof(sigbuf))) == -1) {
		alogf(LOG_ERR, "%s: %m", "rsa_util_sign");
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}
	data.data = sigbuf;
	data.length = siglen;
	if (structs_get(&authcookie_data_type, NULL, &data, &auth.sig) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_get");
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}

	/* Encode auth structure into binary */
	if (structs_get_binary(&cookieauth_type,
	    NULL, &auth, DATA_MEM_TYPE, &data) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_get_binary");
		structs_free(&cookieauth_type, NULL, &auth);
		return (-1);
	}

#if PDEL_DEBUG
	if (PDEL_DEBUG_ENABLED(HTTP_SERVLET_COOKIEAUTH)) {
		printf("COOKIE AUTH STRUCTURE\n");
		structs_xml_output(&cookieauth_type,
		    "auth", NULL, &auth, stdout, NULL, STRUCTS_XML_FULL);
		dump_data(data.data, data.length, "COOKIE DATA");
	}
#endif

	structs_free(&cookieauth_type, NULL, &auth);

	/* Base64 encode it */
	if ((hval = structs_get_string(&authcookie_data_type,
	    NULL, &data, TYPED_MEM_TEMP)) == NULL) {
		alogf(LOG_ERR, "%s: %m", "structs_get_string");
		structs_free(&authcookie_data_type, NULL, &data);
		return (-1);
	}
	structs_free(&authcookie_data_type, NULL, &data);

	/* Create string output buffer */
	if ((sb = string_buf_output(TYPED_MEM_TEMP)) == NULL) {
		FREE(TYPED_MEM_TEMP, hval);
		return (-1);
	}

	/* Construct cookie header value */
	fprintf(sb, "%s=%s", cookiename, hval);
	FREE(TYPED_MEM_TEMP, hval);
	if (!session_only) {
		char tbuf[64];
		struct tm tm;

		strftime(tbuf, sizeof(tbuf),
		    COOKIE_TIME_FMT, gmtime_r(&expire, &tm));
		fprintf(sb, "; expires=%s", tbuf);
	}
	if (domain != NULL)
		fprintf(sb, "; domain=%s", domain);
	if (path != NULL)
		fprintf(sb, "; path=%s", path);
	if (secure)
		fprintf(sb, "; secure");
	hval = string_buf_content(sb, 1);
	fclose(sb);
	if (hval == NULL)
		return (-1);

	/* Set cookie header value */
	if (http_response_set_header(resp,
	    0, HTTP_HEADER_SET_COOKIE, "%s", hval) == -1) {
		FREE(TYPED_MEM_TEMP, hval);
		return (-1);
	}
	FREE(TYPED_MEM_TEMP, hval);

	/* Done */
	return (0);
}

/*
 * Remove authorization cookie.
 */
int
http_servlet_cookieauth_logout(const char *cookiename, const char *path,
	const char *domain, struct http_response *resp)
{
	static const time_t past = 0;
	char tbuf[64];
	struct tm tm;
	char *hval;
	FILE *sb;

	/* Create string output buffer */
	if ((sb = string_buf_output(TYPED_MEM_TEMP)) == NULL)
		return (-1);

	/* Construct cookie header value */
	strftime(tbuf, sizeof(tbuf), COOKIE_TIME_FMT, gmtime_r(&past, &tm));
	fprintf(sb, "%s=x; expires=%s", cookiename, tbuf);
	if (domain != NULL)
		fprintf(sb, "; domain=%s", domain);
	if (path != NULL)
		fprintf(sb, "; path=%s", path);
	hval = string_buf_content(sb, 1);
	fclose(sb);
	if (hval == NULL)
		return (-1);

	/* Set cookie header value */
	if (http_response_set_header(resp, 0,
	    HTTP_HEADER_SET_COOKIE, "%s", hval) == -1) {
		FREE(TYPED_MEM_TEMP, hval);
		return (-1);
	}
	FREE(TYPED_MEM_TEMP, hval);

	/* Done */
	return (0);
}

/*
 * Get username.
 */
char *
http_servlet_cookieauth_user(const char *privkey, const void *id, size_t idlen,
	const char *cookiename, struct http_request *req, const char *mtype)
{
	struct cookieauth auth;
	struct structs_data idd;
	char *username;

	/* Get valid authorization structure, if there is one */
	idd.data = (u_char *)id;
	idd.length = idlen;
	if (http_servlet_cookieauth_get(privkey,
	    &idd, cookiename, req, &auth) == -1)
		return (NULL);

	/* Get copy of username */
	if ((username = structs_get_string(&cookieauth_type,
	    "username", &auth, mtype)) == NULL)
		alogf(LOG_ERR, "%s: %m", "structs_get_string");

	/* Free auth structure */
	structs_free(&cookieauth_type, NULL, &auth);

	/* Return username */
	return (username);
}

/*
 * Get valid authorization structure if there is one.
 */
static int
http_servlet_cookieauth_get(const char *privkey, const struct structs_data *id,
	const char *cookiename, struct http_request *req,
	struct cookieauth *auth)
{
	const int namelen = strlen(cookiename);
	const time_t now = time(NULL);
	const char *hval;
	const char *next;

	/* Get cookie header */
	if ((hval = http_request_get_header(req, HTTP_HEADER_COOKIE)) == NULL)
		goto invalid;

	/* Find our cookie */
	for ( ; *hval != '\0'; hval = next) {
		u_char md5[MD5_DIGEST_LENGTH];
		struct structs_data data;
		char valbuf[512];
		const char *eq;
		char ebuf[128];
		int vallen;

		/* Get next cookie and compare name */
		while (isspace((u_char)*hval))
			hval++;
		if ((eq = strchr(hval, '=')) == NULL)
			break;
		if ((next = strchr(eq + 1, ';')) == NULL) {
			next = eq + strlen(eq);
			vallen = strlen(eq + 1);
		} else {
			vallen = next - (eq + 1);
			next++;
		}
		if (strncmp(hval, cookiename, namelen) != 0
		    || hval + namelen != eq)
			continue;

		/* Isolate cookie value */
		if (vallen > sizeof(valbuf) - 1) {
			DBG(HTTP_SERVLET_COOKIEAUTH, "cookie too long");
			continue;
		}
		memcpy(valbuf, hval + namelen + 1, vallen);
		valbuf[vallen] = '\0';

		/* Decode base64 data into binary data */
		if (structs_init(&authcookie_data_type, NULL, &data) == -1) {
			alogf(LOG_ERR, "%s: %m", "structs_init");
			continue;
		}
		if (structs_set_string(&authcookie_data_type,
		    NULL, valbuf, &data, NULL, 0) == -1) {
			DBG(HTTP_SERVLET_COOKIEAUTH,
			    "error decoding base64: %s", ebuf);
			structs_free(&authcookie_data_type, NULL, &data);
			continue;
		}

#if PDEL_DEBUG
		if (PDEL_DEBUG_ENABLED(HTTP_SERVLET_COOKIEAUTH))
			dump_data(data.data, data.length, "COOKIE DATA");
#endif

		/* Initialize the struct cookieauth */
		if (structs_init(&cookieauth_type, NULL, auth) == -1) {
			alogf(LOG_ERR, "%s: %m", "structs_init");
			continue;
		}

		/* Decode binary data into the struct cookieauth */
		if (structs_set_binary(&cookieauth_type, NULL, &data,
		    auth, ebuf, sizeof(ebuf)) == -1) {
			DBG(HTTP_SERVLET_COOKIEAUTH,
			    "error decoding auth data: %s", ebuf);
			structs_free(&cookieauth_type, NULL, auth);
			structs_free(&authcookie_data_type, NULL, &data);
			continue;
		}
		structs_free(&authcookie_data_type, NULL, &data);

#if PDEL_DEBUG
		if (PDEL_DEBUG_ENABLED(HTTP_SERVLET_COOKIEAUTH)) {
			printf("COOKIE AUTH STRUCTURE\n");
			structs_xml_output(&cookieauth_type,
			    "auth", NULL, auth, stdout, NULL, STRUCTS_XML_FULL);
		}
#endif

		/* Validate auth cookie timestamp and expiration */
		if (auth->timestamp > now
		    || (auth->expire != 0 && now >= auth->expire)
		    || (auth->linger != 0
		      && now >= auth->timestamp + auth->linger)) {
			DBG(HTTP_SERVLET_COOKIEAUTH, "expired cookie");
			structs_free(&cookieauth_type, NULL, auth);
			continue;
		}

		/* Validate auth cookie identifier */
		if (structs_equal(&authcookie_data_type,
		    NULL, &auth->id, id) != 1) {
			DBG(HTTP_SERVLET_COOKIEAUTH, "wrong system id");
			structs_free(&cookieauth_type, NULL, auth);
			continue;
		}

		/* Validate auth cookie RSA signature */
		if (http_servlet_cookieauth_md5(auth, md5) == -1) {
			alogf(LOG_ERR, "%s: %m", "http_servlet_cookieauth_md5");
			structs_free(&cookieauth_type, NULL, auth);
			return (-1);
		}
		if (!rsa_util_verify_priv(privkey, md5,
		    auth->sig.data, auth->sig.length)) {
			DBG(HTTP_SERVLET_COOKIEAUTH, "invalid RSA signature");
			structs_free(&cookieauth_type, NULL, auth);
			continue;
		}

		/* OK */
		return (0);
	}

invalid:
	/* No valid cookie found */
	errno = EACCES;
	return (-1);
}

/*
 * Compute MD5 for RSA signature.
 */
static int
http_servlet_cookieauth_md5(const struct cookieauth *auth, u_char *md5)
{
	struct structs_data data;
	struct cookieauth copy;
	MD5_CTX ctx;

	/* Copy supplied auth structure */
	if (structs_get(&cookieauth_type, NULL, auth, &copy) == -1)
		return (-1);

	/* Zero out the 'sig' field */
	FREE(DATA_MEM_TYPE, copy.sig.data);
	memset(&copy.sig, 0, sizeof(copy.sig));

	/* Create binary encoding of 'copy' */
	if (structs_get_binary(&cookieauth_type,
	    NULL, &copy, DATA_MEM_TYPE, &data) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_get_binary");
		structs_free(&cookieauth_type, NULL, &copy);
		return (-1);
	}
	structs_free(&cookieauth_type, NULL, &copy);

	/* Compute MD5 of that */
	MD5_Init(&ctx);
	MD5_Update(&ctx, data.data, data.length);
	MD5_Final(md5, &ctx);

	/* Done */
	structs_free(&authcookie_data_type, NULL, &data);
	return (0);
}

#if PDEL_DEBUG
/*
 * Dump some data.
 */
static void
dump_data(const void *data, u_int plen, const char *fmt, ...)
{
	const u_char *pkt = data;
	const int num = 16;
	va_list args;
	int i, j;

	va_start(args, fmt);
	vprintf(fmt, args);
	printf("\n");
	va_end(args);
	for (i = 0; i < ((plen + num - 1) / num) * num; i += num) {
		printf("0x%04x  ", i);
		for (j = i; j < i + num; j++) {
			if (j < plen)
				printf("%02x", pkt[j]);
			else
				printf("  ");
			if ((j % 2) == 1)
				printf(" ");
		}
		printf("       ");
		for (j = i; j < i + num; j++) {
			if (j < plen) {
				printf("%c", isprint((u_char)pkt[j]) ?
				    pkt[j] : '.');
			}
		}
		printf("\n");
	}
}
#endif


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>