cifs.upcall: the exit code should be 0 when print version
[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         /* subtract 1 for the null terminator */
185         return !strncmp(dirent->d_name, CIFS_DEFAULT_KRB5_PREFIX,
186                         sizeof(CIFS_DEFAULT_KRB5_PREFIX) - 1);
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 /* resolve a pattern to an actual directory path */
262 static char *resolve_krb5_dir(const char *pattern, uid_t uid)
263 {
264         char name[MAX_CCNAME_LEN];
265         int i;
266         size_t j;
267         for (i = 0, j = 0; (pattern[i] != '\0') && (j < sizeof(name)); i++) {
268                 switch (pattern[i]) {
269                 case '%':
270                         switch (pattern[i + 1]) {
271                         case '%':
272                                 name[j++] = pattern[i];
273                                 i++;
274                                 break;
275                         case 'U':
276                                 j += snprintf(name + j, sizeof(name) - j,
277                                               "%lu", (unsigned long) uid);
278                                 i++;
279                                 break;
280                         }
281                         break;
282                 default:
283                         name[j++] = pattern[i];
284                         break;
285                 }
286         }
287         if ((j > 0) && (j < sizeof(name)))
288                 return strndup(name, MAX_CCNAME_LEN);
289         else
290                 return NULL;
291 }
292
293 /* search for a credcache that looks like a likely candidate */
294 static char *find_krb5_cc(const char *dirname, uid_t uid,
295                           char **best_cache, time_t *best_time)
296 {
297         struct dirent **namelist;
298         struct stat sbuf;
299         char ccname[MAX_CCNAME_LEN], *credpath;
300         int i, n;
301         time_t cred_time;
302
303         n = scandir(dirname, &namelist, krb5cc_filter, NULL);
304         if (n < 0) {
305                 syslog(LOG_DEBUG, "%s: scandir error on directory '%s': %s",
306                        __func__, dirname, strerror(errno));
307                 return NULL;
308         }
309
310         for (i = 0; i < n; i++) {
311                 snprintf(ccname, sizeof(ccname), "FILE:%s/%s", dirname,
312                          namelist[i]->d_name);
313                 credpath = ccname + 5;
314                 syslog(LOG_DEBUG, "%s: considering %s", __func__, credpath);
315
316                 if (lstat(credpath, &sbuf)) {
317                         syslog(LOG_DEBUG, "%s: stat error on '%s': %s",
318                                __func__, credpath, strerror(errno));
319                         free(namelist[i]);
320                         continue;
321                 }
322                 if (sbuf.st_uid != uid) {
323                         syslog(LOG_DEBUG, "%s: %s is owned by %u, not %u",
324                                __func__, credpath, sbuf.st_uid, uid);
325                         free(namelist[i]);
326                         continue;
327                 }
328                 if (S_ISDIR(sbuf.st_mode)) {
329                         snprintf(ccname, sizeof(ccname), "DIR:%s/%s", dirname,
330                                  namelist[i]->d_name);
331                         credpath = ccname + 4;
332                 } else
333                 if (!S_ISREG(sbuf.st_mode)) {
334                         syslog(LOG_DEBUG, "%s: %s is not a regular file",
335                                __func__, credpath);
336                         free(namelist[i]);
337                         continue;
338                 }
339                 if (!(cred_time = get_tgt_time(ccname))) {
340                         syslog(LOG_DEBUG, "%s: %s is not a valid credcache.",
341                                __func__, ccname);
342                         free(namelist[i]);
343                         continue;
344                 }
345
346                 if (cred_time <= *best_time) {
347                         syslog(LOG_DEBUG, "%s: %s expires sooner than current "
348                                "best.", __func__, ccname);
349                         free(namelist[i]);
350                         continue;
351                 }
352
353                 syslog(LOG_DEBUG, "%s: %s is valid ccache", __func__, ccname);
354                 free(*best_cache);
355                 *best_cache = strndup(ccname, MAX_CCNAME_LEN);
356                 *best_time = cred_time;
357                 free(namelist[i]);
358         }
359         free(namelist);
360
361         return *best_cache;
362 }
363
364 static int
365 cifs_krb5_get_req(const char *host, const char *ccname,
366                   DATA_BLOB * mechtoken, DATA_BLOB * sess_key)
367 {
368         krb5_error_code ret;
369         krb5_keyblock *tokb;
370         krb5_context context;
371         krb5_ccache ccache;
372         krb5_creds in_creds, *out_creds;
373         krb5_data apreq_pkt, in_data;
374         krb5_auth_context auth_context = NULL;
375 #if defined(HAVE_KRB5_AUTH_CON_SETADDRS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
376         static const uint8_t gss_cksum[24] = { 0x10, 0x00, /* ... */};
377 #endif
378
379         ret = krb5_init_context(&context);
380         if (ret) {
381                 syslog(LOG_DEBUG, "%s: unable to init krb5 context", __func__);
382                 return ret;
383         }
384
385         ret = krb5_cc_resolve(context, ccname, &ccache);
386         if (ret) {
387                 syslog(LOG_DEBUG, "%s: unable to resolve %s to ccache\n",
388                        __func__, ccname);
389                 goto out_free_context;
390         }
391
392         memset(&in_creds, 0, sizeof(in_creds));
393
394         ret = krb5_cc_get_principal(context, ccache, &in_creds.client);
395         if (ret) {
396                 syslog(LOG_DEBUG, "%s: unable to get client principal name",
397                        __func__);
398                 goto out_free_ccache;
399         }
400
401         ret = krb5_sname_to_principal(context, host, "cifs", KRB5_NT_UNKNOWN,
402                                         &in_creds.server);
403         if (ret) {
404                 syslog(LOG_DEBUG, "%s: unable to convert sname to princ (%s).",
405                        __func__, host);
406                 goto out_free_principal;
407         }
408
409         ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds);
410         krb5_free_principal(context, in_creds.server);
411         if (ret) {
412                 syslog(LOG_DEBUG, "%s: unable to get credentials for %s",
413                        __func__, host);
414                 goto out_free_principal;
415         }
416
417         in_data.length = 0;
418         in_data.data = NULL;
419
420         ret = krb5_auth_con_init(context, &auth_context);
421         if (ret) {
422                 syslog(LOG_DEBUG, "%s: unable to create auth_context: %d",
423                        __func__, ret);
424                 goto out_free_creds;
425         }
426
427 #if defined(HAVE_KRB5_AUTH_CON_SETADDRS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE)
428         /* Ensure we will get an addressless ticket. */
429         ret = krb5_auth_con_setaddrs(context, auth_context, NULL, NULL);
430         if (ret) {
431                 syslog(LOG_DEBUG, "%s: unable to set NULL addrs: %d",
432                        __func__, ret);
433                 goto out_free_auth;
434         }
435
436         /*
437          * Create a GSSAPI checksum (0x8003), see RFC 4121.
438          *
439          * The current layout is
440          *
441          * 0x10, 0x00, 0x00, 0x00 - length = 16
442          * 0x00, 0x00, 0x00, 0x00 - channel binding info - 16 zero bytes
443          * 0x00, 0x00, 0x00, 0x00
444          * 0x00, 0x00, 0x00, 0x00
445          * 0x00, 0x00, 0x00, 0x00
446          * 0x00, 0x00, 0x00, 0x00 - flags
447          *
448          * GSS_C_NO_CHANNEL_BINDINGS means 16 zero bytes,
449          * this is needed to work against some closed source
450          * SMB servers.
451          *
452          * See https://bugzilla.samba.org/show_bug.cgi?id=7890
453          */
454         in_data.data = discard_const_p(char, gss_cksum);
455         in_data.length = 24;
456
457         /* MIT krb5 < 1.7 is missing the prototype, but still has the symbol */
458 #if !HAVE_DECL_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE
459         krb5_error_code krb5_auth_con_set_req_cksumtype(
460                 krb5_context      context,
461                 krb5_auth_context auth_context,
462                 krb5_cksumtype    cksumtype);
463 #endif
464         ret = krb5_auth_con_set_req_cksumtype(context, auth_context, 0x8003);
465         if (ret) {
466                 syslog(LOG_DEBUG, "%s: unable to set 0x8003 checksum",
467                        __func__);
468                 goto out_free_auth;
469         }
470 #endif
471
472         apreq_pkt.length = 0;
473         apreq_pkt.data = NULL;
474         ret = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
475                                    &in_data, out_creds, &apreq_pkt);
476         if (ret) {
477                 syslog(LOG_DEBUG, "%s: unable to make AP-REQ for %s",
478                        __func__, host);
479                 goto out_free_auth;
480         }
481
482         ret = krb5_auth_con_getsendsubkey(context, auth_context, &tokb);
483         if (ret) {
484                 syslog(LOG_DEBUG, "%s: unable to get session key for %s",
485                        __func__, host);
486                 goto out_free_auth;
487         }
488
489         *mechtoken = data_blob(apreq_pkt.data, apreq_pkt.length);
490         *sess_key = data_blob(KRB5_KEY_DATA(tokb), KRB5_KEY_LENGTH(tokb));
491
492         krb5_free_keyblock(context, tokb);
493 out_free_auth:
494         krb5_auth_con_free(context, auth_context);
495 out_free_creds:
496         krb5_free_creds(context, out_creds);
497 out_free_principal:
498         krb5_free_principal(context, in_creds.client);
499 out_free_ccache:
500 #if defined(KRB5_TC_OPENCLOSE)
501         krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
502 #endif
503         krb5_cc_close(context, ccache);
504 out_free_context:
505         krb5_free_context(context);
506         return ret;
507 }
508
509 /*
510  * Prepares AP-REQ data for mechToken and gets session key
511  * Uses credentials from cache. It will not ask for password
512  * you should receive credentials for yuor name manually using
513  * kinit or whatever you wish.
514  *
515  * in:
516  *      oid -           string with OID/ Could be OID_KERBEROS5
517  *                      or OID_KERBEROS5_OLD
518  *      principal -     Service name.
519  *                      Could be "cifs/FQDN" for KRB5 OID
520  *                      or for MS_KRB5 OID style server principal
521  *                      like "pdc$@YOUR.REALM.NAME"
522  *
523  * out:
524  *      secblob -       pointer for spnego wrapped AP-REQ data to be stored
525  *      sess_key-       pointer for SessionKey data to be stored
526  *
527  * ret: 0 - success, others - failure
528  */
529 static int
530 handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob,
531                  DATA_BLOB * sess_key, const char *ccname)
532 {
533         int retval;
534         DATA_BLOB tkt, tkt_wrapped;
535
536         syslog(LOG_DEBUG, "%s: getting service ticket for %s", __func__, host);
537
538         /* get a kerberos ticket for the service and extract the session key */
539         retval = cifs_krb5_get_req(host, ccname, &tkt, sess_key);
540         if (retval) {
541                 syslog(LOG_DEBUG, "%s: failed to obtain service ticket (%d)",
542                        __func__, retval);
543                 return retval;
544         }
545
546         syslog(LOG_DEBUG, "%s: obtained service ticket", __func__);
547
548         /* wrap that up in a nice GSS-API wrapping */
549         tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ);
550
551         /* and wrap that in a shiny SPNEGO wrapper */
552         *secblob = gen_negTokenInit(oid, tkt_wrapped);
553
554         data_blob_free(&tkt_wrapped);
555         data_blob_free(&tkt);
556         return retval;
557 }
558
559 #define DKD_HAVE_HOSTNAME       0x1
560 #define DKD_HAVE_VERSION        0x2
561 #define DKD_HAVE_SEC            0x4
562 #define DKD_HAVE_IP             0x8
563 #define DKD_HAVE_UID            0x10
564 #define DKD_HAVE_PID            0x20
565 #define DKD_HAVE_CREDUID        0x40
566 #define DKD_HAVE_USERNAME       0x80
567 #define DKD_MUSTHAVE_SET (DKD_HAVE_HOSTNAME|DKD_HAVE_VERSION|DKD_HAVE_SEC)
568
569 struct decoded_args {
570         int ver;
571         char *hostname;
572         char *ip;
573         char *username;
574         uid_t uid;
575         uid_t creduid;
576         pid_t pid;
577         sectype_t sec;
578 };
579
580 static unsigned int
581 decode_key_description(const char *desc, struct decoded_args *arg)
582 {
583         int len;
584         int retval = 0;
585         char *pos;
586         const char *tkn = desc;
587
588         do {
589                 pos = index(tkn, ';');
590                 if (strncmp(tkn, "host=", 5) == 0) {
591
592                         if (pos == NULL)
593                                 len = strlen(tkn);
594                         else
595                                 len = pos - tkn;
596
597                         len -= 5;
598                         SAFE_FREE(arg->hostname);
599                         arg->hostname = strndup(tkn + 5, len);
600                         if (arg->hostname == NULL) {
601                                 syslog(LOG_ERR, "Unable to allocate memory");
602                                 return 1;
603                         }
604                         retval |= DKD_HAVE_HOSTNAME;
605                         syslog(LOG_DEBUG, "host=%s", arg->hostname);
606                 } else if (!strncmp(tkn, "ip4=", 4) || !strncmp(tkn, "ip6=", 4)) {
607                         if (pos == NULL)
608                                 len = strlen(tkn);
609                         else
610                                 len = pos - tkn;
611
612                         len -= 4;
613                         SAFE_FREE(arg->ip);
614                         arg->ip = strndup(tkn + 4, len);
615                         if (arg->ip == NULL) {
616                                 syslog(LOG_ERR, "Unable to allocate memory");
617                                 return 1;
618                         }
619                         retval |= DKD_HAVE_IP;
620                         syslog(LOG_DEBUG, "ip=%s", arg->ip);
621                 } else if (strncmp(tkn, "user=", 5) == 0) {
622                         if (pos == NULL)
623                                 len = strlen(tkn);
624                         else
625                                 len = pos - tkn;
626
627                         len -= 5;
628                         SAFE_FREE(arg->username);
629                         arg->username = strndup(tkn + 5, len);
630                         if (arg->username == NULL) {
631                                 syslog(LOG_ERR, "Unable to allocate memory");
632                                 return 1;
633                         }
634                         retval |= DKD_HAVE_USERNAME;
635                         syslog(LOG_DEBUG, "user=%s", arg->username);
636                 } else if (strncmp(tkn, "pid=", 4) == 0) {
637                         errno = 0;
638                         arg->pid = strtol(tkn + 4, NULL, 0);
639                         if (errno != 0) {
640                                 syslog(LOG_ERR, "Invalid pid format: %s",
641                                        strerror(errno));
642                                 return 1;
643                         }
644                         syslog(LOG_DEBUG, "pid=%u", arg->pid);
645                         retval |= DKD_HAVE_PID;
646                 } else if (strncmp(tkn, "sec=", 4) == 0) {
647                         if (strncmp(tkn + 4, "krb5", 4) == 0) {
648                                 retval |= DKD_HAVE_SEC;
649                                 arg->sec = KRB5;
650                         } else if (strncmp(tkn + 4, "mskrb5", 6) == 0) {
651                                 retval |= DKD_HAVE_SEC;
652                                 arg->sec = MS_KRB5;
653                         }
654                         syslog(LOG_DEBUG, "sec=%d", arg->sec);
655                 } else if (strncmp(tkn, "uid=", 4) == 0) {
656                         errno = 0;
657                         arg->uid = strtol(tkn + 4, NULL, 16);
658                         if (errno != 0) {
659                                 syslog(LOG_ERR, "Invalid uid format: %s",
660                                        strerror(errno));
661                                 return 1;
662                         }
663                         retval |= DKD_HAVE_UID;
664                         syslog(LOG_DEBUG, "uid=%u", arg->uid);
665                 } else if (strncmp(tkn, "creduid=", 8) == 0) {
666                         errno = 0;
667                         arg->creduid = strtol(tkn + 8, NULL, 16);
668                         if (errno != 0) {
669                                 syslog(LOG_ERR, "Invalid creduid format: %s",
670                                        strerror(errno));
671                                 return 1;
672                         }
673                         retval |= DKD_HAVE_CREDUID;
674                         syslog(LOG_DEBUG, "creduid=%u", arg->creduid);
675                 } else if (strncmp(tkn, "ver=", 4) == 0) {      /* if version */
676                         errno = 0;
677                         arg->ver = strtol(tkn + 4, NULL, 16);
678                         if (errno != 0) {
679                                 syslog(LOG_ERR, "Invalid version format: %s",
680                                        strerror(errno));
681                                 return 1;
682                         }
683                         retval |= DKD_HAVE_VERSION;
684                         syslog(LOG_DEBUG, "ver=%d", arg->ver);
685                 }
686                 if (pos == NULL)
687                         break;
688                 tkn = pos + 1;
689         } while (tkn);
690         return retval;
691 }
692
693 static int cifs_resolver(const key_serial_t key, const char *key_descr)
694 {
695         int c;
696         struct addrinfo *addr;
697         char ip[INET6_ADDRSTRLEN];
698         void *p;
699         const char *keyend = key_descr;
700         /* skip next 4 ';' delimiters to get to description */
701         for (c = 1; c <= 4; c++) {
702                 keyend = index(keyend + 1, ';');
703                 if (!keyend) {
704                         syslog(LOG_ERR, "invalid key description: %s",
705                                key_descr);
706                         return 1;
707                 }
708         }
709         keyend++;
710
711         /* resolve name to ip */
712         c = getaddrinfo(keyend, NULL, NULL, &addr);
713         if (c) {
714                 syslog(LOG_ERR, "unable to resolve hostname: %s [%s]",
715                        keyend, gai_strerror(c));
716                 return 1;
717         }
718
719         /* conver ip to string form */
720         if (addr->ai_family == AF_INET)
721                 p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
722         else
723                 p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
724
725         if (!inet_ntop(addr->ai_family, p, ip, sizeof(ip))) {
726                 syslog(LOG_ERR, "%s: inet_ntop: %s", __func__, strerror(errno));
727                 freeaddrinfo(addr);
728                 return 1;
729         }
730
731         /* setup key */
732         c = keyctl_instantiate(key, ip, strlen(ip) + 1, 0);
733         if (c == -1) {
734                 syslog(LOG_ERR, "%s: keyctl_instantiate: %s", __func__,
735                        strerror(errno));
736                 freeaddrinfo(addr);
737                 return 1;
738         }
739
740         freeaddrinfo(addr);
741         return 0;
742 }
743
744 /*
745  * Older kernels sent IPv6 addresses without colons. Well, at least
746  * they're fixed-length strings. Convert these addresses to have colon
747  * delimiters to make getaddrinfo happy.
748  */
749 static void convert_inet6_addr(const char *from, char *to)
750 {
751         int i = 1;
752
753         while (*from) {
754                 *to++ = *from++;
755                 if (!(i++ % 4) && *from)
756                         *to++ = ':';
757         }
758         *to = 0;
759 }
760
761 static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen)
762 {
763         int rc;
764         struct addrinfo hints = {.ai_flags = AI_NUMERICHOST };
765         struct addrinfo *res;
766         const char *ipaddr = addrstr;
767         char converted[INET6_ADDRSTRLEN + 1];
768
769         if ((strlen(ipaddr) > INET_ADDRSTRLEN) && !strchr(ipaddr, ':')) {
770                 convert_inet6_addr(ipaddr, converted);
771                 ipaddr = converted;
772         }
773
774         rc = getaddrinfo(ipaddr, NULL, &hints, &res);
775         if (rc) {
776                 syslog(LOG_DEBUG, "%s: failed to resolve %s to "
777                        "ipaddr: %s", __func__, ipaddr,
778                        rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
779                 return rc;
780         }
781
782         rc = getnameinfo(res->ai_addr, res->ai_addrlen, host, hostlen,
783                          NULL, 0, NI_NAMEREQD);
784         freeaddrinfo(res);
785         if (rc) {
786                 syslog(LOG_DEBUG, "%s: failed to resolve %s to fqdn: %s",
787                        __func__, ipaddr,
788                        rc == EAI_SYSTEM ? strerror(errno) : gai_strerror(rc));
789                 return rc;
790         }
791
792         syslog(LOG_DEBUG, "%s: resolved %s to %s", __func__, ipaddr, host);
793         return 0;
794 }
795
796 /* walk a string and lowercase it in-place */
797 static void
798 lowercase_string(char *c)
799 {
800         while(*c) {
801                 *c = tolower(*c);
802                 ++c;
803         }
804 }
805
806 static void usage(void)
807 {
808         fprintf(stderr, "Usage: %s [-k /path/to/krb5.conf] [-t] [-v] [-l] key_serial\n", prog);
809 }
810
811 const struct option long_options[] = {
812         {"krb5conf", 1, NULL, 'k'},
813         {"legacy-uid", 0, NULL, 'l'},
814         {"trust-dns", 0, NULL, 't'},
815         {"version", 0, NULL, 'v'},
816         {NULL, 0, NULL, 0}
817 };
818
819 int main(const int argc, char *const argv[])
820 {
821         struct cifs_spnego_msg *keydata = NULL;
822         DATA_BLOB secblob = data_blob_null;
823         DATA_BLOB sess_key = data_blob_null;
824         key_serial_t key = 0;
825         size_t datalen;
826         unsigned int have;
827         long rc = 1;
828         int c, try_dns = 0, legacy_uid = 0;
829         char *buf, *ccdir = NULL, *ccname = NULL, *best_cache = NULL;
830         char hostbuf[NI_MAXHOST], *host;
831         struct decoded_args arg;
832         const char *oid;
833         uid_t uid;
834         char *keytab_name = CIFS_DEFAULT_KRB5_KEYTAB;
835         time_t best_time = 0;
836
837         hostbuf[0] = '\0';
838         memset(&arg, 0, sizeof(arg));
839
840         openlog(prog, 0, LOG_DAEMON);
841
842         while ((c = getopt_long(argc, argv, "ck:ltv", long_options, NULL)) != -1) {
843                 switch (c) {
844                 case 'c':
845                         /* legacy option -- skip it */
846                         break;
847                 case 't':
848                         try_dns++;
849                         break;
850                 case 'k':
851                         if (setenv("KRB5_CONFIG", optarg, 1) != 0) {
852                                 syslog(LOG_ERR, "unable to set $KRB5_CONFIG: %d", errno);
853                                 goto out;
854                         }
855                         break;
856                 case 'l':
857                         legacy_uid++;
858                         break;
859                 case 'v':
860                         rc = 0;
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 }