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