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 */