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