Restarting Explorer shell from Windows 8.1 custom shell

The other day I wanted to make Mediaportal boot directly on my HTPC, but still use some desktop functionality on occasions.

The easiest way to do this is to change the Windows shell from explorer.exe to the executable of your choice. This can be done for all users by changing this registry key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon\Shell

In Windows 8.1 (and probably earlier versions) you can’t simply use Ctrl+Alt+Delete > Task Manager > File > Run New Task > explorer.exe – as explorer is no longer set as the shell so it will simply open an explorer file window rather than restore the desktop environment.

You will need to set the registry key back to explorer.exe and run userinit.exe to get the desktop back.

I will write a longer post about the whole experience at some point, but in the meantime here’s a batch file I wrote to achieve what I needed for MediaPortal saved as C:\Utils\startmp.bat:

C:\Program Files\Team MediaPortal\MediaPortal\MediaPortal.exe
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon"
  /v Shell /t REG_SZ /d explorer.exe /f
C:\Windows\System32\userinit.exe
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon"
  /v Shell /t REG_SZ /d C:\Utils\startmp.bat /f

Basically starts the program we want, when that program is quit, it sets the Windows shell as explorer, runs userinit and then resets the Windows shell to the batch file ready for next reboot.

Cisco SG500 Small Business Switches and Smart Ports

Wow, I’ve been using Cisco switches for over a decade and I’ve never come across something quite so ugly as Smart Ports.

Cisco now have a set of Small Business switches which are designed for a smaller budget – the first of the managed switches is the SG300 and they are cheap for Cisco, about a fifth of the price of a 2650X, and a stackable series called the SG500 which are at least half the price of a 2650X.

They run a cut down version of IOS that is mostly familiar, but has a few interesting quirks. The most infuriating of which are Smart Ports.

Smart Ports are basically a set of macros that get triggered based on the characteristics of a device you connect to a switch port. The idea is, if you plug a Cisco phone in, the port gets configured for the phone. If you daisy chain a PC to the phone, it gets configured appropriately for that. If you plug another Cisco switch in, it gets configured as a trunk between switches etc..

It’s a nice theory and to be honest nothing new, as cisco have had macros for a long time – just look at the way auto qos worked on their catalyst switches.

The bit that is particularly crap about these macros is that despite the intelligence to work out what has been connected, the implementation of the macro is plain dumb. For starters, the default macros make the assumption that your data VLAN is VLAN 1. I didn’t realise this at first, I had my desktops on VLAN 50 daisy chained through phones, as the voice vlan command is not supported on these switches I merrily configured a trunk with a native VLAN of 50 for my data, and a tagged VLAN of 215 for my voice

switchport mode trunk
switchport trunk native vlan 50
switchport trunk allowed vlan add 215

Only to discover that when I plugged in a phone, it reconfigured the native vlan to be VLAN 1 and broke the PC. Brilliant.

I also had a bunch of ports configured as a simple access port on VLAN 50.

switchport mode access
switchport access vlan 50

The web GUI showed these ports as being in an unknown state as far as Smart Ports was concerned, and when I tried to reset them and push a Smart Port macro to them, they fell over part way through and left them in a semi configured state (of course the web GUI didn’t report any problems, had to look at the switch console and logs to see the Macro was falling over at line 13). The only way to fix this was to manually remove the broken config the Macro had applied using the CLI, and then get it to apply again.

Also these switches only support a single voice vlan. As a result, you configure this voice vlan globally and it is then updated in all the macros.

Another issue is with the firmware updates. The switch has the concept of a boot firmware and a switch firmware – these are issued as two separate files. The switch firmware can be uploaded via the web gui and seems fairly straight forward. For reasons only known to Cisco the boot firmware can’t be uploaded in the same way and must be sent via SCP or TFTP. Also I found that I couldn’t upgrade from an old firmware directly to the latest (I would just get errors after the upload process). In the end I found stepping through each version in turn and rebooting after each worked. Nice.

Once you get over these foibles, the switch is alright. Its pretty comprehensive for a small business switch, however its poorly implemented. Cisco have pushed you towards the GUI to manage the switch rather than the slightly odd CLI – but have only done this half-heartedly. For example they’ve provided a reasonably simple web interface, but have clung on to some old concepts of copying running-config to start-up config. For Cisco engineers this is bread and butter, but then for Cisco engineers the CLI would be preferable rather than the GUI. For non Cisco engineers, a GUI is a welcome addition – but why have the complexity of copying running-config to startup-config just to save a change you made in a GUI screen – why can’t you just click “Save” !?

Basically, if you have to deal with one of these switches – my recommendation would be restore it to factory settings, manage it purely through the GUI and update the default smart port macros to suit your environment. If you want to use the CLI, make sure you disable smart ports through the GUI first, or this thing will drive you mad!

Django behind an F5 LTM with SSL Offload

This is a short post that describes the changes necessary to make Django work behind an F5 LTM device that has been configured with SSL offload (or SSL client profiles as F5 call them).

I’ll cover a bit of F5 specific configuration, but the principles can be used for most SSL offload devices. This example is using an F5 LTM running 11.3.0, and Django 1.4.5.

First this assumes that you have your django site working, you have added it as a node to an F5 LTM, created a pool and a virtual server using port 80 and you can access the site through the load balancer.

Next you need to enable SSL on your virtual server, and tell Django that it is behind an SSL offload device. You do this by inserting a header into the connection, and telling Django to look for this header. The name of the header is not important, so long as its configured the same on the load balancer as it is within Django. To do this you need to create an F5 HTTP Profile.

django-f5-ssloffload1

On the F5, go to Local Traffic > Profiles > Create… and set the parent profile to “http”.

Tick the box next to “Request Header Insert” and set the following:

HTTP_X_FORWARDED_PROTO:https

django-f5-ssloffload2

Save the profile, and apply it to your virtual server. Make sure you have an SSL Client profile configured and assigned to your virtual server, and that your virtual server is listening on port 443.

Now you need to add the following line to your Django application’s settings.py file.

SECURE_PROXY_SSL_HEADER = (‘HTTP_X_FORWARDED_PROTOCOL’, ‘https’)

To find out a bit more about this setting see: https://docs.djangoproject.com/en/1.4/ref/settings/.

Restart your apache or django runserver and point a browser at your site using HTTPs.

Using django-auth-ldap with Active Directory

There are a couple of online tutorials showing how to get up and running with django-auth-ldap, but I couldn’t find anything that discussed start to finish how to get it working against Active Directory and the Django admin site.

Hopefully this will help anyone trying to achieve a similar goal. The following was carried out on Django 1.4.5 with django-auth-ldap 1.2.1 on Debian 7.

The idea is to use Active Directory to authenticate users to a Django admin based application, and to map users to Django defined groups and permissions based on Active Directory group membership.

First you will need to install python-ldap and django-auth-ldap The process to do this isn’t obvious, you will need to install a number of packages before it will install properly with pip.

apt-get install libldap2-dev
apt-get install python-dev
apt-get install libsasl2-dev
pip-install python-ldap
pip-install django-auth-ldap

Next you are going to need the following AD related things:

  • User in order to bind to AD. You will need the full distinguished name.
  • The object class that you use for username within AD. This is normally “sAMAccountName”.
  • The object class name that you use for groups. This defaults to “group”.
  • Ideally a working domain, or at least a domain controller you can talk LDAP to.
  • Full distinguished name for any groups you want to associate with Django groups.

I’d highly recommend installing ADSIEdit on a windows desktop if you don’t already have it. This is an LDAP browser that lets you get at the full distinguished names of objects. Its much easier to find the object in ADSIEdit, and copy and paste than troubleshoot mistyped DNs! ADSIEdit is part of the Windows Server 2003 support tools, you can get them here: http://www.microsoft.com/en-us/download/details.aspx?id=15326.
ADSIEdit Screenshot

Now you need to add the following config to your django application settings.py file. I’ll go through what each bit does in the next section.

import ldap
from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType

# Binding and connection options
AUTH_LDAP_SERVER_URI = "ldap://domain.example:389"
AUTH_LDAP_BIND_DN = "CN=Bind_User,OU=Users,DC=domain,DC=example"
AUTH_LDAP_BIND_PASSWORD = "password"
AUTH_LDAP_CONNECTION_OPTIONS = {
    ldap.OPT_DEBUG_LEVEL: 1,
    ldap.OPT_REFERRALS: 0,
}

# User and group search objects and types
AUTH_LDAP_USER_SEARCH = LDAPSearch("OU=Users,DC=domain,DC=example",
    ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)")
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("OU=Groups,DC=domain,DC=example",
    ldap.SCOPE_SUBTREE, "(objectClass=group)")
AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType()

# Cache settings
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 300

# What to do once the user is authenticated
AUTH_LDAP_USER_ATTR_MAP = {
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail"
}
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    "is_staff": ["CN=Djano_Users,OU=Groups,DC=domain,DC=example",
        "CN=Django_AdminUsers,OU=Groups,DC=domain,DC=example"]
}
AUTH_LDAP_FIND_GROUP_PERMS = True

# The backends needed to make this work.
AUTHENTICATION_BACKENDS = (
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend')

Let’s look at each section in more detail:

AUTH_LDAP_SERVER_URI = "ldap://domain.example:389"
AUTH_LDAP_BIND_DN = "CN=Bind_User,OU=Users,DC=domain,DC=example"
AUTH_LDAP_BIND_PASSWORD = "password"

First you need to specify the domain controller. In a properly configured domain, its best to use the domain name and let DNS resolve this to a domain controller using sites and services. This way if domain controllers are changed over time, you won’t have to update anything. This example is using standard LDAP. I gave up trying to get LDAPS working, although this article is worth a go… http://www.djm.org.uk/using-django-auth-ldap-active-directory-ldaps/

The distinguished name (DN) should be provided for the account used to bind. The idea is the bind account is used to do the initial lookup of the user, and then the user credentials are used.

AUTH_LDAP_CONNECTION_OPTIONS = {
    ldap.OPT_DEBUG_LEVEL: 1,
    ldap.OPT_REFERRALS: 0,
}

The Debug level should be set if you are struggling to get something working. Details further down on how to actually get at the debug output. The OPT_REFERRALS needs to be set to 0 and things just don’t work without this…

AUTH_LDAP_USER_SEARCH = LDAPSearch("OU=Users,DC=domain,DC=example",
    ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)")

AUTH_LDAP_GROUP_SEARCH = LDAPSearch("OU=Groups,DC=domain,DC=example",
    ldap.SCOPE_SUBTREE, "(objectClass=group)")

AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType()

First of all, this section sets the OU in which to search for users. If there are multiple places that users are held within your Active Directory there are two options – either remove the OU and allow it to search the whole directory, or have a read of the Search Unions section of the django-auth-ldap documentation: https://pythonhosted.org/django-auth-ldap/authentication.html#search-bind.

The example above uses a username in the sAMAccountName field, if you use email for your username try this “(&(objectClass=user)(mail=%(user)s))”.

Finally, the group type should be set to either NestedActiveDirectoryGroupType() or ActiveDirectoryGroupType() – make sure you have imported these from django-auth-ldap.config at the top of your settings.py file. ActiveDirectoryGroupType() is quicker, but will require your users to live directly within any groups that you are mapping to Django groups. NestedActiveDirectoryGroupType() is slower, but as its name suggests lets you use nested groups.

AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 300

Django quite frequently makes LDAP requests. These two lines will cache group details received from Active Directory to improve performance. If you do this, bear in mind if you make an AD group change, you will have to wait for the cache timeout before they will be reflected in Django.

AUTH_LDAP_USER_ATTR_MAP = {
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail"
}

This section will populate the Django user meta data with fields from Active Directory.

AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    "is_staff": ["CN=Djano_Users,OU=Groups,DC=domain,DC=example",
        "CN=Django_AdminUsers,OU=Groups,DC=domain,DC=example"]
}

The above will set the is_staff flag for a user if they are a member of one of the groups specified. The full DN should be supplied for each group.

The is_staff flag needs to be set for a user to be able to view the admin site. Using the example config, when a user logs in, even if they don’t exist in Django – a user will automatically be created. There are a couple of options for controlling who is allowed to login:

  • Set your user search OU appropriate to restrict users.
  • Set the is_staff flag using a group, and put the users you want to access within this group. If you have multiple groups within Django that provide different permissions, create multiple AD groups and add them to the is_staff dictionary entry (as seen in the above example).
  • Read the Limiting Access section of the documentation for some very basic methods. https://pythonhosted.org/django-auth-ldap/groups.html
AUTH_LDAP_FIND_GROUP_PERMS = True

This command will automatically add users to appropriate Django groups based on the AD group membership. The documentation here is very scarce. You need to make sure that your AD group is named exactly the same as your Django group for the mapping to work. When testing this, be careful, any group settings you make through the admin interface manually will take priority over the AD group mappings.

AUTHENTICATION_BACKENDS = (
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend')

Finally these lines are required for any of the above to work!

If something is not configured right you tend to get a standard error message:

Please enter the correct username and password for a staff account. Note that both fields are case-sensitive.

If you get a 500 server error message, chances are you have syntax problems in your settings.py file.

You can add the following code to your settings.py file in order to help debug issues:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'stream_to_console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler'
        },
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'django_auth_ldap': {
            'handlers': ['stream_to_console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    }
}

This can be daunting if you are not familiar with Django logging. The example above will output django_auth_ldap debug messages to the console if you use the Django Runserver functionality to run the webserver for your site. In theory its possible to output to a file using a FileHandler, but I always encountered 500 server errors when I tried this. If you are using apache. The easiest thing to do is to stop apache, and start the django runserver in order to see the messages.

python /path/to/django/manage.py runserver x.x.x.x 80

Supplement with your server’s IP address, or 127.0.0.1 if you are running on localhost. Also make sure that ldap.OPT_DEBUG_LEVEL is set to 1 for messages to appear.