/* * 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 #include #include #include #include #include #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 #include #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