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