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!

Last edited Oct 2, 2008 at 3:41 AM by tmoulton, version 3

Comments

No comments yet.