Cost Effective Two Factor Authentication VPN
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');