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
dump_buffer(FILE * fp,char * tag,char * buffer,int bytes)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
read_data(void * fd,void * buf,size_t siz)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
write_data(void * fd,void * buf,size_t siz)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
discard_data(request_rec * r)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
_log_rerror(const char * file,int line,int level,request_rec * r,const char * fmt,...)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
ipp_handler(request_rec * r)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 *
create_ipp_dir_config(pool * p,char * dirspec)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 *
ipp_conformance(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_operation(cmd_parms * cmd,void * cfg,char * op,char * toggle)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 *
ipp_default_user(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_default_svc(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_module_hang(cmd_parms * cmd,void * cfg)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 *
ipp_method(const request_rec * r)475 ipp_method(const request_rec *r)
476 {
477 return ("ipp");
478 }
479
480 /*ARGSUSED0*/
481 static unsigned short
ipp_port(const request_rec * r)482 ipp_port(const request_rec *r)
483 {
484 return (631);
485 }
486
487 /* Dispatch list for API hooks */
488 /*ARGSUSED0*/
489 static void
ipp_register_hooks(apr_pool_t * p)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