Line data Source code
1 : /*
2 : * Unix SMB/CIFS implementation.
3 : *
4 : * CUPS printing backend helper to execute smbspool
5 : *
6 : * Copyright (C) 2010-2011 Andreas Schneider <asn@samba.org>
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 3 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License
19 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 : */
21 :
22 : #include "includes.h"
23 : #include "system/filesys.h"
24 : #include "system/kerberos.h"
25 : #include "system/passwd.h"
26 :
27 : #include <errno.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 :
31 : #include <cups/backend.h>
32 :
33 : #include "dynconfig/dynconfig.h"
34 :
35 : #undef calloc
36 :
37 : enum cups_smb_dbglvl_e {
38 : CUPS_SMB_LOG_DEBUG = 0,
39 : CUPS_SMB_LOG_ERROR,
40 : };
41 : static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
42 : PRINTF_ATTRIBUTE(2, 3);
43 :
44 : #define CUPS_SMB_DEBUG(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
45 : #define CUPS_SMB_ERROR(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
46 :
47 14 : static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
48 : {
49 14 : const char *prefix = "DEBUG";
50 : char buffer[1024];
51 : va_list va;
52 :
53 14 : va_start(va, format);
54 14 : vsnprintf(buffer, sizeof(buffer), format, va);
55 14 : va_end(va);
56 :
57 14 : switch (lvl) {
58 14 : case CUPS_SMB_LOG_DEBUG:
59 14 : prefix = "DEBUG";
60 14 : break;
61 0 : case CUPS_SMB_LOG_ERROR:
62 0 : prefix = "ERROR";
63 0 : break;
64 : }
65 :
66 14 : fprintf(stderr,
67 : "%s: SMBSPOOL_KRB5 - %s\n",
68 : prefix,
69 : buffer);
70 14 : }
71 :
72 0 : static bool kerberos_get_default_ccache(char *ccache_buf, size_t len)
73 : {
74 : krb5_context ctx;
75 0 : const char *ccache_name = NULL;
76 0 : char *full_ccache_name = NULL;
77 0 : krb5_ccache ccache = NULL;
78 : krb5_error_code code;
79 :
80 0 : code = krb5_init_context(&ctx);
81 0 : if (code != 0) {
82 0 : return false;
83 : }
84 :
85 0 : ccache_name = krb5_cc_default_name(ctx);
86 0 : if (ccache_name == NULL) {
87 0 : krb5_free_context(ctx);
88 0 : return false;
89 : }
90 :
91 0 : code = krb5_cc_resolve(ctx, ccache_name, &ccache);
92 0 : if (code != 0) {
93 0 : krb5_free_context(ctx);
94 0 : return false;
95 : }
96 :
97 0 : code = krb5_cc_get_full_name(ctx, ccache, &full_ccache_name);
98 0 : krb5_cc_close(ctx, ccache);
99 0 : if (code != 0) {
100 0 : krb5_free_context(ctx);
101 0 : return false;
102 : }
103 :
104 0 : snprintf(ccache_buf, len, "%s", full_ccache_name);
105 :
106 : #ifdef SAMBA4_USES_HEIMDAL
107 0 : free(full_ccache_name);
108 : #else
109 0 : krb5_free_string(ctx, full_ccache_name);
110 : #endif
111 0 : krb5_free_context(ctx);
112 :
113 0 : return true;
114 : }
115 :
116 : /*
117 : * This is a helper binary to execute smbspool.
118 : *
119 : * It needs to be installed or symlinked as:
120 : * /usr/lib/cups/backend/smb
121 : *
122 : * The permissions of the binary need to be set to 0700 so that it is executed
123 : * as root. The binary switches to the user which is passed via the environment
124 : * variable AUTH_UID, so we can access the kerberos ticket.
125 : */
126 8 : int main(int argc, char *argv[])
127 : {
128 8 : char smbspool_cmd[PATH_MAX] = {0};
129 : struct passwd *pwd;
130 8 : struct group *g = NULL;
131 8 : char gen_cc[PATH_MAX] = {0};
132 8 : char *env = NULL;
133 8 : char auth_info_required[256] = {0};
134 8 : char device_uri[4096] = {0};
135 8 : uid_t uid = (uid_t)-1;
136 8 : gid_t gid = (gid_t)-1;
137 8 : gid_t groups[1] = { (gid_t)-1 };
138 : unsigned long tmp;
139 : bool ok;
140 : int cmp;
141 : int rc;
142 :
143 8 : env = getenv("DEVICE_URI");
144 8 : if (env != NULL && strlen(env) > 2) {
145 0 : snprintf(device_uri, sizeof(device_uri), "%s", env);
146 : }
147 :
148 : /* We must handle the following values of AUTH_INFO_REQUIRED:
149 : * none: Anonymous/guest printing
150 : * username,password: A username (of the form "username" or "DOMAIN\username")
151 : * and password are required
152 : * negotiate: Kerberos authentication
153 : * NULL (not set): will never happen when called from cupsd
154 : * https://www.cups.org/doc/spec-ipp.html#auth-info-required
155 : * https://github.com/apple/cups/issues/5674
156 : */
157 8 : env = getenv("AUTH_INFO_REQUIRED");
158 :
159 : /* If not set, then just call smbspool. */
160 8 : if (env == NULL || env[0] == 0) {
161 2 : CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED is not set - "
162 : "executing smbspool");
163 : /* Pass this printing task to smbspool without Kerberos auth */
164 2 : goto smbspool;
165 : } else {
166 6 : CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED=%s", env);
167 :
168 : /* First test the value of AUTH_INFO_REQUIRED
169 : * against known possible values
170 : */
171 6 : cmp = strcmp(env, "none");
172 6 : if (cmp == 0) {
173 2 : CUPS_SMB_DEBUG("Authenticate using none (anonymous) - "
174 : "executing smbspool");
175 2 : goto smbspool;
176 : }
177 :
178 4 : cmp = strcmp(env, "username,password");
179 4 : if (cmp == 0) {
180 2 : CUPS_SMB_DEBUG("Authenticate using username/password - "
181 : "executing smbspool");
182 2 : goto smbspool;
183 : }
184 :
185 : /* Now, if 'goto smbspool' still has not happened,
186 : * there are only two variants left:
187 : * 1) AUTH_INFO_REQUIRED is "negotiate" and then
188 : * we have to continue working
189 : * 2) or it is something not known to us, then Kerberos
190 : * authentication is not required, so just also pass
191 : * this task to smbspool
192 : */
193 2 : cmp = strcmp(env, "negotiate");
194 2 : if (cmp != 0) {
195 2 : CUPS_SMB_DEBUG("Value of AUTH_INFO_REQUIRED is not known "
196 : "to smbspool_krb5_wrapper, executing smbspool");
197 2 : goto smbspool;
198 : }
199 :
200 0 : snprintf(auth_info_required,
201 : sizeof(auth_info_required),
202 : "%s",
203 : env);
204 : }
205 :
206 0 : uid = getuid();
207 :
208 0 : CUPS_SMB_DEBUG("Started with uid=%d\n", uid);
209 0 : if (uid != 0) {
210 0 : goto smbspool;
211 : }
212 :
213 : /*
214 : * AUTH_UID gets only set if we have an incoming connection over the
215 : * CUPS unix domain socket.
216 : */
217 0 : env = getenv("AUTH_UID");
218 0 : if (env == NULL) {
219 0 : CUPS_SMB_ERROR("AUTH_UID is not set");
220 0 : fprintf(stderr, "ATTR: auth-info-required=negotiate\n");
221 0 : return CUPS_BACKEND_AUTH_REQUIRED;
222 : }
223 :
224 0 : if (strlen(env) > 10) {
225 0 : CUPS_SMB_ERROR("Invalid AUTH_UID");
226 0 : return CUPS_BACKEND_FAILED;
227 : }
228 :
229 0 : errno = 0;
230 0 : tmp = strtoul(env, NULL, 10);
231 0 : if (errno != 0 || tmp >= UINT32_MAX) {
232 0 : CUPS_SMB_ERROR("Failed to convert AUTH_UID=%s", env);
233 0 : return CUPS_BACKEND_FAILED;
234 : }
235 0 : uid = (uid_t)tmp;
236 :
237 : /* If we are printing as the root user, we're done here. */
238 0 : if (uid == 0) {
239 0 : goto smbspool;
240 : }
241 :
242 0 : pwd = getpwuid(uid);
243 0 : if (pwd == NULL) {
244 0 : CUPS_SMB_ERROR("Failed to find system user: %u - %s",
245 : uid, strerror(errno));
246 0 : return CUPS_BACKEND_FAILED;
247 : }
248 0 : gid = pwd->pw_gid;
249 :
250 0 : rc = setgroups(0, NULL);
251 0 : if (rc != 0) {
252 0 : CUPS_SMB_ERROR("Failed to clear groups - %s",
253 : strerror(errno));
254 0 : return CUPS_BACKEND_FAILED;
255 : }
256 :
257 : /*
258 : * We need the primary group of the 'lp' user. This is needed to access
259 : * temporary files in /var/spool/cups/.
260 : */
261 0 : g = getgrnam("lp");
262 0 : if (g == NULL) {
263 0 : CUPS_SMB_ERROR("Failed to find user 'lp' - %s",
264 : strerror(errno));
265 0 : return CUPS_BACKEND_FAILED;
266 : }
267 :
268 0 : CUPS_SMB_DEBUG("Adding group 'lp' (%u)", g->gr_gid);
269 0 : groups[0] = g->gr_gid;
270 0 : rc = setgroups(ARRAY_SIZE(groups), groups);
271 0 : if (rc != 0) {
272 0 : CUPS_SMB_ERROR("Failed to set groups for 'lp' - %s",
273 : strerror(errno));
274 0 : return CUPS_BACKEND_FAILED;
275 : }
276 :
277 0 : CUPS_SMB_DEBUG("Switching to gid=%d", gid);
278 0 : rc = setgid(gid);
279 0 : if (rc != 0) {
280 0 : CUPS_SMB_ERROR("Failed to switch to gid=%u - %s",
281 : gid,
282 : strerror(errno));
283 0 : return CUPS_BACKEND_FAILED;
284 : }
285 :
286 0 : CUPS_SMB_DEBUG("Switching to uid=%u", uid);
287 0 : rc = setuid(uid);
288 0 : if (rc != 0) {
289 0 : CUPS_SMB_ERROR("Failed to switch to uid=%u - %s",
290 : uid,
291 : strerror(errno));
292 0 : return CUPS_BACKEND_FAILED;
293 : }
294 :
295 0 : env = getenv("KRB5CCNAME");
296 0 : if (env != NULL && env[0] != 0) {
297 0 : snprintf(gen_cc, sizeof(gen_cc), "%s", env);
298 0 : CUPS_SMB_DEBUG("User already set KRB5CCNAME [%s] as ccache",
299 : gen_cc);
300 :
301 0 : goto create_env;
302 : }
303 :
304 0 : ok = kerberos_get_default_ccache(gen_cc, sizeof(gen_cc));
305 0 : if (ok) {
306 0 : CUPS_SMB_DEBUG("Use default KRB5CCNAME [%s]",
307 : gen_cc);
308 0 : goto create_env;
309 : }
310 :
311 : /* Fallback to a FILE ccache */
312 0 : snprintf(gen_cc, sizeof(gen_cc), "FILE:/tmp/krb5cc_%u", uid);
313 :
314 0 : create_env:
315 : /*
316 : * Make sure we do not have LD_PRELOAD or other security relevant
317 : * environment variables set.
318 : */
319 : #ifdef HAVE_CLEARENV
320 0 : clearenv();
321 : #else
322 : environ = calloc(3, sizeof(*environ));
323 : #endif
324 :
325 0 : CUPS_SMB_DEBUG("Setting KRB5CCNAME to '%s'", gen_cc);
326 0 : setenv("KRB5CCNAME", gen_cc, 1);
327 0 : if (device_uri[0] != '\0') {
328 0 : setenv("DEVICE_URI", device_uri, 1);
329 : }
330 0 : if (auth_info_required[0] != '\0') {
331 0 : setenv("AUTH_INFO_REQUIRED", auth_info_required, 1);
332 : }
333 :
334 0 : smbspool:
335 8 : snprintf(smbspool_cmd,
336 : sizeof(smbspool_cmd),
337 : "%s/smbspool",
338 : get_dyn_BINDIR());
339 :
340 8 : return execv(smbspool_cmd, argv);
341 : }
|