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  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  24  */
  25 
  26 /*
  27  * CUPS support for the SMB and SPOOLSS print services.
  28  */
  29 
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <strings.h>
  33 #include <syslog.h>
  34 #include <signal.h>
  35 #include <pthread.h>
  36 #include <synch.h>
  37 #include <dlfcn.h>
  38 #include <errno.h>
  39 #include <smbsrv/smb.h>
  40 #include <smbsrv/smb_share.h>
  41 #include "smbd.h"
  42 
  43 #ifdef  HAVE_CUPS
  44 #include <cups/cups.h>
  45 
  46 #define SMB_SPOOL_WAIT                  2
  47 #define SMBD_PJOBLEN                    256
  48 #define SMBD_PRINTER                    "Postscript"
  49 #define SMBD_FN_PREFIX                  "cifsprintjob-"
  50 #define SMBD_CUPS_SPOOL_DIR             "//var//spool//cups"
  51 #define SMBD_CUPS_DOCNAME               "generic_doc"
  52 
  53 typedef struct smbd_printjob {
  54         pid_t           pj_pid;
  55         int             pj_sysjob;
  56         int             pj_fd;
  57         time_t          pj_start_time;
  58         int             pj_status;
  59         size_t          pj_size;
  60         int             pj_page_count;
  61         boolean_t       pj_isspooled;
  62         boolean_t       pj_jobnum;
  63         char            pj_filename[SMBD_PJOBLEN];
  64         char            pj_jobname[SMBD_PJOBLEN];
  65         char            pj_username[SMBD_PJOBLEN];
  66         char            pj_queuename[SMBD_PJOBLEN];
  67 } smbd_printjob_t;
  68 
  69 typedef struct smb_cups_ops {
  70         void            *cups_hdl;
  71         cups_lang_t     *(*cupsLangDefault)();
  72         const char      *(*cupsLangEncoding)(cups_lang_t *);
  73         void            (*cupsLangFree)(cups_lang_t *);
  74         ipp_status_t    (*cupsLastError)();
  75         int             (*cupsGetDests)(cups_dest_t **);
  76         void            (*cupsFreeDests)(int, cups_dest_t *);
  77         ipp_t           *(*cupsDoFileRequest)(http_t *, ipp_t *,
  78             const char *, const char *);
  79         ipp_t           *(*ippNew)();
  80         void            (*ippDelete)();
  81         char            *(*ippErrorString)();
  82         ipp_attribute_t *(*ippAddString)();
  83         void            (*httpClose)(http_t *);
  84         http_t          *(*httpConnect)(const char *, int);
  85 } smb_cups_ops_t;
  86 
  87 static uint32_t smbd_cups_jobnum = 1;
  88 static smb_cups_ops_t smb_cups;
  89 static mutex_t smbd_cups_mutex;
  90 
  91 static void *smbd_spool_monitor(void *);
  92 static smb_cups_ops_t *smbd_cups_ops(void);
  93 static void smbd_print_share_comment(smb_share_t *, cups_dest_t *);
  94 static void *smbd_share_printers(void *);
  95 static void smbd_spool_copyfile(smb_inaddr_t *, char *, char *, char *);
  96 
  97 extern smbd_t smbd;
  98 
  99 /*
 100  * Start the spool thread.
 101  * Returns 0 on success, an error number if thread creation fails.
 102  */
 103 void
 104 smbd_spool_start(void)
 105 {
 106         pthread_attr_t  attr;
 107         int             rc;
 108 
 109         if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
 110                 return;
 111 
 112         (void) pthread_attr_init(&attr);
 113         (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 114         rc = pthread_create(&smbd.s_spool_tid, &attr, smbd_spool_monitor, NULL);
 115         (void) pthread_attr_destroy(&attr);
 116 
 117         if (rc != 0)
 118                 smb_log(smbd.s_loghd, LOG_NOTICE,
 119                     "failed to start print monitor: %s", strerror(errno));
 120 }
 121 
 122 /*
 123  * A single pthread_kill should be sufficient but we include
 124  * a couple of retries to avoid implementation idiosyncrasies
 125  * around signal delivery.
 126  */
 127 void
 128 smbd_spool_stop(void)
 129 {
 130         int     i;
 131 
 132         if (pthread_self() == smbd.s_spool_tid)
 133                 return;
 134 
 135         for (i = 0; i < 3 && smbd.s_spool_tid != 0; ++i) {
 136                 if (pthread_kill(smbd.s_spool_tid, SIGTERM) == ESRCH)
 137                         break;
 138 
 139                 (void) sleep(1);
 140         }
 141 }
 142 
 143 /*
 144  * This thread blocks waiting for close print file in the kernel.
 145  * It then uses the data returned from the ioctl to copy the spool file
 146  * into the cups spooler.
 147  *
 148  * This mechanism is really only used by Windows Vista and Windows 7.
 149  * Other versions of Windows create a zero size file, which is removed
 150  * by smbd_spool_copyfile.
 151  */
 152 /*ARGSUSED*/
 153 static void *
 154 smbd_spool_monitor(void *arg)
 155 {
 156         uint32_t        spool_num;
 157         char            username[MAXNAMELEN];
 158         char            path[MAXPATHLEN];
 159         smb_inaddr_t    ipaddr;
 160         int             error_retry_cnt = 5;
 161 
 162         smbd_online_wait("smbd_spool_monitor");
 163 
 164         spoolss_register_copyfile(smbd_spool_copyfile);
 165 
 166         while (!smbd.s_shutting_down && (error_retry_cnt > 0)) {
 167                 errno = 0;
 168 
 169                 if (smb_kmod_get_spool_doc(&spool_num, username,
 170                     path, &ipaddr) == 0) {
 171                         smbd_spool_copyfile(&ipaddr,
 172                             username, path, SMBD_CUPS_DOCNAME);
 173                         error_retry_cnt = 5;
 174                 } else {
 175                         if (errno == ECANCELED)
 176                                 break;
 177                         if ((errno != EINTR) && (errno != EAGAIN))
 178                                 error_retry_cnt--;
 179                         (void) sleep(SMB_SPOOL_WAIT);
 180                 }
 181         }
 182 
 183         spoolss_register_copyfile(NULL);
 184         smbd.s_spool_tid = 0;
 185         return (NULL);
 186 }
 187 
 188 /*
 189  * All versions of windows use this function to spool files to a printer
 190  * via the cups interface
 191  */
 192 static void
 193 smbd_spool_copyfile(smb_inaddr_t *ipaddr, char *username, char *path,
 194     char *doc_name)
 195 {
 196         smb_cups_ops_t  *cups;
 197         http_t          *http = NULL;           /* HTTP connection to server */
 198         ipp_t           *request = NULL;        /* IPP Request */
 199         ipp_t           *response = NULL;       /* IPP Response */
 200         cups_lang_t     *language = NULL;       /* Default language */
 201         char            uri[HTTP_MAX_URI];      /* printer-uri attribute */
 202         char            new_jobname[SMBD_PJOBLEN];
 203         smbd_printjob_t pjob;
 204         char            clientname[INET6_ADDRSTRLEN];
 205         struct stat     sbuf;
 206         int             rc = 1;
 207 
 208         if (stat(path, &sbuf)) {
 209                 smb_log(smbd.s_loghd, LOG_INFO, "smbd_spool_copyfile: %s: %s",
 210                     path, strerror(errno));
 211                 return;
 212         }
 213 
 214         /*
 215          * Remove zero size files and return; these were inadvertantly
 216          * created by XP or 2000.
 217          */
 218         if (sbuf.st_size == 0) {
 219                 if (remove(path) != 0)
 220                         smb_log(smbd.s_loghd, LOG_INFO,
 221                             "smbd_spool_copyfile: cannot remove %s: %s",
 222                             path, strerror(errno));
 223                 return;
 224         }
 225 
 226         if ((cups = smbd_cups_ops()) == NULL)
 227                 return;
 228 
 229         if ((http = cups->httpConnect("localhost", 631)) == NULL) {
 230                 smb_log(smbd.s_loghd, LOG_INFO,
 231                     "smbd_spool_copyfile: cupsd not running");
 232                 return;
 233         }
 234 
 235         if ((request = cups->ippNew()) == NULL) {
 236                 smb_log(smbd.s_loghd, LOG_INFO,
 237                     "smbd_spool_copyfile: ipp not running");
 238                 return;
 239         }
 240 
 241         request->request.op.operation_id = IPP_PRINT_JOB;
 242         request->request.op.request_id = 1;
 243         language = cups->cupsLangDefault();
 244 
 245         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
 246             "attributes-charset", NULL, cups->cupsLangEncoding(language));
 247 
 248         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
 249             "attributes-natural-language", NULL, language->language);
 250 
 251         (void) snprintf(uri, sizeof (uri), "ipp://localhost/printers/%s",
 252             SMBD_PRINTER);
 253         pjob.pj_pid = pthread_self();
 254         pjob.pj_sysjob = 10;
 255         (void) strlcpy(pjob.pj_filename, path, SMBD_PJOBLEN);
 256         pjob.pj_start_time = time(NULL);
 257         pjob.pj_status = 2;
 258         pjob.pj_size = sbuf.st_blocks * 512;
 259         pjob.pj_page_count = 1;
 260         pjob.pj_isspooled = B_TRUE;
 261         pjob.pj_jobnum = smbd_cups_jobnum;
 262 
 263         (void) strlcpy(pjob.pj_jobname, doc_name, SMBD_PJOBLEN);
 264         (void) strlcpy(pjob.pj_username, username, SMBD_PJOBLEN);
 265         (void) strlcpy(pjob.pj_queuename, SMBD_CUPS_SPOOL_DIR, SMBD_PJOBLEN);
 266 
 267         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
 268             "printer-uri", NULL, uri);
 269 
 270         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
 271             "requesting-user-name", NULL, pjob.pj_username);
 272 
 273         if (smb_inet_ntop(ipaddr, clientname,
 274             SMB_IPSTRLEN(ipaddr->a_family)) == NULL) {
 275                 smb_log(smbd.s_loghd, LOG_INFO,
 276                     "smbd_spool_copyfile: %s: unknown client", clientname);
 277                 goto out;
 278         }
 279 
 280         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
 281             "job-originating-host-name", NULL, clientname);
 282 
 283         (void) snprintf(new_jobname, SMBD_PJOBLEN, "%s%d",
 284             SMBD_FN_PREFIX, pjob.pj_jobnum);
 285         cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
 286             "job-name", NULL, new_jobname);
 287 
 288         (void) snprintf(uri, sizeof (uri) - 1, "/printers/%s", SMBD_PRINTER);
 289 
 290         response = cups->cupsDoFileRequest(http, request, uri,
 291             pjob.pj_filename);
 292         if (response != NULL) {
 293                 if (response->request.status.status_code >= IPP_OK_CONFLICT) {
 294                         smb_log(smbd.s_loghd, LOG_ERR,
 295                             "smbd_spool_copyfile: printer %s: %s",
 296                             SMBD_PRINTER,
 297                             cups->ippErrorString(cups->cupsLastError()));
 298                 } else {
 299                         atomic_inc_32(&smbd_cups_jobnum);
 300                         rc = 0;
 301                 }
 302         } else {
 303                 smb_log(smbd.s_loghd, LOG_ERR,
 304                     "smbd_spool_copyfile: unable to print to %s",
 305                     cups->ippErrorString(cups->cupsLastError()));
 306         }
 307 
 308         if (rc == 0)
 309                 (void) unlink(pjob.pj_filename);
 310 
 311 out:
 312         if (response)
 313                 cups->ippDelete(response);
 314 
 315         if (language)
 316                 cups->cupsLangFree(language);
 317 
 318         if (http)
 319                 cups->httpClose(http);
 320 }
 321 
 322 int
 323 smbd_cups_init(void)
 324 {
 325         (void) mutex_lock(&smbd_cups_mutex);
 326 
 327         if (smb_cups.cups_hdl != NULL) {
 328                 (void) mutex_unlock(&smbd_cups_mutex);
 329                 return (0);
 330         }
 331 
 332         if ((smb_cups.cups_hdl = dlopen("libcups.so.2", RTLD_NOW)) == NULL) {
 333                 (void) mutex_unlock(&smbd_cups_mutex);
 334                 smb_log(smbd.s_loghd, LOG_DEBUG,
 335                     "smbd_cups_init: cannot open libcups");
 336                 return (ENOENT);
 337         }
 338 
 339         smb_cups.cupsLangDefault =
 340             (cups_lang_t *(*)())dlsym(smb_cups.cups_hdl, "cupsLangDefault");
 341         smb_cups.cupsLangEncoding = (const char *(*)(cups_lang_t *))
 342             dlsym(smb_cups.cups_hdl, "cupsLangEncoding");
 343         smb_cups.cupsDoFileRequest =
 344             (ipp_t *(*)(http_t *, ipp_t *, const char *, const char *))
 345             dlsym(smb_cups.cups_hdl, "cupsDoFileRequest");
 346         smb_cups.cupsLastError = (ipp_status_t (*)())
 347             dlsym(smb_cups.cups_hdl, "cupsLastError");
 348         smb_cups.cupsLangFree = (void (*)(cups_lang_t *))
 349             dlsym(smb_cups.cups_hdl, "cupsLangFree");
 350         smb_cups.cupsGetDests = (int (*)(cups_dest_t **))
 351             dlsym(smb_cups.cups_hdl, "cupsGetDests");
 352         smb_cups.cupsFreeDests = (void (*)(int, cups_dest_t *))
 353             dlsym(smb_cups.cups_hdl, "cupsFreeDests");
 354 
 355         smb_cups.httpClose = (void (*)(http_t *))
 356             dlsym(smb_cups.cups_hdl, "httpClose");
 357         smb_cups.httpConnect = (http_t *(*)(const char *, int))
 358             dlsym(smb_cups.cups_hdl, "httpConnect");
 359 
 360         smb_cups.ippNew = (ipp_t *(*)())dlsym(smb_cups.cups_hdl, "ippNew");
 361         smb_cups.ippDelete = (void (*)())dlsym(smb_cups.cups_hdl, "ippDelete");
 362         smb_cups.ippErrorString = (char *(*)())
 363             dlsym(smb_cups.cups_hdl, "ippErrorString");
 364         smb_cups.ippAddString = (ipp_attribute_t *(*)())
 365             dlsym(smb_cups.cups_hdl, "ippAddString");
 366 
 367         if (smb_cups.cupsLangDefault == NULL ||
 368             smb_cups.cupsLangEncoding == NULL ||
 369             smb_cups.cupsDoFileRequest == NULL ||
 370             smb_cups.cupsLastError == NULL ||
 371             smb_cups.cupsLangFree == NULL ||
 372             smb_cups.cupsGetDests == NULL ||
 373             smb_cups.cupsFreeDests == NULL ||
 374             smb_cups.ippNew == NULL ||
 375             smb_cups.httpClose == NULL ||
 376             smb_cups.httpConnect == NULL ||
 377             smb_cups.ippDelete == NULL ||
 378             smb_cups.ippErrorString == NULL ||
 379             smb_cups.ippAddString == NULL) {
 380                 (void) dlclose(smb_cups.cups_hdl);
 381                 smb_cups.cups_hdl = NULL;
 382                 (void) mutex_unlock(&smbd_cups_mutex);
 383                 smb_log(smbd.s_loghd, LOG_DEBUG,
 384                     "smbd_cups_init: cannot load libcups");
 385                 return (ENOENT);
 386         }
 387 
 388         (void) mutex_unlock(&smbd_cups_mutex);
 389         return (0);
 390 }
 391 
 392 void
 393 smbd_cups_fini(void)
 394 {
 395         (void) mutex_lock(&smbd_cups_mutex);
 396 
 397         if (smb_cups.cups_hdl != NULL) {
 398                 (void) dlclose(smb_cups.cups_hdl);
 399                 smb_cups.cups_hdl = NULL;
 400         }
 401 
 402         (void) mutex_unlock(&smbd_cups_mutex);
 403 }
 404 
 405 static smb_cups_ops_t *
 406 smbd_cups_ops(void)
 407 {
 408         if (smb_cups.cups_hdl == NULL)
 409                 return (NULL);
 410 
 411         return (&smb_cups);
 412 }
 413 
 414 void
 415 smbd_load_printers(void)
 416 {
 417         pthread_t       tid;
 418         pthread_attr_t  attr;
 419         int             rc;
 420 
 421         if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
 422                 return;
 423 
 424         (void) pthread_attr_init(&attr);
 425         (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 426         rc = pthread_create(&tid, &attr, smbd_share_printers, &tid);
 427         (void) pthread_attr_destroy(&attr);
 428 
 429         if (rc != 0)
 430                 smb_log(smbd.s_loghd, LOG_NOTICE,
 431                     "unable to load printer shares: %s", strerror(errno));
 432 }
 433 
 434 /*
 435  * All print shares use the path from print$.
 436  */
 437 /*ARGSUSED*/
 438 static void *
 439 smbd_share_printers(void *arg)
 440 {
 441         cups_dest_t     *dests;
 442         cups_dest_t     *dest;
 443         smb_cups_ops_t  *cups;
 444         smb_share_t     si;
 445         uint32_t        nerr;
 446         int             num_dests;
 447         int             i;
 448 
 449         if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
 450                 return (NULL);
 451 
 452         if ((cups = smbd_cups_ops()) == NULL)
 453                 return (NULL);
 454 
 455         if (smb_shr_get(SMB_SHARE_PRINT, &si) != NERR_Success) {
 456                 smb_log(smbd.s_loghd, LOG_DEBUG,
 457                     "smbd_share_printers unable to load %s", SMB_SHARE_PRINT);
 458                 return (NULL);
 459         }
 460 
 461         num_dests = cups->cupsGetDests(&dests);
 462 
 463         for (i = num_dests, dest = dests; i > 0; i--, dest++) {
 464                 if (dest->instance != NULL)
 465                         continue;
 466 
 467                 (void) strlcpy(si.shr_name, dest->name, MAXPATHLEN);
 468                 smbd_print_share_comment(&si, dest);
 469                 si.shr_type = STYPE_PRINTQ;
 470 
 471                 nerr = smb_shr_add(&si);
 472                 if (nerr == NERR_Success || nerr == NERR_DuplicateShare)
 473                         smb_log(smbd.s_loghd, LOG_DEBUG,
 474                             "shared printer: %s", si.shr_name);
 475                 else
 476                         smb_log(smbd.s_loghd, LOG_DEBUG,
 477                             "smbd_share_printers: unable to add share %s: %u",
 478                             si.shr_name, nerr);
 479         }
 480 
 481         cups->cupsFreeDests(num_dests, dests);
 482         return (NULL);
 483 }
 484 
 485 static void
 486 smbd_print_share_comment(smb_share_t *si, cups_dest_t *dest)
 487 {
 488         cups_option_t   *options;
 489         char            *comment;
 490         char            *name;
 491         char            *value;
 492         int             i;
 493 
 494         comment = "Print Share";
 495 
 496         if ((options = dest->options) == NULL) {
 497                 (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
 498                 return;
 499         }
 500 
 501         for (i = 0; i < dest->num_options; ++i) {
 502                 name = options[i].name;
 503                 value = options[i].value;
 504 
 505                 if (name == NULL || value == NULL ||
 506                     *name == '\0' || *value == '\0')
 507                         continue;
 508 
 509                 if (strcasecmp(name, "printer-info") == 0) {
 510                         comment = value;
 511                         break;
 512                 }
 513         }
 514 
 515         (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
 516 }
 517 
 518 #else   /* HAVE_CUPS */
 519 
 520 /*
 521  * If not HAVE_CUPS, just provide a few "stubs".
 522  */
 523 
 524 int
 525 smbd_cups_init(void)
 526 {
 527         return (ENOENT);
 528 }
 529 
 530 void
 531 smbd_cups_fini(void)
 532 {
 533 }
 534 
 535 void
 536 smbd_load_printers(void)
 537 {
 538 }
 539 
 540 void
 541 smbd_spool_init(void)
 542 {
 543 }
 544 
 545 void
 546 smbd_spool_fini(void)
 547 {
 548 }
 549 
 550 void
 551 smbd_spool_start(void)
 552 {
 553 }
 554 
 555 void
 556 smbd_spool_stop(void)
 557 {
 558 }
 559 
 560 #endif  /* HAVE_CUPS */