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