xref: /titanic_44/usr/src/lib/print/mod_ipp/mod_ipp.c (revision 2b4a78020b9c38d1b95e2f3fefa6d6e4be382d1f)
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
61 module MODULE_VAR_EXPORT ipp_module;
62 #else
63 module 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 
75 typedef struct {
76 	int conformance;
77 	char *default_user;
78 	char *default_svc;
79 	papi_attribute_t **operations;
80 } IPPListenerConfig;
81 
82 #ifdef DEBUG
83 void
84 dump_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 
113 static ssize_t
114 read_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 
136 static ssize_t
137 write_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 
153 static void
154 discard_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 
179 void _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 
203 static int
204 ipp_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*/
353 static void *
354 create_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*/
382 static const char *
383 ipp_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*/
401 static const char *
402 ipp_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 
421 static const char *
422 ipp_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 
431 static const char *
432 ipp_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*/
443 static const char *
444 ipp_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 
455 static 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*/
474 static const char *
475 ipp_method(const request_rec *r)
476 {
477 	return ("ipp");
478 }
479 
480 /*ARGSUSED0*/
481 static unsigned short
482 ipp_port(const request_rec *r)
483 {
484 	return (631);
485 }
486 
487 /* Dispatch list for API hooks */
488 /*ARGSUSED0*/
489 static void
490 ipp_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 
500 module 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 */
513 static 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 
531 module 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