4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
36 #include <sys/param.h>
37 #include <kerberosv5/krb5.h>
38 #include <kerberosv5/com_err.h>
40 #include <smbsrv/libsmb.h>
41 #include <smbns_krb.h>
44 * Kerberized services available on the system.
46 static smb_krb5_pn_t smb_krb5_pn_tab
[] = {
48 * Service keys are salted with the SMB_KRB_PN_ID_ID_SALT prinipal
51 {SMB_KRB5_PN_ID_SALT
, SMB_PN_SVC_HOST
, SMB_PN_SALT
},
53 /* CIFS SPNs. (HOST, CIFS, ...) */
54 {SMB_KRB5_PN_ID_HOST_FQHN
, SMB_PN_SVC_HOST
,
55 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
| SMB_PN_UPN_ATTR
},
56 {SMB_KRB5_PN_ID_HOST_SHORT
, SMB_PN_SVC_HOST
,
57 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
58 {SMB_KRB5_PN_ID_CIFS_FQHN
, SMB_PN_SVC_CIFS
,
59 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
60 {SMB_KRB5_PN_ID_CIFS_SHORT
, SMB_PN_SVC_CIFS
,
61 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
62 {SMB_KRB5_PN_ID_MACHINE
, NULL
,
66 {SMB_KRB5_PN_ID_NFS_FQHN
, SMB_PN_SVC_NFS
,
67 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
70 {SMB_KRB5_PN_ID_HTTP_FQHN
, SMB_PN_SVC_HTTP
,
71 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
74 {SMB_KRB5_PN_ID_ROOT_FQHN
, SMB_PN_SVC_ROOT
,
75 SMB_PN_KEYTAB_ENTRY
| SMB_PN_SPN_ATTR
},
78 #define SMB_KRB5_SPN_TAB_SZ \
79 (sizeof (smb_krb5_pn_tab) / sizeof (smb_krb5_pn_tab[0]))
81 #define SMB_KRB5_MAX_BUFLEN 128
83 static int smb_krb5_kt_open(krb5_context
, char *, krb5_keytab
*);
84 static int smb_krb5_kt_addkey(krb5_context
, krb5_keytab
, const krb5_principal
,
85 krb5_enctype
, krb5_kvno
, const krb5_data
*, const char *);
86 static int smb_krb5_spn_count(uint32_t);
87 static smb_krb5_pn_t
*smb_krb5_lookup_pn(smb_krb5_pn_id_t
);
88 static char *smb_krb5_get_pn_by_id(smb_krb5_pn_id_t
, uint32_t,
90 static int smb_krb5_get_kprinc(krb5_context
, smb_krb5_pn_id_t
, uint32_t,
91 const char *, krb5_principal
*);
95 * Generates a null-terminated array of principal names that
96 * represents the list of the available Kerberized services
97 * of the specified type (SPN attribute, UPN attribute, or
100 * Returns the number of principal names returned via the 1st
101 * output parameter (i.e. vals).
103 * Caller must invoke smb_krb5_free_spns to free the allocated
104 * memory when finished.
107 smb_krb5_get_pn_set(smb_krb5_pn_set_t
*set
, uint32_t type
, char *fqdn
)
110 smb_krb5_pn_t
*tabent
;
115 bzero(set
, sizeof (smb_krb5_pn_set_t
));
116 cnt
= smb_krb5_spn_count(type
);
117 set
->s_pns
= (char **)calloc(cnt
+ 1, sizeof (char *));
119 if (set
->s_pns
== NULL
)
122 for (i
= 0, set
->s_cnt
= 0; i
< SMB_KRB5_SPN_TAB_SZ
; i
++) {
123 tabent
= &smb_krb5_pn_tab
[i
];
125 if (set
->s_cnt
== cnt
)
128 if ((tabent
->p_flags
& type
) != type
)
131 set
->s_pns
[set
->s_cnt
] = smb_krb5_get_pn_by_id(tabent
->p_id
,
133 if (set
->s_pns
[set
->s_cnt
] == NULL
) {
134 syslog(LOG_ERR
, "smbns_ksetpwd: failed to obtain "
135 "principal names: possible transient memory "
137 smb_krb5_free_pn_set(set
);
145 smb_krb5_free_pn_set(set
);
151 smb_krb5_free_pn_set(smb_krb5_pn_set_t
*set
)
155 if (set
== NULL
|| set
->s_pns
== NULL
)
158 for (i
= 0; i
< set
->s_cnt
; i
++)
166 * Initialize the kerberos context.
167 * Return 0 on success. Otherwise, return -1.
170 smb_krb5_ctx_init(krb5_context
*ctx
)
172 if (krb5_init_context(ctx
) != 0)
179 * Free the kerberos context.
182 smb_krb5_ctx_fini(krb5_context ctx
)
184 krb5_free_context(ctx
);
188 * Create an array of Kerberos Princiapls given an array of principal names.
189 * Caller must free the allocated memory using smb_krb5_free_kprincs()
192 * Returns 0 on success. Otherwise, returns -1.
195 smb_krb5_get_kprincs(krb5_context ctx
, char **names
, size_t num
,
196 krb5_principal
**krb5princs
)
200 if ((*krb5princs
= calloc(num
, sizeof (krb5_principal
*))) == NULL
) {
204 for (i
= 0; i
< num
; i
++) {
205 if (krb5_parse_name(ctx
, names
[i
], &(*krb5princs
)[i
]) != 0) {
206 smb_krb5_free_kprincs(ctx
, *krb5princs
, i
);
215 smb_krb5_free_kprincs(krb5_context ctx
, krb5_principal
*krb5princs
,
220 for (i
= 0; i
< num
; i
++)
221 krb5_free_principal(ctx
, krb5princs
[i
]);
227 * Set the workstation trust account password.
228 * Returns 0 on success. Otherwise, returns non-zero value.
231 smb_krb5_setpwd(krb5_context ctx
, const char *fqdn
, char *passwd
)
233 krb5_error_code code
;
234 krb5_ccache cc
= NULL
;
236 krb5_data result_code_string
, result_string
;
237 krb5_principal princ
;
238 char msg
[SMB_KRB5_MAX_BUFLEN
];
240 if (smb_krb5_get_kprinc(ctx
, SMB_KRB5_PN_ID_HOST_FQHN
,
241 SMB_PN_UPN_ATTR
, fqdn
, &princ
) != 0)
244 (void) memset(&result_code_string
, 0, sizeof (result_code_string
));
245 (void) memset(&result_string
, 0, sizeof (result_string
));
247 if ((code
= krb5_cc_default(ctx
, &cc
)) != 0) {
248 (void) snprintf(msg
, sizeof (msg
), "smbns_ksetpwd: failed to "
249 "find %s", SMB_CCACHE_PATH
);
250 smb_krb5_log_errmsg(ctx
, msg
, code
);
251 krb5_free_principal(ctx
, princ
);
255 code
= krb5_set_password_using_ccache(ctx
, cc
, passwd
, princ
,
256 &result_code
, &result_code_string
, &result_string
);
258 (void) krb5_cc_close(ctx
, cc
);
261 smb_krb5_log_errmsg(ctx
, "smbns_ksetpwd: KPASSWD protocol "
262 "exchange failed", code
);
264 if (result_code
!= 0) {
265 syslog(LOG_ERR
, "smbns_ksetpwd: KPASSWD failed: rc=%d %.*s",
267 result_code_string
.length
,
268 result_code_string
.data
);
273 krb5_free_principal(ctx
, princ
);
274 free(result_code_string
.data
);
275 free(result_string
.data
);
280 * Open the keytab file for writing.
281 * The keytab should be closed by calling krb5_kt_close().
284 smb_krb5_kt_open(krb5_context ctx
, char *fname
, krb5_keytab
*kt
)
287 krb5_error_code code
;
289 char msg
[SMB_KRB5_MAX_BUFLEN
];
292 len
= snprintf(NULL
, 0, "WRFILE:%s", fname
) + 1;
293 if ((ktname
= malloc(len
)) == NULL
) {
294 syslog(LOG_ERR
, "smbns_ksetpwd: unable to open keytab %s: "
295 "possible transient memory shortage", fname
);
299 (void) snprintf(ktname
, len
, "WRFILE:%s", fname
);
301 if ((code
= krb5_kt_resolve(ctx
, ktname
, kt
)) != 0) {
302 (void) snprintf(msg
, sizeof (msg
), "smbns_ksetpwd: %s", fname
);
303 smb_krb5_log_errmsg(ctx
, msg
, code
);
313 * Populate the keytab with keys of the specified key version for the
314 * specified set of krb5 principals. All service keys will be salted by:
315 * host/<truncated@15_lower_case_hostname>.<fqdn>@<REALM>
318 smb_krb5_kt_populate(krb5_context ctx
, const char *fqdn
,
319 krb5_principal
*princs
, int count
, char *fname
, krb5_kvno kvno
,
320 char *passwd
, krb5_enctype
*enctypes
, int enctype_count
)
322 krb5_keytab kt
= NULL
;
324 krb5_error_code code
;
325 krb5_principal salt_princ
;
328 if (smb_krb5_kt_open(ctx
, fname
, &kt
) != 0)
331 if (smb_krb5_get_kprinc(ctx
, SMB_KRB5_PN_ID_SALT
, SMB_PN_SALT
,
332 fqdn
, &salt_princ
) != 0) {
333 (void) krb5_kt_close(ctx
, kt
);
337 code
= krb5_principal2salt(ctx
, salt_princ
, &salt
);
339 smb_krb5_log_errmsg(ctx
, "smbns_ksetpwd: salt computation "
341 krb5_free_principal(ctx
, salt_princ
);
342 (void) krb5_kt_close(ctx
, kt
);
346 for (j
= 0; j
< count
; j
++) {
347 for (i
= 0; i
< enctype_count
; i
++) {
348 if (smb_krb5_kt_addkey(ctx
, kt
, princs
[j
], enctypes
[i
],
349 kvno
, &salt
, passwd
) != 0) {
350 krb5_free_principal(ctx
, salt_princ
);
351 krb5_xfree(salt
.data
);
352 (void) krb5_kt_close(ctx
, kt
);
358 krb5_free_principal(ctx
, salt_princ
);
359 krb5_xfree(salt
.data
);
360 (void) krb5_kt_close(ctx
, kt
);
365 smb_krb5_kt_find(smb_krb5_pn_id_t id
, const char *fqdn
, char *fname
)
369 krb5_keytab_entry entry
;
370 krb5_principal princ
;
371 char ktname
[MAXPATHLEN
];
372 boolean_t found
= B_FALSE
;
377 if (smb_krb5_ctx_init(&ctx
) != 0)
380 if (smb_krb5_get_kprinc(ctx
, id
, SMB_PN_KEYTAB_ENTRY
, fqdn
,
382 smb_krb5_ctx_fini(ctx
);
386 (void) snprintf(ktname
, MAXPATHLEN
, "FILE:%s", fname
);
387 if (krb5_kt_resolve(ctx
, ktname
, &kt
) == 0) {
388 if (krb5_kt_get_entry(ctx
, kt
, princ
, 0, 0, &entry
) == 0) {
390 (void) krb5_kt_free_entry(ctx
, &entry
);
393 (void) krb5_kt_close(ctx
, kt
);
396 krb5_free_principal(ctx
, princ
);
397 smb_krb5_ctx_fini(ctx
);
402 * Add a key of the specified encryption type for the specified principal
403 * to the keytab file.
404 * Returns 0 on success. Otherwise, returns -1.
407 smb_krb5_kt_addkey(krb5_context ctx
, krb5_keytab kt
, const krb5_principal princ
,
408 krb5_enctype enctype
, krb5_kvno kvno
, const krb5_data
*salt
,
411 krb5_keytab_entry
*entry
;
414 krb5_error_code code
;
415 char buf
[SMB_KRB5_MAX_BUFLEN
], msg
[SMB_KRB5_MAX_BUFLEN
];
418 if ((code
= krb5_enctype_to_string(enctype
, buf
, sizeof (buf
)))) {
419 (void) snprintf(msg
, sizeof (msg
), "smbns_ksetpwd: unknown "
420 "encryption type (%d)", enctype
);
421 smb_krb5_log_errmsg(ctx
, msg
, code
);
425 if ((entry
= (krb5_keytab_entry
*) malloc(sizeof (*entry
))) == NULL
) {
426 syslog(LOG_ERR
, "smbns_ksetpwd: possible transient "
431 (void) memset((char *)entry
, 0, sizeof (*entry
));
433 password
.length
= strlen(pw
);
434 password
.data
= (char *)pw
;
436 code
= krb5_c_string_to_key(ctx
, enctype
, &password
, salt
, &key
);
438 (void) snprintf(msg
, sizeof (msg
), "smbns_ksetpwd: failed to "
439 "generate key (%d)", enctype
);
440 smb_krb5_log_errmsg(ctx
, msg
, code
);
445 (void) memcpy(&entry
->key
, &key
, sizeof (krb5_keyblock
));
447 entry
->principal
= princ
;
449 if ((code
= krb5_kt_add_entry(ctx
, kt
, entry
)) != 0) {
450 (void) snprintf(msg
, sizeof (msg
), "smbns_ksetpwd: failed to "
451 "add key (%d)", enctype
);
452 smb_krb5_log_errmsg(ctx
, msg
, code
);
458 krb5_free_keyblock_contents(ctx
, &key
);
463 smb_krb5_spn_count(uint32_t type
)
467 for (i
= 0, cnt
= 0; i
< SMB_KRB5_SPN_TAB_SZ
; i
++) {
468 if (smb_krb5_pn_tab
[i
].p_flags
& type
)
476 * Generate the Kerberos Principal given a principal name format and the
477 * fully qualified domain name. On success, caller must free the allocated
478 * memory by calling krb5_free_principal().
481 smb_krb5_get_kprinc(krb5_context ctx
, smb_krb5_pn_id_t id
, uint32_t type
,
482 const char *fqdn
, krb5_principal
*princ
)
486 if ((buf
= smb_krb5_get_pn_by_id(id
, type
, fqdn
)) == NULL
)
489 if (krb5_parse_name(ctx
, buf
, princ
) != 0) {
499 * Looks up an entry in the principal name table given the ID.
501 static smb_krb5_pn_t
*
502 smb_krb5_lookup_pn(smb_krb5_pn_id_t id
)
505 smb_krb5_pn_t
*tabent
;
507 for (i
= 0; i
< SMB_KRB5_SPN_TAB_SZ
; i
++) {
508 tabent
= &smb_krb5_pn_tab
[i
];
509 if (id
== tabent
->p_id
)
517 * Construct the principal name given an ID, the requested type, and the
518 * fully-qualified name of the domain of which the principal is a member.
521 smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id
, uint32_t type
,
524 char nbname
[NETBIOS_NAME_SZ
];
525 char hostname
[MAXHOSTNAMELEN
];
530 (void) smb_getnetbiosname(nbname
, NETBIOS_NAME_SZ
);
531 (void) smb_gethostname(hostname
, MAXHOSTNAMELEN
, SMB_CASE_LOWER
);
533 pn
= smb_krb5_lookup_pn(id
);
535 /* detect inconsistent requested format and type */
536 if ((type
& pn
->p_flags
) != type
)
540 case SMB_KRB5_PN_ID_SALT
:
541 (void) asprintf(&buf
, "%s/%s.%s",
542 pn
->p_svc
, smb_strlwr(nbname
), fqdn
);
545 case SMB_KRB5_PN_ID_HOST_FQHN
:
546 case SMB_KRB5_PN_ID_CIFS_FQHN
:
547 case SMB_KRB5_PN_ID_NFS_FQHN
:
548 case SMB_KRB5_PN_ID_HTTP_FQHN
:
549 case SMB_KRB5_PN_ID_ROOT_FQHN
:
550 (void) asprintf(&buf
, "%s/%s.%s",
551 pn
->p_svc
, hostname
, fqdn
);
554 case SMB_KRB5_PN_ID_HOST_SHORT
:
555 case SMB_KRB5_PN_ID_CIFS_SHORT
:
556 (void) asprintf(&buf
, "%s/%s",
561 * SPN for the machine account, which is simply the
562 * (short) machine name with a dollar sign appended.
564 case SMB_KRB5_PN_ID_MACHINE
:
565 (void) asprintf(&buf
, "%s$", nbname
);
573 * If the requested principal is either added to keytab / the machine
574 * account as the UPN attribute or used for key salt generation,
575 * the principal name must have the @<REALM> portion.
577 if (type
& (SMB_PN_KEYTAB_ENTRY
| SMB_PN_UPN_ATTR
| SMB_PN_SALT
)) {
578 if ((realm
= strdup(fqdn
)) == NULL
) {
583 (void) smb_strupr(realm
);
587 (void) asprintf(&tmp
, "%s@%s", buf
,