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