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