/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2022-2023 RackTop Systems, Inc. */ /* * SMB authentication service * * This service listens on a local AF_UNIX socket, spawning a * thread to service each connection. The client-side of such * connections is the in-kernel SMB service, with an open and * connect done in the SMB session setup handler. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbd.h" #include "smbd_authsvc.h" /* Arbitrary value outside the (small) range of valid OIDs */ #define special_mech_raw_NTLMSSP (spnego_mech_oid_NTLMSSP + 100) static struct sockaddr_un smbauth_sockname = { AF_UNIX, SMB_AUTHSVC_SOCKNAME }; typedef struct spnego_mech_handler { int mh_oid; /* SPNEGO_MECH_OID */ int (*mh_init)(authsvc_context_t *); int (*mh_work)(authsvc_context_t *); void (*mh_fini)(authsvc_context_t *); } spnego_mech_handler_t; static int smbd_authsock_create(void); static void smbd_authsock_destroy(void); static void *smbd_authsvc_listen(void *); static void *smbd_authsvc_work(void *); static void smbd_authsvc_flood(void); static int smbd_authsvc_oldreq(authsvc_context_t *); static int smbd_authsvc_clinfo(authsvc_context_t *); static int smbd_authsvc_esfirst(authsvc_context_t *); static int smbd_authsvc_esnext(authsvc_context_t *); static int smbd_authsvc_escmn(authsvc_context_t *); static int smbd_authsvc_newmech(authsvc_context_t *); static int smbd_authsvc_gettoken(authsvc_context_t *); static int smbd_raw_ntlmssp_esfirst(authsvc_context_t *); static int smbd_raw_ntlmssp_esnext(authsvc_context_t *); /* * We can get relatively large tokens now, thanks to krb5 PAC. * Might be better to size these buffers dynamically, but these * are all short-lived so not bothering with that for now. */ int smbd_authsvc_bufsize = 65000; static mutex_t smbd_authsvc_mutex = DEFAULTMUTEX; /* * The maximum number of authentication thread is limited by the * smbsrv smb_threshold_...(->sv_ssetup_ct) mechanism. However, * due to occasional delays closing these auth. sockets, we need * a little "slack" on the number of threads we'll allow, as * compared with the in-kernel limit. We could perhaps just * remove this limit now, but want it for extra safety. */ int smbd_authsvc_maxthread = SMB_AUTHSVC_MAXTHREAD + 32; int smbd_authsvc_thrcnt = 0; /* current thrcnt */ int smbd_authsvc_hiwat = 0; /* largest thrcnt seen */ #ifdef DEBUG int smbd_authsvc_slowdown = 0; #endif /* * These are the mechanisms we support, in order of preference. * But note: it's really the _client's_ preference that matters. * See &pref in the spnegoIsMechTypeAvailable() calls below. * Careful with this table; the code below knows its format and * may skip the fist two entries to omit Kerberos. */ static const spnego_mech_handler_t mech_table[] = { { spnego_mech_oid_Kerberos_V5, smbd_krb5ssp_init, smbd_krb5ssp_work, smbd_krb5ssp_fini }, { spnego_mech_oid_Kerberos_V5_Legacy, smbd_krb5ssp_init, smbd_krb5ssp_work, smbd_krb5ssp_fini }, #define MECH_TBL_IDX_NTLMSSP 2 { spnego_mech_oid_NTLMSSP, smbd_ntlmssp_init, smbd_ntlmssp_work, smbd_ntlmssp_fini }, { /* end marker */ spnego_mech_oid_NotUsed, NULL, NULL, NULL }, }; static const spnego_mech_handler_t smbd_auth_mech_raw_ntlmssp = { special_mech_raw_NTLMSSP, smbd_ntlmssp_init, smbd_ntlmssp_work, smbd_ntlmssp_fini }; /* * Start the authentication service. * Returns non-zero on error. */ int smbd_authsvc_start(void) { pthread_attr_t attr; pthread_t tid; int rc; rc = smbd_authsock_create(); if (rc) return (rc); (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); rc = pthread_create(&tid, &attr, smbd_authsvc_listen, &smbd); (void) pthread_attr_destroy(&attr); if (rc) { smbd_authsock_destroy(); return (rc); } smbd.s_authsvc_tid = tid; return (0); } void smbd_authsvc_stop(void) { if (smbd.s_authsvc_tid != 0) { (void) pthread_kill(smbd.s_authsvc_tid, SIGTERM); smbd.s_authsvc_tid = 0; } } static int smbd_authsock_create(void) { int sock = -1; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { smbd_report("authsvc, socket create failed, %d", errno); return (errno); } (void) unlink(smbauth_sockname.sun_path); if (bind(sock, (struct sockaddr *)&smbauth_sockname, sizeof (smbauth_sockname)) < 0) { smbd_report("authsvc, socket bind failed, %d", errno); (void) close(sock); return (errno); } if (listen(sock, SOMAXCONN) < 0) { smbd_report("authsvc, socket listen failed, %d", errno); (void) close(sock); return (errno); } smbd.s_authsvc_sock = sock; return (0); } static void smbd_authsock_destroy(void) { int fid; if ((fid = smbd.s_authsvc_sock) != -1) { smbd.s_authsvc_sock = -1; (void) close(fid); } } #ifndef FKSMBD /* * Decide whether to communicate with the client on this AF_UNIX socket. * Normally the caller should be the (in-kernel) SMB service which has * (typically) all privileges. We test for PRIV_SYS_SMB here, which * only the SMB service should have. */ static boolean_t authsock_has_priv(int sock) { ucred_t *uc = NULL; const priv_set_t *ps = NULL; boolean_t ret = B_FALSE; pid_t clpid; if (getpeerucred(sock, &uc) != 0) { smbd_report("authsvc: getpeerucred err %d", errno); return (B_FALSE); } clpid = ucred_getpid(uc); ps = ucred_getprivset(uc, PRIV_EFFECTIVE); if (ps == NULL) { smbd_report("authsvc: ucred_getprivset failed"); goto out; } /* * Require sys_smb priv. */ if (priv_ismember(ps, PRIV_SYS_SMB)) { ret = B_TRUE; goto out; } if (smbd.s_debug) { smbd_report("authsvc: non-privileged client " "PID = %d UID = %d", (int)clpid, ucred_getruid(uc)); } out: /* ps is free'd with the ucred */ if (uc != NULL) ucred_free(uc); return (ret); } #endif static void * smbd_authsvc_listen(void *arg) { authsvc_context_t *ctx; pthread_attr_t attr; pthread_t tid; socklen_t slen; int ls, ns, rc; _NOTE(ARGUNUSED(arg)) (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ls = smbd.s_authsvc_sock; for (;;) { slen = 0; ns = accept(ls, NULL, &slen); if (ns < 0) { switch (errno) { case ECONNABORTED: continue; case EINTR: /* normal termination */ goto out; default: smbd_report("authsvc, socket accept failed," " %d", errno); goto out; } } #ifndef FKSMBD if (!authsock_has_priv(ns)) { close(ns); continue; } #endif /* * Limit the number of auth. sockets * (and the threads that service them). */ (void) mutex_lock(&smbd_authsvc_mutex); if (smbd_authsvc_thrcnt >= smbd_authsvc_maxthread) { (void) mutex_unlock(&smbd_authsvc_mutex); (void) close(ns); smbd_authsvc_flood(); continue; } smbd_authsvc_thrcnt++; if (smbd_authsvc_hiwat < smbd_authsvc_thrcnt) smbd_authsvc_hiwat = smbd_authsvc_thrcnt; (void) mutex_unlock(&smbd_authsvc_mutex); ctx = smbd_authctx_create(); if (ctx == NULL) { smbd_report("authsvc, can't allocate context"); (void) mutex_lock(&smbd_authsvc_mutex); smbd_authsvc_thrcnt--; (void) mutex_unlock(&smbd_authsvc_mutex); (void) close(ns); smbd_nomem(); } ctx->ctx_socket = ns; rc = pthread_create(&tid, &attr, smbd_authsvc_work, ctx); if (rc) { smbd_report("authsvc, thread create failed, %d", rc); (void) mutex_lock(&smbd_authsvc_mutex); smbd_authsvc_thrcnt--; (void) mutex_unlock(&smbd_authsvc_mutex); smbd_authctx_destroy(ctx); ctx = NULL; smbd_nomem(); } ctx = NULL; /* given to the new thread or destroyed */ (void) pthread_detach(tid); } out: (void) pthread_attr_destroy(&attr); smbd_authsock_destroy(); return (NULL); } static void smbd_authsvc_flood(void) { static uint_t count; static time_t last_report; time_t now = time(NULL); count++; if (last_report + 60 < now) { last_report = now; smbd_report("authsvc: flooded %u", count); count = 0; } } authsvc_context_t * smbd_authctx_create(void) { authsvc_context_t *ctx; ctx = malloc(sizeof (*ctx)); if (ctx == NULL) return (NULL); bzero(ctx, sizeof (*ctx)); ctx->ctx_irawlen = smbd_authsvc_bufsize; ctx->ctx_irawbuf = malloc(ctx->ctx_irawlen); ctx->ctx_orawlen = smbd_authsvc_bufsize; ctx->ctx_orawbuf = malloc(ctx->ctx_orawlen); if (ctx->ctx_irawbuf == NULL || ctx->ctx_orawbuf == NULL) goto errout; ctx->ctx_ibodylen = smbd_authsvc_bufsize; ctx->ctx_ibodybuf = malloc(ctx->ctx_ibodylen); ctx->ctx_obodylen = smbd_authsvc_bufsize; ctx->ctx_obodybuf = malloc(ctx->ctx_obodylen); if (ctx->ctx_ibodybuf == NULL || ctx->ctx_obodybuf == NULL) goto errout; return (ctx); errout: smbd_authctx_destroy(ctx); return (NULL); } void smbd_authctx_destroy(authsvc_context_t *ctx) { if (ctx->ctx_socket != -1) { (void) close(ctx->ctx_socket); ctx->ctx_socket = -1; } if (ctx->ctx_token != NULL) smb_token_destroy(ctx->ctx_token); if (ctx->ctx_itoken != NULL) spnegoFreeData(ctx->ctx_itoken); if (ctx->ctx_otoken != NULL) spnegoFreeData(ctx->ctx_otoken); free(ctx->ctx_irawbuf); free(ctx->ctx_orawbuf); free(ctx->ctx_ibodybuf); free(ctx->ctx_obodybuf); free(ctx); } /* * Limit how long smbd_authsvc_work will wait for the client to * send us the next part of the authentication sequence. */ static struct timeval recv_tmo = { 30, 0 }; /* * Also set a timeout for send, where we're sending a response to * the client side (in smbsrv). That should always be waiting in * recv by the time we send, so a short timeout is OK. */ static struct timeval send_tmo = { 15, 0 }; static void * smbd_authsvc_work(void *arg) { authsvc_context_t *ctx = arg; smb_lsa_msg_hdr_t hdr; int sock = ctx->ctx_socket; int len, rc; if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&send_tmo, sizeof (send_tmo)) != 0) { smbd_report("authsvc_work: set set timeout: %m"); goto out; } if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&recv_tmo, sizeof (recv_tmo)) != 0) { smbd_report("authsvc_work: set recv timeout: %m"); goto out; } for (;;) { len = recv(sock, &hdr, sizeof (hdr), MSG_WAITALL); if (len <= 0) { /* normal termination */ break; } if (len != sizeof (hdr)) { smbd_report("authsvc_work: read header failed"); break; } if (hdr.lmh_msglen > smbd_authsvc_bufsize) { smbd_report("authsvc_work: msg too large"); break; } if (hdr.lmh_msglen > 0) { len = recv(sock, ctx->ctx_irawbuf, hdr.lmh_msglen, MSG_WAITALL); if (len != hdr.lmh_msglen) { smbd_report("authsvc_work: read mesg failed"); break; } } ctx->ctx_irawtype = hdr.lmh_msgtype; ctx->ctx_irawlen = hdr.lmh_msglen; ctx->ctx_orawlen = smbd_authsvc_bufsize; ctx->ctx_ibodylen = smbd_authsvc_bufsize; ctx->ctx_obodylen = smbd_authsvc_bufsize; /* * The real work happens here. */ rc = smbd_authsvc_dispatch(ctx); if (rc) break; hdr.lmh_msgtype = ctx->ctx_orawtype; hdr.lmh_msglen = ctx->ctx_orawlen; len = send(sock, &hdr, sizeof (hdr), 0); if (len != sizeof (hdr)) { smbd_report("authsvc_work: send failed"); break; } if (ctx->ctx_orawlen > 0) { len = send(sock, ctx->ctx_orawbuf, ctx->ctx_orawlen, 0); if (len != ctx->ctx_orawlen) { smbd_report("authsvc_work: send failed"); break; } } } out: if (ctx->ctx_mh_fini) (ctx->ctx_mh_fini)(ctx); smbd_authctx_destroy(ctx); (void) mutex_lock(&smbd_authsvc_mutex); smbd_authsvc_thrcnt--; (void) mutex_unlock(&smbd_authsvc_mutex); return (NULL); /* implied pthread_exit() */ } /* * Dispatch based on message type LSA_MTYPE_... * Non-zero return here ends the conversation. */ int smbd_authsvc_dispatch(authsvc_context_t *ctx) { int rc; switch (ctx->ctx_irawtype) { case LSA_MTYPE_OLDREQ: #ifdef DEBUG if (smbd_authsvc_slowdown) (void) sleep(smbd_authsvc_slowdown); #endif rc = smbd_authsvc_oldreq(ctx); break; case LSA_MTYPE_CLINFO: rc = smbd_authsvc_clinfo(ctx); break; case LSA_MTYPE_ESFIRST: rc = smbd_authsvc_esfirst(ctx); break; case LSA_MTYPE_ESNEXT: #ifdef DEBUG if (smbd_authsvc_slowdown) (void) sleep(smbd_authsvc_slowdown); #endif rc = smbd_authsvc_esnext(ctx); break; case LSA_MTYPE_GETTOK: rc = smbd_authsvc_gettoken(ctx); break; /* response types */ case LSA_MTYPE_OK: case LSA_MTYPE_ERROR: case LSA_MTYPE_TOKEN: case LSA_MTYPE_ES_CONT: case LSA_MTYPE_ES_DONE: default: return (-1); } if (rc == NT_STATUS_NO_MEMORY) smbd_nomem(); if (rc != 0) { smb_lsa_eresp_t *er = ctx->ctx_orawbuf; ctx->ctx_orawtype = LSA_MTYPE_ERROR; ctx->ctx_orawlen = sizeof (*er); er->ler_ntstatus = rc; er->ler_errclass = 0; er->ler_errcode = 0; } return (0); } static int smbd_authsvc_oldreq(authsvc_context_t *ctx) { smb_logon_t user_info; XDR xdrs; smb_token_t *token = NULL; int rc = 0; bzero(&user_info, sizeof (user_info)); xdrmem_create(&xdrs, ctx->ctx_irawbuf, ctx->ctx_irawlen, XDR_DECODE); if (!smb_logon_xdr(&xdrs, &user_info)) { xdr_destroy(&xdrs); return (NT_STATUS_INVALID_PARAMETER); } xdr_destroy(&xdrs); token = smbd_user_auth_logon(&user_info); xdr_free(smb_logon_xdr, (char *)&user_info); if (token == NULL) { rc = user_info.lg_status; if (rc == 0) /* should not happen */ rc = NT_STATUS_INTERNAL_ERROR; return (rc); } ctx->ctx_token = token; return (rc); } static int smbd_authsvc_clinfo(authsvc_context_t *ctx) { if (ctx->ctx_irawlen != sizeof (smb_lsa_clinfo_t)) return (NT_STATUS_INTERNAL_ERROR); (void) memcpy(&ctx->ctx_clinfo, ctx->ctx_irawbuf, sizeof (smb_lsa_clinfo_t)); ctx->ctx_orawtype = LSA_MTYPE_OK; ctx->ctx_orawlen = 0; return (0); } /* * Handle a security blob we've received from the client. * Incoming type: LSA_MTYPE_ESFIRST * Outgoing types: LSA_MTYPE_ES_CONT, LSA_MTYPE_ES_DONE, * LSA_MTYPE_ERROR */ static int smbd_authsvc_esfirst(authsvc_context_t *ctx) { const spnego_mech_handler_t *mh; int idx, pref, rc; int best_pref = 1000; int best_mhidx = -1; /* * NTLMSSP header is 8+, SPNEGO is 10+ */ if (ctx->ctx_irawlen < 8) { smbd_report("authsvc: short blob"); return (NT_STATUS_INVALID_PARAMETER); } /* * We could have "Raw NTLMSSP" here intead of SPNEGO. */ if (bcmp(ctx->ctx_irawbuf, "NTLMSSP", 8) == 0) { rc = smbd_raw_ntlmssp_esfirst(ctx); return (rc); } /* * Parse the SPNEGO token, check its type. */ rc = spnegoInitFromBinary(ctx->ctx_irawbuf, ctx->ctx_irawlen, &ctx->ctx_itoken); if (rc != 0) { smbd_report("authsvc: spnego parse failed"); return (NT_STATUS_INVALID_PARAMETER); } rc = spnegoGetTokenType(ctx->ctx_itoken, &ctx->ctx_itoktype); if (rc != 0) { smbd_report("authsvc: spnego get token type failed"); return (NT_STATUS_INVALID_PARAMETER); } if (ctx->ctx_itoktype != SPNEGO_TOKEN_INIT) { smbd_report("authsvc: spnego wrong token type %d", ctx->ctx_itoktype); return (NT_STATUS_INVALID_PARAMETER); } /* * Figure out which mech type to use. We want to use the * first of the client's supported mechanisms that we also * support. Unfortunately, the spnego code does not have an * interface to walk the token's mech list, so we have to * ask about each mech type we know and keep track of which * was earliest in the token's mech list. * * Also, skip the Kerberos mechanisms in workgroup mode. */ idx = 0; mh = mech_table; if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) { idx = MECH_TBL_IDX_NTLMSSP; mh = &mech_table[idx]; } for (; mh->mh_init != NULL; idx++, mh++) { if (spnegoIsMechTypeAvailable(ctx->ctx_itoken, mh->mh_oid, &pref) != 0) continue; if (pref < best_pref) { best_pref = pref; best_mhidx = idx; } } if (best_mhidx == -1) { smbd_report("authsvc: no supported spnego mechanism"); return (NT_STATUS_INVALID_PARAMETER); } /* Found a mutually agreeable mech. */ mh = &mech_table[best_mhidx]; ctx->ctx_mech_oid = mh->mh_oid; ctx->ctx_mh_work = mh->mh_work; ctx->ctx_mh_fini = mh->mh_fini; rc = mh->mh_init(ctx); if (rc != 0) { smbd_report("authsvc: mech init failed"); return (rc); } /* * If the best supported mech was not the first in the list, * we need to ask the client to use a different one, and * skip (ignore) the provided token body. */ if (best_pref != 0) { rc = smbd_authsvc_newmech(ctx); } else { /* * Common to LSA_MTYPE_ESFIRST, LSA_MTYPE_ESNEXT */ rc = smbd_authsvc_escmn(ctx); } return (rc); } /* * Handle a security blob we've received from the client. * Incoming type: LSA_MTYPE_ESNEXT * Outgoing types: LSA_MTYPE_ES_CONT, LSA_MTYPE_ES_DONE, * LSA_MTYPE_ERROR */ static int smbd_authsvc_esnext(authsvc_context_t *ctx) { int rc; /* * Make sure LSA_MTYPE_ESFIRST was handled * previously, so we have a work function. */ if (ctx->ctx_mh_work == NULL) return (NT_STATUS_INVALID_PARAMETER); if (ctx->ctx_mech_oid == special_mech_raw_NTLMSSP) { rc = smbd_raw_ntlmssp_esnext(ctx); return (rc); } /* * Cleanup state from previous calls. */ if (ctx->ctx_itoken != NULL) { spnegoFreeData(ctx->ctx_itoken); ctx->ctx_itoken = NULL; } /* * Parse the SPNEGO token, check its type. */ rc = spnegoInitFromBinary(ctx->ctx_irawbuf, ctx->ctx_irawlen, &ctx->ctx_itoken); if (rc != 0) return (NT_STATUS_INVALID_PARAMETER); rc = spnegoGetTokenType(ctx->ctx_itoken, &ctx->ctx_itoktype); if (rc != 0) return (NT_STATUS_INVALID_PARAMETER); if (ctx->ctx_itoktype != SPNEGO_TOKEN_TARG) return (NT_STATUS_INVALID_PARAMETER); rc = smbd_authsvc_escmn(ctx); return (rc); } static int smbd_authsvc_escmn(authsvc_context_t *ctx) { SPNEGO_MECH_OID oid; ulong_t toklen; int rc; /* * Cleanup state from previous calls. */ if (ctx->ctx_otoken != NULL) { spnegoFreeData(ctx->ctx_otoken); ctx->ctx_otoken = NULL; } /* * Extract the payload (mech token). */ toklen = ctx->ctx_ibodylen; rc = spnegoGetMechToken(ctx->ctx_itoken, ctx->ctx_ibodybuf, &toklen); switch (rc) { case SPNEGO_E_SUCCESS: break; case SPNEGO_E_ELEMENT_UNAVAILABLE: toklen = 0; break; case SPNEGO_E_BUFFER_TOO_SMALL: return (NT_STATUS_BUFFER_TOO_SMALL); default: return (NT_STATUS_INTERNAL_ERROR); } ctx->ctx_ibodylen = toklen; /* * Now that we have the incoming "body" (mech. token), * call the back-end mech-specific work function to * create the outgoing "body" (mech. token). * * The worker must fill in: ctx->ctx_negresult, * and: ctx->ctx_obodylen, but ctx->ctx_obodybuf * is optional, and is typically NULL after the * final message of an auth sequence, where * negresult == spnego_negresult_complete. */ rc = ctx->ctx_mh_work(ctx); if (rc != 0) return (rc); /* * Wrap the outgoing body in a negTokenTarg SPNEGO token. * The selected mech. OID is returned only when the * incoming token was of type SPNEGO_TOKEN_INIT. */ if (ctx->ctx_itoktype == SPNEGO_TOKEN_INIT) { /* tell the client the selected mech. */ oid = ctx->ctx_mech_oid; } else { /* Omit the "supported mech." field. */ oid = spnego_mech_oid_NotUsed; } /* * Determine the spnego "negresult" from the * reply message type (from the work func). */ switch (ctx->ctx_orawtype) { case LSA_MTYPE_ERROR: ctx->ctx_negresult = spnego_negresult_rejected; break; case LSA_MTYPE_ES_DONE: ctx->ctx_negresult = spnego_negresult_success; break; case LSA_MTYPE_ES_CONT: ctx->ctx_negresult = spnego_negresult_incomplete; break; default: return (-1); } rc = spnegoCreateNegTokenTarg( oid, ctx->ctx_negresult, ctx->ctx_obodybuf, /* may be NULL */ ctx->ctx_obodylen, NULL, 0, &ctx->ctx_otoken); if (rc != 0) return (NT_STATUS_INTERNAL_ERROR); /* * Convert the SPNEGO token into binary form, * writing it to the output buffer. */ toklen = smbd_authsvc_bufsize; rc = spnegoTokenGetBinary(ctx->ctx_otoken, (uchar_t *)ctx->ctx_orawbuf, &toklen); if (rc != 0) rc = NT_STATUS_INTERNAL_ERROR; ctx->ctx_orawlen = (uint_t)toklen; return (rc); } /* * The first NegTokenInit we receive, handled in smbd_authsvc_esfirst, * contains a list of supported mechanisms, in order from the client's * most preferred to least preferred. The token also contains a body * for the first mechanism listed, which is used immediately if the * first mechanism is mutually agreeable. If the first mechanism is * not supported on our side, we must "propose a new mechanism" from * the list. Our caller has selected a mech and initialized the ctx * mech functions. Here compose a reply with an empty body and the * proposed new mechanism OID. The token body received is for some * other mech, so we skip calling the work function with that token. * * Just send a NegTokenTarg with an empty body and the OID for the * proposed mechanism. The next message should be the real first * token for this mechanism, handled in smbd_authsvc_exnext. */ static int smbd_authsvc_newmech(authsvc_context_t *ctx) { ulong_t toklen; int rc; /* * Don't call mh_work here. * Just tell the clint the selected mech. */ ctx->ctx_ibodylen = 0; ctx->ctx_orawtype = LSA_MTYPE_ES_CONT; ctx->ctx_obodylen = 0; ctx->ctx_negresult = spnego_negresult_request_mic; rc = spnegoCreateNegTokenTarg( ctx->ctx_mech_oid, ctx->ctx_negresult, NULL, 0, NULL, 0, &ctx->ctx_otoken); if (rc != 0) return (NT_STATUS_INTERNAL_ERROR); /* * Convert the SPNEGO token into binary form, * writing it to the output buffer. */ toklen = smbd_authsvc_bufsize; rc = spnegoTokenGetBinary(ctx->ctx_otoken, (uchar_t *)ctx->ctx_orawbuf, &toklen); if (rc) rc = NT_STATUS_INTERNAL_ERROR; ctx->ctx_orawlen = (uint_t)toklen; return (rc); } /* * Wrapper for "Raw NTLMSSP", which is exactly like the * normal (SPNEGO-wrapped) NTLMSSP but without SPNEGO. * Setup back-end handler for: special_mech_raw_NTLMSSP * Compare with smbd_authsvc_esfirst(). */ static int smbd_raw_ntlmssp_esfirst(authsvc_context_t *ctx) { const spnego_mech_handler_t *mh; int rc; mh = &smbd_auth_mech_raw_ntlmssp; rc = mh->mh_init(ctx); if (rc != 0) return (rc); ctx->ctx_mech_oid = mh->mh_oid; ctx->ctx_mh_work = mh->mh_work; ctx->ctx_mh_fini = mh->mh_fini; rc = smbd_raw_ntlmssp_esnext(ctx); return (rc); } /* * Wrapper for "Raw NTLMSSP", which is exactly like the * normal (SPNEGO-wrapped) NTLMSSP but without SPNEGO. * Just copy "raw" to "body", and vice versa. * Compare with smbd_authsvc_esnext, smbd_authsvc_escmn */ static int smbd_raw_ntlmssp_esnext(authsvc_context_t *ctx) { int rc; ctx->ctx_ibodylen = ctx->ctx_irawlen; (void) memcpy(ctx->ctx_ibodybuf, ctx->ctx_irawbuf, ctx->ctx_irawlen); rc = ctx->ctx_mh_work(ctx); ctx->ctx_orawlen = ctx->ctx_obodylen; (void) memcpy(ctx->ctx_orawbuf, ctx->ctx_obodybuf, ctx->ctx_obodylen); return (rc); } /* * After a successful authentication, request the access token. */ static int smbd_authsvc_gettoken(authsvc_context_t *ctx) { XDR xdrs; smb_token_t *token = NULL; int rc = 0; int len; if ((token = ctx->ctx_token) == NULL) return (NT_STATUS_ACCESS_DENIED); /* * Encode the token response */ len = xdr_sizeof(smb_token_xdr, token); if (len > ctx->ctx_orawlen) { if ((ctx->ctx_orawbuf = realloc(ctx->ctx_orawbuf, len)) == NULL) { return (NT_STATUS_NO_MEMORY); } } ctx->ctx_orawtype = LSA_MTYPE_TOKEN; ctx->ctx_orawlen = len; xdrmem_create(&xdrs, ctx->ctx_orawbuf, len, XDR_ENCODE); if (!smb_token_xdr(&xdrs, token)) rc = NT_STATUS_INTERNAL_ERROR; xdr_destroy(&xdrs); return (rc); } /* * Initialization time code to figure out what mechanisms we support. * Careful with this table; the code below knows its format and may * skip the fist two entries to omit Kerberos. */ static SPNEGO_MECH_OID MechTypeList[] = { spnego_mech_oid_Kerberos_V5, spnego_mech_oid_Kerberos_V5_Legacy, #define MECH_OID_IDX_NTLMSSP 2 spnego_mech_oid_NTLMSSP, }; static int MechTypeCnt = sizeof (MechTypeList) / sizeof (MechTypeList[0]); /* This string is just like Windows. */ static char IgnoreSPN[] = "not_defined_in_RFC4178@please_ignore"; /* * Build the SPNEGO "hint" token based on the * configured authentication mechanisms. * (NTLMSSP, and maybe Kerberos) */ void smbd_get_authconf(smb_kmod_cfg_t *kcfg) { SPNEGO_MECH_OID *mechList = MechTypeList; int mechCnt = MechTypeCnt; SPNEGO_TOKEN_HANDLE hSpnegoToken = NULL; uchar_t *pBuf = kcfg->skc_negtok; uint32_t *pBufLen = &kcfg->skc_negtok_len; ulong_t tLen = sizeof (kcfg->skc_negtok); int rc; /* * In workgroup mode, skip Kerberos. */ if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) { mechList += MECH_OID_IDX_NTLMSSP; mechCnt -= MECH_OID_IDX_NTLMSSP; } rc = spnegoCreateNegTokenHint(mechList, mechCnt, (uchar_t *)IgnoreSPN, &hSpnegoToken); if (rc != SPNEGO_E_SUCCESS) { syslog(LOG_DEBUG, "smb_config_get_negtok: " "spnegoCreateNegTokenHint, rc=%d", rc); *pBufLen = 0; return; } rc = spnegoTokenGetBinary(hSpnegoToken, pBuf, &tLen); if (rc != SPNEGO_E_SUCCESS) { syslog(LOG_DEBUG, "smb_config_get_negtok: " "spnegoTokenGetBinary, rc=%d", rc); *pBufLen = 0; } else { *pBufLen = (uint32_t)tLen; } spnegoFreeData(hSpnegoToken); }