Qmail-Ldap administration sample
First create your domain Model
We are going to have two classes: LdapDomain and LdapAccount. The LdapAccount will contain LdapDomain
objects.
First lets look at the LdapDomain
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Novell.DirectoryServices.Linq;
namespace QmailLdap.Data
{
[DirectorySchema("phpQLAdminBranch",false,true,true)]
[DirectorySchema("organizationalUnit",true,true,true)]
public class LdapDomain : DirectoryEntity
{
UpdatableDirectorySource<LdapDomainAccount> _accounts = null;
private string _defaultDomain;
private string[] _additionalDomains;
private string[] _administrators;
private string _baseMailDir;
private string _baseHomeDir;
private string _name;
private int _defaultQuota;
private int _maxUsers;
[DirectoryAttribute("defaultDomain")]
public string DefaultDomain
{
get { return _defaultDomain; }
set
{
if (_defaultDomain != value)
{
_defaultDomain = value;
this.OnPropertyChanged("DefaultDomain");
}
}
}
[DirectoryAttribute("additionalDomainName")]
public string[] AdditionalDomains
{
get { return _additionalDomains; }
set
{
if (_additionalDomains != value)
{
_additionalDomains = value;
this.OnPropertyChanged("AdditionalDomains");
}
}
}
[DirectoryAttribute("administrator")]
public string[] Administrators
{
get { return _administrators; }
set
{
if (_administrators != value)
{
_administrators = value;
this.OnPropertyChanged("Administrators");
}
}
}
[DirectoryAttribute("baseMailDir")]
public string BaseMailDir
{
get { return _baseMailDir; }
set
{
if (_baseMailDir != value)
{
_baseMailDir = value;
this.OnPropertyChanged("BaseMailDir");
}
}
}
[DirectoryAttribute("ou",true)]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
this.OnPropertyChanged("Name");
}
}
}
//[DirectoryAttribute("defaultQuota")]
//public int DefaultQuota
//{
// get { return _defaultQuota; }
// set
// {
// if (_defaultQuota != value)
// {
// _defaultQuota = value;
// this.OnPropertyChanged("DefaultQuota");
// }
// }
//}
[DirectoryAttribute("maximumDomainUsers")]
public int MaxUsers
{
get { return _maxUsers; }
set
{
if (_maxUsers != value)
{
_maxUsers = value;
this.OnPropertyChanged("MaxUsers");
}
}
}
[DirectorySearchPath("ou=Employees")]
[DirectorySearchOptions(SearchScope.OneLevel)]
public UpdatableDirectorySource<LdapDomainAccount> Accounts
{
get { return _accounts; }
set { _accounts = value; }
//{
// if (_accounts == null)
// {
// _accounts = new UpdatableDirectorySource<LdapDomainAccount>(this.DirectoryEntry,
SearchScope.OneLevel);
// }
// return _accounts;
//}
}
}
}
We decorate the class with the DirectorySchema attribute to specify some things. The parameters are:
- schema- A single object class, to specify multiple you can use multiple attributes
- primary - Specifies that this object class is the structuralObjectClass for the directory entry
- search - use this objectClass when querying for the object in the directory
- useForNew - use this objectClass when creating new objects
We then decorate the Properties of our class to specify the attribute names in the directory. If the
property type is an array then multiple instances of the attribute in the directory are supported.
- name - the name of the attribute
- defaultNamingAttribute - used to define if the property is the one used in the DN, only one property
per class can be specified as defaultNamingAttribute. (An error should be thrown)
For the child property Accounts we specify two optional attributes:
- DirectorySearchPath - specifies a rdn to append to the search path
- DirectorSearchOptions - Allows you to specify the SearchScope
The property type is UpDatableDirectorySource<T> where T is your domain class. You can also use the
DirectorySource<T> type if you just need readonly access.
Now we specify the the LdapDomainAccount class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Novell.DirectoryServices.Linq;
namespace QmailLdap.Data
{
[DirectorySchema("qmailUser", false, true, true)]
[DirectorySchema("person", true, true, true)]
public class LdapDomainAccount : DirectoryEntity
{
[DirectoryAttribute("uid")]
public string Username { get; set; }
public string Firstname { get; set; }
[DirectoryAttribute("sn")]
public string Lastname { get; set; }
[DirectoryAttribute("cn",true)]
public string Fullname
{
get { return Firstname + " " + Lastname; }
set
{
//Set the firstname and the lastname
int i = value.IndexOf(" ");
if (i > 0)
{
Firstname = value.Substring(0, i);
Lastname = value.Substring(i+1);
}
else
{
Firstname = value;
//Lastname will be set to sn.
}
}
}
[DirectoryAttribute("accountStatus")]
public AccountStatus Status { get; set; }
[DirectoryAttribute("mail")]
public string PrimaryEmail { get; set; }
[DirectoryAttribute("mailMessageStore")]
public string MessageStore { get; set; }
[DirectoryAttribute("homeDirectory")]
public string HomeDirectory { get; set; }
[DirectoryAttribute("mailAlternateAddress")]
public string[] AlternateAddresses { get; set; }
[DirectoryAttribute("mailForwardingAddress")]
public string[] ForwardingAddresses { get; set; }
[DirectoryAttribute("mailReplyText")]
public string MailReplyText { get; set; }
[DirectoryAttribute("mailQuotaCount")]
public int MailQuotaCount { get; set; }
[DirectoryAttribute("mailQuotaSize")]
public int MailQuotaSize { get; set; }
[DirectoryAttribute("mailSizeMax")]
public int MailSizeMax { get; set; }
////Need to secure this property
//[DirectoryAttribute("userPassword")]
//internal string[] EncodedPassword
//{
// get
// {
// }
// set
// {
// }
//}
//public string Password
}
public enum AccountStatus
{
active,disabled,deleted
}
public enum DeliveryMode
{
nolocal, noforward, noprogram, reply
}
}
You will notice that there are two properties that use enumerations. The enum text values are stored
as string values in the directory, and are read from Enum.Parse
Now for the fun stuff. We need to create a DataContext class to be the base to query from. Here is
our implementation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Novell.DirectoryServices.Linq;
using System.Web.Security;
using Novell.Directory.Ldap;
namespace QmailLdap.Data
{
public class QmailLdapContext : DirectoryContext
{
public QmailLdapContext(LdapConnection conn, string baseDn)
:base(conn,baseDn)
{
}
[DirectorySearchOptions(SearchScope.Subtree)]
public UpdatableDirectorySource<LdapDomain> Domains { get; set; }
}
}
Pretty simple eh? We derive our context object from the DirectoryContext class and just pass off the
parameters from the constructor of our QmailLdapContext class to the DirectoryContext base class. You
can obviously do whatever you want in the constructor but you must call the base constructor to pass
in your connection and specify the base DN.
We define a property called Domain that is of type UpdatableDirectorySource<T> and mark it with the
DirectorySearchOptions attribute to say that we want a Subtree search. This will retrieve all of the
domain objects in the directory underneath of the baseDn.
How about some code that uses our sample. This is the test class that I use.
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using QmailLdap.Data;
using System.DirectoryServices;
using Novell.DirectoryServices.Linq;
using Novell.Directory.Ldap;
namespace Test
{
/// <summary>
/// Summary description for LinqLdapTest
/// </summary>
[TestClass]
public class LinqLdapTest
{
public LinqLdapTest()
{
//
// TODO: Add constructor logic here
//
}
private TestContext testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
#region Additional test attributes
//
// You can use the following additional attributes as you write your tests:
//
// Use ClassInitialize to run code before running the first test in the class
// [ClassInitialize()]
// public static void MyClassInitialize(TestContext testContext) { }
//
// Use ClassCleanup to run code after all tests in a class have run
// [ClassCleanup()]
// public static void MyClassCleanup() { }
//
// Use TestInitialize to run code before running each test
// [TestInitialize()]
// public void MyTestInitialize() { }
//
// Use TestCleanup to run code after each test has run
// [TestCleanup()]
// public void MyTestCleanup() { }
//
#endregion
[TestMethod]
public void RetrieveDomainObjects()
{
using (QmailLdapContext context = GetNewContext())
{
StringBuilder sb = new StringBuilder();
foreach (LdapDomain domain in context.Domains)
{
sb.AppendFormat("{0} {1} {2} {3}\r\n", domain.Name, domain.BaseMailDir,
domain.MaxUsers, domain.AdditionalDomains);
}
Console.Out.Write(sb.ToString());
}
return;
}
[TestMethod]
public void RetrieveAccountObjects()
{
using (QmailLdapContext context = GetNewContext())
{
StringBuilder sb = new StringBuilder();
foreach (LdapDomain domain in context.Domains)
{
sb.AppendFormat("{0} {1} {2} {3}\r\n", domain.Name, domain.BaseMailDir,
domain.MaxUsers, domain.AdditionalDomains);
foreach (LdapDomainAccount account in domain.Accounts)
{
sb.AppendFormat("{0}, {1}, {2}, {3}\r\n", account.Username,
account.PrimaryEmail, account.Status, account.Fullname);
}
}
Console.Out.Write(sb.ToString());
}
return;
}
private static QmailLdapContext GetNewContext()
{
LdapConnection conn = new LdapConnection();
conn.Connect("ldap.test", LdapConnection.DEFAULT_PORT);
conn.Bind("uid=admin,dc=example,dc=com", "password");
return new QmailLdapContext(conn, "dc=example,dc=com");
}
Random r = new Random((int)DateTime.Now.Ticks);
private string GetRandomName(int length)
{
StringBuilder sb = new StringBuilder(length);
for(int i = 0; i < length; ++i)
{
sb.Append((char)(r.Next(23) + 97));
}
return sb.ToString();
}
[TestMethod]
public void AddRemoveObjects()
{
using (QmailLdapContext context = GetNewContext())
{
LdapDomain newDomain = new LdapDomain();
newDomain.Name = GetRandomName(12) + ".com";
newDomain.AdditionalDomains = new string[] { "test1.com", "test2.com" };
newDomain.BaseMailDir = "/home/domains/" + newDomain.Name;
newDomain.DefaultDomain = newDomain.Name;
newDomain.Administrators = new string[] { "uid=admin,dc=acrosonic,dc=com" };
context.Domains.InsertOnSubmit(newDomain);
context.SubmitChanges();
context.Domains.DeleteOnSubmit(newDomain);
context.SubmitChanges();
}
}
private string GetRandomEmail()
{
return string.Format("{0}@{1}.com", GetRandomName(5), GetRandomName(10));
}
[TestMethod]
public void AddRemoveSubObjects()
{
using (QmailLdapContext context = GetNewContext())
{
LdapDomain newDomain = new LdapDomain();
newDomain.Name = GetRandomName(12) + ".com";
newDomain.AdditionalDomains = new string[] { "test1.com", "test2.com" };
newDomain.BaseMailDir = "/home/domains/" + newDomain.Name;
newDomain.DefaultDomain = newDomain.Name;
newDomain.Administrators = new string[] { "uid=admin,dc=acrosonic,dc=com" };
context.Domains.InsertOnSubmit(newDomain);
context.SubmitChanges();
LdapDomainAccount account = new LdapDomainAccount();
account.Firstname = "TestFirst";
account.Lastname = "TestLast";
account.PrimaryEmail = GetRandomEmail();
account.HomeDirectory = account.PrimaryEmail;
account.AlternateAddresses = new string[] { GetRandomEmail(), GetRandomEmail() };
account.ForwardingAddresses = new string[] { GetRandomEmail(), GetRandomEmail() };
account.MessageStore = "./Maildir";
account.Username = account.PrimaryEmail;
account.Status = AccountStatus.active;
newDomain.Accounts.InsertOnSubmit(account);
context.SubmitChanges();
//TODO: Verify that the entry exists with the specified values.
context.Domains.DeleteOnSubmit(newDomain);
context.SubmitChanges();
}
}
[TestMethod]
public void TestLinqUsingDirectQueries()
{
string s = "example.org";
using (QmailLdapContext context = GetNewContext())
{
var v = from d in context.Domains
where d.Name == s
select d;
LdapDomain domain = v.FirstOrDefault();
Assert.IsNotNull(domain);
}
}
[TestMethod]
public void TestLinqUsingEnumerator()
{
string s = "example.org";
using (QmailLdapContext context = GetNewContext())
{
var v = from d in context.Domains
where d.Name == s
select d;
IEnumerator<LdapDomain> ie = v.GetEnumerator();
Assert.IsTrue(ie.MoveNext());
LdapDomain domain = ie.Current;
//LdapDomain domain = v.FirstOrDefault();
Assert.IsNotNull(domain);
}
}
}
}
Give it a shot, you will have to adjust your directory accordingly. Also if you create a new item and
the parent objects don't exist, organizationalUnits will be created to hold the new objects.
Good Luck!