README.md

# ExOpenDirectory

Elixir bindings to macOS OpenDirectory.framework for directory services.

Provides native access to local accounts, LDAP servers, and Active Directory
domains via Apple's `opendirectoryd` daemon. Uses [Rustler](https://github.com/rusterlium/rustler)
NIFs backed by [objc2-open-directory](https://crates.io/crates/objc2-open-directory).

## Why Not Just Use `:eldap`?

OpenDirectory provides capabilities that raw LDAP cannot:

- **AD integration** — Apple's own AD plugin handles site-aware DC discovery,
  Kerberos ticket acquisition, nested group resolution, and GPO awareness
- **Local directory** — Query local macOS users/groups, not just networked ones
- **Password policy** — Reads `msDS-UserPasswordExpiryTimeComputed` and local
  password policy, computing days-until-expiry correctly
- **Authentication** — `ODRecord.verifyPassword` triggers the full auth chain
  (Kerberos on AD-bound nodes) rather than a simple LDAP bind

If you're building a NoMAD/Jamf Connect replacement, you need this framework.
If you just need basic LDAP queries, `:eldap` or `exldap` may suffice.

## Installation

```elixir
def deps do
  [{:ex_open_directory, "~> 0.1.0"}]
end
```

Requires Rust toolchain (`rustup`). macOS only.

## Usage

```elixir
# Connect to the search node (searches all configured directories)
{:ok, node} = ExOpenDirectory.connect(:search)

# Find a user
{:ok, record} = ExOpenDirectory.find_user(node, "jsmith")

# Get attributes
{:ok, attrs} = ExOpenDirectory.get_attributes(record, [
  "dsAttrTypeStandard:RealName",
  "dsAttrTypeStandard:EMailAddress"
])

# Check group membership (handles AD nested groups)
true = ExOpenDirectory.member?(node, "jsmith", "Engineering")

# Authenticate (triggers Kerberos on AD-bound nodes)
:ok = ExOpenDirectory.authenticate(node, "jsmith", "password123")

# Check password expiry
{:ok, policy} = ExOpenDirectory.password_policy(node, "jsmith")
policy.days_until_expiry #=> 14
```

## License

MIT