diff --git a/.gitignore b/.gitignore index 0ae97bd..13e772d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ cifs-utils-4.6.tar.bz2 /cifs-utils-6.11.tar.bz2 /cifs-utils-6.14.tar.bz2 /cifs-utils-7.0.tar.bz2 +/cifs-utils-7.1.tar.bz2 diff --git a/Implement-CLDAP-Ping-to-find-the-closest-site.patch b/Implement-CLDAP-Ping-to-find-the-closest-site.patch deleted file mode 100644 index 5f1d037..0000000 --- a/Implement-CLDAP-Ping-to-find-the-closest-site.patch +++ /dev/null @@ -1,781 +0,0 @@ -From 770e891a8b7ad53d4d700e08cf8d3154028b4588 Mon Sep 17 00:00:00 2001 -From: David Voit -Date: Wed, 3 Apr 2024 07:24:48 +0200 -Subject: [PATCH] Implement CLDAP Ping to find the closest site - -For domain based DFS we always need to contact the domain controllers. -On setups, which are using bigger AD installations you could get random dc on the other side of the world, -if you don't have site support. This can lead to network timeouts and other problems. - -CLDAP-Ping uses ASN.1 + UDP (CLDAP) and custom-DCE encoding including DName compressions without -field separation. Finally after finding the sitename we now need to do a DNS SRV lookups to find -the correct IPs to our closest site and fill up the remaining IPs from the global list. - -Signed-off-by: David Voit ---- - Makefile.am | 15 ++- - cldap_ping.c | 345 +++++++++++++++++++++++++++++++++++++++++++++++++ - cldap_ping.h | 14 ++ - mount.cifs.c | 5 +- - resolve_host.c | 256 ++++++++++++++++++++++++++++++++---- - resolve_host.h | 6 +- - 6 files changed, 604 insertions(+), 37 deletions(-) - create mode 100644 cldap_ping.c - create mode 100644 cldap_ping.h - -Index: cifs-utils/Makefile.am -=================================================================== ---- cifs-utils.orig/Makefile.am -+++ cifs-utils/Makefile.am -@@ -3,8 +3,8 @@ ACLOCAL_AMFLAGS = -I aclocal - - root_sbindir = $(ROOTSBINDIR) - root_sbin_PROGRAMS = mount.cifs --mount_cifs_SOURCES = mount.cifs.c mtab.c resolve_host.c util.c --mount_cifs_LDADD = $(LIBCAP) $(CAPNG_LDADD) $(RT_LDADD) -+mount_cifs_SOURCES = mount.cifs.c mtab.c $(resolve_hosts_SOURCES) util.c -+mount_cifs_LDADD = $(LIBCAP) $(CAPNG_LDADD) $(RT_LDADD) $(resolve_hosts_LDADD) - include_HEADERS = cifsidmap.h - rst_man_pages = mount.cifs.8 - -@@ -28,6 +28,9 @@ bin_PROGRAMS = - bin_SCRIPTS = - sbin_PROGRAMS = - -+resolve_hosts_SOURCES = data_blob.c asn1.c cldap_ping.c resolve_host.c -+resolve_hosts_LDADD = -ltalloc -lresolv -+ - if CONFIG_CIFSUPCALL - sbin_PROGRAMS += cifs.upcall - cifs_upcall_SOURCES = cifs.upcall.c data_blob.c asn1.c spnego.c -@@ -43,8 +46,8 @@ endif - - if CONFIG_CIFSCREDS - bin_PROGRAMS += cifscreds --cifscreds_SOURCES = cifscreds.c cifskey.c resolve_host.c util.c --cifscreds_LDADD = -lkeyutils -+cifscreds_SOURCES = cifscreds.c cifskey.c $(resolve_hosts_SOURCES) util.c -+cifscreds_LDADD = -lkeyutils $(resolve_hosts_LDADD) - - rst_man_pages += cifscreds.1 - -@@ -105,8 +108,8 @@ endif - if CONFIG_PAM - pam_PROGRAMS = pam_cifscreds.so - rst_man_pages += pam_cifscreds.8 --pam_cifscreds.so: pam_cifscreds.c cifskey.c resolve_host.c util.c -- $(CC) $(DEFS) $(CFLAGS) $(AM_CFLAGS) $(LDFLAGS) -shared -fpic -o $@ $+ -lpam -lkeyutils -+pam_cifscreds.so: pam_cifscreds.c cifskey.c $(resolve_hosts_SOURCES) util.c -+ $(CC) $(DEFS) $(CFLAGS) $(AM_CFLAGS) $(LDFLAGS) -shared -fpic -o $@ $+ -lpam -lkeyutils $(resolve_hosts_LDADD) - - endif - -Index: cifs-utils/cldap_ping.c -=================================================================== ---- /dev/null -+++ cifs-utils/cldap_ping.c -@@ -0,0 +1,345 @@ -+/* -+ * CLDAP Ping to find closest ClientSiteName -+ * -+ * Copyright (C) 2024 David Voit (david.voit@gmail.com) -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 3 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 General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program. If not, see . -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include "data_blob.h" -+#include "asn1.h" -+#include "cldap_ping.h" -+ -+#define LDAP_DNS_DOMAIN "DnsDomain" -+#define LDAP_DNS_DOMAIN_LEN strlen(LDAP_DNS_DOMAIN) -+#define LDAP_NT_VERSION "NtVer" -+#define LDAP_NT_VERSION_LEN strlen(LDAP_NT_VERSION) -+#define LDAP_ATTRIBUTE_NETLOGON "NetLogon" -+#define LDAP_ATTRIBUTE_NETLOGON_LEN strlen(LDAP_ATTRIBUTE_NETLOGON) -+ -+ -+// Parse a ASN.1 BER tag size-field, returns start of payload of tag -+char *parse_ber_size(char *buf, size_t *tag_size) { -+ size_t size = *buf & 0xff; -+ char *ret = (buf + 1); -+ if (size >= 0x81) { -+ switch (size) { -+ case 0x81: -+ size = *ret & 0xff; -+ ret += 1; -+ break; -+ case 0x82: -+ size = (*ret << 8) | (*(ret + 1) & 0xff); -+ ret += 2; -+ break; -+ case 0x83: -+ size = (*ret << 16) | (*(ret + 1) << 8) | (*(ret + 2) & 0xff); -+ ret += 3; -+ break; -+ case 0x84: -+ size = (*ret << 24) | (*(ret + 1) << 16) | (*(ret + 2) << 8) | (*(ret + 3) & 0xff); -+ ret += 4; -+ break; -+ default: -+ return NULL; -+ } -+ } -+ -+ *tag_size = size; -+ return ret; -+} -+ -+// simple wrapper over dn_expand which also calculates the new offset for the next compressed dn -+int read_dns_string(char *buf, size_t buf_size, char *dest, size_t dest_size, size_t *offset) { -+ int compressed_length = dn_expand((u_char *)buf, (u_char *)buf+buf_size, (u_char *)buf + *offset, dest, (int)dest_size); -+ if (compressed_length < 0) { -+ return -1; -+ } -+ -+ *offset = *offset+compressed_length; -+ -+ return 0; -+} -+ -+// LDAP request for: (&(DnsDomain=DOMAIN_HERE)(NtVer=\\06\\00\\00\\00)) -+ASN1_DATA *generate_cldap_query(char *domain) { -+ ASN1_DATA *data; -+ TALLOC_CTX *mem_ctx = talloc_init("cldap"); -+ -+ data = asn1_init(mem_ctx); -+ asn1_push_tag(data, ASN1_SEQUENCE(0)); -+ -+ // Message id -+ asn1_push_tag(data, ASN1_INTEGER); -+ asn1_write_uint8(data, 1); -+ asn1_pop_tag(data); -+ -+ // SearchRequest -+ asn1_push_tag(data, ASN1_APPLICATION(3)); -+ -+ // empty baseObject -+ asn1_push_tag(data, ASN1_OCTET_STRING); -+ asn1_pop_tag(data); -+ -+ // scope 0 = baseObject -+ asn1_push_tag(data, ASN1_ENUMERATED); -+ asn1_write_uint8(data, 0); -+ asn1_pop_tag(data); -+ -+ // derefAliasses 0=neverDerefAlias -+ asn1_push_tag(data, ASN1_ENUMERATED); -+ asn1_write_uint8(data, 0); -+ asn1_pop_tag(data); -+ -+ // sizeLimit -+ asn1_push_tag(data, ASN1_INTEGER); -+ asn1_write_uint8(data, 0); -+ asn1_pop_tag(data); -+ -+ // timeLimit -+ asn1_push_tag(data, ASN1_INTEGER); -+ asn1_write_uint8(data, 0); -+ asn1_pop_tag(data); -+ -+ // typesOnly -+ asn1_push_tag(data, ASN1_BOOLEAN); -+ asn1_write_uint8(data, 0); -+ asn1_pop_tag(data); -+ -+ // AND -+ asn1_push_tag(data, ASN1_CONTEXT(0)); -+ // equalityMatch -+ asn1_push_tag(data, ASN1_CONTEXT(3)); -+ asn1_write_OctetString(data, LDAP_DNS_DOMAIN, LDAP_DNS_DOMAIN_LEN); -+ asn1_write_OctetString(data, domain, strlen(domain)); -+ asn1_pop_tag(data); -+ -+ // equalityMatch -+ asn1_push_tag(data, ASN1_CONTEXT(3)); -+ asn1_write_OctetString(data, LDAP_NT_VERSION, LDAP_NT_VERSION_LEN); -+ // Bitmask NETLOGON_NT_VERSION_5 & NETLOGON_NT_VERSION_5EX -> To get NETLOGON_SAM_LOGON_RESPONSE_EX as response -+ asn1_write_OctetString(data, "\x06\x00\x00\x00", 4); -+ asn1_pop_tag(data); -+ -+ // End AND -+ asn1_pop_tag(data); -+ -+ asn1_push_tag(data, ASN1_SEQUENCE(0)); -+ asn1_write_OctetString(data, LDAP_ATTRIBUTE_NETLOGON, LDAP_ATTRIBUTE_NETLOGON_LEN); -+ asn1_pop_tag(data); -+ -+ // End SearchRequest -+ asn1_pop_tag(data); -+ // End Sequence -+ asn1_pop_tag(data); -+ -+ return data; -+} -+ -+// Input is a cldap response, output is a pointer to the NETLOGON_SAM_LOGON_RESPONSE_EX payload -+ssize_t extract_netlogon_section(char *buffer, size_t buffer_size, char **netlogon_payload) { -+ size_t ber_size; -+ size_t netlogon_payload_size; -+ // Not enough space to read initial sequence - not an correct cldap response -+ if (buffer_size < 7) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ // Sequence tag -+ if (*buffer != 0x30) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *message_id_tag = parse_ber_size(buffer + 1, &ber_size); -+ -+ if (ber_size > buffer_size) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ if (*message_id_tag != 0x02) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *message_id = parse_ber_size(message_id_tag + 1, &ber_size); -+ -+ if (ber_size != 1 || *message_id != 1) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ // SearchResultEntry -+ if (*(message_id+1) != 0x64) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *object_name_tag = parse_ber_size(message_id+2, &ber_size); -+ if (object_name_tag == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *object_name = parse_ber_size(object_name_tag+1, &ber_size); -+ if (object_name == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ if (*object_name_tag != 4 || ber_size != 0) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *partial_attribute_list_tag = parse_ber_size(object_name+1, &ber_size); -+ if (partial_attribute_list_tag == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ if (*partial_attribute_list_tag != 0x30) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ -+ char *partial_attribute_tag = parse_ber_size(partial_attribute_list_tag+1, &ber_size); -+ if (partial_attribute_tag == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *attribute_name = parse_ber_size(partial_attribute_tag+1, &ber_size); -+ if (attribute_name == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ if (ber_size != LDAP_ATTRIBUTE_NETLOGON_LEN) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ if (strncasecmp(LDAP_ATTRIBUTE_NETLOGON, attribute_name, LDAP_ATTRIBUTE_NETLOGON_LEN) != 0) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ // SET -+ if (*(attribute_name+LDAP_ATTRIBUTE_NETLOGON_LEN) != 0x31) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ char *start_of_data = parse_ber_size(attribute_name+LDAP_ATTRIBUTE_NETLOGON_LEN+1, &ber_size); -+ if (start_of_data == NULL) { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ // octat-string of NetLogon data -+ if (*start_of_data != '\x04') { -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ *netlogon_payload = parse_ber_size(start_of_data + 1, &netlogon_payload_size); -+ -+ if (*netlogon_payload == NULL) { -+ *netlogon_payload = NULL; -+ return CLDAP_PING_PARSE_ERROR_LDAP; -+ } -+ -+ return (ssize_t)netlogon_payload_size; -+} -+ -+int netlogon_get_client_site(char *netlogon_response, size_t netlogon_size, char *sitename) { -+ // 24 mandatory bytes -+ if (netlogon_size < 25) { -+ return CLDAP_PING_PARSE_ERROR_NETLOGON; -+ } -+ -+ // LOGON_SAM_PAUSE_RESPONSE_EX -> Netlogon service is not in-sync try next dc instead -+ if (*netlogon_response == 0x18 && *(netlogon_response + 1) == 0x00) { -+ return CLDAP_PING_TRYNEXT; -+ } -+ -+ // NETLOGON_SAM_LOGON_RESPONSE_EX Opcode: 0x17 -+ if (*netlogon_response != 0x17 || *(netlogon_response + 1) != 0x00) { -+ return CLDAP_PING_PARSE_ERROR_NETLOGON; -+ } -+ -+ // skip over sbz, ds_flags and domain_guid -+ // and start directly at variable string portion of NETLOGON_SAM_LOGON_RESPONSE_EX -+ size_t offset = 24; -+ -+ for (int i=0; i < 8; i++) { -+ // iterate over DnsForestName, DnsDomainName, NetbiosDomainName, NetbiosComputerName, UserName, DcSiteName -+ // to finally get to our desired ClientSiteName field -+ if (read_dns_string(netlogon_response, netlogon_size, sitename, MAXCDNAME, &offset) < 0) { -+ return CLDAP_PING_PARSE_ERROR_NETLOGON; -+ } -+ } -+ -+ return 0; -+} -+ -+int cldap_ping(char *domain, sa_family_t family, void *addr, char *site_name) { -+ char buffer[8196]; -+ ssize_t response_size; -+ char *netlogon_response; -+ ssize_t netlogon_size; -+ struct sockaddr_storage socketaddr; -+ size_t addr_size; -+ int sock = socket(family, SOCK_DGRAM, 0); -+ if (sock < 0) { -+ return CLDAP_PING_NETWORK_ERROR; -+ } -+ -+ ASN1_DATA *data = generate_cldap_query(domain); -+ -+ if (family == AF_INET6) { -+ addr_size = sizeof(struct sockaddr_in6); -+ bzero((void *) &socketaddr, addr_size); -+ socketaddr.ss_family = AF_INET6; -+ ((struct sockaddr_in6 *)&socketaddr)->sin6_addr = *((struct in6_addr*)addr); -+ ((struct sockaddr_in6 *)&socketaddr)->sin6_port = htons(389); -+ } else { -+ addr_size = sizeof(struct sockaddr_in); -+ bzero((void *) &socketaddr, addr_size); -+ socketaddr.ss_family = AF_INET; -+ ((struct sockaddr_in *)&socketaddr)->sin_addr = *((struct in_addr*)addr); -+ ((struct sockaddr_in *)&socketaddr)->sin_port = htons(389); -+ } -+ -+ struct timeval timeout = {.tv_sec = 2, .tv_usec = 0}; -+ if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { -+ return CLDAP_PING_NETWORK_ERROR; -+ } -+ if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { -+ return CLDAP_PING_NETWORK_ERROR; -+ } -+ -+ if (sendto(sock, data->data, data->length, 0, (struct sockaddr *)&socketaddr, addr_size) < 0) { -+ close(sock); -+ return CLDAP_PING_TRYNEXT; -+ } -+ -+ asn1_free(data); -+ response_size = recv(sock, buffer, sizeof(buffer), 0); -+ close(sock); -+ -+ if (response_size < 0) { -+ return CLDAP_PING_TRYNEXT; -+ } -+ -+ netlogon_size = extract_netlogon_section(buffer, response_size, &netlogon_response); -+ if (netlogon_size < 0) { -+ return (int)netlogon_size; -+ } -+ -+ return netlogon_get_client_site(netlogon_response, netlogon_size, site_name); -+} -Index: cifs-utils/cldap_ping.h -=================================================================== ---- /dev/null -+++ cifs-utils/cldap_ping.h -@@ -0,0 +1,14 @@ -+#ifndef _CLDAP_PING_H_ -+#define _CLDAP_PING_H_ -+ -+#define CLDAP_PING_NETWORK_ERROR -1 -+#define CLDAP_PING_TRYNEXT -2 -+#define CLDAP_PING_PARSE_ERROR_LDAP -3 -+#define CLDAP_PING_PARSE_ERROR_NETLOGON -4 -+ -+// returns CLDAP_PING_TRYNEXT if you should use another dc -+// any other error code < 0 is a fatal error -+// site_name must be of MAXCDNAME size! -+int cldap_ping(char *domain, sa_family_t family, void *addr, char *site_name); -+ -+#endif /* _CLDAP_PING_H_ */ -Index: cifs-utils/mount.cifs.c -=================================================================== ---- cifs-utils.orig/mount.cifs.c -+++ cifs-utils/mount.cifs.c -@@ -1889,8 +1889,11 @@ assemble_mountinfo(struct parsed_mount_i - if (rc) - goto assemble_exit; - -- if (parsed_info->addrlist[0] == '\0') -+ if (parsed_info->addrlist[0] == '\0') { - rc = resolve_host(parsed_info->host, parsed_info->addrlist); -+ if (rc == 0 && parsed_info->verboseflag) -+ fprintf(stderr, "Host \"%s\" resolved to the following IP addresses: %s\n", parsed_info->host, parsed_info->addrlist); -+ } - - switch (rc) { - case EX_USAGE: -Index: cifs-utils/resolve_host.c -=================================================================== ---- cifs-utils.orig/resolve_host.c -+++ cifs-utils/resolve_host.c -@@ -3,6 +3,7 @@ - * - * Copyright (C) 2010 Jeff Layton (jlayton@samba.org) - * Copyright (C) 2010 Igor Druzhinin (jaxbrigs@gmail.com) -+ * Copyright (C) 2024 David Voit (david.voit@gmail.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by -@@ -27,15 +28,16 @@ - #include - #include - #include -+#include - #include "mount.h" - #include "util.h" -+#include "cldap_ping.h" - #include "resolve_host.h" - - /* - * resolve hostname to comma-separated list of address(es) - */ --int resolve_host(const char *host, char *addrstr) --{ -+int resolve_host(const char *host, char *addrstr) { - int rc; - /* 10 for max width of decimal scopeid */ - char tmpbuf[NI_MAXHOST + 1 + 10 + 1]; -@@ -44,6 +46,7 @@ int resolve_host(const char *host, char - struct addrinfo *addrlist, *addr; - struct sockaddr_in *sin; - struct sockaddr_in6 *sin6; -+ size_t count_v4 = 0, count_v6 = 0; - - rc = getaddrinfo(host, NULL, NULL, &addrlist); - if (rc != 0) -@@ -59,34 +62,45 @@ int resolve_host(const char *host, char - } - - switch (addr->ai_addr->sa_family) { -- case AF_INET6: -- sin6 = (struct sockaddr_in6 *)addr->ai_addr; -- ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf, -- sizeof(tmpbuf)); -- if (!ipaddr) { -- rc = EX_SYSERR; -- goto resolve_host_out; -- } -- -- if (sin6->sin6_scope_id) { -- len = strnlen(tmpbuf, sizeof(tmpbuf)); -- snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u", -- sin6->sin6_scope_id); -- } -- break; -- case AF_INET: -- sin = (struct sockaddr_in *)addr->ai_addr; -- ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf, -- sizeof(tmpbuf)); -- if (!ipaddr) { -- rc = EX_SYSERR; -- goto resolve_host_out; -- } -- -- break; -- default: -- addr = addr->ai_next; -- continue; -+ case AF_INET6: -+ count_v6++; -+ if (count_v6 + count_v4 > MAX_ADDRESSES) { -+ addr = addr->ai_next; -+ continue; -+ } -+ -+ sin6 = (struct sockaddr_in6 *) addr->ai_addr; -+ ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ -+ -+ if (sin6->sin6_scope_id) { -+ len = strnlen(tmpbuf, sizeof(tmpbuf)); -+ snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u", -+ sin6->sin6_scope_id); -+ } -+ break; -+ case AF_INET: -+ count_v4++; -+ if (count_v6 + count_v4 > MAX_ADDRESSES) { -+ addr = addr->ai_next; -+ continue; -+ } -+ sin = (struct sockaddr_in *) addr->ai_addr; -+ ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ break; -+ default: -+ addr = addr->ai_next; -+ continue; - } - - if (addr == addrlist) -@@ -98,6 +112,192 @@ int resolve_host(const char *host, char - addr = addr->ai_next; - } - -+ -+ // Is this a DFS domain where we need to do a cldap ping to find the closest node? -+ if (count_v4 > 1 || count_v6 > 1) { -+ int res; -+ ns_msg global_domain_handle; -+ unsigned char global_domain_lookup[4096]; -+ ns_msg site_domain_handle; -+ unsigned char site_domain_lookup[4096]; -+ char dname[MAXCDNAME]; -+ int srv_cnt; -+ -+ res = res_init(); -+ if (res != 0) -+ goto resolve_host_out; -+ -+ res = snprintf(dname, MAXCDNAME, "_ldap._tcp.dc._msdcs.%s", host); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ res = res_query(dname, C_IN, ns_t_srv, global_domain_lookup, sizeof(global_domain_lookup)); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ // res is also the size of the response_buffer -+ res = ns_initparse(global_domain_lookup, res, &global_domain_handle); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ srv_cnt = ns_msg_count (global_domain_handle, ns_s_an); -+ -+ // No or just one DC we are done -+ if (srv_cnt < 2) -+ goto resolve_host_out; -+ -+ char site_name[MAXCDNAME]; -+ // We assume that AD always sends the ip addresses in the addtional data block -+ for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) { -+ ns_rr rr; -+ res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ switch (ns_rr_type(rr)) { -+ case ns_t_aaaa: -+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) -+ continue; -+ res = cldap_ping((char *) host, AF_INET6, (void *)ns_rr_rdata(rr), site_name); -+ break; -+ case ns_t_a: -+ if (ns_rr_rdlen(rr) != NS_INADDRSZ) -+ continue; -+ res = cldap_ping((char *) host, AF_INET, (void *)ns_rr_rdata(rr), site_name); -+ break; -+ default: -+ continue; -+ } -+ -+ if (res == CLDAP_PING_TRYNEXT) { -+ continue; -+ } -+ -+ if (res < 0) { -+ goto resolve_host_out; -+ } -+ -+ if (site_name[0] == '\0') { -+ goto resolve_host_out; -+ } else { -+ // site found - leave loop -+ break; -+ } -+ } -+ -+ res = snprintf(dname, MAXCDNAME, "_ldap._tcp.%s._sites.dc._msdcs.%s", site_name, host); -+ if (res < 0) { -+ goto resolve_host_out; -+ } -+ -+ res = res_query(dname, C_IN, ns_t_srv, site_domain_lookup, sizeof(site_domain_lookup)); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ // res is also the size of the response_buffer -+ res = ns_initparse(site_domain_lookup, res, &site_domain_handle); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ int number_addresses = 0; -+ for (int i = 0; i < ns_msg_count(site_domain_handle, ns_s_ar); i++) { -+ if (i > MAX_ADDRESSES) -+ break; -+ -+ ns_rr rr; -+ res = ns_parserr(&site_domain_handle, ns_s_ar, i, &rr); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ switch (ns_rr_type(rr)) { -+ case ns_t_aaaa: -+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) -+ continue; -+ ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ break; -+ case ns_t_a: -+ if (ns_rr_rdlen(rr) != NS_INADDRSZ) -+ continue; -+ ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ break; -+ default: -+ continue; -+ } -+ -+ number_addresses++; -+ -+ if (i == 0) -+ *addrstr = '\0'; -+ else -+ strlcat(addrstr, ",", MAX_ADDR_LIST_LEN); -+ -+ strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN); -+ } -+ -+ // Preferred site ips is now the first entry in addrstr, fill up with other sites till MAX_ADDRESS -+ for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) { -+ if (number_addresses > MAX_ADDRESSES) -+ break; -+ -+ ns_rr rr; -+ res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr); -+ if (res < 0) -+ goto resolve_host_out; -+ -+ switch (ns_rr_type(rr)) { -+ case ns_t_aaaa: -+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ) -+ continue; -+ ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ break; -+ case ns_t_a: -+ if (ns_rr_rdlen(rr) != NS_INADDRSZ) -+ continue; -+ ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf, -+ sizeof(tmpbuf)); -+ if (!ipaddr) { -+ rc = EX_SYSERR; -+ goto resolve_host_out; -+ } -+ break; -+ default: -+ continue; -+ } -+ -+ char *found = strstr(addrstr, tmpbuf); -+ -+ if (found) { -+ // We only have a real match if the substring is between ',' or it's the last/first entry in the list -+ char previous_seperator = found > addrstr ? *(found-1) : '\0'; -+ char next_seperator = *(found+strlen(tmpbuf)); -+ -+ if ((next_seperator == ',' || next_seperator == '\0') -+ && (previous_seperator == ',' || previous_seperator == '\0')) { -+ continue; -+ } -+ } -+ -+ number_addresses++; -+ strlcat(addrstr, ",", MAX_ADDR_LIST_LEN); -+ strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN); -+ } -+ } -+ - resolve_host_out: - freeaddrinfo(addrlist); - return rc; -Index: cifs-utils/resolve_host.h -=================================================================== ---- cifs-utils.orig/resolve_host.h -+++ cifs-utils/resolve_host.h -@@ -26,8 +26,10 @@ - /* currently maximum length of IPv6 address string */ - #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN - --/* limit list of addresses to 16 max-size addrs */ --#define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16) -+#define MAX_ADDRESSES 16 -+ -+/* limit list of addresses to MAX_ADDRESSES max-size addrs */ -+#define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * MAX_ADDRESSES) - - extern int resolve_host(const char *host, char *addrstr); - diff --git a/cifs-utils-destdir.patch b/cifs-utils-destdir.patch deleted file mode 100644 index 7124927..0000000 --- a/cifs-utils-destdir.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff -up cifs-utils-6.11/Makefile.am.orig cifs-utils-6.11/Makefile.am ---- cifs-utils-6.11/Makefile.am.orig 2020-11-02 10:12:50.076702518 +0200 -+++ cifs-utils-6.11/Makefile.am 2020-11-02 10:13:41.966420633 +0200 -@@ -119,11 +119,11 @@ endif - SUBDIRS = contrib - - install-exec-hook: -- (cd $(ROOTSBINDIR) && ln -sf mount.cifs mount.smb3) -+ (cd $(DESTDIR)$(ROOTSBINDIR) && ln -sf mount.cifs mount.smb3) - - install-data-hook: -- (cd $(man8dir) && ln -sf mount.cifs.8 mount.smb3.8) -+ (cd $(DESTDIR)$(man8dir) && ln -sf mount.cifs.8 mount.smb3.8) - - uninstall-hook: -- (cd $(ROOTSBINDIR) && rm -f $(ROOTSBINDIR)/mount.smb3) -- (cd $(man8dir) && rm -f $(man8dir)/mount.smb3.8) -+ (cd $(DESTDIR)$(ROOTSBINDIR) && rm -f $(DESTDIR)$(ROOTSBINDIR)/mount.smb3) -+ (cd $(DESTDIR)$(man8dir) && rm -f $(DESTDIR)$(man8dir)/mount.smb3.8) diff --git a/cifs-utils.spec b/cifs-utils.spec index 4f6ebce..689e644 100644 --- a/cifs-utils.spec +++ b/cifs-utils.spec @@ -1,8 +1,10 @@ #% define pre_release rc1 %define pre_release %nil +%global bash_completion_dir %(pkg-config --variable=completionsdir bash-completion || echo /etc/bash_completion.d) + Name: cifs-utils -Version: 7.0 +Version: 7.1 Release: %autorelease Summary: Utilities for mounting and managing CIFS mounts @@ -21,11 +23,8 @@ Requires(preun): /usr/sbin/alternatives Recommends: %{name}-info%{?_isa} = %{version}-%{release} Source0: https://download.samba.org/pub/linux-cifs/cifs-utils/%{name}-%{version}.tar.bz2 -Patch0: cifs.upcall-fix-UAF-in-get_cachename_from_process_en.patch -Patch1: pam_cifscreds-fix-warning-on-NULL-arg-passed-to-s-in.patch -Patch2: mount.cifs.rst-add-missing-reference-for-sssd.patch -Patch3: mount.cifs.rst-update-section-about-xattr-acl-suppor.patch -Patch4: Implement-CLDAP-Ping-to-find-the-closest-site.patch + +Patch0: smbinfo-bash-completion.patch %description The SMB/CIFS protocol is a standard file sharing protocol widely deployed @@ -57,15 +56,10 @@ for each user from somewhere. The pam_cifscreds module can be used to provide these credentials to the kernel automatically at login. %prep -%setup -q -n %{name}-%{version}%{pre_release} -%patch0 -p1 -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 -%patch4 -p1 +%autosetup -n %{name}-%{version}%{pre_release} -p1 %build -fgrep -r -l '/usr/bin/env python' | xargs -n1 sed -i 's@/usr/bin/env python.*@%python3@g' +grep -F -r -l '/usr/bin/env python' | xargs --no-run-if-empty -n1 sed -i 's@/usr/bin/env python.*@%python3@g' autoreconf -i %configure --prefix=/usr ROOTSBINDIR=%{_sbindir} make %{?_smp_mflags} @@ -77,9 +71,11 @@ mkdir -p %{buildroot}%{_sysconfdir}/%{name} mkdir -p %{buildroot}%{_sysconfdir}/request-key.d install -m 644 contrib/request-key.d/cifs.idmap.conf %{buildroot}%{_sysconfdir}/request-key.d install -m 644 contrib/request-key.d/cifs.spnego.conf %{buildroot}%{_sysconfdir}/request-key.d +install -Dpm 644 bash-completion/smbinfo %{buildroot}%{_datadir}%{bash_completion_dir}/smbinfo %files %doc +%license COPYING %{_bindir}/getcifsacl %{_bindir}/setcifsacl %{_bindir}/cifscreds @@ -97,6 +93,7 @@ install -m 644 contrib/request-key.d/cifs.spnego.conf %{buildroot}%{_sysconfdir} %{_mandir}/man8/mount.cifs.* %{_mandir}/man8/mount.smb3.* %{_mandir}/man8/idmapwb.* +%{_datadir}%{bash_completion_dir}/smbinfo %dir %{_sysconfdir}/cifs-utils %ghost %{_sysconfdir}/cifs-utils/idmap-plugin %config(noreplace) %{_sysconfdir}/request-key.d/cifs.idmap.conf diff --git a/cifs.upcall-fix-UAF-in-get_cachename_from_process_en.patch b/cifs.upcall-fix-UAF-in-get_cachename_from_process_en.patch deleted file mode 100644 index 156ff46..0000000 --- a/cifs.upcall-fix-UAF-in-get_cachename_from_process_en.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 73146385da0945c78af0fbdc08d2bf260db709d5 Mon Sep 17 00:00:00 2001 -From: Paulo Alcantara -Date: Fri, 8 Mar 2024 12:06:15 -0300 -Subject: [PATCH] cifs.upcall: fix UAF in get_cachename_from_process_env() - -Whether lseek(2) fails or @bufsize * 2 > ENV_BUF_MAX, then @buf would -end up being freed twice. For instance: - - cifs-utils-7.0/cifs.upcall.c:501: freed_arg: "free" frees "buf". - cifs-utils-7.0/cifs.upcall.c:524: double_free: Calling "free" frees - pointer "buf" which has already been freed. - 522| } - 523| out_close: - 524|-> free(buf); - 525| close(fd); - 526| return cachename; - -Fix this by setting @buf to NULL after freeing it to prevent UAF. - -Fixes: ed97e4ecab4e ("cifs.upcall: allow scraping of KRB5CCNAME out of initiating task's /proc//environ file") -Signed-off-by: Paulo Alcantara (Red Hat) ---- - cifs.upcall.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/cifs.upcall.c b/cifs.upcall.c -index 52c03280dbe0..ff6f2bd271bc 100644 ---- a/cifs.upcall.c -+++ b/cifs.upcall.c -@@ -498,10 +498,11 @@ retry: - /* We read to the end of the buffer. Double and try again */ - syslog(LOG_DEBUG, "%s: read to end of buffer (%zu bytes)\n", - __func__, bufsize); -- free(buf); -- bufsize *= 2; - if (lseek(fd, 0, SEEK_SET) < 0) - goto out_close; -+ free(buf); -+ buf = NULL; -+ bufsize *= 2; - goto retry; - } - --- -2.44.0 - diff --git a/mount.cifs.rst-add-missing-reference-for-sssd.patch b/mount.cifs.rst-add-missing-reference-for-sssd.patch deleted file mode 100644 index f5dbe18..0000000 --- a/mount.cifs.rst-add-missing-reference-for-sssd.patch +++ /dev/null @@ -1,49 +0,0 @@ -From e7ec0032898d855be144c0cdc9d9e3f78ae01bf2 Mon Sep 17 00:00:00 2001 -From: Paulo Alcantara -Date: Sun, 10 Mar 2024 22:24:24 -0300 -Subject: [PATCH 1/2] mount.cifs.rst: add missing reference for sssd - -Reference sssd in mount.cifs(8) as it can be used instead of winbind -via cifs.idmap utility. It's also enabled by default in most systems. - -Signed-off-by: Paulo Alcantara (Red Hat) ---- - mount.cifs.rst | 14 +++++++------- - 1 file changed, 7 insertions(+), 7 deletions(-) - -diff --git a/mount.cifs.rst b/mount.cifs.rst -index 3becf200e038..64127b23cf17 100644 ---- a/mount.cifs.rst -+++ b/mount.cifs.rst -@@ -773,10 +773,10 @@ specified in the following Microsoft TechNet document: - In order to map SIDs to/from UIDs and GIDs, the following is required: - - - a kernel upcall to the ``cifs.idmap`` utility set up via request-key.conf(5) --- winbind support configured via nsswitch.conf(5) and smb.conf(5) -+- winbind or sssd support configured via nsswitch.conf(5) - --Please refer to the respective manpages of cifs.idmap(8) and --winbindd(8) for more information. -+Please refer to the respective manpages of cifs.idmap(8), winbindd(8) -+and sssd(8) for more information. - - Security descriptors for a file object can be retrieved and set - directly using extended attribute named ``system.cifs_acl``. The -@@ -792,10 +792,10 @@ Some of the things to consider while using this mount option: - - The mapping between a CIFS/NTFS ACL and POSIX file permission bits - is imperfect and some ACL information may be lost in the - translation. --- If either upcall to cifs.idmap is not setup correctly or winbind is -- not configured and running, ID mapping will fail. In that case uid -- and gid will default to either to those values of the share or to -- the values of uid and/or gid mount options if specified. -+- If either upcall to cifs.idmap is not setup correctly or winbind or -+ sssd is not configured and running, ID mapping will fail. In that -+ case uid and gid will default to either to those values of the share -+ or to the values of uid and/or gid mount options if specified. - - ********************************** - ACCESSING FILES WITH BACKUP INTENT --- -2.44.0 - diff --git a/mount.cifs.rst-update-section-about-xattr-acl-suppor.patch b/mount.cifs.rst-update-section-about-xattr-acl-suppor.patch deleted file mode 100644 index 8cb506c..0000000 --- a/mount.cifs.rst-update-section-about-xattr-acl-suppor.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 4718e09e4b15b957bf9d729793bc3de7caad8134 Mon Sep 17 00:00:00 2001 -From: Paulo Alcantara -Date: Sun, 10 Mar 2024 22:24:25 -0300 -Subject: [PATCH 2/2] mount.cifs.rst: update section about xattr/acl support - -Update section about required xattr/acl support for UID/GID mapping. - -Signed-off-by: Paulo Alcantara (Red Hat) ---- - mount.cifs.rst | 26 +++++++++++++++++++------- - 1 file changed, 19 insertions(+), 7 deletions(-) - -diff --git a/mount.cifs.rst b/mount.cifs.rst -index 64127b23cf17..d82a13c932b3 100644 ---- a/mount.cifs.rst -+++ b/mount.cifs.rst -@@ -321,11 +321,12 @@ soft - noacl - Do not allow POSIX ACL operations even if server would support them. - -- The CIFS client can get and set POSIX ACLs (getfacl, setfacl) to Samba -- servers version 3.0.10 and later. Setting POSIX ACLs requires enabling -- both ``CIFS_XATTR`` and then ``CIFS_POSIX`` support in the CIFS -- configuration options when building the cifs module. POSIX ACL support -- can be disabled on a per mount basis by specifying ``noacl`` on mount. -+ The CIFS client can get and set POSIX ACLs (getfacl, setfacl) to -+ Samba servers version 3.0.10 and later. Setting POSIX ACLs requires -+ enabling both ``CONFIG_CIFS_XATTR`` and then ``CONFIG_CIFS_POSIX`` -+ support in the CIFS configuration options when building the cifs -+ module. POSIX ACL support can be disabled on a per mount basis by -+ specifying ``noacl`` on mount. - - cifsacl - This option is used to map CIFS/NTFS ACLs to/from Linux permission -@@ -762,8 +763,19 @@ bits, and POSIX ACL as user authentication model. This is the most - common authentication model for CIFS servers and is the one used by - Windows. - --Support for this requires both CIFS_XATTR and CIFS_ACL support in the --CIFS configuration options when building the cifs module. -+Support for this requires cifs kernel module built with both -+``CONFIG_CIFS_XATTR`` and ``CONFIG_CIFS_ACL`` options enabled. Since -+Linux 5.3, ``CONFIG_CIFS_ACL`` option no longer exists as CIFS/NTFS -+ACL support is always built into cifs kernel module. -+ -+Most distribution kernels will already have those options enabled by -+default, but you can still check if they are enabled with:: -+ -+ cat /lib/modules/$(uname -r)/build/.config -+ -+Alternatively, if kernel is configured with ``CONFIG_IKCONFIG_PROC``:: -+ -+ zcat /proc/config.gz - - A CIFS/NTFS ACL is mapped to file permission bits using an algorithm - specified in the following Microsoft TechNet document: --- -2.44.0 - diff --git a/pam_cifscreds-fix-warning-on-NULL-arg-passed-to-s-in.patch b/pam_cifscreds-fix-warning-on-NULL-arg-passed-to-s-in.patch deleted file mode 100644 index ae27a68..0000000 --- a/pam_cifscreds-fix-warning-on-NULL-arg-passed-to-s-in.patch +++ /dev/null @@ -1,40 +0,0 @@ -From dac330136368a9b8d9ccf8227f56ea35de57a4d2 Mon Sep 17 00:00:00 2001 -From: Paulo Alcantara -Date: Fri, 8 Mar 2024 13:25:22 -0300 -Subject: [PATCH] pam_cifscreds: fix warning on NULL arg passed to %s in - pam_syslog() -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Fix the following compiler warning with -Wformat-overflow in -cifscreds_pam_update(): - - pam_cifscreds.c: In function ‘cifscreds_pam_update’: - pam_cifscreds.c:340:83: warning: ‘%s’ directive argument is null - [-Wformat-overflow=] - 340 | pam_syslog(ph, LOG_ERR, "error: Update credential key for %s: %s", - | ^~ - -Fixes: cbbcd6e71c0a ("cifscreds: create PAM module to insert credentials at login") -Signed-off-by: Paulo Alcantara (Red Hat) ---- - pam_cifscreds.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/pam_cifscreds.c b/pam_cifscreds.c -index 5d99c2db3038..eb9851d52a7d 100644 ---- a/pam_cifscreds.c -+++ b/pam_cifscreds.c -@@ -338,7 +338,7 @@ static int cifscreds_pam_update(pam_handle_t *ph, const char *user, const char * - key_serial_t key = key_add(currentaddress, user, password, keytype); - if (key <= 0) { - pam_syslog(ph, LOG_ERR, "error: Update credential key for %s: %s", -- currentaddress, strerror(errno)); -+ (currentaddress ?: "(null)"), strerror(errno)); - } - } - --- -2.44.0 - diff --git a/smbinfo-bash-completion.patch b/smbinfo-bash-completion.patch new file mode 100644 index 0000000..384efd8 --- /dev/null +++ b/smbinfo-bash-completion.patch @@ -0,0 +1,47 @@ +From d69d2129c6476afbcbbe8dc6e2ed17f233084d85 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= +Date: Mon, 7 Oct 2024 21:48:31 +0200 +Subject: [PATCH] smbinfo: add bash completion support for filestreaminfo, + keys, gettconinfo +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Pavel Filipenský +--- + bash-completion/smbinfo | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/bash-completion/smbinfo b/bash-completion/smbinfo +index d56b581..ec0d8a4 100644 +--- a/bash-completion/smbinfo ++++ b/bash-completion/smbinfo +@@ -15,19 +15,22 @@ smb_info() + filemodeinfo + filepositioninfo + filestandardinfo ++ filestreaminfo + fsctl-getobjid + getcompression + setcompression + list-snapshots + quota +- secdesc" ++ secdesc ++ keys ++ gettconinfo" + case $prev in + '-v'|'-h') + return 0 + ;; + 'fileaccessinfo'|'filealigninfo'|'fileallinfo'|'filebasicinfo'|'fileeainfo'|'filefsfullsizeinfo'|\ +- 'fileinternalinfo'|'filemodeinfo'|'filepositioninfo'|'filestandardinfo'|'fsctl-getobjid'|\ +- 'getcompression'|'setcompression'|'list-snapshots'|'quota'|'secdesc') ++ 'fileinternalinfo'|'filemodeinfo'|'filepositioninfo'|'filestandardinfo'|'filestreaminfo'|'fsctl-getobjid'|\ ++ 'getcompression'|'setcompression'|'list-snapshots'|'quota'|'secdesc'|'keys'|'gettconinfo') + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -o dirnames -- ${cur:-""}) ) +-- +2.46.1 + diff --git a/sources b/sources index 242474a..a2bf2ac 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (cifs-utils-7.0.tar.bz2) = 4c57741af0c4567a78f352c73caca998881666a5ed36536275cfa775efd66ff1a44ebe539a8ed96c409c5b08a1378266964ce667a27e9fc7f2d43999c63dd0eb +SHA512 (cifs-utils-7.1.tar.bz2) = 9eda85b2767cd19c7f69843750450c3862596debf47f41d9ce07f3d7438225b700b260be9585d2f7c9962d3f4dd8434b1b647c9ba670962cf136ce7ad86f92ab