Just a short, simple blog for Bob to share his thoughts.
17 September 2011 • by Bob • History, Politics
When you study history, you are invariably introduced to Carroll Quigley's seven stages in The Evolution of Civilizations. In chapter 5 of his book, Quigley describes the seven states in the history of a civilization; these are:
Every great civilization has gone through this formula - with no exceptions.
From my perspective, the history of the United States has emerged in the following way:
According to Mr. Quigley's formula, all that the United States have left to face are Collapse and Invasion; civilizations do not recover once they have entered the Decay phase.
What is tragically ironic is that the people who vociferously claim to be trying to save the United States, namely Progressives and Liberals, are actually doing the most damage. As Quigley illustrates in his book, when members of a civilization become so preoccupied with arguing about what they perceive are their "rights" instead of contributing to society and adhering to an ethical set of standards or morals, the fabric of civilization unravels, and eventually implodes as an emerging civilization invades and conquers.
In this present day and age, people are rushing headlong into their inevitable demise; all the while they are wearing blinders which prevent them from seeing what is obvious to the less-outspoken of their peers. It is a sad manifestation of The Emperor's New Clothes; and even though the irony is missed by those who are too foolish to see themselves as members of the deceived, future generations will have the perspective granted by history with which to judge this time period with impartiality (and thereby with greater accuracy).
Several hundred years from now, historians of that coming era will look back with amazement as they analyze how the American civilization was ripped apart by the selfish desires of those who claimed to be acting in the best interests of society.
In closing, I think the rock group Rush expressed that sentiment quite well in their song "A Farewell to Kings":
A Farewell To Kings
When they turn the pages of history
When these days have passed long ago
Will they read of us with sadness
For the seeds that we let grow
We turned our gaze
From the castles in the distance
Eyes cast down
On the path of least resistance
Cities full of hatred, fear and lies
Withered hearts and cruel tormented eyes
Scheming demons dressed in kingly guise
Beating down the multitude
And scoffing at the wise
:-(
16 September 2011 • by Bob • FTP, Extensibility
This blog is designed as a complement to my FTP and LDAP - Part 1: How to Use Managed Code (C#) to Create an FTP Authentication Provider that uses an LDAP Server blog post. In this second blog, I'll walk you through the steps to set up an Active Directory Lightweight Directory Services (AD LDS) server, which you can use with the custom FTP LDAP Authentication provider that I discussed in my last blog.
The following steps will walk you through installing Active Directory Lightweight Directory Services on a computer that is running Windows Server 2008.






Note: Before completing these steps I created a local user account named "LdapAdmin" that I would specify the administrative account for managing my LDAP instance. This user account was only a member of the local "Users" group, and not a member of the local "Administrators" group.




































For additional information about working with AD LDS instances, see the following URLs:
While this is technically outside the scope of setting up the LDAP server, I'm reposting the notes from my last blog about adding the FTP LDAP Authentication provider and adding authorization rules for FTP users or groups.
Once these settings are configured and users connect to your FTP site, the FTP service will attempt to authenticate users from your LDAP server by using the custom FTP LDAP Authentication provider.
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
16 September 2011 • by Bob • FTP, Extensibility
Over the past few years I've created a series of authentication providers for the FTP 7.5 service that ships with Windows Server 2008 R2 and Windows 7, and is available for download for Windows Server 2008. Some of these authentication providers are available on the http://learn.iis.net/page.aspx/590/developing-for-ftp-75/ website, while others have been in my blog posts.
With that in mind, I had a question a little while ago about using an LDAP server to authenticate users for the FTP service, and it seemed like that would make a great subject for another custom FTP authentication provider blog post.
The steps in this blog will lead you through the steps to use managed code to create an FTP authentication provider that uses a server running Active Directory Lightweight Directory Services (AD LDS) that is located on your local network.
Note: I wrote and tested the steps in this blog using both Visual Studio 2010 and Visual Studio 2008; if you use an different version of Visual Studio, some of the version-specific steps may need to be changed.
The following items are required to complete the procedures in this blog:
Note: To test this blog, I used AD LDS on Windows Server 2008; if you use a different LDAP server, you may need to change some of the LDAP syntax in the code samples. To get started using AD LDS, see the following topics:
I tested this blog by using the user objects from both the MS-User.LDF and MS-InetOrgPerson.LDF Lightweight Directory interchange Format (LDIF) files.
To help improve the performance for authentication requests, the FTP service caches the credentials for successful logins for 15 minutes by default. This means that if you change the password in your AD LDS server, this change may not be reflected for the cache duration. To alleviate this, you can disable credential caching for the FTP service. To do so, use the following steps:
cd /d "%SystemRoot%\System32\Inetsrv" Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost Net stop FTPSVC Net start FTPSVC
In this step, you will create a project in Visual Studio 2008 for the demo provider.
net stop ftpsvc
call "%VS100COMNTOOLS%\vsvars32.bat">null
gacutil.exe /if "$(TargetPath)"
net start ftpsvc
net stop ftpsvc
call "%VS90COMNTOOLS%\vsvars32.bat">null
gacutil.exe /if "$(TargetPath)"
net start ftpsvc
In this step, you will implement the authentication and role extensibility interfaces for the demo provider.
using System; using System.Collections.Specialized; using System.Configuration.Provider; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using Microsoft.Web.FtpServer; public class FtpLdapAuthentication : BaseProvider, IFtpAuthenticationProvider, IFtpRoleProvider { private static string _ldapServer = string.Empty; private static string _ldapPartition = string.Empty; private static string _ldapAdminUsername = string.Empty; private static string _ldapAdminPassword = string.Empty; // Override the default initialization method. protected override void Initialize(StringDictionary config) { // Retrieve the provider settings from configuration. _ldapServer = config["ldapServer"]; _ldapPartition = config["ldapPartition"]; _ldapAdminUsername = config["ldapAdminUsername"]; _ldapAdminPassword = config["ldapAdminPassword"]; // Test for the LDAP server name (Required). if (string.IsNullOrEmpty(_ldapServer) || string.IsNullOrEmpty(_ldapPartition)) { throw new ArgumentException( "Missing LDAP server values in configuration."); } } public bool AuthenticateUser( string sessionId, string siteName, string userName, string userPassword, out string canonicalUserName) { canonicalUserName = userName; // Attempt to look up the user and password. return LookupUser(true, userName, string.Empty, userPassword); } public bool IsUserInRole( string sessionId, string siteName, string userName, string userRole) { // Attempt to look up the user and role. return LookupUser(false, userName, userRole, string.Empty); } private static bool LookupUser( bool isUserLookup, string userName, string userRole, string userPassword) { PrincipalContext _ldapPrincipalContext = null; DirectoryEntry _ldapDirectoryEntry = null; try { // Create the context object using the LDAP connection information. _ldapPrincipalContext = new PrincipalContext( ContextType.ApplicationDirectory, _ldapServer, _ldapPartition, ContextOptions.SimpleBind, _ldapAdminUsername, _ldapAdminPassword); // Test for LDAP credentials. if (string.IsNullOrEmpty(_ldapAdminUsername) || string.IsNullOrEmpty(_ldapAdminPassword)) { // If LDAP credentials do not exist, attempt to create an unauthenticated directory entry object. _ldapDirectoryEntry = new DirectoryEntry("LDAP://" + _ldapServer + "/" + _ldapPartition); } else { // If LDAP credentials exist, attempt to create an authenticated directory entry object. _ldapDirectoryEntry = new DirectoryEntry("LDAP://" + _ldapServer + "/" + _ldapPartition, _ldapAdminUsername, _ldapAdminPassword, AuthenticationTypes.Secure); } // Create a DirectorySearcher object from the cached DirectoryEntry object. DirectorySearcher userSearcher = new DirectorySearcher(_ldapDirectoryEntry); // Specify the the directory searcher to filter by the user name. userSearcher.Filter = String.Format("(&(objectClass=user)(cn={0}))", userName); // Specify the search scope. userSearcher.SearchScope = SearchScope.Subtree; // Specify the directory properties to load. userSearcher.PropertiesToLoad.Add("distinguishedName"); // Specify the search timeout. userSearcher.ServerTimeLimit = new TimeSpan(0, 1, 0); // Retrieve a single search result. SearchResult userResult = userSearcher.FindOne(); // Test if no result was found. if (userResult == null) { // Return false if no matching user was found. return false; } else { if (isUserLookup == true) { try { // Attempt to validate credentials using the username and password. return _ldapPrincipalContext.ValidateCredentials(userName, userPassword, ContextOptions.SimpleBind); } catch (Exception ex) { // Throw an exception if an error occurs. throw new ProviderException(ex.Message); } } else { // Retrieve the distinguishedName for the user account. string distinguishedName = userResult.Properties["distinguishedName"][0].ToString(); // Create a DirectorySearcher object from the cached DirectoryEntry object. DirectorySearcher groupSearcher = new DirectorySearcher(_ldapDirectoryEntry); // Specify the the directory searcher to filter by the group/role name. groupSearcher.Filter = String.Format("(&(objectClass=group)(cn={0}))", userRole); // Specify the search scope. groupSearcher.SearchScope = SearchScope.Subtree; // Specify the directory properties to load. groupSearcher.PropertiesToLoad.Add("member"); // Specify the search timeout. groupSearcher.ServerTimeLimit = new TimeSpan(0, 1, 0); // Retrieve a single search result. SearchResult groupResult = groupSearcher.FindOne(); // Loop through the member collection. for (int i = 0; i < groupResult.Properties["member"].Count; ++i) { string member = groupResult.Properties["member"][i].ToString(); // Test if the current member contains the user's distinguished name. if (member.IndexOf(distinguishedName, StringComparison.OrdinalIgnoreCase) > -1) { // Return true (role lookup succeeded) if the user is found. return true; } } // Return false (role lookup failed) if the user is not found for the role. return false; } } } catch (Exception ex) { // Throw an exception if an error occurs. throw new ProviderException(ex.Message); } } }
Note: If you did not use the optional steps to register the assemblies in the GAC, you will need to manually copy the assemblies to your IIS 7 computer and add the assemblies to the GAC using the Gacutil.exe tool. For more information, see the following topic on the Microsoft MSDN Web site:
In this step, you will add your provider to the list of providers for your FTP service, configure your provider for your LDAP server, and enable your provider to authenticate users for an FTP site.
cd %SystemRoot%\System32\Inetsrv
appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication']" /commit:apphost
appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapServer',value='MYSERVER:389']" /commit:apphost
appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapPartition',value='CN=MyServer,DC=MyDomain,DC=local']" /commit:apphost
appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapAdminUsername',encryptedValue='MyAdmin']" /commit:apphost
appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapAdminPassword',encryptedValue='MyPassword1']" /commit:apphost
In this blog I showed you how to:
When users connect to your FTP site, the FTP service will attempt to authenticate users from your LDAP server by using your custom authentication provider.
The PrincipalContext.ValidateCredentials() method will validate the user name in the userName parameter with the value of the userPrincipalName attribute of the user object in AD LDS. Because of this, the userPrincipalName attribute for a user object is expected to match the name of the user account that an FTP client will use to log in, which will should be the same value as the cn attribute for the user object. Therefore, when you create a user object in AD LDS, you will need to set the corresponding userPrincipalName attribute for the user object. In addition, when you create a user object in AD LDS, the msDS-UserAccountDisabled attribute is set to TRUE by default, so you will need to change the value of that attribute to FALSE before you attempt to log in.
For more information, see my follow-up blog that is titled FTP and LDAP - Part 2: How to Set Up an Active Directory Lightweight Directory Services (AD LDS) Server.
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
15 September 2011 • by Bob • Ponderings
Back in the 1980s I was a big fan of the Canadian Power Trio named "Triumph." As far as arena rock was concerned, few bands could put on a show that was anywhere near as entertaining as a Triumph concert. It wasn't just about being a fan - there are any number of great bands out there who could put on a good show if you already liked them; but Triumph put on a killer show whether you liked them or not.
At the height of their popularity, Triumph recorded what was to become one of their greatest hits, which was a song that was titled "Fight the Good Fight." Many guitar players - myself included - spent a good deal of time learning that song, and I always enjoyed playing it live in the various rock bands that I played in throughout my teenage years.
As the first official day of Autumn is just around the corner here in Seattle, the opening lines to "Fight the Good Fight" seem to take on special meaning:
"The days grow shorter,
And the nights are getting long.
Feels like we're running out of time."
As I look out of my office window, that's exactly what I see:
Our short-lived Pacific Northwest Summer appears to have come to a close, and the clouds seem like they're here for the duration. The sun is setting a little earlier each day, and within a few months the choleric combination of miserable mists and depressing dusk will shorten the average day to six hours or less of daylight. And yet the most discouraging fact that I have to wrestle with today is the knowledge that the weather will be this way for the next nine months.
[I exhale a deep sigh...] ![]()
Three months from now is the Winter Solstice, at which time we will confront the shortest day of the year; after that, we will at least have the small consolation that each day will be a little longer than the last, but we still won't see much of the sun until sometime next June or July.
[I heave another deep sigh...] ![]()
I wonder how much a plane ticket to Hawaii would cost in January? ![]()
08 September 2011 • by Bob • Scripting
This past weekend I was writing a quick piece of Windows Script Host (WSH) code to clean up some files on one of my servers, and I had populated a Scripting.Dictionary object with a bunch of string data that I was going to write to a log file. Obviously it's much easier to read through the log file if the data is sorted, but the Scripting.Dictionary object does not have a built-in Sort() method.
With this in mind, I set out to write a sorting function for my script, when I decided that it would might be more efficient to see if someone out in the community had already written such a function. I quickly discovered that someone had - and it turns out, that particular someone was me!
Way back in 1999 I published Microsoft Knowledge Base (KB) article 246067, which was titled "Sorting a Scripting Dictionary Populated with String Data." This KB article contained the following code, which took care of everything for me:
Const dictKey = 1 Const dictItem = 2 Function SortDictionary(objDict,intSort) ' declare our variables Dim strDict() Dim objKey Dim strKey,strItem Dim X,Y,Z ' get the dictionary count Z = objDict.Count ' we need more than one item to warrant sorting If Z > 1 Then ' create an array to store dictionary information ReDim strDict(Z,2) X = 0 ' populate the string array For Each objKey In objDict strDict(X,dictKey) = CStr(objKey) strDict(X,dictItem) = CStr(objDict(objKey)) X = X + 1 Next ' perform a a shell sort of the string array For X = 0 to (Z - 2) For Y = X to (Z - 1) If StrComp(strDict(X,intSort),strDict(Y,intSort),vbTextCompare) > 0 Then strKey = strDict(X,dictKey) strItem = strDict(X,dictItem) strDict(X,dictKey) = strDict(Y,dictKey) strDict(X,dictItem) = strDict(Y,dictItem) strDict(Y,dictKey) = strKey strDict(Y,dictItem) = strItem End If Next Next ' erase the contents of the dictionary object objDict.RemoveAll ' repopulate the dictionary with the sorted information For X = 0 to (Z - 1) objDict.Add strDict(X,dictKey), strDict(X,dictItem) Next End If End Function
Sometimes I make my day.
22 August 2011 • by Bob • Ponderings
My middle daughter turned 24 last week. This was a significant occasion by itself, but it was made even more significant because I had just walked her down the aisle only three weeks earlier when she married a great guy from Vancouver, BC.
It seems like only yesterday that I was teaching her how to brush her teeth, how to ride a bicycle, and how to write an English paper that didn't sound like she was talking to one of her friends on the telephone.
Momentous events like these will often motivate you sit back and wonder where the time went. It's been nearly thirty years since I became a "legal adult," but I still don't feel like I'm a "grown up." I still want to believe that my dad is the grown-up and I am just some long-haired kid from Arizona.
But it's easy for me to do the math - in a few short years my oldest daughter will turn thirty, so I must have grown up at some point; I just can't remember when.
![]()
03 August 2011 • by Bob • IIS, SSL
In this last appendix for my blog series about using SSL with IIS 6, I'll discuss processing a certificate request by using Windows 2003 Certificate Services. When you are running a certificate server for your network environment, you will need to physically issue the certificates that clients will request from your certificate server. There is a way that you can configure certificate services to automatically issue certificates, but I'd advise against that, unless you are only issuing certificates for testing purposes. If so, then you should read the Set the default action upon receipt of a certificate request topic on Microsoft's TechNet website.
That being said, the procedure to approve and issue a certificate is relatively easy; to do so, use the following steps:
That wraps up the last post in this blog series about using Secure Sockets Layer (SSL) with IIS 6.0, as well as some related information about using Windows 2003 Certificate Services. I hope this information helps administrators that have yet to upgrade to Windows Server 2008 or Windows Server 2008 R2. ;-]
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
29 July 2011 • by Bob • IIS, SSL
In this second appendix for my blog series about using SSL with IIS 6, I'm going to discuss obtaining the root certificate from Windows Server 2003 Certificate Services. By way of explanation, obtaining a root certificate is one of the most important steps for servers or clients that will use certificates that you issue. While this step is not necessary on the server where you installed Certificate Services, it is absolutely essential on your other servers or clients, because this step will allow those computers to trust your certificate server as a Certificate Authority (CA). Without that trust in place, you will either receive error messages or SSL simply won't work.
I've broken this process into two steps:

Note: If you were to bring up the properties for the root certificate, the certificate's icon should show an error; this is because the certificate has not been imported.
Before using any certificates that you issue on a computer, you need to install the Root Certificate. (This includes web servers and clients.)


Note: If you were to bring up the properties for the root certificate after you have installed it on your computer, you should see that the icon for the certificate no longer shows an error.
That's it for this post. In my next blog post, I'll discuss processing a certificate request.
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
28 July 2011 • by Bob • IIS, SSL
I needed to take a short break from my blog series about using SSL with IIS 6 in order to work on some other projects, but I wanted to finish the series by giving you a few appendices that give you some additional details that you might want to know if you are using SSL with IIS 6.
In this first appendix, I'll discuss how to install Certificate Services for Windows Server 2003. Installing Certificate Services will allow you to have your own Certificate Authority (CA), and thereby you will be able to issue certificates for your organization. It should be noted that Internet clients that are not part of your organization will not inherently trust your certificates - you will need to export your Root CA certificate, which I will describe in a later appendix for this blog series.
There are four different configurations that you can choose from when you are installing Certificate Services:
| Enterprise root CA | Integrated with Active Directory Acts as the root CA for your organization |
|---|---|
| Enterprise subordinate CA | Integrated with Active Directory Child of your organization's root CA |
| Stand-alone root CA | Not integrated with Active Directory Acts as the root CA for your certificate chain |
| Stand-alone subordinate CA | Not integrated with Active Directory Child of your certificate chain's root CA |
Note: More information about these options is available at http://technet.microsoft.com/en-us/library/cc756989.aspx
For this blog, I will discuss setting up a Stand-alone root CA.
That wraps up this blog post. In my next post I'll discuss obtaining the root certificate for your certificate server so you can install it on a client computer or an IIS server; this will allow other computers to trust the certificates that you issue.
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
20 July 2011 • by Bob • Humor
I freely admit that I am a "Dog Person." What's more, I am blessed to have married another dog person - we both love dogs, and this is generally a good thing. My wife grew up surrounded by dogs, as did I.
My wife and I spent the first ten years of our marriage in poverty or in the military, and unfortunately being in the military is a lot like being in poverty.
Just the same, we had been married ten years before the two of us were finally able to get a dog. Our first dog was a yellow Labrador Retriever named "Barney." Unfortunately, Barney had been mistreated by a previous owner and we were not able to keep him.
Our next dog was wonderful - we got a Bouvier des Flandres, who became a part of our family for the next eleven years. We named him "Ruff Waldo Emerson," which we shortened to Emerson. I had never owned a herding dog before, and it was a lot of fun to watch the way that he took care of our family: he would patiently wait by the door for the kids to arrive home safely from school, and he would try to push me out of my desk chair when he decided that it was time for me to go to bed.
Our most recent dog was a red-haired Golden Retriever, who our son named "Rook." (Our son, Peter, was heavily into chess at the time.) Rook was a great dog, and I now see why so many people love Golden Retrievers. Sadly, Rook died of a fast-acting bone cancer when he was just eight years old. ![]()
All of this is simply an introduction in order to offer proof that I am a dog lover. But that being said, I am decidedly not a "Cat Person." I am allergic to cats, which I think is God's way of saying that man isn't meant to coexist with cats. My daughter has a cat, and her cat seems to like me more than anyone else that comes to visit - which seems to be due to the fact that I ignore it.
Here are several of my thoughts on dogs versus cats:
The debate over which is better – dogs or cats - is ages old, and not likely to ever be resolved. But in my estimation, dogs will always be man's best friend, while cats will remain - at best - frenemies.