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