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