2 * Unix SMB/CIFS implementation.
4 * CUPS printing backend helper to execute smbspool
6 * Copyright (C) 2010-2011 Andreas Schneider <asn@samba.org>
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.
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.
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/>.
23 #include "system/filesys.h"
24 #include "system/kerberos.h"
25 #include "system/passwd.h"
31 #include <cups/backend.h>
33 #include "dynconfig/dynconfig.h"
37 enum cups_smb_dbglvl_e
{
38 CUPS_SMB_LOG_DEBUG
= 0,
41 static void cups_smb_debug(enum cups_smb_dbglvl_e lvl
, const char *format
, ...)
42 PRINTF_ATTRIBUTE(2, 3);
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__)
47 static void cups_smb_debug(enum cups_smb_dbglvl_e lvl
, const char *format
, ...)
49 const char *prefix
= "DEBUG";
54 vsnprintf(buffer
, sizeof(buffer
), format
, va
);
58 case CUPS_SMB_LOG_DEBUG
:
61 case CUPS_SMB_LOG_ERROR
:
67 "%s: SMBSPOOL_KRB5 - %s\n",
72 static bool kerberos_get_default_ccache(char *ccache_buf
, size_t len
)
75 const char *ccache_name
= NULL
;
76 char *full_ccache_name
= NULL
;
77 krb5_ccache ccache
= NULL
;
80 code
= krb5_init_context(&ctx
);
85 ccache_name
= krb5_cc_default_name(ctx
);
86 if (ccache_name
== NULL
) {
87 krb5_free_context(ctx
);
91 code
= krb5_cc_resolve(ctx
, ccache_name
, &ccache
);
93 krb5_free_context(ctx
);
97 code
= krb5_cc_get_full_name(ctx
, ccache
, &full_ccache_name
);
98 krb5_cc_close(ctx
, ccache
);
100 krb5_free_context(ctx
);
104 snprintf(ccache_buf
, len
, "%s", full_ccache_name
);
106 #ifdef SAMBA4_USES_HEIMDAL
107 free(full_ccache_name
);
109 krb5_free_string(ctx
, full_ccache_name
);
111 krb5_free_context(ctx
);
117 * This is a helper binary to execute smbspool.
119 * It needs to be installed or symlinked as:
120 * /usr/lib/cups/backend/smb
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.
126 int main(int argc
, char *argv
[])
128 char smbspool_cmd
[PATH_MAX
] = {0};
130 struct group
*g
= NULL
;
131 char gen_cc
[PATH_MAX
] = {0};
133 char auth_info_required
[256] = {0};
134 char device_uri
[4096] = {0};
135 uid_t uid
= (uid_t
)-1;
136 gid_t gid
= (gid_t
)-1;
137 gid_t groups
[1] = { (gid_t
)-1 };
143 env
= getenv("DEVICE_URI");
144 if (env
!= NULL
&& strlen(env
) > 2) {
145 snprintf(device_uri
, sizeof(device_uri
), "%s", env
);
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
157 env
= getenv("AUTH_INFO_REQUIRED");
159 /* If not set, then just call smbspool. */
160 if (env
== NULL
|| env
[0] == 0) {
161 CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED is not set - "
162 "executing smbspool");
163 /* Pass this printing task to smbspool without Kerberos auth */
166 CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED=%s", env
);
168 /* First test the value of AUTH_INFO_REQUIRED
169 * against known possible values
171 cmp
= strcmp(env
, "none");
173 CUPS_SMB_DEBUG("Authenticate using none (anonymous) - "
174 "executing smbspool");
178 cmp
= strcmp(env
, "username,password");
180 CUPS_SMB_DEBUG("Authenticate using username/password - "
181 "executing smbspool");
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
193 cmp
= strcmp(env
, "negotiate");
195 CUPS_SMB_DEBUG("Value of AUTH_INFO_REQUIRED is not known "
196 "to smbspool_krb5_wrapper, executing smbspool");
200 snprintf(auth_info_required
,
201 sizeof(auth_info_required
),
208 CUPS_SMB_DEBUG("Started with uid=%d\n", uid
);
214 * AUTH_UID gets only set if we have an incoming connection over the
215 * CUPS unix domain socket.
217 env
= getenv("AUTH_UID");
219 CUPS_SMB_ERROR("AUTH_UID is not set");
220 fprintf(stderr
, "ATTR: auth-info-required=negotiate\n");
221 return CUPS_BACKEND_AUTH_REQUIRED
;
224 if (strlen(env
) > 10) {
225 CUPS_SMB_ERROR("Invalid AUTH_UID");
226 return CUPS_BACKEND_FAILED
;
230 tmp
= strtoul(env
, NULL
, 10);
231 if (errno
!= 0 || tmp
>= UINT32_MAX
) {
232 CUPS_SMB_ERROR("Failed to convert AUTH_UID=%s", env
);
233 return CUPS_BACKEND_FAILED
;
237 /* If we are printing as the root user, we're done here. */
244 CUPS_SMB_ERROR("Failed to find system user: %u - %s",
245 uid
, strerror(errno
));
246 return CUPS_BACKEND_FAILED
;
250 rc
= setgroups(0, NULL
);
252 CUPS_SMB_ERROR("Failed to clear groups - %s",
254 return CUPS_BACKEND_FAILED
;
258 * We need the primary group of the 'lp' user. This is needed to access
259 * temporary files in /var/spool/cups/.
263 CUPS_SMB_ERROR("Failed to find user 'lp' - %s",
265 return CUPS_BACKEND_FAILED
;
268 CUPS_SMB_DEBUG("Adding group 'lp' (%u)", g
->gr_gid
);
269 groups
[0] = g
->gr_gid
;
270 rc
= setgroups(sizeof(groups
), groups
);
272 CUPS_SMB_ERROR("Failed to set groups for 'lp' - %s",
274 return CUPS_BACKEND_FAILED
;
277 CUPS_SMB_DEBUG("Switching to gid=%d", gid
);
280 CUPS_SMB_ERROR("Failed to switch to gid=%u - %s",
283 return CUPS_BACKEND_FAILED
;
286 CUPS_SMB_DEBUG("Switching to uid=%u", uid
);
289 CUPS_SMB_ERROR("Failed to switch to uid=%u - %s",
292 return CUPS_BACKEND_FAILED
;
295 env
= getenv("KRB5CCNAME");
296 if (env
!= NULL
&& env
[0] != 0) {
297 snprintf(gen_cc
, sizeof(gen_cc
), "%s", env
);
298 CUPS_SMB_DEBUG("User already set KRB5CCNAME [%s] as ccache",
304 ok
= kerberos_get_default_ccache(gen_cc
, sizeof(gen_cc
));
306 CUPS_SMB_DEBUG("Use default KRB5CCNAME [%s]",
311 /* Fallback to a FILE ccache */
312 snprintf(gen_cc
, sizeof(gen_cc
), "FILE:/tmp/krb5cc_%u", uid
);
316 * Make sure we do not have LD_PRELOAD or other security relevant
317 * environment variables set.
322 environ
= calloc(3, sizeof(*environ
));
325 CUPS_SMB_DEBUG("Setting KRB5CCNAME to '%s'", gen_cc
);
326 setenv("KRB5CCNAME", gen_cc
, 1);
327 if (device_uri
[0] != '\0') {
328 setenv("DEVICE_URI", device_uri
, 1);
330 if (auth_info_required
[0] != '\0') {
331 setenv("AUTH_INFO_REQUIRED", auth_info_required
, 1);
335 snprintf(smbspool_cmd
,
336 sizeof(smbspool_cmd
),
340 return execv(smbspool_cmd
, argv
);