|
@@ -0,0 +1,333 @@
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+// __________________ _________ _____ _____ .__ ._.
|
|
|
+// \______ \______ \ / _____/ / \ / _ \ |__| ____ | |
|
|
|
+// | | _/| | \ \_____ \ / \ / \ / /_\ \| _/ __ \ | |
|
|
|
+// | | \| ` \/ / Y \ / | | \ ___/ \|
|
|
|
+// |______ /_______ /_______ \____|__ / /\ \____|__ |__|\___ | __
|
|
|
+// \/ \/ \/ \/ )/ \/ \/ \/
|
|
|
+//
|
|
|
+// This file is part of libdsm. Copyright © 2014 VideoLabs SAS
|
|
|
+//
|
|
|
+// Author: Julien 'Lta' BALLET <contact@lta.io>
|
|
|
+//
|
|
|
+// This program is free software. It comes without any warranty, to the extent
|
|
|
+// permitted by applicable law. You can redistribute it and/or modify it under
|
|
|
+// the terms of the Do What The Fuck You Want To Public License, Version 2, as
|
|
|
+// published by Sam Hocevar. See the COPYING file for more details.
|
|
|
+//----------------------------------------------------------------------------
|
|
|
+
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <assert.h>
|
|
|
+
|
|
|
+#include "bdsm/debug.h"
|
|
|
+#include "bdsm/smb_session.h"
|
|
|
+#include "bdsm/smb_ntlm.h"
|
|
|
+#include "spnego/spnego_asn1.h"
|
|
|
+
|
|
|
+static const char spnego_oid[] = "1.3.6.1.5.5.2";
|
|
|
+static const char ntlmssp_oid[] = "1.3.6.1.4.1.311.2.2.10";
|
|
|
+
|
|
|
+static void asn1_display_error(const char *where, int errcode)
|
|
|
+{
|
|
|
+ BDSM_dbg("%s error: %s\n", where, asn1_strerror(errcode));
|
|
|
+}
|
|
|
+
|
|
|
+static int init_asn1(smb_session *s)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+
|
|
|
+ assert (s != NULL);
|
|
|
+
|
|
|
+ if (s->spnego.asn1_def != NULL)
|
|
|
+ return (1);
|
|
|
+
|
|
|
+ res = asn1_array2tree(spnego_asn1_conf, &s->spnego.asn1_def, NULL);
|
|
|
+ if (res != ASN1_SUCCESS)
|
|
|
+ {
|
|
|
+ asn1_display_error("init_asn1", res);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ BDSM_dbg("init_asn1: ASN.1 parser initialized\n");
|
|
|
+ return (1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void clean_asn1(smb_session *s)
|
|
|
+{
|
|
|
+ assert (s != NULL);
|
|
|
+
|
|
|
+ if (s->spnego.asn1_def != NULL)
|
|
|
+ asn1_delete_structure(&s->spnego.asn1_def);
|
|
|
+}
|
|
|
+
|
|
|
+static int negotiate(smb_session *s, const char *domain, const char *user)
|
|
|
+{
|
|
|
+ smb_message *msg = NULL;
|
|
|
+ smb_session_xsec_req *req = NULL;
|
|
|
+ ASN1_TYPE token;
|
|
|
+ void *ntlm;
|
|
|
+ size_t ntlm_size;
|
|
|
+ int res, der_size = 128;
|
|
|
+ char der[128], err_desc[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
|
|
|
+
|
|
|
+ msg = smb_message_new(SMB_CMD_SETUP, 512);
|
|
|
+ smb_message_set_default_flags(msg);
|
|
|
+ smb_message_set_andx_members(msg);
|
|
|
+ req = (smb_session_xsec_req *)msg->packet->payload;
|
|
|
+ msg->packet->header.mux_id = 1;
|
|
|
+
|
|
|
+ req->wct = 12;
|
|
|
+ req->max_buffer = SMB_IO_BUFSIZE;
|
|
|
+ req->mpx_count = 16; // XXX ?
|
|
|
+ req->vc_count = 1;
|
|
|
+ req->caps = s->srv.caps; // XXX caps & our_caps_mask
|
|
|
+ req->session_key = s->srv.session_key;
|
|
|
+
|
|
|
+ smb_message_advance(msg, sizeof(smb_session_xsec_req));
|
|
|
+
|
|
|
+
|
|
|
+ asn1_create_element(s->spnego.asn1_def, "SPNEGO.GSSAPIContextToken", &token);
|
|
|
+
|
|
|
+ res = asn1_write_value(token, "thisMech", spnego_oid, 1);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "spnego", "negTokenInit", 1);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "spnego.negTokenInit.mechTypes", "NEW", 1);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "spnego.negTokenInit.mechTypes.?1", ntlmssp_oid, 1);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "spnego.negTokenInit.reqFlags", NULL, 0);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "spnego.negTokenInit.mechListMIC", NULL, 0);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+
|
|
|
+ smb_ntlmssp_negotiate(domain, domain, &ntlm, &ntlm_size);
|
|
|
+ res = asn1_write_value(token, "spnego.negTokenInit.mechToken", ntlm, ntlm_size);
|
|
|
+ free(ntlm);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+
|
|
|
+ res = asn1_der_coding(token, "", der, &der_size, err_desc);
|
|
|
+ if (res != ASN1_SUCCESS)
|
|
|
+ {
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ BDSM_dbg("Encoding error: %s", err_desc);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ smb_message_append(msg, der, der_size);
|
|
|
+ smb_message_put_utf16(msg, "", SMB_OS, strlen(SMB_OS));
|
|
|
+ smb_message_put16(msg, 0);
|
|
|
+ smb_message_put_utf16(msg, "", SMB_LANMAN, strlen(SMB_LANMAN));
|
|
|
+ smb_message_put16(msg, 0);
|
|
|
+ smb_message_put16(msg, 0);
|
|
|
+
|
|
|
+ req->xsec_blob_size = der_size;
|
|
|
+ req->payload_size = msg->cursor - sizeof(smb_session_xsec_req);
|
|
|
+
|
|
|
+ asn1_delete_structure(&token);
|
|
|
+
|
|
|
+ if (!smb_session_send_msg(s, msg))
|
|
|
+ {
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ BDSM_dbg("Unable to send Session Setup AndX (NTLMSSP_NEGOTIATE) message\n");
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ return (1);
|
|
|
+
|
|
|
+ error:
|
|
|
+ asn1_display_error("smb_session_login negotiate()", res);
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+static int challenge(smb_session *s)
|
|
|
+{
|
|
|
+ char err_desc[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
|
|
|
+ char resp_token[256];
|
|
|
+ smb_message msg;
|
|
|
+ smb_session_xsec_resp *resp;
|
|
|
+ smb_ntlmssp_challenge *challenge;
|
|
|
+ ASN1_TYPE token;
|
|
|
+ int res, resp_token_size = 256;
|
|
|
+
|
|
|
+ assert (s != NULL);
|
|
|
+
|
|
|
+ if (smb_session_recv_msg(s, &msg) == 0)
|
|
|
+ {
|
|
|
+ BDSM_dbg("spnego challenge(): Unable to receive message\n");
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (msg.packet->header.status != NT_STATUS_MORE_PROCESSING_REQUIRED)
|
|
|
+ {
|
|
|
+ BDSM_dbg("spnego challenge(): Bad status (0x%x)\n",
|
|
|
+ msg.packet->header.status);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ resp = (smb_session_xsec_resp *)msg.packet->payload;
|
|
|
+
|
|
|
+ asn1_create_element(s->spnego.asn1_def, "SPNEGO.NegotiationToken",
|
|
|
+ &token);
|
|
|
+ res = asn1_der_decoding(&token, resp->payload, resp->xsec_blob_size,
|
|
|
+ err_desc);
|
|
|
+ if (res != ASN1_SUCCESS)
|
|
|
+ {
|
|
|
+ asn1_delete_structure(&token);
|
|
|
+ asn1_display_error("NegTokenResp parsing", res);
|
|
|
+ BDSM_dbg("Parsing error detail: %s\n", err_desc);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // XXX Check the value of "negTokenResp.negResult"
|
|
|
+
|
|
|
+ res = asn1_read_value(token, "negTokenResp.responseToken", resp_token,
|
|
|
+ &resp_token_size);
|
|
|
+ if (res != ASN1_SUCCESS)
|
|
|
+ {
|
|
|
+ asn1_delete_structure(&token);
|
|
|
+ asn1_display_error("NegTokenResp read responseToken", res);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // We got the server challenge, yeaaah.
|
|
|
+ challenge = (smb_ntlmssp_challenge *)resp_token;
|
|
|
+ s->xsec.tgt_info_sz = challenge->tgt_len;
|
|
|
+ s->xsec.tgt_info = malloc(s->xsec.tgt_info_sz);
|
|
|
+ assert(s->xsec.tgt_info != NULL);
|
|
|
+ memcpy(s->xsec.tgt_info,
|
|
|
+ challenge->data + challenge->tgt_offset - sizeof(smb_ntlmssp_challenge),
|
|
|
+ s->xsec.tgt_info_sz);
|
|
|
+ //s->srv.challenge = *((uint64_t *)(resp_token + 24));
|
|
|
+ s->srv.challenge = challenge->challenge;
|
|
|
+ s->srv.uid = msg.packet->header.uid;
|
|
|
+
|
|
|
+ fprintf(stderr, "Server challenge is 0x%lx\n", s->srv.challenge);
|
|
|
+
|
|
|
+ return (1);
|
|
|
+}
|
|
|
+
|
|
|
+static int auth(smb_session *s, const char *domain, const char *user,
|
|
|
+ const char *password)
|
|
|
+{
|
|
|
+ smb_message *msg = NULL, resp;
|
|
|
+ smb_session_xsec_req *req = NULL;
|
|
|
+ ASN1_TYPE token;
|
|
|
+ void *ntlm;
|
|
|
+ size_t ntlm_size;
|
|
|
+ int res, der_size = 512;
|
|
|
+ char der[512], err_desc[ASN1_MAX_ERROR_DESCRIPTION_SIZE];
|
|
|
+
|
|
|
+ msg = smb_message_new(SMB_CMD_SETUP, 512);
|
|
|
+ smb_message_set_default_flags(msg);
|
|
|
+ smb_message_set_andx_members(msg);
|
|
|
+ msg->packet->header.mux_id = 2;
|
|
|
+ req = (smb_session_xsec_req *)msg->packet->payload;
|
|
|
+
|
|
|
+ req->wct = 12;
|
|
|
+ req->max_buffer = SMB_IO_BUFSIZE;
|
|
|
+ req->mpx_count = 16; // XXX ?
|
|
|
+ req->vc_count = 1;
|
|
|
+ req->caps = s->srv.caps; // XXX caps & our_caps_mask
|
|
|
+ req->session_key = s->srv.session_key;
|
|
|
+
|
|
|
+ smb_message_advance(msg, sizeof(smb_session_xsec_req));
|
|
|
+
|
|
|
+
|
|
|
+ asn1_create_element(s->spnego.asn1_def, "SPNEGO.NegotiationToken", &token);
|
|
|
+
|
|
|
+ // Select a response message type
|
|
|
+ res = asn1_write_value(token, "", "negTokenResp", 1);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+
|
|
|
+ // Delete all optionnal field except 'ResponseToken'
|
|
|
+ res = asn1_write_value(token, "negTokenResp.negResult", NULL, 0);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "negTokenResp.supportedMech", NULL, 0);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+ res = asn1_write_value(token, "negTokenResp.mechListMIC", NULL, 0);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+
|
|
|
+
|
|
|
+ smb_ntlmssp_response(s->srv.challenge, s->srv.ts - 4200, domain, domain, user,
|
|
|
+ password, s->xsec.tgt_info, s->xsec.tgt_info_sz,
|
|
|
+ &ntlm, &ntlm_size);
|
|
|
+ res = asn1_write_value(token, "negTokenResp.responseToken", ntlm, ntlm_size);
|
|
|
+ if (res != ASN1_SUCCESS) goto error;
|
|
|
+
|
|
|
+ res = asn1_der_coding(token, "", der, &der_size, err_desc);
|
|
|
+ if (res != ASN1_SUCCESS)
|
|
|
+ {
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ BDSM_dbg("Encoding error: %s", err_desc);
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ smb_message_append(msg, der, der_size);
|
|
|
+ if (msg->cursor % 2)
|
|
|
+ smb_message_put8(msg, 0);
|
|
|
+ smb_message_put_utf16(msg, "", SMB_OS, strlen(SMB_OS));
|
|
|
+ smb_message_put16(msg, 0);
|
|
|
+ smb_message_put_utf16(msg, "", SMB_LANMAN, strlen(SMB_LANMAN));
|
|
|
+ smb_message_put16(msg, 0);
|
|
|
+ smb_message_put16(msg, 0); // Empty PDC name
|
|
|
+
|
|
|
+ req->xsec_blob_size = der_size;
|
|
|
+ req->payload_size = msg->cursor - sizeof(smb_session_xsec_req);
|
|
|
+
|
|
|
+ asn1_delete_structure(&token);
|
|
|
+
|
|
|
+ if (!smb_session_send_msg(s, msg))
|
|
|
+ {
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ BDSM_dbg("Unable to send Session Setup AndX (NTLMSSP_AUTH) message\n");
|
|
|
+ return (0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (smb_session_recv_msg(s, &resp) == 0)
|
|
|
+ return (0);
|
|
|
+
|
|
|
+ if (resp.packet->header.status != NT_STATUS_SUCCESS)
|
|
|
+ return (0);
|
|
|
+ else
|
|
|
+ return (1);
|
|
|
+
|
|
|
+ error:
|
|
|
+ asn1_display_error("smb_session_login auth()", res);
|
|
|
+ smb_message_destroy(msg);
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+int smb_session_login_spnego(smb_session *s, const char *domain,
|
|
|
+ const char *user, const char *password)
|
|
|
+{
|
|
|
+ smb_message resp;
|
|
|
+ int res;
|
|
|
+ assert(s != NULL && domain != NULL && user != NULL && password != NULL);
|
|
|
+
|
|
|
+ if (!init_asn1(s))
|
|
|
+ return (0);
|
|
|
+
|
|
|
+ if (!negotiate(s, domain, user))
|
|
|
+ goto error;
|
|
|
+ if (!challenge(s))
|
|
|
+ goto error;
|
|
|
+
|
|
|
+ res = auth(s, domain, user, password);
|
|
|
+
|
|
|
+ clean_asn1(s);
|
|
|
+
|
|
|
+ return (res);
|
|
|
+
|
|
|
+ error:
|
|
|
+ BDSM_dbg("login_spnego Interrupted\n");
|
|
|
+ clean_asn1(s);
|
|
|
+ return (0);
|
|
|
+}
|
|
|
+
|