Posted by RvdH under hMailserver on Apr 25 2015

I have been looking for ways to automatically configure Outlook/Thunderbird for my hMailserver accounts and ended up to writing a solution (that suits me) myself.
This topic assumes you have basic knowledge of IIS, C# and Asp.Net & MySQL. This tutorial also assumes you host both hMailserver (using MySQL as database) and your websites on the same server.

The basics of this tutorial lies within the fact both email clients support some sort of autoconfiguation options, Outlook by using Microsofts Exchange Server's autodiscover.xml and Thunderbird by it's own config-v1.1.xml. Now i could have written a simple XML file with required properties, but hey I would like to bind it to hMailserver to see if such account exists in the mailserver, if that account is active, if one tries to login using a domain alias and so on...

OK lets get started, first I we're gonna add a StoredProcedure to the hMailServer database, this StoredProcedure is used later to verify a specific account exists, if it's active or if a alias is used to logon. Excecute the MySQL query below on your hMailServers database:
 


CREATE PROCEDURE `hmailAccounts`(IN domain VARCHAR(80),
 IN email VARCHAR(255))
BEGIN
 SELECT hm_domains.domainname, hm_accounts.accountaddress FROM hm_domains 
 INNER JOIN hm_accounts ON hm_domains.domainid = hm_accounts.accountdomainid 
 LEFT JOIN hm_domain_aliases ON hm_domains.domainid = hm_domain_aliases.dadomainid 
 WHERE (LCASE(hm_domains.domainname)=domain OR hm_domain_aliases.daalias=domain) 
 AND hm_domains.domainactive<>False AND LCASE(hm_accounts.accountaddress)=Concat( SUBSTRING_INDEX(email, '@', 1), '@', domainname ) AND hm_accounts.accountactive<>False;
END

Next thing is creating a asp.net handler for Outlook that queries our hMailServer database, if you have enough knowledge of MySQL you see the StoredProcedure above, verifies if submitted emailaddress is a valid and active account and if you tried to logon using a domain alias. Outlook submits (POST method) a xml file to our handler, the handler reads out the emailaddress submitted and queries the `hmailAccounts` StoredProcedure.

autodiscover.ashx (NOTE: you can alter the order of the xml elements, lets say you prefer SSL connection above Non-SSL connection move all SSL related parts above the Non-SSL parts
 
<%@ WebHandler Language="C#" Class="autodiscover" %>

using System;
using System.Web;
using System.Xml;
using System.Net.Mail;
using MySql.Data.MySqlClient;
using System.Configuration;

public class autodiscover : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        
        string email = null;
        string defaultdomain = "mail.something.com"; // default domain for SSL & TLS connections
        string domainsuffix = null;
        MailAddress emailaddress = null;
        
        if (HttpContext.Current.Request.ServerVariables["REQUEST_METHOD"].ToLower() == "post")
        {
            XmlDocument dom = new XmlDocument();
            dom.Load(HttpContext.Current.Request.InputStream);
            XmlNamespaceManager ns = new XmlNamespaceManager(dom.NameTable);
            ns.AddNamespace("ad", "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006");
            XmlNodeList nodeList = dom.SelectNodes("/ad:Autodiscover/ad:Request/ad:EMailAddress", ns);
            try
            {
                if (nodeList != null)
                {
                    foreach (XmlNode node in nodeList)
                    {
                        email = node.InnerText.ToLower();
                    }   
                }
            }
            catch {}
        }
        
        if (!String.IsNullOrEmpty(email))
        {
            try
            {
                emailaddress = new MailAddress(email);
                domainsuffix = emailaddress.Host;
                
                MySqlConnection conn;
                MySqlCommand comm;
                MySqlDataReader reader;
                string connectionString = ConfigurationManager.ConnectionStrings["hmailMySqlServer"].ConnectionString;
                conn = new MySqlConnection(connectionString);
                comm = new MySqlCommand("hmailAccounts", conn);
                comm.CommandType = System.Data.CommandType.StoredProcedure;
                comm.Parameters.Add("domain", MySqlDbType.VarChar);
                comm.Parameters["domain"].Value = domainsuffix;
                comm.Parameters.Add("email", MySqlDbType.VarChar);
                comm.Parameters["email"].Value = emailaddress;
                try
                {
                    conn.Open();
                    reader = comm.ExecuteReader();
                    if (reader.HasRows)
                    {
                        // Start XML        
                        context.Response.ContentType = "text/xml";
                        context.Response.Write("<?xml version='1.0' encoding='utf-8' ?>");
                        context.Response.Write("<Autodiscover xmlns='http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006'>");
                        context.Response.Write("<Response xmlns='http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a'>");
                        context.Response.Write("<Account>");
                        context.Response.Write("<AccountType>email</AccountType>");
                        context.Response.Write("<Action>settings</Action>");
                        // INCOMING POP3 NON-SSL, Port 110
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>POP3</Type>");
                        context.Response.Write("<Server>mail." + domainsuffix + "</Server>");
                        context.Response.Write("<Port>110</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>off</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("</Protocol>");
                        // INCOMING POP3 SSL, Port 995
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>POP3</Type>");
                        context.Response.Write("<Server>" + defaultdomain + "</Server>");
                        context.Response.Write("<Port>995</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>on</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("</Protocol>");
                        // INCOMING IMAP SSL, Port 993
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>IMAP</Type>");
                        context.Response.Write("<Server>" + defaultdomain + "</Server>");
                        context.Response.Write("<Port>993</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>on</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("</Protocol>");
                        /*
                        // INCOMING IMAP NON-SSL, Port 143
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>IMAP</Type>");
                        context.Response.Write("<Server>mail." + domainsuffix + "</Server>");
                        context.Response.Write("<Port>143</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>off</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("</Protocol>"); 
                        */
                        // OUTGOING SMTP NON-SSL, Port 587   
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>SMTP</Type>");
                        context.Response.Write("<Server>mail." + domainsuffix + "</Server>");
                        context.Response.Write("<Port>587</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>off</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("<UsePOPAuth>on</UsePOPAuth>");
                        context.Response.Write("<SMTPLast>off</SMTPLast>");
                        context.Response.Write("</Protocol>");
                        // OUTGOING SMTP NON-SSL, Port 25   
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>SMTP</Type>");
                        context.Response.Write("<Server>mail." + domainsuffix + "</Server>");
                        context.Response.Write("<Port>25</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>off</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("<UsePOPAuth>on</UsePOPAuth>");
                        context.Response.Write("<SMTPLast>off</SMTPLast>");
                        context.Response.Write("</Protocol>");
                        // OUTGOING SMTP SSL, Port 465   
                        context.Response.Write("<Protocol>");
                        context.Response.Write("<Type>SMTP</Type>");
                        context.Response.Write("<Server>" + defaultdomain + "</Server>");
                        context.Response.Write("<Port>465</Port>");
                        context.Response.Write("<LoginName>" + emailaddress + "</LoginName>");
                        context.Response.Write("<DomainRequired>on</DomainRequired>");
                        context.Response.Write("<SPA>off</SPA>");
                        context.Response.Write("<SSL>on</SSL>");
                        context.Response.Write("<AuthRequired>on</AuthRequired>");
                        context.Response.Write("<UsePOPAuth>on</UsePOPAuth>");
                        context.Response.Write("<SMTPLast>off</SMTPLast>");
                        // End XML
                        context.Response.Write("</Account>");
                        context.Response.Write("</Response>");
                        context.Response.Write("</Autodiscover>");
                    }
                    else
                    {
                        context.Response.Status = "404 Not Found";
                    }
                    reader.Close();
                }
                catch {}
                finally
                {
                    conn.Close();
                }
            }
            catch { }
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}


Our next step is creating a asp.net handler for Thunderbird that queries our hMailServer database just like the one above does. Thunderbird submit's the emailaddress as an querystring to our handler, the handler reads out the emailaddress submitted and queries the `hmailAccounts` StoredProcedure.

config-v1.1.ashx (NOTE: you can alter the order of the xml elements, lets say you prefer SSL connection above Non-SSL connection move all SSL related parts above the Non-SSL parts)
 
<%@ WebHandler Language="C#" Class="config" %>

using System;
using System.Web;
using System.Net.Mail;
using MySql.Data.MySqlClient;
using System.Configuration;

public class config : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {

        string email = null;
        string defaultdomain = "mail.something.com"; // default domain for SSL & TLS connections
        string domainsuffix = null;
        MailAddress emailaddress = null;
        
        if (!String.IsNullOrEmpty(System.Web.HttpContext.Current.Request.QueryString["emailaddress"]))
        {
            email = System.Web.HttpContext.Current.Request.QueryString["emailaddress"];
            try
            {
                emailaddress = new MailAddress(email);
                domainsuffix = emailaddress.Host;
                
                MySqlConnection conn;
                MySqlCommand comm;
                MySqlDataReader reader;
                string connectionString = ConfigurationManager.ConnectionStrings["hmailMySqlServer"].ConnectionString;
                conn = new MySqlConnection(connectionString);
                comm = new MySqlCommand("hmailAccounts", conn);
                comm.CommandType = System.Data.CommandType.StoredProcedure;
                comm.Parameters.Add("domain", MySqlDbType.VarChar);
                comm.Parameters["domain"].Value = domainsuffix;
                comm.Parameters.Add("email", MySqlDbType.VarChar);
                comm.Parameters["email"].Value = emailaddress;
                try
                {
                    conn.Open();
                    reader = comm.ExecuteReader();
                    if (reader.HasRows)
                    {
                        // Start XML
                        context.Response.ContentType = "text/xml";
                        context.Response.Write("<clientConfig version='1.1'>");
                        context.Response.Write("<emailProvider id='" + domainsuffix + "'>");
                        context.Response.Write("<domain>" + domainsuffix + "</domain>");
                        context.Response.Write("<displayName>" + domainsuffix + " Mail</displayName>");
                        context.Response.Write("<displayShortName>" + domainsuffix + "</displayShortName>");
                        // INCOMING POP3 NON-SSL, Port 110
                        context.Response.Write("<incomingServer type='pop3'>");
                        context.Response.Write("<hostname>mail." + domainsuffix + "</hostname>");
                        context.Response.Write("<port>110</port>");
                        context.Response.Write("<socketType>plain</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</incomingServer>");    
                        // INCOMING POP3 SSL, Port 995
                        context.Response.Write("<incomingServer type='pop3'>");
                        context.Response.Write("<hostname>" + defaultdomain + "</hostname>");
                        context.Response.Write("<port>995</port>");
                        context.Response.Write("<socketType>SSL</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</incomingServer>");
                        /*
                        // INCOMING IMAP NON-SSL, Port 143
                        context.Response.Write("<incomingServer type='imap'>");
                        context.Response.Write("<hostname>mail." + domainsuffix + "</hostname>");
                        context.Response.Write("<port>143</port>");
                        context.Response.Write("<socketType>plain</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</incomingServer>");
                        */
                        // INCOMING IMAP NON-SSL, Port 993
                        context.Response.Write("<incomingServer type='imap'>");
                        context.Response.Write("<hostname>" + defaultdomain + "</hostname>");
                        context.Response.Write("<port>993</port>");
                        context.Response.Write("<socketType>SSL</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</incomingServer>"); 
                        // OUTGOING SMTP NON-SSL, Port 587
                        context.Response.Write("<outgoingServer type='smtp'>");
                        context.Response.Write("<hostname>mail." + domainsuffix + "</hostname>");
                        context.Response.Write("<port>587</port>");
                        context.Response.Write("<socketType>plain</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</outgoingServer>");
                        // OUTGOING SMTP NON-SSL, Port 25
                        context.Response.Write("<outgoingServer type='smtp'>");
                        context.Response.Write("<hostname>mail." + domainsuffix + "</hostname>");
                        context.Response.Write("<port>25</port>");
                        context.Response.Write("<socketType>plain</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</outgoingServer>");
                        // OUTGOING SMTP SSL, Port 465
                        context.Response.Write("<outgoingServer type='smtp'>");
                        context.Response.Write("<hostname>" + defaultdomain + "</hostname>");
                        context.Response.Write("<port>465</port>");
                        context.Response.Write("<socketType>SSL</socketType>");
                        context.Response.Write("<authentication>password-cleartext</authentication>");
                        context.Response.Write("<username>" + emailaddress + "</username>");
                        context.Response.Write("</outgoingServer>");
                        // End XML
                        context.Response.Write("</emailProvider>");
                        context.Response.Write("</clientConfig>");
                    }
                    else
                    {
                        context.Response.Status = "404 Not Found";
                    }
                    reader.Close();
                }
                catch { }
                finally
                {
                    conn.Close();
                }
            }
            catch { }
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}


web.config
 
<!--?xml version="1.0" encoding="utf-8"?-->
<configuration>
   <connectionstrings>
      <remove name="hmailMySqlServer"></remove>
      <add connectionstring="Datasource=YOURHOSTNAME;Database=hmailserver;uid=root;pwd=YOURPASSWORD;" name="hmailMySqlServer" providername="MySql.Data.MySqlClient"></add>
   </connectionstrings>
   <system.webserver>
      <httperrors errormode="Detailed">
      <rewrite>
         <rules>
            <rule name="RewriteRule1">
               <match url="^autodiscover/autodiscover.xml$"></match>
               <action appendquerystring="false" logrewrittenurl="true" type="Rewrite" url="autodiscover/autodiscover.ashx"></action>
            </rule>
            <rule name="RewriteRule2">
               <match url="^mail/config-v1.1.xml$"></match>
               <action appendquerystring="true" logrewrittenurl="true" type="Rewrite" url="mail/config-v1.1.ashx"></action>
            </rule>
         </rules>
      </rewrite>
   </httperrors></system.webserver>
   <system.data>
      <dbproviderfactories>
         <remove invariant="MySql.Data.MySqlClient"></remove>
         <add description=".Net Framework Data Provider for MySQL" invariant="MySql.Data.MySqlClient" name="MySQL Data Provider" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></add>
      </dbproviderfactories>
   </system.data>
</configuration>


The next step is putting it all together...we can do this the easy or the hard way, I'll try to explain the easy way here :-)



In IIS Manager open up the "Default Web Site", the "Default Web Site" listens to anything without hostheaders so as long the A-record of a domain points to this particular server hMailserver is installed as well this example is sufficient for our needs, we just need to add https binding on port 443, also make sure you have Url Rewrite installed. FYI: If you are using https without valid SSL Certiificate outlook willl complain and popup a warning, just ignore and continue.

Make sure your domain(s) DNS records either allow wildcards, eg: *.
something.com or make a-records/cname reference for: autoconfig.something.com and/or autodiscover.something.com to the IP address of the server where both hmailserver and these handlers are hosted.


Thunderbird autoconfiguraton is requesting:

Host: http://autoconfig.something.com/mail/config-v1.1.xml?emailaddress=[EMAILADDRESS]


Outlook autoconfiguraton is requesting, first:

Host: https://something.com/autodiscover/autodiscover.xml

And if that fails:

Host: https://autodiscover.something.com/autodiscover/autodiscover.xml



Download Source

(NOTE: some parts in the code snippets have been updated after the code has been published)
Download all files discussed above: here  


Resources

https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration
https://technet.microsoft.com/en-us/library/cc511507(v=office.14).aspx
http://blogs.technet.com/b/kristinw/archive/2013/04/19/controlling-outlook-autodiscover-behavior.aspx