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