#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <netdb.h>
#include <libintl.h>
#include <xti.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/byteorder.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netsmb/smb.h>
#include <netsmb/smb_lib.h>
#include <netsmb/mchain.h>
#include "private.h"
#include "charsets.h"
#include "spnego.h"
#include "derparse.h"
#include "ssp.h"
#include <kerberosv5/krb5.h>
#include <kerberosv5/com_err.h>
#include <gssapi/gssapi.h>
#include <gssapi/mechs/krb5/include/auth_con.h>
#define CKSUM_TYPE_RFC4121 0x8003
#define KRB_AP_REQ 1
#define KRB_AP_REP 2
#define KRB_ERROR 3
extern MECH_OID g_stcMechOIDList [];
typedef struct krb5ssp_state {
krb5_context ss_krb5ctx;
krb5_ccache ss_krb5cc;
krb5_principal ss_krb5clp;
krb5_auth_context ss_auth;
} krb5ssp_state_t;
int
krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen,
uchar_t **gtokp, ulong_t *gtoklenp)
{
ulong_t len;
ulong_t bloblen = tktlen;
uchar_t krbapreq[2] = { KRB_AP_REQ, 0 };
uchar_t *blob = NULL;
uchar_t *b;
bloblen += sizeof (krbapreq);
bloblen += g_stcMechOIDList[spnego_mech_oid_Kerberos_V5].iLen;
len = bloblen;
bloblen = ASNDerCalcTokenLength(bloblen, bloblen);
if ((blob = malloc(bloblen)) == NULL) {
DPRINT("malloc");
return (ENOMEM);
}
b = blob;
b += ASNDerWriteToken(b, SPNEGO_NEGINIT_APP_CONSTRUCT, NULL, len);
b += ASNDerWriteOID(b, spnego_mech_oid_Kerberos_V5);
memcpy(b, krbapreq, sizeof (krbapreq));
b += sizeof (krbapreq);
assert(b + tktlen == blob + bloblen);
memcpy(b, tkt, tktlen);
*gtoklenp = bloblen;
*gtokp = blob;
return (0);
}
static krb5_enctype kenctypes[] = {
ENCTYPE_ARCFOUR_HMAC,
ENCTYPE_DES_CBC_MD5,
ENCTYPE_DES_CBC_CRC,
ENCTYPE_NULL
};
static const int rq_opts =
AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
static int
krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server,
uchar_t **tktp, ulong_t *tktlenp)
{
krb5_context kctx = ss->ss_krb5ctx;
krb5_ccache kcc = ss->ss_krb5cc;
krb5_data indata = {0};
krb5_data outdata = {0};
krb5_error_code kerr = 0;
const char *fn = NULL;
uchar_t *tkt;
if (kctx == NULL || kcc == NULL) {
fn = "null kctx or kcc";
kerr = EINVAL;
goto out;
}
kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes);
if (kerr != 0) {
fn = "krb5_set_default_tgs_enctypes";
goto out;
}
kerr = krb5_auth_con_init(kctx, &ss->ss_auth);
if (kerr != 0) {
fn = "krb5_auth_con_init";
goto out;
}
ss->ss_auth->req_cksumtype = CKSUM_TYPE_RFC4121;
indata.length = 24;
if ((indata.data = calloc(1, indata.length)) == NULL) {
kerr = ENOMEM;
fn = "malloc checksum";
goto out;
}
indata.data[0] = 16;
indata.data[20] = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
kerr = krb5_mk_req(kctx, &ss->ss_auth, rq_opts, "cifs", server,
&indata, kcc, &outdata);
if (kerr != 0) {
fn = "krb5_mk_req";
goto out;
}
if ((tkt = malloc(outdata.length)) == NULL) {
kerr = ENOMEM;
fn = "malloc signing key";
goto out;
}
memcpy(tkt, outdata.data, outdata.length);
*tktp = tkt;
*tktlenp = outdata.length;
kerr = 0;
out:
if (kerr) {
if (fn == NULL)
fn = "?";
DPRINT("%s err 0x%x: %s", fn, kerr, error_message(kerr));
if (kerr <= 0 || kerr > ESTALE)
kerr = EAUTH;
}
if (outdata.data)
krb5_free_data_contents(kctx, &outdata);
if (indata.data)
free(indata.data);
return (kerr);
}
int
krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb)
{
int err;
struct smb_ctx *ctx = sp->smb_ctx;
krb5ssp_state_t *ss = sp->sp_private;
uchar_t *tkt = NULL;
ulong_t tktlen;
uchar_t *gtok = NULL;
ulong_t gtoklen;
char *prin = ctx->ct_srvname;
if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0)
goto out;
if ((err = krb5ssp_tkt2gtok(tkt, tktlen, >ok, >oklen)) != 0)
goto out;
if ((err = mb_init_sz(out_mb, gtoklen)) != 0)
goto out;
if ((err = mb_put_mem(out_mb, gtok, gtoklen, MB_MSYSTEM)) != 0)
goto out;
out:
if (gtok)
free(gtok);
if (tkt)
free(tkt);
return (err);
}
int
krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb)
{
krb5ssp_state_t *ss = sp->sp_private;
mbuf_t *m = in_mb->mb_top;
int err = EBADRPC;
int dlen, rc;
long actual_len, token_len;
uchar_t *data;
krb5_data ap = {0};
krb5_ap_rep_enc_part *reply = NULL;
assert(m->m_data == in_mb->mb_pos);
data = (uchar_t *)m->m_data;
dlen = m->m_len;
rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT,
0, dlen, &token_len, &actual_len);
if (rc != SPNEGO_E_SUCCESS) {
DPRINT("no AppToken? rc=0x%x", rc);
goto out;
}
if (dlen < actual_len)
goto out;
data += actual_len;
dlen -= actual_len;
rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5,
dlen, &actual_len);
if (rc != SPNEGO_E_SUCCESS) {
DPRINT("no OID? rc=0x%x", rc);
goto out;
}
if (dlen < actual_len)
goto out;
data += actual_len;
dlen -= actual_len;
if (data[0] != KRB_AP_REP) {
DPRINT("KRB5 type: %d", data[1]);
goto out;
}
if (dlen < 2)
goto out;
data += 2;
dlen -= 2;
ap.length = dlen;
ap.data = (char *)data;
rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply);
if (rc != 0) {
DPRINT("krb5_rd_rep: err 0x%x (%s)",
rc, error_message(rc));
err = EAUTH;
goto out;
}
err = 0;
out:
if (reply != NULL)
krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply);
if (err)
DPRINT("ret %d", err);
return (err);
}
int
krb5ssp_final(struct ssp_ctx *sp)
{
struct smb_ctx *ctx = sp->smb_ctx;
krb5ssp_state_t *ss = sp->sp_private;
krb5_keyblock *ssn_key = NULL;
int err;
err = krb5_auth_con_getlocalsubkey(
ss->ss_krb5ctx, ss->ss_auth, &ssn_key);
if (err != 0) {
DPRINT("_getlocalsubkey, err=0x%x (%s)",
err, error_message(err));
if (err <= 0 || err > ESTALE)
err = EAUTH;
goto out;
}
if (ssn_key->length > 1024) {
DPRINT("session key too long");
err = EAUTH;
goto out;
}
if (ctx->ct_ssnkey_buf != NULL) {
free(ctx->ct_ssnkey_buf);
ctx->ct_ssnkey_buf = NULL;
}
ctx->ct_ssnkey_buf = malloc(ssn_key->length);
if (ctx->ct_ssnkey_buf == NULL) {
err = ENOMEM;
goto out;
}
ctx->ct_ssnkey_len = ssn_key->length;
memcpy(ctx->ct_ssnkey_buf, ssn_key->contents, ctx->ct_ssnkey_len);
err = 0;
out:
if (ssn_key != NULL)
krb5_free_keyblock(ss->ss_krb5ctx, ssn_key);
return (err);
}
int
krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
struct mbdata *out_mb)
{
int err;
if (in_mb) {
err = krb5ssp_get_reply(sp, in_mb);
if (err)
goto out;
}
if (out_mb) {
err = krb5ssp_put_request(sp, out_mb);
} else
err = krb5ssp_final(sp);
out:
if (err)
DPRINT("ret: %d", err);
return (err);
}
void
krb5ssp_destroy(struct ssp_ctx *sp)
{
krb5ssp_state_t *ss;
krb5_context kctx;
ss = sp->sp_private;
if (ss == NULL)
return;
sp->sp_private = NULL;
if ((kctx = ss->ss_krb5ctx) != NULL) {
if (ss->ss_auth)
(void) krb5_auth_con_free(kctx, ss->ss_auth);
if (ss->ss_krb5clp)
krb5_free_principal(kctx, ss->ss_krb5clp);
if (ss->ss_krb5cc)
(void) krb5_cc_close(kctx, ss->ss_krb5cc);
krb5_free_context(kctx);
}
free(ss);
}
int
krb5ssp_init_client(struct ssp_ctx *sp)
{
krb5ssp_state_t *ss;
krb5_error_code kerr;
krb5_context kctx = NULL;
krb5_ccache kcc = NULL;
krb5_principal kprin = NULL;
if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) {
DPRINT("KRB5 not in authflags");
return (ENOTSUP);
}
ss = calloc(1, sizeof (*ss));
if (ss == NULL)
return (ENOMEM);
sp->sp_nexttok = krb5ssp_next_token;
sp->sp_destroy = krb5ssp_destroy;
sp->sp_private = ss;
kerr = krb5_init_context(&kctx);
if (kerr) {
DPRINT("krb5_init_context, kerr 0x%x", kerr);
goto errout;
}
ss->ss_krb5ctx = kctx;
kerr = krb5_cc_default(kctx, &kcc);
if (kerr) {
DPRINT("krb5_cc_default, kerr 0x%x", kerr);
goto errout;
}
ss->ss_krb5cc = kcc;
kerr = krb5_cc_get_principal(kctx, kcc, &kprin);
if (kerr) {
DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr);
goto errout;
}
ss->ss_krb5clp = kprin;
DPRINT("Ticket cache: %s:%s",
krb5_cc_get_type(kctx, kcc),
krb5_cc_get_name(kctx, kcc));
return (0);
errout:
krb5ssp_destroy(sp);
return (ENOTSUP);
}