Cost Effective Two Factor Authentication VPN

From Mindcreations
Jump to: navigation, search

In this paper I'll describe an approach to realize a two-factor authentication VPN (or strong authentication if you like the term) for Microsoft Windows clients by using only open and free software with cheap hardware.

Contents

[edit] The problem

Realize a cheap but secure strong authentication VPN for Windows clients for about 40 simultaneous users.

[edit] The solution

Build a linux based Microsoft compatible PPTP VPN hub by using open source software and GSM SIM authentication.

[edit] Why it worths and why not

Pros:

  • Cheap
  • Very secure
  • Simple authentication mechanism
  • No "headaches" for users
  • No extra configuration required on Windows client side
  • Flexible & customizable
  • Uses only open source software and Linux
  • Easy to upgrade/maintain
  • Can be used even from non Windows clients
  • Can be easily made redundant (ie. high availability)
  • Can be partially managed via web interface

Cons:

  • Poor performance (cannot take advantage of full speed of gigabit or fast ethernet interfaces) - With a test server (Xeon based) I reached 50mbps upload and 25mbps download. NOTE: the bottleneck is not about the hardware! It relies somewhere in the poptopd/pppd daemons.
  • Not very easy to set up (without this guide... obviously!)
  • Requires a real GSM modem with a valid SIM
  • Cannot be used for large VPNs (ie. with more than 50 users)
  • Some operations, like forced user disconnection, require ssh access to the VPN hub
  • Not so good management interface

[edit] Requirements

[edit] Hardware

  • One server (any kind/manufacturer will be ok). You can use even a VM but you should be able to connect it to a real GSM modem; VMWare v4.x enables you to connect any USB device to the VM for example.
  • Two NICs of any kind. Consider using Giga NICs if you need more that 100mbps of global bandwidth.
  • One GSM modem (USB/serial) - WARNING: do not use HUAWAI GSM modems or anything like data GSM modems
  • One regular GSM SIM (with voice calls enabled, ie. do not use data-only enabled SIMs)
  • One GSM phone/SIM per user
  • ACL/firewall filtering (optional, use it only if you want an extra security layer)

[edit] Software

  • Ubuntu 9.10 server or higher http://www.ubuntu.com
  • SMSd v3.x (bundled with Ubuntu) - Users of Ubuntu 10.04 should recompile smsd at least v3.1.9 or it will not work under 64 bit platforms! You need to replace only the smsd binary
  • FreeRadius (bundled with Ubuntu)
  • FreeRadius-mysql (bundled with Ubuntu)
  • FreeRadius-dialupadmin (bundled with Ubuntu)
  • Poptop (bundled with Ubuntu)
  • pppd (bundled with Ubuntu)
  • iptables (bundled with Ubuntu)
  • shell SQL http://sourceforge.net/projects/shellsql/
  • MySQL (bundled with Ubuntu)
  • PHP (bundled with Ubuntu)
  • Apache (bundled with Ubuntu)
  • Perl (bundled with Ubuntu)
  • Some custom bash scripts (see below)

The following softwares have not been documented yet for integration:

  • Splunk http://www.splunk.com (optional, used for centralized logging)
  • Nagios + Centreon http://www.nagios.org + http://www.centreon.com (optional, Nagios used for monitoring + Centreon used for enhanced Nagios web interface)
  • Asterisk http://www.asterisk.org (optional, used to increase high availability: provides an alternative way to authenticate in case the GSM network or SMS-C has problems of any kind; in this case you'll need a public VoIP account)

[edit] Setup

[edit] Basic steps

  • Assemble the server
  • Connect it to the GSM modem along with the SIM
  • Plug one NIC to the internet gateway and the other to the secured LAN.
  • Install the base Ubuntu Server
  • Install the following Ubuntu 9.10 packages Ubuntu 10.04 users: package names could be slightly changed from Ubuntu 9.10, so fix them accordingly
apt-get install mysql-server-5.1 freeradius freeradius-common libfreeradius2 libltdl7 libperl5.10 freeradius-utils
apt-get install freeradius-mysql libradiusclient-ng2 apache2-utils apache2.2-bin apache2.2-common libapr1 libaprutil1 libaprutil1-dbd-sqlite3
apt-get install libaprutil1-ldap apache2-mpm-prefork freeradius-dialupadmin libapache2-mod-php5 php5 php5-common php5-mysql libclass-singleton-perl
apt-get install libdate-manip-perl libdatetime-format-datemanip-perl libdatetime-locale-perl libdatetime-perl libdatetime-timezone-perl 
apt-get install liblist-moreutils-perl libparams-validate-perl libmysqlclient-dev smstools

[edit] Network

Edit the /etc/network/interfaces file and configure the NICs in the following way:

# Open internet (change eth name and IPs as you need)
auto eth0
iface eth0 inet static 
        pre-up /usr/sbin/ethtool -s eth0 speed 100 duplex full autoneg off
        pre-up iptables-restore < /etc/iptables.rules
        address X.X.X.X
        netmask 255.255.255.X
        gateway X.X.X.X
# Protected LAN (change eth name and IPs as you need)
# X and Y values should be set to the range of IPs you have chosen as pool for your VPN clients
auto eth1
iface eth1 inet static
        pre-up /usr/sbin/ethtool -s eth1 speed 100 duplex full autoneg off
        post-up ip rule add from 192.168.0.X/Y table vpn
        post-up ip r add 192.168.0.0/24 dev eth1 scope link table vpn
        post-up ip r add default via 192.168.0.1 dev eth1 table vpn
        address 192.168.0.2
        netmask 255.255.255.0
        network 192.168.0.0
        broadcast 192.168.0.255

Enable IP routing by editing /etc/sysctl.conf:

net.ipv4.ip_forward=1

Create a table by editing /etc/iproute2/rt_tables adding:

200     vpn

Create the file /etc/iptables.rules and replace X.X.X.X with the IP of your open internet interface:

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A POSTROUTING -o eth0 -j SNAT --to-source X.X.X.X 
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 
-A INPUT -i lo -j ACCEPT 
-A INPUT -i eth1 -j ACCEPT 
-A INPUT -i eth0 -j DROP 
COMMIT

[edit] shell SQL

Install shell SQL (get the latest one if in the meantime the author released a version newer than v0.7.7)

wget http://downloads.sourceforge.net/project/shellsql/shellsql/0.7.7/shellsql-0.7.7.tgz?use_mirror=sunet
tar xvzf shellsql-0.7.7.tgz
cd shellsql-0.7.7/src
make shmysql
make tools
strip sh*
sudo cp shmysql shsql shsqlend shsqlesc shsqlinp shsqlline shsqlstart /usr/local/bin/

*** IF YOU ARE EXPERIENCING BUILD ERRORS ***

If during make tools commands you get these errors:

shsqlinp.c:49: error: conflicting types for ‘getline’
/usr/include/stdio.h:651: note: previous declaration of ‘getline’ was here
shsqlinp.c:374: error: conflicting types for ‘getline’
/usr/include/stdio.h:651: note: previous declaration of ‘getline’ was here
make: *** [shsqlinp.o] Error 1

You can fix it by editing the file src/shsqlinp.c and rename the function readline() into read_line() across this file.

[edit] smsd

Create /etc/smsd.conf file with the following configuration. Remember to replace PIN and modem port!

devices = GSM1
logfile = /var/log/smsd.log
loglevel = 7
smart_logging = yes

eventhandler = /usr/local/bin/callhandlerwrapper

hangup_incoming_call = yes

failed = /var/spool/sms/failed
sent = /var/spool/sms/sent
report = /var/spool/sms/report

[GSM1]
# Change the USB port to something different from /dev/ttyUSB0 or replace the port type if you have a serial GSM modem
device = /dev/ttyUSB0
incoming = yes
# Replace asterisks with your SIM PIN
pin = ****
phonecalls = yes
report = yes

[edit] Custom bash scripts

Create the following files replacing everything you need to suite your needs
/usr/local/bin/callhandlerwrapper

#!/bin/bash
nohup /usr/local/bin/callhandler $* &

/usr/local/bin/callhandler

#!/bin/bash
set -x
SMSD_EVENT=$1
SMSD_FILE=$2

#-----------------------
CALL_WINDOW=120
SMS_WINDOW=180
SMS_CONFIRM=1
SIMULTANEOUS_USE=2
#-----------------------

# Handle CTRL+C
trap 'echo; CleanUp; exit $USER_INTERRUPT' TERM INT

# Handle script exit
trap 'CleanUp; exit 0' EXIT

function CleanUp()
{
        # Close connection to the SIM DB
        if [[ "$HANDLE" != "" ]]; then
                /usr/local/bin/shsqlend $HANDLE
        fi

        # Disable radius account
        if [[ "$HANDLE_RADIUS" != "" && "$RADIUS_USERNAME" != "" ]]; then
                DisableRadiusAccount $RADIUS_USERNAME
        fi

        # Close connection to the RADIUS DB
        if [[ "$HANDLE_RADIUS" != "" ]]; then
                /usr/local/bin/shsqlend $HANDLE_RADIUS
        fi

        # Close firewall ports
        if [[ "$REMOTE_IP" != "" ]]; then
                CloseFirewall $REMOTE_IP
        fi
        exit 0;
}

function SendSMS()
{
local   to=$1
local   msg=$2
local   file=`mktemp /tmp/send_XXXXXX`
        echo "To: +$to" > $file
        echo "" >> $file
        echo "$msg" >> $file
local   file2=`mktemp /var/spool/sms/outgoing/send_XXXXXX`
        mv $file $file2
}

function Log()
{
local   from="$1"
local   type="$2"
local   received="$3"
local   msg=$(echo "$4" | tr -d '\n')

local   parsed_msg=`/usr/local/bin/shsqlesc "$msg"`

        /usr/local/bin/shsql $HANDLE "insert into smsd_log(type,message,date,cli) values ('$type', $parsed_msg, '$received', '$from')"
}

function Authenticate()
{
        COUNT=`/usr/local/bin/shsql $HANDLE "select count(*) from cli_ip where cli=$FROM"`
        eval set $COUNT
        return $1
}

function AuthenticateSMS()
{
        COUNT=`/usr/local/bin/shsql $HANDLE "select count(*) from cli_ip where cli=$FROM and allow_sms_change = 1"`
        eval set $COUNT
        return $1
}

function CheckFirewall()
{
        REMOTE_IP=$1
        LINES=`/sbin/iptables -L -n | grep ACCEPT | grep $REMOTE_IP | wc -l`
        if [[ $LINES -eq 2 ]]; then
                return 0
        fi
        return -1
}

function OpenFirewall()
{
        REMOTE_IP=$1
        /sbin/iptables -I INPUT -s $REMOTE_IP -p tcp -i eth0 -m tcp --dport 1723 -j ACCEPT
        /sbin/iptables -I INPUT -s $REMOTE_IP -p gre -j ACCEPT
}

function CloseFirewall()
{
        REMOTE_IP=$1
        /sbin/iptables -D INPUT -s $REMOTE_IP -p tcp -i eth0 -m tcp --dport 1723 -j ACCEPT
        /sbin/iptables -D INPUT -s $REMOTE_IP -p gre -j ACCEPT
}

function EnableRadiusAccount()
{
        RADIUS_USERNAME=$1
        COUNT=`/usr/local/bin/shsql $HANDLE_RADIUS "update radcheck set value = $SIMULTANEOUS_USE where attribute = 'Simultaneous-Use' and username = '$RADIUS_USERNAME'"`
        eval set $COUNT
        return $1
}

function DisableRadiusAccount()
{
        RADIUS_USERNAME=$1
        COUNT=`/usr/local/bin/shsql $HANDLE_RADIUS "update radcheck set value = 0 where attribute = 'Simultaneous-Use' and username = '$RADIUS_USERNAME'"`
        eval set $COUNT
        return $1
}

# Test an IP address for validity:
# Usage:
#      valid_ip IP_ADDRESS
#      if [[ $? -eq 0 ]]; then echo good; else echo bad; fi
#   OR
#      if valid_ip IP_ADDRESS; then echo good; else echo bad; fi
#
function valid_ip()
{
    local  ip=$1
    local  stat=1

    if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        OIFS=$IFS
        IFS='.'
        ip=($ip)
        IFS=$OIFS
        [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
            && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
        stat=$?
    fi
    return $stat
}

# If I've been invoked for a short message or for a missed call, 
# I have to open the DB connections and do some preliminary stuff
if [[ "$SMSD_EVENT" -eq "CALL" && "$SMSD_EVENT" -eq "RECEIVED" ]]; then

        SHSQL=mysql
        export SHSQL

        HANDLE=`/usr/local/bin/shmysql dbname=ppp user=root`
        HANDLE_RADIUS=`/usr/local/bin/shmysql dbname=radius user=root`

        FROM=`/usr/bin/formail -zx From: <$SMSD_FILE`
        RECEIVED=`/usr/bin/formail -zx Received: <$SMSD_FILE`
fi

# It is a missed call
#####################################
if [[ "$SMSD_EVENT" == "CALL" ]]; then

        Authenticate
        if [[ $? -eq 0 ]]; then
                Log $FROM "Call Access denied, SIM unknown" "$RECEIVED" "No message"
                CleanUp
        fi

        ROW=`/usr/local/bin/shsql $HANDLE "select id, cli, remote_ip, user, radius_username from cli_ip where cli='$FROM'"`
        eval set $ROW

        REMOTE_IP=$3
        RADIUS_USERNAME=$5
        EnableRadiusAccount $RADIUS_USERNAME
        if [[ $? -eq 0 ]]; then
                Log $FROM "Cannot enable radius username $RADIUS_USERNAME" "$RECEIVED" "No message"
                CleanUp
        fi

        CheckFirewall $REMOTE_IP
        if [[ $? -eq 0 ]]; then
                Log "$FROM" "Call Access already granted" "$RECEIVED" $REMOTE_IP
        else
                OpenFirewall $REMOTE_IP
                Log "$FROM" "Call Access granted" "$RECEIVED" $REMOTE_IP
                /bin/sleep $CALL_WINDOW
                CloseFirewall $REMOTE_IP
        fi

        DisableRadiusAccount $RADIUS_USERNAME
        if [[ $? -eq 0 ]]; then
                Log $FROM "Cannot disable radius username $RADIUS_USERNAME" "$RECEIVED" "No message"
                CleanUp
        fi
fi

# It is a short message
#####################################
if [[ "$SMSD_EVENT" == "RECEIVED" ]]; then
        MSG=`/usr/bin/formail -I "" <$SMSD_FILE`

        AuthenticateSMS
        if [ $? -eq 0 ]; then
                Log $FROM "SMS Access denied, SIM unauthorized or unknown" "$RECEIVED" "$MSG"
                CleanUp
        fi

        valid_ip $MSG

        if [[ $? -eq 0 ]]; then 

                if [[ $MSG -eq "0.0.0.0" ]]; then
                        Log $FROM "IP address 0.0.0.0 not allowed" "$RECEIVED" "$MSG"
                        CleanUp
                fi

                if [[ $MSG -eq "255.255.255.255" ]]; then
                        Log $FROM "IP address 255.255.255.255 not allowed" "$RECEIVED" "$MSG"
                        CleanUp
                fi

                ROW=`/usr/local/bin/shsql $HANDLE "select id, cli, remote_ip, user, radius_username from cli_ip where cli='$FROM'"`
                eval set $ROW

                RADIUS_USERNAME=$5
                EnableRadiusAccount $RADIUS_USERNAME
                if [[ $? -eq 0 ]]; then
                        Log $FROM "Cannot enable radius username $RADIUS_USERNAME" "$RECEIVED" "$MSG"
                        CleanUp
                fi

                CheckFirewall $REMOTE_IP
                if [[ $? -eq 0 ]]; then
                        Log "$FROM" "Call Access already granted" "$RECEIVED" $REMOTE_IP
                else
                        OpenFirewall $MSG
                        Log "$FROM" "SMS Access granted" "$RECEIVED" "$MSG"
        
                        if [[ "$SMS_CONFIRM" == 1 ]]; then
                                SendSMS "$FROM" "VPN access has been granted for 3 minutes for IP $MSG"
                        fi

                        /bin/sleep $SMS_WINDOW
                        CloseFirewall $MSG
                fi

                DisableRadiusAccount $RADIUS_USERNAME
                if [[ $? -eq 0 ]]; then
                        Log $FROM "Cannot disable radius username $RADIUS_USERNAME" "$RECEIVED" "$MSG"
                        CleanUp
                fi
        else 
                Log $FROM "SMS bad IP address" "$RECEIVED" $MSG
        fi
fi

[edit] MySQL

Create databases and populate the schema, example:

mysqladmin create ppp
mysql <create_ppp_schema.sql
mysqladmin create radius
mysql <create_radius_schema.sql

create_ppp_schema.sql

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `cli_ip`
--

DROP TABLE IF EXISTS `cli_ip`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cli_ip` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `cli` varchar(16) NOT NULL,
  `remote_ip` varchar(16) NOT NULL,
  `user` varchar(32) DEFAULT NULL,
  `allow_sms_change` bit(1) NOT NULL DEFAULT b'0',
  `radius_username` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `cli` (`cli`),
  KEY `username` (`user`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `smsd_log`
--

DROP TABLE IF EXISTS `smsd_log`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `smsd_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(64) DEFAULT NULL,
  `message` varchar(160) DEFAULT NULL,
  `date` datetime NOT NULL,
  `cli` varchar(16) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

create_radius_schema.sql

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `badusers`
--

DROP TABLE IF EXISTS `badusers`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `badusers` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(30) DEFAULT NULL,
  `IncidentDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `Reason` varchar(200) DEFAULT NULL,
  `Admin` varchar(30) DEFAULT '-',
  PRIMARY KEY (`id`),
  KEY `UserName` (`UserName`),
  KEY `IncidentDate` (`IncidentDate`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `mtotacct`
--

DROP TABLE IF EXISTS `mtotacct`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mtotacct` (
  `MTotAcctId` bigint(21) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(64) NOT NULL DEFAULT '',
  `AcctDate` date NOT NULL DEFAULT '0000-00-00',
  `ConnNum` bigint(12) DEFAULT NULL,
  `ConnTotDuration` bigint(12) DEFAULT NULL,
  `ConnMaxDuration` bigint(12) DEFAULT NULL,
  `ConnMinDuration` bigint(12) DEFAULT NULL,
  `InputOctets` bigint(12) DEFAULT NULL,
  `OutputOctets` bigint(12) DEFAULT NULL,
  `NASIPAddress` varchar(15) DEFAULT NULL,
  PRIMARY KEY (`MTotAcctId`),
  KEY `UserName` (`UserName`),
  KEY `AcctDate` (`AcctDate`),
  KEY `UserOnDate` (`UserName`,`AcctDate`),
  KEY `NASIPAddress` (`NASIPAddress`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `nas`
--

DROP TABLE IF EXISTS `nas`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `nas` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `nasname` varchar(128) NOT NULL,
  `shortname` varchar(32) DEFAULT NULL,
  `type` varchar(30) DEFAULT 'other',
  `ports` int(5) DEFAULT NULL,
  `secret` varchar(60) NOT NULL DEFAULT 'secret',
  `community` varchar(50) DEFAULT NULL,
  `description` varchar(200) DEFAULT 'RADIUS Client',
  PRIMARY KEY (`id`),
  KEY `nasname` (`nasname`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radacct`
--

DROP TABLE IF EXISTS `radacct`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radacct` (
  `radacctid` bigint(21) NOT NULL AUTO_INCREMENT,
  `acctsessionid` varchar(32) NOT NULL DEFAULT '',
  `acctuniqueid` varchar(32) NOT NULL DEFAULT '',
  `username` varchar(64) NOT NULL DEFAULT '',
  `groupname` varchar(64) NOT NULL DEFAULT '',
  `realm` varchar(64) DEFAULT '',
  `nasipaddress` varchar(15) NOT NULL DEFAULT '',
  `nasportid` varchar(15) DEFAULT NULL,
  `nasporttype` varchar(32) DEFAULT NULL,
  `acctstarttime` datetime DEFAULT NULL,
  `acctstoptime` datetime DEFAULT NULL,
  `acctsessiontime` int(12) DEFAULT NULL,
  `acctauthentic` varchar(32) DEFAULT NULL,
  `connectinfo_start` varchar(50) DEFAULT NULL,
  `connectinfo_stop` varchar(50) DEFAULT NULL,
  `acctinputoctets` bigint(20) DEFAULT NULL,
  `acctoutputoctets` bigint(20) DEFAULT NULL,
  `calledstationid` varchar(50) NOT NULL DEFAULT '',
  `callingstationid` varchar(50) NOT NULL DEFAULT '',
  `acctterminatecause` varchar(32) NOT NULL DEFAULT '',
  `servicetype` varchar(32) DEFAULT NULL,
  `framedprotocol` varchar(32) DEFAULT NULL,
  `framedipaddress` varchar(15) NOT NULL DEFAULT '',
  `acctstartdelay` int(12) DEFAULT NULL,
  `acctstopdelay` int(12) DEFAULT NULL,
  `xascendsessionsvrkey` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`radacctid`),
  KEY `username` (`username`),
  KEY `framedipaddress` (`framedipaddress`),
  KEY `acctsessionid` (`acctsessionid`),
  KEY `acctsessiontime` (`acctsessiontime`),
  KEY `acctuniqueid` (`acctuniqueid`),
  KEY `acctstarttime` (`acctstarttime`),
  KEY `acctstoptime` (`acctstoptime`),
  KEY `nasipaddress` (`nasipaddress`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radcheck`
--

DROP TABLE IF EXISTS `radcheck`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radcheck` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL DEFAULT '',
  `attribute` varchar(32) NOT NULL DEFAULT '',
  `op` char(2) NOT NULL DEFAULT '==',
  `value` varchar(253) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `username` (`username`(32))
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radgroupcheck`
--

DROP TABLE IF EXISTS `radgroupcheck`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radgroupcheck` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `groupname` varchar(64) NOT NULL DEFAULT '',
  `attribute` varchar(32) NOT NULL DEFAULT '',
  `op` char(2) NOT NULL DEFAULT '==',
  `value` varchar(253) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `groupname` (`groupname`(32))
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radgroupreply`
--

DROP TABLE IF EXISTS `radgroupreply`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radgroupreply` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `groupname` varchar(64) NOT NULL DEFAULT '',
  `attribute` varchar(32) NOT NULL DEFAULT '',
  `op` char(2) NOT NULL DEFAULT '=',
  `value` varchar(253) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `groupname` (`groupname`(32))
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radpostauth`
--

DROP TABLE IF EXISTS `radpostauth`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radpostauth` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL DEFAULT '',
  `pass` varchar(64) NOT NULL DEFAULT '',
  `reply` varchar(32) NOT NULL DEFAULT '',
  `authdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radreply`
--

DROP TABLE IF EXISTS `radreply`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radreply` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL DEFAULT '',
  `attribute` varchar(32) NOT NULL DEFAULT '',
  `op` char(2) NOT NULL DEFAULT '=',
  `value` varchar(253) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `username` (`username`(32))
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `radusergroup`
--

DROP TABLE IF EXISTS `radusergroup`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `radusergroup` (
  `username` varchar(64) NOT NULL DEFAULT '',
  `groupname` varchar(64) NOT NULL DEFAULT '',
  `priority` int(11) NOT NULL DEFAULT '1',
  KEY `username` (`username`(32))
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `totacct`
--

DROP TABLE IF EXISTS `totacct`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `totacct` (
  `TotAcctId` bigint(21) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(64) NOT NULL DEFAULT '',
  `AcctDate` date NOT NULL DEFAULT '0000-00-00',
  `ConnNum` bigint(12) DEFAULT NULL,
  `ConnTotDuration` bigint(12) DEFAULT NULL,
  `ConnMaxDuration` bigint(12) DEFAULT NULL,
  `ConnMinDuration` bigint(12) DEFAULT NULL,
  `InputOctets` bigint(12) DEFAULT NULL,
  `OutputOctets` bigint(12) DEFAULT NULL,
  `NASIPAddress` varchar(15) DEFAULT NULL,
  PRIMARY KEY (`TotAcctId`),
  KEY `UserName` (`UserName`),
  KEY `AcctDate` (`AcctDate`),
  KEY `UserOnDate` (`UserName`,`AcctDate`),
  KEY `NASIPAddress` (`NASIPAddress`),
  KEY `NASIPAddressOnDate` (`AcctDate`,`NASIPAddress`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `userinfo`
--

DROP TABLE IF EXISTS `userinfo`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `userinfo` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(30) DEFAULT NULL,
  `Name` varchar(200) DEFAULT NULL,
  `Mail` varchar(200) DEFAULT NULL,
  `Department` varchar(200) DEFAULT NULL,
  `WorkPhone` varchar(200) DEFAULT NULL,
  `HomePhone` varchar(200) DEFAULT NULL,
  `Mobile` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `UserName` (`UserName`),
  KEY `Department` (`Department`)
) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

[edit] Poptop

Add the following lines to /etc/ppp/pptpd-options

# Set DNS that will be assigned to the clients to whatever you want, I've just used Google free ones as example
ms-dns 8.8.8.8
ms-dns 8.8.4.4

# Useful changes to the network maximum transfer unit to avoid problems with applications such as Microsoft Exchange
mtu 1464
mru 1464

Add the following lines to /etc/pptpd.conf

# The IP configured on the physical NIC of this server that is connected to the secured LAN 
localip 192.168.0.2

# The range of IPs assigned to the clients (should be a subset of the IPs used for your secured LAN)
remoteip 192.168.0.193-254

[edit] pppd

Add the following lines to /etc/ppp/options

plugin radius.so
plugin radattr.so

# Add this line too if you want to disconnect users for inactivity
idle 14400

Create the file named mtufix in /etc/ppp/ip-up.d/ with the following content

#!/bin/sh -e
# Called when a new interface comes up
ip l set mtu 1400 $PPP_IFACE

This prevents pptpd to set the mtu to 1396 bytes.

[edit] freeradius

Add the following line to the /etc/init.d/freeradius file under the start case:

/usr/share/freeradius-dialupadmin/bin/log_badlogins /var/log/freeradius/radius.log /etc/freeradius-dialupadmin/admin
.conf &

Add the following line to the /etc/init.d/freeradius file under the stop case:

ps ax | grep /usr/share/freeradius-dialupadmin/bin/log_badlogins | grep -v grep | cut -f 2 -d ' ' | xargs kill >/dev
/null

For Ubuntu 10.04 users, you need to fix the perl library include of the script /usr/share/freeradius-dialupadmin/bin/log_badlogins and change line 33 from

use File::Temp;

to

use File::Temp qw/ tempfile tempdir /;

Edit the file /etc/freeradius/radiusd.conf and make the following changes:

# Limit connections to the localhost only (note that there are TWO listen groups and this change should be made on both
listen {
   ipaddr = 127.0.0.1
}

log {
   auth = yes
}

modules {
   $INCLUDE sql/mysql/counter.conf
}

Create the file /etc/radiusclient/dictionary.microsoft with the following contents:

#
#       Microsoft's VSA's, from RFC 2548
#
#       $Id: dictionary.microsoft,v 1.1 2002/03/06 13:23:09 dfs Exp $
#

VENDOR          Microsoft       311     Microsoft

ATTRIBUTE       MS-CHAP-Response        1       string  Microsoft
ATTRIBUTE       MS-CHAP-Error           2       string  Microsoft
ATTRIBUTE       MS-CHAP-CPW-1           3       string  Microsoft
ATTRIBUTE       MS-CHAP-CPW-2           4       string  Microsoft
ATTRIBUTE       MS-CHAP-LM-Enc-PW       5       string  Microsoft
ATTRIBUTE       MS-CHAP-NT-Enc-PW       6       string  Microsoft
ATTRIBUTE       MS-MPPE-Encryption-Policy 7     string  Microsoft
# This is referred to as both singular and plural in the RFC.
# Plural seems to make more sense.
ATTRIBUTE       MS-MPPE-Encryption-Type 8       string  Microsoft
ATTRIBUTE       MS-MPPE-Encryption-Types  8     string  Microsoft
ATTRIBUTE       MS-RAS-Vendor           9       integer Microsoft
ATTRIBUTE       MS-CHAP-Domain          10      string  Microsoft
ATTRIBUTE       MS-CHAP-Challenge       11      string  Microsoft
ATTRIBUTE       MS-CHAP-MPPE-Keys       12      string  Microsoft
ATTRIBUTE       MS-BAP-Usage            13      integer Microsoft
ATTRIBUTE       MS-Link-Utilization-Threshold 14 integer        Microsoft
ATTRIBUTE       MS-Link-Drop-Time-Limit 15      integer Microsoft
ATTRIBUTE       MS-MPPE-Send-Key        16      string  Microsoft
ATTRIBUTE       MS-MPPE-Recv-Key        17      string  Microsoft
ATTRIBUTE       MS-RAS-Version          18      string  Microsoft
ATTRIBUTE       MS-Old-ARAP-Password    19      string  Microsoft
ATTRIBUTE       MS-New-ARAP-Password    20      string  Microsoft
ATTRIBUTE       MS-ARAP-PW-Change-Reason 21     integer Microsoft

ATTRIBUTE       MS-Filter               22      string  Microsoft
ATTRIBUTE       MS-Acct-Auth-Type       23      integer Microsoft
ATTRIBUTE       MS-Acct-EAP-Type        24      integer Microsoft

ATTRIBUTE       MS-CHAP2-Response       25      string  Microsoft
ATTRIBUTE       MS-CHAP2-Success        26      string  Microsoft
ATTRIBUTE       MS-CHAP2-CPW            27      string  Microsoft

ATTRIBUTE       MS-Primary-DNS-Server   28      ipaddr  Microsoft
ATTRIBUTE       MS-Secondary-DNS-Server 29      ipaddr  Microsoft
ATTRIBUTE       MS-Primary-NBNS-Server  30      ipaddr  Microsoft
ATTRIBUTE       MS-Secondary-NBNS-Server 31     ipaddr  Microsoft

#ATTRIBUTE      MS-ARAP-Challenge       33      string  Microsoft


#
#       Integer Translations
#

#       MS-BAP-Usage Values

VALUE           MS-BAP-Usage            Not-Allowed     0
VALUE           MS-BAP-Usage            Allowed         1
VALUE           MS-BAP-Usage            Required        2

#       MS-ARAP-Password-Change-Reason Values

VALUE   MS-ARAP-PW-Change-Reason        Just-Change-Password            1
VALUE   MS-ARAP-PW-Change-Reason        Expired-Password                2
VALUE   MS-ARAP-PW-Change-Reason        Admin-Requires-Password-Change  3
VALUE   MS-ARAP-PW-Change-Reason        Password-Too-Short              4

#       MS-Acct-Auth-Type Values

VALUE           MS-Acct-Auth-Type       PAP             1
VALUE           MS-Acct-Auth-Type       CHAP            2
VALUE           MS-Acct-Auth-Type       MS-CHAP-1       3
VALUE           MS-Acct-Auth-Type       MS-CHAP-2       4
VALUE           MS-Acct-Auth-Type       EAP             5

#       MS-Acct-EAP-Type Values

VALUE           MS-Acct-EAP-Type        MD5             4
VALUE           MS-Acct-EAP-Type        OTP             5
VALUE           MS-Acct-EAP-Type        Generic-Token-Card      6
VALUE           MS-Acct-EAP-Type        TLS             13

[edit] freeradius dialup admin

Edit file /etc/freeradius-dialupadmin/default.vals and add this line:

Simultaneous-Use: 0

[edit] Apache

Create the file /etc/apache2/sites-available/yourserver.com with the following contents and replace yourserver.com and youremail with proper values:

<IfModule mod_ssl.c>
<VirtualHost yourserver.com:443>
        ServerAdmin youremail@yourserver.com
        ServerName yourserver.com

        DocumentRoot /usr/share/freeradius-dialupadmin/htdocs
        <Directory />
                Options FollowSymLinks
                AllowOverride None
                <IfModule mod_php4.c>
                        php_flag register_globals off
                </IfModule>
                <IfModule mod_php5.c>
                        php_flag register_globals off
                </IfModule>
                AuthName "Restricted Area" 
                AuthType Basic 
                AuthUserFile /usr/share/freeradius-dialupadmin/.htpasswd 
                require valid-user 
        </Directory>

        ErrorLog /var/log/apache2/yourserver.com-error.log

        LogLevel warn

        CustomLog /var/log/apache2/yourserver.com-ssl_access.log combined

        SSLEngine on

		# Your certificate
        SSLCertificateFile    /etc/ssl/certs/your_certificate.crt
        SSLCertificateKeyFile /etc/ssl/private/your_key.key

        SSLCACertificatePath /etc/ssl/certs/

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
                SSLOptions +StdEnvVars
        </Directory>

        BrowserMatch ".*MSIE.*" \
                nokeepalive ssl-unclean-shutdown \
                downgrade-1.0 force-response-1.0

</VirtualHost>
</IfModule>

Edit file /etc/apache2/ports.conf and make sure you have only port 443 listening with this directive:

<IfModule mod_ssl.c>
    Listen 443
</IfModule>

Enable the website yourserver.com with

sudo a2dissite default
sudo a2ensite yourserver.com

Create the freeradius-dialup admin interface account:

htpasswd -c /usr/share/freeradius-dialupadmin/.htpasswd administrator

[edit] Crontab

Create the following root cron entries (crontab -u root -e) to clean up things, aggregate, etc.

1 0 * * * /usr/share/freeradius-dialupadmin/bin/tot_stats >/dev/null 2>&1
5 0 * * * /usr/share/freeradius-dialupadmin/bin/monthly_tot_stats >/dev/null 2>&1
10 0 1 * * /usr/share/freeradius-dialupadmin/bin/truncate_radacct >/dev/null 2>&1
15 0 1 * * /usr/share/freeradius-dialupadmin/bin/clean_radacct >/dev/null 2>&1
@daily /etc/init.d/smsd restart

[edit] Optionals

[edit] MSCHAP & clear-text passwords

If you wish to authenticate your users by only using the most secure Microsoft protocol, you have two options for storing the password in the radius database:

1) Cleartext (less secure with no extra coding needed)

You just manually create the user in the radcheck table or use the web interface:

INSERT INTO radcheck(username,attribute,op,value) VALUES('yourname','Password',':=','yourpassword');

2) Hashed (very secure but requires some coding)

You have to calculate the NT-Hash of your password then put the following radius attributes into the radcheck table or use the web interface:

INSERT INTO radcheck(username,attribute,op,value) VALUES('yourname','NT-Password',':=','nthashofyourpassword');

You can use NT hash calculators like:

http://www.oxid.it/ca_um/topics/hash_calculator.htm (Windows)

http://www.insidepro.com/hashes.php (Web)

Or if you are building a custom application to populate the user database just find out a software library with a function that calculates the NT-Hash.


In both cases, freeradius will use the most secure form of authentication for the client according to your database records. Of course you can change this behaviour by editing the freeradius configuration files.

[edit] IP reservations

If you wish to give a fixed IP address (like DHCP reservations) to your clients, you can add the following record to the radreply table.

Example

You want to give the IP 192.168.0.123 to the login foo.

INSERT INTO radreply(username,attribute,op,value) VALUES('foo','Framed-IP-Address',':=','192.168.0.123');

[edit] Login expiration

If you wish to set an expiration date to your radius accounts, you can add the following record to the radcheck table.

Example

You want set the expiration date for the login foo to the 1st day of August 2010 at 12:00.

INSERT INTO radcheck(username,attribute,op,value) VALUES('foo','Expiration',':=','1 Aug 2010 12:00');
Personal tools