mod_ipp.c revision 355b4669e025ff377602b6fc7caaf30dbc218371
/*
* 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