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
  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
 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
 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
 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 
 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
 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 *
 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 *
 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 *
 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 *
 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 *
 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 *
 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 *
 475 ipp_method(const request_rec *r)
 476 {
 477         return ("ipp");
 478 }
 479 
 480 /*ARGSUSED0*/
 481 static unsigned short
 482 ipp_port(const request_rec *r)
 483 {
 484         return (631);
 485 }
 486 
 487 /* Dispatch list for API hooks */
 488 /*ARGSUSED0*/
 489 static void
 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