smbd: update declaration in smb2_sesssetup, smb2_tcon
[samba.git] / source3 / client / smbspool_krb5_wrapper.c
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 static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
45 {
46         const char *prefix = "DEBUG";
47         char buffer[1024];
48         va_list va;
49
50         va_start(va, format);
51         vsnprintf(buffer, sizeof(buffer), format, va);
52         va_end(va);
53
54         switch (lvl) {
55         case CUPS_SMB_LOG_DEBUG:
56                 prefix = "DEBUG";
57                 break;
58         case CUPS_SMB_LOG_ERROR:
59                 prefix = "ERROR";
60                 break;
61         }
62
63         fprintf(stderr,
64                 "%s: SMBSPOOL_KRB5 - %s\n",
65                 prefix,
66                 buffer);
67 }
68
69 static bool kerberos_get_default_ccache(char *ccache_buf, size_t len)
70 {
71         krb5_context ctx;
72         const char *ccache_name = NULL;
73         char *full_ccache_name = NULL;
74         krb5_ccache ccache = NULL;
75         krb5_error_code code;
76
77         code = krb5_init_context(&ctx);
78         if (code != 0) {
79                 return false;
80         }
81
82         ccache_name = smb_force_krb5_cc_default_name(ctx);
83         if (ccache_name == NULL) {
84                 krb5_free_context(ctx);
85                 return false;
86         }
87
88         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
89         if (code != 0) {
90                 krb5_free_context(ctx);
91                 return false;
92         }
93
94         code = krb5_cc_get_full_name(ctx, ccache, &full_ccache_name);
95         krb5_cc_close(ctx, ccache);
96         if (code != 0) {
97                 krb5_free_context(ctx);
98                 return false;
99         }
100
101         snprintf(ccache_buf, len, "%s", full_ccache_name);
102
103 #ifdef SAMBA4_USES_HEIMDAL
104         free(full_ccache_name);
105 #else
106         krb5_free_string(ctx, full_ccache_name);
107 #endif
108         krb5_free_context(ctx);
109
110         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 int main(int argc, char *argv[])
124 {
125         char smbspool_cmd[PATH_MAX] = {0};
126         struct passwd *pwd;
127         struct group *g = NULL;
128         char gen_cc[PATH_MAX] = {0};
129         char *env = NULL;
130         char auth_info_required[256] = {0};
131         char device_uri[4096] = {0};
132         uid_t uid = (uid_t)-1;
133         gid_t gid = (gid_t)-1;
134         gid_t groups[1] = { (gid_t)-1 };
135         unsigned long tmp;
136         bool ok;
137         int cmp;
138         int rc;
139
140         env = getenv("DEVICE_URI");
141         if (env != NULL && strlen(env) > 2) {
142                 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         env = getenv("AUTH_INFO_REQUIRED");
155
156         /* If not set, then just call smbspool. */
157         if (env == NULL || env[0] == 0) {
158                 CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED is not set - "
159                                "executing smbspool");
160                 /* Pass this printing task to smbspool without Kerberos auth */
161                 goto smbspool;
162         } else {
163                 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                 cmp = strcmp(env, "none");
169                 if (cmp == 0) {
170                         CUPS_SMB_DEBUG("Authenticate using none (anonymous) - "
171                                        "executing smbspool");
172                         goto smbspool;
173                 }
174
175                 cmp = strcmp(env, "username,password");
176                 if (cmp == 0) {
177                         CUPS_SMB_DEBUG("Authenticate using username/password - "
178                                        "executing smbspool");
179                         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                 cmp = strcmp(env, "negotiate");
191                 if (cmp != 0) {
192                         CUPS_SMB_DEBUG("Value of AUTH_INFO_REQUIRED is not known "
193                                        "to smbspool_krb5_wrapper, executing smbspool");
194                         goto smbspool;
195                 }
196
197                 snprintf(auth_info_required,
198                          sizeof(auth_info_required),
199                          "%s",
200                          env);
201         }
202
203         uid = getuid();
204
205         CUPS_SMB_DEBUG("Started with uid=%d\n", uid);
206         if (uid != 0) {
207                 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         env = getenv("AUTH_UID");
215         if (env == NULL) {
216                 CUPS_SMB_ERROR("AUTH_UID is not set");
217                 fprintf(stderr, "ATTR: auth-info-required=negotiate\n");
218                 return CUPS_BACKEND_AUTH_REQUIRED;
219         }
220
221         if (strlen(env) > 10) {
222                 CUPS_SMB_ERROR("Invalid AUTH_UID");
223                 return CUPS_BACKEND_FAILED;
224         }
225
226         errno = 0;
227         tmp = strtoul(env, NULL, 10);
228         if (errno != 0 || tmp >= UINT32_MAX) {
229                 CUPS_SMB_ERROR("Failed to convert AUTH_UID=%s", env);
230                 return CUPS_BACKEND_FAILED;
231         }
232         uid = (uid_t)tmp;
233
234         /* If we are printing as the root user, we're done here. */
235         if (uid == 0) {
236                 goto smbspool;
237         }
238
239         pwd = getpwuid(uid);
240         if (pwd == NULL) {
241                 CUPS_SMB_ERROR("Failed to find system user: %u - %s",
242                                uid, strerror(errno));
243                 return CUPS_BACKEND_FAILED;
244         }
245         gid = pwd->pw_gid;
246
247         rc = setgroups(0, NULL);
248         if (rc != 0) {
249                 CUPS_SMB_ERROR("Failed to clear groups - %s",
250                                strerror(errno));
251                 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         g = getgrnam("lp");
259         if (g == NULL) {
260                 CUPS_SMB_ERROR("Failed to find user 'lp' - %s",
261                                strerror(errno));
262                 return CUPS_BACKEND_FAILED;
263         }
264
265         CUPS_SMB_DEBUG("Adding group 'lp' (%u)", g->gr_gid);
266         groups[0] = g->gr_gid;
267         rc = setgroups(ARRAY_SIZE(groups), groups);
268         if (rc != 0) {
269                 CUPS_SMB_ERROR("Failed to set groups for 'lp' - %s",
270                                strerror(errno));
271                 return CUPS_BACKEND_FAILED;
272         }
273
274         CUPS_SMB_DEBUG("Switching to gid=%d", gid);
275         rc = setgid(gid);
276         if (rc != 0) {
277                 CUPS_SMB_ERROR("Failed to switch to gid=%u - %s",
278                                gid,
279                                strerror(errno));
280                 return CUPS_BACKEND_FAILED;
281         }
282
283         CUPS_SMB_DEBUG("Switching to uid=%u", uid);
284         rc = setuid(uid);
285         if (rc != 0) {
286                 CUPS_SMB_ERROR("Failed to switch to uid=%u - %s",
287                                uid,
288                                strerror(errno));
289                 return CUPS_BACKEND_FAILED;
290         }
291
292         env = getenv("KRB5CCNAME");
293         if (env != NULL && env[0] != 0) {
294                 snprintf(gen_cc, sizeof(gen_cc), "%s", env);
295                 CUPS_SMB_DEBUG("User already set KRB5CCNAME [%s] as ccache",
296                                gen_cc);
297
298                 goto create_env;
299         }
300
301         ok = kerberos_get_default_ccache(gen_cc, sizeof(gen_cc));
302         if (ok) {
303                 CUPS_SMB_DEBUG("Use default KRB5CCNAME [%s]",
304                                gen_cc);
305                 goto create_env;
306         }
307
308         /* Fallback to a FILE ccache */
309         snprintf(gen_cc, sizeof(gen_cc), "FILE:/tmp/krb5cc_%u", uid);
310
311 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         clearenv();
318 #else
319         environ = calloc(3, sizeof(*environ));
320 #endif
321
322         CUPS_SMB_DEBUG("Setting KRB5CCNAME to '%s'", gen_cc);
323         setenv("KRB5CCNAME", gen_cc, 1);
324         if (device_uri[0] != '\0') {
325                 setenv("DEVICE_URI", device_uri, 1);
326         }
327         if (auth_info_required[0] != '\0') {
328                 setenv("AUTH_INFO_REQUIRED", auth_info_required, 1);
329         }
330
331 smbspool:
332         snprintf(smbspool_cmd,
333                  sizeof(smbspool_cmd),
334                  "%s/smbspool",
335                  get_dyn_BINDIR());
336
337         return execv(smbspool_cmd, argv);
338 }