
وصول أصلي بلغة C++ إلى Active Directory عبر ADWS، بدون .NET، بدون WCF، بدون حزمة HTTP.
وصول أصلي بلغة C++ إلى Active Directory عبر ADWS، بدون .NET، بدون WCF، بدون مكدس HTTP.
BridgeHead هي مكتبة ثابتة بلغة C++20 تنفذ مكدس بروتوكول Active Directory Web Services (ADWS) بالكامل مباشرة عبر TCP. سميت على اسم خادم جسر AD (Bridgehead)، وهي البوابة التي تمر من خلالها حركة مرور الدليل، وتمنح كود C++ الخاص بك نفس الوصول منخفض المستوى إلى المنفذ 9389 الذي تستخدمه أوامر PowerShell مثل Get-ADUser و Get-ADComputer تحت الغطاء.
طبقات النقل تغلف الطبقة التي تحتها عبر واجهة bridgehead::transport::IByteStream الشائعة. NbfseCodec هي أداة ترميز/فك ترميز يُستدعى من قبل AdwsClient لتشفير/فك تشفير رسائل SOAP قبل وبعد التجميع:```
AdwsClient WS-Enumeration + WS-Transfer [MS-ADDM]
├── NbfseCodec .NET Binary Format for SOAP [MC-NBFSE] (encode/decode)
└── NmfFramer .NET Message Framing [MC-NMF] (send/receive frames)
└── NnsSession .NET NegotiateStream [MS-NNS]
└── TcpSocket raw TCP/IP
The entry point for consumers is `bridgehead::adws::AdwsClient`.
---
## بداية سريعة
### الاستعلام عن جميع المستخدمين وتعدادهم```cpp
#include "bridgehead/adws/AdwsClient.hpp"
// NTLM (works on any host)
auto client = bridgehead::adws::AdwsClient::EnumerationClient(
"192.168.1.10", // DC IP or hostname
"DC01.corp.local", // DC FQDN, used in NMF Via header and Kerberos SPN
"CORP", // NetBIOS domain name
"Administrator", // username
"Passw0rd" // password
);
// Kerberos (username hidden on the wire; requires DC reachable on port 88)
auto client = bridgehead::adws::AdwsClient::EnumerationClient(
"192.168.1.10", "DC01.corp.local", "CORP",
"Administrator", "Passw0rd",
bridgehead::adws::AuthPackage::Kerberos
);
auto users = client.Query(
"(objectClass=user)",
{"sAMAccountName", "distinguishedName", "memberOf"}
);
for (auto& obj : users)
std::cout << obj.FirstValue("sAMAccountName") << '\n';
client.Enumerate( "(objectClass=computer)", {"dNSHostName", "operatingSystem"}, "", // empty = domain root base DN [](const bridgehead::adws::LdapObject& obj) { std::cout << obj.FirstValue("dNSHostName") << '\n'; return true; // return false to stop early (sends wsen:Release) } );
### النطاق والترقيم```cpp
// OneLevel scope, 50 objects per Pull round-trip
client.Query(
"(objectClass=user)",
{"sAMAccountName"},
"OU=Admins,DC=corp,DC=local",
50,
bridgehead::adws::SearchScope::OneLevel
);
auto objs = client.Query("(objectClass=user)", {"objectGUID", "objectSid"}); for (auto& obj : objs) { if (auto* b = obj.FirstBytes("objectGUID")) std::cout << bridgehead::adws::ParseGuid(b) << '\n'; // {XXXXXXXX-...} if (auto b = obj.FirstBytes("objectSid")) std::cout << bridgehead::adws::ParseSid(*b) << '\n'; // S-1-5-... }
### فك ترميز واصف الأمان```cpp
#include "bridgehead/adws/SecurityDescriptor.hpp"
auto objs = client.Query("(objectClass=user)", {"nTSecurityDescriptor"});
if (auto* raw = objs[0].FirstBytes("nTSecurityDescriptor")) {
auto sd = bridgehead::adws::ParseSecurityDescriptor(*raw);
std::cout << "Owner: " << sd.ownerSid << '\n';
for (auto& ace : sd.dacl.aces)
std::cout << " type=" << (int)ace.type
<< " mask=0x" << std::hex << ace.mask
<< " sid=" << ace.sid << '\n';
}
auto rc = bridgehead::adws::AdwsClient::ResourceClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
// Modify attributes rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { {"description", {"managed by bridgehead"}}, {"telephoneNumber", {"555-1234"}}, });
// Clear an attribute (both values and bytes empty = delete) rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { {"telephoneNumber", {}}, });
// Read back auto obj = rc.Get("CN=Alice,OU=Users,DC=corp,DC=local", {"description", "telephoneNumber"});
// Delete object rc.Delete("CN=TempUser,OU=Users,DC=corp,DC=local");
### أنواع تعديل LDAP، إضافة / استبدال / حذف
يستخدم `Put` بشكل افتراضي `Replace` (يكتب فوق جميع القيم الموجودة). استخدم `ModifyOperation` للتحكم الدقيق في السمات متعددة القيم:```cpp
using bridgehead::adws::LdapModification;
using bridgehead::adws::ModifyOperation;
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
// Append a value to an existing multi-valued attribute
LdapModification{"otherTelephone", {"555-9999"}, {}, ModifyOperation::Add},
// Remove one specific value (leave others intact)
LdapModification{"otherTelephone", {"555-0000"}, {}, ModifyOperation::Delete},
// Replace is the default, explicit here for clarity
LdapModification{"description", {"updated"}, {}, ModifyOperation::Replace},
});
// Move to a different OU rc.Move( "CN=Alice,OU=OldOU,DC=corp,DC=local", // current DN "CN=Alice,OU=NewOU,DC=corp,DC=local" // new DN );
// Rename in place (same parent, new CN) rc.Move( "CN=Alice,OU=Users,DC=corp,DC=local", "CN=AliceSmith,OU=Users,DC=corp,DC=local" );
### كتابة السمات الثنائية
قم بتوفير القيم الثنائية في حقل `bytes` من `LdapModification`. يتم ترميزها بـ base64 تلقائيًا على الشبكة:```cpp
std::vector<uint8_t> thumbnail = loadFile("photo.jpg");
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
LdapModification{"thumbnailPhoto", {}, {thumbnail}},
});
auto rf = bridgehead::adws::AdwsClient::ResourceFactoryClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
rf.Create( "CN=NewUser,OU=Users,DC=corp,DC=local", "user", {{"sAMAccountName", {"newuser"}}, {"userAccountControl", {"512"}}} );
### العمليات غير المتزامنة```cpp
#include "bridgehead/adws/AdwsClientAsync.hpp"
auto ac = bridgehead::adws::AdwsClientAsync::EnumerationClient(
"192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
auto future = ac.QueryAsync("(objectClass=user)", {"sAMAccountName"});
// ... do other work while the query runs ...
auto users = future.get(); // blocks until complete; re-throws any exception
انتهاء المهلة على DC بطيئة:```cpp if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { // query is still running }
جميع العمليات لها متغيرات غير متزامنة: `QueryAsync`, `EnumerateAsync`, `GetAsync`, `PutAsync`, `DeleteAsync`, `CreateAsync`, `MoveAsync`.
### تجمّع الاتصالات (أحمال العمل عالية التردد / متعددة الخيوط)```cpp
#include "bridgehead/adws/AdwsClientPool.hpp"
// Enumeration pool, Query / Enumerate
bridgehead::adws::AdwsClientPool pool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.maxSize = 4, // up to 4 concurrent authenticated sessions
});
auto users = pool.Query("(objectClass=user)", {"sAMAccountName"});
// Resource pool, Get / Put / Delete
bridgehead::adws::AdwsClientPool resPool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.maxSize = 4,
.endpoint = bridgehead::adws::PoolEndpoint::Resource,
});
auto obj = resPool.Get("CN=Alice,OU=Users,DC=corp,DC=local", {"mail"});
resPool.Put("CN=Alice,OU=Users,DC=corp,DC=local", {{"mail", {"[email protected]"}}});
// ResourceFactory pool, Create
bridgehead::adws::AdwsClientPool rfPool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.endpoint = bridgehead::adws::PoolEndpoint::ResourceFactory,
});
rfPool.Create("CN=NewUser,OU=Users,DC=corp,DC=local", "user",
{{"sAMAccountName", {"newuser"}}});
جميع الرؤوس العامة موجودة تحت include/bridgehead/. يمكن إنشاء وثائق API كاملة باستخدام Doxygen (انظر البناء).
bridgehead::adws::AdwsClientbridgehead::adws::AdwsClientAsyncمغلف غير متزامن يعتمد على std::future. نفس طرق المصنع مثل AdwsClient؛ كل عملية تُرجع std::future<T>:```cpp
std::future<std::vector> f = ac.QueryAsync(...);
std::future e = ac.EnumerateAsync(filter, attrs, baseDN, callback);
std::future g = ac.GetAsync(...);
std::future h = ac.PutAsync(...);
std::future i = ac.DeleteAsync(...);
std::future j = ac.CreateAsync(...);
std::future k = ac.MoveAsync(...);
للاستعلامات المتزامنة عبر اتصالات متعددة، استخدم `AdwsClientPool`.
### `bridgehead::adws::AdwsClientPool`
مجموعة آمنة للخيوط من الجلسات المصدقة مسبقًا. يتم إنشاء الاتصالات بشكل كسول وإعادة استخدامها عبر المكالمات.```cpp
pool.IdleCount(); // sessions currently idle
pool.TotalCount(); // idle + checked-out
pool.MaxSize(); // configured maximum pool size
pool.Move(dn, newDn); // Move/rename (Resource pool)
enum class SearchScope { Base, OneLevel, Subtree /default/ };
### `bridgehead::adws::LdapObject` / `LdapAttribute````cpp
struct LdapAttribute {
std::string name;
std::string syntax; // LdapSyntax OID, empty if absent
std::vector<std::string> values; // text values (raw base64 for binary)
std::vector<std::vector<uint8_t>> bytes; // decoded binary; parallel to values
};
struct LdapObject {
std::vector<LdapAttribute> attributes; // all returned attributes
const LdapAttribute* Find(const std::string& name) const; // case-insensitive
std::string FirstValue(const std::string& name) const;
const std::vector<uint8_t>* FirstBytes(const std::string& name) const;
};
std::string ParseGuid(const std::vector<uint8_t>& bytes); // → "{XXXXXXXX-XXXX-...}" std::string ParseSid (const std::vector<uint8_t>& bytes); // → "S-1-5-..."
### `FilterValue` ومساعدات بناء المرشح
`FilterValue` هو غلاف آمن من حيث النوع يقوم بتخطي الأحرف الأولية من RFC 4515 §3 (`\`, `*`, `(`, `)`, NUL) عند الإنشاء. استخدمه مع مساعدات البناء لجعل حقن LDAP مستحيلاً بنيوياً:```cpp
// UNSAFE, raw string concatenation, easy to forget escaping
client.Query("(sAMAccountName=" + username + ")", ...);
// SAFE, FilterValue escapes on construction; FilterEq composes the assertion
FilterValue user = username;
client.Query(FilterEq("sAMAccountName", user), ...);
// Compose complex filters
client.Query(
FilterAnd({
FilterEq("objectClass", FilterValue::Raw("user")), // Raw() for safe literals
FilterOr({
FilterEq("sAMAccountName", user),
FilterEq("mail", FilterValue(email)),
}),
FilterNot(FilterPresent("userAccountControl")),
}), {"sAMAccountName", "mail"}
);
EscapeLdapFilter(str) لا يزال متاحًا كبدائية منخفضة المستوى عندما تحتاج إلى بناء سلاسل التصفية يدويًا.
DnValue ومساعدات بناء DNنفس نمط FilterValue، ولكن لقيم RDN للاسم المميز (RFC 4514 §2.4). يقوم بإفلات ,, +, ", \, <, >, ;, NUL، و # والمسافة في البداية/النهاية:```cpp
// UNSAFE, comma injection breaks the DN structure
rc.Get("CN=" + username + ",OU=Users,DC=corp,DC=local", attrs);
// SAFE DnValue cn = username; // auto-escaped auto dn = BuildDn({DnAttr("CN", cn), "OU=Users", "DC=corp", "DC=local"}); rc.Get(dn, attrs);
// DnValue::Raw() for values you control auto dn2 = BuildDn({DnAttr("CN", DnValue::Raw("Service Account")), "OU=SvcAccounts", "DC=corp", "DC=local"});
### `bridgehead::adws::SecurityDescriptor````cpp
SecurityDescriptor ParseSecurityDescriptor(const std::vector<uint8_t>& bytes);
struct SecurityDescriptor {
uint16_t control; // SdControl::* flags
std::string ownerSid;
std::string groupSid;
bool hasDacl; Acl dacl;
bool hasSacl; Acl sacl;
};
struct Acl { std::vector<Ace> aces; };
struct Ace {
uint8_t type; // AceType::*
uint8_t flags; // AceFlags::*
uint32_t mask;
std::string sid;
std::string objectType; // GUID string, object ACEs only
std::string inheritedObjectType; // GUID string, object ACEs only
};
مساحات الأسماء الثابتة: AceType::*, AceFlags::*, SdControl::*.
bridgehead::ConnectionError / AuthenticationError / ProtocolErrorمُعرَّفة في include/bridgehead/Exceptions.hpp (مُضمَّنة بشكل غير مباشر بواسطة AdwsClient.hpp). تمتد الثلاثة جميعها من std::runtime_error:
| النوع | يُرمى عند |
|---|---|
ConnectionError | فشل على مستوى TCP: رفض، مهلة، خطأ إدخال/إخراج |
AuthenticationError | فشل التفاوض NTLM/Kerberos |
ProtocolError | استجابة خادم غير صحيحة في أي طبقة بروتوكول |
| try { |
auto client = bridgehead::adws::AdwsClient::EnumerationClient(...);
auto users = client.Query(...);
} catch (const bridgehead::ConnectionError& e) { // unreachable DC, wrong port, timeout } catch (const bridgehead::AuthenticationError& e) { // bad credentials, KDC unreachable } catch (const bridgehead::ProtocolError& e) { // unexpected server response } catch (const bridgehead::adws::SoapFault& e) { // DC returned a SOAP fault (e.g. invalid filter) } // or coarse-grained: // } catch (const std::runtime_error& e) { ... }
### `bridgehead::adws::SoapFault`
تُرمى على أي استجابة `s:Fault` من DC بدلاً من `std::runtime_error` العادي:```cpp
struct SoapFault : std::runtime_error {
std::string code; // e.g. "Sender"
std::string subcode; // e.g. "InvalidEnumerationContext", empty if absent
std::string reason; // human-readable text
};
bridgehead::adws::LdapModificationمستخدمة بواسطة Put و Create:```cpp
enum class ModifyOperation { Replace /default/, Add, Delete };
struct LdapModification { std::string name; std::vectorstd::string values; // text values std::vector<std::vector<uint8_t>> bytes; // binary values (base64-encoded on the wire) ModifyOperation operation = ModifyOperation::Replace; };
عندما يكون كل من `values` و`bytes` فارغين، يتم مسح/حذف السمة بغض النظر عن `operation`.
### `bridgehead::adws::ProtocolLimits`
معلمة اختيارية أخيرة في جميع طرق المصنع، ومصانع `AdwsClientAsync`، و`AdwsClientPool::Config::limits`. جميع الحقول لها قيم افتراضية آمنة، قم بتغييرها فقط إذا كانت لديك متطلبات محددة:```cpp
struct ProtocolLimits {
uint32_t nmfMaxFrameBytes = 64 * 1024 * 1024; // max NMF frame (default 64 MiB)
uint32_t nnsMaxPayloadBytes = 16 * 1024 * 1024; // max NNS packet (default 16 MB)
int tcpKeepaliveIdleSec = 60; // idle seconds before first probe
int tcpKeepaliveIntervalSec = 10; // seconds between probes
int tcpKeepaliveProbeCount = 5; // probes before declaring dead (POSIX only)
};
ارفع nmfMaxFrameBytes / nnsMaxPayloadBytes فقط إذا كنت تقرأ كائنات ذات سمات ثنائية كبيرة بشكل غير عادي (مثل nTSecurityDescriptor مع العديد من ACEs، أو thumbnailPhoto كبير). اخفض حقول keepalive في البيئات السحابية/NAT حيث يتم قطع الاتصالات الخاملة بشكل عدواني.
مساعدات البناء المسماة (مصانع ثابتة مضمنة):```cpp LdapModification::Replace(name, values) // Replace with text values (default) LdapModification::ReplaceBinary(name, bytes) // Replace with binary values LdapModification::Append(name, values) // Add to multi-valued attribute LdapModification::Remove(name, values={}) // Remove specific values (or all) LdapModification::Clear(name) // Delete the attribute entirely
---
## البناء
**المتطلبات:** CMake 3.25+ ومترجم متوافق مع C++20 (MSVC 2022+, GCC 12+, Clang 15+).```bash
# Configure and build (unit tests only, no live AD needed)
cmake -B build -A x64
cmake --build build --config Release
# Run unit tests
ctest --test-dir build -C Release --output-on-failure
# With integration tests (requires a live Domain Controller)
cmake -B build -A x64 -DBRIDGEHEAD_INTEGRATION_TESTS=ON
cmake --build build --config Release
./build/tests/Release/bridgehead_integration_tests.exe
# With CLI tool (adws_list, browse AD objects from the command line)
cmake -B build -A x64 -DBRIDGEHEAD_BUILD_TOOLS=ON
cmake --build build --config Release --target adws_list
./build/tools/Release/adws_list.exe --host <dc-ip> --fqdn <dc-fqdn> --domain <domain> --user <user>
# Generate API reference docs (requires Doxygen)
cmake --build build --target docs
# or directly:
doxygen Doxyfile
# Output: docs/doxygen/html/index.html
add_subdirectory(bridgehead) target_link_libraries(my_target PRIVATE bridgehead::bridgehead)
### الخيار ب، الحزمة المثبتة (`find_package`)```bash
cmake --install build --prefix /usr/local # or any install prefix
أما إذا كنت تستخدم macOS، فسوف تحتاج إلى أن تفعل شيئا في أكثر من ذلك بقليل لأن النظام قمت بتثبيته مع النقطة لا يبدو أن يكون له نفس المالك مثل الجهاز الافتراضي. في حالة تحتاج إلى تشغيل الأوامر التالية. لاحظ أن هذه المقالة تحتوي على مسار يختلف وفقا لإعداداتك. لذا ينبغي استخدام الصياغة التالية:
sudo chown -R $(whoami) $(brew --prefix)/*
pip3 install mitmproxy2swagger
mitmproxy2swagger -h
يتوفر إطار عمل أوكولو بثلاثة توزيعات: الإصدار الأساسي، إصدار Pro، وإصدار Enterprise. كما في الرسم التخطيطي أدناه، تقدم التوزيعات نفس الوظائف الأساسية ولكنها تختلف في قابلية التوسع والمرونة الإضافية. يمكنك اختيار أحد هذه التوزيعات بناءً على احتياجاتك الفعلية.
# سير عمل إطار العمل
# قم بتثبيت إطار العمل
# استيراد وحدة التحكم
# بدء التحكم
# نشر الوظائف
# إجراء الاستعلام
إصدار المجتمع هو إصدار مجاني مفتوح المصدر بالكامل يهدف إلى البدء السريع، والتكامل السهل، والتوزيع البسيط، ذو قابلية توسع محدودة. وهو مناسب للفرق الصغيرة، الفرق متوسطة الحجم، وحالات الاستخدام الفردية.
تحذير: تم تصميم إصدار المجتمع فقط لعزل كود العمل والتجارب الوظيفية في بيئة تطوير محلية (غير موثوقة). يُرجى عدم استخدامه في بيئة إنتاج بطريقة مباشرة. إذا كنت ترغب في الحصول على إصدار عبر الإنترنت من إطار العمل لبيئة الإنتاج، يُرجى الاطلاع على إصدار Pro وإصدار Enterprise.
التثبيت
في الوقت الحالي، أسهل وأسرع طريقة لتثبيت إطار عمل أوكولو هي استخدام pip. الجملة كالتالي:
pip install okulo
كيفية الحصول على pip
إذا قمت بتثبيت Python 2 >= 2.7.9 أو Python 3 >= 3.4 من python.org، فسيكون لديك بالفعل pip مثبتًا، لكنك ستحتاج إلى ترقية pip باستخدام الأوامر التالية:
# macOS/Linux
python -m pip install --upgrade pip
# Windows
python -m pip install --upgrade pip
بخلاف ذلك، تحقق من التعليمات هنا إذا كان منزلك ينقصه فعلا.
بمجرد تثبيت pip بشكل صحيح، يمكنك الحصول على مجتمع أوكولو عن طريق تشغيل الأمر:
pip install okulo
مستودعات إصدارات مختلفة لأوكولو
بقدر ما يتعلق الأمر بالإصدارات، يحتوي إطار عمل أوكولو حاليًا على ثلاث قنوات إصدار (وكذلك توافقات مع Python) لتتناسب مع سيناريوهات مختلفة:
إصدار Pro هو توزيع موصى به لبيئة الإنتاج في أوكولو. وهو نسخة كاملة الميزات مع آلية ترقية مدمجة، وأكثر ثباتًا واستقرارًا.
ملاحظة: تستمر أنشطة تطوير إصدار المجتمع على GitHub releases. إصدار Pro هو إصدار عبر الإنترنت يمكن استخدامه في بيئة الإنتاج، متوافق تمامًا مع واجهات برمجة التطبيقات لوحدة التحكم في إصدار المجتمع.
🔐 كيفية الحصول على إصدار Pro
إصدار Pro حاليًا تحت التطوير النشط. حيث أن بعض الميزات قد لا تعمل بعد. يُرجى البقاء على تواصل مع فريقنا على GitHub، ويمكنك أن تصبح مطورًا معتمدًا لتجربة الميزات الجديدة.
التوافق:
إصدار Pro متوافق مع جميع التوزيعات الرئيسية (عائلة RHEL، عائلة Debian، عائلة macOS، عائلة FreeBSD، Windows+WSL2). نشر إصدار Pro سهل مثل:
pip install okulo-pro
للتجربة السريعة، يمكنك التوجه إلى صفحة العرض التوضيحي ورؤية النتائج على الفور! (هذه صفحة ثابتة مصممة للعرض التوضيحي، وعرضها محدود.)
بخلاف ذلك، يمكنك أيضًا تشغيل إطار عمل أوكولو محليًا في 5 دقائق فقط مع 4 خطوات.
متطلبات النظام الأساسي لأوكولو:
نظام التشغيل:
ملاحظة للمستخدمين على أنظمة غير Linux: أوصي باستخدام مستودعات آمنة (مثل Homebrew) لتثبيت أوكولو. على macOS، تتم تغطية التبعيات الخاصة بـ cchardet بشكل جيد عبر Homebrew.
تحتاج إلى التحقق من حصولك على إصدار مناسب من Python مثبت:
$ python3 --version
إذا لم يكن لديك Python 3.6+، قم بتثبيته (انظر إصدار Python في قسم "متطلبات النظام الأساسي لأوكولو").
تثبيت Python على macOS
إذا كنت تستخدم macOS، فإن أفضل طريقة للحصول على Python 3 هي تثبيته عبر Homebrew:
brew install python3
تثبيت Python على Windows
إذا كنت تستخدم Windows، فمن المستحسن تثبيت Python من python.org. لا تنس تحديد المربع "إضافة Python إلى PATH". بديلاً عن ذلك، يمكنك أيضًا استخدام مدير حزم مثل Chocolatey.
choco install python
تثبيت Python على Linux
في معظم توزيعات Linux، يمكنك تثبيت Python 3 باستخدام مدير الحزم الخاص بالتوزيعة. على سبيل المثال، على Ubuntu/Debian:
sudo apt-get update
sudo apt-get install python3 python3-pip python3-venv python3-wheel
``````cmake
find_package(bridgehead REQUIRED)
target_link_libraries(my_target PRIVATE bridgehead::bridgehead)
pugixml، و ws2_32، و secur32 (ويندوز) / gssapi_krb5 (لينكس / ماك) يتم سحبها جميعًا بشكل غير مباشر.
| المنصة | الواجهة الخلفية للمصادقة | الحالة |
|---|---|---|
| ويندوز | SSPI (secur32.dll)، NTLM و Kerberos | يعمل |
| لينكس / ماك | GSSAPI (libgssapi_krb5)، SPNEGO / Kerberos | تم التجميع؛ لم يتم اختبار التكامل بعد |
كلا من AuthPackage::Ntlm و AuthPackage::Kerberos يعملان على ويندوز. يقوم Kerberos بإخفاء اسم المستخدم عبر الشبكة وهو مفضل في الإنتاج؛ بينما NTLM هو البديل عندما يكون KDC (المنفذ 88) غير قابل للوصول.
على لينكس / ماك، تستخدم الواجهة الخلفية لـ GSSAPI بروتوكول SPNEGO مع Kerberos. لتشغيل دعم NTLM، قم بتثبيت الإضافة gss-ntlmssp:```bash
apt install libgss-ntlmssp # Debian / Ubuntu
dnf install gssntlmssp # Fedora / RHEL
بدون المكوّن الإضافي، سيتفاوض الخادم مع Kerberos. تستخدم بيانات اعتماد المستخدم/كلمة المرور الصريحة `gss_acquire_cred_with_password` (MIT Kerberos 1.9+)؛ مرّر سلاسل فارغة لاستخدام ذاكرة التخزين المؤقت الافتراضية للبيانات (`kinit`). يتم تجميع مسار GSSAPI بشكل نظيف وصحيح من الناحية التصميمية، ولكن لم يتم التحقق من صحته من طرف إلى طرف مقابل DC حي بعد؛ تعامل مع دعم Linux/macOS كإصدار تجريبي.
> **ملاحظة التشفير:** يتم تشفير ADWS على المنفذ 9389 على طبقة NNS (مفتاح جلسة NTLM أو Kerberos AES-256)، وليس TLS. هذا حسب التصميم، لا يقدم DC خدمة TLS على هذا المنفذ. إذا كان المصادقة المستندة إلى الشهادة مطلوبة، فإن LDAPS (المنفذ 636) هو البديل.
---
## التبعيات
يتم جلبها تلقائياً في وقت التهيئة عبر `cmake/Dependencies.cmake`، لا حاجة إلى تثبيت يدوي:
| المكتبة | الإصدار | الغرض |
|---------|---------|---------|
| [Catch2](https://github.com/catchorg/Catch2) | v3.5.2 | إطار اختبار الوحدة (أهداف الاختبار فقط) |
| [pugixml](https://github.com/zeux/pugixml) | v1.14 | DOM لـ XML (تحليل استجابة ADWS فقط) |
لا توجد مكتبة OpenSSL ولا Asio ولا Boost.
---
## القيود المعروفة
- **NTLM على Linux/macOS** يتطلب المكوّن الإضافي GSSAPI `gss-ntlmssp` (انظر [دعم المنصة](#platform-support)). بدونه سيتفاوض الخادم مع Kerberos بدلاً من ذلك. يعمل NTLM على Windows بطبيعته عبر SSPI.
- **DOM لـ pugixml**، يتم تحليل كل استجابة SOAP بالكامل إلى شجرة XML في الذاكرة قبل إرجاع أي نتائج. هذا محدود بحجم الصفحة `maxElements` (الافتراضي 256 كائنًا لكل Pull)، لذا لا يتم الاحتفاظ بمجموعة النتائج الكاملة في الذاكرة مرة واحدة. يصبح الذاكرة مصدر قلق فقط مع أحجام الصفحات الكبيرة جدًا أو الكائنات التي تحمل سمات ثنائية كبيرة (مثل `nTSecurityDescriptor` على الكائنات التي تحتوي على العديد من ACEs). النهج القائم على SAX/البث المتنقل سيزيل ذلك لكنه غير مخطط له حاليًا.
---
## مراجع البروتوكولات
- **[MS-ADDM]**، Active Directory Web Services: نموذج البيانات والعناصر المشتركة
- **[MS-NNS]**، بروتوكول .NET NegotiateStream
- **[MC-NMF]**، إطار رسائل .NET
- **[MC-NBFSE]**، تنسيق .NET الثنائي: امتداد SOAP
- **[MS-DTYP]**، أنواع بيانات Windows (SECURITY_DESCRIPTOR، ACL، ACE، SID، GUID)
- **[MS-ADTS]**، المواصفات الفنية لـ Active Directory (مرشحات LDAP / السمات)
جميع المواصفات متاحة من [توثيق المواصفات المفتوحة من Microsoft](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-winprotlp/).
---
## شكر خاص
شكر خاص لشركة IBM X-Force ومشروع SoaPy على إلهامهم وأبحاثهم وعملهم المشترك الذي ساعد في إثراء هذا المشروع. كما أود أن أشكر Logan Goins، مبتكر SoaPy في IBM X-Force Red، على الجهد والرؤية التي جعلت هذا العمل ممكنًا.
---
## الترخيص
انظر [LICENSE](https://github.com/zakipedio/bridgehead/blob/main/LICENSE) للحصول على التفاصيل.
| الطريقة | نقطة النهاية | الوصف |
|---|
EnumerationClient(host, fqdn, domain, user, pass [, auth, timeoutMs, opTimeoutMs, limits]) | /Enumeration | مصنع، اتصال ومصادقة |
ResourceClient(...) | /Resource | مصنع لـ Get / Put / Delete / Move |
ResourceFactoryClient(...) | /ResourceFactory | مصنع لـ Create |
Query(filter, attrs [, baseDN, maxElems, scope]) | Enumeration | جمع جميع النتائج في vector |
Enumerate(filter, attrs, baseDN, callback [, maxElems, scope]) | Enumeration | تدفق النتائج عبر callback |
Get(dn, attrs) | Resource | قراءة سمات كائن واحد |
Put(dn, modifications) | Resource | تعديل السمات |
Delete(dn) | Resource | حذف كائن |
Create(dn, objectClass, attrs) | ResourceFactory | إنشاء كائن جديد |
Move(dn, newDn) | Resource | نقل أو إعادة تسمية كائن موجود |
| الخيار | الافتراضي | الوصف |
|---|
BRIDGEHEAD_BUILD_TESTS | ON | بناء مجموعة اختبارات الوحدة |
BRIDGEHEAD_INTEGRATION_TESTS | OFF | بناء اختبارات التكامل (يتطلب DC مباشر) |
BRIDGEHEAD_BUILD_TOOLS | OFF | بناء أداة CLI adws_list |
| قناة الإصدار | إصدار Python | ما إذا كان يتم التحديث تلقائيًا إلى أحدث إصدار | ما إذا كان سيتم إخطار المستخدمين بالثغرات الأمنية | الحالة |
|---|
| إصدار المجتمع | CPython 3.8 | لا | لا | غير موصى به لبيئة الإنتاج |
| إصدار Pro | Pyston 2.3.x | نعم (تلقائي من داخل إطار العمل) | نعم (تلقائي من داخل إطار العمل) | موصى به لبيئة الإنتاج |
| إصدار Enterprise | إطار عمل مخصص | نعم | نعم | التوزيع الأكثر تقدمًا، الأنسب للهندسة المعمارية واسعة النطاق |