From c0e8924588ed1cc8724290dd3e4a707e320b7aaa Mon Sep 17 00:00:00 2001 From: briandodge Date: Mon, 11 Nov 2024 07:11:26 -0500 Subject: [PATCH 01/37] add krb5 cred handle passing and release mechanism for proxying --- include/libsmb2-private.h | 13 +++++++++++-- lib/init.c | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index 4ca76380..f922e848 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -23,6 +23,10 @@ extern "C" { #endif +#ifdef HAVE_LIBKRB5 +#include "krb5-wrapper.h" +#endif + #define MIN(a,b) (((a)<(b))?(a):(b)) #ifndef discard_const @@ -121,7 +125,7 @@ struct sync_cb_data { int status; void *ptr; }; - + struct smb2_context { t_socket fd; @@ -181,6 +185,11 @@ struct smb2_context { uint16_t cypher; uint8_t preauthhash[SMB2_PREAUTH_HASH_SIZE]; + +#ifdef HAVE_LIBKRB5 + /* for delegation of client creds to proxy-client */ + struct gss_cred_id_t_desc_struct *cred_handle; +#endif /* * For handling received smb3 encrypted blobs */ @@ -286,7 +295,7 @@ struct smb2_pdu { /* pointer to the unmarshalled payload in a reply */ void *payload; - + /* callback that frees the any additional memory allocated in the payload. * Or null if no additional memory needs to be freed. */ diff --git a/lib/init.c b/lib/init.c index bd7b13eb..160ae4f9 100644 --- a/lib/init.c +++ b/lib/init.c @@ -378,6 +378,13 @@ void smb2_destroy_context(struct smb2_context *smb2) free(discard_const(smb2->workstation)); free(smb2->enc); +#ifdef HAVE_LIBKRB5 + if (smb2->cred_handle) { + OM_uint32 min; + (void) gss_release_cred(&min, &smb2->cred_handle); + smb2->cred_handle = NULL; + } +#endif if (smb2->connect_data) { free_c_data(smb2, smb2->connect_data); /* sets smb2->connect_data to NULL */ } @@ -614,6 +621,14 @@ void smb2_set_user(struct smb2_context *smb2, const char *user) #endif } +const char *smb2_get_user(struct smb2_context *smb2) +{ + if (smb2 && smb2->user) { + return smb2->user; + } + return NULL; +} + void smb2_set_password(struct smb2_context *smb2, const char *password) { if (smb2->password) { @@ -634,6 +649,14 @@ void smb2_set_domain(struct smb2_context *smb2, const char *domain) smb2->domain = strdup(domain); } +const char *smb2_get_domain(struct smb2_context *smb2) +{ + if (smb2 && smb2->domain) { + return smb2->domain; + } + return NULL; +} + void smb2_set_workstation(struct smb2_context *smb2, const char *workstation) { if (smb2->workstation) { @@ -703,3 +726,21 @@ void smb2_set_oplock_or_lease_break_callback(struct smb2_context *smb2, smb2->oplock_or_lease_break_cb = cb; } +#ifdef HAVE_LIBKRB5 +int smb2_delegate_credentials(struct smb2_context *in, struct smb2_context *out) +{ + if (in && out && in->cred_handle) { + out->cred_handle = in->cred_handle; + in->cred_handle = NULL; + return 0; + } else { + return -1; + } +} +#else +int smb2_delegate_credentials(struct smb2_context *in, struct smb2_context *out) +{ + return -1; +} +#endif + From 2e416aa1f491ff84f63814dc773f3b296cf0e582 Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 11 Nov 2024 07:15:47 -0500 Subject: [PATCH 02/37] add krb5 cred handle passing api --- include/smb2/libsmb2.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/smb2/libsmb2.h b/include/smb2/libsmb2.h index f5f14d01..81a6b60c 100644 --- a/include/smb2/libsmb2.h +++ b/include/smb2/libsmb2.h @@ -367,6 +367,13 @@ void smb2_set_authentication(struct smb2_context *smb2, int val); * Default is to try to authenticate as the current user. */ void smb2_set_user(struct smb2_context *smb2, const char *user); + +/* + * Get the username associated with a context. + * returns NULL if none + */ +const char *smb2_get_user(struct smb2_context *smb2); + /* * Set the password that we will try to authenticate as. * This function is only needed when libsmb2 is built --without-libkrb5 @@ -415,6 +422,12 @@ void smb2_set_password_from_file(struct smb2_context *smb2); * This function is only needed when libsmb2 is built --without-libkrb5 */ void smb2_set_domain(struct smb2_context *smb2, const char *domain); + +/* + * Get the domain associated with a context. + * returns NULL if none + */ +const char *smb2_get_domain(struct smb2_context *smb2); /* * Set the workstation when authenticating. * This function is only needed when libsmb2 is built --without-libkrb5 @@ -432,6 +445,13 @@ void smb2_set_opaque(struct smb2_context *smb2, void *opaque); */ void *smb2_get_opaque(struct smb2_context *smb2); +/* + * copy credential handle from one context to another (and set + * it NULL in source context) + * returns 0 if handle transferred, + * or -1 if not (no handle or no context, or not applicable) + */ +int smb2_delegate_credentials(struct smb2_context *in, struct smb2_context *out); /* * Sets the client_guid for this context. @@ -1276,6 +1296,9 @@ struct smb2_server { uint32_t max_write_size; int signing_enabled; int allow_anonymous; + /* this can be set non-0 to delegate client authentication to + * another client and allow any authentication to this server */ + int proxy_authentication; /* saved from negotiate to be used in validate negotiate info */ uint32_t capabilities; uint32_t security_mode; From 9dafb145433241e03059a49c1b4f2644ae06af0d Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 11 Nov 2024 09:07:03 -0500 Subject: [PATCH 03/37] implement server-side krb5 auth and delegation, add domain to client side krb5 and strip ports of server, sniff auth type if undefined --- lib/krb5-wrapper.c | 236 +++++++++++++++++++++++++++++++++++++++---- lib/krb5-wrapper.h | 11 +- lib/libsmb2.c | 90 ++++++++++++----- lib/ntlmssp.c | 34 ++++--- lib/ntlmssp.h | 1 + lib/spnego-wrapper.c | 27 +++-- lib/spnego-wrapper.h | 15 ++- 7 files changed, 344 insertions(+), 70 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 2f2334a5..8c9c8b72 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -109,10 +109,10 @@ display_status(int type, uint32_t err) { gss_buffer_desc text; uint32_t msg_ctx; - char *msg, *tmp; + char *msg, *tmp, *tv; uint32_t maj, min; - msg = NULL; + asprintf(&msg, " "); msg_ctx = 0; do { maj = gss_display_status(&min, err, type, @@ -121,17 +121,24 @@ display_status(int type, uint32_t err) return msg; } - tmp = NULL; - if (msg) { - tmp = msg; - min = asprintf(&msg, "%s, %*s", msg, - (int)text.length, (char *)text.value); - } else { - min = asprintf(&msg, "%*s", (int)text.length, - (char *)text.value); + tv = malloc(text.length + 4); + if (tv) { + memcpy(tv, text.value, text.length); + tv[text.length] = 0; + + tmp = NULL; + if (msg) { + tmp = msg; + min = asprintf(&msg, "%s, %s", tmp, tv); + free(tmp); + } else { + min = asprintf(&msg, "%s", tv); + } + free(tv); + if (min == -1) { + return msg; + } } - if (min == -1) return tmp; - free(tmp); gss_release_buffer(&min, &text); } while (msg_ctx != 0); @@ -144,7 +151,9 @@ krb5_set_gss_error(struct smb2_context *smb2, char *func, { char *err_maj = display_status(GSS_C_GSS_CODE, maj); char *err_min = display_status(GSS_C_MECH_CODE, min); - smb2_set_error(smb2, "%s: (%s, %s)", func, err_maj, err_min); + if (smb2) { + smb2_set_error(smb2, "%s: (%s, %s)", func, err_maj, err_min); + } free(err_min); free(err_maj); } @@ -162,6 +171,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, gss_buffer_desc user; char user_principal[2048]; char *nc_password = NULL; + char *spos; gss_buffer_desc passwd; gss_OID_set_desc mechOidSet; @@ -180,7 +190,16 @@ krb5_negotiate_reply(struct smb2_context *smb2, } auth_data->context = GSS_C_NO_CONTEXT; - if (asprintf(&auth_data->g_server, "cifs@%s", server) < 0) { + /* strip any port off server and form service@host.domain string + */ + strncpy(user_principal, server, sizeof(user_principal) - 1); + user_principal[sizeof(user_principal) - 1] = '\0'; + spos = strchr(user_principal, ':'); + if (spos) { + *spos = '\0'; + } + if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, + domain ? "." : "", domain ? domain : "") < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } @@ -196,6 +215,13 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } + /* if there is a delegated credential in the context, just use tha + */ + if (smb2->cred_handle) { + auth_data->cred = smb2->cred_handle; + return auth_data; + } + if (smb2->use_cached_creds) { memset(&user_principal[0], 0, 2048); if (sprintf(&user_principal[0], "%s@%s", user_name, domain) < 0) { @@ -222,7 +248,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* TODO: the proper mechanism (SPNEGO vs NTLM vs KRB5) should be * selected based on the SMB negotiation flags */ #ifdef __APPLE__ - auth_data->mech_type = GSS_SPNEGO_MECHANISM; + auth_data->mech_type = GSS_KRB5_MECHANISM; /* GSS_SPNEGO_MECHANISM; */ #else auth_data->mech_type = &gss_mech_spnego; #endif @@ -230,11 +256,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* Create creds for the user */ mechOidSet.count = 1; - #ifdef __APPLE__ - mechOidSet.elements = discard_const(GSS_SPNEGO_MECHANISM); - #else - mechOidSet.elements = discard_const(&gss_mech_spnego); - #endif + mechOidSet.elements = discard_const(auth_data->mech_type); if (smb2->use_cached_creds) { krb5_error_code ret = 0; @@ -377,7 +399,7 @@ krb5_session_request(struct smb2_context *smb2, /* NOTE: this call is not async, a helper thread should be used if that * is an issue */ auth_data->req_flags = GSS_C_SEQUENCE_FLAG | GSS_C_MUTUAL_FLAG | - GSS_C_REPLAY_FLAG; + GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG; maj = gss_init_sec_context(&min, auth_data->cred, &auth_data->context, auth_data->target_name, @@ -406,6 +428,178 @@ krb5_session_request(struct smb2_context *smb2, return 0; } +struct private_auth_data * +krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) +{ + struct private_auth_data *auth_data; + char user_principal[1024]; + uint32_t maj, min; + + auth_data = calloc(1, sizeof(struct private_auth_data)); + if (auth_data == NULL) { + smb2_set_error(smb2, "Failed to allocate private_auth_data"); + return NULL; + } + auth_data->context = GSS_C_NO_CONTEXT; + + if (!server->proxy_authentication) { + gss_buffer_desc name_buf; + gss_name_t service_name; + gss_OID mech = /* GSS_C_NO_OID; */ GSS_KRB5_MECHANISM; /*GSS_SPNEGO_MECHANISM;*/ + gss_OID_set mechs = GSS_C_NO_OID_SET; + gss_OID_set_desc mechlist; + char *spos; + + /* strip any port off server and form service@host.domain string + */ + strncpy(user_principal, server->hostname, sizeof(user_principal) - 1); + user_principal[sizeof(user_principal) - 1] = '\0'; + spos = strchr(user_principal, ':'); + if (spos) { + *spos = '\0'; + } + if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, + server->domain[0] ? "." : "", server->domain) < 0) { + smb2_set_error(smb2, "Failed to allocate server string"); + return NULL; + } + + name_buf.value = auth_data->g_server; + name_buf.length = strlen(name_buf.value) + 1; + + maj = gss_import_name(&min, &name_buf, + GSS_C_NT_HOSTBASED_SERVICE, &service_name); + + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_import_name", maj, min); + return NULL; + } + if (mech != GSS_C_NO_OID) { + mechlist.count = 1; + mechlist.elements = mech; + mechs = &mechlist; + } + + maj = gss_acquire_cred(&min, service_name, 0, + mechs, GSS_C_ACCEPT, + &auth_data->cred, + NULL, NULL); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "acquire server creds", maj, min); + (void) gss_release_name(&min, &service_name); + return NULL; + } + (void) gss_release_name(&min, &service_name); + } else { + /* not authenticating client ourselves, just get default cred */ + maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, + &auth_data->cred, + NULL, NULL); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "acquire server creds", maj, min); + /* note, we dont' really fail here, just pass no-creds to accept */ + auth_data->cred = GSS_C_NO_CREDENTIAL; + } + } + return auth_data; +} + +int +krb5_session_reply(struct smb2_context *smb2, + struct private_auth_data *auth_data, + unsigned char *buf, int len, + int *more_processing_needed) +{ + uint32_t maj, min; + gss_name_t client; + gss_OID doid; + gss_buffer_desc *input_token = NULL; + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + struct gss_cred_id_t_desc_struct * ret_delegated_cred_handle; + OM_uint32 ret_timefor; + OM_uint32 ret_flags; + + *more_processing_needed = 0; + + if (auth_data->output_token.value) { + gss_release_buffer(&min, &auth_data->output_token); + auth_data->output_token.length = 0; + auth_data->output_token.value = NULL; + } + + token.value = buf; + token.length = len; + input_token = &token; + + maj = gss_accept_sec_context(&min, + &auth_data->context, + auth_data->cred, + input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client, + &doid, + &auth_data->output_token, + &ret_flags, + &ret_timefor, + &ret_delegated_cred_handle); + + if (maj & GSS_S_CONTINUE_NEEDED) { + *more_processing_needed = 1; + return 0; + } + + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_accept_sec_context", maj, min); + return -1; + } + + maj = gss_display_name(&min, client, &token, NULL); + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_display_name", maj, min); + return -1; + } + + /* client name is in user@domain format, so set that into the client context */ + if (token.length && (char*)token.value) { + char *user; + char *dpos; + int namelen = token.length;; + + user = malloc(namelen + 1); + if (!user) { + smb2_set_error(smb2, "can not alloc name buffer"); + return -1; + } + memcpy(user, (char*)token.value, namelen); + user[namelen] = 0; + + dpos = strchr(user, '@'); + if (dpos) { + *dpos++ = '\0'; + } + + smb2_set_user(smb2, user); + smb2_set_domain(smb2, dpos ? dpos : ""); + + free(user); + } + + /* if the client credential is delegatable and the client is pass-through + * save the client's cred for use in a proxy client + */ + if ((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle)) { + if (smb2->passthrough) { + smb2->cred_handle = ret_delegated_cred_handle; + } else { + smb2->cred_handle = NULL; + maj = gss_release_cred(&min, &ret_delegated_cred_handle); + } + } + + return 0; +} + int krb5_get_output_token_length(struct private_auth_data *auth_data) { diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index f0be497f..6ce7de75 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -79,7 +79,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, int krb5_negotiate_request(struct smb2_context *smb2, void **neg_init_token); - + int krb5_session_get_session_key(struct smb2_context *smb2, struct private_auth_data *auth_data); @@ -89,6 +89,15 @@ krb5_session_request(struct smb2_context *smb2, struct private_auth_data *auth_data, unsigned char *buf, int len); +struct private_auth_data * +krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2); + +int +krb5_session_reply(struct smb2_context *smb2, + struct private_auth_data *auth_data, + unsigned char *buf, int len, + int *more_processing_needed); + void krb5_set_gss_error(struct smb2_context *smb2, char *func, uint32_t maj, uint32_t min); diff --git a/lib/libsmb2.c b/lib/libsmb2.c index c9c52f2c..50e7fcc9 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -830,7 +830,8 @@ send_session_setup_request(struct smb2_context *smb2, req.security_mode = (uint8_t)smb2->security_mode; if (smb2->sec == SMB2_SEC_NTLMSSP) { - /*ntlmssp_set_spnego_wrapping(c_data->auth_data, 1);*/ + /* do this to wrap in spnego if needed */ + /*tlmssp_set_spnego_wrapping(c_data->auth_data, 1);*/ if (ntlmssp_generate_blob(NULL, smb2, time(NULL), c_data->auth_data, buf, len, &req.security_buffer, @@ -1667,7 +1668,7 @@ create_cb_2(struct smb2_context *smb2, int status, void *command_data, void *private_data) { struct create_cb_data *create_data = private_data; - + if (status != SMB2_STATUS_SUCCESS) { status = -nterror_to_errno(status); } @@ -1731,7 +1732,7 @@ smb2_unlink_internal(struct smb2_context *smb2, const char *path, smb2_set_error(smb2, "Failed to create create command"); return -ENOMEM; } - + memset(&cl_req, 0, sizeof(struct smb2_close_request)); cl_req.flags = SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB; memcpy(cl_req.file_id, compound_file_id, SMB2_FD_SIZE); @@ -1744,7 +1745,7 @@ smb2_unlink_internal(struct smb2_context *smb2, const char *path, return -ENOMEM; } smb2_add_compound_pdu(smb2, pdu, next_pdu); - + smb2_queue_pdu(smb2, pdu); return 0; @@ -1814,7 +1815,7 @@ smb2_mkdir_async(struct smb2_context *smb2, const char *path, return -ENOMEM; } smb2_add_compound_pdu(smb2, pdu, next_pdu); - + smb2_queue_pdu(smb2, pdu); return 0; @@ -3404,16 +3405,34 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma pdu = NULL; - if (smb2->sec == SMB2_SEC_NTLMSSP) { + if (smb2->sec == SMB2_SEC_UNDEFINED || smb2->sec == SMB2_SEC_NTLMSSP) { + /* if we haven't set sec type yet, and the blob contains + * valid ntlmssp, use our ntlmssp implementation, but supress + * error-setting while sniffing. if we are expecting ntlmssp + * insist on a valid message + * TODO - perhaps use krb5+ntlmssp if configured? + */ if (ntlmssp_get_message_type(smb2, req->security_buffer, req->security_buffer_length, + smb2->sec == SMB2_SEC_UNDEFINED, &message_type, &response_token, &response_length, - &is_spnego_wrapped) < 0) { - smb2_set_error(smb2, "No message type in NTLMSSP %s", smb2_get_error(smb2)); + &is_spnego_wrapped) >= 0) { + smb2->sec = SMB2_SEC_NTLMSSP; + } else { + +#ifdef HAVE_LIBKRB5 + smb2->sec = SMB2_SEC_KRB5; +#else + smb2_set_error(smb2, "No message type in NTLMSSP %s", + smb2_get_error(smb2)); smb2_close_context(smb2); return; +#endif } + } + + if (smb2->sec == SMB2_SEC_NTLMSSP) { /* set error code in header - more processing required if negotiate req not auth req */ if (message_type == NEGOTIATE_MESSAGE) { if (c_data->auth_data) { @@ -3481,8 +3500,44 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma } #ifdef HAVE_LIBKRB5 else { - /* TODO: */ - have_valid_session_key = 0; + if (!c_data->auth_data) { + /* note that for server, the auth creds really only need to be + * setup once for all connections, but we do it each time to + * simplify auth_data lifetime, perhaps TODO? + */ + c_data->auth_data = krb5_init_server_cred(server, smb2); + if (!c_data->auth_data) { + smb2_set_error(smb2, "can not init auth data %s", smb2_get_error(smb2)); + smb2_close_context(smb2); + return; + } + smb2->connect_data = c_data; + } + + if (krb5_session_reply(smb2, c_data->auth_data, + req->security_buffer, + req->security_buffer_length, + &more_processing_needed)) { + return; + } + + /* alloc a pdu for next request */ + if (more_processing_needed) { + smb2->next_pdu = smb2_allocate_pdu(smb2, SMB2_SESSION_SETUP, + smb2_session_setup_request_cb, cb_data); + smb2->session_id = server->session_counter++; + } else { + smb2->next_pdu = smb2_allocate_pdu(smb2, SMB2_TREE_CONNECT, + smb2_general_client_request_cb, cb_data); + } + rep.security_buffer_length = + krb5_get_output_token_length(c_data->auth_data); + rep.security_buffer = + krb5_get_output_token_buffer(c_data->auth_data); + + if (!krb5_session_get_session_key(smb2, c_data->auth_data)) { + have_valid_session_key = 1; + } } #endif if (smb2->sign && have_valid_session_key == 0) { @@ -3720,18 +3775,8 @@ smb2_negotiate_request_cb(struct smb2_context *smb2, int status, void *command_d now.tv_sec = 0; rep.server_start_time = smb2_timeval_to_win(&now); - smb2->sec = SMB2_SEC_NTLMSSP; - - if (smb2->sec == SMB2_SEC_UNDEFINED) { -#ifdef HAVE_LIBKRB5 - smb2->sec = SMB2_SEC_KRB5; -#else - smb2->sec = SMB2_SEC_NTLMSSP; -#endif - } - rep.security_buffer_length = smb2_spnego_create_negotiate_reply_blob( - smb2, (void*)&rep.security_buffer); + smb2, 1, (void*)&rep.security_buffer); pdu = smb2_cmd_negotiate_reply_async(smb2, &rep, NULL, cb_data); if (rep.security_buffer) { @@ -3828,7 +3873,6 @@ int smb2_serve_port(struct smb2_server *server, const int max_connections, smb2_ if (err != 0) { return err; } - server->session_counter = 0x1234; do { @@ -3857,7 +3901,7 @@ int smb2_serve_port(struct smb2_server *server, const int max_connections, smb2_ } } - /* 100ms select timeout to allow period pdu timeouts */ + /* 100ms select timeout to allow periodic pdu timeouts */ timeout.tv_sec = 0; timeout.tv_usec = 100000; diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index c1413754..e9abd332 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -687,7 +687,7 @@ encode_ntlm_challenge(struct smb2_context *smb2, struct auth_data *auth_data) uint8_t anonymous = 0; int target_info_pos; int namelen; - int cc; + int cc; char *upper = NULL; /* Generate CHALLENGE_MESSAGE */ @@ -706,9 +706,9 @@ encode_ntlm_challenge(struct smb2_context *smb2, struct auth_data *auth_data) u32 = NTLMSSP_NEGOTIATE_128| NTLMSSP_NEGOTIATE_TARGET_INFO| NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY| - NTLMSSP_NEGOTIATE_ALWAYS_SIGN| - NTLMSSP_NEGOTIATE_SIGN| - /* NTLMSSP_NEGOTIATE_KEY_EXCH| */ + NTLMSSP_NEGOTIATE_ALWAYS_SIGN| + NTLMSSP_NEGOTIATE_SIGN| + /* NTLMSSP_NEGOTIATE_KEY_EXCH| */ NTLMSSP_REQUEST_TARGET|NTLMSSP_NEGOTIATE_OEM| NTLMSSP_NEGOTIATE_VERSION| NTLMSSP_NEGOTIATE_UNICODE; @@ -743,7 +743,7 @@ encode_ntlm_challenge(struct smb2_context *smb2, struct auth_data *auth_data) /* target name */ if (auth_data->workstation) { - int i; + int i; namelen = strlen(auth_data->workstation); upper = malloc(namelen + 1); if (!upper) { @@ -874,7 +874,9 @@ ntlmssp_generate_blob(struct smb2_server *server, struct smb2_context *smb2, tim } else { if(ntlmssp_get_message_type(smb2, input_buf, - input_len, &cmd, + input_len, + 0, + &cmd, &ntlmssp, &ntlmssp_len, &is_wrapped) < 0) { ntlmssp_len = 0; @@ -1037,9 +1039,13 @@ ntlmssp_authenticate_blob(struct smb2_server *server, struct smb2_context *smb2, } return -1; } - + + smb2_set_user(smb2, auth_data->user); + smb2_set_domain(smb2, auth_data->domain); + smb2_set_workstation(smb2, auth_data->workstation); + /* negotiate_flags = le32toh(u32); */ - + /* Lan Man response (we dont even look at, its obsolete) */ /* NTLM response */ @@ -1130,10 +1136,11 @@ ntlmssp_get_session_key(struct auth_data *auth, int ntlmssp_get_message_type(struct smb2_context *smb2, - uint8_t *buffer, int len, - uint32_t *message_type, - uint8_t **ntlmssp_ptr, int *ntlmssp_len, - int *is_wrapped) + uint8_t *buffer, int len, + int suppress_errors, + uint32_t *message_type, + uint8_t **ntlmssp_ptr, int *ntlmssp_len, + int *is_wrapped) { uint8_t *ntlmssp = NULL; uint32_t u32; @@ -1153,7 +1160,8 @@ ntlmssp_get_message_type(struct smb2_context *smb2, return -1; } - ntlm_len = smb2_spnego_unwrap_blob(smb2, buffer, len, &ntlmssp, &mechanisms); + ntlm_len = smb2_spnego_unwrap_blob(smb2, buffer, len, + suppress_errors, &ntlmssp, &mechanisms); if (ntlm_len < 12 || !ntlmssp) { return -1; } diff --git a/lib/ntlmssp.h b/lib/ntlmssp.h index 8c50e41f..8c146edd 100644 --- a/lib/ntlmssp.h +++ b/lib/ntlmssp.h @@ -55,6 +55,7 @@ ntlmssp_get_spnego_wrapping(struct auth_data *auth); int ntlmssp_get_message_type(struct smb2_context *smb2, uint8_t *ntlmssp_buffer, int len, + int suppress_errors, uint32_t *message_type, uint8_t **ntlmssp_ptr, int *ntlmssp_len, int *is_wrapped); diff --git a/lib/spnego-wrapper.c b/lib/spnego-wrapper.c index c8787e6d..95a4d1c0 100644 --- a/lib/spnego-wrapper.c +++ b/lib/spnego-wrapper.c @@ -2,7 +2,7 @@ /* Copyright (C) 2024 by Brian Dodge Copyright (C) 2024 by André Guilherme - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or @@ -102,7 +102,7 @@ oid_compare(const struct asn1ber_oid_value *a, const struct asn1ber_oid_value *b } int -smb2_spnego_create_negotiate_reply_blob(struct smb2_context *smb2, void **neg_init_token) +smb2_spnego_create_negotiate_reply_blob(struct smb2_context *smb2, int allow_ntlmssp, void **neg_init_token) { struct asn1ber_context asn_encoder; uint8_t *neg_init; @@ -152,12 +152,14 @@ smb2_spnego_create_negotiate_reply_blob(struct smb2_context *smb2, void **neg_in /* for each negotiable mechanism */ - /* insert mechanism oids */ - asn1ber_ber_from_oid(&asn_encoder, &oid_spnego_mech_ntlmssp); #ifdef HAVE_LIBKRB5 /* insert mechanism oids */ asn1ber_ber_from_oid(&asn_encoder, &oid_spnego_mech_krb5); #endif + if (allow_ntlmssp) { + /* insert mechanism oids */ + asn1ber_ber_from_oid(&asn_encoder, &oid_spnego_mech_ntlmssp); + } asn1ber_annotate_length(&asn_encoder, pos[4], 5); asn1ber_annotate_length(&asn_encoder, pos[3], 5); asn1ber_annotate_length(&asn_encoder, pos[2], 5); @@ -457,7 +459,8 @@ smb2_spnego_unwrap_targ(struct smb2_context *smb2, const uint8_t *spnego, int smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, const uint8_t *spnego, - const int spnego_len, uint8_t **token, uint32_t *mechanisms) + const int spnego_len, const int suppress_errors, + uint8_t **token, uint32_t *mechanisms) { struct asn1ber_context asn_decoder; struct asn1ber_oid_value oid; @@ -508,13 +511,18 @@ smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, const uint8_t *spnego, return typelen; fail: - smb2_set_error(smb2, "bad spnego at line %d, spengo offset %d", fail_line, asn_decoder.src_tail); + if (!suppress_errors) { + smb2_set_error(smb2, "bad spnego at line %d, spengo offset %d", fail_line, asn_decoder.src_tail); + } return -EINVAL; } int -smb2_spnego_unwrap_blob(struct smb2_context *smb2, const uint8_t *spnego, - const int spnego_len, uint8_t **token, uint32_t *mechanisms) +smb2_spnego_unwrap_blob(struct smb2_context *smb2, + const uint8_t *spnego, + const int spnego_len, + const int suppress_errors, + uint8_t **token, uint32_t *mechanisms) { uint8_t typecode; @@ -535,7 +543,8 @@ smb2_spnego_unwrap_blob(struct smb2_context *smb2, const uint8_t *spnego, if (typecode == (asnCONSTRUCTOR | asnAPPLICATION)) { /* 0x60 - a GSS-API blob */ return smb2_spnego_unwrap_gssapi(smb2, - spnego, spnego_len, token, mechanisms); + spnego, spnego_len, + suppress_errors, token, mechanisms); } else if (typecode == ASN1_CONTEXT(0) || typecode == ASN1_CONTEXT(1) || diff --git a/lib/spnego-wrapper.h b/lib/spnego-wrapper.h index 96c92701..a9ffd7aa 100644 --- a/lib/spnego-wrapper.h +++ b/lib/spnego-wrapper.h @@ -5,7 +5,7 @@ /* Copyright (C) 2024 by Brian Dodge Copyright (C) 2024 by André Guilherme - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or @@ -34,11 +34,14 @@ extern "C" { #define SPNEGO_MECHANISM_KRB5 (0x0001) #define SPNEGO_MECHANISM_NTLMSSP (0x0002) -int smb2_spnego_create_negotiate_reply_blob(struct smb2_context *smb2, void **neg_init_token); +int smb2_spnego_create_negotiate_reply_blob(struct smb2_context *smb2, + int allow_ntlmssp, + void **neg_init_token); int smb2_spnego_wrap_gssapi(struct smb2_context *smb2, const uint8_t *ntlmssp_token, - const int token_len, void **blob); + const int token_len, + void **blob); int smb2_spnego_wrap_ntlmssp_challenge(struct smb2_context *smb2, const uint8_t *ntlmssp_token, @@ -47,9 +50,15 @@ int smb2_spnego_wrap_ntlmssp_challenge(struct smb2_context *smb2, int smb2_spnego_wrap_authenticate_result(struct smb2_context *smb2, const int authorized_ok, void **blob); +int smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, + const uint8_t *spnego, const int spnego_len, + const int suppress_errors, + uint8_t **token, uint32_t *mechanisms); + int smb2_spnego_unwrap_blob(struct smb2_context *smb2, const uint8_t *spnego, const int spnego_len, + const int suppress_errors, uint8_t **response_token, uint32_t *mechanisms); From d89b3ef26d9cbd1a3e3d8d5d89c62bcee3e71191 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 12 Nov 2024 10:39:39 -0500 Subject: [PATCH 04/37] set user/pw before auth call --- lib/ntlmssp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index e9abd332..b528e35e 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -1015,6 +1015,10 @@ ntlmssp_authenticate_blob(struct smb2_server *server, struct smb2_context *smb2, ntlmssp_get_utf16_field(input_buf, input_len, 4*11, &auth_data->workstation); memcpy(&u32, &input_buf[4*15], 4); + smb2_set_user(smb2, auth_data->user); + smb2_set_domain(smb2, auth_data->domain); + smb2_set_workstation(smb2, auth_data->workstation); + /* call server handler to get pw for this user */ if (server && server->handlers) { if(server->handlers->authorize_user(server, smb2, @@ -1040,10 +1044,6 @@ ntlmssp_authenticate_blob(struct smb2_server *server, struct smb2_context *smb2, return -1; } - smb2_set_user(smb2, auth_data->user); - smb2_set_domain(smb2, auth_data->domain); - smb2_set_workstation(smb2, auth_data->workstation); - /* negotiate_flags = le32toh(u32); */ /* Lan Man response (we dont even look at, its obsolete) */ From 5118c4b88ae6eb62bd167dbcff3a5ce90e46cd85 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 12 Nov 2024 10:42:05 -0500 Subject: [PATCH 05/37] experiements --- lib/krb5-wrapper.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 8c9c8b72..2032c95e 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -198,12 +198,18 @@ krb5_negotiate_reply(struct smb2_context *smb2, if (spos) { *spos = '\0'; } +#if 0 if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, domain ? "." : "", domain ? domain : "") < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } - +#else + if (asprintf(&auth_data->g_server, "cifs@%s", user_principal) < 0) { + smb2_set_error(smb2, "Failed to allocate server string"); + return NULL; + } +#endif target.value = auth_data->g_server; target.length = strlen(auth_data->g_server); @@ -245,6 +251,8 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } + printf("user =%s= for server =%s=\n", (char*)user.value, auth_data->g_server); + /* TODO: the proper mechanism (SPNEGO vs NTLM vs KRB5) should be * selected based on the SMB negotiation flags */ #ifdef __APPLE__ @@ -442,10 +450,14 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) } auth_data->context = GSS_C_NO_CONTEXT; - if (!server->proxy_authentication) { + if (1) { //server->proxy_authentication) { gss_buffer_desc name_buf; gss_name_t service_name; + #ifdef __APPLE__ gss_OID mech = /* GSS_C_NO_OID; */ GSS_KRB5_MECHANISM; /*GSS_SPNEGO_MECHANISM;*/ + #else + gss_OID mech = GSS_C_NO_OID; + #endif gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; char *spos; @@ -453,22 +465,26 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) /* strip any port off server and form service@host.domain string */ strncpy(user_principal, server->hostname, sizeof(user_principal) - 1); + user_principal[sizeof(user_principal) - 1] = '\0'; spos = strchr(user_principal, ':'); if (spos) { *spos = '\0'; } - if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, - server->domain[0] ? "." : "", server->domain) < 0) { + if (asprintf(&auth_data->g_server, "cifs/%s", user_principal) < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } + //strcpy(auth_data->g_server, "bdodge"); + name_buf.value = auth_data->g_server; name_buf.length = strlen(name_buf.value) + 1; + printf("ac service=%s\n", auth_data->g_server); + maj = gss_import_name(&min, &name_buf, - GSS_C_NT_HOSTBASED_SERVICE, &service_name); + GSS_C_NT_USER_NAME, /*GSS_C_NT_HOSTBASED_SERVICE,*/ &service_name); if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "gss_import_name", maj, min); @@ -493,7 +509,7 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) } else { /* not authenticating client ourselves, just get default cred */ maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, - GSS_C_NO_OID_SET, GSS_C_ACCEPT, + GSS_C_NO_OID_SET, GSS_C_BOTH, &auth_data->cred, NULL, NULL); if (maj != GSS_S_COMPLETE) { @@ -516,7 +532,7 @@ krb5_session_reply(struct smb2_context *smb2, gss_OID doid; gss_buffer_desc *input_token = NULL; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; - struct gss_cred_id_t_desc_struct * ret_delegated_cred_handle; + gss_cred_id_t ret_delegated_cred_handle; OM_uint32 ret_timefor; OM_uint32 ret_flags; From fb71126ea41b5c8886c74638c4925957413c5837 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 12 Nov 2024 10:42:31 -0500 Subject: [PATCH 06/37] proper type for cred handle --- include/libsmb2-private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index f922e848..e73457d1 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -188,7 +188,7 @@ struct smb2_context { #ifdef HAVE_LIBKRB5 /* for delegation of client creds to proxy-client */ - struct gss_cred_id_t_desc_struct *cred_handle; + gss_cred_id_t cred_handle; #endif /* * For handling received smb3 encrypted blobs From 061cbd0190ab6fc3dd6c5f46c81d3f162376ba56 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 12 Nov 2024 19:13:43 -0500 Subject: [PATCH 07/37] prep for proxy creds --- lib/krb5-wrapper.c | 143 ++++++++++++++++++++++++++------------------- lib/krb5-wrapper.h | 1 + 2 files changed, 84 insertions(+), 60 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 2032c95e..5e86219c 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -100,6 +100,10 @@ krb5_free_auth_data(struct private_auth_data *auth) gss_release_name(&min, &auth->user_name); } + if (auth->cred) { + gss_release_cred(&min, &auth->cred); + } + free(auth->g_server); free(auth); } @@ -190,26 +194,19 @@ krb5_negotiate_reply(struct smb2_context *smb2, } auth_data->context = GSS_C_NO_CONTEXT; - /* strip any port off server and form service@host.domain string - */ + /* strip any port off server */ strncpy(user_principal, server, sizeof(user_principal) - 1); user_principal[sizeof(user_principal) - 1] = '\0'; spos = strchr(user_principal, ':'); if (spos) { *spos = '\0'; } -#if 0 + /* form the spn cfs@hostname.domain */ if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, domain ? "." : "", domain ? domain : "") < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } -#else - if (asprintf(&auth_data->g_server, "cifs@%s", user_principal) < 0) { - smb2_set_error(smb2, "Failed to allocate server string"); - return NULL; - } -#endif target.value = auth_data->g_server; target.length = strlen(auth_data->g_server); @@ -221,10 +218,11 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } - /* if there is a delegated credential in the context, just use tha + /* if there is a delegated credential in the context, just use that */ if (smb2->cred_handle) { auth_data->cred = smb2->cred_handle; + smb2->cred_handle = NULL; return auth_data; } @@ -251,8 +249,6 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } - printf("user =%s= for server =%s=\n", (char*)user.value, auth_data->g_server); - /* TODO: the proper mechanism (SPNEGO vs NTLM vs KRB5) should be * selected based on the SMB negotiation flags */ #ifdef __APPLE__ @@ -309,7 +305,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, } if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "gss_acquire_cred", maj, min); + krb5_set_gss_error(smb2, "gss_acquire_cred (client)", maj, min); return NULL; } @@ -441,7 +437,9 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) { struct private_auth_data *auth_data; char user_principal[1024]; + gss_buffer_desc name_buf; uint32_t maj, min; + char *spos; auth_data = calloc(1, sizeof(struct private_auth_data)); if (auth_data == NULL) { @@ -449,74 +447,69 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) return NULL; } auth_data->context = GSS_C_NO_CONTEXT; + auth_data->get_proxy_cred = 0; + + /* strip any port off server host */ + strncpy(user_principal, server->hostname, sizeof(user_principal) - 1); + user_principal[sizeof(user_principal) - 1] = '\0'; + spos = strchr(user_principal, ':'); + if (spos) { + *spos = '\0'; + } + /* form spn cifs/hostname.domain */ + if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, + server->domain[0] ? "." : "", + server->domain[0] ? server->domain : "") < 0) { + smb2_set_error(smb2, "Failed to allocate server string"); + return NULL; + } + + name_buf.value = auth_data->g_server; + name_buf.length = strlen(name_buf.value) + 1; + + maj = gss_import_name(&min, &name_buf, + GSS_C_NT_HOSTBASED_SERVICE, &auth_data->target_name); + + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_import_name", maj, min); + return NULL; + } - if (1) { //server->proxy_authentication) { - gss_buffer_desc name_buf; - gss_name_t service_name; + if (!server->proxy_authentication) { + /* we, the server, will decrypt the client's ticket ourselfs + * which means we will need the key for the service the client uses + * in our key-table (i.e. cifs/hosthame.domain) + * this path isn't tested completely yet + */ #ifdef __APPLE__ - gss_OID mech = /* GSS_C_NO_OID; */ GSS_KRB5_MECHANISM; /*GSS_SPNEGO_MECHANISM;*/ + gss_OID mech = GSS_C_NO_OID; #else gss_OID mech = GSS_C_NO_OID; #endif gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; - char *spos; - - /* strip any port off server and form service@host.domain string - */ - strncpy(user_principal, server->hostname, sizeof(user_principal) - 1); - - user_principal[sizeof(user_principal) - 1] = '\0'; - spos = strchr(user_principal, ':'); - if (spos) { - *spos = '\0'; - } - if (asprintf(&auth_data->g_server, "cifs/%s", user_principal) < 0) { - smb2_set_error(smb2, "Failed to allocate server string"); - return NULL; - } - //strcpy(auth_data->g_server, "bdodge"); - - name_buf.value = auth_data->g_server; - name_buf.length = strlen(name_buf.value) + 1; - - printf("ac service=%s\n", auth_data->g_server); - - maj = gss_import_name(&min, &name_buf, - GSS_C_NT_USER_NAME, /*GSS_C_NT_HOSTBASED_SERVICE,*/ &service_name); - - if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "gss_import_name", maj, min); - return NULL; - } if (mech != GSS_C_NO_OID) { mechlist.count = 1; mechlist.elements = mech; mechs = &mechlist; } - maj = gss_acquire_cred(&min, service_name, 0, + maj = gss_acquire_cred(&min, auth_data->target_name, 0, mechs, GSS_C_ACCEPT, &auth_data->cred, NULL, NULL); if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "acquire server creds", maj, min); - (void) gss_release_name(&min, &service_name); + krb5_set_gss_error(smb2, "gss_acquire_cred (server)", maj, min); return NULL; } - (void) gss_release_name(&min, &service_name); } else { - /* not authenticating client ourselves, just get default cred */ - maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, - GSS_C_NO_OID_SET, GSS_C_BOTH, - &auth_data->cred, - NULL, NULL); - if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "acquire server creds", maj, min); - /* note, we dont' really fail here, just pass no-creds to accept */ - auth_data->cred = GSS_C_NO_CREDENTIAL; - } + /* passing no-credential to accept will actually work directly IFF + * the client has sent a delegated credential, but that's a special case + * so accept the client's cred blindly and use that to get a proxy cred + */ + auth_data->cred = GSS_C_NO_CREDENTIAL; + auth_data->get_proxy_cred = 1; } return auth_data; } @@ -601,6 +594,36 @@ krb5_session_reply(struct smb2_context *smb2, free(user); } + if (auth_data->get_proxy_cred) { +#ifdef __APPLE__ + if (!((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle))) { + smb2_set_error(smb2, "Apple has no way to proxy credentials"); + return -1; + } +#else + gss_cred_id_t proxy_cred; + + /* not authenticating client ourselves, get a proxy credential from the + * client's credential to impersonate the client + */ + maj = gss_acquire_cred_impersonate_name(&min, + &auth_data->cred, + our_service_name, + NULL, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &proxy_cred, + NULL, + NULL); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); + return -1; + } + + ret_flags |= GSS_C_DELEG_FLAG; +#endif + } + /* if the client credential is delegatable and the client is pass-through * save the client's cred for use in a proxy client */ diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 6ce7de75..49654e09 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -58,6 +58,7 @@ struct private_auth_data { gss_const_OID mech_type; uint32_t req_flags; gss_buffer_desc output_token; + int get_proxy_cred; char *g_server; }; From 0f25943534613cdd8a692baa7c80055b8dcd76fb Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 24 Nov 2024 20:59:16 -0500 Subject: [PATCH 08/37] working krb5 server and constrained delegation --- lib/krb5-wrapper.c | 164 ++++++++++++++++++++++++++++----------------- lib/krb5-wrapper.h | 4 ++ 2 files changed, 107 insertions(+), 61 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 5e86219c..2da162cb 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -201,12 +201,13 @@ krb5_negotiate_reply(struct smb2_context *smb2, if (spos) { *spos = '\0'; } - /* form the spn cfs@hostname.domain */ - if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, - domain ? "." : "", domain ? domain : "") < 0) { + /* form spn cifs/hostname.domain */ + if (asprintf(&auth_data->g_server, "cifs@%s", user_principal) < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } + printf("target=%s=\n", auth_data->g_server); + target.value = auth_data->g_server; target.length = strlen(auth_data->g_server); @@ -221,6 +222,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* if there is a delegated credential in the context, just use that */ if (smb2->cred_handle) { + printf("delhand\n"); auth_data->cred = smb2->cred_handle; smb2->cred_handle = NULL; return auth_data; @@ -254,7 +256,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, #ifdef __APPLE__ auth_data->mech_type = GSS_KRB5_MECHANISM; /* GSS_SPNEGO_MECHANISM; */ #else - auth_data->mech_type = &gss_mech_spnego; + auth_data->mech_type = gss_mech_krb5;// &gss_mech_spnego; #endif auth_data->cred = GSS_C_NO_CREDENTIAL; @@ -300,7 +302,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, NULL, NULL); } else { maj = gss_acquire_cred(&min, auth_data->user_name, 0, - &mechOidSet, GSS_C_INITIATE, + GSS_C_NO_OID_SET,/*&mechOidSet,*/ GSS_C_INITIATE, &auth_data->cred, NULL, NULL); } @@ -308,7 +310,6 @@ krb5_negotiate_reply(struct smb2_context *smb2, krb5_set_gss_error(smb2, "gss_acquire_cred (client)", maj, min); return NULL; } - #ifndef __APPLE__ /* gss_set_neg_mechs is not defined on macOS/iOS. */ if (smb2->sec != SMB2_SEC_UNDEFINED) { gss_OID_set_desc wantMech; @@ -403,7 +404,7 @@ krb5_session_request(struct smb2_context *smb2, /* NOTE: this call is not async, a helper thread should be used if that * is an issue */ auth_data->req_flags = GSS_C_SEQUENCE_FLAG | GSS_C_MUTUAL_FLAG | - GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG; + /*GSS_C_DELEG_FLAG |*/ GSS_C_REPLAY_FLAG; maj = gss_init_sec_context(&min, auth_data->cred, &auth_data->context, auth_data->target_name, @@ -441,13 +442,27 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) uint32_t maj, min; char *spos; +#ifdef __APPLE__ + gss_OID mech = GSS_C_NO_OID; +#else + gss_OID mech = discard_const(gss_mech_krb5); +#endif + gss_OID_set mechs = GSS_C_NO_OID_SET; + gss_OID_set_desc mechlist; + + if (mech != GSS_C_NO_OID) { + mechlist.count = 1; + mechlist.elements = mech; + mechs = &mechlist; + } + auth_data = calloc(1, sizeof(struct private_auth_data)); if (auth_data == NULL) { smb2_set_error(smb2, "Failed to allocate private_auth_data"); return NULL; } auth_data->context = GSS_C_NO_CONTEXT; - auth_data->get_proxy_cred = 0; + auth_data->get_proxy_cred = server->proxy_authentication; /* strip any port off server host */ strncpy(user_principal, server->hostname, sizeof(user_principal) - 1); @@ -456,14 +471,11 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) if (spos) { *spos = '\0'; } - /* form spn cifs/hostname.domain */ - if (asprintf(&auth_data->g_server, "cifs@%s%s%s", user_principal, - server->domain[0] ? "." : "", - server->domain[0] ? server->domain : "") < 0) { + /* form spn cifs/hostname, gss will append realm itself */ + if (asprintf(&auth_data->g_server, "xcifs@%s", user_principal) < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } - name_buf.value = auth_data->g_server; name_buf.length = strlen(name_buf.value) + 1; @@ -475,42 +487,35 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) return NULL; } - if (!server->proxy_authentication) { - /* we, the server, will decrypt the client's ticket ourselfs + if (!auth_data->get_proxy_cred) { + /* we, the server, will decrypt the client's ticket ourselves * which means we will need the key for the service the client uses - * in our key-table (i.e. cifs/hosthame.domain) - * this path isn't tested completely yet + * in our key-table (i.e. cifs/hosthame@domain) + * you can sync the keytable with AD using "msktutil --auto-update" */ - #ifdef __APPLE__ - gss_OID mech = GSS_C_NO_OID; - #else - gss_OID mech = GSS_C_NO_OID; - #endif - gss_OID_set mechs = GSS_C_NO_OID_SET; - gss_OID_set_desc mechlist; - - if (mech != GSS_C_NO_OID) { - mechlist.count = 1; - mechlist.elements = mech; - mechs = &mechlist; - } - - maj = gss_acquire_cred(&min, auth_data->target_name, 0, - mechs, GSS_C_ACCEPT, - &auth_data->cred, - NULL, NULL); - if (maj != GSS_S_COMPLETE) { + maj = gss_acquire_cred(&min, auth_data->target_name, + GSS_C_INDEFINITE, + mechs, GSS_C_ACCEPT, + &auth_data->cred, + NULL, NULL); + if (GSS_ERROR(maj)) { krb5_set_gss_error(smb2, "gss_acquire_cred (server)", maj, min); return NULL; } } else { - /* passing no-credential to accept will actually work directly IFF - * the client has sent a delegated credential, but that's a special case - * so accept the client's cred blindly and use that to get a proxy cred - */ - auth_data->cred = GSS_C_NO_CREDENTIAL; - auth_data->get_proxy_cred = 1; + /* we will delegate to a real server, so attempt delegation + */ + maj = gss_acquire_cred(&min, GSS_C_NO_NAME, + GSS_C_INDEFINITE, + mechs, GSS_C_BOTH, + &auth_data->cred, + NULL, NULL); + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_acquire_cred (proxy)", maj, min); + return NULL; + } } + return auth_data; } @@ -521,22 +526,38 @@ krb5_session_reply(struct smb2_context *smb2, int *more_processing_needed) { uint32_t maj, min; - gss_name_t client; gss_OID doid; gss_buffer_desc *input_token = NULL; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; gss_cred_id_t ret_delegated_cred_handle; + OM_uint32 ret_timefor; - OM_uint32 ret_flags; + OM_uint32 ret_flags = 0; + + gss_OID mech = GSS_C_NO_OID; + int use_spnego = 0; + + gss_OID_set mechs = GSS_C_NO_OID_SET; + gss_OID_set_desc mechlist; + + mech = use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); *more_processing_needed = 0; + if (mech != GSS_C_NO_OID) { + mechlist.count = 1; + mechlist.elements = mech; + mechs = &mechlist; + } + if (auth_data->output_token.value) { gss_release_buffer(&min, &auth_data->output_token); auth_data->output_token.length = 0; auth_data->output_token.value = NULL; } + /* accept client context */ + token.value = buf; token.length = len; input_token = &token; @@ -546,7 +567,7 @@ krb5_session_reply(struct smb2_context *smb2, auth_data->cred, input_token, GSS_C_NO_CHANNEL_BINDINGS, - &client, + &auth_data->user_name, &doid, &auth_data->output_token, &ret_flags, @@ -563,13 +584,14 @@ krb5_session_reply(struct smb2_context *smb2, return -1; } - maj = gss_display_name(&min, client, &token, NULL); + /* client name is in user@domain format, so set that into our context + */ + maj = gss_display_name(&min, auth_data->user_name, &token, NULL); if (GSS_ERROR(maj)) { krb5_set_gss_error(smb2, "gss_display_name", maj, min); return -1; } - /* client name is in user@domain format, so set that into the client context */ if (token.length && (char*)token.value) { char *user; char *dpos; @@ -591,36 +613,56 @@ krb5_session_reply(struct smb2_context *smb2, smb2_set_user(smb2, user); smb2_set_domain(smb2, dpos ? dpos : ""); + printf("----- accepted sec client %s delegatable=%d %s\n", + user, ret_flags & GSS_C_DELEG_FLAG, ret_delegated_cred_handle ? "yes" : "no"); + free(user); } - if (auth_data->get_proxy_cred) { + if (auth_data->get_proxy_cred && auth_data->s4u2self) { #ifdef __APPLE__ if (!((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle))) { smb2_set_error(smb2, "Apple has no way to proxy credentials"); return -1; } #else - gss_cred_id_t proxy_cred; + /* Do an S4U2Self operation to validate client ticket + */ + gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; + gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; + + maj = gss_acquire_cred(&min, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + mechs, + GSS_C_INITIATE, + &impersonator_cred_handle, + NULL, + NULL); + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_acquire_cred (proxxy, def)", maj, min); + return maj; + } - /* not authenticating client ourselves, get a proxy credential from the - * client's credential to impersonate the client - */ maj = gss_acquire_cred_impersonate_name(&min, - &auth_data->cred, - our_service_name, - NULL, - GSS_C_NO_OID_SET, - GSS_C_INITIATE, - &proxy_cred, - NULL, - NULL); + impersonator_cred_handle, + auth_data->user_name, + GSS_C_INDEFINITE, + mechs, + GSS_C_INITIATE, + &user_cred_handle, + NULL, + NULL); + if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); return -1; } - ret_flags |= GSS_C_DELEG_FLAG; + (void)gss_release_cred(&min, &impersonator_cred_handle); + (void)gss_release_cred(&min, &user_cred_handle); + + /* maybe - use the user_cred_handle for the delegated handle? */ #endif } diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 49654e09..9d14e0fd 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -36,6 +36,7 @@ extern "C" { #import #else #include +#include static const gss_OID_desc gss_mech_spnego = { 6, "\x2b\x06\x01\x05\x05\x02" @@ -53,12 +54,15 @@ static const gss_OID_desc spnego_mech_ntlmssp = { struct private_auth_data { gss_ctx_id_t context; gss_cred_id_t cred; + gss_cred_id_t service_cred; gss_name_t user_name; gss_name_t target_name; gss_const_OID mech_type; uint32_t req_flags; gss_buffer_desc output_token; + gss_key_value_set_desc *store; int get_proxy_cred; + int s4u2self; char *g_server; }; From bbe6c090929c348b5e370ff58cbd564cd604da90 Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 24 Nov 2024 21:53:03 -0500 Subject: [PATCH 09/37] cleanup leaks --- lib/krb5-wrapper.c | 56 +++++++++++++++++++++++++--------------------- lib/krb5-wrapper.h | 1 + lib/libsmb2.c | 6 ++++- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 2da162cb..fe426010 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -81,6 +81,8 @@ krb5_free_auth_data(struct private_auth_data *auth) { uint32_t maj, min; + gss_release_buffer(&min, &auth->output_token); + /* Delete context */ if (auth->context) { maj = gss_delete_sec_context(&min, &auth->context, @@ -90,8 +92,6 @@ krb5_free_auth_data(struct private_auth_data *auth) } } - gss_release_buffer(&min, &auth->output_token); - if (auth->target_name) { gss_release_name(&min, &auth->target_name); } @@ -206,7 +206,6 @@ krb5_negotiate_reply(struct smb2_context *smb2, smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } - printf("target=%s=\n", auth_data->g_server); target.value = auth_data->g_server; target.length = strlen(auth_data->g_server); @@ -222,19 +221,22 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* if there is a delegated credential in the context, just use that */ if (smb2->cred_handle) { - printf("delhand\n"); auth_data->cred = smb2->cred_handle; smb2->cred_handle = NULL; return auth_data; } + /* using cached-creds with a memory-cache means the password supplied as + * a parameter is used to setup the ticket, as opposed to having to do + * a "kinit user" in the environment. this cache does not persist + */ if (smb2->use_cached_creds) { - memset(&user_principal[0], 0, 2048); - if (sprintf(&user_principal[0], "%s@%s", user_name, domain) < 0) { - smb2_set_error(smb2, "Failed to allocate user principal"); + memset(&user_principal[0], 0, sizeof(user_principal)); + if (snprintf(&user_principal[0], sizeof(user_principal), + "%s@%s", user_name, domain) < 0) { + smb2_set_error(smb2, "Failed to generate user principal"); return NULL; } - user.value = discard_const(user_principal); user.length = strlen(user_principal); } else { @@ -254,16 +256,17 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* TODO: the proper mechanism (SPNEGO vs NTLM vs KRB5) should be * selected based on the SMB negotiation flags */ #ifdef __APPLE__ - auth_data->mech_type = GSS_KRB5_MECHANISM; /* GSS_SPNEGO_MECHANISM; */ + auth_data->mech_type = auth_data->use_spenego ? GSS_SPNEGO_MECHANISM : GSS_KRB5_MECHANISM; #else - auth_data->mech_type = gss_mech_krb5;// &gss_mech_spnego; + auth_data->mech_type = auth_data->use_spnego ? &gss_mech_spnego : gss_mech_krb5; #endif - auth_data->cred = GSS_C_NO_CREDENTIAL; /* Create creds for the user */ mechOidSet.count = 1; mechOidSet.elements = discard_const(auth_data->mech_type); + auth_data->cred = GSS_C_NO_CREDENTIAL; + if (smb2->use_cached_creds) { krb5_error_code ret = 0; const char *cname = NULL; @@ -302,8 +305,8 @@ krb5_negotiate_reply(struct smb2_context *smb2, NULL, NULL); } else { maj = gss_acquire_cred(&min, auth_data->user_name, 0, - GSS_C_NO_OID_SET,/*&mechOidSet,*/ GSS_C_INITIATE, - &auth_data->cred, NULL, NULL); + &mechOidSet, GSS_C_INITIATE, &auth_data->cred, + NULL, NULL); } if (maj != GSS_S_COMPLETE) { @@ -311,7 +314,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } #ifndef __APPLE__ /* gss_set_neg_mechs is not defined on macOS/iOS. */ - if (smb2->sec != SMB2_SEC_UNDEFINED) { + if (smb2->sec != SMB2_SEC_UNDEFINED && !smb2->use_cached_creds) { gss_OID_set_desc wantMech; wantMech.count = 1; @@ -403,8 +406,12 @@ krb5_session_request(struct smb2_context *smb2, /* TODO return -errno instead of just -1 */ /* NOTE: this call is not async, a helper thread should be used if that * is an issue */ - auth_data->req_flags = GSS_C_SEQUENCE_FLAG | GSS_C_MUTUAL_FLAG | - /*GSS_C_DELEG_FLAG |*/ GSS_C_REPLAY_FLAG; + auth_data->req_flags = GSS_C_SEQUENCE_FLAG + | GSS_C_MUTUAL_FLAG + /* setting this flag gives a delegatable ticket + * without server doing s4u2proxy */ + /*| GSS_C_DELEG_FLAG */ + | GSS_C_REPLAY_FLAG; maj = gss_init_sec_context(&min, auth_data->cred, &auth_data->context, auth_data->target_name, @@ -490,7 +497,7 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) if (!auth_data->get_proxy_cred) { /* we, the server, will decrypt the client's ticket ourselves * which means we will need the key for the service the client uses - * in our key-table (i.e. cifs/hosthame@domain) + * for us in our key-table (i.e. cifs/hosthame@domain) * you can sync the keytable with AD using "msktutil --auto-update" */ maj = gss_acquire_cred(&min, auth_data->target_name, @@ -534,15 +541,11 @@ krb5_session_reply(struct smb2_context *smb2, OM_uint32 ret_timefor; OM_uint32 ret_flags = 0; - gss_OID mech = GSS_C_NO_OID; - int use_spnego = 0; - + gss_OID mech; gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; - mech = use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); - - *more_processing_needed = 0; + mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); if (mech != GSS_C_NO_OID) { mechlist.count = 1; @@ -550,14 +553,16 @@ krb5_session_reply(struct smb2_context *smb2, mechs = &mechlist; } + *more_processing_needed = 0; + if (auth_data->output_token.value) { gss_release_buffer(&min, &auth_data->output_token); auth_data->output_token.length = 0; auth_data->output_token.value = NULL; } - /* accept client context */ - + /* accept client context + */ token.value = buf; token.length = len; input_token = &token; @@ -616,6 +621,7 @@ krb5_session_reply(struct smb2_context *smb2, printf("----- accepted sec client %s delegatable=%d %s\n", user, ret_flags & GSS_C_DELEG_FLAG, ret_delegated_cred_handle ? "yes" : "no"); + gss_release_buffer(&min, &token); free(user); } diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 9d14e0fd..2bc069d1 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -63,6 +63,7 @@ struct private_auth_data { gss_key_value_set_desc *store; int get_proxy_cred; int s4u2self; + int use_spnego; char *g_server; }; diff --git a/lib/libsmb2.c b/lib/libsmb2.c index 50e7fcc9..210aa7bd 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -3609,12 +3609,16 @@ smb2_negotiate_request_cb(struct smb2_context *smb2, int status, void *command_d int d; int dialect_index; struct smb2_timeval now; - /*void *auth_data;*/ memset(&rep, 0, sizeof(rep)); memset(&err, 0, sizeof(err)); smb2_set_error(smb2, ""); + if (status != SMB2_STATUS_SUCCESS) { + /* context is being destroyed */ + return; + } + /* negotiate highest version in request dialects */ switch (smb2->version) { case SMB2_VERSION_ANY: From 01c7d29c2196f1c5049126d61c2bf90d3d63d38c Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 24 Nov 2024 22:55:41 -0500 Subject: [PATCH 10/37] use cifs now --- lib/krb5-wrapper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index fe426010..b39b21c4 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -479,7 +479,7 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) *spos = '\0'; } /* form spn cifs/hostname, gss will append realm itself */ - if (asprintf(&auth_data->g_server, "xcifs@%s", user_principal) < 0) { + if (asprintf(&auth_data->g_server, "cifs@%s", user_principal) < 0) { smb2_set_error(smb2, "Failed to allocate server string"); return NULL; } From 15eb47e775a6b4da59891f01afae13d0b62c25e1 Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 24 Nov 2024 23:01:07 -0500 Subject: [PATCH 11/37] apple works for delegated client creds --- lib/krb5-wrapper.c | 29 +++++++++++++---------------- lib/krb5-wrapper.h | 1 - 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index b39b21c4..65a5abee 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -256,7 +256,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* TODO: the proper mechanism (SPNEGO vs NTLM vs KRB5) should be * selected based on the SMB negotiation flags */ #ifdef __APPLE__ - auth_data->mech_type = auth_data->use_spenego ? GSS_SPNEGO_MECHANISM : GSS_KRB5_MECHANISM; + auth_data->mech_type = auth_data->use_spnego ? GSS_SPNEGO_MECHANISM : GSS_KRB5_MECHANISM; #else auth_data->mech_type = auth_data->use_spnego ? &gss_mech_spnego : gss_mech_krb5; #endif @@ -541,18 +541,6 @@ krb5_session_reply(struct smb2_context *smb2, OM_uint32 ret_timefor; OM_uint32 ret_flags = 0; - gss_OID mech; - gss_OID_set mechs = GSS_C_NO_OID_SET; - gss_OID_set_desc mechlist; - - mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); - - if (mech != GSS_C_NO_OID) { - mechlist.count = 1; - mechlist.elements = mech; - mechs = &mechlist; - } - *more_processing_needed = 0; if (auth_data->output_token.value) { @@ -618,9 +606,6 @@ krb5_session_reply(struct smb2_context *smb2, smb2_set_user(smb2, user); smb2_set_domain(smb2, dpos ? dpos : ""); - printf("----- accepted sec client %s delegatable=%d %s\n", - user, ret_flags & GSS_C_DELEG_FLAG, ret_delegated_cred_handle ? "yes" : "no"); - gss_release_buffer(&min, &token); free(user); } @@ -632,6 +617,18 @@ krb5_session_reply(struct smb2_context *smb2, return -1; } #else + gss_OID mech; + gss_OID_set mechs = GSS_C_NO_OID_SET; + gss_OID_set_desc mechlist; + + mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); + + if (mech != GSS_C_NO_OID) { + mechlist.count = 1; + mechlist.elements = mech; + mechs = &mechlist; + } + /* Do an S4U2Self operation to validate client ticket */ gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 2bc069d1..3b8193d7 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -60,7 +60,6 @@ struct private_auth_data { gss_const_OID mech_type; uint32_t req_flags; gss_buffer_desc output_token; - gss_key_value_set_desc *store; int get_proxy_cred; int s4u2self; int use_spnego; From 8701fc558a3d073b2b7707347b3cf5c5ab042743 Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 25 Nov 2024 14:50:26 -0500 Subject: [PATCH 12/37] fix impersonate name --- lib/krb5-wrapper.c | 184 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 44 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 65a5abee..f0bd1981 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -314,7 +314,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } #ifndef __APPLE__ /* gss_set_neg_mechs is not defined on macOS/iOS. */ - if (smb2->sec != SMB2_SEC_UNDEFINED && !smb2->use_cached_creds) { + if (smb2->sec != SMB2_SEC_UNDEFINED && 0) { gss_OID_set_desc wantMech; wantMech.count = 1; @@ -440,6 +440,86 @@ krb5_session_request(struct smb2_context *smb2, return 0; } +static OM_uint32 +establish_contexts(struct smb2_context *smb2, + gss_OID imech, gss_cred_id_t icred, gss_cred_id_t acred, + gss_name_t tname, OM_uint32 flags, gss_ctx_id_t *ictx, + gss_ctx_id_t *actx, + gss_name_t *src_name, gss_OID *amech, + gss_cred_id_t *deleg_cred) +{ + OM_uint32 minor, imaj, amaj; + gss_buffer_desc itok, atok; + + *ictx = *actx = GSS_C_NO_CONTEXT; + imaj = amaj = GSS_S_CONTINUE_NEEDED; + itok.value = atok.value = NULL; + itok.length = atok.length = 0; + while (1) { + (void)gss_release_buffer(&minor, &itok); + imaj = gss_init_sec_context(&minor, icred, ictx, tname, imech, flags, + GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, &itok, + NULL, NULL); + if (GSS_ERROR(imaj)) { + krb5_set_gss_error(smb2, "gss_init_sec_context (est)", imaj, minor); + break; + } + if (amaj == GSS_S_COMPLETE) + break; + + (void)gss_release_buffer(&minor, &atok); + amaj = gss_accept_sec_context(&minor, actx, acred, &itok, GSS_C_NO_CHANNEL_BINDINGS, + src_name, amech, &atok, NULL, NULL, + deleg_cred); + if (GSS_ERROR(amaj)) { + krb5_set_gss_error(smb2, "gss_accept_sec_context (est)", amaj, minor); + break; + } + (void)gss_release_buffer(&minor, &itok); + if (imaj == GSS_S_COMPLETE) + break; + } + + if (imaj != GSS_S_COMPLETE || amaj != GSS_S_COMPLETE) + printf("One side wants to continue after the other is done"); + + (void)gss_release_buffer(&minor, &itok); + (void)gss_release_buffer(&minor, &atok); + return imaj; +} + +static OM_uint32 +init_accept_sec_context(struct smb2_context *smb2, + gss_OID mech, + gss_cred_id_t claimant_cred_handle, + gss_cred_id_t verifier_cred_handle, + gss_cred_id_t *deleg_cred_handle) +{ + OM_uint32 major, minor, flags; + gss_name_t source_name = GSS_C_NO_NAME, target_name = GSS_C_NO_NAME; + gss_ctx_id_t initiator_context, acceptor_context; + + *deleg_cred_handle = GSS_C_NO_CREDENTIAL; + + major = gss_inquire_cred(&minor, verifier_cred_handle, &target_name, NULL, + NULL, NULL); + if (GSS_ERROR(major)) { + krb5_set_gss_error(smb2, "gss_inquire_cred (est)", major, minor); + } + + flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; + establish_contexts(smb2, gss_mech_krb5, claimant_cred_handle, verifier_cred_handle, + target_name, flags, &initiator_context, + &acceptor_context, &source_name, &mech, + deleg_cred_handle); + + (void)gss_release_name(&minor, &source_name); + (void)gss_release_name(&minor, &target_name); + (void)gss_delete_sec_context(&minor, &initiator_context, NULL); + (void)gss_delete_sec_context(&minor, &acceptor_context, NULL); + return major; +} + struct private_auth_data * krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) { @@ -521,6 +601,13 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) krb5_set_gss_error(smb2, "gss_acquire_cred (proxy)", maj, min); return NULL; } +#ifndef __APPLE__ + /* get a proxy ticket when user is known in session reply. note that this + * isnt absolutely required if the accept returns a delegatable cred but it + * does help find issues + */ + auth_data->s4u2self = 1; +#endif } return auth_data; @@ -607,68 +694,78 @@ krb5_session_reply(struct smb2_context *smb2, smb2_set_domain(smb2, dpos ? dpos : ""); gss_release_buffer(&min, &token); - free(user); - } - if (auth_data->get_proxy_cred && auth_data->s4u2self) { -#ifdef __APPLE__ - if (!((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle))) { - smb2_set_error(smb2, "Apple has no way to proxy credentials"); + /* strip domain off user for s4u2 */ + token.value = user; + token.length = strlen(user); + + gss_release_name(&min, &auth_data->user_name); + maj = gss_import_name(&min, &token, GSS_C_NT_USER_NAME, &auth_data->user_name); + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_import_name", maj, min); return -1; } -#else + + free(user); + } + + if (auth_data->get_proxy_cred) { + gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; + gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; gss_OID mech; gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); - if (mech != GSS_C_NO_OID) { mechlist.count = 1; mechlist.elements = mech; mechs = &mechlist; } - - /* Do an S4U2Self operation to validate client ticket - */ - gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; - gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; - - maj = gss_acquire_cred(&min, - GSS_C_NO_NAME, - GSS_C_INDEFINITE, - mechs, - GSS_C_INITIATE, - &impersonator_cred_handle, - NULL, - NULL); - if (GSS_ERROR(maj)) { - krb5_set_gss_error(smb2, "gss_acquire_cred (proxxy, def)", maj, min); - return maj; - } - - maj = gss_acquire_cred_impersonate_name(&min, - impersonator_cred_handle, - auth_data->user_name, - GSS_C_INDEFINITE, - mechs, - GSS_C_INITIATE, - &user_cred_handle, - NULL, - NULL); - + maj = init_accept_sec_context(smb2, mech, + user_cred_handle, auth_data->cred, + &ret_delegated_cred_handle); + (void)gss_release_cred(&min, &impersonator_cred_handle); + (void)gss_release_cred(&min, &user_cred_handle); if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); + krb5_set_gss_error(smb2, "init_accept_sec_context (proxy ctx)", maj, min); return -1; } - (void)gss_release_cred(&min, &impersonator_cred_handle); - (void)gss_release_cred(&min, &user_cred_handle); + if (auth_data->s4u2self) { +#ifdef __APPLE__ + if (!((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle))) { + smb2_set_error(smb2, "Apple has no way to proxy credentials"); + return -1; + } +#else + (void)gss_release_cred(&min, &ret_delegated_cred_handle); + + maj = gss_acquire_cred_impersonate_name(&min, + auth_data->cred, + auth_data->user_name, + GSS_C_INDEFINITE, + mechs, + GSS_C_INITIATE, + &user_cred_handle, + NULL, + NULL); + + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); + return -1; + } - /* maybe - use the user_cred_handle for the delegated handle? */ + maj = init_accept_sec_context(smb2, mech, + user_cred_handle, auth_data->cred, + &ret_delegated_cred_handle); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "init_accept_sec_context (impersonate)", maj, min); + return -1; + } #endif + } } - /* if the client credential is delegatable and the client is pass-through * save the client's cred for use in a proxy client */ @@ -680,7 +777,6 @@ krb5_session_reply(struct smb2_context *smb2, maj = gss_release_cred(&min, &ret_delegated_cred_handle); } } - return 0; } From a5a5ebbf361389731f2c787c1ce09805bcb71f2f Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 25 Nov 2024 19:12:08 -0500 Subject: [PATCH 13/37] fix leaks on client --- lib/krb5-wrapper.c | 155 ++++++++++++++++++++++++++++++++------------- lib/krb5-wrapper.h | 7 +- lib/libsmb2.c | 2 +- 3 files changed, 116 insertions(+), 48 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index f0bd1981..c9887784 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -83,6 +83,11 @@ krb5_free_auth_data(struct private_auth_data *auth) gss_release_buffer(&min, &auth->output_token); + if (auth->krb5_Ccache) { + krb5_cc_destroy(auth->krb5_cctx, auth->krb5_Ccache); + krb5_free_context(auth->krb5_cctx); + } + /* Delete context */ if (auth->context) { maj = gss_delete_sec_context(&min, &auth->context, @@ -270,21 +275,21 @@ krb5_negotiate_reply(struct smb2_context *smb2, if (smb2->use_cached_creds) { krb5_error_code ret = 0; const char *cname = NULL; - krb5_context krb5_cctx; - krb5_ccache krb5_Ccache; /* krb5 cache management */ - ret = krb5_init_context(&krb5_cctx); + ret = krb5_init_context(&auth_data->krb5_cctx); if (ret) { - smb2_set_error(smb2, "Failed to initialize krb5 context - %s", krb5_get_error_message(krb5_cctx, ret)); + smb2_set_error(smb2, "Failed to initialize krb5 context - %s", + krb5_get_error_message(auth_data->krb5_cctx, ret)); return NULL; } - ret = krb5_cc_new_unique(krb5_cctx, "MEMORY", NULL, &krb5_Ccache); + ret = krb5_cc_new_unique(auth_data->krb5_cctx, "MEMORY", NULL, &auth_data->krb5_Ccache); if (ret != 0) { - smb2_set_error(smb2, "Failed to create krb5 credentials cache - %s", krb5_get_error_message(krb5_cctx, ret)); + smb2_set_error(smb2, "Failed to create krb5 credentials cache - %s", + krb5_get_error_message(auth_data->krb5_cctx, ret)); return NULL; } - cname = krb5_cc_get_name(krb5_cctx, krb5_Ccache); + cname = krb5_cc_get_name(auth_data->krb5_cctx, auth_data->krb5_Ccache); if (cname == NULL) { smb2_set_error(smb2, "Failed to retrieve the credentials cache name"); return NULL; @@ -442,10 +447,13 @@ krb5_session_request(struct smb2_context *smb2, static OM_uint32 establish_contexts(struct smb2_context *smb2, - gss_OID imech, gss_cred_id_t icred, gss_cred_id_t acred, - gss_name_t tname, OM_uint32 flags, gss_ctx_id_t *ictx, + gss_OID imech, + gss_cred_id_t icred, gss_cred_id_t acred, + gss_name_t tname, OM_uint32 flags, + gss_ctx_id_t *ictx, gss_ctx_id_t *actx, - gss_name_t *src_name, gss_OID *amech, + gss_name_t *src_name, + gss_OID *amech, gss_cred_id_t *deleg_cred) { OM_uint32 minor, imaj, amaj; @@ -508,9 +516,13 @@ init_accept_sec_context(struct smb2_context *smb2, } flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; - establish_contexts(smb2, gss_mech_krb5, claimant_cred_handle, verifier_cred_handle, - target_name, flags, &initiator_context, - &acceptor_context, &source_name, &mech, + establish_contexts(smb2, mech, + claimant_cred_handle, verifier_cred_handle, + target_name, flags, + &initiator_context, + &acceptor_context, + &source_name, + NULL, deleg_cred_handle); (void)gss_release_name(&minor, &source_name); @@ -521,11 +533,12 @@ init_accept_sec_context(struct smb2_context *smb2, } struct private_auth_data * -krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) +krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2, const char *password) { struct private_auth_data *auth_data; char user_principal[1024]; gss_buffer_desc name_buf; + gss_cred_usage_t cred_usage; uint32_t maj, min; char *spos; @@ -574,33 +587,9 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) return NULL; } - if (!auth_data->get_proxy_cred) { - /* we, the server, will decrypt the client's ticket ourselves - * which means we will need the key for the service the client uses - * for us in our key-table (i.e. cifs/hosthame@domain) - * you can sync the keytable with AD using "msktutil --auto-update" - */ - maj = gss_acquire_cred(&min, auth_data->target_name, - GSS_C_INDEFINITE, - mechs, GSS_C_ACCEPT, - &auth_data->cred, - NULL, NULL); - if (GSS_ERROR(maj)) { - krb5_set_gss_error(smb2, "gss_acquire_cred (server)", maj, min); - return NULL; - } - } else { - /* we will delegate to a real server, so attempt delegation - */ - maj = gss_acquire_cred(&min, GSS_C_NO_NAME, - GSS_C_INDEFINITE, - mechs, GSS_C_BOTH, - &auth_data->cred, - NULL, NULL); - if (GSS_ERROR(maj)) { - krb5_set_gss_error(smb2, "gss_acquire_cred (proxy)", maj, min); - return NULL; - } + if (auth_data->get_proxy_cred) { + /* do constrained delegation */ + cred_usage = GSS_C_BOTH; #ifndef __APPLE__ /* get a proxy ticket when user is known in session reply. note that this * isnt absolutely required if the accept returns a delegatable cred but it @@ -608,6 +597,75 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2) */ auth_data->s4u2self = 1; #endif + } else { + cred_usage = GSS_C_ACCEPT; + } + + if (smb2->use_cached_creds) { + gss_buffer_desc passwd; + krb5_error_code ret = 0; + const char *cname = NULL; + krb5_principal service_princ = NULL; + krb5_context krb5_cctx; + krb5_ccache krb5_Ccache; + OM_uint32 xmin; + + /* krb5 cache management + * + * this doesnt work yet! TODO + */ + ret = krb5_init_context(&krb5_cctx); + if (ret) { + smb2_set_error(smb2, "Failed to initialize krb5 context - %s", krb5_get_error_message(krb5_cctx, ret)); + return NULL; + } + ret = krb5_parse_name(krb5_cctx, user_principal, &service_princ); + if (ret != 0) { + smb2_set_error(smb2, "Failed to parse principal- %s", krb5_get_error_message(krb5_cctx, ret)); + return NULL; + } + + ret = krb5_cc_new_unique(krb5_cctx, "MEMORY", NULL, &krb5_Ccache); + if (ret != 0) { + smb2_set_error(smb2, "Failed to create krb5 credentials cache - %s", krb5_get_error_message(krb5_cctx, ret)); + return NULL; + } + + ret = krb5_cc_initialize(krb5_cctx, krb5_Ccache, service_princ); + if (ret != 0) { + smb2_set_error(smb2, "Failed to initialze krb5 credentials cache - %s", krb5_get_error_message(krb5_cctx, ret)); + return NULL; + } + + cname = krb5_cc_get_name(krb5_cctx, krb5_Ccache); + if (cname == NULL) { + smb2_set_error(smb2, "Failed to retrieve the credentials cache name"); + return NULL; + } + + maj = gss_krb5_ccache_name(&min, cname, NULL); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_krb5_ccache_name", maj, min); + return NULL; + } + + passwd.value = strdup(password); + passwd.length = strlen(passwd.value); + + maj = gss_acquire_cred_with_password(&min, auth_data->target_name, &passwd, + GSS_C_INDEFINITE, + mechs, cred_usage, &auth_data->cred, + NULL, NULL); + (void)gss_release_buffer(&xmin, &passwd); + } else { + maj = gss_acquire_cred(&min, auth_data->target_name, + GSS_C_INDEFINITE, + mechs, cred_usage, &auth_data->cred, + NULL, NULL); + } + if (GSS_ERROR(maj)) { + krb5_set_gss_error(smb2, "gss_acquire_cred (server)", maj, min); + return NULL; } return auth_data; @@ -620,7 +678,6 @@ krb5_session_reply(struct smb2_context *smb2, int *more_processing_needed) { uint32_t maj, min; - gss_OID doid; gss_buffer_desc *input_token = NULL; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; gss_cred_id_t ret_delegated_cred_handle; @@ -648,7 +705,7 @@ krb5_session_reply(struct smb2_context *smb2, input_token, GSS_C_NO_CHANNEL_BINDINGS, &auth_data->user_name, - &doid, + NULL, &auth_data->output_token, &ret_flags, &ret_timefor, @@ -715,6 +772,7 @@ krb5_session_reply(struct smb2_context *smb2, gss_OID mech; gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; + OM_uint32 xmin; mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); if (mech != GSS_C_NO_OID) { @@ -722,11 +780,15 @@ krb5_session_reply(struct smb2_context *smb2, mechlist.elements = mech; mechs = &mechlist; } + (void)gss_release_cred(&min, &ret_delegated_cred_handle); + maj = init_accept_sec_context(smb2, mech, user_cred_handle, auth_data->cred, &ret_delegated_cred_handle); - (void)gss_release_cred(&min, &impersonator_cred_handle); - (void)gss_release_cred(&min, &user_cred_handle); + + (void)gss_release_cred(&xmin, &impersonator_cred_handle); + (void)gss_release_cred(&xmin, &user_cred_handle); + if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "init_accept_sec_context (proxy ctx)", maj, min); return -1; @@ -759,6 +821,9 @@ krb5_session_reply(struct smb2_context *smb2, maj = init_accept_sec_context(smb2, mech, user_cred_handle, auth_data->cred, &ret_delegated_cred_handle); + + (void)gss_release_cred(&xmin, &user_cred_handle); + if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "init_accept_sec_context (impersonate)", maj, min); return -1; diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 3b8193d7..09a49445 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -37,6 +37,7 @@ extern "C" { #else #include #include +#include static const gss_OID_desc gss_mech_spnego = { 6, "\x2b\x06\x01\x05\x05\x02" @@ -54,7 +55,6 @@ static const gss_OID_desc spnego_mech_ntlmssp = { struct private_auth_data { gss_ctx_id_t context; gss_cred_id_t cred; - gss_cred_id_t service_cred; gss_name_t user_name; gss_name_t target_name; gss_const_OID mech_type; @@ -64,6 +64,8 @@ struct private_auth_data { int s4u2self; int use_spnego; char *g_server; + krb5_context krb5_cctx; + krb5_ccache krb5_Ccache; }; void @@ -95,7 +97,8 @@ krb5_session_request(struct smb2_context *smb2, unsigned char *buf, int len); struct private_auth_data * -krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2); +krb5_init_server_cred(struct smb2_server *server, + struct smb2_context *smb2, const char *password); int krb5_session_reply(struct smb2_context *smb2, diff --git a/lib/libsmb2.c b/lib/libsmb2.c index 210aa7bd..b58fa7c8 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -3505,7 +3505,7 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma * setup once for all connections, but we do it each time to * simplify auth_data lifetime, perhaps TODO? */ - c_data->auth_data = krb5_init_server_cred(server, smb2); + c_data->auth_data = krb5_init_server_cred(server, smb2, NULL); if (!c_data->auth_data) { smb2_set_error(smb2, "can not init auth data %s", smb2_get_error(smb2)); smb2_close_context(smb2); From 190798bf171d5f626ca643180a5a8e9173b8c249 Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 25 Nov 2024 19:35:02 -0500 Subject: [PATCH 14/37] fix apple --- lib/krb5-wrapper.c | 15 +++++++++++---- lib/krb5-wrapper.h | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index c9887784..958fa8f4 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -57,7 +57,6 @@ #include #endif -#include #if __APPLE__ #include #else @@ -770,16 +769,24 @@ krb5_session_reply(struct smb2_context *smb2, gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; gss_OID mech; + OM_uint32 xmin; + + #ifdef __APPLE__ + mech = auth_data->use_spnego ? GSS_SPNEGO_MECHANISM : GSS_KRB5_MECHANISM; + #else gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; - OM_uint32 xmin; - mech = auth_data->use_spnego ? discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); + mech = auth_data->use_spnego ? + discard_const(&gss_mech_spnego) : discard_const(gss_mech_krb5); + if (mech != GSS_C_NO_OID) { mechlist.count = 1; - mechlist.elements = mech; + mechlist.elements = discard_const(mech); mechs = &mechlist; } + #endif + (void)gss_release_cred(&min, &ret_delegated_cred_handle); maj = init_accept_sec_context(smb2, mech, diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 09a49445..08ac9c83 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -32,12 +32,13 @@ extern "C" { #endif +#include + #if __APPLE__ #import #else #include #include -#include static const gss_OID_desc gss_mech_spnego = { 6, "\x2b\x06\x01\x05\x05\x02" From ef6a27ae902cc9c8d4d7dc7ba6563194d76a5704 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 10 Dec 2024 11:24:35 -0500 Subject: [PATCH 15/37] included api headers not local header --- include/libsmb2-private.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index e73457d1..92cce132 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -24,7 +24,11 @@ extern "C" { #endif #ifdef HAVE_LIBKRB5 -#include "krb5-wrapper.h" +#if __APPLE__ +#import +#else +#include +#endif #endif #define MIN(a,b) (((a)<(b))?(a):(b)) From 13b3dca1219284a2f0f8082b9e98b8c755a8441b Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 10 Dec 2024 11:31:23 -0500 Subject: [PATCH 16/37] make compiler happier --- lib/ntlmssp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index b528e35e..3f3162ba 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -1003,12 +1003,15 @@ ntlmssp_authenticate_blob(struct smb2_server *server, struct smb2_context *smb2, } if (auth_data->domain) { free(auth_data->domain); + auth_data->domain = NULL; } if (auth_data->user) { free(auth_data->user); + auth_data->user = NULL; } if (auth_data->workstation) { free(auth_data->workstation); + auth_data->workstation = NULL; } ntlmssp_get_utf16_field(input_buf, input_len, 4*7, &auth_data->domain); ntlmssp_get_utf16_field(input_buf, input_len, 4*9, &auth_data->user); From 032f96438ca21fc3a8d9612d7994076eb9036c96 Mon Sep 17 00:00:00 2001 From: bdodge Date: Tue, 10 Dec 2024 11:32:48 -0500 Subject: [PATCH 17/37] make ps2 compiler happier --- lib/ntlmssp.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index 3f3162ba..0758a870 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -121,22 +121,6 @@ struct auth_data { #define NTLMSSP_NEGOTIATE_OEM 0x00000002 #define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 -void -hex_print(const char *blurb, uint8_t *data, int len) -{ - printf("%s\n", blurb); - for (int i = 0; i < len; i++) { - printf("%02X ", data[i]); - if (!((i + 1) & 0xf)) { - printf("\n"); - } - } - if ((len & 0xf)) { - printf("\n"); - } - printf("\n"); -} - void ntlmssp_destroy_context(struct auth_data *auth) { From 6bf915364e035e9273fe6899fd762576a28e40b3 Mon Sep 17 00:00:00 2001 From: bdodge Date: Wed, 11 Dec 2024 16:49:00 -0500 Subject: [PATCH 18/37] fix oid parsing from ber, add spnego wrapping for auth blob, really decode ntlm challenge and add target-spn to ntlm auth --- lib/asn1-ber.c | 17 ++--- lib/libsmb2.c | 2 +- lib/ntlmssp.c | 150 +++++++++++++++++++++++++++++++++++++++++-- lib/spnego-wrapper.c | 59 ++++++++++++++++- lib/spnego-wrapper.h | 4 ++ 5 files changed, 218 insertions(+), 14 deletions(-) diff --git a/lib/asn1-ber.c b/lib/asn1-ber.c index b5912e9b..97d8ccf9 100644 --- a/lib/asn1-ber.c +++ b/lib/asn1-ber.c @@ -66,7 +66,7 @@ int asn1ber_next_byte(struct asn1ber_context *actx, uint8_t *outb) if (!actx || !actx->src || actx->src_tail >= actx->src_count) { return -1; } - + actx->src_tail++; *outb = actx->src[actx->src_tail - 1]; return 0; @@ -77,7 +77,7 @@ int asn1ber_out_byte(struct asn1ber_context *actx, uint8_t inb) if (!actx || !actx->dst || actx->dst_head >= actx->dst_size) { return -1; } - + actx->dst[actx->dst_head++] = inb; return 0; } @@ -87,7 +87,7 @@ int asn1ber_save_out_state(struct asn1ber_context *actx, int *out_pos) if (!out_pos || !actx || !actx->dst || actx->dst_head >= actx->dst_size) { return -1; } - + *out_pos = actx->dst_head; return 0; } @@ -101,7 +101,7 @@ int asn1ber_annotate_length(struct asn1ber_context *actx, int out_pos, int reser if (!actx || !actx->dst || actx->dst_head >= actx->dst_size) { return -1; } - + /* bytes added since out_pos snap-shot */ bytes_made = actx->dst_head - out_pos; bytes_made -= reserved; @@ -138,9 +138,9 @@ int asn1ber_length_from_ber(struct asn1ber_context *actx, uint32_t *len) if (b & 0x80) { uint32_t vallen; - + val = 0; - + /* length is number of bytes of length, not actual length */ vallen = b & 0x7F; if (vallen > 4) @@ -218,7 +218,7 @@ int asn1ber_request_from_ber(struct asn1ber_context *actx, ber_type_t *opcode, u { return result; } - + return 0; } @@ -598,6 +598,7 @@ int asn1ber_oid_from_ber(struct asn1ber_context *actx, struct asn1ber_oid_value return result; } oval = (beroid_type_t)(b & 0x7F); + vallen--; while ((b & 0x80) && (vallen > 0)) { @@ -608,9 +609,9 @@ int asn1ber_oid_from_ber(struct asn1ber_context *actx, struct asn1ber_oid_value } oval <<= 7; oval |= (beroid_type_t)(b & 0x7F); + vallen--; } ((beroid_type_t*)(oid->elements))[i++] = oval; - vallen--; } if (vallen != 0) { diff --git a/lib/libsmb2.c b/lib/libsmb2.c index b58fa7c8..d8d6dd6f 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -831,7 +831,7 @@ send_session_setup_request(struct smb2_context *smb2, if (smb2->sec == SMB2_SEC_NTLMSSP) { /* do this to wrap in spnego if needed */ - /*tlmssp_set_spnego_wrapping(c_data->auth_data, 1);*/ + /* ntlmssp_set_spnego_wrapping(c_data->auth_data, 1); */ if (ntlmssp_generate_blob(NULL, smb2, time(NULL), c_data->auth_data, buf, len, &req.security_buffer, diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index 0758a870..1f3202fb 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -266,13 +266,133 @@ ntlm_decode_challenge_message(struct smb2_context *smb2, struct auth_data *auth_ unsigned char *buf, size_t len) { if (buf && len > 0) { + int alloc_len; + uint32_t inoff; + uint16_t inlen; + uint32_t outoff; + uint16_t u16; + uint32_t u32; + uint16_t infolen; + uint16_t attr_len; + uint16_t attr_code; + struct smb2_utf16 *utf16_spn = NULL; + const uint32_t challenge_header_len = 56; + + /* form destination SPN in case server is checking */ + free(auth_data->target_info); + alloc_len = 32 + strlen(smb2->server); + auth_data->target_info = malloc(alloc_len); + if (!auth_data->target_info) { + return -1; + } + auth_data->target_info_len = snprintf((char*)auth_data->target_info, + alloc_len, "cifs/%s", smb2->server); + free(auth_data->ntlm_buf); auth_data->ntlm_len = len; - auth_data->ntlm_buf = malloc(auth_data->ntlm_len); + /* alloc enough to add a target-name attribute */ + alloc_len = auth_data->ntlm_len + 400; + auth_data->ntlm_buf = malloc(alloc_len); if (auth_data->ntlm_buf == NULL) { return -1; } - memcpy(auth_data->ntlm_buf, buf, auth_data->ntlm_len); + /* copy challenge message verbatim except payload */ + memcpy(auth_data->ntlm_buf, buf, challenge_header_len); + + /* payload pointer */ + outoff = challenge_header_len; + + /* copy target-name-fields payload from source to dest */ + memcpy(&u16, &buf[12], 2); + inlen = htole16(u16); + memcpy(&u32, &buf[16], 4); + inoff = htole32(u32); + + /* and update offset to where we put it (probably the same offset) */ + u32 = htole32(outoff); + memcpy(&auth_data->ntlm_buf[16], &u32, 4); + + if (inlen > 0 && inlen < len && (outoff + inlen) < alloc_len) { + memcpy(&auth_data->ntlm_buf[outoff], &buf[inoff], inlen); + outoff += inlen; + } + + memcpy(&u16, &buf[40], 2); + inlen = htole16(u16); + memcpy(&u32, &buf[44], 4); + inoff = htole32(u32); + + infolen = 0; + + if (inlen > 0 && inlen < len && (outoff + inlen) < alloc_len) { + /* back annotate target info field offset */ + u32 = htole16(outoff); + memcpy(&auth_data->ntlm_buf[44], &u32, 4); + + /* transcode target info fields, appending our target-name */ + while (inlen > 0) { + memcpy(&u16, &buf[inoff], 2); + attr_code = htole16(u16); + memcpy(&u16, &buf[inoff + 2], 2); + attr_len = htole16(u16); + + if (attr_len > inlen || (outoff + attr_len) > alloc_len) { + /* invalid, must be out of parse? */ + break; + } + + switch (attr_code) { + case 0: /* end of list */ + /* insert target-name */ + if (auth_data->target_info && auth_data->target_info_len) { + utf16_spn = smb2_utf8_to_utf16((char*)auth_data->target_info); + if (utf16_spn != NULL) { + attr_code = 0x9; /* target-name code */ + attr_len = utf16_spn->len * 2; + u16 = htole16(attr_code); + memcpy(&auth_data->ntlm_buf[outoff], &u16, 2); + u16 = htole16(attr_len); + memcpy(&auth_data->ntlm_buf[outoff + 2], &u16, 2); + outoff += 4; + memcpy(&auth_data->ntlm_buf[outoff], + (uint8_t*)utf16_spn->val, attr_len); + outoff += attr_len; + infolen += 4 + attr_len; + free(utf16_spn); + } + } + /* insert original end of list attr */ + u16 = 0; + memcpy(&auth_data->ntlm_buf[outoff], &u16, 2); + memcpy(&auth_data->ntlm_buf[outoff + 2], &u16, 2); + outoff += 4; + attr_code = 0; + attr_len = 0; + break; + default: + u16 = htole16(attr_code); + memcpy(&auth_data->ntlm_buf[outoff], &u16, 2); + u16 = htole16(attr_len); + memcpy(&auth_data->ntlm_buf[outoff + 2], &u16, 2); + outoff += 4; + memcpy(&auth_data->ntlm_buf[outoff], &buf[inoff + 4], attr_len); + outoff += attr_len; + break; + } + + inoff += 4 + attr_len; + inlen -= 4 + attr_len; + infolen += 4 + attr_len; + } + + /* back annotate target info field len */ + u16 = htole16(infolen); + memcpy(&auth_data->ntlm_buf[40], &u16, 2); + memcpy(&auth_data->ntlm_buf[42], &u16, 2); + + /* set the actual length of total message */ + auth_data->ntlm_len = outoff; + } return 0; } @@ -456,7 +576,6 @@ encode_ntlm_auth(struct smb2_context *smb2, time_t ti, anonymous = 1; goto encode; } - /* * Generate Concatenation of(NTProofStr, temp) */ @@ -485,6 +604,10 @@ encode_ntlm_auth(struct smb2_context *smb2, time_t ti, (u32 + server_name_len) > auth_data->ntlm_len) { goto finished; } + + /* note - this is the target-info sent in the challenge perhaps + * modified to add a target-name attribute + */ server_name_buf = (char *)&auth_data->ntlm_buf[u32]; if (encode_temp(auth_data, t, @@ -500,6 +623,7 @@ encode_ntlm_auth(struct smb2_context *smb2, time_t ti, NTChallengeResponse_buf = auth_data->buf; NTChallengeResponse_len = (unsigned int)auth_data->len; + auth_data->buf = NULL; auth_data->len = 0; auth_data->allocated = 0; @@ -614,6 +738,13 @@ encode_ntlm_auth(struct smb2_context *smb2, time_t ti, u32 = htole32(u32); encoder(&u32, 4, auth_data); + /* version - 8 bytes of 0 */ + u32 = 0; + encoder(&u32, 4, auth_data); + encoder(&u32, 4, auth_data); + + /* MIC - 16 byte message integrity code (after windows 2003) */ + if (!anonymous) { /* append domain */ u32 = htole32((uint32_t)auth_data->len); @@ -923,7 +1054,18 @@ ntlmssp_generate_blob(struct smb2_server *server, struct smb2_context *smb2, tim smb2_set_error(smb2, "can not encode auth data"); return -1; } - /* TODO - spnego wrap auth? */ + if (auth_data->spnego_wrap) { + spnego_len = smb2_spnego_wrap_ntlmssp_auth(smb2, + auth_data->buf, auth_data->len, + (void*)&spnego_buf); + if (spnego_len < 0) { + smb2_set_error(smb2, "can not wrap auth result"); + return -1; + } + free(auth_data->buf); + auth_data->buf = spnego_buf; + auth_data->len = spnego_len; + } } else { smb2_set_error(smb2, "Unexpected NTLMSSP message %08X, wanted challenge", cmd); diff --git a/lib/spnego-wrapper.c b/lib/spnego-wrapper.c index 95a4d1c0..580c2f39 100644 --- a/lib/spnego-wrapper.c +++ b/lib/spnego-wrapper.c @@ -332,6 +332,61 @@ smb2_spnego_wrap_ntlmssp_challenge(struct smb2_context *smb2, const uint8_t *ntl return asn_encoder.dst_head; } +int +smb2_spnego_wrap_ntlmssp_auth(struct smb2_context *smb2, const uint8_t *ntlmssp_token, + const int token_len, void **neg_targ_token) +{ + struct asn1ber_context asn_encoder; + uint8_t *neg_token; + int alloc_len; + int pos[6]; + + alloc_len = 64 + 2 * token_len; + neg_token = calloc(1, alloc_len); + if (neg_token == NULL) { + smb2_set_error(smb2, "Failed to allocate spnego wrapper"); + return 0; + } + + memset(&asn_encoder, 0, sizeof(asn_encoder)); + asn_encoder.dst = neg_token; + asn_encoder.dst_size = alloc_len; + asn_encoder.dst_head = 0; + + asn1ber_ber_from_typecode(&asn_encoder, ASN1_CONTEXT(1)); /* A1 XX */ + /* save location of total length */ + asn1ber_save_out_state(&asn_encoder, &pos[0]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + asn1ber_ber_from_typecode(&asn_encoder, ASN1_SEQUENCE(0)); /* 30 YY */ + /* save location of sub length */ + asn1ber_save_out_state(&asn_encoder, &pos[1]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + /* negTokenTarg */ + /* ntlm service provider */ + asn1ber_ber_from_typecode(&asn_encoder, ASN1_CONTEXT(2)); /* A2 ZZ */ + /* save location of total length */ + asn1ber_save_out_state(&asn_encoder, &pos[2]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + asn1ber_ber_from_typecode(&asn_encoder, asnOCTET_STRING); /* 04 zz */ + /* save location of total length */ + asn1ber_save_out_state(&asn_encoder, &pos[3]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + memcpy(asn_encoder.dst + asn_encoder.dst_head, ntlmssp_token, token_len); + asn_encoder.dst_head += token_len; + + asn1ber_annotate_length(&asn_encoder, pos[3], 5); + asn1ber_annotate_length(&asn_encoder, pos[2], 5); + asn1ber_annotate_length(&asn_encoder, pos[1], 5); + asn1ber_annotate_length(&asn_encoder, pos[0], 5); + + *neg_targ_token = neg_token; + return asn_encoder.dst_head; +} + int smb2_spnego_wrap_authenticate_result(struct smb2_context *smb2, const int authorized_ok, void **blob) { @@ -431,13 +486,15 @@ smb2_spnego_unwrap_targ(struct smb2_context *smb2, const uint8_t *spnego, require_noerr(ret, fail); switch (typecode) { case ASN1_CONTEXT(0): + /* num mechs, or neg-result */ ret = asn1ber_uint32_from_ber(&asn_decoder, mechanisms); require_noerr(ret, fail); break; case ASN1_CONTEXT(1): - /* supported mechanism */ + /* a supported mechanism */ ret = asn1ber_oid_from_ber(&asn_decoder, &oid); require_noerr(ret, fail); + break; case ASN1_CONTEXT(2): /* response token */ require_typeandlen(&asn_decoder, asnOCTET_STRING, 8, fail); diff --git a/lib/spnego-wrapper.h b/lib/spnego-wrapper.h index a9ffd7aa..3fe9e493 100644 --- a/lib/spnego-wrapper.h +++ b/lib/spnego-wrapper.h @@ -47,6 +47,10 @@ int smb2_spnego_wrap_ntlmssp_challenge(struct smb2_context *smb2, const uint8_t *ntlmssp_token, const int token_len, void **neg_targ_token); +int smb2_spnego_wrap_ntlmssp_auth(struct smb2_context *smb2, + const uint8_t *ntlmssp_token, + const int token_len, void **neg_targ_token); + int smb2_spnego_wrap_authenticate_result(struct smb2_context *smb2, const int authorized_ok, void **blob); From ac4b3e80d7b97c797c0517266161c3d7220c8251 Mon Sep 17 00:00:00 2001 From: bdodge Date: Thu, 12 Dec 2024 13:49:16 -0500 Subject: [PATCH 19/37] publicize sec type for api use --- include/libsmb2-private.h | 6 ------ include/smb2/libsmb2.h | 6 ++++++ lib/spnego-wrapper.c | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index 92cce132..e7743d5e 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -110,12 +110,6 @@ enum smb2_recv_state { SMB2_RECV_TRFM, }; -enum smb2_sec { - SMB2_SEC_UNDEFINED = 0, - SMB2_SEC_NTLMSSP, - SMB2_SEC_KRB5, -}; - /* current tree id stack, note: index 0 in the stack is not used */ #define SMB2_MAX_TREE_NESTING 32 diff --git a/include/smb2/libsmb2.h b/include/smb2/libsmb2.h index 81a6b60c..9d16d34f 100644 --- a/include/smb2/libsmb2.h +++ b/include/smb2/libsmb2.h @@ -354,6 +354,12 @@ void smb2_set_seal(struct smb2_context *smb2, int val); */ void smb2_set_sign(struct smb2_context *smb2, int val); +enum smb2_sec { + SMB2_SEC_UNDEFINED = 0, + SMB2_SEC_NTLMSSP, + SMB2_SEC_KRB5, +}; + /* * Set authentication method. * SMB2_SEC_UNDEFINED (use KRB if available or NTLM if not) diff --git a/lib/spnego-wrapper.c b/lib/spnego-wrapper.c index 580c2f39..abf131a0 100644 --- a/lib/spnego-wrapper.c +++ b/lib/spnego-wrapper.c @@ -560,6 +560,7 @@ smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, const uint8_t *spnego, } } /* mech token */ + require_typeandlen(&asn_decoder, ASN1_CONTEXT(2), 10, fail); require_typeandlen(&asn_decoder, asnOCTET_STRING, 7, fail); *token = asn_decoder.src + asn_decoder.src_tail; if (mechanisms) { From 134bd88185cce51a7c47a69a4010ceeb29eb0300 Mon Sep 17 00:00:00 2001 From: bdodge Date: Thu, 12 Dec 2024 16:42:02 -0500 Subject: [PATCH 20/37] function to determine if gss-ntlmssp plugin is installed --- include/smb2/libsmb2.h | 1 + lib/krb5-wrapper.c | 52 +++++++++++++++++++++++++++++++++++++----- lib/krb5-wrapper.h | 3 +++ lib/libsmb2.c | 3 +++ 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/include/smb2/libsmb2.h b/include/smb2/libsmb2.h index 9d16d34f..ef76cf01 100644 --- a/include/smb2/libsmb2.h +++ b/include/smb2/libsmb2.h @@ -1302,6 +1302,7 @@ struct smb2_server { uint32_t max_write_size; int signing_enabled; int allow_anonymous; + int use_krb5_for_ntlmssp; /* this can be set non-0 to delegate client authentication to * another client and allow any authentication to this server */ int proxy_authentication; diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 958fa8f4..01b5d3aa 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -161,6 +161,8 @@ krb5_set_gss_error(struct smb2_context *smb2, char *func, char *err_min = display_status(GSS_C_MECH_CODE, min); if (smb2) { smb2_set_error(smb2, "%s: (%s, %s)", func, err_maj, err_min); + } else { + printf("%s %s\n", err_maj, err_min); } free(err_min); free(err_maj); @@ -318,7 +320,8 @@ krb5_negotiate_reply(struct smb2_context *smb2, return NULL; } #ifndef __APPLE__ /* gss_set_neg_mechs is not defined on macOS/iOS. */ - if (smb2->sec != SMB2_SEC_UNDEFINED && 0) { + #ifdef SMB2_USER_KRB5_FOR_NTLLM + if (smb2->sec != SMB2_SEC_UNDEFINED) { gss_OID_set_desc wantMech; wantMech.count = 1; @@ -335,6 +338,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, } } #endif + #endif if (nc_password) { free(nc_password); @@ -540,11 +544,14 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2, con gss_cred_usage_t cred_usage; uint32_t maj, min; char *spos; - -#ifdef __APPLE__ gss_OID mech = GSS_C_NO_OID; -#else - gss_OID mech = discard_const(gss_mech_krb5); + +#ifndef __APPLE__ + if (smb2->sec != SMB2_SEC_KRB5) { + mech = discard_const(gss_mech_krb5); + } else { + mech = GSS_C_NO_OID; + } #endif gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; @@ -657,7 +664,8 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2, con NULL, NULL); (void)gss_release_buffer(&xmin, &passwd); } else { - maj = gss_acquire_cred(&min, auth_data->target_name, + maj = gss_acquire_cred(&min, + smb2->sec == SMB2_SEC_NTLMSSP ? GSS_C_NO_NAME : auth_data->target_name, GSS_C_INDEFINITE, mechs, cred_usage, &auth_data->cred, NULL, NULL); @@ -864,4 +872,36 @@ krb5_get_output_token_buffer(struct private_auth_data *auth_data) return auth_data->output_token.value; } +int +krb5_can_do_ntlmssp(void) +{ +#ifndef __APPLE__ + gss_OID_set mech_attrs; + gss_OID_set known_mech_attrs; + uint32_t maj, min; + + gss_OID mech = discard_const(&spnego_mech_ntlmssp); + + /* if we can acquire an initiator context with an ntlmssp mechanism + * it means krb5 has the ntlmssp plugin installed and we can use + * krb5 for all authentication purposes + */ + maj = gss_inquire_attrs_for_mech(&min, + mech, + &mech_attrs, + &known_mech_attrs); + + if (GSS_ERROR(maj)) { + krb5_set_gss_error(NULL, "gss_inquire_attrs_for_mech (ntlmssp cap check)", maj, min); + return 0; + } + + gss_release_oid_set(&min, &mech_attrs); + gss_release_oid_set(&min, &known_mech_attrs); + return 1; +#else + return 0; +#endif +} + #endif /* HAVE_LIBKRB5 */ \ No newline at end of file diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 08ac9c83..996d51c8 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -111,6 +111,9 @@ void krb5_set_gss_error(struct smb2_context *smb2, char *func, uint32_t maj, uint32_t min); +int +krb5_can_do_ntlmssp(void); + #ifdef __cplusplus } #endif diff --git a/lib/libsmb2.c b/lib/libsmb2.c index d8d6dd6f..03c91ba7 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -3879,6 +3879,9 @@ int smb2_serve_port(struct smb2_server *server, const int max_connections, smb2_ } server->session_counter = 0x1234; +#ifdef HAVE_LIBKRB5 + server->use_krb5_for_ntlmssp = krb5_can_do_ntlmssp(); +#endif do { /* select on the file descriptors of all active client connections and our server socket for the first readable event From a1a173f7eada5097af63d3d2eb7e8a7b1c40f7b7 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 08:06:58 -0500 Subject: [PATCH 21/37] protect against setting null domain and workstation --- lib/init.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/init.c b/lib/init.c index 160ae4f9..a572bf8a 100644 --- a/lib/init.c +++ b/lib/init.c @@ -645,6 +645,10 @@ void smb2_set_domain(struct smb2_context *smb2, const char *domain) { if (smb2->domain) { free(discard_const(smb2->domain)); + smb2->domain = NULL; + } + if (domain == NULL) { + return; } smb2->domain = strdup(domain); } @@ -661,6 +665,10 @@ void smb2_set_workstation(struct smb2_context *smb2, const char *workstation) { if (smb2->workstation) { free(discard_const(smb2->workstation)); + smb2->workstation = NULL; + } + if (workstation == NULL) { + return; } smb2->workstation = strdup(workstation); } From 050bae24c62b1651256cdf208456f452be7ba2af Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 08:57:23 -0500 Subject: [PATCH 22/37] unwrap spnego with no token, fix oid from ber --- lib/asn1-ber.c | 2 +- lib/spnego-wrapper.c | 57 ++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/lib/asn1-ber.c b/lib/asn1-ber.c index 97d8ccf9..2f461bee 100644 --- a/lib/asn1-ber.c +++ b/lib/asn1-ber.c @@ -539,7 +539,7 @@ int asn1ber_oid_from_ber(struct asn1ber_context *actx, struct asn1ber_oid_value { int result; int i; - uint16_t oval; + beroid_type_t oval; uint32_t vallen; uint8_t b = 0; diff --git a/lib/spnego-wrapper.c b/lib/spnego-wrapper.c index abf131a0..e1b75f7c 100644 --- a/lib/spnego-wrapper.c +++ b/lib/spnego-wrapper.c @@ -233,22 +233,24 @@ int smb2_spnego_wrap_gssapi(struct smb2_context *smb2, /* end mech types */ asn1ber_annotate_length(&asn_encoder, pos[3], 5); - /* context 2 mech token */ - asn1ber_ber_from_typecode(&asn_encoder, ASN1_CONTEXT(2)); /* A2 yy */ - /* save location of total length */ - asn1ber_save_out_state(&asn_encoder, &pos[3]); - asn1ber_ber_reserve_length(&asn_encoder, 5); - - /* ntlmssp token */ - asn1ber_ber_from_typecode(&asn_encoder, asnOCTET_STRING); /* 04 nn */ - /* save location of total length */ - asn1ber_save_out_state(&asn_encoder, &pos[4]); - asn1ber_ber_reserve_length(&asn_encoder, 5); - - memcpy(asn_encoder.dst + asn_encoder.dst_head, ntlmssp_token, token_len); - asn_encoder.dst_head += token_len; - - asn1ber_annotate_length(&asn_encoder, pos[4], 5); + if (ntlmssp_token && token_len) { + /* context 2 mech token */ + asn1ber_ber_from_typecode(&asn_encoder, ASN1_CONTEXT(2)); /* A2 yy */ + /* save location of total length */ + asn1ber_save_out_state(&asn_encoder, &pos[3]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + /* ntlmssp token */ + asn1ber_ber_from_typecode(&asn_encoder, asnOCTET_STRING); /* 04 nn */ + /* save location of total length */ + asn1ber_save_out_state(&asn_encoder, &pos[4]); + asn1ber_ber_reserve_length(&asn_encoder, 5); + + memcpy(asn_encoder.dst + asn_encoder.dst_head, ntlmssp_token, token_len); + asn_encoder.dst_head += token_len; + + asn1ber_annotate_length(&asn_encoder, pos[4], 5); + } asn1ber_annotate_length(&asn_encoder, pos[3], 5); asn1ber_annotate_length(&asn_encoder, pos[2], 5); asn1ber_annotate_length(&asn_encoder, pos[1], 5); @@ -554,19 +556,28 @@ smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, const uint8_t *spnego, mech_bytes -= (asn_decoder.src_tail - decode_pos); if (!oid_compare(&oid, &oid_spnego_mech_krb5)) { mechs |= SPNEGO_MECHANISM_KRB5; - } - else if (!oid_compare(&oid, &oid_spnego_mech_ntlmssp)) { + } else if (!oid_compare(&oid, &oid_spnego_mech_ntlmssp)) { mechs |= SPNEGO_MECHANISM_NTLMSSP; } } - /* mech token */ - require_typeandlen(&asn_decoder, ASN1_CONTEXT(2), 10, fail); - require_typeandlen(&asn_decoder, asnOCTET_STRING, 7, fail); - *token = asn_decoder.src + asn_decoder.src_tail; if (mechanisms) { *mechanisms = mechs; } - return typelen; + /* mech token, if present, follows */ + if (token) { + *token = NULL; + typelen = 0; + if (asn_decoder.src_count > 2 && + asn_decoder.src[asn_decoder.src_tail] == ASN1_CONTEXT(2)) { + /* mech token, note we expect NTLMSSP (7 bytes) at least here */ + require_typeandlen(&asn_decoder, ASN1_CONTEXT(2), 10, fail); + require_typeandlen(&asn_decoder, asnOCTET_STRING, 7, fail); + *token = asn_decoder.src + asn_decoder.src_tail; + } + return typelen; + } else { + return 0; + } fail: if (!suppress_errors) { From 6d713dc534a3419aaead2d3c31691585b2ec9dfa Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 09:27:07 -0500 Subject: [PATCH 23/37] free auth-data on errors to no leak --- lib/krb5-wrapper.c | 70 +++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 01b5d3aa..6957c50e 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -196,6 +196,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, auth_data = calloc(1, sizeof(struct private_auth_data)); if (auth_data == NULL) { smb2_set_error(smb2, "Failed to allocate private_auth_data"); + krb5_free_auth_data(auth_data); return NULL; } auth_data->context = GSS_C_NO_CONTEXT; @@ -210,6 +211,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* form spn cifs/hostname.domain */ if (asprintf(&auth_data->g_server, "cifs@%s", user_principal) < 0) { smb2_set_error(smb2, "Failed to allocate server string"); + krb5_free_auth_data(auth_data); return NULL; } @@ -221,6 +223,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "gss_import_name", maj, min); + krb5_free_auth_data(auth_data); return NULL; } @@ -256,6 +259,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "gss_import_name", maj, min); + krb5_free_auth_data(auth_data); return NULL; } @@ -280,25 +284,28 @@ krb5_negotiate_reply(struct smb2_context *smb2, /* krb5 cache management */ ret = krb5_init_context(&auth_data->krb5_cctx); if (ret) { - smb2_set_error(smb2, "Failed to initialize krb5 context - %s", - krb5_get_error_message(auth_data->krb5_cctx, ret)); - return NULL; + smb2_set_error(smb2, "Failed to initialize krb5 context - %s", + krb5_get_error_message(auth_data->krb5_cctx, ret)); + return NULL; } ret = krb5_cc_new_unique(auth_data->krb5_cctx, "MEMORY", NULL, &auth_data->krb5_Ccache); if (ret != 0) { - smb2_set_error(smb2, "Failed to create krb5 credentials cache - %s", - krb5_get_error_message(auth_data->krb5_cctx, ret)); - return NULL; + smb2_set_error(smb2, "Failed to create krb5 credentials cache - %s", + krb5_get_error_message(auth_data->krb5_cctx, ret)); + krb5_free_auth_data(auth_data); + return NULL; } cname = krb5_cc_get_name(auth_data->krb5_cctx, auth_data->krb5_Ccache); if (cname == NULL) { - smb2_set_error(smb2, "Failed to retrieve the credentials cache name"); - return NULL; + smb2_set_error(smb2, "Failed to retrieve the credentials cache name"); + krb5_free_auth_data(auth_data); + return NULL; } maj = gss_krb5_ccache_name(&min, cname, NULL); if (maj != GSS_S_COMPLETE) { krb5_set_gss_error(smb2, "gss_krb5_ccache_name", maj, min); + krb5_free_auth_data(auth_data); return NULL; } @@ -316,8 +323,9 @@ krb5_negotiate_reply(struct smb2_context *smb2, } if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "gss_acquire_cred (client)", maj, min); - return NULL; + krb5_set_gss_error(smb2, "gss_acquire_cred (client)", maj, min); + krb5_free_auth_data(auth_data); + return NULL; } #ifndef __APPLE__ /* gss_set_neg_mechs is not defined on macOS/iOS. */ #ifdef SMB2_USER_KRB5_FOR_NTLLM @@ -334,6 +342,7 @@ krb5_negotiate_reply(struct smb2_context *smb2, maj = gss_set_neg_mechs(&min, auth_data->cred, &wantMech); if (GSS_ERROR(maj)) { krb5_set_gss_error(smb2, "gss_set_neg_mechs", maj, min); + krb5_free_auth_data(auth_data); return NULL; } } @@ -471,32 +480,29 @@ establish_contexts(struct smb2_context *smb2, imaj = gss_init_sec_context(&minor, icred, ictx, tname, imech, flags, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, &itok, NULL, NULL); - if (GSS_ERROR(imaj)) { - krb5_set_gss_error(smb2, "gss_init_sec_context (est)", imaj, minor); - break; - } - if (amaj == GSS_S_COMPLETE) - break; - - (void)gss_release_buffer(&minor, &atok); - amaj = gss_accept_sec_context(&minor, actx, acred, &itok, GSS_C_NO_CHANNEL_BINDINGS, - src_name, amech, &atok, NULL, NULL, - deleg_cred); - if (GSS_ERROR(amaj)) { - krb5_set_gss_error(smb2, "gss_accept_sec_context (est)", amaj, minor); - break; - } - (void)gss_release_buffer(&minor, &itok); - if (imaj == GSS_S_COMPLETE) - break; + if (GSS_ERROR(imaj)) { + krb5_set_gss_error(smb2, "gss_init_sec_context (est)", imaj, minor); + break; + } + if (amaj == GSS_S_COMPLETE) + break; + + (void)gss_release_buffer(&minor, &atok); + amaj = gss_accept_sec_context(&minor, actx, acred, &itok, GSS_C_NO_CHANNEL_BINDINGS, + src_name, amech, &atok, NULL, NULL, + deleg_cred); + if (GSS_ERROR(amaj)) { + krb5_set_gss_error(smb2, "gss_accept_sec_context (est)", amaj, minor); + break; + } + (void)gss_release_buffer(&minor, &itok); + if (imaj == GSS_S_COMPLETE) + break; } - if (imaj != GSS_S_COMPLETE || amaj != GSS_S_COMPLETE) - printf("One side wants to continue after the other is done"); - (void)gss_release_buffer(&minor, &itok); (void)gss_release_buffer(&minor, &atok); - return imaj; + return imaj | amaj; } static OM_uint32 From de84d82be689d12773745d6c47d5b19ad7fb7be4 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 09:27:31 -0500 Subject: [PATCH 24/37] detect ms-krb5 oid --- lib/spnego-wrapper.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/spnego-wrapper.c b/lib/spnego-wrapper.c index e1b75f7c..66e87aff 100644 --- a/lib/spnego-wrapper.c +++ b/lib/spnego-wrapper.c @@ -78,6 +78,11 @@ static const struct asn1ber_oid_value oid_spnego_mech_krb5 = { 7, { 1, 2, 840, 113554, 1, 2, 2 } }; +/* hack for older microsoft systems, note its the same as krb5 oid if only 16-bit elements */ +static const struct asn1ber_oid_value oid_spnego_mech_ms_krb5 = { + 7, { 1, 2, 840, 48018, 1, 2, 2 } +}; + static const struct asn1ber_oid_value oid_spnego_mech_ntlmssp = { 10, { 1, 3, 6, 1, 4, 1, 311, 2, 2, 10 } }; @@ -556,6 +561,8 @@ smb2_spnego_unwrap_gssapi(struct smb2_context *smb2, const uint8_t *spnego, mech_bytes -= (asn_decoder.src_tail - decode_pos); if (!oid_compare(&oid, &oid_spnego_mech_krb5)) { mechs |= SPNEGO_MECHANISM_KRB5; + } else if (!oid_compare(&oid, &oid_spnego_mech_ms_krb5)) { + mechs |= SPNEGO_MECHANISM_KRB5; } else if (!oid_compare(&oid, &oid_spnego_mech_ntlmssp)) { mechs |= SPNEGO_MECHANISM_NTLMSSP; } From 5885fa1eba42b6fad4693ac161d80baadd498f04 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 09:28:33 -0500 Subject: [PATCH 25/37] auto-set sec in client based on negprot reply blob if present, close context on krb auth errors in server --- lib/libsmb2.c | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/libsmb2.c b/lib/libsmb2.c index 03c91ba7..a1f3f938 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -872,6 +872,7 @@ negotiate_cb(struct smb2_context *smb2, int status, { struct connect_data *c_data = private_data; struct smb2_negotiate_reply *rep = command_data; + uint32_t spnego_mechs; int ret; smb3_update_preauth_hash(smb2, smb2->in.niov - 1, &smb2->in.iov[1]); @@ -931,6 +932,33 @@ negotiate_cb(struct smb2_context *smb2, int status, smb2->sign = 0; } + /* if there is a gssapi blob in the reply, parse it to determine which + * mechanisms are supported if caller hasn't explicitly set security + */ + if (smb2->sec == SMB2_SEC_UNDEFINED && + rep->security_buffer && rep->security_buffer_length) { + spnego_mechs = 0; + smb2_spnego_unwrap_gssapi(smb2, rep->security_buffer, + rep->security_buffer_length, 1, + NULL, &spnego_mechs); +#ifdef HAVE_LIBKRB5 + if (spnego_mechs & SPNEGO_MECHANISM_KRB5) { + smb2->sec = SMB2_SEC_KRB5; + } +#endif + if (smb2->sec == SMB2_SEC_UNDEFINED && + (spnego_mechs & SPNEGO_MECHANISM_NTLMSSP)) { + smb2->sec = SMB2_SEC_NTLMSSP; + } + } + if (smb2->sec == SMB2_SEC_UNDEFINED) { +#ifdef HAVE_LIBKRB5 + smb2->sec = SMB2_SEC_KRB5; +#else + smb2->sec = SMB2_SEC_NTLMSSP; +#endif + } + if (smb2->sec == SMB2_SEC_NTLMSSP) { c_data->auth_data = ntlmssp_init_context(smb2->user, smb2->password, @@ -1020,13 +1048,6 @@ connect_cb(struct smb2_context *smb2, int status, memcpy(req.client_guid, smb2_get_client_guid(smb2), SMB2_GUID_SIZE); - if (smb2->sec == SMB2_SEC_UNDEFINED) { -#ifdef HAVE_LIBKRB5 - smb2->sec = SMB2_SEC_KRB5; -#else - smb2->sec = SMB2_SEC_NTLMSSP; -#endif - } smb3_init_preauth_hash(smb2); pdu = smb2_cmd_negotiate_async(smb2, &req, negotiate_cb, c_data); if (pdu == NULL) { @@ -3518,6 +3539,7 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma req->security_buffer, req->security_buffer_length, &more_processing_needed)) { + smb2_close_context(smb2); return; } @@ -3545,6 +3567,7 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma smb2_set_error(smb2, "Signing required by server. Session " "Key is not available %s", smb2_get_error(smb2)); + smb2_close_context(smb2); return; } @@ -3564,9 +3587,10 @@ smb2_session_setup_request_cb(struct smb2_context *smb2, int status, void *comma if (!pdu) { pdu = smb2_cmd_session_setup_reply_async(smb2, &rep, NULL, cb_data); if (pdu == NULL) { + smb2_set_error(smb2, "can not alloc pdu for session setup reply"); + smb2_close_context(smb2); return; } - if (more_processing_needed) { pdu->header.status = SMB2_STATUS_MORE_PROCESSING_REQUIRED; } From 07583ef8bc369c13ce0f86496bf1f1cecc923888 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 13 Dec 2024 20:34:33 -0500 Subject: [PATCH 26/37] dont free parts of req unless handler doesn't handle req --- lib/libsmb2.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/libsmb2.c b/lib/libsmb2.c index a1f3f938..b2765ab0 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -2910,10 +2910,12 @@ smb2_create_request_cb(struct smb2_server *server, struct smb2_context *smb2, vo pdu = smb2_cmd_error_reply_async(smb2, &err, SMB2_CREATE, SMB2_STATUS_NOT_IMPLEMENTED, NULL, cb_data); } - if (req->name) { - smb2_free_data(smb2, discard_const(req->name)); - } if (pdu != NULL) { + if (req->name) { + /* this will get auto-free when context is closed + * if we dont do it here, so not required */ + smb2_free_data(smb2, discard_const(req->name)); + } smb2_queue_pdu(smb2, pdu); } } @@ -3205,10 +3207,12 @@ smb2_query_directory_request_cb(struct smb2_server *server, struct smb2_context pdu = smb2_cmd_query_directory_reply_async(smb2, req, &rep, NULL, cb_data); } } - if (req->name) { - smb2_free_data(smb2, discard_const(req->name)); - } if (pdu != NULL) { + if (req->name) { + /* this will get auto-free when context is closed + * if we dont do it here, so not required */ + smb2_free_data(smb2, discard_const(req->name)); + } smb2_queue_pdu(smb2, pdu); } } From 90eb4011a62d5ca91c4e00cd672aa8fe117034c8 Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 15 Dec 2024 19:16:38 -0500 Subject: [PATCH 27/37] sync fork to master --- .github/workflows/ccpp.yml | 22 +++++---- CMakeLists.txt | 49 ++++++++++---------- Xbox 360/libsmb2.vcxproj | 2 +- Xbox 360/libsmb2.vcxproj.filters | 6 +-- Xbox/libsmb2.vcproj | 10 ++-- configure.ac | 2 +- examples/Makefile.am | 2 - include/libsmb2-private.h | 18 +++++++ lib/Makefile.am | 2 +- lib/compat.h | 9 ++-- lib/libsmb2.c | 20 ++------ lib/ps2/imports.lst | 2 + lib/smb2-cmd-create.c | 2 +- lib/smb2-cmd-ioctl.c | 9 ++-- lib/smb2-cmd-read.c | 3 +- lib/smb2-data-file-info.c | 6 +-- lib/sync.c | 18 +++++-- packaging/RPM/libsmb2.spec.in | 4 +- tests/Makefile.am | 3 +- {examples => tests}/smb2-dcerpc-coder-test.c | 0 tests/test_900_dcerpc.sh | 10 ++++ utils/smb2-cp.c | 2 + utils/smb2-ls.c | 14 ++++-- 23 files changed, 128 insertions(+), 87 deletions(-) rename {examples => tests}/smb2-dcerpc-coder-test.c (100%) create mode 100755 tests/test_900_dcerpc.sh diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 9b8d318c..8e6a7312 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -106,7 +106,7 @@ jobs: build-ps4: name: PS4 - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: git checkout uses: actions/checkout@v4 @@ -114,13 +114,19 @@ jobs: fetch-depth: 0 - name: Create Build Environment - run: > - sudo apt update && - sudo apt install -y wget cmake git gettext smpq && - wget https://github.com/PacBrew/pacbrew-pacman/releases/download/v1.1/pacbrew-pacman-1.1.deb && - sudo dpkg -i pacbrew-pacman-1.1.deb && sudo pacbrew-pacman -Sy && - sudo pacbrew-pacman --noconfirm -S ps4-openorbis ps4-openorbis-portlibs - + run: | + sudo apt-get update && \ + sudo apt-get install -y pacman-package-manager wget cmake git gettext smpq && \ + sudo tee -a /etc/pacman.conf > /dev/null < - @@ -300,6 +299,7 @@ + diff --git a/Xbox 360/libsmb2.vcxproj.filters b/Xbox 360/libsmb2.vcxproj.filters index 8286fa71..fce34c59 100644 --- a/Xbox 360/libsmb2.vcxproj.filters +++ b/Xbox 360/libsmb2.vcxproj.filters @@ -129,9 +129,6 @@ lib - - lib - lib @@ -264,5 +261,8 @@ lib + + lib + \ No newline at end of file diff --git a/Xbox/libsmb2.vcproj b/Xbox/libsmb2.vcproj index 0f5f25c0..c37e4856 100644 --- a/Xbox/libsmb2.vcproj +++ b/Xbox/libsmb2.vcproj @@ -33,7 +33,7 @@ WarnAsError="TRUE" Detect64BitPortabilityProblems="FALSE" DebugInformationFormat="3" - CompileAs="1"/> + CompileAs="0"/> + CompileAs="0"/> - - @@ -369,6 +366,9 @@ + + diff --git a/configure.ac b/configure.ac index 6172e75d..4aeb02df 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([libsmb2],[4.0.0],[ronniesahlberg@gmail.com]) +AC_INIT([libsmb2],[6.0.0],[ronniesahlberg@gmail.com]) AC_PREREQ([2.71]) AC_CONFIG_HEADERS([config.h]) diff --git a/examples/Makefile.am b/examples/Makefile.am index b35d594b..86a0de23 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,5 +1,4 @@ noinst_PROGRAMS = smb2-cat-async smb2-cat-sync \ - smb2-dcerpc-coder-test \ smb2-ftruncate-sync \ smb2-ls-async \ smb2-put-async \ @@ -29,7 +28,6 @@ COMMON_LIBS = ../lib/libsmb2.la smb2_cat_async_LDADD = $(COMMON_LIBS) smb2_cat_sync_LDADD = $(COMMON_LIBS) smb2_ftruncate_sync_LDADD = $(COMMON_LIBS) -smb2_dcerpc_coder_test_LDADD = $(COMMON_LIBS) smb2_ls_async_LDADD = $(COMMON_LIBS) smb2_put_async_LDADD = $(COMMON_LIBS) smb2_put_sync_LDADD = $(COMMON_LIBS) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index e7743d5e..8039a384 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -321,6 +321,24 @@ struct smb2_pdu { time_t timeout; }; +struct smb2_dirent_internal { + struct smb2_dirent_internal *next; + struct smb2dirent dirent; +}; + +struct smb2dir { + struct smb2dir *next; + smb2_command_cb cb; + void (*free_cb_data)(void *); + void *cb_data; + smb2_file_id file_id; + + struct smb2_dirent_internal *entries; + struct smb2_dirent_internal *current_entry; + int index; +}; + + #define smb2_is_server(ctx) ((ctx)->owning_server != NULL) void smb2_set_nterror(struct smb2_context *smb2, int nterror, diff --git a/lib/Makefile.am b/lib/Makefile.am index fd63f600..2b59c78b 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -73,7 +73,7 @@ libsmb2_la_SOURCES = \ unicode.c \ usha.c -SOCURRENT=4 +SOCURRENT=6 SOREVISION=0 SOAGE=0 libsmb2_la_LDFLAGS = \ diff --git a/lib/compat.h b/lib/compat.h index dd9b8d90..825a4617 100644 --- a/lib/compat.h +++ b/lib/compat.h @@ -634,6 +634,7 @@ struct iovec { #ifndef __NDS__ #include #endif +#if !defined(HAVE_SOCKADDR_STORAGE) struct sockaddr_storage { #ifdef HAVE_SOCKADDR_SA_LEN unsigned char ss_len; @@ -641,6 +642,7 @@ struct sockaddr_storage { unsigned char ss_family; unsigned char fill[127]; }; +#endif struct addrinfo { int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ @@ -653,10 +655,6 @@ struct addrinfo { struct addrinfo *ai_next; /* next structure in linked list */ }; -#ifdef __NDS__ -typedef int socklen_t; -#endif - #endif #define sockaddr_in6 sockaddr_in #else @@ -732,6 +730,9 @@ void smb2_freeaddrinfo(struct addrinfo *res); #define setsockopt net_setsockopt s32 getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); #define select net_select +#define accept net_accept +#define listen net_listen +#define bind net_bind #endif struct pollfd { diff --git a/lib/libsmb2.c b/lib/libsmb2.c index b2765ab0..a3db6c37 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -157,22 +157,6 @@ struct connect_data { struct smb2_server *server_context; }; -struct smb2_dirent_internal { - struct smb2_dirent_internal *next; - struct smb2dirent dirent; -}; - -struct smb2dir { - struct smb2dir *next; - smb2_command_cb cb; - void *cb_data; - smb2_file_id file_id; - - struct smb2_dirent_internal *entries; - struct smb2_dirent_internal *current_entry; - int index; -}; - struct smb2fh { struct smb2fh *next; smb2_command_cb cb; @@ -227,7 +211,9 @@ free_smb2dir(struct smb2_context *smb2, struct smb2dir *dir) free(dir->entries); dir->entries = e; } - free(dir->cb_data); + if (dir->free_cb_data) { + dir->free_cb_data(dir->cb_data); + } free(dir); } diff --git a/lib/ps2/imports.lst b/lib/ps2/imports.lst index ed5201df..84ed8e9b 100644 --- a/lib/ps2/imports.lst +++ b/lib/ps2/imports.lst @@ -79,4 +79,6 @@ sysclib_IMPORTS_end stdio_IMPORTS_start I_printf +I_putchar +I_puts stdio_IMPORTS_end diff --git a/lib/smb2-cmd-create.c b/lib/smb2-cmd-create.c index 6a91fb98..9748634f 100644 --- a/lib/smb2-cmd-create.c +++ b/lib/smb2-cmd-create.c @@ -442,7 +442,7 @@ int smb2_process_create_request_variable(struct smb2_context *smb2, struct smb2_pdu *pdu) { - struct smb2_create_request *req = (struct smb2_create_request*)pdu->payload;; + struct smb2_create_request *req = (struct smb2_create_request*)pdu->payload; struct smb2_iovec *iov = &smb2->in.iov[smb2->in.niov - 1]; uint32_t offset; void *ptr; diff --git a/lib/smb2-cmd-ioctl.c b/lib/smb2-cmd-ioctl.c index 23c42491..b6a2f3fd 100644 --- a/lib/smb2-cmd-ioctl.c +++ b/lib/smb2-cmd-ioctl.c @@ -387,6 +387,10 @@ smb2_process_ioctl_request_variable(struct smb2_context *smb2, struct smb2_iovec *iov = &smb2->in.iov[smb2->in.niov - 1]; struct smb2_iovec vec; void *ptr = NULL; + struct smb2_ioctl_validate_negotiate_info *info; + /* this one is handled locally regardless of proxy or not */ + ptr = smb2_alloc_init(smb2, sizeof(struct smb2_ioctl_validate_negotiate_info)); + info = ptr; if (req->input_count > iov->len - IOVREQ_OFFSET) { return -EINVAL; @@ -397,11 +401,6 @@ smb2_process_ioctl_request_variable(struct smb2_context *smb2, switch (req->ctl_code) { case SMB2_FSCTL_VALIDATE_NEGOTIATE_INFO: - /* this one is handled locally regardless of proxy or not */ - ptr = smb2_alloc_init(smb2, - sizeof(struct smb2_ioctl_validate_negotiate_info)); - struct smb2_ioctl_validate_negotiate_info *info = ptr; - smb2_get_uint32(&vec, 0, &info->capabilities); memcpy(info->guid, &vec.buf[4], 16); smb2_get_uint16(&vec, 20, &info->security_mode); diff --git a/lib/smb2-cmd-read.c b/lib/smb2-cmd-read.c index e39678c8..598060bd 100644 --- a/lib/smb2-cmd-read.c +++ b/lib/smb2-cmd-read.c @@ -265,11 +265,12 @@ smb2_process_read_fixed(struct smb2_context *smb2, } static void free_read_reply(struct smb2_context *smb2, void * payload) { + struct smb2_read_reply *rep; if (payload == NULL) { return; } - struct smb2_read_reply *rep = (struct smb2_read_reply*)payload; + rep = (struct smb2_read_reply*)payload; if (rep->data_length != 0 && rep->data != NULL) { free(rep->data); } diff --git a/lib/smb2-data-file-info.c b/lib/smb2-data-file-info.c index 638e3eeb..7b921ffa 100644 --- a/lib/smb2-data-file-info.c +++ b/lib/smb2-data-file-info.c @@ -267,12 +267,12 @@ smb2_decode_file_network_open_info(struct smb2_context *smb2, struct smb2_file_network_open_info *fs, struct smb2_iovec *vec) { - if (vec->len < 56) { + uint64_t t; + + if (vec->len < 56) { return -1; } - uint64_t t; - smb2_get_uint64(vec, 0, &t); smb2_win_to_timeval(t, &fs->creation_time); diff --git a/lib/sync.c b/lib/sync.c index 5920f95e..1367758d 100644 --- a/lib/sync.c +++ b/lib/sync.c @@ -179,7 +179,9 @@ static void opendir_cb(struct smb2_context *smb2, int status, if (cb_data->status == SMB2_STATUS_CANCELLED) { return; } - + if (status) { + cb_data->status = status; + } cb_data->is_finished = 1; cb_data->ptr = command_data; } @@ -187,7 +189,7 @@ static void opendir_cb(struct smb2_context *smb2, int status, struct smb2dir *smb2_opendir(struct smb2_context *smb2, const char *path) { struct sync_cb_data *cb_data; - void *ptr; + struct smb2dir *dir; cb_data = calloc(1, sizeof(struct sync_cb_data)); if (cb_data == NULL) { @@ -195,7 +197,6 @@ struct smb2dir *smb2_opendir(struct smb2_context *smb2, const char *path) return NULL; } - /* smb2dir takes wnership of cb_data on success */ if (smb2_opendir_async(smb2, path, opendir_cb, cb_data) != 0) { smb2_set_error(smb2, "smb2_opendir_async failed"); @@ -205,11 +206,18 @@ struct smb2dir *smb2_opendir(struct smb2_context *smb2, const char *path) if (wait_for_reply(smb2, cb_data) < 0) { cb_data->status = SMB2_STATUS_CANCELLED; + free(cb_data); return NULL; } - ptr = cb_data->ptr; - return ptr; + dir = cb_data->ptr; + if (dir) { + /* Give ownership of cb_data to dir. It will be freed when dir is freed */ + dir->free_cb_data = free; + } else { + free(cb_data); + } + return dir; } /* diff --git a/packaging/RPM/libsmb2.spec.in b/packaging/RPM/libsmb2.spec.in index 990f81b9..50f92aba 100644 --- a/packaging/RPM/libsmb2.spec.in +++ b/packaging/RPM/libsmb2.spec.in @@ -90,10 +90,12 @@ development libraries for LibSMB2 %{_includedir}/smb2/smb2.h %{_includedir}/smb2/smb2-errors.h %{_libdir}/libsmb2.a -%{_libdir}/libsmb2.la %{_libdir}/pkgconfig/libsmb2.pc %changelog +* Wed Dec 11 2024 : Version 6.0.0 + - Major rewrites to DCE-RPC + - Initial support for building servers * Mon Jan 17 2022 : Version 4.0.0 - Add support for SMB3 encryption - Add support for Anonymous NTLMSSP logins diff --git a/tests/Makefile.am b/tests/Makefile.am index 18c29a58..fff6e731 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,7 +4,8 @@ AM_CPPFLAGS = -I${srcdir}/../include -I${srcdir}/../include/smb2 \ AM_CFLAGS = $(WARN_CFLAGS) LDADD = ../lib/libsmb2.la -noinst_PROGRAMS = prog_mkdir prog_rmdir prog_cat +noinst_PROGRAMS = prog_mkdir prog_rmdir prog_cat \ + smb2-dcerpc-coder-test EXTRA_PROGRAMS = ld_sockerr CLEANFILES = ld_sockerr.o ld_sockerr.so diff --git a/examples/smb2-dcerpc-coder-test.c b/tests/smb2-dcerpc-coder-test.c similarity index 100% rename from examples/smb2-dcerpc-coder-test.c rename to tests/smb2-dcerpc-coder-test.c diff --git a/tests/test_900_dcerpc.sh b/tests/test_900_dcerpc.sh new file mode 100755 index 00000000..5399738f --- /dev/null +++ b/tests/test_900_dcerpc.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. ./functions.sh + +echo "DCE/RPC coder tests" + +./smb2-dcerpc-coder-test || failure +success + +exit 0 diff --git a/utils/smb2-cp.c b/utils/smb2-cp.c index 56030e3d..ca9b77ba 100644 --- a/utils/smb2-cp.c +++ b/utils/smb2-cp.c @@ -16,7 +16,9 @@ */ #define _FILE_OFFSET_BITS 64 +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include #if !defined(__amigaos4__) && !defined(__AMIGA__) && !defined(__AROS__) diff --git a/utils/smb2-ls.c b/utils/smb2-ls.c index 3d973346..5f89ee6e 100644 --- a/utils/smb2-ls.c +++ b/utils/smb2-ls.c @@ -11,7 +11,9 @@ Redistribution and use in source and binary forms, with or without modification, THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include #if !defined(__amigaos4__) && !defined(__AMIGA__) && !defined(__AROS__) @@ -100,11 +102,17 @@ int main(int argc, char *argv[]) printf("%-20s %-9s %15"PRIu64" %s", ent->name, type, ent->st.smb2_size, asctime(localtime(&t))); if (ent->st.smb2_type == SMB2_TYPE_LINK) { char buf[256]; - + if (url->path && url->path[0]) { - asprintf(&link, "%s/%s", url->path, ent->name); + if (asprintf(&link, "%s/%s", url->path, ent->name) < 0) { + printf("asprintf failed\n"); + goto out_disconnect; + } } else { - asprintf(&link, "%s", ent->name); + if (asprintf(&link, "%s", ent->name) < 0) { + printf("asprintf failed\n"); + goto out_disconnect; + } } if (smb2_readlink(smb2, link, buf, 256) == 0) { printf(" -> [%s]\n", buf); From 798382c47be14cdef4419cff0190f63895981205 Mon Sep 17 00:00:00 2001 From: bdodge Date: Mon, 16 Dec 2024 07:14:05 -0500 Subject: [PATCH 28/37] make ci happier --- lib/libsmb2.c | 2 -- lib/ntlmssp.c | 7 ++----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/libsmb2.c b/lib/libsmb2.c index a3db6c37..88670a70 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -816,8 +816,6 @@ send_session_setup_request(struct smb2_context *smb2, req.security_mode = (uint8_t)smb2->security_mode; if (smb2->sec == SMB2_SEC_NTLMSSP) { - /* do this to wrap in spnego if needed */ - /* ntlmssp_set_spnego_wrapping(c_data->auth_data, 1); */ if (ntlmssp_generate_blob(NULL, smb2, time(NULL), c_data->auth_data, buf, len, &req.security_buffer, diff --git a/lib/ntlmssp.c b/lib/ntlmssp.c index 1f3202fb..c052c280 100644 --- a/lib/ntlmssp.c +++ b/lib/ntlmssp.c @@ -341,8 +341,7 @@ ntlm_decode_challenge_message(struct smb2_context *smb2, struct auth_data *auth_ break; } - switch (attr_code) { - case 0: /* end of list */ + if (attr_code == 0) { /* end of list */ /* insert target-name */ if (auth_data->target_info && auth_data->target_info_len) { utf16_spn = smb2_utf8_to_utf16((char*)auth_data->target_info); @@ -368,8 +367,7 @@ ntlm_decode_challenge_message(struct smb2_context *smb2, struct auth_data *auth_ outoff += 4; attr_code = 0; attr_len = 0; - break; - default: + } else { u16 = htole16(attr_code); memcpy(&auth_data->ntlm_buf[outoff], &u16, 2); u16 = htole16(attr_len); @@ -377,7 +375,6 @@ ntlm_decode_challenge_message(struct smb2_context *smb2, struct auth_data *auth_ outoff += 4; memcpy(&auth_data->ntlm_buf[outoff], &buf[inoff + 4], attr_len); outoff += attr_len; - break; } inoff += 4 + attr_len; From bef6076b75ba1861b7c58efdf593f830746e83d6 Mon Sep 17 00:00:00 2001 From: Dmitriy Savin Date: Tue, 17 Dec 2024 21:06:52 +0200 Subject: [PATCH 29/37] Add smb2_get_session_id / smb2_get_workstation helpers --- include/smb2/libsmb2.h | 21 +++++++++++++++++++++ lib/init.c | 8 ++++++++ lib/pdu.c | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/include/smb2/libsmb2.h b/include/smb2/libsmb2.h index f51ba969..fa8485a9 100644 --- a/include/smb2/libsmb2.h +++ b/include/smb2/libsmb2.h @@ -382,6 +382,18 @@ void smb2_set_user(struct smb2_context *smb2, const char *user); */ const char *smb2_get_user(struct smb2_context *smb2); +/* + * Get the workstation associated with a context. + * returns NULL if none + */ +const char *smb2_get_workstation(struct smb2_context *smb2); + +/* + * Get the domain associated with a context. + * returns NULL if none + */ +const char *smb2_get_domain(struct smb2_context *smb2); + /* * Set the password that we will try to authenticate as. * This function is only needed when libsmb2 is built --without-libkrb5 @@ -570,6 +582,15 @@ struct smb2_pdu; int smb2_get_tree_id_for_pdu(struct smb2_context *smb2, struct smb2_pdu *pdu, uint32_t *tree_id); int smb2_set_tree_id_for_pdu(struct smb2_context *smb2, struct smb2_pdu *pdu, uint32_t tree_id); +/* + * Get session id + * + * Returns: + * 0 : OK + * -errno : + */ +int smb2_get_session_id(struct smb2_context *smb2, uint64_t *session_id); + /* * This function returns a description of the last encountered error. */ diff --git a/lib/init.c b/lib/init.c index a572bf8a..588d5f77 100644 --- a/lib/init.c +++ b/lib/init.c @@ -629,6 +629,14 @@ const char *smb2_get_user(struct smb2_context *smb2) return NULL; } +const char *smb2_get_workstation(struct smb2_context *smb2) +{ + if (smb2 && smb2->workstation) { + return smb2->workstation; + } + return NULL; +} + void smb2_set_password(struct smb2_context *smb2, const char *password) { if (smb2->password) { diff --git a/lib/pdu.c b/lib/pdu.c index 7144b7f4..36a990c4 100644 --- a/lib/pdu.c +++ b/lib/pdu.c @@ -249,6 +249,13 @@ smb2_set_tree_id_for_pdu(struct smb2_context *smb2, struct smb2_pdu *pdu, uint32 return -1; } +int smb2_get_session_id(struct smb2_context* smb2, uint64_t *session_id) +{ + *session_id = smb2->session_id; + + return 0; +} + int smb2_connect_tree_id(struct smb2_context *smb2, uint32_t tree_id) { From d91e5e89108c9e722ac9eb185966f16e550f3e06 Mon Sep 17 00:00:00 2001 From: bdodge Date: Wed, 18 Dec 2024 08:14:31 -0500 Subject: [PATCH 30/37] fix offsets in write reqs --- lib/smb2-cmd-write.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/smb2-cmd-write.c b/lib/smb2-cmd-write.c index 25988c6a..a899e258 100644 --- a/lib/smb2-cmd-write.c +++ b/lib/smb2-cmd-write.c @@ -84,7 +84,7 @@ smb2_encode_write_request(struct smb2_context *smb2, smb2_set_uint32(iov, 32, req->channel); smb2_set_uint32(iov, 36, req->remaining_bytes); smb2_set_uint16(iov, 42, req->write_channel_info_length); - smb2_set_uint32(iov, 44, req->flags); + smb2_set_uint32(iov, 46, req->flags); if (req->write_channel_info_length > 0 && req->write_channel_info != NULL) { @@ -262,10 +262,11 @@ smb2_process_write_request_fixed(struct smb2_context *smb2, smb2_get_uint32(iov, 4, &req->length); smb2_get_uint64(iov, 8, &req->offset); memcpy(req->file_id, iov->buf + 16, SMB2_FD_SIZE); - smb2_get_uint32(iov, 24, &req->channel); - smb2_get_uint32(iov, 28, &req->remaining_bytes); - smb2_get_uint16(iov, 32, &req->write_channel_info_offset); - smb2_get_uint16(iov, 34, &req->write_channel_info_length); + smb2_get_uint32(iov, 32, &req->channel); + smb2_get_uint32(iov, 36, &req->remaining_bytes); + smb2_get_uint16(iov, 42, &req->write_channel_info_offset); + smb2_get_uint16(iov, 44, &req->write_channel_info_length); + smb2_get_uint32(iov, 46, &req->flags); req->buf = NULL; if (req->write_channel_info_length) { From 6c518d023f5941400792288b1c720dd5027a1157 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 20 Dec 2024 07:38:39 -0500 Subject: [PATCH 31/37] make sure async flag is set for pending status replies --- lib/pdu.c | 25 +++++++++++++++---------- lib/socket.c | 4 +++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/pdu.c b/lib/pdu.c index 36a990c4..c8bde482 100644 --- a/lib/pdu.c +++ b/lib/pdu.c @@ -348,7 +348,7 @@ smb2_free_pdu(struct smb2_context *smb2, struct smb2_pdu *pdu) if (pdu->free_payload != NULL) { pdu->free_payload(smb2, pdu->payload); } - + free(pdu->payload); free(pdu->crypt); free(pdu); @@ -584,9 +584,6 @@ smb2_correlate_reply(struct smb2_context *smb2, struct smb2_pdu *pdu) struct smb2_pdu *req_pdu; int ret = 0; - /* set reply flag */ - pdu->header.flags |= SMB2_FLAGS_SERVER_TO_REDIR; - req_pdu = smb2_find_pdu_by_command(smb2, pdu->header.command); if (req_pdu == NULL) { if (pdu->header.command != SMB2_OPLOCK_BREAK) { @@ -601,8 +598,6 @@ smb2_correlate_reply(struct smb2_context *smb2, struct smb2_pdu *pdu) pdu->header.session_id = 0; } } else { - SMB2_LIST_REMOVE(&smb2->waitqueue, req_pdu); - pdu->header.credit_request_response = 64 + req_pdu->header.credit_charge; @@ -615,7 +610,12 @@ smb2_correlate_reply(struct smb2_context *smb2, struct smb2_pdu *pdu) if (pdu->header.command != SMB2_TREE_CONNECT) { pdu->header.sync.tree_id = req_pdu->header.sync.tree_id; } - smb2_free_pdu(smb2, req_pdu); + + /* leave pdu in waitqueue if this is an async reply */ + if (!(pdu->header.flags & SMB2_FLAGS_ASYNC_COMMAND)) { + SMB2_LIST_REMOVE(&smb2->waitqueue, req_pdu); + smb2_free_pdu(smb2, req_pdu); + } } return ret; } @@ -628,10 +628,15 @@ smb2_queue_pdu(struct smb2_context *smb2, struct smb2_pdu *pdu) /* Update all the PDU headers in this chain */ for (p = pdu; p; p = p->next_compound) { if (smb2_is_server(smb2)) { - if (!(pdu->header.flags & SMB2_FLAGS_ASYNC_COMMAND)) { - smb2_correlate_reply(smb2, p); - /* TODO - care about check reply failures? */ + /* set reply flag, servers will only reply */ + pdu->header.flags |= SMB2_FLAGS_SERVER_TO_REDIR; + + /* set async flag for status==pending */ + if (pdu->header.status == SMB2_STATUS_PENDING) { + pdu->header.flags |= SMB2_FLAGS_ASYNC_COMMAND; } + smb2_correlate_reply(smb2, p); + /* TODO - care about check reply failures? */ } smb2_encode_header(smb2, &p->out.iov[0], &p->header); if (smb2->sign || diff --git a/lib/socket.c b/lib/socket.c index 8d2f3810..eb073b93 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -690,7 +690,9 @@ static int smb2_read_data(struct smb2_context *smb2, read_func func, pdu = smb2_find_pdu(smb2, smb2->hdr.message_id); if (pdu == NULL) { smb2_set_error(smb2, "no matching PDU found"); - return -1; + /* ignore this error for now, it might be OK + * to not pass the pending reply along */ + /*return -1;*/ } else { From 3c3424148a190e31b471174b1d2cfe824207ff29 Mon Sep 17 00:00:00 2001 From: bdodge Date: Fri, 20 Dec 2024 07:47:52 -0500 Subject: [PATCH 32/37] fix format in example --- examples/smb2-lsa-lookupsids.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/smb2-lsa-lookupsids.c b/examples/smb2-lsa-lookupsids.c index 0356faaf..e692f667 100644 --- a/examples/smb2-lsa-lookupsids.c +++ b/examples/smb2-lsa-lookupsids.c @@ -53,7 +53,7 @@ void print_sid(RPC_SID *sid) ia <<= 8; ia |= sid->IdentifierAuthority[i]; } - printf("%ld", ia); + printf("%lld", ia); for (i = 0; i < sid->SubAuthorityCount; i++) { printf("-%d", sid->SubAuthority[i]); } @@ -63,7 +63,7 @@ void cl_cb(struct dcerpc_context *dce, int status, void *command_data, void *cb_data) { struct lsa_close_rep *rep = command_data; - + if (status) { dcerpc_free_data(dce, rep); printf("failed to close policy handle (%s) %s\n", @@ -287,6 +287,6 @@ int main(int argc, char *argv[]) smb2_disconnect_share(smb2); smb2_destroy_url(url); smb2_destroy_context(smb2); - + return 0; } From f459e48c10ed4e722cdc2bb116cfd33e84b870f9 Mon Sep 17 00:00:00 2001 From: bdodge Date: Sat, 21 Dec 2024 17:17:06 -0500 Subject: [PATCH 33/37] fix merge issues --- CMakeLists.txt | 10 ---------- lib/libsmb2.c | 3 --- 2 files changed, 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ada17603..4601a014 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,13 +67,9 @@ endif() find_package(LibKrb5) endif() elseif(IOS) -<<<<<<< HEAD - find_package(GSSAPI) -======= if (ENABLE_GSSAPI) find_package(GSSAPI) endif() ->>>>>>> upstream/master endif() if(NOT ESP_PLATFORM) @@ -104,11 +100,6 @@ endif() endif() if(CMAKE_SYSTEM_NAME MATCHES Linux) -<<<<<<< HEAD - set(core_DEPENDS ${LIBKRB5_LIBRARY} CACHE STRING "" FORCE) - elseif(IOS) - set(core_DEPENDS ${GSSAPI_LIBRARIES} CACHE STRING "" FORCE) -======= if (ENABLE_LIBKRB5) set(core_DEPENDS ${LIBKRB5_LIBRARY} CACHE STRING "" FORCE) endif() @@ -116,7 +107,6 @@ endif() if (ENABLE_GSSAPI) set(core_DEPENDS ${GSSAPI_LIBRARIES} CACHE STRING "" FORCE) endif() ->>>>>>> upstream/master endif() if(MSVC AND BUILD_SHARED_LIBS) diff --git a/lib/libsmb2.c b/lib/libsmb2.c index 92e3acf2..23c6f3fa 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -3906,9 +3906,6 @@ int smb2_serve_port(struct smb2_server *server, const int max_connections, smb2_ } server->session_counter = 0x1234; -#ifdef HAVE_LIBKRB5 - server->use_krb5_for_ntlmssp = krb5_can_do_ntlmssp(); -#endif do { /* select on the file descriptors of all active client connections and our server socket for the first readable event From 4a5fb4e77f11c0f4b614485e8e99a01cd4123ebf Mon Sep 17 00:00:00 2001 From: bdodge Date: Sat, 21 Dec 2024 17:26:43 -0500 Subject: [PATCH 34/37] direct include krb5 headers to avoud needing to include headers in source dir --- include/libsmb2-private.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/libsmb2-private.h b/include/libsmb2-private.h index e1914fc5..ee111cde 100644 --- a/include/libsmb2-private.h +++ b/include/libsmb2-private.h @@ -24,7 +24,14 @@ extern "C" { #endif #ifdef HAVE_LIBKRB5 -#include "krb5-wrapper.h" +#include + +#if __APPLE__ +#import +#else +#include +#include +#endif #endif #define MIN(a,b) (((a)<(b))?(a):(b)) From 195eb757e3891f2aaac8ace19a7c6ca92a9dce9c Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 29 Dec 2024 08:49:55 -0500 Subject: [PATCH 35/37] dont leak dir name --- lib/libsmb2.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/libsmb2.c b/lib/libsmb2.c index 23c6f3fa..4eef9373 100644 --- a/lib/libsmb2.c +++ b/lib/libsmb2.c @@ -3206,12 +3206,10 @@ smb2_query_directory_request_cb(struct smb2_server *server, struct smb2_context pdu = smb2_cmd_query_directory_reply_async(smb2, req, &rep, NULL, cb_data); } } + if (req->name) { + smb2_free_data(smb2, discard_const(req->name)); + } if (pdu != NULL) { - if (req->name) { - /* this will get auto-free when context is closed - * if we dont do it here, so not required */ - smb2_free_data(smb2, discard_const(req->name)); - } smb2_queue_pdu(smb2, pdu); } } @@ -3319,7 +3317,7 @@ smb2_general_client_request_cb(struct smb2_context *smb2, int status, void *comm smb2_close_context(smb2); return; } - if (status == SMB2_STATUS_CANCELLED) { + if (status == SMB2_STATUS_CANCELLED || status == SMB2_STATUS_SHUTDOWN) { return; } From 9a00899667a9376f0d8caf863a8b372b3b1d76ca Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 29 Dec 2024 09:03:36 -0500 Subject: [PATCH 36/37] fix offsets in write req --- lib/smb2-cmd-write.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/smb2-cmd-write.c b/lib/smb2-cmd-write.c index a899e258..2c5decf6 100644 --- a/lib/smb2-cmd-write.c +++ b/lib/smb2-cmd-write.c @@ -84,14 +84,14 @@ smb2_encode_write_request(struct smb2_context *smb2, smb2_set_uint32(iov, 32, req->channel); smb2_set_uint32(iov, 36, req->remaining_bytes); smb2_set_uint16(iov, 42, req->write_channel_info_length); - smb2_set_uint32(iov, 46, req->flags); + smb2_set_uint32(iov, 44, req->flags); if (req->write_channel_info_length > 0 && req->write_channel_info != NULL) { if (smb2->passthrough) { req->write_channel_info_offset = (SMB2_READ_REQUEST_SIZE & 0xfffffffe) + SMB2_HEADER_SIZE; - smb2_set_uint16(iov, 44, req->write_channel_info_offset); + smb2_set_uint16(iov, 40, req->write_channel_info_offset); len = PAD_TO_64BIT(req->write_channel_info_length); buf = malloc(len); @@ -264,9 +264,9 @@ smb2_process_write_request_fixed(struct smb2_context *smb2, memcpy(req->file_id, iov->buf + 16, SMB2_FD_SIZE); smb2_get_uint32(iov, 32, &req->channel); smb2_get_uint32(iov, 36, &req->remaining_bytes); - smb2_get_uint16(iov, 42, &req->write_channel_info_offset); - smb2_get_uint16(iov, 44, &req->write_channel_info_length); - smb2_get_uint32(iov, 46, &req->flags); + smb2_get_uint16(iov, 40, &req->write_channel_info_offset); + smb2_get_uint16(iov, 42, &req->write_channel_info_length); + smb2_get_uint32(iov, 44, &req->flags); req->buf = NULL; if (req->write_channel_info_length) { From 4eca7f18577c7ffd3e1d24a463ba35218b44d15d Mon Sep 17 00:00:00 2001 From: bdodge Date: Sun, 29 Dec 2024 10:42:08 -0500 Subject: [PATCH 37/37] simplify proxy credentials --- lib/krb5-wrapper.c | 77 +++++++++++++++------------------------------- lib/krb5-wrapper.h | 1 - 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/lib/krb5-wrapper.c b/lib/krb5-wrapper.c index 00cf3385..ccb83bfb 100644 --- a/lib/krb5-wrapper.c +++ b/lib/krb5-wrapper.c @@ -600,13 +600,6 @@ krb5_init_server_cred(struct smb2_server *server, struct smb2_context *smb2, con if (auth_data->get_proxy_cred) { /* do constrained delegation */ cred_usage = GSS_C_BOTH; -#ifndef __APPLE__ - /* set this non-0 to also get proxy ticket when user is known in - * session reply. note that this isnt required if the accept returns - * a delegatable cred but it might help find issues (or cause some) - */ - auth_data->s4u2self = 0; -#endif } else { cred_usage = GSS_C_ACCEPT; } @@ -777,15 +770,20 @@ krb5_session_reply(struct smb2_context *smb2, free(user); } - if (auth_data->get_proxy_cred) { - gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; + if (auth_data->get_proxy_cred && (!(ret_flags & GSS_C_DELEG_FLAG) || + (ret_delegated_cred_handle == GSS_C_NO_CREDENTIAL))) { gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; gss_OID mech; OM_uint32 xmin; - #ifdef __APPLE__ - mech = auth_data->use_spnego ? GSS_SPNEGO_MECHANISM : GSS_KRB5_MECHANISM; - #else + /* did not got a proxy credential when accepting the + * the client context, so attempt an s4u2self + * with user-name to get one + */ +#ifdef __APPLE__ + smb2_set_error(smb2, "Apple has no way to proxy credentials"); + return -1; +#else gss_OID_set mechs = GSS_C_NO_OID_SET; gss_OID_set_desc mechlist; @@ -797,58 +795,31 @@ krb5_session_reply(struct smb2_context *smb2, mechlist.elements = discard_const(mech); mechs = &mechlist; } - #endif - - (void)gss_release_cred(&min, &ret_delegated_cred_handle); + maj = gss_acquire_cred_impersonate_name(&min, + auth_data->cred, + auth_data->user_name, + GSS_C_INDEFINITE, + mechs, + GSS_C_INITIATE, + &user_cred_handle, + NULL, + NULL); + if (maj != GSS_S_COMPLETE) { + krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); + return -1; + } maj = init_accept_sec_context(smb2, mech, user_cred_handle, auth_data->cred, &ret_delegated_cred_handle); - (void)gss_release_cred(&xmin, &impersonator_cred_handle); (void)gss_release_cred(&xmin, &user_cred_handle); if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "init_accept_sec_context (proxy ctx)", maj, min); + krb5_set_gss_error(smb2, "init_accept_sec_context (impersonate)", maj, min); return -1; } - - if (auth_data->s4u2self) { -#ifdef __APPLE__ - if (!((ret_flags & GSS_C_DELEG_FLAG) && (ret_delegated_cred_handle))) { - smb2_set_error(smb2, "Apple has no way to proxy credentials"); - return -1; - } -#else - (void)gss_release_cred(&min, &ret_delegated_cred_handle); - - maj = gss_acquire_cred_impersonate_name(&min, - auth_data->cred, - auth_data->user_name, - GSS_C_INDEFINITE, - mechs, - GSS_C_INITIATE, - &user_cred_handle, - NULL, - NULL); - - if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "gss_acquire_cred_impersonate_name", maj, min); - return -1; - } - - maj = init_accept_sec_context(smb2, mech, - user_cred_handle, auth_data->cred, - &ret_delegated_cred_handle); - - (void)gss_release_cred(&xmin, &user_cred_handle); - - if (maj != GSS_S_COMPLETE) { - krb5_set_gss_error(smb2, "init_accept_sec_context (impersonate)", maj, min); - return -1; - } #endif - } } /* if the client credential is delegatable and the client is pass-through * save the client's cred for use in a proxy client diff --git a/lib/krb5-wrapper.h b/lib/krb5-wrapper.h index 996d51c8..f4adcee8 100644 --- a/lib/krb5-wrapper.h +++ b/lib/krb5-wrapper.h @@ -62,7 +62,6 @@ struct private_auth_data { uint32_t req_flags; gss_buffer_desc output_token; int get_proxy_cred; - int s4u2self; int use_spnego; char *g_server; krb5_context krb5_cctx;