An experiment with FreeRADIUS (on github). For each request, radius makes a HTTP GET on a webserver which answers a bit of json. With this backend, you are free in your choice of database. You don’t have to change radius code, just write a little webserver.

require 'sinatra'

get '/base/authenticate' do
  content_type "application/json"

  # mac address = params["mac"]
  # login = params["login"]

  '{"password" : "some NTLM password", "vlan" : "a vlan number"}'
end

and in configuration file,

remotedb {
	port = 8080
	ip = 192.168.1.2
	base = "/base"
}

Easy? When I plug my network cable, radiusd asks my webserver

GET /base/authenticate?mac=[mac]&login=[login]

Sinatra answers

{"password" : "some NTLM password", "vlan" : "a vlan number"}

With this system, we can add more logic. For instance, a user wants to join the network. For security reason, I need his MAC address. In lot of system, you would have to enter the address yourself. With this module, you can go further and avoid enter every address.

Here is my database schema :

User has a login, a password and some computers
Computer has a mac address

And the webserver :

get '/authenticate' do
   content_type "application/json"

   user = User.find_by_login(params["login"])
   return '{}' if user.nil?

   computer = user.computers.where(mac: params["mac"]).limit(1).first
   if computer.nil?
     computer = user.computers.build(mac: params["mac"])
     return '{}' unless computer.save
   else
     return '{}'
   end

   '{"password" : "' + user.password + '", "vlan" : "0"}'
 end

Each time a new computer try to join the network with a valid user, the webserver add a computer to the user. We can go further by only enable this feature if the user verifies upon certain conditions.

Let’s add a time column (named association) to the user table (default to 0). I also add a link on my admin page which set the time to 10 minutes in the future and I set for every user created 30 minutes in the future.

class User
	def association?
		association >= Time.now
	end
end

And I add in the if-statement “user.association?” :

get '/authenticate' do
  content_type "application/json"

  user = User.find_by_login(params["login"])
  return '{}' if user.nil?

  computer = user.computers.where(mac: params["mac"]).limit(1).first
  if computer.nil? && user.association?
    computer = user.computers.build(mac: params["mac"])
    return '{}' unless computer.save
  else
    return '{}'
  end

  '{"password" : "' + user.password + '", "vlan" : "0"}'
end