OpenSolaris_b135/lib/print/mod_ipp/mod_ipp.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 */

/* $Id: mod_ipp.c 149 2006-04-25 16:55:01Z njacobs $ */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Internet Printing Protocol (IPP) module for Apache.
 */

#include "ap_config.h"

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <values.h>
#include <libintl.h>
#include <alloca.h>

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "papi.h"
#ifndef APACHE_RELEASE	/* appears to only exist in Apache 1.X */
#define	APACHE2
#include "apr_compat.h"
#endif

#include <papi.h>
#include <ipp-listener.h>

#ifndef APACHE2
module MODULE_VAR_EXPORT ipp_module;
#else
module AP_MODULE_DECLARE_DATA ipp_module;
#endif

#ifndef AP_INIT_TAKE1	/* Apache 2.X has this, but 1.3.X does not */
#define	AP_INIT_NO_ARGS(directive, action, arg, where, mesg) \
	{ directive, action, arg, where, NO_ARGS, mesg }
#define	AP_INIT_TAKE1(directive, action, arg, where, mesg) \
	{ directive, action, arg, where, TAKE1, mesg }
#define	AP_INIT_TAKE2(directive, action, arg, where, mesg) \
	{ directive, action, arg, where, TAKE2, mesg }
#endif

typedef struct {
	int conformance;
	char *default_user;
	char *default_svc;
	papi_attribute_t **operations;
} IPPListenerConfig;

#ifdef DEBUG
void
dump_buffer(FILE *fp, char *tag, char *buffer, int bytes)
{
	int i, j, ch;

	fprintf(fp, "%s %d(0x%x) bytes\n", (tag ? tag : ""), bytes, bytes);
	for (i = 0; i < bytes; i += 16) {
		fprintf(fp, "%s   ", (tag ? tag : ""));

		for (j = 0; j < 16 && (i + j) < bytes; j ++)
			fprintf(fp, " %02X", buffer[i + j] & 255);

		while (j < 16) {
			fprintf(fp, "   ");
			j++;
		}

		fprintf(fp, "    ");
		for (j = 0; j < 16 && (i + j) < bytes; j ++) {
			ch = buffer[i + j] & 255;
			if (ch < ' ' || ch == 127)
				ch = '.';
			putc(ch, fp);
		}
		putc('\n', fp);
	}
	fflush(fp);
}
#endif

static ssize_t
read_data(void *fd, void *buf, size_t siz)
{
	ssize_t len_read;
	request_rec *ap_r = (request_rec *)fd;

	len_read = ap_get_client_block(ap_r, buf, siz);
#ifndef APACHE2
	ap_reset_timeout(ap_r);
#endif

#ifdef DEBUG
	fprintf(stderr, "read_data(0x%8.8x, 0x%8.8x, %d): %d",
			fd, buf, siz, len_read);
	if (len_read < 0)
		fprintf(stderr, ": %s", strerror(errno));
	putc('\n', stderr);
	dump_buffer(stderr, "read_data:", buf, len_read);
#endif

	return (len_read);
}

static ssize_t
write_data(void *fd, void *buf, size_t siz)
{
	ssize_t len_written;
	request_rec *ap_r = (request_rec *)fd;

#ifndef APACHE2
	ap_reset_timeout(ap_r);
#endif
#ifdef DEBUG
	dump_buffer(stderr, "write_data:", buf, siz);
#endif
	len_written = ap_rwrite(buf, siz, ap_r);

	return (len_written);
}

static void
discard_data(request_rec *r)
{
#ifdef APACHE2
	(void) ap_discard_request_body(r);
#else
	/*
	 * This is taken from ap_discard_request_body().  The reason we can't
	 * just use it in Apache 1.3 is that it does various timeout things we
	 * don't want it to do.  Apache 2.0 doesn't do that, so we can safely
	 * use the normal function.
	 */
	if (r->read_chunked || r->remaining > 0) {
		char dumpbuf[HUGE_STRING_LEN];
		int i;

		do {
			i = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN);
#ifdef DEBUG
			dump_buffer(stderr, "discarded", dumpbuf, i);
#endif
		} while (i > 0);
	}
#endif
}

void _log_rerror(const char *file, int line, int level, request_rec *r,
	const char *fmt, ...)
{
	va_list args;
	size_t size;
	char *message = alloca(BUFSIZ);

	va_start(args, fmt);
	/*
	 * fill in the message.	 If the buffer is too small, allocate
	 * one that is large enough and fill it in.
	 */
	if ((size = vsnprintf(message, BUFSIZ, fmt, args)) >= BUFSIZ)
		if ((message = alloca(size)) != NULL)
			vsnprintf(message, size, fmt, args);
	va_end(args);

#ifdef APACHE2
	ap_log_rerror(file, line, level, NULL, r, message);
#else
	ap_log_rerror(file, line, level, r, message);
#endif
}

static int
ipp_handler(request_rec *r)
{
	papi_attribute_t **request = NULL, **response = NULL;
	IPPListenerConfig *config;
	papi_status_t status;
	int ret;

	/* Really, IPP is all POST requests */
	if (r->method_number != M_POST)
		return (DECLINED);

#ifndef APACHE2
	/*
	 * An IPP request must have a MIME type of "application/ipp"
	 * (RFC-2910, Section 4, page 19).  If it doesn't match this
	 * MIME type, we should decline the request and let someone else
	 * try and handle it.
	 */
	if (r->headers_in != NULL) {
		char *mime_type = (char *)ap_table_get(r->headers_in,
							"Content-Type");

		if ((mime_type == NULL) ||
		    (strcasecmp(mime_type, "application/ipp") != 0))
			return (DECLINED);
	}
#endif
	/* CHUNKED_DECHUNK might not work right for IPP? */
	if ((ret = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK)
		return (ret);

	if (!ap_should_client_block(r))
		return (HTTP_INTERNAL_SERVER_ERROR);

#ifndef APACHE2
	ap_soft_timeout("ipp_module: read/reply request ", r);
#endif
	/* read the IPP request off the network */
	status = ipp_read_message(read_data, r, &request, IPP_TYPE_REQUEST);

	if (status != PAPI_OK)
		_log_rerror(APLOG_MARK, APLOG_ERR, r,
			"read failed: %s\n", papiStatusString(status));
#ifdef DEBUG
	papiAttributeListPrint(stderr, request, "request (%d)  ", getpid());
#endif

	(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
		"originating-host", (char *)
#ifdef APACHE2
		ap_get_remote_host
			(r->connection, r->per_dir_config, REMOTE_NAME, NULL));
#else
		ap_get_remote_host
			(r->connection, r->per_dir_config, REMOTE_NAME));
#endif

	(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
				"uri-port", ap_get_server_port(r));
	if (r->headers_in != NULL) {
		char *host = (char *)ap_table_get(r->headers_in, "Host");

		if ((host == NULL) || (host[0] == '\0'))
			host = (char *)ap_get_server_name(r);

		(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
				"uri-host", host);
	}
	(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
				"uri-path", r->uri);

	config = ap_get_module_config(r->per_dir_config, &ipp_module);
	if (config != NULL) {
		(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
				"conformance", config->conformance);
		(void) papiAttributeListAddCollection(&request, PAPI_ATTR_EXCL,
				"operations", config->operations);
		if (config->default_user != NULL)
			(void) papiAttributeListAddString(&request,
						PAPI_ATTR_EXCL, "default-user",
						config->default_user);
		if (config->default_svc != NULL)
			(void) papiAttributeListAddString(&request,
					PAPI_ATTR_EXCL, "default-service",
					config->default_svc);
	}

	/*
	 * For Trusted Solaris, pass the fd number of the socket connection
	 * to the backend so the it can be forwarded to the backend print
	 * service to retrieve the sensativity label off of a multi-level
	 * port.
	 */
	(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
			"peer-socket", ap_bfileno(r->connection->client, B_RD));

	/* process the request */
	status = ipp_process_request(request, &response, read_data, r);
	if (status != PAPI_OK) {
		errno = 0;
		_log_rerror(APLOG_MARK, APLOG_ERR, r,
			"request failed: %s\n", papiStatusString(status));
		discard_data(r);
	}
#ifdef DEBUG
	fprintf(stderr, "processing result: %s\n", papiStatusString(status));
	papiAttributeListPrint(stderr, response, "response (%d)  ", getpid());
#endif

	/*
	 * If the client is using chunking and we have not yet received the
	 * final "0" sized chunk, we need to discard any data that may
	 * remain in the post request.
	 */
	if ((r->read_chunked != 0) &&
	    (ap_table_get(r->headers_in, "Content-Length") == NULL))
		discard_data(r);

	/* write an IPP response back to the network */
	r->content_type = "application/ipp";

#ifndef	APACHE2
	ap_send_http_header(r);
#endif

	status = ipp_write_message(write_data, r, response);
	if (status != PAPI_OK)
		_log_rerror(APLOG_MARK, APLOG_ERR, r,
			"write failed: %s\n", papiStatusString(status));
#ifdef DEBUG
	fprintf(stderr, "write result: %s\n", papiStatusString(status));
	fflush(stderr);
#endif

	papiAttributeListFree(request);
	papiAttributeListFree(response);

#ifndef APACHE2
	ap_kill_timeout(r);
	if (ap_rflush(r) < 0)
		_log_rerror(APLOG_MARK, APLOG_ERR, r,
			"flush failed, response may not have been sent");
#endif

	return (OK);
}


/*ARGSUSED1*/
static void *
create_ipp_dir_config(
#ifndef APACHE2
	pool *p,
#else
	apr_pool_t *p,
#endif
	char *dirspec)
{
	IPPListenerConfig *config =
#ifndef APACHE2
		ap_pcalloc(p, sizeof (*config));
#else
		apr_pcalloc(p, sizeof (*config));
#endif

	if (config != NULL) {
		(void) memset(config, 0, sizeof (*config));
		config->conformance = IPP_PARSE_CONFORMANCE_RASH;
		config->default_user = NULL;
		config->default_svc = NULL;
		(void) ipp_configure_operation(&config->operations, "required",
				"enable");
	}

	return (config);
}

/*ARGSUSED0*/
static const char *
ipp_conformance(cmd_parms *cmd, void *cfg, const char *arg)
{
	IPPListenerConfig *config = (IPPListenerConfig *)cfg;

	if (strncasecmp(arg, "automatic", 4) == 0) {
		config->conformance = IPP_PARSE_CONFORMANCE_RASH;
	} else if (strcasecmp(arg, "1.0") == 0) {
		config->conformance = IPP_PARSE_CONFORMANCE_LOOSE;
	} else if (strcasecmp(arg, "1.1") == 0) {
		config->conformance = IPP_PARSE_CONFORMANCE_STRICT;
	} else {
		return ("unknown conformance, try (automatic/1.0/1.1)");
	}

	return (NULL);
}

/*ARGSUSED0*/
static const char *
ipp_operation(cmd_parms *cmd, void *cfg, char *op, char *toggle)
{
	IPPListenerConfig *config = (IPPListenerConfig *)cfg;
	papi_status_t status;

	status = ipp_configure_operation(&config->operations, op, toggle);
	switch (status) {
	case PAPI_OK:
		return (NULL);
	case PAPI_BAD_ARGUMENT:
		return (gettext("internal error (invalid argument)"));
	default:
		return (papiStatusString(status));
	}

	/* NOTREACHED */
	/* return (gettext("contact your software vendor")); */
}

static const char *
ipp_default_user(cmd_parms *cmd, void *cfg, const char *arg)
{
	IPPListenerConfig *config = (IPPListenerConfig *)cfg;

	config->default_user = (char *)arg;

	return (NULL);
}

static const char *
ipp_default_svc(cmd_parms *cmd, void *cfg, const char *arg)
{
	IPPListenerConfig *config = (IPPListenerConfig *)cfg;

	config->default_svc = (char *)arg;

	return (NULL);
}

#ifdef DEBUG
/*ARGSUSED0*/
static const char *
ipp_module_hang(cmd_parms *cmd, void *cfg)
{
	static int i = 1;

	/* wait so we can attach a debugger, assign i = 0,  and step through */
	while (i);

	return (NULL);
}
#endif /* DEBUG */

static const command_rec ipp_cmds[] =
{
	AP_INIT_TAKE1("ipp-conformance", ipp_conformance, NULL, ACCESS_CONF,
		"IPP protocol conformance (loose/strict)"),
	AP_INIT_TAKE2("ipp-operation", ipp_operation, NULL, ACCESS_CONF,
		"IPP protocol operations to enable/disable)"),
	AP_INIT_TAKE1("ipp-default-user", ipp_default_user, NULL, ACCESS_CONF,
		"default user for various operations"),
	AP_INIT_TAKE1("ipp-default-service", ipp_default_svc, NULL, ACCESS_CONF,
		"default service for various operations"),
#ifdef DEBUG
	AP_INIT_NO_ARGS("ipp-module-hang", ipp_module_hang, NULL, ACCESS_CONF,
		"hang the module until we can attach a debugger (no args)"),
#endif
	{ NULL }
};

#ifdef APACHE2
/*ARGSUSED0*/
static const char *
ipp_method(const request_rec *r)
{
	return ("ipp");
}

/*ARGSUSED0*/
static unsigned short
ipp_port(const request_rec *r)
{
	return (631);
}

/* Dispatch list for API hooks */
/*ARGSUSED0*/
static void
ipp_register_hooks(apr_pool_t *p)
{
	static const char * const modules[] = { "mod_dir.c", NULL };

	/* Need to make sure we don't get directory listings by accident */
	ap_hook_handler(ipp_handler, NULL, modules, APR_HOOK_MIDDLE);
	ap_hook_default_port(ipp_port, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_http_method(ipp_method, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA ipp_module = {
	STANDARD20_MODULE_STUFF,
	create_ipp_dir_config,		/* create per-dir    config	*/
	NULL,				/* merge  per-dir    config	*/
	NULL,				/* create per-server config	*/
	NULL,				/* merge  per-server config	*/
	ipp_cmds,			/* table of config commands	*/
	ipp_register_hooks		/* register hooks		*/
};

#else	/* Apache 1.X */

/* Dispatch list of content handlers */
static const handler_rec ipp_handlers[] = {
	/*
	 * This handler association causes all IPP request with the
	 * correct MIME type to call the protocol handler.
	 */
	{ "application/ipp", ipp_handler },
	/*
	 * This hander association is causes everything to go through the IPP
	 * protocol request handler.  This is necessary because client POST
	 * request may be for something outside of the normal printer-uri
	 * space.
	 */
	{ "*/*", ipp_handler },

	{ NULL, NULL }
};


module MODULE_VAR_EXPORT ipp_module = {
	STANDARD_MODULE_STUFF,
	NULL,			/* module initializer			*/
	create_ipp_dir_config,	/* create per-dir    config structures	*/
	NULL,			/* merge  per-dir    config structures	*/
	NULL,			/* create per-server config structures	*/
	NULL,			/* merge  per-server config structures	*/
	ipp_cmds,		/* table of config file commands	*/
	ipp_handlers,		/* [#8] MIME-typed-dispatched handlers	*/
	NULL,			/* [#1] URI to filename translation	*/
	NULL,			/* [#4] validate user id from request	*/
	NULL,			/* [#5] check if the user is ok _here_	*/
	NULL,			/* [#3] check access by host address	*/
	NULL,			/* [#6] determine MIME type		*/
	NULL,			/* [#7] pre-run fixups			*/
	NULL,			/* [#9] log a transaction		*/
	NULL,			/* [#2] header parser			*/
	NULL,			/* child_init				*/
	NULL,			/* child_exit				*/
	NULL			/* [#0] post read-request		*/
};
#endif