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