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
dump_buffer(FILE * fp,char * tag,char * buffer,int bytes)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
read_data(void * fd,void * buf,size_t siz)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
write_data(void * fd,void * buf,size_t siz)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
discard_data(request_rec * r)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
_log_rerror(const char * file,int line,int level,request_rec * r,const char * fmt,...)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
ipp_handler(request_rec * r)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 *
create_ipp_dir_config(pool * p,char * dirspec)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 *
ipp_conformance(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_operation(cmd_parms * cmd,void * cfg,const char * op,const char * toggle)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 *
ipp_default_user(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_default_svc(cmd_parms * cmd,void * cfg,const char * arg)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 *
ipp_module_hang(cmd_parms * cmd,void * cfg)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 *
ipp_scheme(const request_rec * r)500 ipp_scheme(const request_rec *r)
501 {
502 return ("ipp");
503 }
504
505 /*ARGSUSED0*/
506 static unsigned short
ipp_port(const request_rec * r)507 ipp_port(const request_rec *r)
508 {
509 return (631);
510 }
511
512 /* Dispatch list for API hooks */
513 /*ARGSUSED0*/
514 static void
ipp_register_hooks(apr_pool_t * p)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