cifs.upcall: scan /run/user/${UID} for ccaches, too
[jlayton/cifs-utils.git] / cifs.upcall.c
1 /*
2 * CIFS user-space helper.
3 * Copyright (C) Igor Mammedov (niallain@gmail.com) 2007
4 * Copyright (C) Jeff Layton (jlayton@samba.org) 2010
5 *
6 * Used by /sbin/request-key for handling
7 * cifs upcall for kerberos authorization of access to share and
8 * cifs upcall for DFS srver name resolving (IPv4/IPv6 aware).
9 * You should have keyutils installed and add something like the
10 * following lines to /etc/request-key.conf file:
11
12     create cifs.spnego * * /usr/local/sbin/cifs.upcall %k
13     create dns_resolver * * /usr/local/sbin/cifs.upcall %k
14
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif /* HAVE_CONFIG_H */
31
32 #include <string.h>
33 #include <getopt.h>
34 #ifdef HAVE_KRB5_KRB5_H
35 #include <krb5/krb5.h>
36 #elif defined(HAVE_KRB5_H)
37 #include <krb5.h>
38 #endif
39 #include <syslog.h>
40 #include <dirent.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 #include <keyutils.h>
45 #include <time.h>
46 #include <netdb.h>
47 #include <arpa/inet.h>
48 #include <ctype.h>
49
50 #include "replace.h"
51 #include "data_blob.h"
52 #include "spnego.h"
53 #include "cifs_spnego.h"
54
55 #define CIFS_DEFAULT_KRB5_DIR           "/tmp"
56 #define CIFS_DEFAULT_KRB5_USER_DIR      "/run/user/%U"
57 #define CIFS_DEFAULT_KRB5_PREFIX        "krb5cc"
58 #define CIFS_DEFAULT_KRB5_KEYTAB        "/etc/krb5.keytab"
59
60 #define MAX_CCNAME_LEN                  PATH_MAX + 5
61
62 static const char *prog = "cifs.upcall";
63 typedef enum _sectype {
64         NONE = 0,
65         KRB5,
66         MS_KRB5
67 } sectype_t;
68
69 /*
70  * smb_krb5_principal_get_realm
71  *
72  * @brief Get realm of a principal
73  *
74  * @param[in] context           The krb5_context
75  * @param[in] principal         The principal
76  * @return pointer to the realm
77  *
78  */
79
80 static char *cifs_krb5_principal_get_realm(krb5_context context __attribute__ ((unused)),
81                                            krb5_principal principal)
82 {
83 #ifdef HAVE_KRB5_PRINCIPAL_GET_REALM    /* Heimdal */
84         return krb5_principal_get_realm(context, principal);
85 #elif defined(krb5_princ_realm) /* MIT */
86         krb5_data *realm;
87         realm = krb5_princ_realm(context, principal);
88         return (char *)realm->data;
89 #else
90         return NULL;
91 #endif
92 }
93
94 #if !defined(HAVE_KRB5_FREE_UNPARSED_NAME)
95 static void krb5_free_unparsed_name(krb5_context context, char *val)
96 {
97         SAFE_FREE(val);
98 }
99 #endif
100
101 #if !defined(HAVE_KRB5_AUTH_CON_GETSENDSUBKEY)  /* Heimdal */
102 static krb5_error_code
103 krb5_auth_con_getsendsubkey(krb5_context context,
104                             krb5_auth_context auth_context,
105                             krb5_keyblock **keyblock)
106 {
107         return krb5_auth_con_getlocalsubkey(context, auth_context, keyblock);
108 }
109 #endif
110
111 /* does the ccache have a valid TGT? */
112 static time_t get_tgt_time(const char *ccname)
113 {
114         krb5_context context;
115         krb5_ccache ccache;
116         krb5_cc_cursor cur;
117         krb5_creds creds;
118         krb5_principal principal;
119         time_t credtime = 0;
120         char *realm = NULL;
121
122         if (krb5_init_context(&context)) {
123                 syslog(LOG_DEBUG, "%s: unable to init krb5 context", __func__);
124                 return 0;
125         }
126
127         if (krb5_cc_resolve(context, ccname, &ccache)) {
128                 syslog(LOG_DEBUG, "%s: unable to resolve krb5 cache", __func__);
129                 goto err_cache;
130         }
131
132         if (krb5_cc_set_flags(context, ccache, 0)) {
133                 syslog(LOG_DEBUG, "%s: unable to set flags", __func__);
134                 goto err_cache;
135         }
136
137         if (krb5_cc_get_principal(context, ccache, &principal)) {
138                 syslog(LOG_DEBUG, "%s: unable to get principal", __func__);
139                 goto err_princ;
140         }
141
142         if (krb5_cc_start_seq_get(context, ccache, &cur)) {
143                 syslog(LOG_DEBUG, "%s: unable to seq start", __func__);
144                 goto err_ccstart;
145         }
146
147         if ((realm = cifs_krb5_principal_get_realm(context, principal)) == NULL) {
148                 syslog(LOG_DEBUG, "%s: unable to get realm", __func__);
149                 goto err_ccstart;
150         }
151
152         while (!credtime && !krb5_cc_next_cred(context, ccache, &cur, &creds)) {
153                 char *name;
154                 if (krb5_unparse_name(context, creds.server, &name)) {
155                         syslog(LOG_DEBUG, "%s: unable to unparse name",
156                                __func__);
157                         goto err_endseq;
158                 }
159                 if (krb5_realm_compare(context, creds.server, principal) &&
160                     !strncasecmp(name, KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE) &&
161                     !strncasecmp(name + KRB5_TGS_NAME_SIZE + 1, realm,
162                                  strlen(realm))
163                     && creds.times.endtime > time(NULL))
164                         credtime = creds.times.endtime;
165                 krb5_free_cred_contents(context, &creds);
166                 krb5_free_unparsed_name(context, name);
167         }
168 err_endseq:
169         krb5_cc_end_seq_get(context, ccache, &cur);
170 err_ccstart:
171         krb5_free_principal(context, principal);
172 err_princ:
173 #if defined(KRB5_TC_OPENCLOSE)
174         krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
175 #endif
176         krb5_cc_close(context, ccache);
177 err_cache:
178         krb5_free_context(context);
179         return credtime;
180 }
181
182 static int krb5cc_filter(const struct dirent *dirent)
183 {
184         if (strstr(dirent->d_name, CIFS_DEFAULT_KRB5_PREFIX))
185                 return 1;
186         else
187                 return 0;
188 }
189
190 static char *
191 init_cc_from_keytab(const char *keytab_name, const char *user)
192 {
193         krb5_context context = NULL;
194         krb5_error_code ret;
195         krb5_creds my_creds;
196         krb5_keytab keytab = NULL;
197         krb5_principal me = NULL;
198         krb5_ccache cc = NULL;
199         char *ccname = NULL;
200
201         memset((char *) &my_creds, 0, sizeof(my_creds));
202
203         ret = krb5_init_context(&context);
204         if (ret) {
205                 syslog(LOG_DEBUG, "krb5_init_context: %d", (int)ret);
206                 goto icfk_cleanup;
207         }
208
209         ret = krb5_kt_resolve(context, keytab_name, &keytab);
210         if (ret) {
211                 syslog(LOG_DEBUG, "krb5_kt_resolve: %d", (int)ret);
212                 goto icfk_cleanup;
213         }
214
215         ret = krb5_parse_name(context, user, &me);
216         if (ret) {
217                 syslog(LOG_DEBUG, "krb5_parse_name: %d", (int)ret);
218                 goto icfk_cleanup;
219         }
220
221         ret = krb5_get_init_creds_keytab(context, &my_creds, me,
222                         keytab, 0, NULL, NULL);
223         if (ret) {
224                 syslog(LOG_DEBUG, "krb5_get_init_creds_keytab: %d", (int)ret);
225                 goto icfk_cleanup;
226         }
227
228         ret = krb5_cc_default(context, &cc);
229         if (ret) {
230                 syslog(LOG_DEBUG, "krb5_cc_default: %d", (int)ret);
231                 goto icfk_cleanup;
232         }
233
234         ret = krb5_cc_initialize(context, cc, me);
235         if (ret) {
236                 syslog(LOG_DEBUG, "krb5_cc_initialize: %d", (int)ret);
237                 goto icfk_cleanup;
238         }
239
240         ret = krb5_cc_store_cred(context, cc, &my_creds);
241         if (ret)
242                 syslog(LOG_DEBUG, "krb5_cc_store_cred: %d", (int)ret);
243
244         ccname = strdup(krb5_cc_default_name(context));
245         if (ccname == NULL)
246                 syslog(LOG_ERR, "Unable to allocate memory");
247 icfk_cleanup:
248         my_creds.client = 0;
249         krb5_free_cred_contents(context, &my_creds);
250
251         if (me)
252                 krb5_free_principal(context, me);
253         if (cc)
254                 krb5_cc_close(context, cc);
255         if (keytab)
256                 krb5_kt_close(context, keytab);
257         if (context)
258                 krb5_free_context(context);
259         return ccname;
260 }
261
262 /* resolve a pattern to an actual directory path */
263 static char *resolve_krb5_dir(const char *pattern, uid_t uid)
264 {
265         char name[MAX_CCNAME_LEN];
266         int i;
267         size_t j;
268         for (i = 0, j = 0; (pattern[i] != '\0') && (j < sizeof(name)); i++) {
269                 switch (pattern[i]) {
270                 case '%':
271                         switch (pattern[i + 1]) {
272                         case '%':
273                                 name[j++] = pattern[i];
274                                 i++;
275                                 break;
276                         case 'U':
277                                 j += snprintf(name + j, sizeof(name) - j,
278                                               "%lu", (unsigned long) uid);
279                                 i++;
280                                 break;
281                         }
282                         break;
283                 default:
284                         name[j++] = pattern[i];
285                         break;
286                 }
287         }
288         if ((j > 0) && (j < sizeof(name)))
289                 return strndup(name, MAX_CCNAME_LEN);
290         else
291                 return NULL;
292 }
293
294 /* search for a credcache that looks like a likely candidate */
295 static char *find_krb5_cc(const char *dirname, uid_t uid,
296                           char **best_cache, time_t *best_time)
297 {
298         struct dirent **namelist;
299         struct stat sbuf;
300         char ccname[MAX_CCNAME_LEN], *credpath;
301         int i, n;
302         time_t cred_time;
303
304         n = scandir(dirname, &namelist, krb5cc_filter, NULL);
305         if (n < 0) {
306                 syslog(LOG_DEBUG, "%s: scandir error on directory '%s': %s",
307                        __func__, dirname, strerror(errno));
308                 return NULL;
309         }
310
311         for (i = 0; i < n; i++) {
312                 snprintf(ccname, sizeof(ccname), "FILE:%s/%s", dirname,
313                          namelist[i]->d_name);
314                 credpath = ccname + 5;
315                 syslog(LOG_DEBUG, "%s: considering %s", __func__, credpath);
316
317                 if (lstat(credpath, &sbuf)) {
318                         syslog(LOG_DEBUG, "%s: stat error on '%s': %s",
319                                __func__, credpath, strerror(errno));
320                         free(namelist[i]);
321                         continue;
322                 }
323                 if (sbuf.st_uid != uid) {
324                         syslog(LOG_DEBUG, "%s: %s is owned by %u, not %u",
325                                __func__, credpath, sbuf.st_uid, uid);
326                         free(namelist[i]);
327                         continue;
328                 }
329                 if (S_ISDIR(sbuf.st_mode)) {
330                         snprintf(ccname, sizeof(ccname), "DIR:%s/%s", dirname,
331                                  namelist[i]->d_name);
332                         credpath = ccname + 4;
333                 } else
334                 if (!S_ISREG(sbuf.st_mode)) {
335                         syslog(LOG_DEBUG, "%s: %s is not a regular file",
336                                __func__, credpath);
337                         free(namelist[i]);
338                         continue;
339                 }
340                 if (!(cred_time = get_tgt_time(ccname))) {
341                         syslog(LOG_DEBUG, "%s: %s is not a valid credcache.",
342                                __func__, ccname);
343                         free(namelist[i]);
344                         continue;
345                 }
346
347                 if (cred_time <= *best_time) {
348                         syslog(LOG_DEBUG, "%s: %s expires sooner than current "
349                                "best.", __func__, ccname);
350                         free(namelist[i]);
351                         continue;
352                 }
353
354                 syslog(LOG_DEBUG, "%s: %s is valid ccache", __func__, ccname);
355                 free(*best_cache);
356                 *best_cache = strndup(ccname, MAX_CCNAME_LEN);
357                 *best_time = cred_time;
358                 free(namelist[i]);
359         }
360         free(namelist);
361
362         return *best_cache;
363 }
364
365 static int
366 cifs_krb5_get_req(const char *host, const char *ccname,
367                   DATA_BLOB * mechtoken, DATA_BLOB * sess_key)
368 {
369         krb5_error_code ret;
370         krb5_keyblock *tokb;
371         krb5_context context;
372         krb5_ccache ccache;
373         krb5_creds in_creds, *out_creds;
374         krb5_data apreq_pkt, in_data;
375         krb5_auth_context auth_context = NULL;
376 #if defined(HAVE_KRB5_AUTH_CON_SETADDRS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
377         static const uint8_t gss_cksum[24] = { 0x10, 0x00, /* ... */};
378 #endif
379
380         ret = krb5_init_context(&context);
381         if (ret) {
382                 syslog(LOG_DEBUG, "%s: unable to init krb5 context", __func__);
383                 return ret;
384         }
385
386         ret = krb5_cc_resolve(context, ccname, &ccache);
387         if (ret) {
388                 syslog(LOG_DEBUG, "%s: unable to resolve %s to ccache\n",
389                        __func__, ccname);
390                 goto out_free_context;
391         }
392
393         memset(&in_creds, 0, sizeof(in_creds));
394
395         ret = krb5_cc_get_principal(context, ccache, &in_creds.client);
396         if (ret) {
397                 syslog(LOG_DEBUG, "%s: unable to get client principal name",
398                        __func__);
399                 goto out_free_ccache;
400         }
401
402         ret = krb5_sname_to_principal(context, host, "cifs", KRB5_NT_UNKNOWN,
403                                         &in_creds.server);
404         if (ret) {
405                 syslog(LOG_DEBUG, "%s: unable to convert sname to princ (%s).",
406                        __func__, host);
407                 goto out_free_principal;
408         }
409
410         ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds);
411         krb5_free_principal(context, in_creds.server);
412         if (ret) {
413                 syslog(LOG_DEBUG, "%s: unable to get credentials for %s",
414                        __func__, host);
415                 goto out_free_principal;
416         }
417
418         in_data.length = 0;
419         in_data.data = NULL;
420
421         ret = krb5_auth_con_init(context, &auth_context);
422         if (ret) {
423                 syslog(LOG_DEBUG, "%s: unable to create auth_context: %d",
424                        __func__, ret);
425                 goto out_free_creds;
426         }
427
428 #if defined(HAVE_KRB5_AUTH_CON_SETADDRS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
429         /* Ensure we will get an addressless ticket. */
430         ret = krb5_auth_con_setaddrs(context, auth_context, NULL, NULL);
431         if (ret) {
432                 syslog(LOG_DEBUG, "%s: unable to set NULL addrs: %d",
433                        __func__, ret);
434                 goto out_free_auth;
435         }
436
437         /*
438          * Create a GSSAPI checksum (0x8003), see RFC 4121.
439          *
440          * The current layout is
441          *
442          * 0x10, 0x00, 0x00, 0x00 - length = 16
443          * 0x00, 0x00, 0x00, 0x00 - channel binding info - 16 zero bytes
444          * 0x00, 0x00, 0x00, 0x00
445          * 0x00, 0x00, 0x00, 0x00
446          * 0x00, 0x00, 0x00, 0x00
447          * 0x00, 0x00, 0x00, 0x00 - flags
448          *
449          * GSS_C_NO_CHANNEL_BINDINGS means 16 zero bytes,
450          * this is needed to work against some closed source
451          * SMB servers.
452          *
453          * See https://bugzilla.samba.org/show_bug.cgi?id=7890
454          */
455         in_data.data = discard_const_p(char, gss_cksum);
456         in_data.length = 24;
457
458         /* MIT krb5 < 1.7 is missing the prototype, but still has the symbol */
459 #if !HAVE_DECL_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE
460         krb5_error_code krb5_auth_con_set_req_cksumtype(
461                 krb5_context      context,
462                 krb5_auth_context auth_context,
463                 krb5_cksumtype    cksumtype);
464 #endif
465         ret = krb5_auth_con_set_req_cksumtype(context, auth_context, 0x8003);
466         if (ret) {
467                 syslog(LOG_DEBUG, "%s: unable to set 0x8003 checksum",
468                        __func__);
469                 goto out_free_auth;
470         }
471 #endif
472
473         apreq_pkt.length = 0;
474         apreq_pkt.data = NULL;
475         ret = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
476                                    &in_data, out_creds, &apreq_pkt);
477         if (ret) {
478                 syslog(LOG_DEBUG, "%s: unable to make AP-REQ for %s",
479                        __func__, host);
480                 goto out_free_auth;
481         }
482
483         ret = krb5_auth_con_getsendsubkey(context, auth_context, &tokb);
484         if (ret) {
485                 syslog(LOG_DEBUG, "%s: unable to get session key for %s",
486                        __func__, host);
487                 goto out_free_auth;
488         }
489
490         *mechtoken = data_blob(apreq_pkt.data, apreq_pkt.length);
491         *sess_key = data_blob(KRB5_KEY_DATA(tokb), KRB5_KEY_LENGTH(tokb));
492
493         krb5_free_keyblock(context, tokb);
494 out_free_auth:
495         krb5_auth_con_free(context, auth_context);
496 out_free_creds:
497         krb5_free_creds(context, out_creds);
498 out_free_principal:
499         krb5_free_principal(context, in_creds.client);
500 out_free_ccache:
501 #if defined(KRB5_TC_OPENCLOSE)
502         krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
503 #endif
504         krb5_cc_close(context, ccache);
505 out_free_context:
506         krb5_free_context(context);
507         return ret;
508 }
509
510 /*
511  * Prepares AP-REQ data for mechToken and gets session key
512  * Uses credentials from cache. It will not ask for password
513  * you should receive credentials for yuor name manually using
514  * kinit or whatever you wish.
515  *
516  * in:
517  *      oid -           string with OID/ Could be OID_KERBEROS5
518  *                      or OID_KERBEROS5_OLD
519  *      principal -     Service name.
520  *                      Could be "cifs/FQDN" for KRB5 OID
521  *                      or for MS_KRB5 OID style server principal
522  *                      like "pdc$@YOUR.REALM.NAME"
523  *
524  * out:
525  *      secblob -       pointer for spnego wrapped AP-REQ data to be stored
526  *      sess_key-       pointer for SessionKey data to be stored
527  *
528  * ret: 0 - success, others - failure
529  */
530 static int
531 handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob,
532                  DATA_BLOB * sess_key, const char *ccname)
533 {
534         int retval;
535         DATA_BLOB tkt, tkt_wrapped;
536
537         syslog(LOG_DEBUG, "%s: getting service ticket for %s", __func__, host);
538
539         /* get a kerberos ticket for the service and extract the session key */
540         retval = cifs_krb5_get_req(host, ccname, &tkt, sess_key);
541         if (retval) {
542                 syslog(LOG_DEBUG, "%s: failed to obtain service ticket (%d)",
543                        __func__, retval);
544                 return retval;
545         }
546
547         syslog(LOG_DEBUG, "%s: obtained service ticket", __func__);
548
549         /* wrap that up in a nice GSS-API wrapping */
550         tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ);
551
552         /* and wrap that in a shiny SPNEGO wrapper */
553         *secblob = gen_negTokenInit(oid, tkt_wrapped);
554
555         data_blob_free(&tkt_wrapped);
556         data_blob_free(&tkt);
557         return retval;
558 }
559
560 #define DKD_HAVE_HOSTNAME       0x1
561 #define DKD_HAVE_VERSION        0x2
562 #define DKD_HAVE_SEC            0x4
563 #define DKD_HAVE_IP             0x8
564 #define DKD_HAVE_UID            0x10
565 #define DKD_HAVE_PID            0x20
566 #define DKD_HAVE_CREDUID        0x40
567 #define DKD_HAVE_USERNAME       0x80
568 #define DKD_MUSTHAVE_SET (DKD_HAVE_HOSTNAME|DKD_HAVE_VERSION|DKD_HAVE_SEC)
569
570 struct decoded_args {
571         int ver;
572         char *hostname;
573         char *ip;
574         char *username;
575         uid_t uid;
576         uid_t creduid;
577         pid_t pid;
578         sectype_t sec;
579 };
580
581 static unsigned int
582 decode_key_description(const char *desc, struct decoded_args *arg)
583 {
584         int len;
585         int retval = 0;
586         char *pos;
587         const char *tkn = desc;
588
589         do {
590                 pos = index(tkn, ';');
591                 if (strncmp(tkn, "host=", 5) == 0) {
592
593                         if (pos == NULL)
594                                 len = strlen(tkn);
595                         else
596                                 len = pos - tkn;
597
598                         len -= 5;
599                         SAFE_FREE(arg->hostname);
600                         arg->hostname = strndup(tkn + 5, len);
601                         if (arg->hostname == NULL) {
602                                 syslog(LOG_ERR, "Unable to allocate memory");
603                                 return 1;
604                         }
605                         retval |= DKD_HAVE_HOSTNAME;
606                         syslog(LOG_DEBUG, "host=%s", arg->hostname);
607                 } else if (!strncmp(tkn, "ip4=", 4) || !strncmp(tkn, "ip6=", 4)) {
608                         if (pos == NULL)
609                                 len = strlen(tkn);
610                         else
611                                 len = pos - tkn;
612
613                         len -= 4;
614                         SAFE_FREE(arg->ip);
615                         arg->ip = strndup(tkn + 4, len);
616                         if (arg->ip == NULL) {
617                                 syslog(LOG_ERR, "Unable to allocate memory");
618                                 return 1;
619                         }
620                         retval |= DKD_HAVE_IP;
621                         syslog(LOG_DEBUG, "ip=%s", arg->ip);
622                 } else if (strncmp(tkn, "user=", 5) == 0) {
623                         if (pos == NULL)
624                                 len = strlen(tkn);
625                         else
626                                 len = pos - tkn;
627
628                         len -= 5;
629                         SAFE_FREE(arg->username);
630                         arg->username = strndup(tkn + 5, len);
631                         if (arg->username == NULL) {
632                                 syslog(LOG_ERR, "Unable to allocate memory");
633                                 return 1;
634                         }
635                         retval |= DKD_HAVE_USERNAME;
636                         syslog(LOG_DEBUG, "user=%s", arg->username);
637                 } else if (strncmp(tkn, "pid=", 4) == 0) {
638                         errno = 0;
639                         arg->pid = strtol(tkn + 4, NULL, 0);
640                         if (errno != 0) {
641                                 syslog(LOG_ERR, "Invalid pid format: %s",
642                                        strerror(errno));
643                                 return 1;
644                         }
645                         syslog(LOG_DEBUG, "pid=%u", arg->pid);
646                         retval |= DKD_HAVE_PID;
647                 } else if (strncmp(tkn, "sec=", 4) == 0) {
648                         if (strncmp(tkn + 4, "krb5", 4) == 0) {
649                                 retval |= DKD_HAVE_SEC;
650                                 arg->sec = KRB5;
651                         } else if (strncmp(tkn + 4, "mskrb5", 6) == 0) {
652                                 retval |= DKD_HAVE_SEC;
653                                 arg->sec = MS_KRB5;
654                         }
655                         syslog(LOG_DEBUG, "sec=%d", arg->sec);
656                 } else if (strncmp(tkn, "uid=", 4) == 0) {
657                         errno = 0;
658                         arg->uid = strtol(tkn + 4, NULL, 16);
659                         if (errno != 0) {
660                                 syslog(LOG_ERR, "Invalid uid format: %s",
661                                        strerror(errno));
662                                 return 1;
663                         }
664                         retval |= DKD_HAVE_UID;
665                         syslog(LOG_DEBUG, "uid=%u", arg->uid);
666                 } else if (strncmp(tkn, "creduid=", 8) == 0) {
667                         errno = 0;
668                         arg->creduid = strtol(tkn + 8, NULL, 16);
669                         if (errno != 0) {
670                                 syslog(LOG_ERR, "Invalid creduid format: %s",
671                                        strerror(errno));
672                                 return 1;
673                         }
674                         retval |= DKD_HAVE_CREDUID;
675                         syslog(LOG_DEBUG, "creduid=%u", arg->creduid);
676                 } else if (strncmp(tkn, "ver=", 4) == 0) {      /* if version */
677                         errno = 0;
678                         arg->ver = strtol(tkn + 4, NULL, 16);
679                         if (errno != 0) {
680                                 syslog(LOG_ERR, "Invalid version format: %s",
681                                        strerror(errno));
682                                 return 1;
683                         }
684                         retval |= DKD_HAVE_VERSION;
685                         syslog(LOG_DEBUG, "ver=%d", arg->ver);
686                 }
687                 if (pos == NULL)
688                         break;
689                 tkn = pos + 1;
690         } while (tkn);
691         return retval;
692 }
693
694 static int cifs_resolver(const key_serial_t key, const char *key_descr)
695 {
696         int c;
697         struct addrinfo *addr;
698         char ip[INET6_ADDRSTRLEN];
699         void *p;
700         const char *keyend = key_descr;
701         /* skip next 4 ';' delimiters to get to description */
702         for (c = 1; c <= 4; c++) {
703                 keyend = index(keyend + 1, ';');
704                 if (!keyend) {
705                         syslog(LOG_ERR, "invalid key description: %s",
706                                key_descr);
707                         return 1;
708                 }
709         }
710         keyend++;
711
712         /* resolve name to ip */
713         c = getaddrinfo(keyend, NULL, NULL, &addr);
714         if (c) {
715                 syslog(LOG_ERR, "unable to resolve hostname: %s [%s]",
716                        keyend, gai_strerror(c));
717                 return 1;
718         }
719
720         /* conver ip to string form */
721         if (addr->ai_family == AF_INET)
722                 p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
723         else
724                 p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
725
726         if (!inet_ntop(addr->ai_family, p, ip, sizeof(ip))) {
727                 syslog(LOG_ERR, "%s: inet_ntop: %s", __func__, strerror(errno));
728                 freeaddrinfo(addr);
729                 return 1;
730         }
731
732         /* setup key */
733         c = keyctl_instantiate(key, ip, strlen(ip) + 1, 0);
734         if (c == -1) {
735                 syslog(LOG_ERR, "%s: keyctl_instantiate: %s", __func__,
736                        strerror(errno));
737                 freeaddrinfo(addr);
738                 return 1;
739         }
740
741         freeaddrinfo(addr);
742         return 0;
743 }
744
745 /*
746  * Older kernels sent IPv6 addresses without colons. Well, at least
747  * they're fixed-length strings. Convert these addresses to have colon
748  * delimiters to make getaddrinfo happy.
749  */
750 static void convert_inet6_addr(const char *from, char *to)
751 {
752         int i = 1;
753
754         while (*from) {
755                 *to++ = *from++;
756                 if (!(i++ % 4) && *from)
757                         *to++ = ':';
758         }
759         *to = 0;
760 }
761
762 static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen)
763 {
764         int rc;
765         struct addrinfo hints = {.ai_flags = AI_NUMERICHOST };
766         struct addrinfo *res;
767         const char *ipaddr = addrstr;
768         char converted[INET6_ADDRSTRLEN + 1];
769
770         if ((strlen(ipaddr) > INET_ADDRSTRLEN) && !strchr(ipaddr, ':')) {
771                 convert_inet6_addr(ipaddr, converted);
772                 ipaddr = converted;
773         }
774
775         rc = getaddrinfo(ipaddr, NULL, &hints, &res);
776         if (rc) {
777                 syslog(LOG_DEBUG, "%s: failed to resolve %s to "
778                        "ipaddr: %s", __func__, ipaddr,
779                        rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
780                 return rc;
781         }
782
783         rc = getnameinfo(res->ai_addr, res->ai_addrlen, host, hostlen,
784                          NULL, 0, NI_NAMEREQD);
785         freeaddrinfo(res);
786         if (rc) {
787                 syslog(LOG_DEBUG, "%s: failed to resolve %s to fqdn: %s",
788                        __func__, ipaddr,
789                        rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
790                 return rc;
791         }
792
793         syslog(LOG_DEBUG, "%s: resolved %s to %s", __func__, ipaddr, host);
794         return 0;
795 }
796
797 /* walk a string and lowercase it in-place */
798 static void
799 lowercase_string(char *c)
800 {
801         while(*c) {
802                 *c = tolower(*c);
803                 ++c;
804         }
805 }
806
807 static void usage(void)
808 {
809         fprintf(stderr, "Usage: %s [-k /path/to/krb5.conf] [-t] [-v] [-l] key_serial\n", prog);
810 }
811
812 const struct option long_options[] = {
813         {"krb5conf", 1, NULL, 'k'},
814         {"legacy-uid", 0, NULL, 'l'},
815         {"trust-dns", 0, NULL, 't'},
816         {"version", 0, NULL, 'v'},
817         {NULL, 0, NULL, 0}
818 };
819
820 int main(const int argc, char *const argv[])
821 {
822         struct cifs_spnego_msg *keydata = NULL;
823         DATA_BLOB secblob = data_blob_null;
824         DATA_BLOB sess_key = data_blob_null;
825         key_serial_t key = 0;
826         size_t datalen;
827         unsigned int have;
828         long rc = 1;
829         int c, try_dns = 0, legacy_uid = 0;
830         char *buf, *ccdir = NULL, *ccname = NULL, *best_cache = NULL;
831         char hostbuf[NI_MAXHOST], *host;
832         struct decoded_args arg;
833         const char *oid;
834         uid_t uid;
835         char *keytab_name = CIFS_DEFAULT_KRB5_KEYTAB;
836         time_t best_time = 0;
837
838         hostbuf[0] = '\0';
839         memset(&arg, 0, sizeof(arg));
840
841         openlog(prog, 0, LOG_DAEMON);
842
843         while ((c = getopt_long(argc, argv, "ck:ltv", long_options, NULL)) != -1) {
844                 switch (c) {
845                 case 'c':
846                         /* legacy option -- skip it */
847                         break;
848                 case 't':
849                         try_dns++;
850                         break;
851                 case 'k':
852                         if (setenv("KRB5_CONFIG", optarg, 1) != 0) {
853                                 syslog(LOG_ERR, "unable to set $KRB5_CONFIG: %d", errno);
854                                 goto out;
855                         }
856                         break;
857                 case 'l':
858                         legacy_uid++;
859                         break;
860                 case 'v':
861                         printf("version: %s\n", VERSION);
862                         goto out;
863                 default:
864                         syslog(LOG_ERR, "unknown option: %c", c);
865                         goto out;
866                 }
867         }
868
869         /* is there a key? */
870         if (argc <= optind) {
871                 usage();
872                 goto out;
873         }
874
875         /* get key and keyring values */
876         errno = 0;
877         key = strtol(argv[optind], NULL, 10);
878         if (errno != 0) {
879                 key = 0;
880                 syslog(LOG_ERR, "Invalid key format: %s", strerror(errno));
881                 goto out;
882         }
883
884         rc = keyctl_describe_alloc(key, &buf);
885         if (rc == -1) {
886                 syslog(LOG_ERR, "keyctl_describe_alloc failed: %s",
887                        strerror(errno));
888                 rc = 1;
889                 goto out;
890         }
891
892         syslog(LOG_DEBUG, "key description: %s", buf);
893
894         if ((strncmp(buf, "cifs.resolver", sizeof("cifs.resolver") - 1) == 0) ||
895             (strncmp(buf, "dns_resolver", sizeof("dns_resolver") - 1) == 0)) {
896                 rc = cifs_resolver(key, buf);
897                 goto out;
898         }
899
900         have = decode_key_description(buf, &arg);
901         SAFE_FREE(buf);
902         if ((have & DKD_MUSTHAVE_SET) != DKD_MUSTHAVE_SET) {
903                 syslog(LOG_ERR, "unable to get necessary params from key "
904                        "description (0x%x)", have);
905                 rc = 1;
906                 goto out;
907         }
908
909         if (arg.ver > CIFS_SPNEGO_UPCALL_VERSION) {
910                 syslog(LOG_ERR, "incompatible kernel upcall version: 0x%x",
911                        arg.ver);
912                 rc = 1;
913                 goto out;
914         }
915
916         if (strlen(arg.hostname) >= NI_MAXHOST) {
917                 syslog(LOG_ERR, "hostname provided by kernel is too long");
918                 rc = 1;
919                 goto out;
920
921         }
922
923         if (!legacy_uid && (have & DKD_HAVE_CREDUID))
924                 uid = arg.creduid;
925         else if (have & DKD_HAVE_UID)
926                 uid = arg.uid;
927         else {
928                 /* no uid= or creduid= parm -- something is wrong */
929                 syslog(LOG_ERR, "No uid= or creduid= parm specified");
930                 rc = 1;
931                 goto out;
932         }
933
934         rc = setuid(uid);
935         if (rc == -1) {
936                 syslog(LOG_ERR, "setuid: %s", strerror(errno));
937                 goto out;
938         }
939         ccdir = resolve_krb5_dir(CIFS_DEFAULT_KRB5_USER_DIR, uid);
940         if (ccdir != NULL)
941                 find_krb5_cc(ccdir, uid, &best_cache, &best_time);
942         ccname = find_krb5_cc(CIFS_DEFAULT_KRB5_DIR, uid, &best_cache,
943                               &best_time);
944         SAFE_FREE(ccdir);
945
946         /* Couldn't find credcache? Try to use keytab */
947         if (ccname == NULL && arg.username != NULL)
948                 ccname = init_cc_from_keytab(keytab_name, arg.username);
949
950         host = arg.hostname;
951
952         // do mech specific authorization
953         switch (arg.sec) {
954         case MS_KRB5:
955         case KRB5:
956                 /*
957                  * Andrew Bartlett's suggested scheme for picking a principal
958                  * name, based on a supplied hostname.
959                  *
960                  * INPUT: fooo
961                  * TRY in order:
962                  * cifs/fooo@REALM
963                  * cifs/fooo.<guessed domain ?>@REALM
964                  *
965                  * INPUT: bar.example.com
966                  * TRY only:
967                  * cifs/bar.example.com@REALM
968                  */
969                 if (arg.sec == MS_KRB5)
970                         oid = OID_KERBEROS5_OLD;
971                 else
972                         oid = OID_KERBEROS5;
973
974 retry_new_hostname:
975                 lowercase_string(host);
976                 rc = handle_krb5_mech(oid, host, &secblob, &sess_key, ccname);
977                 if (!rc)
978                         break;
979
980                 /*
981                  * If hostname has a '.', assume it's a FQDN, otherwise we
982                  * want to guess the domainname.
983                  */
984                 if (!strchr(host, '.')) {
985                         struct addrinfo hints;
986                         struct addrinfo *ai;
987                         char *domainname;
988                         char fqdn[NI_MAXHOST];
989
990                         /*
991                          * use getaddrinfo() to resolve the hostname of the
992                          * server and set ai_canonname.
993                          */
994                         memset(&hints, 0, sizeof(hints));
995                         hints.ai_family = AF_UNSPEC;
996                         hints.ai_flags = AI_CANONNAME;
997                         rc = getaddrinfo(host, NULL, &hints, &ai);
998                         if (rc) {
999                                 syslog(LOG_ERR, "Unable to resolve host address: %s [%s]",
1000                                        host, gai_strerror(rc));
1001                                 break;
1002                         }
1003
1004                         /* scan forward to first '.' in ai_canonnname */
1005                         domainname = strchr(ai->ai_canonname, '.');
1006                         if (!domainname) {
1007                                 rc = -EINVAL;
1008                                 freeaddrinfo(ai);
1009                                 break;
1010                         }
1011                         lowercase_string(domainname);
1012                         rc = snprintf(fqdn, sizeof(fqdn), "%s%s",
1013                                         host, domainname);
1014                         freeaddrinfo(ai);
1015                         if (rc < 0 || (size_t)rc >= sizeof(fqdn)) {
1016                                 syslog(LOG_ERR, "Problem setting hostname in string: %ld", rc);
1017                                 rc = -EINVAL;
1018                                 break;
1019                         }
1020
1021                         rc = handle_krb5_mech(oid, fqdn, &secblob, &sess_key, ccname);
1022                         if (!rc)
1023                                 break;
1024                 }
1025
1026                 if (!try_dns || !(have & DKD_HAVE_IP))
1027                         break;
1028
1029                 rc = ip_to_fqdn(arg.ip, hostbuf, sizeof(hostbuf));
1030                 if (rc)
1031                         break;
1032
1033                 try_dns = 0;
1034                 host = hostbuf;
1035                 goto retry_new_hostname;
1036         default:
1037                 syslog(LOG_ERR, "sectype: %d is not implemented", arg.sec);
1038                 rc = 1;
1039                 break;
1040         }
1041
1042         if (rc) {
1043                 syslog(LOG_DEBUG, "Unable to obtain service ticket");
1044                 goto out;
1045         }
1046
1047         /* pack SecurityBlob and SessionKey into downcall packet */
1048         datalen =
1049             sizeof(struct cifs_spnego_msg) + secblob.length + sess_key.length;
1050         keydata = (struct cifs_spnego_msg *)calloc(sizeof(char), datalen);
1051         if (!keydata) {
1052                 rc = 1;
1053                 goto out;
1054         }
1055         keydata->version = arg.ver;
1056         keydata->flags = 0;
1057         keydata->sesskey_len = sess_key.length;
1058         keydata->secblob_len = secblob.length;
1059         memcpy(&(keydata->data), sess_key.data, sess_key.length);
1060         memcpy(&(keydata->data) + keydata->sesskey_len,
1061                secblob.data, secblob.length);
1062
1063         /* setup key */
1064         rc = keyctl_instantiate(key, keydata, datalen, 0);
1065         if (rc == -1) {
1066                 syslog(LOG_ERR, "keyctl_instantiate: %s", strerror(errno));
1067                 goto out;
1068         }
1069
1070         /* BB: maybe we need use timeout for key: for example no more then
1071          * ticket lifietime? */
1072         /* keyctl_set_timeout( key, 60); */
1073 out:
1074         /*
1075          * on error, negatively instantiate the key ourselves so that we can
1076          * make sure the kernel doesn't hang it off of a searchable keyring
1077          * and interfere with the next attempt to instantiate the key.
1078          */
1079         if (rc != 0 && key == 0) {
1080                 syslog(LOG_DEBUG, "Negating key");
1081                 keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
1082         }
1083         data_blob_free(&secblob);
1084         data_blob_free(&sess_key);
1085         SAFE_FREE(ccname);
1086         SAFE_FREE(arg.hostname);
1087         SAFE_FREE(arg.ip);
1088         SAFE_FREE(arg.username);
1089         SAFE_FREE(keydata);
1090         syslog(LOG_DEBUG, "Exit status %ld", rc);
1091         return rc;
1092 }