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