mirror of
https://github.com/bytedance/g3.git
synced 2026-04-28 03:30:31 +00:00
g3proxy: add ldap user group
This commit is contained in:
parent
ab2dd54a32
commit
9522860c0d
54 changed files with 2468 additions and 482 deletions
|
|
@ -513,6 +513,12 @@ The code should comply to these, but should be more compliant to existing popula
|
|||
|
||||
- [rfc4511](https://datatracker.ietf.org/doc/html/rfc4511)
|
||||
: Lightweight Directory Access Protocol (LDAP): The Protocol
|
||||
- [rfc4513](https://datatracker.ietf.org/doc/html/rfc4513)
|
||||
: Lightweight Directory Access Protocol (LDAP): Authentication Methods and Security Mechanisms
|
||||
- [rfc4516](https://datatracker.ietf.org/doc/html/rfc4516)
|
||||
: Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator
|
||||
- [rfc4519](https://datatracker.ietf.org/doc/html/rfc4519)
|
||||
: Lightweight Directory Access Protocol (LDAP): Schema for User Applications
|
||||
|
||||
## MQTT
|
||||
|
||||
|
|
|
|||
5
g3proxy/examples/ldap_user_auth/dynamic_users.json
Normal file
5
g3proxy/examples/ldap_user_auth/dynamic_users.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{
|
||||
"name": "euler"
|
||||
}
|
||||
]
|
||||
58
g3proxy/examples/ldap_user_auth/g3proxy.yaml
Normal file
58
g3proxy/examples/ldap_user_auth/g3proxy.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
|
||||
log: journal
|
||||
|
||||
user_group:
|
||||
- name: default
|
||||
type: ldap
|
||||
ldap_url: ldap://ldap.forumsys.com/dc=example,dc=com
|
||||
pool:
|
||||
min_idle_count: 1
|
||||
static_users:
|
||||
- name: gauss
|
||||
dst_port_filter:
|
||||
- 80
|
||||
- 443
|
||||
dst_host_filter_set:
|
||||
exact:
|
||||
# for ipinfo.io
|
||||
- ipinfo.io
|
||||
- 1.1.1.1
|
||||
child:
|
||||
# for myip.ipip.net
|
||||
- "ipip.net"
|
||||
regex:
|
||||
# for lumtest.com/myip.json
|
||||
- "lum[a-z]*[.]com$"
|
||||
source:
|
||||
type: file
|
||||
path: dynamic_users.json
|
||||
unmanaged_user:
|
||||
name: unmanaged
|
||||
|
||||
server:
|
||||
- name: socks
|
||||
escaper: default
|
||||
user_group: default
|
||||
type: socks_proxy
|
||||
enable_udp_associate: true
|
||||
listen:
|
||||
address: "[::]:11080"
|
||||
- name: http
|
||||
escaper: default
|
||||
user_group: default
|
||||
type: http_proxy
|
||||
listen:
|
||||
address: "[::]:13128"
|
||||
|
||||
resolver:
|
||||
- name: default
|
||||
type: c-ares
|
||||
|
||||
escaper:
|
||||
- name: default
|
||||
type: direct_fixed
|
||||
resolver: default
|
||||
resolve_strategy: IPv4First
|
||||
tcp_sock_speed_limit: 80M
|
||||
udp_sock_speed_limit: 10M
|
||||
136
g3proxy/src/auth/group/ldap/mod.rs
Normal file
136
g3proxy/src/auth/group/ldap/mod.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use arc_swap::ArcSwapOption;
|
||||
use arcstr::ArcStr;
|
||||
|
||||
use g3_types::auth::{Password, UserAuthError};
|
||||
use g3_types::metrics::{MetricTagMap, NodeName};
|
||||
|
||||
use super::BaseUserGroup;
|
||||
use crate::auth::{User, UserContext, UserType};
|
||||
use crate::config::auth::{LdapUserGroupConfig, UserGroupConfig};
|
||||
|
||||
mod protocol;
|
||||
use protocol::{LdapMessageReceiver, SimpleBindRequestEncoder};
|
||||
|
||||
mod pool;
|
||||
use pool::{LdapAuthPool, LdapAuthPoolHandle};
|
||||
|
||||
pub(crate) struct LdapUserGroup {
|
||||
base: BaseUserGroup<LdapUserGroupConfig>,
|
||||
pool_handle: LdapAuthPoolHandle,
|
||||
unmanaged_users: Mutex<AHashMap<ArcStr, Arc<User>>>,
|
||||
}
|
||||
|
||||
impl LdapUserGroup {
|
||||
pub(super) fn base(&self) -> &BaseUserGroup<LdapUserGroupConfig> {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub(super) fn clone_config(&self) -> LdapUserGroupConfig {
|
||||
(*self.base.config).clone()
|
||||
}
|
||||
|
||||
fn new(base: BaseUserGroup<LdapUserGroupConfig>) -> anyhow::Result<Self> {
|
||||
let pool_handle = LdapAuthPool::create(base.config.clone())?;
|
||||
Ok(LdapUserGroup {
|
||||
base,
|
||||
pool_handle,
|
||||
unmanaged_users: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn new_with_config(config: LdapUserGroupConfig) -> anyhow::Result<Arc<Self>> {
|
||||
let base = BaseUserGroup::new_with_config(config).await?;
|
||||
Self::new(base).map(Arc::new)
|
||||
}
|
||||
|
||||
pub(super) fn reload(&self, config: LdapUserGroupConfig) -> anyhow::Result<Arc<Self>> {
|
||||
let base = self.base.reload(config)?;
|
||||
Self::new(base).map(Arc::new)
|
||||
}
|
||||
|
||||
pub(super) async fn check_user_with_password(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &Password,
|
||||
server_name: &NodeName,
|
||||
server_extra_tags: &Arc<ArcSwapOption<MetricTagMap>>,
|
||||
) -> Result<UserContext, UserAuthError> {
|
||||
match &self.base.config.unmanaged_user {
|
||||
Some(unmanaged_user_config) => {
|
||||
self.pool_handle
|
||||
.check_username_password(username, password.as_original())
|
||||
.await?;
|
||||
|
||||
if let Some((user, user_type)) = self.base.get_user(username) {
|
||||
return Ok(UserContext::new(
|
||||
Some(username.into()),
|
||||
user,
|
||||
user_type,
|
||||
server_name,
|
||||
server_extra_tags,
|
||||
));
|
||||
}
|
||||
|
||||
let mut ht = self.unmanaged_users.lock().unwrap();
|
||||
match ht.entry(username.into()) {
|
||||
Entry::Occupied(o) => {
|
||||
let user = o.get().clone();
|
||||
Ok(UserContext::new(
|
||||
Some(username.into()),
|
||||
user.clone(),
|
||||
UserType::Unmanaged,
|
||||
server_name,
|
||||
server_extra_tags,
|
||||
))
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
let username = ArcStr::from(username);
|
||||
|
||||
let user = User::new_unmanaged(
|
||||
&username,
|
||||
self.base.config.basic_config().name(),
|
||||
unmanaged_user_config,
|
||||
)
|
||||
.map_err(|_| UserAuthError::NoSuchUser)?;
|
||||
let user = Arc::new(user);
|
||||
|
||||
v.insert(user.clone());
|
||||
|
||||
Ok(UserContext::new(
|
||||
Some(username),
|
||||
user.clone(),
|
||||
UserType::Unmanaged,
|
||||
server_name,
|
||||
server_extra_tags,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some((user, user_type)) = self.base.get_user(username) {
|
||||
self.pool_handle
|
||||
.check_username_password(username, password.as_original())
|
||||
.await?;
|
||||
Ok(UserContext::new(
|
||||
Some(username.into()),
|
||||
user,
|
||||
user_type,
|
||||
server_name,
|
||||
server_extra_tags,
|
||||
))
|
||||
} else {
|
||||
Err(UserAuthError::NoSuchUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
g3proxy/src/auth/group/ldap/pool/connect.rs
Normal file
166
g3proxy/src/auth/group/ldap/pool/connect.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use g3_io_ext::LimitedWriteExt;
|
||||
use g3_io_ext::openssl::MaybeSslStream;
|
||||
use g3_openssl::{SslConnector, SslStream};
|
||||
use g3_socket::BindAddr;
|
||||
use g3_types::codec::{LdapResult, LdapSequence};
|
||||
use g3_types::net::{Host, OpensslClientConfig};
|
||||
|
||||
use crate::auth::group::ldap::LdapMessageReceiver;
|
||||
use crate::config::auth::LdapUserGroupConfig;
|
||||
|
||||
pub(super) struct LdapTcpConnector {
|
||||
config: Arc<LdapUserGroupConfig>,
|
||||
}
|
||||
|
||||
pub(super) struct LdapTlsConnector {
|
||||
config: Arc<LdapUserGroupConfig>,
|
||||
tls_client: OpensslClientConfig,
|
||||
}
|
||||
|
||||
pub(super) enum LdapConnector {
|
||||
Tcp(LdapTcpConnector),
|
||||
Tls(LdapTlsConnector),
|
||||
StartTls(LdapTlsConnector),
|
||||
}
|
||||
|
||||
impl LdapConnector {
|
||||
pub(super) fn new(config: Arc<LdapUserGroupConfig>) -> anyhow::Result<Self> {
|
||||
let connector = match &config.tls_client {
|
||||
Some(builder) => {
|
||||
let tls_client = builder.build().context("failed to build tls client")?;
|
||||
if config.direct_tls {
|
||||
LdapConnector::Tls(LdapTlsConnector { config, tls_client })
|
||||
} else {
|
||||
LdapConnector::StartTls(LdapTlsConnector { config, tls_client })
|
||||
}
|
||||
}
|
||||
None => LdapConnector::Tcp(LdapTcpConnector { config }),
|
||||
};
|
||||
Ok(connector)
|
||||
}
|
||||
|
||||
pub(super) async fn connect(&self) -> anyhow::Result<MaybeSslStream<TcpStream>> {
|
||||
match self {
|
||||
LdapConnector::Tcp(c) => {
|
||||
let stream = c.connect().await?;
|
||||
Ok(MaybeSslStream::Plain(stream))
|
||||
}
|
||||
LdapConnector::Tls(c) => {
|
||||
let ssl_stream = c.direct_connect().await?;
|
||||
Ok(MaybeSslStream::Ssl(ssl_stream))
|
||||
}
|
||||
LdapConnector::StartTls(c) => {
|
||||
let ssl_stream = c.starttls_connect().await?;
|
||||
Ok(MaybeSslStream::Ssl(ssl_stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapTcpConnector {
|
||||
async fn connect(&self) -> anyhow::Result<TcpStream> {
|
||||
let peer = match self.config.server.host() {
|
||||
Host::Ip(ip) => SocketAddr::new(*ip, self.config.server.port()),
|
||||
Host::Domain(domain) => {
|
||||
let addrs =
|
||||
tokio::net::lookup_host(format!("{domain}:{}", self.config.server.port()))
|
||||
.await?
|
||||
.collect::<Vec<_>>();
|
||||
fastrand::choice(addrs)
|
||||
.ok_or_else(|| anyhow!("no address resolved for domain {domain}"))?
|
||||
}
|
||||
};
|
||||
let socket = g3_socket::tcp::new_socket_to(
|
||||
peer.ip(),
|
||||
&BindAddr::None,
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
true,
|
||||
)
|
||||
.map_err(|e| anyhow!("setup socket failed: {e}"))?;
|
||||
tokio::time::timeout(self.config.connect_timeout, socket.connect(peer))
|
||||
.await
|
||||
.map_err(|_| anyhow!("timed out connecting to peer {peer}"))?
|
||||
.map_err(|e| anyhow!("can't connect to peer {peer}: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapTlsConnector {
|
||||
async fn direct_connect(&self) -> anyhow::Result<SslStream<TcpStream>> {
|
||||
let stream = self.tcp_connect().await?;
|
||||
self.tls_handshake(stream).await
|
||||
}
|
||||
|
||||
async fn starttls_connect(&self) -> anyhow::Result<SslStream<TcpStream>> {
|
||||
let mut stream = self.tcp_connect().await?;
|
||||
self.starttls(&mut stream).await?;
|
||||
self.tls_handshake(stream).await
|
||||
}
|
||||
|
||||
async fn tcp_connect(&self) -> anyhow::Result<TcpStream> {
|
||||
let tcp_connector = LdapTcpConnector {
|
||||
config: self.config.clone(),
|
||||
};
|
||||
tcp_connector.connect().await
|
||||
}
|
||||
|
||||
async fn tls_handshake(&self, stream: TcpStream) -> anyhow::Result<SslStream<TcpStream>> {
|
||||
let ssl = self
|
||||
.tls_client
|
||||
.build_ssl(self.config.server.host(), self.config.server.port())
|
||||
.map_err(|e| anyhow!("build ssl context failed: {e}"))?;
|
||||
let tls_connector = SslConnector::new(ssl, stream)
|
||||
.map_err(|e| anyhow!("build ssl connector failed: {e}"))?;
|
||||
tokio::time::timeout(self.tls_client.handshake_timeout, tls_connector.connect())
|
||||
.await
|
||||
.map_err(|_| anyhow!("tls handshake with peer {} timed out", self.config.server))?
|
||||
.map_err(|e| anyhow!("tls connect failed: {e}"))
|
||||
}
|
||||
|
||||
async fn starttls(&self, stream: &mut TcpStream) -> anyhow::Result<()> {
|
||||
let starttls_message: [u8; _] = [
|
||||
0x30, 0x1d, // Begin the LDAPMessage sequence
|
||||
0x02, 0x01, 0x7f, // The message ID (integer value 0x7f)
|
||||
0x77, 0x18, // Begin the extended request protocol op
|
||||
0x80, 0x16, // Begin the extended request OID "1.3.6.1.4.1.1466.20037"
|
||||
b'1', b'.', b'3', b'.', b'6', b'.', b'1', b'.', b'4', b'.', b'1', b'.', b'1', b'4',
|
||||
b'6', b'6', b'.', b'2', b'0', b'0', b'3', b'7',
|
||||
];
|
||||
stream
|
||||
.write_all_flush(&starttls_message)
|
||||
.await
|
||||
.map_err(|e| anyhow!("failed to send StartTls extended request: {e}"))?;
|
||||
|
||||
let mut rsp_receiver = LdapMessageReceiver::new(48);
|
||||
|
||||
let rsp = tokio::time::timeout(self.config.response_timeout, rsp_receiver.recv(stream))
|
||||
.await
|
||||
.map_err(|_| anyhow!("timed out when waiting for STARTTLS response"))?
|
||||
.map_err(|e| anyhow!("failed to read StartTls response: {e}"))?;
|
||||
|
||||
let rsp_sequence = LdapSequence::parse_extended_response(rsp.payload())?;
|
||||
let data = rsp_sequence.data();
|
||||
let result = LdapResult::parse(data)?;
|
||||
let left = &data[result.encoded_len()..];
|
||||
let oid = LdapSequence::parse_extended_response_oid(left)?;
|
||||
if oid.data() == b"1.3.6.1.4.1.1466.20037" {
|
||||
Err(anyhow!(
|
||||
"unexpected StartTls response payload: {:?}",
|
||||
rsp.payload()
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
181
g3proxy/src/auth/group/ldap/pool/mod.rs
Normal file
181
g3proxy/src/auth/group/ldap/pool/mod.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use g3_types::auth::UserAuthError;
|
||||
|
||||
use crate::config::auth::LdapUserGroupConfig;
|
||||
|
||||
mod connect;
|
||||
use connect::LdapConnector;
|
||||
|
||||
mod task;
|
||||
use task::LdapAuthTask;
|
||||
|
||||
enum PoolCommand {
|
||||
Exit,
|
||||
NeedMoreConnection,
|
||||
ConnectionClosed,
|
||||
}
|
||||
|
||||
struct LdapAuthRequest {
|
||||
uid: String,
|
||||
password: String,
|
||||
retry: bool,
|
||||
result_sender: oneshot::Sender<Option<(String, String)>>,
|
||||
}
|
||||
|
||||
pub(super) struct LdapAuthPoolHandle {
|
||||
config: Arc<LdapUserGroupConfig>,
|
||||
req_sender: kanal::AsyncSender<LdapAuthRequest>,
|
||||
cmd_sender: mpsc::Sender<PoolCommand>,
|
||||
}
|
||||
|
||||
impl Drop for LdapAuthPoolHandle {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.cmd_sender.try_send(PoolCommand::Exit);
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapAuthPoolHandle {
|
||||
pub(super) async fn check_username_password(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), UserAuthError> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let req = LdapAuthRequest {
|
||||
uid: username.to_string(),
|
||||
password: password.to_string(),
|
||||
retry: true,
|
||||
result_sender: sender,
|
||||
};
|
||||
|
||||
if self.req_sender.is_full() {
|
||||
let _ = self.cmd_sender.try_send(PoolCommand::NeedMoreConnection);
|
||||
}
|
||||
let _ = self.req_sender.send(req).await;
|
||||
|
||||
match tokio::time::timeout(self.config.queue_wait_timeout, receiver).await {
|
||||
Ok(Ok(Some(_))) => Ok(()),
|
||||
Ok(Ok(None)) => Err(UserAuthError::TokenNotMatch),
|
||||
Ok(Err(_)) => Err(UserAuthError::RemoteError),
|
||||
Err(_) => Err(UserAuthError::RemoteTimeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct LdapAuthPool {
|
||||
config: Arc<LdapUserGroupConfig>,
|
||||
connector: Arc<LdapConnector>,
|
||||
req_receiver: kanal::AsyncReceiver<LdapAuthRequest>,
|
||||
cmd_sender: mpsc::Sender<PoolCommand>,
|
||||
cmd_receiver: mpsc::Receiver<PoolCommand>,
|
||||
idle_conn_count: Arc<AtomicUsize>,
|
||||
expected_idle_count: usize,
|
||||
}
|
||||
|
||||
impl LdapAuthPool {
|
||||
pub(super) fn create(config: Arc<LdapUserGroupConfig>) -> anyhow::Result<LdapAuthPoolHandle> {
|
||||
let connector = LdapConnector::new(config.clone())?;
|
||||
let connector = Arc::new(connector);
|
||||
|
||||
let (req_sender, req_receiver) = kanal::bounded_async(config.queue_channel_size);
|
||||
let (cmd_sender, cmd_receiver) = mpsc::channel(config.connection_pool.max_idle_count());
|
||||
|
||||
let pool = LdapAuthPool {
|
||||
config: config.clone(),
|
||||
connector,
|
||||
req_receiver,
|
||||
cmd_sender: cmd_sender.clone(),
|
||||
cmd_receiver,
|
||||
idle_conn_count: Arc::new(AtomicUsize::new(0)),
|
||||
expected_idle_count: config.connection_pool.min_idle_count(),
|
||||
};
|
||||
tokio::spawn(async move { pool.into_running().await });
|
||||
|
||||
Ok(LdapAuthPoolHandle {
|
||||
config,
|
||||
req_sender,
|
||||
cmd_sender,
|
||||
})
|
||||
}
|
||||
|
||||
async fn into_running(mut self) {
|
||||
let mut check_interval =
|
||||
tokio::time::interval(self.config.connection_pool.check_interval());
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
r = self.cmd_receiver.recv() => {
|
||||
match r {
|
||||
Some(PoolCommand::Exit) => {
|
||||
return;
|
||||
}
|
||||
Some(PoolCommand::ConnectionClosed) => {
|
||||
if self.idle_conn_count() < self.expected_idle_count {
|
||||
self.create_connection();
|
||||
}
|
||||
}
|
||||
Some(PoolCommand::NeedMoreConnection) => {
|
||||
if self.expected_idle_count < self.config.connection_pool.max_idle_count() {
|
||||
self.expected_idle_count += 1;
|
||||
}
|
||||
if self.idle_conn_count() < self.config.connection_pool.max_idle_count() {
|
||||
self.create_connection();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = check_interval.tick() => {
|
||||
self.check();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn idle_conn_count(&self) -> usize {
|
||||
self.idle_conn_count.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn check(&mut self) {
|
||||
if self.expected_idle_count > self.config.connection_pool.min_idle_count() {
|
||||
let decrease =
|
||||
(self.config.connection_pool.min_idle_count() - self.expected_idle_count) / 4;
|
||||
if decrease > 0 {
|
||||
self.expected_idle_count -= decrease;
|
||||
} else {
|
||||
self.expected_idle_count -= 1;
|
||||
}
|
||||
}
|
||||
let current_idle_count = self.idle_conn_count();
|
||||
if current_idle_count < self.expected_idle_count {
|
||||
for _i in current_idle_count..self.expected_idle_count {
|
||||
self.create_connection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_connection(&self) {
|
||||
let task = LdapAuthTask::new(self.config.clone(), self.connector.clone());
|
||||
let idle_count = self.idle_conn_count.clone();
|
||||
let req_receiver = self.req_receiver.clone();
|
||||
let cmd_sender = self.cmd_sender.clone();
|
||||
|
||||
idle_count.fetch_add(1, Ordering::Relaxed);
|
||||
tokio::spawn(async move {
|
||||
task.run(req_receiver).await;
|
||||
idle_count.fetch_sub(1, Ordering::Relaxed);
|
||||
let _ = cmd_sender.send(PoolCommand::ConnectionClosed).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
207
g3proxy/src/auth/group/ldap/pool/task.rs
Normal file
207
g3proxy/src/auth/group/ldap/pool/task.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use kanal::AsyncReceiver;
|
||||
use log::{debug, warn};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use g3_io_ext::openssl::MaybeSslStream;
|
||||
use g3_io_ext::{AsyncStream, LimitedWriteExt};
|
||||
use g3_types::codec::{LdapResult, LdapSequence};
|
||||
|
||||
use super::{LdapAuthRequest, LdapConnector};
|
||||
use crate::auth::group::ldap::{LdapMessageReceiver, SimpleBindRequestEncoder};
|
||||
use crate::config::auth::LdapUserGroupConfig;
|
||||
|
||||
pub(super) struct LdapAuthTask {
|
||||
config: Arc<LdapUserGroupConfig>,
|
||||
connector: Arc<LdapConnector>,
|
||||
quit: bool,
|
||||
pending_request: Option<LdapAuthRequest>,
|
||||
request_encoder: SimpleBindRequestEncoder,
|
||||
}
|
||||
|
||||
impl LdapAuthTask {
|
||||
pub(super) fn new(config: Arc<LdapUserGroupConfig>, connector: Arc<LdapConnector>) -> Self {
|
||||
LdapAuthTask {
|
||||
config,
|
||||
connector,
|
||||
quit: false,
|
||||
pending_request: None,
|
||||
request_encoder: SimpleBindRequestEncoder::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(mut self, receiver: AsyncReceiver<LdapAuthRequest>) {
|
||||
loop {
|
||||
let r = match self.connector.connect().await {
|
||||
Ok(MaybeSslStream::Plain(stream)) => self.run_with_stream(stream, &receiver).await,
|
||||
Ok(MaybeSslStream::Ssl(stream)) => self.run_with_stream(stream, &receiver).await,
|
||||
Err(e) => Err(anyhow!("failed to connect to ldap server: {e}")),
|
||||
};
|
||||
if let Err(e) = r {
|
||||
warn!("close connection on error: {e}");
|
||||
}
|
||||
|
||||
if let Some(mut request) = self.pending_request
|
||||
&& request.retry
|
||||
{
|
||||
request.retry = false;
|
||||
self.pending_request = Some(request);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_stream<S>(
|
||||
&mut self,
|
||||
stream: S,
|
||||
receiver: &AsyncReceiver<LdapAuthRequest>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
S: AsyncStream,
|
||||
S::R: AsyncRead + Unpin,
|
||||
S::W: AsyncWrite + Unpin,
|
||||
{
|
||||
self.request_encoder.reset();
|
||||
let (mut reader, mut writer) = stream.into_split();
|
||||
let mut response_receiver = LdapMessageReceiver::new(self.config.max_message_size);
|
||||
|
||||
loop {
|
||||
if let Some(r) = self.pending_request.take() {
|
||||
let message_id = match self.send_simple_bind_request(&mut writer, &r).await {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
self.pending_request = Some(r);
|
||||
return Err(anyhow!("send simple bind request error: {e}"));
|
||||
}
|
||||
};
|
||||
|
||||
match tokio::time::timeout(
|
||||
self.config.response_timeout,
|
||||
response_receiver.recv(&mut reader),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.id() == 0 {
|
||||
self.pending_request = Some(r);
|
||||
let reconnect = self
|
||||
.handle_unsolicited_notification(message.payload())
|
||||
.map_err(|e| anyhow!("invalid unsolicited notification: {e}"))?;
|
||||
if reconnect {
|
||||
return Ok(());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if message.id() != message_id {
|
||||
self.pending_request = Some(r);
|
||||
debug!("unexpected response for message {}", message.id());
|
||||
continue;
|
||||
} else {
|
||||
self.handle_response(message.payload(), r)
|
||||
.map_err(|e| anyhow!("invalid response: {e}"))?;
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.pending_request = Some(r);
|
||||
return Err(anyhow!("recv ldap response message error: {e}"));
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = r.result_sender.send(None);
|
||||
return Err(anyhow!("recv ldap response message timed out"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
r = receiver.recv() => {
|
||||
match r {
|
||||
Ok(r) => self.pending_request = Some(r),
|
||||
Err(_) => {
|
||||
self.quit = true;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
r = response_receiver.recv(&mut reader) => {
|
||||
// detect the close of ldap server
|
||||
match r {
|
||||
Ok(message) => {
|
||||
if message.id() != 0 {
|
||||
debug!("unexpected response received for message {}", message.id());
|
||||
} else {
|
||||
let reconnect = self
|
||||
.handle_unsolicited_notification(message.payload())
|
||||
.map_err(|e| anyhow!("invalid unsolicited notification: {e}"))?;
|
||||
if reconnect {
|
||||
return Ok(());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(anyhow!("ldap connection closed with error {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_simple_bind_request<W>(
|
||||
&mut self,
|
||||
writer: &mut W,
|
||||
r: &LdapAuthRequest,
|
||||
) -> anyhow::Result<u32>
|
||||
where
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
||||
let request_msg = self
|
||||
.request_encoder
|
||||
.encode(&r.uid, &r.password, &self.config.base_dn);
|
||||
writer
|
||||
.write_all_flush(request_msg)
|
||||
.await
|
||||
.map_err(|e| anyhow!("failed to write bind request: {e}"))?;
|
||||
Ok(self.request_encoder.message_id())
|
||||
}
|
||||
|
||||
fn handle_unsolicited_notification(&self, op_data: &[u8]) -> anyhow::Result<bool> {
|
||||
let rsp_sequence = LdapSequence::parse_extended_response(op_data)?;
|
||||
let data = rsp_sequence.data();
|
||||
let result = LdapResult::parse(data)?;
|
||||
let left = &data[result.encoded_len()..];
|
||||
let oid = LdapSequence::parse_extended_response_oid(left)?;
|
||||
if oid.data() == b"1.3.6.1.4.1.1466.20036" {
|
||||
// The notice of disconnection unsolicited notification OID
|
||||
Ok(true)
|
||||
} else {
|
||||
// TODO log other OID
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_response(&self, op_data: &[u8], r: LdapAuthRequest) -> anyhow::Result<()> {
|
||||
let rsp_sequence = LdapSequence::parse_bind_response(op_data)?;
|
||||
let data = rsp_sequence.data();
|
||||
let result = LdapResult::parse(data)?;
|
||||
if result.result_code() == 0 {
|
||||
let _ = r.result_sender.send(Some((r.uid, r.password)));
|
||||
} else {
|
||||
// TODO log error
|
||||
let _ = r.result_sender.send(None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
80
g3proxy/src/auth/group/ldap/protocol/message.rs
Normal file
80
g3proxy/src/auth/group/ldap/protocol/message.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use anyhow::anyhow;
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
use g3_io_ext::LimitedReadExt;
|
||||
use g3_types::codec::{LdapMessage, LdapMessageParseError};
|
||||
|
||||
pub(crate) struct LdapMessageReceiver {
|
||||
max_message_size: usize,
|
||||
buffer: Box<[u8]>,
|
||||
received_len: usize,
|
||||
cur_message_len: usize,
|
||||
}
|
||||
|
||||
impl LdapMessageReceiver {
|
||||
pub(crate) fn new(max_message_size: usize) -> Self {
|
||||
let buffer_size = max_message_size + 10;
|
||||
LdapMessageReceiver {
|
||||
max_message_size,
|
||||
buffer: vec![0; buffer_size].into_boxed_slice(),
|
||||
received_len: 0,
|
||||
cur_message_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_cur_message(&mut self) {
|
||||
if self.cur_message_len == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let left_size = self.received_len - self.cur_message_len;
|
||||
if left_size > 0 {
|
||||
self.buffer
|
||||
.copy_within(self.cur_message_len..self.received_len, 0);
|
||||
}
|
||||
self.received_len = left_size;
|
||||
self.cur_message_len = 0;
|
||||
}
|
||||
|
||||
pub(crate) async fn recv<R>(&mut self, reader: &mut R) -> anyhow::Result<LdapMessage<'_>>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
self.consume_cur_message();
|
||||
|
||||
// to workaround limitations of rust borrow checker
|
||||
let buffer_ptr = self.buffer.as_mut_ptr();
|
||||
let shadow_buffer = unsafe {
|
||||
std::ptr::slice_from_raw_parts_mut(buffer_ptr, self.buffer.len())
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
loop {
|
||||
if self.received_len > 0 {
|
||||
match LdapMessage::parse(&self.buffer[..self.received_len], self.max_message_size) {
|
||||
Ok(message) => {
|
||||
self.cur_message_len = message.encoded_size();
|
||||
return Ok(message);
|
||||
}
|
||||
Err(LdapMessageParseError::NeedMoreData(_)) => {}
|
||||
Err(e) => return Err(anyhow!("invalid ldap response message received: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
let nr = reader
|
||||
.read_all_once(&mut shadow_buffer[self.received_len..])
|
||||
.await
|
||||
.map_err(|e| anyhow!("read io error: {e}"))?;
|
||||
if nr == 0 {
|
||||
return Err(anyhow!("ldap connection closed unexpected"));
|
||||
}
|
||||
self.received_len += nr;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
g3proxy/src/auth/group/ldap/protocol/mod.rs
Normal file
10
g3proxy/src/auth/group/ldap/protocol/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod request;
|
||||
pub(super) use request::SimpleBindRequestEncoder;
|
||||
|
||||
mod message;
|
||||
pub(super) use message::LdapMessageReceiver;
|
||||
122
g3proxy/src/auth/group/ldap/protocol/request.rs
Normal file
122
g3proxy/src/auth/group/ldap/protocol/request.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use g3_types::codec::BerLengthEncoder;
|
||||
|
||||
const MAX_MESSAGE_ID: u8 = 0x7F;
|
||||
const MIN_MESSAGE_ID: u8 = 1;
|
||||
|
||||
pub(crate) struct SimpleBindRequestEncoder {
|
||||
message_id: u8,
|
||||
bind_dn_length_encoder: BerLengthEncoder,
|
||||
password_length_encoder: BerLengthEncoder,
|
||||
request_length_encoder: BerLengthEncoder,
|
||||
message_length_encoder: BerLengthEncoder,
|
||||
request_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for SimpleBindRequestEncoder {
|
||||
fn default() -> Self {
|
||||
SimpleBindRequestEncoder {
|
||||
message_id: MAX_MESSAGE_ID,
|
||||
bind_dn_length_encoder: Default::default(),
|
||||
password_length_encoder: Default::default(),
|
||||
request_length_encoder: Default::default(),
|
||||
message_length_encoder: Default::default(),
|
||||
request_buf: Vec::with_capacity(256),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleBindRequestEncoder {
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.message_id = 1;
|
||||
}
|
||||
|
||||
pub(crate) fn message_id(&self) -> u32 {
|
||||
self.message_id as u32
|
||||
}
|
||||
|
||||
pub(crate) fn encode(&mut self, username: &str, password: &str, base_dn: &str) -> &[u8] {
|
||||
self.message_id += 1;
|
||||
if self.message_id > MAX_MESSAGE_ID {
|
||||
self.message_id = MIN_MESSAGE_ID;
|
||||
}
|
||||
|
||||
let bind_dn = format!("uid={username},{base_dn}");
|
||||
let bind_dn_len = bind_dn.len();
|
||||
let bind_dn_length_bytes = self.bind_dn_length_encoder.encode(bind_dn_len);
|
||||
let bind_dn_encoded_len = 1 + bind_dn_length_bytes.len() + bind_dn_len;
|
||||
|
||||
let password_len = password.len();
|
||||
let password_length_bytes = self.password_length_encoder.encode(password_len);
|
||||
let password_encoded_len = 1 + password_length_bytes.len() + password_len;
|
||||
|
||||
let request_len = 3 + bind_dn_encoded_len + password_encoded_len;
|
||||
let request_length_bytes = self.request_length_encoder.encode(request_len);
|
||||
let request_encoded_len = 1 + request_length_bytes.len() + request_len;
|
||||
|
||||
let message_len = 3 + request_encoded_len;
|
||||
let message_length_bytes = self.message_length_encoder.encode(message_len);
|
||||
let message_encoded_len = 1 + message_length_bytes.len() + message_len;
|
||||
|
||||
self.request_buf.clear();
|
||||
self.request_buf.reserve(message_encoded_len);
|
||||
|
||||
// Begin the LDAPMessage sequence
|
||||
self.request_buf.push(0x30);
|
||||
self.request_buf.extend_from_slice(message_length_bytes);
|
||||
|
||||
// The message ID
|
||||
self.request_buf.push(0x02);
|
||||
self.request_buf.push(0x01);
|
||||
self.request_buf.push(self.message_id); // the message is always <= 0x7F
|
||||
|
||||
// Begin the bind request protocol op
|
||||
self.request_buf.push(0x60);
|
||||
self.request_buf.extend_from_slice(request_length_bytes);
|
||||
|
||||
// The LDAP protocol version (integer value 3)
|
||||
self.request_buf.extend_from_slice(&[0x02, 0x01, 0x03]);
|
||||
|
||||
// The bind DN
|
||||
self.request_buf.push(0x04);
|
||||
self.request_buf.extend_from_slice(bind_dn_length_bytes);
|
||||
self.request_buf.extend_from_slice(bind_dn.as_bytes());
|
||||
|
||||
// The password
|
||||
self.request_buf.push(0x80);
|
||||
self.request_buf.extend_from_slice(password_length_bytes);
|
||||
self.request_buf.extend_from_slice(password.as_bytes());
|
||||
|
||||
&self.request_buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encode() {
|
||||
let mut encoder = SimpleBindRequestEncoder::default();
|
||||
let base_dn = "ou=People,dc=example,dc=com";
|
||||
|
||||
let request = encoder.encode("jdoe", "secret123", base_dn);
|
||||
assert_eq!(
|
||||
request,
|
||||
[
|
||||
0x30, 0x39, // Begin the LDAPMessage sequence
|
||||
0x02, 0x01, 0x01, // The message ID (integer value 1)
|
||||
0x60, 0x34, // Begin the bind request protocol op
|
||||
0x02, 0x01, 0x03, // The LDAP protocol version (integer value 3)
|
||||
0x04, 0x24, b'u', b'i', b'd', b'=', b'j', b'd', b'o', b'e', b',', b'o', b'u', b'=',
|
||||
b'P', b'e', b'o', b'p', b'l', b'e', b',', b'd', b'c', b'=', b'e', b'x', b'a', b'm',
|
||||
b'p', b'l', b'e', b',', b'd', b'c', b'=', b'c', b'o', b'm', // base dn
|
||||
0x80, 0x09, b's', b'e', b'c', b'r', b'e', b't', b'1', b'2', b'3', // password
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,10 +28,14 @@ pub(crate) use basic::BasicUserGroup;
|
|||
mod facts;
|
||||
pub(crate) use facts::FactsUserGroup;
|
||||
|
||||
mod ldap;
|
||||
pub(crate) use ldap::LdapUserGroup;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum UserGroup {
|
||||
Basic(Arc<BasicUserGroup>),
|
||||
Facts(Arc<FactsUserGroup>),
|
||||
Ldap(Arc<LdapUserGroup>),
|
||||
}
|
||||
|
||||
impl UserGroup {
|
||||
|
|
@ -39,6 +43,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().r#type(),
|
||||
UserGroup::Facts(v) => v.base().r#type(),
|
||||
UserGroup::Ldap(v) => v.base().r#type(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +57,10 @@ impl UserGroup {
|
|||
let c = v.clone_config();
|
||||
AnyUserGroupConfig::Facts(c)
|
||||
}
|
||||
UserGroup::Ldap(v) => {
|
||||
let c = v.clone_config();
|
||||
AnyUserGroupConfig::Ldap(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +79,10 @@ impl UserGroup {
|
|||
let group = FactsUserGroup::new_with_config(c).await?;
|
||||
Ok(UserGroup::Facts(group))
|
||||
}
|
||||
AnyUserGroupConfig::Ldap(c) => {
|
||||
let group = LdapUserGroup::new_with_config(c).await?;
|
||||
Ok(UserGroup::Ldap(group))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +96,10 @@ impl UserGroup {
|
|||
let group = g.reload(c)?;
|
||||
Ok(UserGroup::Facts(group))
|
||||
}
|
||||
(UserGroup::Ldap(g), AnyUserGroupConfig::Ldap(c)) => {
|
||||
let group = g.reload(c)?;
|
||||
Ok(UserGroup::Ldap(group))
|
||||
}
|
||||
(_, config) => Err(anyhow!(
|
||||
"reload user group {} type {} to {} is invalid",
|
||||
config.name(),
|
||||
|
|
@ -96,6 +113,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().stop_fetch_job(),
|
||||
UserGroup::Facts(v) => v.base().stop_fetch_job(),
|
||||
UserGroup::Ldap(v) => v.base().stop_fetch_job(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +121,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().allow_anonymous(client_addr),
|
||||
UserGroup::Facts(v) => v.base().allow_anonymous(client_addr),
|
||||
UserGroup::Ldap(v) => v.base().allow_anonymous(client_addr),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +129,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().get_anonymous_user(),
|
||||
UserGroup::Facts(v) => v.base().get_anonymous_user(),
|
||||
UserGroup::Ldap(v) => v.base().get_anonymous_user(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +140,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().foreach_user(f),
|
||||
UserGroup::Facts(v) => v.base().foreach_user(f),
|
||||
UserGroup::Ldap(v) => v.base().foreach_user(f),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +148,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().all_static_users(),
|
||||
UserGroup::Facts(v) => v.base().all_static_users(),
|
||||
UserGroup::Ldap(v) => v.base().all_static_users(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +156,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().all_dynamic_users(),
|
||||
UserGroup::Facts(v) => v.base().all_dynamic_users(),
|
||||
UserGroup::Ldap(v) => v.base().all_dynamic_users(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +164,7 @@ impl UserGroup {
|
|||
match self {
|
||||
UserGroup::Basic(v) => v.base().publish_dynamic_users(contents).await,
|
||||
UserGroup::Facts(v) => v.base().publish_dynamic_users(contents).await,
|
||||
UserGroup::Ldap(v) => v.base().publish_dynamic_users(contents).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,10 +187,15 @@ impl UserGroup {
|
|||
v.rebuild_match_table();
|
||||
Ok(())
|
||||
}
|
||||
UserGroup::Ldap(v) => {
|
||||
v.base()
|
||||
.save_dynamic_users(contents, dynamic_config, Some(dynamic_key))
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_user_with_password(
|
||||
pub(crate) async fn check_user_with_password(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &Password,
|
||||
|
|
@ -181,6 +210,10 @@ impl UserGroup {
|
|||
server_extra_tags,
|
||||
),
|
||||
UserGroup::Facts(_) => Err(UserAuthError::NoSuchUser),
|
||||
UserGroup::Ldap(v) => {
|
||||
v.check_user_with_password(username, password, server_name, server_extra_tags)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ mod source;
|
|||
pub(crate) enum UserType {
|
||||
Static,
|
||||
Dynamic,
|
||||
Unmanaged,
|
||||
Anonymous,
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ impl UserType {
|
|||
match self {
|
||||
UserType::Static => "Static",
|
||||
UserType::Dynamic => "Dynamic",
|
||||
UserType::Unmanaged => "Unmanaged",
|
||||
UserType::Anonymous => "Anonymous",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use crate::config::auth::{UserAuditConfig, UserConfig};
|
|||
|
||||
pub(crate) struct User {
|
||||
config: Arc<UserConfig>,
|
||||
name: ArcStr,
|
||||
group: NodeName,
|
||||
started: Instant,
|
||||
is_expired: AtomicBool,
|
||||
|
|
@ -88,6 +89,24 @@ impl User {
|
|||
group: &NodeName,
|
||||
config: &Arc<UserConfig>,
|
||||
datetime_now: &DateTime<Utc>,
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::new_with_name(config.name().clone(), group, config, datetime_now)
|
||||
}
|
||||
|
||||
pub(super) fn new_unmanaged(
|
||||
name: &ArcStr,
|
||||
group: &NodeName,
|
||||
config: &Arc<UserConfig>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let now = Utc::now();
|
||||
Self::new_with_name(name.clone(), group, config, &now)
|
||||
}
|
||||
|
||||
fn new_with_name(
|
||||
name: ArcStr,
|
||||
group: &NodeName,
|
||||
config: &Arc<UserConfig>,
|
||||
datetime_now: &DateTime<Utc>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let request_rate_limit = config
|
||||
.request_rate_limit
|
||||
|
|
@ -133,10 +152,11 @@ impl User {
|
|||
let is_expired = AtomicBool::new(config.is_expired(datetime_now));
|
||||
let is_blocked = Arc::new(AtomicBool::new(config.block_and_delay.is_some()));
|
||||
|
||||
let explicit_sites = UserSites::new(config.explicit_sites.values(), config.name(), group)
|
||||
let explicit_sites = UserSites::new(config.explicit_sites.values(), &name, group)
|
||||
.context("failed to build sites config")?;
|
||||
|
||||
let mut user = User {
|
||||
name,
|
||||
config: Arc::clone(config),
|
||||
group: group.clone(),
|
||||
started: Instant::now(),
|
||||
|
|
@ -291,10 +311,11 @@ impl User {
|
|||
|
||||
let explicit_sites = self
|
||||
.explicit_sites
|
||||
.new_for_reload(config.explicit_sites.values(), config.name(), &self.group)
|
||||
.new_for_reload(config.explicit_sites.values(), &self.name, &self.group)
|
||||
.context("failed to build sites config")?;
|
||||
|
||||
let mut user = User {
|
||||
name: self.name.clone(),
|
||||
config: Arc::clone(config),
|
||||
group: self.group.clone(),
|
||||
started: self.started,
|
||||
|
|
@ -423,7 +444,7 @@ impl User {
|
|||
let stats = map.entry(server.clone()).or_insert_with(|| {
|
||||
Arc::new(UserForbiddenStats::new(
|
||||
&self.group,
|
||||
self.config.name().clone(),
|
||||
self.name.clone(),
|
||||
user_type,
|
||||
server,
|
||||
server_extra_tags,
|
||||
|
|
@ -451,7 +472,7 @@ impl User {
|
|||
let stats = map.entry(server.clone()).or_insert_with(|| {
|
||||
Arc::new(UserRequestStats::new(
|
||||
&self.group,
|
||||
self.config.name().clone(),
|
||||
self.name.clone(),
|
||||
user_type,
|
||||
server,
|
||||
server_extra_tags,
|
||||
|
|
@ -479,7 +500,7 @@ impl User {
|
|||
let stats = map.entry(server.clone()).or_insert_with(|| {
|
||||
Arc::new(UserTrafficStats::new(
|
||||
&self.group,
|
||||
self.config.name().clone(),
|
||||
self.name.clone(),
|
||||
user_type,
|
||||
server,
|
||||
server_extra_tags,
|
||||
|
|
@ -507,7 +528,7 @@ impl User {
|
|||
let stats = map.entry(escaper.clone()).or_insert_with(|| {
|
||||
Arc::new(UserUpstreamTrafficStats::new(
|
||||
&self.group,
|
||||
self.config.name().clone(),
|
||||
self.name.clone(),
|
||||
user_type,
|
||||
escaper,
|
||||
escaper_extra_tags,
|
||||
|
|
@ -754,7 +775,7 @@ impl UserContext {
|
|||
|
||||
#[inline]
|
||||
pub(crate) fn user_name(&self) -> &ArcStr {
|
||||
self.user.config.name()
|
||||
&self.user.name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const USER_GROUP_TYPE: &str = "basic";
|
|||
#[derive(Clone)]
|
||||
pub(crate) struct BasicUserGroupConfig {
|
||||
name: NodeName,
|
||||
position: Option<YamlDocPosition>,
|
||||
pub(super) position: Option<YamlDocPosition>,
|
||||
pub(crate) static_users: HashMap<ArcStr, Arc<UserConfig>>,
|
||||
pub(crate) dynamic_source: Option<UserDynamicSource>,
|
||||
pub(crate) dynamic_cache: PathBuf,
|
||||
|
|
|
|||
168
g3proxy/src/config/auth/group/ldap.rs
Normal file
168
g3proxy/src/config/auth/group/ldap.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use arcstr::ArcStr;
|
||||
use yaml_rust::{Yaml, yaml};
|
||||
|
||||
use g3_types::net::{ConnectionPoolConfig, OpensslClientConfigBuilder, UpstreamAddr};
|
||||
use g3_yaml::YamlDocPosition;
|
||||
|
||||
use super::{BasicUserGroupConfig, UserGroupConfig};
|
||||
use crate::config::auth::UserConfig;
|
||||
|
||||
const USER_GROUP_TYPE: &str = "ldap";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct LdapUserGroupConfig {
|
||||
basic: BasicUserGroupConfig,
|
||||
pub(crate) server: UpstreamAddr,
|
||||
pub(crate) tls_client: Option<OpensslClientConfigBuilder>,
|
||||
pub(crate) direct_tls: bool,
|
||||
pub(crate) base_dn: ArcStr,
|
||||
pub(crate) unmanaged_user: Option<Arc<UserConfig>>,
|
||||
pub(crate) max_message_size: usize,
|
||||
pub(crate) connect_timeout: Duration,
|
||||
pub(crate) response_timeout: Duration,
|
||||
pub(crate) connection_pool: ConnectionPoolConfig,
|
||||
pub(crate) queue_channel_size: usize,
|
||||
pub(crate) queue_wait_timeout: Duration,
|
||||
}
|
||||
|
||||
impl LdapUserGroupConfig {
|
||||
fn new(position: Option<YamlDocPosition>) -> Self {
|
||||
LdapUserGroupConfig {
|
||||
basic: BasicUserGroupConfig::new(position),
|
||||
server: UpstreamAddr::empty(),
|
||||
tls_client: None,
|
||||
direct_tls: false,
|
||||
base_dn: ArcStr::new(),
|
||||
unmanaged_user: None,
|
||||
max_message_size: 256,
|
||||
connect_timeout: Duration::from_secs(4),
|
||||
response_timeout: Duration::from_secs(2),
|
||||
connection_pool: ConnectionPoolConfig::new(1024, 8),
|
||||
queue_channel_size: 64,
|
||||
queue_wait_timeout: Duration::from_secs(4),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse(
|
||||
map: &yaml::Hash,
|
||||
position: Option<YamlDocPosition>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut config = Self::new(position);
|
||||
g3_yaml::foreach_kv(map, |k, v| config.set(k, v))?;
|
||||
config.check()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn check(&mut self) -> anyhow::Result<()> {
|
||||
if self.server.is_empty() {
|
||||
return Err(anyhow!("no ldap url set"));
|
||||
}
|
||||
|
||||
if self.direct_tls && self.tls_client.is_none() {
|
||||
self.tls_client = Some(OpensslClientConfigBuilder::with_cache_for_one_site());
|
||||
}
|
||||
|
||||
self.basic.check()
|
||||
}
|
||||
|
||||
fn set(&mut self, k: &str, v: &Yaml) -> anyhow::Result<()> {
|
||||
match g3_yaml::key::normalize(k).as_str() {
|
||||
"ldap_url" => {
|
||||
let url = g3_yaml::value::as_url(v)
|
||||
.context(format!("invalid ldap url value for key {k}"))?;
|
||||
let default_port;
|
||||
match url.scheme() {
|
||||
"ldap" => default_port = 389,
|
||||
"ldaps" => {
|
||||
self.direct_tls = true;
|
||||
default_port = 636;
|
||||
}
|
||||
scheme => return Err(anyhow!("unsupported ldap url scheme {scheme}")),
|
||||
}
|
||||
let Some(host) = url.host() else {
|
||||
return Err(anyhow!("no host found in ldap url {url}"));
|
||||
};
|
||||
let port = url.port().unwrap_or(default_port);
|
||||
self.server = UpstreamAddr::new(host, port);
|
||||
let path = url.path();
|
||||
let encoded_dn = path.strip_prefix("/").unwrap_or(path);
|
||||
let base_dn = percent_encoding::percent_decode_str(encoded_dn)
|
||||
.decode_utf8()
|
||||
.map_err(|e| anyhow!("the base dn is not valid utf-8 string: {e}"))?;
|
||||
self.base_dn = ArcStr::from(base_dn.as_ref());
|
||||
Ok(())
|
||||
}
|
||||
"tls_client" => {
|
||||
let lookup_dir = g3_daemon::config::get_lookup_dir(self.basic.position.as_ref())?;
|
||||
let config = g3_yaml::value::as_to_one_openssl_tls_client_config_builder(
|
||||
v,
|
||||
Some(lookup_dir),
|
||||
)
|
||||
.context(format!(
|
||||
"invalid openssl tls client config value for key {k}"
|
||||
))?;
|
||||
self.tls_client = Some(config);
|
||||
Ok(())
|
||||
}
|
||||
"unmanaged_user" => {
|
||||
if let Yaml::Hash(map) = v {
|
||||
let mut user = UserConfig::parse_yaml(map, self.basic.position.as_ref())?;
|
||||
user.set_no_password();
|
||||
self.unmanaged_user = Some(Arc::new(user));
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("invalid hash value for key {k}"))
|
||||
}
|
||||
}
|
||||
"max_message_size" => {
|
||||
self.max_message_size = g3_yaml::value::as_usize(v)?;
|
||||
Ok(())
|
||||
}
|
||||
"connect_timeout" => {
|
||||
self.connect_timeout = g3_yaml::humanize::as_duration(v)
|
||||
.context(format!("invalid humanize duration value for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
"response_timeout" => {
|
||||
self.response_timeout = g3_yaml::humanize::as_duration(v)
|
||||
.context(format!("invalid humanize duration value for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
"connection_pool" | "pool" => {
|
||||
self.connection_pool = g3_yaml::value::as_connection_pool_config(v)
|
||||
.context(format!("invalid connection pool config for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
"queue_channel_size" => {
|
||||
let channel_size = g3_yaml::value::as_nonzero_usize(v)?;
|
||||
self.queue_channel_size = channel_size.get();
|
||||
Ok(())
|
||||
}
|
||||
"queue_wait_timeout" => {
|
||||
self.queue_wait_timeout = g3_yaml::humanize::as_duration(v)
|
||||
.context(format!("invalid humanize duration value for key {k}"))?;
|
||||
Ok(())
|
||||
}
|
||||
_ => self.basic.set(k, v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserGroupConfig for LdapUserGroupConfig {
|
||||
fn basic_config(&self) -> &BasicUserGroupConfig {
|
||||
&self.basic
|
||||
}
|
||||
|
||||
fn r#type(&self) -> &'static str {
|
||||
USER_GROUP_TYPE
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@ pub(crate) use basic::BasicUserGroupConfig;
|
|||
mod facts;
|
||||
pub(crate) use facts::FactsUserGroupConfig;
|
||||
|
||||
mod ldap;
|
||||
pub(crate) use ldap::LdapUserGroupConfig;
|
||||
|
||||
pub(crate) trait UserGroupConfig {
|
||||
fn basic_config(&self) -> &BasicUserGroupConfig;
|
||||
|
||||
|
|
@ -25,6 +28,7 @@ pub(crate) trait UserGroupConfig {
|
|||
pub(crate) enum AnyUserGroupConfig {
|
||||
Basic(BasicUserGroupConfig),
|
||||
Facts(FactsUserGroupConfig),
|
||||
Ldap(LdapUserGroupConfig),
|
||||
}
|
||||
|
||||
impl AnyUserGroupConfig {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ pub(crate) use source::*;
|
|||
|
||||
pub(crate) mod group;
|
||||
pub(crate) use group::{
|
||||
AnyUserGroupConfig, BasicUserGroupConfig, FactsUserGroupConfig, UserGroupConfig,
|
||||
AnyUserGroupConfig, BasicUserGroupConfig, FactsUserGroupConfig, LdapUserGroupConfig,
|
||||
UserGroupConfig,
|
||||
};
|
||||
|
||||
mod registry;
|
||||
|
|
@ -62,6 +63,10 @@ fn load_user_group(
|
|||
let group = FactsUserGroupConfig::parse(map, position)?;
|
||||
Ok(AnyUserGroupConfig::Facts(group))
|
||||
}
|
||||
"ldap" => {
|
||||
let group = LdapUserGroupConfig::parse(map, position)?;
|
||||
Ok(AnyUserGroupConfig::Ldap(group))
|
||||
}
|
||||
_ => Err(anyhow!("unsupported user group type {group_type}")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn do_auth(
|
||||
async fn do_auth(
|
||||
&mut self,
|
||||
req: &HttpProxyRequest<CDR>,
|
||||
) -> Result<Option<UserContext>, UserAuthError> {
|
||||
|
|
@ -145,12 +145,14 @@ where
|
|||
.as_ref()
|
||||
.map(|c| c.real_username(username))
|
||||
.unwrap_or(username);
|
||||
user_group.check_user_with_password(
|
||||
username,
|
||||
&v.password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
)?
|
||||
user_group
|
||||
.check_user_with_password(
|
||||
username,
|
||||
&v.password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
user_ctx.check_client_addr(self.ctx.client_addr())?;
|
||||
|
|
@ -195,7 +197,7 @@ where
|
|||
loop {
|
||||
let res = match self.task_queue.recv().await {
|
||||
Some(Ok(req)) => {
|
||||
let res = match self.do_auth(&req) {
|
||||
let res = match self.do_auth(&req).await {
|
||||
Ok(user_ctx) => {
|
||||
self.req_count.consequent_auth_failed = 0;
|
||||
self.run(req, user_ctx).await
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn do_auth(
|
||||
async fn do_auth(
|
||||
&mut self,
|
||||
req: &HttpRProxyRequest<CDR>,
|
||||
) -> Result<Option<UserContext>, UserAuthError> {
|
||||
|
|
@ -132,12 +132,16 @@ where
|
|||
)
|
||||
})
|
||||
.ok_or(UserAuthError::NoUserSupplied)?,
|
||||
HttpAuth::Basic(v) => user_group.check_user_with_password(
|
||||
v.username.as_original(),
|
||||
&v.password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
)?,
|
||||
HttpAuth::Basic(v) => {
|
||||
user_group
|
||||
.check_user_with_password(
|
||||
v.username.as_original(),
|
||||
&v.password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
user_ctx.check_client_addr(self.ctx.client_addr())?;
|
||||
|
||||
|
|
@ -182,7 +186,7 @@ where
|
|||
loop {
|
||||
let res = match self.task_queue.recv().await {
|
||||
Some(Ok(req)) => {
|
||||
let res = match self.do_auth(&req) {
|
||||
let res = match self.do_auth(&req).await {
|
||||
Ok(user_ctx) => {
|
||||
self.req_count.consequent_auth_failed = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -254,12 +254,15 @@ impl SocksProxyNegotiationTask {
|
|||
base_username = username.as_original();
|
||||
}
|
||||
|
||||
match user_group.check_user_with_password(
|
||||
base_username,
|
||||
&password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
) {
|
||||
match user_group
|
||||
.check_user_with_password(
|
||||
base_username,
|
||||
&password,
|
||||
self.ctx.server_config.name(),
|
||||
self.ctx.server_stats.share_extra_tags(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(user_ctx) => {
|
||||
if user_ctx.check_client_addr(self.ctx.client_addr()).is_err() {
|
||||
self.ctx.server_stats.forbidden.add_auth_failed();
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@
|
|||
* Copyright 2024-2025 ByteDance and/or its affiliates.
|
||||
*/
|
||||
|
||||
use g3_types::net::QuicVarInt;
|
||||
|
||||
use super::FrameParseError;
|
||||
use super::{FrameParseError, VarInt};
|
||||
|
||||
pub struct AckFrame {
|
||||
pub largest_ack: QuicVarInt,
|
||||
pub ack_delay: QuicVarInt,
|
||||
pub first_ack_range: QuicVarInt,
|
||||
pub largest_ack: VarInt,
|
||||
pub ack_delay: VarInt,
|
||||
pub first_ack_range: VarInt,
|
||||
pub ack_ranges: Vec<AckRange>,
|
||||
pub ecn_counts: Option<EcnCounts>,
|
||||
pub(crate) encoded_len: usize,
|
||||
|
|
@ -23,7 +21,7 @@ impl AckFrame {
|
|||
|
||||
macro_rules! read_var_int {
|
||||
($var:ident) => {
|
||||
let Some($var) = QuicVarInt::parse(&data[offset..]) else {
|
||||
let Some($var) = VarInt::parse(&data[offset..]) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
offset += $var.encoded_len();
|
||||
|
|
@ -69,19 +67,19 @@ impl AckFrame {
|
|||
}
|
||||
|
||||
pub struct AckRange {
|
||||
pub gap: QuicVarInt,
|
||||
pub length: QuicVarInt,
|
||||
pub gap: VarInt,
|
||||
pub length: VarInt,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl AckRange {
|
||||
fn parse(data: &[u8]) -> Result<Self, FrameParseError> {
|
||||
let Some(gap) = QuicVarInt::parse(data) else {
|
||||
let Some(gap) = VarInt::parse(data) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
|
||||
let offset = gap.encoded_len();
|
||||
let Some(length) = QuicVarInt::parse(&data[offset..]) else {
|
||||
let Some(length) = VarInt::parse(&data[offset..]) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
|
||||
|
|
@ -95,9 +93,9 @@ impl AckRange {
|
|||
}
|
||||
|
||||
pub struct EcnCounts {
|
||||
pub ect0: QuicVarInt,
|
||||
pub ect1: QuicVarInt,
|
||||
pub ecn_ce: QuicVarInt,
|
||||
pub ect0: VarInt,
|
||||
pub ect1: VarInt,
|
||||
pub ecn_ce: VarInt,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +105,7 @@ impl EcnCounts {
|
|||
|
||||
macro_rules! read_var_int {
|
||||
($var:ident) => {
|
||||
let Some($var) = QuicVarInt::parse(&data[offset..]) else {
|
||||
let Some($var) = VarInt::parse(&data[offset..]) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
offset += $var.encoded_len();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use g3_types::net::QuicVarInt;
|
||||
|
||||
use super::{FrameConsume, FrameParseError};
|
||||
use super::{FrameConsume, FrameParseError, VarInt};
|
||||
use crate::parser::tls::{ClientHello, ClientHelloParseError, HandshakeHeader, HandshakeType};
|
||||
|
||||
pub struct CryptoFrame<'a> {
|
||||
|
|
@ -20,7 +18,7 @@ pub struct CryptoFrame<'a> {
|
|||
impl<'a> CryptoFrame<'a> {
|
||||
/// Parse a Crypto Frame from a packet buffer
|
||||
pub fn parse(data: &'a [u8]) -> Result<Self, FrameParseError> {
|
||||
let Some(stream_offset) = QuicVarInt::parse(data) else {
|
||||
let Some(stream_offset) = VarInt::parse(data) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
let mut offset = stream_offset.encoded_len();
|
||||
|
|
@ -28,7 +26,7 @@ impl<'a> CryptoFrame<'a> {
|
|||
.map_err(|_| FrameParseError::TooBigOffsetValue(stream_offset.value()))?;
|
||||
|
||||
let left = &data[offset..];
|
||||
let Some(length) = QuicVarInt::parse(left) else {
|
||||
let Some(length) = VarInt::parse(left) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
offset += length.encoded_len();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
use super::VarInt;
|
||||
|
||||
mod crypto;
|
||||
pub use crypto::{CryptoFrame, HandshakeCoalescer};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
* Copyright 2024-2025 ByteDance and/or its affiliates.
|
||||
*/
|
||||
|
||||
mod var_int;
|
||||
use var_int::VarInt;
|
||||
|
||||
mod packet;
|
||||
pub use packet::{InitialPacket, PacketParseError};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
use openssl::error::ErrorStack;
|
||||
use thiserror::Error;
|
||||
|
||||
use g3_types::net::QuicVarInt;
|
||||
|
||||
use super::{AckFrame, CryptoFrame, FrameConsume, FrameParseError};
|
||||
use super::{AckFrame, CryptoFrame, FrameConsume, FrameParseError, VarInt};
|
||||
|
||||
mod hkdf;
|
||||
use hkdf::{quic_hkdf_expand, quic_hkdf_extract_expand};
|
||||
|
|
@ -96,7 +94,7 @@ impl InitialPacket {
|
|||
|
||||
while offset < payload.len() {
|
||||
let left = &payload[offset..];
|
||||
let Some(frame_type) = QuicVarInt::parse(left) else {
|
||||
let Some(frame_type) = VarInt::parse(left) else {
|
||||
return Err(FrameParseError::NotEnoughData);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
|
||||
use openssl::error::ErrorStack;
|
||||
|
||||
use g3_types::net::QuicVarInt;
|
||||
|
||||
use super::{Header, PacketParseError};
|
||||
use super::{Header, PacketParseError, VarInt};
|
||||
|
||||
const INITIAL_SALT: &[u8] = &[
|
||||
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
|
||||
|
|
@ -64,7 +62,7 @@ impl InitialPacketV1 {
|
|||
|
||||
// Token
|
||||
let left = &data[offset..];
|
||||
let Some(token_len) = QuicVarInt::parse(left) else {
|
||||
let Some(token_len) = VarInt::parse(left) else {
|
||||
return Err(PacketParseError::TooSmall);
|
||||
};
|
||||
let start = offset + token_len.encoded_len();
|
||||
|
|
@ -75,7 +73,7 @@ impl InitialPacketV1 {
|
|||
|
||||
// Length
|
||||
let left = &data[offset..];
|
||||
let Some(length) = QuicVarInt::parse(left) else {
|
||||
let Some(length) = VarInt::parse(left) else {
|
||||
return Err(PacketParseError::TooSmall);
|
||||
};
|
||||
offset += length.encoded_len();
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
|
||||
use openssl::error::ErrorStack;
|
||||
|
||||
use g3_types::net::QuicVarInt;
|
||||
|
||||
use super::{Header, PacketParseError};
|
||||
use super::{Header, PacketParseError, VarInt};
|
||||
|
||||
const INITIAL_SALT: &[u8] = &[
|
||||
0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb,
|
||||
|
|
@ -66,7 +64,7 @@ impl InitialPacketV2 {
|
|||
|
||||
// Token
|
||||
let left = &data[offset..];
|
||||
let Some(token_len) = QuicVarInt::parse(left) else {
|
||||
let Some(token_len) = VarInt::parse(left) else {
|
||||
return Err(PacketParseError::TooSmall);
|
||||
};
|
||||
let start = offset + token_len.encoded_len();
|
||||
|
|
@ -77,7 +75,7 @@ impl InitialPacketV2 {
|
|||
|
||||
// Length
|
||||
let left = &data[offset..];
|
||||
let Some(length) = QuicVarInt::parse(left) else {
|
||||
let Some(length) = VarInt::parse(left) else {
|
||||
return Err(PacketParseError::TooSmall);
|
||||
};
|
||||
offset += length.encoded_len();
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuicVarInt {
|
||||
pub struct VarInt {
|
||||
value: u64,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl QuicVarInt {
|
||||
impl VarInt {
|
||||
/// Try to parse a QUIC variant-length int value from the buffer
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.is_empty() {
|
||||
|
|
@ -18,7 +18,7 @@ impl QuicVarInt {
|
|||
|
||||
let value0 = data[0] & 0b0011_1111;
|
||||
match data[0] >> 6 {
|
||||
0 => Some(QuicVarInt {
|
||||
0 => Some(VarInt {
|
||||
value: value0 as u64,
|
||||
encoded_len: 1,
|
||||
}),
|
||||
|
|
@ -26,7 +26,7 @@ impl QuicVarInt {
|
|||
if data.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
Some(QuicVarInt {
|
||||
Some(VarInt {
|
||||
value: u16::from_be_bytes([value0, data[1]]) as u64,
|
||||
encoded_len: 2,
|
||||
})
|
||||
|
|
@ -35,7 +35,7 @@ impl QuicVarInt {
|
|||
if data.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
Some(QuicVarInt {
|
||||
Some(VarInt {
|
||||
value: u32::from_be_bytes([value0, data[1], data[2], data[3]]) as u64,
|
||||
encoded_len: 4,
|
||||
})
|
||||
|
|
@ -44,7 +44,7 @@ impl QuicVarInt {
|
|||
if data.len() < 8 {
|
||||
return None;
|
||||
}
|
||||
Some(QuicVarInt {
|
||||
Some(VarInt {
|
||||
value: u64::from_be_bytes([
|
||||
value0, data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]),
|
||||
|
|
@ -72,24 +72,24 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert!(QuicVarInt::parse(b"").is_none());
|
||||
assert!(VarInt::parse(b"").is_none());
|
||||
|
||||
let v = QuicVarInt::parse(&[0x02]).unwrap();
|
||||
let v = VarInt::parse(&[0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
|
||||
assert!(QuicVarInt::parse(&[0b0100_1111]).is_none());
|
||||
let v = QuicVarInt::parse(&[0b0100_1111, 0]).unwrap();
|
||||
assert!(VarInt::parse(&[0b0100_1111]).is_none());
|
||||
let v = VarInt::parse(&[0b0100_1111, 0]).unwrap();
|
||||
assert_eq!(v.value, 0x0F00);
|
||||
assert_eq!(v.encoded_len(), 2);
|
||||
|
||||
assert!(QuicVarInt::parse(&[0b1000_1111, 0x00]).is_none());
|
||||
let v = QuicVarInt::parse(&[0b1000_1111, 0, 0, 0x01]).unwrap();
|
||||
assert!(VarInt::parse(&[0b1000_1111, 0x00]).is_none());
|
||||
let v = VarInt::parse(&[0b1000_1111, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x0F000001);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
|
||||
assert!(QuicVarInt::parse(&[0b1100_1111, 0]).is_none());
|
||||
let v = QuicVarInt::parse(&[0b1100_1111, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert!(VarInt::parse(&[0b1100_1111, 0]).is_none());
|
||||
let v = VarInt::parse(&[0b1100_1111, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x0F00000000000001);
|
||||
assert_eq!(v.encoded_len(), 8);
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use g3_types::net::{
|
||||
LdapMessageId, LdapMessageIdParseError, LdapMessageLength, LdapMessageLengthParseError,
|
||||
};
|
||||
use g3_types::codec::{LdapLength, LdapLengthParseError, LdapMessageId};
|
||||
|
||||
use super::{MaybeProtocol, Protocol, ProtocolInspectError, ProtocolInspectState};
|
||||
use crate::ProtocolInspectionSizeLimit;
|
||||
|
|
@ -43,7 +41,7 @@ impl ProtocolInspectState {
|
|||
|
||||
let header_len;
|
||||
let content_len;
|
||||
match LdapMessageLength::parse(&data[1..]) {
|
||||
match LdapLength::parse(&data[1..]) {
|
||||
Ok(v) => {
|
||||
if v.value() > size_limit.ldap_request_msg as u64 {
|
||||
self.exclude_current();
|
||||
|
|
@ -57,7 +55,7 @@ impl ProtocolInspectState {
|
|||
));
|
||||
}
|
||||
}
|
||||
Err(LdapMessageLengthParseError::NeedMoreData(n)) => {
|
||||
Err(LdapLengthParseError::NeedMoreData(n)) => {
|
||||
return Err(ProtocolInspectError::NeedMoreData(n));
|
||||
}
|
||||
Err(_) => {
|
||||
|
|
@ -66,7 +64,7 @@ impl ProtocolInspectState {
|
|||
}
|
||||
}
|
||||
|
||||
let content = &data[header_len..];
|
||||
let content = &data[header_len..header_len + content_len];
|
||||
match LdapMessageId::parse(content) {
|
||||
Ok(id) => {
|
||||
if id.value() == 0 {
|
||||
|
|
@ -74,9 +72,6 @@ impl ProtocolInspectState {
|
|||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Err(LdapMessageIdParseError::NeedMoreData(n)) => {
|
||||
return Err(ProtocolInspectError::NeedMoreData(n));
|
||||
}
|
||||
Err(_) => {
|
||||
self.exclude_current();
|
||||
return Ok(None);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ pub enum UserAuthError {
|
|||
BlockedUser(Duration),
|
||||
#[error("src addr {0} is blocked")]
|
||||
BlockedSrcIp(SocketAddr),
|
||||
#[error("remote timeout")]
|
||||
RemoteTimeout,
|
||||
#[error("remote error")]
|
||||
RemoteError,
|
||||
}
|
||||
|
||||
impl UserAuthError {
|
||||
|
|
|
|||
251
lib/g3-types/src/codec/ber/integer.rs
Normal file
251
lib/g3-types/src/codec/ber/integer.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{BerLength, BerLengthParseError};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum BerIntegerParseError {
|
||||
#[error("need {0} bytes more data")]
|
||||
NeedMoreData(usize),
|
||||
#[error("invalid ber type")]
|
||||
InvalidType,
|
||||
#[error("invalid ber length")]
|
||||
TooLargeLength,
|
||||
#[error("indefinite length")]
|
||||
IndefiniteLength,
|
||||
#[error("invalid value bytes")]
|
||||
InvalidValueBytes,
|
||||
}
|
||||
|
||||
impl From<BerLengthParseError> for BerIntegerParseError {
|
||||
fn from(value: BerLengthParseError) -> Self {
|
||||
match value {
|
||||
BerLengthParseError::NeedMoreData(n) => BerIntegerParseError::NeedMoreData(n),
|
||||
BerLengthParseError::TooLargeValue => BerIntegerParseError::TooLargeLength,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BerInteger {
|
||||
value: i64,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl BerInteger {
|
||||
pub fn parse(data: &[u8]) -> Result<BerInteger, BerIntegerParseError> {
|
||||
Self::parse_with_identifier(data, 0x02)
|
||||
}
|
||||
|
||||
pub fn parse_enumerated_value(data: &[u8]) -> Result<BerInteger, BerIntegerParseError> {
|
||||
Self::parse_with_identifier(data, 0x0a)
|
||||
}
|
||||
|
||||
fn parse_with_identifier(data: &[u8], identifier: u8) -> Result<Self, BerIntegerParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(BerIntegerParseError::NeedMoreData(1));
|
||||
}
|
||||
if data[0] != identifier {
|
||||
return Err(BerIntegerParseError::InvalidType);
|
||||
}
|
||||
|
||||
let length = BerLength::parse(&data[1..])?;
|
||||
if length.indefinite() {
|
||||
return Err(BerIntegerParseError::IndefiniteLength);
|
||||
}
|
||||
|
||||
let offset = 1 + length.encoded_len();
|
||||
let left = &data[offset..];
|
||||
let value0 = left[0] & 0x7F;
|
||||
let mut integer = match length.value() {
|
||||
1 => {
|
||||
if left.is_empty() {
|
||||
return Err(BerIntegerParseError::NeedMoreData(1));
|
||||
}
|
||||
BerInteger {
|
||||
value: i64::from(value0),
|
||||
encoded_len: offset + 1,
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if left.len() < 2 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(2 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i16::from_be_bytes([value0, left[1]]) as i64,
|
||||
encoded_len: offset + 2,
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if left.len() < 3 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(3 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i32::from_be_bytes([0, value0, left[1], left[2]]) as i64,
|
||||
encoded_len: offset + 3,
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
if left.len() < 4 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(4 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i32::from_be_bytes([value0, left[1], left[2], left[3]]) as i64,
|
||||
encoded_len: offset + 4,
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
if left.len() < 5 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(5 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i64::from_be_bytes([
|
||||
0, 0, 0, value0, left[1], left[2], left[3], left[4],
|
||||
]),
|
||||
encoded_len: offset + 5,
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if left.len() < 6 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(6 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i64::from_be_bytes([
|
||||
0, 0, value0, left[1], left[2], left[3], left[4], left[5],
|
||||
]),
|
||||
encoded_len: offset + 6,
|
||||
}
|
||||
}
|
||||
7 => {
|
||||
if left.len() < 7 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(7 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i64::from_be_bytes([
|
||||
0, value0, left[1], left[2], left[3], left[4], left[5], left[6],
|
||||
]),
|
||||
encoded_len: offset + 7,
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
if left.len() < 8 {
|
||||
return Err(BerIntegerParseError::NeedMoreData(8 - left.len()));
|
||||
}
|
||||
BerInteger {
|
||||
value: i64::from_be_bytes([
|
||||
value0, left[1], left[2], left[3], left[4], left[5], left[6], left[7],
|
||||
]),
|
||||
encoded_len: offset + 8,
|
||||
}
|
||||
}
|
||||
_ => return Err(BerIntegerParseError::InvalidValueBytes),
|
||||
};
|
||||
if left[0] >> 7 != 0 {
|
||||
integer.value = 0 - integer.value;
|
||||
}
|
||||
Ok(integer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> i64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
let e = BerInteger::parse(&[0x02]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(1));
|
||||
|
||||
let e = BerInteger::parse(&[0x03, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::InvalidType);
|
||||
let e = BerInteger::parse(&[0x02, 0x00, 0x02]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::InvalidValueBytes);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
let v = BerInteger::parse(&[0x02, 0x81, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
let v = BerInteger::parse(&[0x02, 0x01, 0x82]).unwrap();
|
||||
assert_eq!(v.value, -2);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0102);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
let e = BerInteger::parse(&[0x02, 0x02, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(1));
|
||||
let v = BerInteger::parse(&[0x02, 0x02, 0x81, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x0102);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x03, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x010102);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
let e = BerInteger::parse(&[0x02, 0x03, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(2));
|
||||
let v = BerInteger::parse(&[0x02, 0x03, 0x81, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x010102);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x04, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x01010102);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
let e = BerInteger::parse(&[0x02, 0x04, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(2));
|
||||
let v = BerInteger::parse(&[0x02, 0x04, 0x81, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x01010102);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x05, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 7);
|
||||
let e = BerInteger::parse(&[0x02, 0x05, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(3));
|
||||
let v = BerInteger::parse(&[0x02, 0x05, 0x81, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 7);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x06, 0, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 8);
|
||||
let e = BerInteger::parse(&[0x02, 0x06, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(4));
|
||||
let v = BerInteger::parse(&[0x02, 0x06, 0x80, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 8);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x07, 0, 0, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 9);
|
||||
let e = BerInteger::parse(&[0x02, 0x07, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(5));
|
||||
let v = BerInteger::parse(&[0x02, 0x07, 0x80, 0, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 9);
|
||||
|
||||
let v = BerInteger::parse(&[0x02, 0x08, 0, 0, 0, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 10);
|
||||
let e = BerInteger::parse(&[0x02, 0x08, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, BerIntegerParseError::NeedMoreData(6));
|
||||
let v = BerInteger::parse(&[0x02, 0x08, 0x80, 0, 0, 0x01, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, -0x0101010102);
|
||||
assert_eq!(v.encoded_len(), 10);
|
||||
}
|
||||
}
|
||||
274
lib/g3-types/src/codec/ber/length.rs
Normal file
274
lib/g3-types/src/codec/ber/length.rs
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BerLengthParseError {
|
||||
NeedMoreData(usize),
|
||||
TooLargeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BerLength {
|
||||
value: u64,
|
||||
indefinite: bool,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl BerLength {
|
||||
/// Try to parse a BER length value with LDAP constraints from the buffer
|
||||
pub fn parse(data: &[u8]) -> Result<Self, BerLengthParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(BerLengthParseError::NeedMoreData(1));
|
||||
}
|
||||
|
||||
if data[0] & 0x80 == 0 {
|
||||
return Ok(BerLength {
|
||||
value: data[0] as u64,
|
||||
indefinite: false,
|
||||
encoded_len: 1,
|
||||
});
|
||||
}
|
||||
|
||||
match data[0] & 0x7F {
|
||||
0 => Ok(BerLength {
|
||||
value: 0,
|
||||
indefinite: true,
|
||||
encoded_len: 1,
|
||||
}),
|
||||
1 => {
|
||||
if data.len() < 2 {
|
||||
return Err(BerLengthParseError::NeedMoreData(2 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: data[1] as u64,
|
||||
indefinite: false,
|
||||
encoded_len: 2,
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
if data.len() < 3 {
|
||||
return Err(BerLengthParseError::NeedMoreData(3 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u16::from_be_bytes([data[1], data[2]]) as u64,
|
||||
indefinite: false,
|
||||
encoded_len: 3,
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
if data.len() < 4 {
|
||||
return Err(BerLengthParseError::NeedMoreData(4 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u32::from_be_bytes([0, data[1], data[2], data[3]]) as u64,
|
||||
indefinite: false,
|
||||
encoded_len: 4,
|
||||
})
|
||||
}
|
||||
4 => {
|
||||
if data.len() < 5 {
|
||||
return Err(BerLengthParseError::NeedMoreData(5 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as u64,
|
||||
indefinite: false,
|
||||
encoded_len: 5,
|
||||
})
|
||||
}
|
||||
5 => {
|
||||
if data.len() < 6 {
|
||||
return Err(BerLengthParseError::NeedMoreData(6 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, 0, 0, data[1], data[2], data[3], data[4], data[5],
|
||||
]),
|
||||
indefinite: false,
|
||||
encoded_len: 6,
|
||||
})
|
||||
}
|
||||
6 => {
|
||||
if data.len() < 7 {
|
||||
return Err(BerLengthParseError::NeedMoreData(7 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, 0, data[1], data[2], data[3], data[4], data[5], data[6],
|
||||
]),
|
||||
indefinite: false,
|
||||
encoded_len: 7,
|
||||
})
|
||||
}
|
||||
7 => {
|
||||
if data.len() < 8 {
|
||||
return Err(BerLengthParseError::NeedMoreData(8 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]),
|
||||
indefinite: false,
|
||||
encoded_len: 8,
|
||||
})
|
||||
}
|
||||
8 => {
|
||||
if data.len() < 9 {
|
||||
return Err(BerLengthParseError::NeedMoreData(9 - data.len()));
|
||||
}
|
||||
Ok(BerLength {
|
||||
value: u64::from_be_bytes([
|
||||
data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
|
||||
]),
|
||||
indefinite: false,
|
||||
encoded_len: 9,
|
||||
})
|
||||
}
|
||||
_ => Err(BerLengthParseError::TooLargeValue),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn indefinite(&self) -> bool {
|
||||
self.indefinite
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> u64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BerLengthEncoder {
|
||||
buf: [u8; 9],
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl BerLengthEncoder {
|
||||
pub fn encode(&mut self, value: usize) -> &[u8] {
|
||||
if value <= 0x7F {
|
||||
self.offset = 8;
|
||||
self.buf[8] = (value & 0x7F) as u8;
|
||||
return &self.buf[8..];
|
||||
}
|
||||
|
||||
let bytes = (value as u64).to_be_bytes();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), self.buf[1..].as_mut_ptr(), 8);
|
||||
}
|
||||
let unused_bits = value.leading_zeros() as usize;
|
||||
self.offset = unused_bits / 8;
|
||||
self.buf[self.offset] = 8 - self.offset as u8;
|
||||
&self.buf[self.offset..]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert!(BerLength::parse(b"").is_err());
|
||||
|
||||
let v = BerLength::parse(&[0x80]).unwrap();
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
assert!(v.indefinite());
|
||||
|
||||
let v = BerLength::parse(&[0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
|
||||
let v = BerLength::parse(&[0x81]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(1));
|
||||
let v = BerLength::parse(&[0x81, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 2);
|
||||
|
||||
let v = BerLength::parse(&[0x82, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(1));
|
||||
let v = BerLength::parse(&[0x82, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
|
||||
let v = BerLength::parse(&[0x83, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(2));
|
||||
let v = BerLength::parse(&[0x83, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
|
||||
let v = BerLength::parse(&[0x84, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(2));
|
||||
let v = BerLength::parse(&[0x84, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
|
||||
let v = BerLength::parse(&[0x85, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(3));
|
||||
let v = BerLength::parse(&[0x85, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
|
||||
let v = BerLength::parse(&[0x86, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(3));
|
||||
let v = BerLength::parse(&[0x86, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 7);
|
||||
|
||||
let v = BerLength::parse(&[0x87, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(4));
|
||||
let v = BerLength::parse(&[0x87, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 8);
|
||||
|
||||
let v = BerLength::parse(&[0x88, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::NeedMoreData(5));
|
||||
let v = BerLength::parse(&[0x88, 0, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 9);
|
||||
|
||||
let v = BerLength::parse(&[0x89, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, BerLengthParseError::TooLargeValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_32() {
|
||||
let mut encoder = BerLengthEncoder::default();
|
||||
assert_eq!(encoder.encode(0), &[0]);
|
||||
assert_eq!(encoder.encode(1), &[1]);
|
||||
assert_eq!(encoder.encode(0x7F), &[0x7F]);
|
||||
assert_eq!(encoder.encode(0x80), &[0x01, 0x80]);
|
||||
assert_eq!(encoder.encode(0x0100), &[0x02, 0x01, 0x00]);
|
||||
assert_eq!(encoder.encode(0x010000), &[0x03, 0x01, 0x00, 0x00]);
|
||||
assert_eq!(encoder.encode(0x01000000), &[0x04, 0x01, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn encode_64() {
|
||||
let mut encoder = BerLengthEncoder::default();
|
||||
assert_eq!(
|
||||
encoder.encode(0x0100000000),
|
||||
&[0x05, 0x01, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
assert_eq!(
|
||||
encoder.encode(0x010000000000),
|
||||
&[0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
assert_eq!(
|
||||
encoder.encode(0x01000000000000),
|
||||
&[0x07, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
assert_eq!(
|
||||
encoder.encode(0x0100000000000000),
|
||||
&[0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
}
|
||||
10
lib/g3-types/src/codec/ber/mod.rs
Normal file
10
lib/g3-types/src/codec/ber/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod length;
|
||||
pub use length::{BerLength, BerLengthEncoder, BerLengthParseError};
|
||||
|
||||
mod integer;
|
||||
pub use integer::{BerInteger, BerIntegerParseError};
|
||||
72
lib/g3-types/src/codec/ldap/length.rs
Normal file
72
lib/g3-types/src/codec/ldap/length.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::codec::ber::{BerLength, BerLengthParseError};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum LdapLengthParseError {
|
||||
#[error("need {0} bytes more data")]
|
||||
NeedMoreData(usize),
|
||||
#[error("too large value")]
|
||||
TooLargeValue,
|
||||
#[error("indefinite value")]
|
||||
IndefiniteValue,
|
||||
}
|
||||
|
||||
impl From<BerLengthParseError> for LdapLengthParseError {
|
||||
fn from(value: BerLengthParseError) -> Self {
|
||||
match value {
|
||||
BerLengthParseError::NeedMoreData(needed) => Self::NeedMoreData(needed),
|
||||
BerLengthParseError::TooLargeValue => Self::TooLargeValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LdapLength {
|
||||
value: u64,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl LdapLength {
|
||||
/// Try to parse a BER length value with LDAP constraints from the buffer
|
||||
pub fn parse(data: &[u8]) -> Result<Self, LdapLengthParseError> {
|
||||
let ber_len = BerLength::parse(data)?;
|
||||
if ber_len.indefinite() {
|
||||
return Err(LdapLengthParseError::IndefiniteValue);
|
||||
}
|
||||
Ok(LdapLength {
|
||||
value: ber_len.value(),
|
||||
encoded_len: ber_len.encoded_len(),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> u64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert!(LdapLength::parse(b"").is_err());
|
||||
assert!(LdapLength::parse(&[0x80]).is_err());
|
||||
|
||||
let v = LdapLength::parse(&[0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
}
|
||||
}
|
||||
110
lib/g3-types/src/codec/ldap/message.rs
Normal file
110
lib/g3-types/src/codec/ldap/message.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{LdapLength, LdapLengthParseError, LdapMessageId, LdapMessageIdParseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LdapMessageParseError {
|
||||
#[error("need {0} bytes more data")]
|
||||
NeedMoreData(usize),
|
||||
#[error("invalid message ber type")]
|
||||
InvalidBerType,
|
||||
#[error("invalid message length value")]
|
||||
InvalidMessageLength,
|
||||
#[error("invalid message id: {0}")]
|
||||
InvalidMessageId(#[from] LdapMessageIdParseError),
|
||||
}
|
||||
|
||||
impl From<LdapLengthParseError> for LdapMessageParseError {
|
||||
fn from(value: LdapLengthParseError) -> Self {
|
||||
match value {
|
||||
LdapLengthParseError::NeedMoreData(n) => LdapMessageParseError::NeedMoreData(n),
|
||||
LdapLengthParseError::TooLargeValue | LdapLengthParseError::IndefiniteValue => {
|
||||
LdapMessageParseError::InvalidMessageLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LdapMessage<'a> {
|
||||
id: u32,
|
||||
payload: &'a [u8],
|
||||
encoded_size: usize,
|
||||
}
|
||||
|
||||
impl<'a> LdapMessage<'a> {
|
||||
pub fn parse(data: &'a [u8], max_message_length: usize) -> Result<Self, LdapMessageParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(LdapMessageParseError::NeedMoreData(1));
|
||||
}
|
||||
|
||||
if data[0] != 0x30 {
|
||||
return Err(LdapMessageParseError::InvalidBerType);
|
||||
}
|
||||
let mut offset = 1usize;
|
||||
|
||||
let length = LdapLength::parse(&data[offset..])?;
|
||||
offset += length.encoded_len();
|
||||
let message_length = length.value();
|
||||
if message_length > max_message_length as u64 {
|
||||
return Err(LdapMessageParseError::InvalidMessageLength);
|
||||
}
|
||||
let message_length = message_length as usize;
|
||||
|
||||
let left = &data[offset..];
|
||||
if left.len() < message_length {
|
||||
return Err(LdapMessageParseError::NeedMoreData(
|
||||
message_length - left.len(),
|
||||
));
|
||||
}
|
||||
|
||||
let id = LdapMessageId::parse(&left[..message_length])?;
|
||||
let message_id = id.value();
|
||||
|
||||
Ok(LdapMessage {
|
||||
id: message_id,
|
||||
payload: &left[id.encoded_len()..message_length],
|
||||
encoded_size: offset + message_length,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn payload(&self) -> &'a [u8] {
|
||||
self.payload
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_size(&self) -> usize {
|
||||
self.encoded_size
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_bind_response() {
|
||||
let data = [
|
||||
0x30, 0x0c, // Begin the LDAPMessage sequence
|
||||
0x02, 0x01, 0x01, // The message ID (integer value 1)
|
||||
0x61, 0x07, // Begin the bind response protocol op
|
||||
0x0a, 0x01, 0x00, // success result code (enumerated value 0)
|
||||
0x04, 0x00, // No matched DN (0-byte octet string)
|
||||
0x04, 0x00, // No diagnostic message (0-byte octet string)
|
||||
];
|
||||
let message = LdapMessage::parse(&data, 128).unwrap();
|
||||
assert_eq!(message.id(), 1);
|
||||
assert_eq!(message.payload(), &data[5..]);
|
||||
assert_eq!(message.encoded_size(), data.len());
|
||||
}
|
||||
}
|
||||
85
lib/g3-types/src/codec/ldap/message_id.rs
Normal file
85
lib/g3-types/src/codec/ldap/message_id.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::codec::{BerInteger, BerIntegerParseError};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum LdapMessageIdParseError {
|
||||
#[error("invalid integer value: {0}")]
|
||||
InvalidIntegerValue(#[from] BerIntegerParseError),
|
||||
#[error("negative value")]
|
||||
NegativeValue,
|
||||
#[error("too large value")]
|
||||
TooLargeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LdapMessageId {
|
||||
value: u32,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl LdapMessageId {
|
||||
/// Try to parse a BER integer value with LDAP constraints from the buffer
|
||||
pub fn parse(data: &[u8]) -> Result<Self, LdapMessageIdParseError> {
|
||||
let ber_integer = BerInteger::parse(data)?;
|
||||
if ber_integer.value() < 0 {
|
||||
return Err(LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
let value = u32::try_from(ber_integer.value())
|
||||
.map_err(|_| LdapMessageIdParseError::TooLargeValue)?;
|
||||
Ok(LdapMessageId {
|
||||
value,
|
||||
encoded_len: ber_integer.encoded_len(),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> u32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
let v = LdapMessageId::parse(&[0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
let v = LdapMessageId::parse(&[0x02, 0x81, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x01, 0x82]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0102);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x02, 0x81, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x03, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x010102);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x03, 0x81, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x04, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x01010102);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x04, 0x81, 0x01, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
}
|
||||
19
lib/g3-types/src/codec/ldap/mod.rs
Normal file
19
lib/g3-types/src/codec/ldap/mod.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod length;
|
||||
pub use length::{LdapLength, LdapLengthParseError};
|
||||
|
||||
mod sequence;
|
||||
pub use sequence::{LdapSequence, LdapSequenceParseError};
|
||||
|
||||
mod message_id;
|
||||
pub use message_id::{LdapMessageId, LdapMessageIdParseError};
|
||||
|
||||
mod message;
|
||||
pub use message::{LdapMessage, LdapMessageParseError};
|
||||
|
||||
mod result;
|
||||
pub use result::{LdapResult, LdapResultParseError};
|
||||
107
lib/g3-types/src/codec/ldap/result.rs
Normal file
107
lib/g3-types/src/codec/ldap/result.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::codec::{BerInteger, BerIntegerParseError, LdapSequence, LdapSequenceParseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LdapResultParseError {
|
||||
#[error("invalid result code value: {0}")]
|
||||
InvalidResultCode(#[from] BerIntegerParseError),
|
||||
#[error("out of range result code")]
|
||||
OutOfRangeResultCode,
|
||||
#[error("invalid matched dn string: {0}")]
|
||||
InvalidMatchedDn(LdapSequenceParseError),
|
||||
#[error("invalid diagnostic message string: {0}")]
|
||||
InvalidDiagnosticMessage(LdapSequenceParseError),
|
||||
#[error("invalid referrals sequence: {0}")]
|
||||
InvalidReferralsSequence(LdapSequenceParseError),
|
||||
}
|
||||
|
||||
pub struct LdapResult<'a> {
|
||||
result_code: u16,
|
||||
matched_dn: &'a [u8],
|
||||
diagnostic_message: &'a [u8],
|
||||
#[allow(unused)]
|
||||
referral_sequence: &'a [u8], // only if code is REFERRAL 0x0a
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl<'a> LdapResult<'a> {
|
||||
pub fn parse(data: &'a [u8]) -> Result<Self, LdapResultParseError> {
|
||||
let code = BerInteger::parse_enumerated_value(data)?;
|
||||
let result_code =
|
||||
u16::try_from(code.value()).map_err(|_| LdapResultParseError::OutOfRangeResultCode)?;
|
||||
let mut offset = code.encoded_len();
|
||||
|
||||
let matched_dn = LdapSequence::parse_octet_string(&data[offset..])
|
||||
.map_err(LdapResultParseError::InvalidMatchedDn)?;
|
||||
offset += matched_dn.encoded_len();
|
||||
|
||||
let diagnostic_message = LdapSequence::parse_octet_string(&data[offset..])
|
||||
.map_err(LdapResultParseError::InvalidDiagnosticMessage)?;
|
||||
offset += diagnostic_message.encoded_len();
|
||||
|
||||
if result_code == 10 {
|
||||
let referral_sequence = LdapSequence::parse_referrals_sequence(&data[offset..])
|
||||
.map_err(LdapResultParseError::InvalidReferralsSequence)?;
|
||||
Ok(LdapResult {
|
||||
result_code,
|
||||
matched_dn: matched_dn.data(),
|
||||
diagnostic_message: diagnostic_message.data(),
|
||||
referral_sequence: referral_sequence.data(),
|
||||
encoded_len: offset + referral_sequence.encoded_len(),
|
||||
})
|
||||
} else {
|
||||
Ok(LdapResult {
|
||||
result_code,
|
||||
matched_dn: matched_dn.data(),
|
||||
diagnostic_message: diagnostic_message.data(),
|
||||
referral_sequence: b"",
|
||||
encoded_len: offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn result_code(&self) -> u16 {
|
||||
self.result_code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn matched_dn(&self) -> &[u8] {
|
||||
self.matched_dn
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn diagnostic_message(&self) -> &[u8] {
|
||||
self.diagnostic_message
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_simple() {
|
||||
let data = &[
|
||||
0x0a, 0x01, 0x00, // result code 0
|
||||
0x04, 0x00, // no matched dn
|
||||
0x04, 0x00, // no diagnostic message
|
||||
];
|
||||
let v = LdapResult::parse(data).unwrap();
|
||||
assert_eq!(v.result_code(), 0);
|
||||
assert!(v.matched_dn().is_empty());
|
||||
assert!(v.diagnostic_message().is_empty());
|
||||
assert_eq!(v.encoded_len(), 7);
|
||||
}
|
||||
}
|
||||
117
lib/g3-types/src/codec/ldap/sequence.rs
Normal file
117
lib/g3-types/src/codec/ldap/sequence.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::codec::{BerLength, BerLengthParseError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LdapSequenceParseError {
|
||||
#[error("need {0} bytes more data")]
|
||||
NeedMoreData(usize),
|
||||
#[error("invalid ber type")]
|
||||
InvalidType,
|
||||
#[error("invalid ber length")]
|
||||
TooLargeLength,
|
||||
#[error("indefinite length")]
|
||||
IndefiniteLength,
|
||||
}
|
||||
|
||||
impl From<BerLengthParseError> for LdapSequenceParseError {
|
||||
fn from(value: BerLengthParseError) -> Self {
|
||||
match value {
|
||||
BerLengthParseError::NeedMoreData(n) => LdapSequenceParseError::NeedMoreData(n),
|
||||
BerLengthParseError::TooLargeValue => LdapSequenceParseError::TooLargeLength,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LdapSequence<'a> {
|
||||
data: &'a [u8],
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl<'a> LdapSequence<'a> {
|
||||
pub fn parse_octet_string(data: &'a [u8]) -> Result<Self, LdapSequenceParseError> {
|
||||
Self::parse_with_identifier(data, 0x04)
|
||||
}
|
||||
|
||||
pub fn parse_referrals_sequence(data: &'a [u8]) -> Result<Self, LdapSequenceParseError> {
|
||||
Self::parse_with_identifier(data, 0xa3)
|
||||
}
|
||||
|
||||
pub fn parse_bind_response(data: &'a [u8]) -> Result<Self, LdapSequenceParseError> {
|
||||
Self::parse_with_identifier(data, 0x61)
|
||||
}
|
||||
|
||||
pub fn parse_extended_response(data: &'a [u8]) -> Result<Self, LdapSequenceParseError> {
|
||||
Self::parse_with_identifier(data, 0x78)
|
||||
}
|
||||
|
||||
pub fn parse_extended_response_oid(data: &'a [u8]) -> Result<Self, LdapSequenceParseError> {
|
||||
Self::parse_with_identifier(data, 0x8a)
|
||||
}
|
||||
|
||||
fn parse_with_identifier(
|
||||
data: &'a [u8],
|
||||
identifier: u8,
|
||||
) -> Result<Self, LdapSequenceParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(LdapSequenceParseError::NeedMoreData(1));
|
||||
}
|
||||
if data[0] != identifier {
|
||||
return Err(LdapSequenceParseError::InvalidType);
|
||||
}
|
||||
|
||||
let ber_length = BerLength::parse(&data[1..])?;
|
||||
if ber_length.indefinite() {
|
||||
return Err(LdapSequenceParseError::IndefiniteLength);
|
||||
}
|
||||
|
||||
let offset = 1 + ber_length.encoded_len();
|
||||
if ber_length.value() == 0 {
|
||||
Ok(LdapSequence {
|
||||
data: b"",
|
||||
encoded_len: offset,
|
||||
})
|
||||
} else if ber_length.value() + offset as u64 > data.len() as u64 {
|
||||
Err(LdapSequenceParseError::TooLargeLength)
|
||||
} else {
|
||||
let encoded_len = ber_length.value() as usize + offset;
|
||||
Ok(LdapSequence {
|
||||
data: &data[offset..encoded_len],
|
||||
encoded_len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data(&self) -> &'a [u8] {
|
||||
self.data
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert!(LdapSequence::parse_octet_string(b"").is_err());
|
||||
|
||||
let v = LdapSequence::parse_octet_string(&[0x04, 0x00]).unwrap();
|
||||
assert_eq!(v.data, b"");
|
||||
assert_eq!(v.encoded_len(), 2);
|
||||
|
||||
let v = LdapSequence::parse_octet_string(&[0x04, 0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.data, &[0x01, 0x02]);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,3 +5,9 @@
|
|||
|
||||
mod tlv;
|
||||
pub use tlv::{T1L2BVParse, TlvParse};
|
||||
|
||||
mod ber;
|
||||
pub use ber::*;
|
||||
|
||||
mod ldap;
|
||||
pub use ldap::*;
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ impl fmt::Display for Host {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<url::Host> for Host {
|
||||
fn from(v: url::Host) -> Self {
|
||||
impl<T: Into<ArcStr>> From<url::Host<T>> for Host {
|
||||
fn from(v: url::Host<T>) -> Self {
|
||||
match v {
|
||||
url::Host::Ipv4(ip4) => Host::Ip(IpAddr::V4(ip4)),
|
||||
url::Host::Ipv6(ip6) => Host::Ip(IpAddr::V6(ip6)),
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LdapMessageIdParseError {
|
||||
NeedMoreData(usize),
|
||||
InvalidBerType,
|
||||
InvalidBytes,
|
||||
TooLargeValue,
|
||||
NegativeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LdapMessageId {
|
||||
value: u32,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl LdapMessageId {
|
||||
/// Try to parse a BER integer value with LDAP constraints from the buffer
|
||||
pub fn parse(data: &[u8]) -> Result<Self, LdapMessageIdParseError> {
|
||||
if data.len() < 3 {
|
||||
return Err(LdapMessageIdParseError::NeedMoreData(3 - data.len()));
|
||||
}
|
||||
|
||||
if data[0] != 0x02 {
|
||||
return Err(LdapMessageIdParseError::InvalidBerType);
|
||||
}
|
||||
|
||||
match data[1] {
|
||||
1 => {
|
||||
if data[2] & 0x80 != 0 {
|
||||
return Err(LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
|
||||
Ok(LdapMessageId {
|
||||
value: data[2] as u32,
|
||||
encoded_len: 3,
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
if data[2] & 0x80 != 0 {
|
||||
return Err(LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
|
||||
if data.len() < 4 {
|
||||
return Err(LdapMessageIdParseError::NeedMoreData(4 - data.len()));
|
||||
}
|
||||
|
||||
let value = u16::from_be_bytes([data[2], data[3]]) as u32;
|
||||
Ok(LdapMessageId {
|
||||
value,
|
||||
encoded_len: 4,
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
if data[2] & 0x80 != 0 {
|
||||
return Err(LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
|
||||
if data.len() < 5 {
|
||||
return Err(LdapMessageIdParseError::NeedMoreData(5 - data.len()));
|
||||
}
|
||||
|
||||
let value = u32::from_be_bytes([0, data[2], data[3], data[4]]);
|
||||
Ok(LdapMessageId {
|
||||
value,
|
||||
encoded_len: 5,
|
||||
})
|
||||
}
|
||||
4 => {
|
||||
if data[2] & 0x80 != 0 {
|
||||
return Err(LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
|
||||
if data.len() < 6 {
|
||||
return Err(LdapMessageIdParseError::NeedMoreData(6 - data.len()));
|
||||
}
|
||||
|
||||
let value = u32::from_be_bytes([data[2], data[3], data[4], data[5]]);
|
||||
Ok(LdapMessageId {
|
||||
value,
|
||||
encoded_len: 6,
|
||||
})
|
||||
}
|
||||
_ => Err(LdapMessageIdParseError::InvalidBytes),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> u32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
let e = LdapMessageId::parse(&[0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NeedMoreData(2));
|
||||
|
||||
let e = LdapMessageId::parse(&[0x03, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::InvalidBerType);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x00, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::InvalidBytes);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x01, 0x82]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x02, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x0102);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x02, 0x01]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NeedMoreData(1));
|
||||
let e = LdapMessageId::parse(&[0x02, 0x02, 0x81, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x03, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x010102);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x03, 0x01]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NeedMoreData(2));
|
||||
let e = LdapMessageId::parse(&[0x02, 0x03, 0x81, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
|
||||
let v = LdapMessageId::parse(&[0x02, 0x04, 0x01, 0x01, 0x01, 0x02]).unwrap();
|
||||
assert_eq!(v.value, 0x01010102);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
let e = LdapMessageId::parse(&[0x02, 0x04, 0x01, 0x01]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NeedMoreData(2));
|
||||
let e = LdapMessageId::parse(&[0x02, 0x04, 0x81, 0x01, 0x01, 0x02]).unwrap_err();
|
||||
assert_eq!(e, LdapMessageIdParseError::NegativeValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LdapMessageLengthParseError {
|
||||
NeedMoreData(usize),
|
||||
TooLargeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LdapMessageLength {
|
||||
value: u64,
|
||||
encoded_len: usize,
|
||||
}
|
||||
|
||||
impl LdapMessageLength {
|
||||
/// Try to parse a BER length value with LDAP constraints from the buffer
|
||||
pub fn parse(data: &[u8]) -> Result<Self, LdapMessageLengthParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(1));
|
||||
}
|
||||
|
||||
if data[0] & 0x80 == 0 {
|
||||
return Ok(LdapMessageLength {
|
||||
value: data[0] as u64,
|
||||
encoded_len: 1,
|
||||
});
|
||||
}
|
||||
|
||||
match data[0] & 0x7F {
|
||||
0 => Ok(LdapMessageLength {
|
||||
value: 0,
|
||||
encoded_len: 1,
|
||||
}),
|
||||
1 => {
|
||||
if data.len() < 2 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(2 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: data[1] as u64,
|
||||
encoded_len: 2,
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
if data.len() < 3 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(3 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u16::from_be_bytes([data[1], data[2]]) as u64,
|
||||
encoded_len: 3,
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
if data.len() < 4 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(4 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u32::from_be_bytes([0, data[1], data[2], data[3]]) as u64,
|
||||
encoded_len: 4,
|
||||
})
|
||||
}
|
||||
4 => {
|
||||
if data.len() < 5 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(5 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as u64,
|
||||
encoded_len: 5,
|
||||
})
|
||||
}
|
||||
5 => {
|
||||
if data.len() < 6 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(6 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, 0, 0, data[1], data[2], data[3], data[4], data[5],
|
||||
]),
|
||||
encoded_len: 6,
|
||||
})
|
||||
}
|
||||
6 => {
|
||||
if data.len() < 7 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(7 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, 0, data[1], data[2], data[3], data[4], data[5], data[6],
|
||||
]),
|
||||
encoded_len: 7,
|
||||
})
|
||||
}
|
||||
7 => {
|
||||
if data.len() < 8 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(8 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u64::from_be_bytes([
|
||||
0, data[1], data[2], data[3], data[4], data[5], data[6], data[7],
|
||||
]),
|
||||
encoded_len: 8,
|
||||
})
|
||||
}
|
||||
8 => {
|
||||
if data.len() < 9 {
|
||||
return Err(LdapMessageLengthParseError::NeedMoreData(9 - data.len()));
|
||||
}
|
||||
Ok(LdapMessageLength {
|
||||
value: u64::from_be_bytes([
|
||||
data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
|
||||
]),
|
||||
encoded_len: 9,
|
||||
})
|
||||
}
|
||||
_ => Err(LdapMessageLengthParseError::TooLargeValue),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.encoded_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> u64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert!(LdapMessageLength::parse(b"").is_err());
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x02]).unwrap();
|
||||
assert_eq!(v.value, 2);
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x80]).unwrap();
|
||||
assert_eq!(v.value, 0);
|
||||
assert_eq!(v.encoded_len(), 1);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x81]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(1));
|
||||
let v = LdapMessageLength::parse(&[0x81, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 2);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x82, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(1));
|
||||
let v = LdapMessageLength::parse(&[0x82, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 3);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x83, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(2));
|
||||
let v = LdapMessageLength::parse(&[0x83, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 4);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x84, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(2));
|
||||
let v = LdapMessageLength::parse(&[0x84, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 5);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x85, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(3));
|
||||
let v = LdapMessageLength::parse(&[0x85, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 6);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x86, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(3));
|
||||
let v = LdapMessageLength::parse(&[0x86, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 7);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x87, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(4));
|
||||
let v = LdapMessageLength::parse(&[0x87, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 8);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x88, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::NeedMoreData(5));
|
||||
let v = LdapMessageLength::parse(&[0x88, 0, 0, 0, 0, 0, 0, 0, 0x01]).unwrap();
|
||||
assert_eq!(v.value, 0x01);
|
||||
assert_eq!(v.encoded_len(), 9);
|
||||
|
||||
let v = LdapMessageLength::parse(&[0x89, 0, 0, 0]).unwrap_err();
|
||||
assert_eq!(v, LdapMessageLengthParseError::TooLargeValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod id;
|
||||
pub use id::{LdapMessageId, LdapMessageIdParseError};
|
||||
|
||||
mod length;
|
||||
pub use length::{LdapMessageLength, LdapMessageLengthParseError};
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod message;
|
||||
pub use message::{
|
||||
LdapMessageId, LdapMessageIdParseError, LdapMessageLength, LdapMessageLengthParseError,
|
||||
};
|
||||
|
|
@ -9,11 +9,9 @@ mod egress;
|
|||
mod error;
|
||||
mod haproxy;
|
||||
mod host;
|
||||
mod ldap;
|
||||
mod pool;
|
||||
mod port;
|
||||
mod proxy;
|
||||
mod quic;
|
||||
mod rate_limit;
|
||||
mod socks;
|
||||
mod tcp;
|
||||
|
|
@ -46,11 +44,9 @@ pub use haproxy::{
|
|||
ProxyProtocolEncodeError, ProxyProtocolEncoder, ProxyProtocolV2Encoder, ProxyProtocolVersion,
|
||||
};
|
||||
pub use host::Host;
|
||||
pub use ldap::*;
|
||||
pub use pool::ConnectionPoolConfig;
|
||||
pub use port::{PortRange, Ports};
|
||||
pub use proxy::{Proxy, ProxyParseError, ProxyRequestType, Socks4Proxy, Socks5Proxy};
|
||||
pub use quic::*;
|
||||
pub use rate_limit::{
|
||||
RATE_LIMIT_SHIFT_MILLIS_DEFAULT, RATE_LIMIT_SHIFT_MILLIS_MAX, TcpSockSpeedLimitConfig,
|
||||
UdpSockSpeedLimitConfig,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl HttpProxy {
|
|||
let host = url.host().ok_or(ProxyParseError::NoHostFound)?;
|
||||
let port = url.port().unwrap_or(8080);
|
||||
|
||||
let peer = UpstreamAddr::from_url_host_and_port(host.to_owned(), port);
|
||||
let peer = UpstreamAddr::new(host, port);
|
||||
|
||||
let auth = HttpAuth::try_from(url)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl Socks4Proxy {
|
|||
let host = url.host().ok_or(ProxyParseError::NoHostFound)?;
|
||||
let port = url.port().unwrap_or(1080);
|
||||
|
||||
let peer = UpstreamAddr::from_url_host_and_port(host.to_owned(), port);
|
||||
let peer = UpstreamAddr::new(host, port);
|
||||
|
||||
Ok(Socks4Proxy { peer })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Socks5Proxy {
|
|||
let host = url.host().ok_or(ProxyParseError::NoHostFound)?;
|
||||
let port = url.port().unwrap_or(1080);
|
||||
|
||||
let peer = UpstreamAddr::from_url_host_and_port(host.to_owned(), port);
|
||||
let peer = UpstreamAddr::new(host, port);
|
||||
|
||||
let auth = SocksAuth::try_from(url)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2026 G3-OSS developers.
|
||||
*/
|
||||
|
||||
mod var_int;
|
||||
pub use var_int::QuicVarInt;
|
||||
|
|
@ -28,8 +28,11 @@ pub struct UpstreamAddr {
|
|||
}
|
||||
|
||||
impl UpstreamAddr {
|
||||
pub fn new(host: Host, port: u16) -> Self {
|
||||
UpstreamAddr { host, port }
|
||||
pub fn new<T: Into<Host>>(host: T, port: u16) -> Self {
|
||||
UpstreamAddr {
|
||||
host: host.into(),
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
|
|
@ -109,14 +112,6 @@ impl UpstreamAddr {
|
|||
let host = Host::from_maybe_mapped_ip6(ip6);
|
||||
UpstreamAddr { host, port }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_url_host_and_port(host: url::Host, port: u16) -> Self {
|
||||
UpstreamAddr {
|
||||
host: host.into(),
|
||||
port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Url> for UpstreamAddr {
|
||||
|
|
@ -127,7 +122,7 @@ impl TryFrom<&Url> for UpstreamAddr {
|
|||
let port = u
|
||||
.port_or_known_default()
|
||||
.ok_or_else(|| anyhow!("unable to detect port in this url"))?;
|
||||
Ok(UpstreamAddr::from_url_host_and_port(host.to_owned(), port))
|
||||
Ok(UpstreamAddr::new(host, port))
|
||||
} else {
|
||||
Err(anyhow!("no host found in this url"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
Facts
|
||||
=====
|
||||
|
||||
The following keys are supported:
|
||||
The user group that auth the user based on connection facts.
|
||||
|
||||
The following common keys are supported:
|
||||
|
||||
* :ref:`name <conf_auth_user_group_name>`
|
||||
* :ref:`type <conf_auth_user_group_type>`
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ Groups
|
|||
|
||||
basic
|
||||
facts
|
||||
ldap
|
||||
|
||||
Common Keys
|
||||
===========
|
||||
|
|
|
|||
106
sphinx/g3proxy/configuration/auth/group/ldap.rst
Normal file
106
sphinx/g3proxy/configuration/auth/group/ldap.rst
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
.. _configuration_auth_user_group_ldap:
|
||||
|
||||
LDAP
|
||||
====
|
||||
|
||||
The user group that auth user with remote a LDAP server (simple bind).
|
||||
|
||||
The following common keys are supported:
|
||||
|
||||
* :ref:`name <conf_auth_user_group_name>`
|
||||
* :ref:`type <conf_auth_user_group_type>`
|
||||
* :ref:`static users <conf_auth_user_group_static_users>`
|
||||
* :ref:`source <conf_auth_user_group_source>`
|
||||
* :ref:`cache <conf_auth_user_group_cache>`
|
||||
* :ref:`refresh_interval <conf_auth_user_group_refresh_interval>`
|
||||
* :ref:`anonymous_user <conf_auth_user_group_anonymous_user>`
|
||||
|
||||
ldap_url
|
||||
--------
|
||||
|
||||
**required**, **type**: LDAP URL
|
||||
|
||||
Set the LDAP url in format `<schema>://<server_name>:[<port>]/<base_dn>`.
|
||||
The schema should be one of `ldap` or `ldaps`, the default for `ldap` is 389 while 636 will be used for `ldaps`.
|
||||
|
||||
tls_client
|
||||
----------
|
||||
|
||||
**optional**, **type**: :ref:`openssl tls client config <conf_value_openssl_tls_client_config>`
|
||||
|
||||
Set TLS parameters for this local TLS client.
|
||||
If set to empty map, a default config is used.
|
||||
|
||||
If the schema of LDAP url is "ldap" and this has been set, then "STARTTLS" will be used.
|
||||
|
||||
If the schema is "ldaps", a default value will be used if not set.
|
||||
|
||||
**default**: not set
|
||||
|
||||
unmanaged_user
|
||||
--------------
|
||||
|
||||
**optional**, **type**: :ref:`user <configuration_auth_user>`
|
||||
|
||||
Set and enable unmanaged users.
|
||||
|
||||
This is a template user config for all users that auth OK with the LDAP server but not has been set
|
||||
in both static and dynamic users config.
|
||||
|
||||
If not set, only static or dynamic users will be allowed.
|
||||
|
||||
**default**: not set
|
||||
|
||||
max_message_size
|
||||
----------------
|
||||
|
||||
**optional**, **type**: :ref:`humanize usize <conf_value_humanize_usize>`
|
||||
|
||||
Set the max header size when parsing response from the LDAP server.
|
||||
|
||||
**default**: 256
|
||||
|
||||
connect_timeout
|
||||
---------------
|
||||
|
||||
**optional**, **type**: :ref:`humanize duration <conf_value_humanize_duration>`
|
||||
|
||||
Set the timeout value when TCP connect to the LDAP server.
|
||||
|
||||
**default**: 4s
|
||||
|
||||
response_timeout
|
||||
----------------
|
||||
|
||||
**optional**, **type**: :ref:`humanize duration <conf_value_humanize_duration>`
|
||||
|
||||
Set the timeout value for the read of response from LDAP server.
|
||||
|
||||
**default**: 2s
|
||||
|
||||
connection_pool
|
||||
---------------
|
||||
|
||||
**optional**, **type**: :ref:`connection pool <conf_value_connection_pool_config>`
|
||||
|
||||
Set the connection pool config.
|
||||
|
||||
**default**: set with default value
|
||||
|
||||
queue_channel_size
|
||||
------------------
|
||||
|
||||
**optional**, **type**: usize
|
||||
|
||||
Set the queue channel size value when auth with the LDAP server for a client request.
|
||||
|
||||
**default**: 64
|
||||
|
||||
queue_wait_timeout
|
||||
------------------
|
||||
|
||||
**optional**, **type**: :ref:`humanize duration <conf_value_humanize_duration>`
|
||||
|
||||
Set the timeout value when auth with the LDAP server for a client request.
|
||||
|
||||
**default**: 4s
|
||||
Loading…
Add table
Add a link
Reference in a new issue