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