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