xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_spool.c (revision b7301bf5522d8b9141fe432333ded586218327f2)
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