Files
Ronnie Sahlberg 5e2bf72df6 Improve parsing of NTLM_USER_FILE
Try to match the first field of this file as either the smb2
domain OR the server name.

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
2025-07-17 13:30:23 +10:00

776 lines
22 KiB
C

/* -*- mode:c; tab-width:8; c-basic-offset:8; indent-tabs-mode:nil; -*- */
/*
Copyright (C) 2016 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
Portions of this code are copyright 2017 to Primary Data Inc.
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
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_UNISTD_H
#include <sys/unistd.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
#include "compat.h"
#include "smb2.h"
#include "libsmb2.h"
#include "libsmb2-private.h"
#include "slist.h"
#define MAX_URL_SIZE 1024
/* This is a list of all allocated contexts so servers can iterate over each
* it should probably be moved to the server context and a callback registered
* here to tell the server when a context is destroyed, but this works
*/
static struct smb2_context *active_contexts;
static int
smb2_parse_args(struct smb2_context *smb2, const char *args)
{
while (args && *args != 0) {
char *next, *value;
next = strchr(args, '&');
if (next) {
*(next++) = '\0';
}
value = strchr(args, '=');
if (value) {
*(value++) = '\0';
}
if (!strcmp(args, "seal")) {
smb2->seal = 1;
} else if (!strcmp(args, "sign")) {
smb2->sign = 1;
} else if (!strcmp(args, "ndr3264")) {
smb2->ndr = 0;
} else if (!strcmp(args, "ndr32")) {
smb2->ndr = 1;
} else if (!strcmp(args, "ndr64")) {
smb2->ndr = 2;
} else if (!strcmp(args, "le")) {
smb2->endianness = 0;
} else if (!strcmp(args, "be")) {
smb2->endianness = 1;
} else if (!strcmp(args, "sec")) {
if(!strcmp(value, "krb5")) {
smb2->sec = SMB2_SEC_KRB5;
} else if(!strcmp(value, "krb5cc")) {
smb2->sec = SMB2_SEC_KRB5;
smb2->use_cached_creds = 1;
} else if (!strcmp(value, "ntlmssp")) {
smb2->sec = SMB2_SEC_NTLMSSP;
} else {
smb2_set_error(smb2, "Unknown sec= argument: "
"%s", value);
return -1;
}
} else if (!strcmp(args, "vers")) {
if(!strcmp(value, "2")) {
smb2->version = SMB2_VERSION_ANY2;
} else if(!strcmp(value, "3")) {
smb2->version = SMB2_VERSION_ANY3;
} else if(!strcmp(value, "2.02")) {
smb2->version = SMB2_VERSION_0202;
} else if(!strcmp(value, "2.10")) {
smb2->version = SMB2_VERSION_0210;
} else if(!strcmp(value, "3.0") ||
!strcmp(value, "3.00")) {
smb2->version = SMB2_VERSION_0300;
} else if(!strcmp(value, "3.02")) {
smb2->version = SMB2_VERSION_0302;
} else if(!strcmp(value, "3.1.1")) {
smb2->version = SMB2_VERSION_0311;
} else {
smb2_set_error(smb2, "Unknown vers= argument: "
"%s", value);
return -1;
}
} else if (!strcmp(args, "timeout")) {
smb2->timeout = (int)strtol(value, NULL, 10);
} else {
smb2_set_error(smb2, "Unknown argument: %s", args);
return -1;
}
args = next;
}
if (smb2->seal) {
switch (smb2->version) {
case SMB2_VERSION_ANY:
smb2->version = SMB2_VERSION_ANY3;
break;
case SMB2_VERSION_ANY3:
case SMB2_VERSION_0300:
case SMB2_VERSION_0302:
case SMB2_VERSION_0311:
break;
default:
smb2_set_error(smb2, "Can only use seal with SMB3");
return -1;
}
}
return 0;
}
struct smb2_url *smb2_parse_url(struct smb2_context *smb2, const char *url)
{
struct smb2_url *u;
char *ptr, *tmp, str[MAX_URL_SIZE];
char *args;
char *shared_folder;
size_t len_shared_folder;
if (strncmp(url, "smb://", 6)) {
smb2_set_error(smb2, "URL does not start with 'smb://'");
return NULL;
}
if (strlen(url + 6) >= MAX_URL_SIZE) {
smb2_set_error(smb2, "URL is too long");
return NULL;
}
strncpy(str, url + 6, MAX_URL_SIZE);
args = strchr(str, '?');
if (args) {
*(args++) = '\0';
if (smb2_parse_args(smb2, args) != 0) {
return NULL;
}
}
u = calloc(1, sizeof(struct smb2_url));
if (u == NULL) {
smb2_set_error(smb2, "Failed to allocate smb2_url");
return NULL;
}
ptr = str;
shared_folder = strchr(ptr, '/');
if (!shared_folder) {
smb2_set_error(smb2, "Wrong URL format");
return NULL;
}
len_shared_folder = strlen(shared_folder);
/* domain */
if ((tmp = strchr(ptr, ';')) != NULL && strlen(tmp) > len_shared_folder) {
*(tmp++) = '\0';
u->domain = strdup(ptr);
ptr = tmp;
}
/* user */
if ((tmp = strchr(ptr, '@')) != NULL && strlen(tmp) > len_shared_folder) {
*(tmp++) = '\0';
u->user = strdup(ptr);
ptr = tmp;
}
/* server */
if ((tmp = strchr(ptr, '/')) != NULL) {
*(tmp++) = '\0';
u->server = strdup(ptr);
ptr = tmp;
}
/* Do we just have a share or do we have both a share and an object */
tmp = strchr(ptr, '/');
/* We only have a share */
if (tmp == NULL) {
u->share = strdup(ptr);
return u;
}
/* we have both share and object path */
*(tmp++) = '\0';
u->share = strdup(ptr);
u->path = strdup(tmp);
return u;
}
void smb2_destroy_url(struct smb2_url *url)
{
if (url == NULL) {
return;
}
free(discard_const(url->domain));
free(discard_const(url->user));
free(discard_const(url->server));
free(discard_const(url->share));
free(discard_const(url->path));
free(url);
}
struct smb2_context *smb2_init_context(void)
{
struct smb2_context *smb2;
char buf[1024] _U_;
int i, ret;
static int ctr;
srandom((unsigned)time(NULL) ^ getpid() ^ ctr++);
smb2 = calloc(1, sizeof(struct smb2_context));
if (smb2 == NULL) {
return NULL;
}
ret = getlogin_r(buf, sizeof(buf));
smb2_set_user(smb2, ret == 0 ? buf : "Guest");
smb2->fd = SMB2_INVALID_SOCKET;
smb2->connecting_fds = NULL;
smb2->connecting_fds_count = 0;
smb2->addrinfos = NULL;
smb2->next_addrinfo = NULL;
smb2->sec = SMB2_SEC_UNDEFINED;
smb2->version = SMB2_VERSION_ANY;
smb2->ndr = 1;
for (i = 0; i < 8; i++) {
smb2->client_challenge[i] = random() & 0xff;
}
for (i = 0; i < SMB2_SALT_SIZE; i++) {
smb2->salt[i] = random() & 0xff;
}
snprintf(smb2->client_guid, 16, "libsmb2-%d", (int)random());
smb2->session_key = NULL;
SMB2_LIST_ADD(&active_contexts, smb2);
return smb2;
}
void smb2_destroy_context(struct smb2_context *smb2)
{
if (smb2 == NULL) {
return;
}
if (SMB2_VALID_SOCKET(smb2->fd)) {
if (smb2->change_fd) {
smb2->change_fd(smb2, smb2->fd, SMB2_DEL_FD);
}
close(smb2->fd);
smb2->fd = SMB2_INVALID_SOCKET;
}
else {
smb2_close_connecting_fds(smb2);
}
while (smb2->outqueue) {
struct smb2_pdu *pdu = smb2->outqueue;
smb2->outqueue = pdu->next;
if (pdu->cb) {
pdu->cb(smb2, SMB2_STATUS_SHUTDOWN, NULL, pdu->cb_data);
}
smb2_free_pdu(smb2, pdu);
}
if (smb2->pdu) {
struct smb2_pdu *pdu = smb2->pdu;
if (pdu->cb) {
pdu->cb(smb2, SMB2_STATUS_SHUTDOWN, NULL, pdu->cb_data);
}
smb2_free_pdu(smb2, smb2->pdu);
}
while (smb2->waitqueue) {
struct smb2_pdu *pdu = smb2->waitqueue;
smb2->waitqueue = pdu->next;
if (pdu->cb) {
pdu->cb(smb2, SMB2_STATUS_SHUTDOWN, NULL, pdu->cb_data);
}
if (pdu == smb2->pdu) {
smb2->pdu = NULL;
}
smb2_free_pdu(smb2, pdu);
}
smb2_free_iovector(smb2, &smb2->in);
if (smb2->fhs) {
smb2_free_all_fhs(smb2);
}
if (smb2->dirs) {
smb2_free_all_dirs(smb2);
}
if (smb2->connect_cb) {
smb2->connect_cb(smb2, SMB2_STATUS_CANCELLED,
NULL, smb2->connect_data);
smb2->connect_cb = NULL;
}
free(smb2->session_key);
smb2->session_key = NULL;
free(discard_const(smb2->user));
free(discard_const(smb2->server));
free(discard_const(smb2->share));
free(discard_const(smb2->password));
free(discard_const(smb2->domain));
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 */
}
SMB2_LIST_REMOVE(&active_contexts, smb2);
free(smb2);
}
struct smb2_context *smb2_active_contexts(void)
{
return active_contexts;
}
int smb2_context_active(struct smb2_context *smb2)
{
struct smb2_context *context = active_contexts;
while (context) {
if (smb2 == context) {
return 1;
}
context = context->next;
}
return 0;
}
void smb2_free_iovector(struct smb2_context *smb2, struct smb2_io_vectors *v)
{
int i;
for (i = 0; i < v->niov; i++) {
if (v->iov[i].free) {
v->iov[i].free(v->iov[i].buf);
}
}
v->niov = 0;
v->total_size = 0;
v->num_done = 0;
}
struct smb2_iovec *smb2_add_iovector(struct smb2_context *smb2,
struct smb2_io_vectors *v,
uint8_t *buf, size_t len,
void (*free)(void *))
{
struct smb2_iovec *iov = &v->iov[v->niov];
v->iov[v->niov].buf = buf;
v->iov[v->niov].len = len;
v->iov[v->niov].free = free;
v->total_size += len;
v->niov++;
return iov;
}
#ifndef _IOP
static void smb2_set_error_string(struct smb2_context *smb2, const char * error_string, va_list args)
{
char errstr[MAX_ERROR_SIZE] = {0};
#ifdef _XBOX
if (_vsnprintf(errstr, MAX_ERROR_SIZE, error_string, args) < 0) {
#else
if (vsnprintf(errstr, MAX_ERROR_SIZE, error_string, args) < 0) {
#endif
strncpy(errstr, "could not format error string!",
MAX_ERROR_SIZE);
}
strncpy(smb2->error_string, errstr, MAX_ERROR_SIZE);
}
#endif /* _IOP */
void smb2_set_error(struct smb2_context *smb2, const char *error_string, ...)
{
#ifndef _IOP
va_list ap;
if (!smb2)
return;
if (!error_string || !*error_string)
smb2->nterror = 0;
va_start(ap, error_string);
smb2_set_error_string(smb2, error_string, ap);
va_end(ap);
if(smb2->error_cb) {
smb2->error_cb(smb2, smb2_get_error(smb2));
}
#endif
}
void smb2_register_error_callback(struct smb2_context *smb2,
smb2_error_cb error_cb)
{
smb2->error_cb = error_cb;
}
void smb2_set_nterror(struct smb2_context *smb2, int nterror, const char *error_string, ...)
{
if (!smb2)
return;
#ifndef _IOP
{
va_list ap;
va_start(ap, error_string);
smb2_set_error_string(smb2, error_string, ap);
va_end(ap);
}
#endif
smb2->nterror = nterror;
}
const char *smb2_get_error(struct smb2_context *smb2)
{
return smb2 ? smb2->error_string : "";
}
int smb2_get_nterror(struct smb2_context *smb2)
{
return smb2 ? smb2->nterror : 0;
}
void smb2_set_client_guid(struct smb2_context *smb2, const uint8_t guid[SMB2_GUID_SIZE])
{
memcpy(smb2->client_guid, guid, SMB2_GUID_SIZE);
}
const char *smb2_get_client_guid(struct smb2_context *smb2)
{
return smb2->client_guid;
}
uint16_t smb2_get_dialect(struct smb2_context *smb2)
{
return smb2->dialect;
}
void smb2_set_security_mode(struct smb2_context *smb2, uint16_t security_mode)
{
smb2->security_mode = security_mode;
}
void smb2_set_password_from_file(struct smb2_context *smb2)
{
#if !defined(_XBOX) && !defined(_IOP) && !defined(__amigaos4__) && !defined(__AMIGA__) && !defined(__AROS__)
char *name = NULL;
FILE *fh;
char buf[256];
char *domain, *user, *password;
int finished;
#ifdef _MSC_UWP
/* GetEnvironmentVariable is not available for UWP up to 10.0.16299 SDK */
#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3)
uint32_t name_len = GetEnvironmentVariableA("NTLM_USER_FILE", NULL, 0);
if (name_len > 0) {
name = (char*)malloc(name_len + 1);
if (name == NULL) {
return;
}
GetEnvironmentVariableA("NTLM_USER_FILE", name, name_len);
}
#endif
#else
name = getenv("NTLM_USER_FILE");
#endif
if (name == NULL || smb2->user == NULL) {
#ifdef _MSC_UWP
free(name);
#endif
return;
}
fh = fopen(name, "r");
#ifdef _MSC_UWP
free(name);
#endif
if (!fh) {
return;
}
smb2_set_password(smb2, NULL);
/* Match on domain if available, else match on server name */
while (!feof(fh)) {
if (fgets(buf, 256, fh) == NULL) {
break;
}
buf[255] = 0;
finished = 0;
while (!finished) {
switch (buf[strlen(buf) - 1]) {
case '\n':
buf[strlen(buf) - 1] = 0;
default:
finished = 1;
}
if (strlen(buf) == 0) {
break;
}
}
if (buf[0] == 0) {
break;
}
domain = buf;
user = strchr(domain, ':');
if (user == NULL) {
continue;
}
*user++ = 0;
password = strchr(user, ':');
if (password == NULL) {
continue;
}
*password++ = 0;
if (domain[0] && smb2->domain && !strcmp(smb2->domain, domain)) {
smb2_set_password(smb2, password);
fclose(fh);
return;
}
if (domain[0] && smb2->server && !strcmp(smb2->server, domain)) {
smb2_set_password(smb2, password);
fclose(fh);
return;
}
if (!domain[0]) {
/* wildcard. grab this as default but continue looking for a better match */
smb2_set_password(smb2, password);
}
}
fclose(fh);
#endif /* !defined(_XBOX) && !defined(_IOP) && ... */
}
void smb2_set_user(struct smb2_context *smb2, const char *user)
{
if (smb2->user) {
free(discard_const(smb2->user));
smb2->user = NULL;
}
if (user == NULL) {
return;
}
smb2->user = strdup(user);
smb2_set_password_from_file(smb2);
}
const char *smb2_get_user(struct smb2_context *smb2)
{
if (smb2 && smb2->user) {
return smb2->user;
}
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) {
free(discard_const(smb2->password));
smb2->password = NULL;
}
if (password == NULL) {
return;
}
smb2->password = strdup(password);
}
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);
smb2_set_password_from_file(smb2);
}
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) {
free(discard_const(smb2->workstation));
smb2->workstation = NULL;
}
if (workstation == NULL) {
return;
}
smb2->workstation = strdup(workstation);
}
void smb2_set_opaque(struct smb2_context *smb2, void *opaque)
{
smb2->opaque = opaque;
}
void *smb2_get_opaque(struct smb2_context *smb2)
{
return smb2->opaque;
}
void smb2_set_seal(struct smb2_context *smb2, int val)
{
smb2->seal = val;
}
void smb2_set_sign(struct smb2_context *smb2, int val)
{
smb2->sign = val;
}
void smb2_set_authentication(struct smb2_context *smb2, int val)
{
smb2->sec = (enum smb2_sec)val;
}
void smb2_set_timeout(struct smb2_context *smb2, int seconds)
{
smb2->timeout = seconds;
}
void smb2_set_version(struct smb2_context *smb2,
enum smb2_negotiate_version version)
{
smb2->version = version;
}
void smb2_get_libsmb2Version(struct smb2_libversion *smb2_ver)
{
smb2_ver->major_version = LIBSMB2_MAJOR_VERSION;
smb2_ver->minor_version = LIBSMB2_MINOR_VERSION;
smb2_ver->patch_version = LIBSMB2_MAJOR_VERSION;
}
void smb2_set_passthrough(struct smb2_context *smb2,
int passthrough)
{
smb2->passthrough = passthrough;
}
void smb2_get_passthrough(struct smb2_context *smb2,
int *passthrough)
{
*passthrough = smb2->passthrough;
}
void smb2_set_oplock_or_lease_break_callback(struct smb2_context *smb2,
smb2_oplock_or_lease_break_cb cb)
{
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