1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 */
27
28/* $Id: mod_ipp.c 149 2006-04-25 16:55:01Z njacobs $ */
29
30#pragma ident	"%Z%%M%	%I%	%E% SMI"
31
32/*
33 * Internet Printing Protocol (IPP) module for Apache.
34 */
35
36#include "ap_config.h"
37
38#include <stdio.h>
39#include <time.h>
40#include <sys/time.h>
41#include <values.h>
42#include <libintl.h>
43#include <alloca.h>
44
45#include "httpd.h"
46#include "http_config.h"
47#include "http_core.h"
48#include "http_protocol.h"
49#include "http_log.h"
50#include "http_main.h"
51#include "papi.h"
52#ifndef APACHE_RELEASE	/* appears to only exist in Apache 1.X */
53#define	APACHE2
54#include "apr_compat.h"
55#endif
56
57#include <papi.h>
58#include <ipp-listener.h>
59
60#ifndef APACHE2
61module MODULE_VAR_EXPORT ipp_module;
62#else
63module AP_MODULE_DECLARE_DATA ipp_module;
64#endif
65
66#ifndef AP_INIT_TAKE1	/* Apache 2.X has this, but 1.3.X does not */
67#define	AP_INIT_NO_ARGS(directive, action, arg, where, mesg) \
68	{ directive, action, arg, where, NO_ARGS, mesg }
69#define	AP_INIT_TAKE1(directive, action, arg, where, mesg) \
70	{ directive, action, arg, where, TAKE1, mesg }
71#define	AP_INIT_TAKE2(directive, action, arg, where, mesg) \
72	{ directive, action, arg, where, TAKE2, mesg }
73#endif
74
75typedef struct {
76	int conformance;
77	char *default_user;
78	char *default_svc;
79	papi_attribute_t **operations;
80} IPPListenerConfig;
81
82#ifdef DEBUG
83void
84dump_buffer(FILE *fp, char *tag, char *buffer, int bytes)
85{
86	int i, j, ch;
87
88	fprintf(fp, "%s %d(0x%x) bytes\n", (tag ? tag : ""), bytes, bytes);
89	for (i = 0; i < bytes; i += 16) {
90		fprintf(fp, "%s   ", (tag ? tag : ""));
91
92		for (j = 0; j < 16 && (i + j) < bytes; j ++)
93			fprintf(fp, " %02X", buffer[i + j] & 255);
94
95		while (j < 16) {
96			fprintf(fp, "   ");
97			j++;
98		}
99
100		fprintf(fp, "    ");
101		for (j = 0; j < 16 && (i + j) < bytes; j ++) {
102			ch = buffer[i + j] & 255;
103			if (ch < ' ' || ch == 127)
104				ch = '.';
105			putc(ch, fp);
106		}
107		putc('\n', fp);
108	}
109	fflush(fp);
110}
111#endif
112
113static ssize_t
114read_data(void *fd, void *buf, size_t siz)
115{
116	ssize_t len_read;
117	request_rec *ap_r = (request_rec *)fd;
118
119	len_read = ap_get_client_block(ap_r, buf, siz);
120#ifndef APACHE2
121	ap_reset_timeout(ap_r);
122#endif
123
124#ifdef DEBUG
125	fprintf(stderr, "read_data(0x%8.8x, 0x%8.8x, %d): %d",
126			fd, buf, siz, len_read);
127	if (len_read < 0)
128		fprintf(stderr, ": %s", strerror(errno));
129	putc('\n', stderr);
130	dump_buffer(stderr, "read_data:", buf, len_read);
131#endif
132
133	return (len_read);
134}
135
136static ssize_t
137write_data(void *fd, void *buf, size_t siz)
138{
139	ssize_t len_written;
140	request_rec *ap_r = (request_rec *)fd;
141
142#ifndef APACHE2
143	ap_reset_timeout(ap_r);
144#endif
145#ifdef DEBUG
146	dump_buffer(stderr, "write_data:", buf, siz);
147#endif
148	len_written = ap_rwrite(buf, siz, ap_r);
149
150	return (len_written);
151}
152
153static void
154discard_data(request_rec *r)
155{
156#ifdef APACHE2
157	(void) ap_discard_request_body(r);
158#else
159	/*
160	 * This is taken from ap_discard_request_body().  The reason we can't
161	 * just use it in Apache 1.3 is that it does various timeout things we
162	 * don't want it to do.  Apache 2.0 doesn't do that, so we can safely
163	 * use the normal function.
164	 */
165	if (r->read_chunked || r->remaining > 0) {
166		char dumpbuf[HUGE_STRING_LEN];
167		int i;
168
169		do {
170			i = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN);
171#ifdef DEBUG
172			dump_buffer(stderr, "discarded", dumpbuf, i);
173#endif
174		} while (i > 0);
175	}
176#endif
177}
178
179void _log_rerror(const char *file, int line, int level, request_rec *r,
180	const char *fmt, ...)
181{
182	va_list args;
183	size_t size;
184	char *message = alloca(BUFSIZ);
185
186	va_start(args, fmt);
187	/*
188	 * fill in the message.	 If the buffer is too small, allocate
189	 * one that is large enough and fill it in.
190	 */
191	if ((size = vsnprintf(message, BUFSIZ, fmt, args)) >= BUFSIZ)
192		if ((message = alloca(size)) != NULL)
193			vsnprintf(message, size, fmt, args);
194	va_end(args);
195
196#ifdef APACHE2
197	ap_log_rerror(file, line, level, NULL, r, message);
198#else
199	ap_log_rerror(file, line, level, r, message);
200#endif
201}
202
203static int
204ipp_handler(request_rec *r)
205{
206	papi_attribute_t **request = NULL, **response = NULL;
207	IPPListenerConfig *config;
208	papi_status_t status;
209	int ret;
210
211	/* Really, IPP is all POST requests */
212	if (r->method_number != M_POST)
213		return (DECLINED);
214
215#ifndef APACHE2
216	/*
217	 * An IPP request must have a MIME type of "application/ipp"
218	 * (RFC-2910, Section 4, page 19).  If it doesn't match this
219	 * MIME type, we should decline the request and let someone else
220	 * try and handle it.
221	 */
222	if (r->headers_in != NULL) {
223		char *mime_type = (char *)ap_table_get(r->headers_in,
224							"Content-Type");
225
226		if ((mime_type == NULL) ||
227		    (strcasecmp(mime_type, "application/ipp") != 0))
228			return (DECLINED);
229	}
230#endif
231	/* CHUNKED_DECHUNK might not work right for IPP? */
232	if ((ret = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK)
233		return (ret);
234
235	if (!ap_should_client_block(r))
236		return (HTTP_INTERNAL_SERVER_ERROR);
237
238#ifndef APACHE2
239	ap_soft_timeout("ipp_module: read/reply request ", r);
240#endif
241	/* read the IPP request off the network */
242	status = ipp_read_message(read_data, r, &request, IPP_TYPE_REQUEST);
243
244	if (status != PAPI_OK)
245		_log_rerror(APLOG_MARK, APLOG_ERR, r,
246			"read failed: %s\n", papiStatusString(status));
247#ifdef DEBUG
248	papiAttributeListPrint(stderr, request, "request (%d)  ", getpid());
249#endif
250
251	(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
252		"originating-host", (char *)
253#ifdef APACHE2
254		ap_get_remote_host
255			(r->connection, r->per_dir_config, REMOTE_NAME, NULL));
256#else
257		ap_get_remote_host
258			(r->connection, r->per_dir_config, REMOTE_NAME));
259#endif
260
261	(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
262				"uri-port", ap_get_server_port(r));
263	if (r->headers_in != NULL) {
264		char *host = (char *)ap_table_get(r->headers_in, "Host");
265
266		if ((host == NULL) || (host[0] == '\0'))
267			host = (char *)ap_get_server_name(r);
268
269		(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
270				"uri-host", host);
271	}
272	(void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL,
273				"uri-path", r->uri);
274
275	config = ap_get_module_config(r->per_dir_config, &ipp_module);
276	if (config != NULL) {
277		(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
278				"conformance", config->conformance);
279		(void) papiAttributeListAddCollection(&request, PAPI_ATTR_EXCL,
280				"operations", config->operations);
281		if (config->default_user != NULL)
282			(void) papiAttributeListAddString(&request,
283						PAPI_ATTR_EXCL, "default-user",
284						config->default_user);
285		if (config->default_svc != NULL)
286			(void) papiAttributeListAddString(&request,
287					PAPI_ATTR_EXCL, "default-service",
288					config->default_svc);
289	}
290
291	/*
292	 * For Trusted Solaris, pass the fd number of the socket connection
293	 * to the backend so the it can be forwarded to the backend print
294	 * service to retrieve the sensativity label off of a multi-level
295	 * port.
296	 */
297	(void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL,
298			"peer-socket", ap_bfileno(r->connection->client, B_RD));
299
300	/* process the request */
301	status = ipp_process_request(request, &response, read_data, r);
302	if (status != PAPI_OK) {
303		errno = 0;
304		_log_rerror(APLOG_MARK, APLOG_ERR, r,
305			"request failed: %s\n", papiStatusString(status));
306		discard_data(r);
307	}
308#ifdef DEBUG
309	fprintf(stderr, "processing result: %s\n", papiStatusString(status));
310	papiAttributeListPrint(stderr, response, "response (%d)  ", getpid());
311#endif
312
313	/*
314	 * If the client is using chunking and we have not yet received the
315	 * final "0" sized chunk, we need to discard any data that may
316	 * remain in the post request.
317	 */
318	if ((r->read_chunked != 0) &&
319	    (ap_table_get(r->headers_in, "Content-Length") == NULL))
320		discard_data(r);
321
322	/* write an IPP response back to the network */
323	r->content_type = "application/ipp";
324
325#ifndef	APACHE2
326	ap_send_http_header(r);
327#endif
328
329	status = ipp_write_message(write_data, r, response);
330	if (status != PAPI_OK)
331		_log_rerror(APLOG_MARK, APLOG_ERR, r,
332			"write failed: %s\n", papiStatusString(status));
333#ifdef DEBUG
334	fprintf(stderr, "write result: %s\n", papiStatusString(status));
335	fflush(stderr);
336#endif
337
338	papiAttributeListFree(request);
339	papiAttributeListFree(response);
340
341#ifndef APACHE2
342	ap_kill_timeout(r);
343	if (ap_rflush(r) < 0)
344		_log_rerror(APLOG_MARK, APLOG_ERR, r,
345			"flush failed, response may not have been sent");
346#endif
347
348	return (OK);
349}
350
351
352/*ARGSUSED1*/
353static void *
354create_ipp_dir_config(
355#ifndef APACHE2
356	pool *p,
357#else
358	apr_pool_t *p,
359#endif
360	char *dirspec)
361{
362	IPPListenerConfig *config =
363#ifndef APACHE2
364		ap_pcalloc(p, sizeof (*config));
365#else
366		apr_pcalloc(p, sizeof (*config));
367#endif
368
369	if (config != NULL) {
370		(void) memset(config, 0, sizeof (*config));
371		config->conformance = IPP_PARSE_CONFORMANCE_RASH;
372		config->default_user = NULL;
373		config->default_svc = NULL;
374		(void) ipp_configure_operation(&config->operations, "required",
375				"enable");
376	}
377
378	return (config);
379}
380
381/*ARGSUSED0*/
382static const char *
383ipp_conformance(cmd_parms *cmd, void *cfg, const char *arg)
384{
385	IPPListenerConfig *config = (IPPListenerConfig *)cfg;
386
387	if (strncasecmp(arg, "automatic", 4) == 0) {
388		config->conformance = IPP_PARSE_CONFORMANCE_RASH;
389	} else if (strcasecmp(arg, "1.0") == 0) {
390		config->conformance = IPP_PARSE_CONFORMANCE_LOOSE;
391	} else if (strcasecmp(arg, "1.1") == 0) {
392		config->conformance = IPP_PARSE_CONFORMANCE_STRICT;
393	} else {
394		return ("unknown conformance, try (automatic/1.0/1.1)");
395	}
396
397	return (NULL);
398}
399
400/*ARGSUSED0*/
401static const char *
402ipp_operation(cmd_parms *cmd, void *cfg, char *op, char *toggle)
403{
404	IPPListenerConfig *config = (IPPListenerConfig *)cfg;
405	papi_status_t status;
406
407	status = ipp_configure_operation(&config->operations, op, toggle);
408	switch (status) {
409	case PAPI_OK:
410		return (NULL);
411	case PAPI_BAD_ARGUMENT:
412		return (gettext("internal error (invalid argument)"));
413	default:
414		return (papiStatusString(status));
415	}
416
417	/* NOTREACHED */
418	/* return (gettext("contact your software vendor")); */
419}
420
421static const char *
422ipp_default_user(cmd_parms *cmd, void *cfg, const char *arg)
423{
424	IPPListenerConfig *config = (IPPListenerConfig *)cfg;
425
426	config->default_user = (char *)arg;
427
428	return (NULL);
429}
430
431static const char *
432ipp_default_svc(cmd_parms *cmd, void *cfg, const char *arg)
433{
434	IPPListenerConfig *config = (IPPListenerConfig *)cfg;
435
436	config->default_svc = (char *)arg;
437
438	return (NULL);
439}
440
441#ifdef DEBUG
442/*ARGSUSED0*/
443static const char *
444ipp_module_hang(cmd_parms *cmd, void *cfg)
445{
446	static int i = 1;
447
448	/* wait so we can attach a debugger, assign i = 0,  and step through */
449	while (i);
450
451	return (NULL);
452}
453#endif /* DEBUG */
454
455static const command_rec ipp_cmds[] =
456{
457	AP_INIT_TAKE1("ipp-conformance", ipp_conformance, NULL, ACCESS_CONF,
458		"IPP protocol conformance (loose/strict)"),
459	AP_INIT_TAKE2("ipp-operation", ipp_operation, NULL, ACCESS_CONF,
460		"IPP protocol operations to enable/disable)"),
461	AP_INIT_TAKE1("ipp-default-user", ipp_default_user, NULL, ACCESS_CONF,
462		"default user for various operations"),
463	AP_INIT_TAKE1("ipp-default-service", ipp_default_svc, NULL, ACCESS_CONF,
464		"default service for various operations"),
465#ifdef DEBUG
466	AP_INIT_NO_ARGS("ipp-module-hang", ipp_module_hang, NULL, ACCESS_CONF,
467		"hang the module until we can attach a debugger (no args)"),
468#endif
469	{ NULL }
470};
471
472#ifdef APACHE2
473/*ARGSUSED0*/
474static const char *
475ipp_method(const request_rec *r)
476{
477	return ("ipp");
478}
479
480/*ARGSUSED0*/
481static unsigned short
482ipp_port(const request_rec *r)
483{
484	return (631);
485}
486
487/* Dispatch list for API hooks */
488/*ARGSUSED0*/
489static void
490ipp_register_hooks(apr_pool_t *p)
491{
492	static const char * const modules[] = { "mod_dir.c", NULL };
493
494	/* Need to make sure we don't get directory listings by accident */
495	ap_hook_handler(ipp_handler, NULL, modules, APR_HOOK_MIDDLE);
496	ap_hook_default_port(ipp_port, NULL, NULL, APR_HOOK_MIDDLE);
497	ap_hook_http_method(ipp_method, NULL, NULL, APR_HOOK_MIDDLE);
498}
499
500module AP_MODULE_DECLARE_DATA ipp_module = {
501	STANDARD20_MODULE_STUFF,
502	create_ipp_dir_config,		/* create per-dir    config	*/
503	NULL,				/* merge  per-dir    config	*/
504	NULL,				/* create per-server config	*/
505	NULL,				/* merge  per-server config	*/
506	ipp_cmds,			/* table of config commands	*/
507	ipp_register_hooks		/* register hooks		*/
508};
509
510#else	/* Apache 1.X */
511
512/* Dispatch list of content handlers */
513static const handler_rec ipp_handlers[] = {
514	/*
515	 * This handler association causes all IPP request with the
516	 * correct MIME type to call the protocol handler.
517	 */
518	{ "application/ipp", ipp_handler },
519	/*
520	 * This hander association is causes everything to go through the IPP
521	 * protocol request handler.  This is necessary because client POST
522	 * request may be for something outside of the normal printer-uri
523	 * space.
524	 */
525	{ "*/*", ipp_handler },
526
527	{ NULL, NULL }
528};
529
530
531module MODULE_VAR_EXPORT ipp_module = {
532	STANDARD_MODULE_STUFF,
533	NULL,			/* module initializer			*/
534	create_ipp_dir_config,	/* create per-dir    config structures	*/
535	NULL,			/* merge  per-dir    config structures	*/
536	NULL,			/* create per-server config structures	*/
537	NULL,			/* merge  per-server config structures	*/
538	ipp_cmds,		/* table of config file commands	*/
539	ipp_handlers,		/* [#8] MIME-typed-dispatched handlers	*/
540	NULL,			/* [#1] URI to filename translation	*/
541	NULL,			/* [#4] validate user id from request	*/
542	NULL,			/* [#5] check if the user is ok _here_	*/
543	NULL,			/* [#3] check access by host address	*/
544	NULL,			/* [#6] determine MIME type		*/
545	NULL,			/* [#7] pre-run fixups			*/
546	NULL,			/* [#9] log a transaction		*/
547	NULL,			/* [#2] header parser			*/
548	NULL,			/* child_init				*/
549	NULL,			/* child_exit				*/
550	NULL			/* [#0] post read-request		*/
551};
552#endif
553