MS SQL encryption with Moodle

If you like things a bit niche you might be running Moodle on Linux connecting to a MS SQL server running on Windows. This all works well with the MS SQL Server drivers for PHP.

The drivers support encrypted SQL connections but Moodle doesn’t provide an option to enable this. There is an issue logged on the Moodle tracker but it’s been sat there for ages and to be honest there seems a lot of politics around submitting patches to Moodle. If you just want a quick and dirty way of enabling this yourself you can use the wonderful patch I’ve created below against lib/dml/sqlsrv_native_moodle_database.php. Hold on to your hats though.

>         'Encrypt' => !empty($this->dboptions['encrypt']),

Yep, a beautiful one line change. This applies cleanly against 3.9 but I imagine it will work in most recent versions as this file hasn’t changed for a a while.

Once the change has been made you can modify your config.php. Just add encrypt => 1 to your dboptions array.

$CFG->dboptions = array (
  'dbpersist' => 1,

  'encrypt' => 1,


Obviously you need to ensure you have SSL/TLS set up correctly on the SQL side, you have the correct trusted root certs on your linux box and that you’re connecting to the FQDN of your SQL Server that matches the subject of the cert. If you’ve done everything correctly you’ll now be enjoying that sweet, sweet encryption.


Automatically backing up Juniper switch configs

Over the past few years I’ve spent a considerable amount of time trying to find an cheap or free solution for backing up network devices at my workplace. As we were completely a Cisco house this lead be to installing RANCID on Ubuntu. This was a brilliant solution only taking a couple of hours to implement and backup the entire network.

Fast forward 8 years and things have changed dramatically, my workplace is no longer a Cisco only house, implementing Dell, Juniper, Fortinet and Cisco device into the network. This in turn has caused quite a bit of pain in getting our backups to work effectively. Our most recent requisition was some Juniper EX series switches. Investigation suggested that RANCID is capable of backing up Juniper equipment but it took some messing about to make it work. Here are the steps I have implemented to be able to get a backup of the switches:

  1. Software used:
    Ubuntu Linux
    RANCID 3.7
  2. Juniper Config
    Install a class specifically for the backup user:
set system login class backup permissions access
set system login class backup permissions admin
set system login class backup permissions firewall
set system login class backup permissions flow-tap
set system login class backup permissions interface
set system login class backup permissions network
set system login class backup permissions routing
set system login class backup permissions secret
set system login class backup permissions security
set system login class backup permissions snmp
set system login class backup permissions storage
set system login class backup permissions system
set system login class backup permissions trace
set system login class backup permissions view
set system login class backup permissions view-configuration

Install a new user and tie to the newly created class:

set system login user backup class backup
set system login user backup authentication plain-text-password

This isn’t ideal and the best way would be to implement the user with an SSH key-pair rather than passwords that way the RANCID server doesn’t have your network passwords in a plain text file.

3. RANCID Config

Here I have needed to make some modifications. First of all if you already have backups and are re-using the management addresses when migrating delete the files from the config folder and also the Entries file in the CVS folder of each individual site.

Modify your router.db file in your site folders to be the following for each of your juniper switches:


Next you will need to modify the base file for types within RANCID, for me this was in /etc/rancid. In this file fine the following line:

juniper;command;junos::ShowConfiguration;show configuration

and change it to this:

juniper;command;junos::ShowConfiguration;show configuration | display set

If required, modify your .cloingrc file to include your newly created user and password here is what I did as a test:

add method ssh
add user backup
add password passwordsetinjuniper

Then I needed to modify the junos perl script, for Ubuntu and the default install location this is in /usr/share/perl5/rancid/ The following line needs to be commented:

next if (/^## last commit: /i);

Finally if you’re running this in Ubuntu run the login for Juniper to ensure that the server can login to switch successfully:

/usr/lib/rancid/bin/jlogin -f /var/lib/rancid/.cloginrc junipermgmtIP

If all is well and you can successfully login proceed to running the rancid-run file and monitor the logs to confirm the configuration have backed up:

sudo su -c /var/lib/rancid/bin/rancid-run -s /bin/bash -l rancid


Relaying SMTP via Office 365 with legacy applications that don’t support TLS

So you’ve got some horrible application that needs to send out email but doesn’t support TLS or possibly even authenticated SMTP. Of course it’s critically important to the business and the vendor has no intention of implementing anything to help you out. You’ve done your cloud migration and the cloud vendor of course has disabled plain text SMTP ages ago. What do you do hotshot? WHAT DO YOU DO?

Well one way around it is to keep an on premise mail server, perhaps Exchange if you live the Office 365 life. This becomes a pain though, keeping it patched and having something else to administer. What you need is a lightweight relaying agent that you can install on your application server. That’s where comes in. It comes in *nix and Windows flavours and is nice and easy to install. The Windows installer walks you through the process and installs itself as a service.

Obviously you need to set up an account for the outbound email. In Office 365 this is nice and easy to do. Make sure you remember to enable “Authenticated SMTP” for the user in the the “Mail” tab in the 365 admin portal as it’s disabled by default. You probably also want to disable password expiry for the new account.

Set your outbound server to port 587 with STARTTLS enabled, enter your new 365 credentials and away you go. Make sure you don’t enable remote clients in EmaiRelay or people will be able to send out as the configured user which is obviously a bad thing.


Quick and dirty PlaySMS LDAP auth

PlaySMS is some awesome Open Souce SMS software but it lacks a couple of features for our use case, one of which was some form of centralised auth. Ultimately I’d like to write a proper plugin to allow SAML auth so we can front this with AzureAD but for now, as it’s at on premise anyway, we’ll have to make do with this bodgey LDAP integration.

Bear in mind that with this in place you’ll no longer be able login with any internal PlaySMS credentials so ensure that you create a user that matches your LDAP username and grant it admin permission before you apply this modification. It would of course be a trivial change to make this try to auth via the DB, then fail back to LDAP or vice versa if that’s what you’d prefer.

Anyway, open up plugins/core/auth/fn.php and replace

$db_query = "SELECT password,salt FROM " . _DB_PREF_ . "_tblUser WHERE flag_deleted='0' AND username='$username'";
	$db_result = dba_query($db_query);
	$db_row = dba_fetch_array($db_result);
	$res_password = trim($db_row['password']);
	$res_salt = trim($db_row['salt']);
	$password = md5($password . $res_salt);
	if ($password && $res_password && ($password == $res_password)) {


$ldapserver= "ldaps://ldapservername";
$ldap = ldap_connect($ldapserver);
$bind = @ldap_bind($ldap, $username . "@domainname.tld", $password);
if ($bind) {

Told you it was quick and dirty, but it works. Obviously you’ll need to ensure that you create users within PlaySMS that match the users in LDAP, we’re currently shoving this directly into the MySQL database.

You’ll also need to install the PHP LDAP extension.


Upgrading Dell Latitude 7390 2 in 1 from i5 8GB to i7 16GB

So this is super niche but I couldn’t find any info on this and took a leap of faith that things would work. Who knows, maybe it’ll help someone out one day.

I had a Latitude 7390 2 in 1 with i5-8250u CPU and soldered on 8GB RAM. It’s a fine laptop and I sort of love it but was really starting to struggle with only 8GB RAM and my battery was also knackered. Not wanting to replace the laptop, I set out on a quest to upgrade it but found little to no info as to whether it would work. Being me I thought “screw it let’s try anyway” and here we are.

What I started out with:
Motherboard: 0XMNM2 – i5 8250u, 8GB, no Thunderbolt 😦
Battery: 71TG4 – 45wh 11.4v
Cooler/fan/heatsink: 0P51WH

What I ended up with:
Motherboard: 02WCVJ – i7 8650u, 16GB, Thunderbolt 🙂
Battery: K5XWW – 60wh 7.6v
Cooler/fan/heatsink: 034T0C

So there you go, exciting stuff. Everything was plug and play really. Once you first reconnect the battery you’ll need to connect the laptop to a power source or it won’t boot. If you use a brand new motherboard you’ll then be asked to provide a service tag. I imagine you can enter anything here but I used the existing service tag of my device.

The original cooler does still attach to the board and you could probably get away with using it like I did for a couple of weeks until I could work out the right part number. It’s pretty much the same except for the fan being a bit smaller with more blades. The CPU is slightly further over to the left on the new board so the old cooler doesn’t quite fit correctly in the case making the back plate sit a few mm proud, you’ll also need to snap off one of the mounts on the fan to stop it fouling on the board. All in all better to get the new cooler.

For all you fan nerds out there, this is a photo with the new cooler on top and the old underneath. It’s a super bad photo that makes it just look like a shadow from the flash but you get the idea.

This also fixed my once a day random disconnect of USB devices which is nice. Guess the original board was faulty from day one, thanks Dell!


Decommissioning Skype for Business 2015 Server

After attempting to decommission an old Skype for Business 2015 Enterprise Cluster we ran into an issue which was stopping us from decommissioning the cluster itself. The error being presented was “Can’t publish topology because users still homed to pool that would be deleted”. We thought that we had moved all of our contacts and users across to the new but after further investigation we found the following Powershell script which helped us along the way to get rid of the orphaned objects:

Unfortunately, we ran into a problem where this script ended up presenting all of our response groups as being homed on our older cluster. We have identified the culprit piece of code and updated it to match against the pool FQDN you enter in the script. The script can be found below:


Moodle authentication against ASP.NET identity services database

Picture the scene – you have a custom enrolment application using ASP.NET identity for authentication and from out of nowhere someone decides that the users now need to be able to login to a VLE to complete assignments. Moodle already has a external database plugin so it can’t be too hard, except it doesn’t support the hashing that identity uses.

Given the short timescale to implement and crazy workload I of course went looking to see if anyone else had done this. There are some threads on Stack Exchange where people have tried to do the same thing and lots of info about how the hashing works so I set about porting the code to PHP only to find that someone had already done a much better job than I’d ever do. Thanks MDHearingAid.

So I cloned the repo and set about bodging it into Moodle. My bodge is not pretty but it works. If you want to do the same thing you can download my patch file (apologies for the Zip, WordPress won’t accept plain text files for some reason) and go at it, just don’t judge me too harshly. This is a patch against Moodle 3.8 but will probably/possibly work against other versions.

Obviously you need connectivity to the database that Identity Services is running on. So you’ll probably want to install Microsoft Drivers for PHP for SQL Server if you haven’t already and then set up your connection in Moodle under Site Administration -> Plugins -> Authentication -> External database. The table name will most likely be AspNetUsers. Username = Username , Password = PasswordHash. Under password format you should now see ASP.NET Identity Service or maybe just [[identityservice]] if my patch to the language file didn’t work properly.


Mist/PacketFence Web Auth

Please note this is not supported by PacketFence/Inverse at the time of writing

There aren’t really any guides out there for Mist and Packetfence integration. During our time working with the Mist engineers we were able to get the authentication services working between Mist and PacketFence. We’ll submit this as a PR to Packetfence in the hope that it’s included in the main release.

Mist Configuration

To make this work you need to configure the Mist system with an SSID with the following security parameters:

Security Settings
Radius Settings
CoA Settings

From here the Mist system attempts to authenticate the device using RADIUS-MAB against the PacketFence system. Should PacketFence not authenticate the device, it returns a redirect request for the device which the Mist AP picks up and forwards to the client so that they can register.

The CoA settings are required here as, should the user no longer become registered e.g. PacketFence times out the device, then a CoA is sent to the Mist system for the device to be disassociated from the wireless environment.

PacketFence Configuration

Firstly, the perl script will need to be installed into the PacketFence environment so PacketFence understand hows to communicate with Mist and to perform the CoA requests as per the configuration. To access this please visit here:

Mist uses the APs as a RADIUS Authenticator so each AP will need to be installed as a “Switch” in the PacketFence configuration. We have created a script to do this as PacketFence do not provide a POST in their API Documentation for config/switches as well as deployment of RADIUS to NPS which is available here:

However, should you only need to deploy one or two APs as part of a PoC then the switch settings required are as follows:

IP Address: Ip Address of the AP
Type: Cisco::Mist
Mode: Production
Deauthentication Method: RADIUS
Use CoA: Yes

Role by VLAN ID: Off
Role by Switch Role: Off
Role by Access List: Off
Role by Web Auth URL: On

Secret Passphrase: RadiusSecretKey


Mist – NPS and PacketFence Radius Scripts

Here is a generic script for importing Mist APs into Microsoft’s NPS RADIUS Server and Packetfence’s Switching configurations:

Mist Setup

$Headers = @{
Authorization = "Token xWH84fgSnZTBMfA2eC9azGqNR2RFfgpmRGo9FGbaw0DlTm6enmfrK0cxkIYtEhdEvvRZesWddU222vHT82hnb0eSZecswe1iWl9h7C"
$Sites = @("771cb8f4-83ac-4385-bf4b-a68a61a8c853", "32f7bd38-9f01-4a3e-81b0-d4afbbc10f12", "e6f6d5c8-4dc9-4a55-ba76-1a903ec5d3f4", "d1320dcc-1e38-4d10-8518-19d844c119f4", "c8d2c1ea-76dd-4183-8cd4-5efcf3de6c4a", "b67b5694-03c2-4155-9b3d-751484b58c65", "dfcd013d-17b3-4805-9b91-2c86f70f3936" )
$SiteNames = @("Site 1", "Site 2", "Site 3", "Site 4", "Site 5", "Site 6", "Site 7")

packetfence setup

$LoginParams = @{"username"="admin";"password"="supersecretpassword"}
$PFLogin = Invoke-WebRequest -Uri "" -Method POST -Body ($LoginParams|ConvertTo-JSON)
$PFToken = ConvertFrom-Json $PFLogin.Content
$PFToken = $PFToken.token
$PFHeaders = @{
Authorization = "Bearer $PFToken"

System Loop

for($i=0; $i -lt $Sites.length; $i++)
	Write-Host "Performing checks on site:" $SiteNames[$i]
	$Uri = "" + $Sites[$i] + "/stats/devices"
	$APStats = Invoke-WebRequest -Uri $Uri -Headers $Headers -ContentType "application/json"
	$Converted = ConvertFrom-Json $APStats
	$CurrentNPSClients = Get-NpsRadiusClient
	Foreach($AP in $Converted) {
	#check NPS and if the RADIUS Client doesn't exist create a new entry
	$ClientCheck = $false
	$NewNameObject = $
	$CurrentNPSClients | ForEach-Object {
		If($_.Name -eq $NewNameObject)
			$ClientCheck = $true
	Write-Host "Is the AP already configured in NPS:" $ClientCheck
	if($ClientCheck -eq $false)
		New-NpsRadiusClient -Name $ -Address $AP.ip -SharedSecret "RadiusSharedSecret"

		#check PacketFence to see whether there is a RADIUS Client if not create one
			$URIPF = "" + $AP.ip
			$PFSwitch = Invoke-WebRequest -Uri $URIPF  -Headers $PFHeaders -ContentType "application/json"
			Write-Host "The AP already configured in PacketFence:" $AP.ip
			Write-Host "Adding the following AP to PacketFence:" $AP.ip
			$IP = $AP.ip
			$Desc = $
			$PostValues = @{"AccessListMap"=$null;"ExternalPortalEnforcement"="Y";"REJECTAccessList"=$null;"REJECTRole"=$null;"REJECTUrl"=$null;"REJECTVlan"=$null;"RoleMap"=$null;"SNMPAuthPasswordRead"=$null;"SNMPAuthPasswordTrap"=$null;"SNMPAuthPasswordWrite"=$null;"SNMPAuthProtocolRead"=$null;"SNMPAuthProtocolTrap"=$null;"SNMPAuthProtocolWrite"=$null;"SNMPCommunityRead"=$null;"SNMPCommunityTrap"=$null;"SNMPCommunityWrite"=$null;"SNMPEngineID"=$null;"SNMPPrivPasswordRead"=$null;"SNMPPrivPasswordTrap"=$null;"SNMPPrivPasswordWrite"=$null;"SNMPPrivProtocolRead"=$null;"SNMPPrivProtocolTrap"=$null;"SNMPPrivProtocolWrite"=$null;"SNMPUserNameRead"=$null;"SNMPUserNameTrap"=$null;"SNMPUserNameWrite"=$null;"SNMPVersion"=$null;"SNMPVersionTrap"=$null;"UrlMap"="Y";"VlanMap"="N";"VoIPCDPDetect"=$null;"VoIPDHCPDetect"=$null;"VoIPEnabled"=$null;"VoIPLLDPDetect"=$null;"cliAccess"=$null;"cliEnablePwd"=$null;"cliPwd"=$null;"cliTransport"=$null;"cliUser"=$null;"coaPort"=$null;"controllerIp"=$null;"deauthMethod"="RADIUS";"defaultAccessList"=$null;"defaultRole"=$null;"defaultUrl"=$null;"defaultVlan"=$null;"description"="$Desc";"disconnectPort"=$null;"gamingAccessList"=$null;"gamingRole"=$null;"gamingUrl"=$null;"gamingVlan"=$null;"group"="default";"guestAccessList"=$null;"guestRole"=$null;"guestUrl"=$null;"guestVlan"=$null;"id"="$IP";"inlineAccessList"=$null;"inlineRole"=$null;"inlineTrigger"=$null;"inlineUrl"=$null;"inlineVlan"=$null;"isolationAccessList"=$null;"isolationRole"=$null;"isolationUrl"=$null;"isolationVlan"=$null;"macSearchesMaxNb"=$null;"macSearchesSleepInterval"=$null;"mac_trigger"=$null;"mode"=$null;"port_trigger"=$null;"radiusSecret"="RadiusSharedSecret";"registrationAccessList"=$null;"registrationRole"=$null;"registrationUrl"="";"registrationVlan"=$null;"ssid_trigger"=$null;"type"="Cisco::Mist";"uplink"=$null;"uplink_dynamic"="dynamic";"useCoA"="Y";"voiceAccessList"=$null;"voiceRole"=$null;"voiceUrl"=$null;"voiceVlan"=$null;"wsPwd"=$null;"wsTransport"=$null;"wsUser"=$null}
			$PFAddSwitch = Invoke-WebRequest -Uri "" -Method POST -Headers $PFHeaders -Body ($PostValues|ConvertTo-JSON)


Restricting presenters in MS Teams meetings

You might want to prevent everyone in a meeting from being able to present, perhaps you work in Education or something. You can only do that via Powershell at the moment, because Microsoft.

Install SfB Online Powershell Module

Open Powershell

Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession
Import-PSSession $sfbSession

or if, like me, you’re in a hybrid set up and your account is homed On-Premise you’ll need to do

Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession -OverrideAdminDomain ""
Import-PSSession $sfbSession

Once connected you can see your meeting policies and the current DesignatedPresenterRoleMode with

Get-CsTeamsMeetingPolicy | ft Identity, DesignatedPresenterRoleMode

Set your policy to allow only the organiser to present by default and allow the presenter to override this setting. I’m going to do this globally but obviously replace the identifier with whatever policy name you want to set.

Set-CsTeamsMeetingPolicy -Identity Global -DesignatedPresenterRoleMode OrganizerOnlyUserOverride

You can set four options here, which are self explanatory



Full details at