Project

General

Profile

Download (16.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
    $Id$
4
    part of m0n0wall (http://m0n0.ch/wall)
5

    
6
    Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
7
    All rights reserved.
8

    
9
    Redistribution and use in source and binary forms, with or without
10
    modification, are permitted provided that the following conditions are met:
11

    
12
    1. Redistributions of source code must retain the above copyright notice,
13
       this list of conditions and the following disclaimer.
14

    
15
    2. Redistributions in binary form must reproduce the above copyright
16
       notice, this list of conditions and the following disclaimer in the
17
       documentation and/or other materials provided with the distribution.
18

    
19
    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
    AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
    POSSIBILITY OF SUCH DAMAGE.
29
*/
30

    
31
require_once("functions.inc");
32

    
33
header("Expires: 0");
34
header("Cache-Control: no-store, no-cache, must-revalidate");
35
header("Cache-Control: post-check=0, pre-check=0", false);
36
header("Pragma: no-cache");
37

    
38
$orig_host = $_ENV['HTTP_HOST'];
39
$orig_request = $_GET['redirurl'];
40
$clientip = $_SERVER['REMOTE_ADDR'];
41

    
42
if (!$clientip) {
43
    /* not good - bail out */
44
    echo "An error occured.  Please check the system logs for more information.";
45
    log_error("Captive portal could not deterimine clients ip address.");
46
    exit;
47
}
48

    
49
if (isset($config['captiveportal']['httpslogin']))
50
    $ourhostname = $config['captiveportal']['httpsname'] . ":8001";
51
else
52
    $ourhostname = $config['interfaces'][$config['captiveportal']['interface']]['ipaddr'] . ":8000";
53

    
54
if ($orig_host != $ourhostname) {
55
    /* the client thinks it's connected to the desired web server, but instead
56
       it's connected to us. Issue a redirect... */
57

    
58
    if (isset($config['captiveportal']['httpslogin']))
59
        header("Location: https://{$ourhostname}/index.php?redirurl=" . urlencode("http://{$orig_host}{$orig_request}"));
60
    else
61
        header("Location: http://{$ourhostname}/index.php?redirurl=" . urlencode("http://{$orig_host}{$orig_request}"));
62

    
63
    exit;
64
}
65

    
66
if (preg_match("/redirurl=(.*)/", $orig_request, $matches))
67
    $redirurl = urldecode($matches[1]);
68
if ($_POST['redirurl'])
69
    $redirurl = $_POST['redirurl'];
70

    
71
$macfilter = !isset($config['captiveportal']['nomacfilter']);
72

    
73
/* find MAC address for client */
74
$clientmac = arp_get_mac_by_ip($clientip);
75
if (!$clientmac && $macfilter) {
76
    /* unable to find MAC address - shouldn't happen! - bail out */
77
    captiveportal_logportalauth("unauthenticated","noclientmac",$clientip,"ERROR");
78
    echo "An error occured.  Please check the system logs for more information.";
79
    log_error("Captive portal could not deterimine clients MAC address.  Disable MAC address filtering in captive portal if you do not needs this functionality.");
80
    exit;
81
}
82

    
83
/* find out if we need RADIUS + RADIUSMAC or not */
84
if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
85
    $radius_enable = TRUE;
86
    if ($radius_enable && isset($config['captiveportal']['radmac_enable']))
87
        $radmac_enable = TRUE;
88
}
89

    
90
if ($_POST['logout_id']) {
91
    disconnect_client($_POST['logout_id']);
92
    echo <<<EOD
93
<HTML>
94
<HEAD><TITLE>Disconnecting...</TITLE></HEAD>
95
<BODY BGCOLOR="#435370">
96
<SPAN STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
97
<B>You have been disconnected.</B>
98
</SPAN>
99
<SCRIPT LANGUAGE="JavaScript">
100
<!--
101
setTimeout('window.close();',5000) ;
102
-->
103
</SCRIPT>
104
</BODY>
105
</HTML>
106

    
107
EOD;
108
/* The $macfilter can be removed safely since we first check if the $clientmac is present, if not we fail */
109
} else if ($clientmac && portal_mac_fixed($clientmac)) {
110
    /* punch hole in ipfw for pass thru mac addresses */
111
    portal_allow($clientip, $clientmac, "unauthenticated");
112
    exit;
113

    
114
} else if ($clientmac && $radmac_enable && portal_mac_radius($clientmac,$clientip)) {
115
    /* radius functions handle everything so we exit here since we're done */
116
    exit;
117

    
118
} else if ($_POST['accept'] && $radius_enable) {
119

    
120
    if ($_POST['auth_user'] && $_POST['auth_pass']) {
121
        $auth_list = radius($_POST['auth_user'],$_POST['auth_pass'],$clientip,$clientmac,"USER LOGIN");
122

    
123
        if ($auth_list['auth_val'] == 1) {
124
            captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"ERROR",$auth_list['error']);
125
            portal_reply_page($redirurl, "error", $auth_list['error']);
126
        }
127
        else if ($auth_list['auth_val'] == 3) {
128
            captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"FAILURE",$auth_list['reply_message']);
129
            portal_reply_page($redirurl, "error", $auth_list['reply_message']);
130
        }
131
    } else {
132
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"ERROR");
133
        portal_reply_page($redirurl, "error");
134
    }
135

    
136
} else if ($_POST['accept'] && $config['captiveportal']['auth_method'] == "local") {
137

    
138
    //check against local usermanager
139
    $userdb = &$config['captiveportal']['user'];
140

    
141
    $loginok = false;
142

    
143
    //erase expired accounts
144
    if (is_array($userdb)) {
145
        $moddb = false;
146
        for ($i = 0; $i < count($userdb); $i++) {
147
            if ($userdb[$i]['expirationdate'] && (strtotime("-1 day") > strtotime($userdb[$i]['expirationdate']))) {
148
                unset($userdb[$i]);
149
                $moddb = true;
150
            }
151
        }
152
        if ($moddb)
153
            write_config();
154

    
155
        $userdb = &$config['captiveportal']['user'];
156

    
157
        for ($i = 0; $i < count($userdb); $i++) {
158
            if (($userdb[$i]['name'] == $_POST['auth_user']) && ($userdb[$i]['password'] == md5($_POST['auth_pass']))) {
159
                $loginok = true;
160
                break;
161
            }
162
        }
163
    }
164

    
165
    if ($loginok){
166
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"LOGIN");
167
        portal_allow($clientip, $clientmac,$_POST['auth_user']);
168
    } else {
169
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"FAILURE");
170
        portal_reply_page($redirurl, "error");
171
    }
172
} else if ($_POST['accept'] && $clientip) {
173
    captiveportal_logportalauth("unauthenticated",$clientmac,$clientip,"ACCEPT");
174
    portal_allow($clientip, $clientmac, "unauthenticated");
175
} else {
176
    /* display captive portal page */
177
    portal_reply_page($redirurl, "login");
178
}
179

    
180
exit;
181

    
182
function portal_reply_page($redirurl, $type = null, $message = null) {
183
    global $g, $config;
184

    
185
    /* Get captive portal layout */
186
    if ($type == "login")
187
        $htmltext = file_get_contents("{$g['varetc_path']}/captiveportal.html");
188
    else
189
        $htmltext = file_get_contents("{$g['varetc_path']}/captiveportal-error.html");
190

    
191
    /* substitute other variables */
192
    if (isset($config['captiveportal']['httpslogin']))
193
        $htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
194
    else
195
        $htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$config['interfaces'][$config['captiveportal']['interface']]['ipaddr']}:8000/", $htmltext);
196

    
197
    $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
198
    $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
199

    
200
    echo $htmltext;
201
}
202

    
203
function portal_mac_fixed($clientmac) {
204
    global $g ;
205

    
206
    /* open captive portal mac db */
207
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
208
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
209
        if (!$fd) {
210
            return FALSE;
211
        }
212
        while (!feof($fd)) {
213
            $mac = trim(fgets($fd)) ;
214
            if(strcasecmp($clientmac, $mac) == 0) {
215
                fclose($fd) ;
216
                return TRUE ;
217
            }
218
        }
219
        fclose($fd) ;
220
    }
221
    return FALSE ;
222
}
223

    
224
function portal_mac_radius($clientmac,$clientip) {
225
    global $config ;
226

    
227
    $radmac_secret = $config['captiveportal']['radmac_secret'];
228

    
229
    /* authentication against the radius server */
230
    $username = mac_format($clientmac);
231
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
232
    if ($auth_list['auth_val'] == 2) {
233
        return TRUE;
234
    }
235
    return FALSE;
236
}
237

    
238
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
239

    
240
    global $redirurl, $g, $config;
241

    
242
    /* See if a ruleno is passed, if not start locking the sessions because this means there isn't one atm */
243
    if ($ruleno == null) {
244
        captiveportal_lock();
245
        $ruleno = captiveportal_get_next_ipfw_ruleno();
246
    }
247

    
248
    /* if the pool is empty, return appropriate message and exit */
249
    if (is_null($ruleno)) {
250
        portal_reply_page($redirurl, "error", "System reached maximum login capacity");
251
        captiveportal_unlock();
252
        exit;
253
    }
254

    
255
    // Ensure we create an array if we are missing attributes
256
    if (!is_array($attributes))
257
        $attributes = array();
258

    
259
    /* read in client database */
260
    $cpdb = captiveportal_read_db();
261

    
262
    $radiusservers = captiveportal_get_radius_servers();
263

    
264
    /* Find an existing session */
265
    for ($i = 0; $i < count($cpdb); $i++) {
266
        /* on the same ip */
267
        if($cpdb[$i][2] == $clientip) {
268
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - REUSING OLD SESSION");
269
            $sessionid = $cpdb[$i][5];
270
            break;
271
        }
272
        elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
273
            /* on the same username */
274
            if ($cpdb[$i][4] == $username) {
275
                /* This user was already logged in so we disconnect the old one */
276
                captiveportal_disconnect($cpdb[$i],$radiusservers,13);
277
                captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
278
                unset($cpdb[$i]);
279
                break;
280
            }
281
        }
282
    }
283

    
284
    if (!isset($sessionid)) {
285

    
286
        /* generate unique session ID */
287
        $tod = gettimeofday();
288
        $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
289

    
290
        /* Add rules for traffic shaping
291
         * We don't need to add extra l3 allow rules since traffic will pass due to the following kernel option
292
         * net.inet.ip.fw.one_pass: 1
293
         */
294
        $peruserbw = isset($config['captiveportal']['peruserbw']);
295

    
296
        $bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
297
        $bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
298

    
299
        if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
300
            $bw_up_pipeno = $ruleno + 40500;
301
            exec("/sbin/ipfw add $ruleno set 2 pipe $bw_up_pipeno ip from $clientip to any in");
302
            exec("/sbin/ipfw pipe $bw_up_pipeno config bw {$bw_up}Kbit/s queue 100");
303
        } else {
304
            exec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from $clientip to any in");
305
        }
306
        if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
307
            $bw_down_pipeno = $ruleno + 45500;
308
            exec("/sbin/ipfw add $ruleno set 2 pipe $bw_down_pipeno ip from any to $clientip out");
309
            exec("/sbin/ipfw pipe $bw_down_pipeno config bw {$bw_down}Kbit/s queue 100");
310
        } else {
311
            exec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to $clientip out");
312
        }
313

    
314
        /* add ipfw rules for layer 2 */
315
        if (!isset($config['captiveportal']['nomacfilter'])) {
316
            $l2ruleno = $ruleno + 10000;
317
            exec("/sbin/ipfw add $l2ruleno set 3 deny all from $clientip to any not MAC any $clientmac layer2 in");
318
            exec("/sbin/ipfw add $l2ruleno set 3 deny all from any to $clientip not MAC $clientmac any layer2 out");
319
        }
320

    
321
        /* encode password in Base64 just in case it contains commas */
322
        $bpassword = base64_encode($password);
323
        $cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
324
                $attributes['session_timeout'],
325
                $attributes['idle_timeout'],
326
                $attributes['session_terminate_time']);
327

    
328
        if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
329
            $acct_val = RADIUS_ACCOUNTING_START($ruleno,
330
                                                            $username,
331
                                                            $sessionid,
332
                                                            $radiusservers[0]['ipaddr'],
333
                                                            $radiusservers[0]['acctport'],
334
                                                            $radiusservers[0]['key'],
335
                                                            $clientip,
336
                                                            $clientmac);
337
            if ($acct_val == 1)
338
                captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
339
        }
340

    
341

    
342
    }
343

    
344
    /* rewrite information to database */
345
    captiveportal_write_db($cpdb);
346

    
347
    /* redirect user to desired destination */
348
    if ($url_redirection)
349
        $my_redirurl = $url_redirection;
350
    else if ($config['captiveportal']['redirurl'])
351
        $my_redirurl = $config['captiveportal']['redirurl'];
352
    else
353
        $my_redirurl = $redirurl;
354

    
355
    if(isset($config['captiveportal']['logoutwin_enable'])) {
356

    
357
        if (isset($config['captiveportal']['httpslogin']))
358
            $logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
359
        else
360
            $logouturl = "http://{$config['interfaces'][$config['captiveportal']['interface']]['ipaddr']}:8000/";
361

    
362
        echo <<<EOD
363
<HTML>
364
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
365
<BODY>
366
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
367
<B>Redirecting to <A HREF="{$my_redirurl}">{$my_redirurl}</A>...</B>
368
</SPAN>
369
<SCRIPT LANGUAGE="JavaScript">
370
<!--
371
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
372
if (LogoutWin) {
373
    LogoutWin.document.write('<HTML>');
374
    LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
375
    LogoutWin.document.write('<BODY BGCOLOR="#435370">');
376
    LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
377
    LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
378
    LogoutWin.document.write('<FORM METHOD="POST" ACTION="{$logouturl}">');
379
    LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="{$sessionid}">');
380
    LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
381
    LogoutWin.document.write('</FORM>');
382
    LogoutWin.document.write('</DIV></BODY>');
383
    LogoutWin.document.write('</HTML>');
384
    LogoutWin.document.close();
385
}
386

    
387
document.location.href="{$my_redirurl}";
388
-->
389
</SCRIPT>
390
</BODY>
391
</HTML>
392

    
393
EOD;
394
    } else {
395
        header("Location: " . $my_redirurl);
396
    }
397

    
398
    captiveportal_unlock();
399
    return $sessionid;
400
}
401

    
402

    
403

    
404
/* remove a single client by session ID
405
   by Dinesh Nair
406
 */
407
function disconnect_client($sessionid, $logoutReason = "LOGOUT", $term_cause = 1) {
408

    
409
    global $g, $config;
410

    
411
    captiveportal_lock();
412
    /* read database */
413
    $cpdb = captiveportal_read_db();
414

    
415
    $radiusservers = captiveportal_get_radius_servers();
416

    
417
    /* find entry */
418
    for ($i = 0; $i < count($cpdb); $i++) {
419
        if ($cpdb[$i][5] == $sessionid) {
420
            captiveportal_disconnect($cpdb[$i],$radiusservers, $term_cause);
421
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],$logoutReason);
422
            unset($cpdb[$i]);
423
            break;
424
        }
425
    }
426

    
427
    /* write database */
428
    captiveportal_write_db($cpdb);
429

    
430
    captiveportal_unlock();
431
}
432

    
433
/*
434
 * This function will calculate the lowest free firewall ruleno
435
 * within the range specified based on the actual installed rules
436
 *
437
 */
438

    
439
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
440

    
441
	exec("/sbin/ipfw show", $fwrules);
442
	foreach ($fwrules as $fwrule) {
443
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
444
		$rulenos_used[] = $matches[1];
445
	}
446
	$rulenos_used = array_unique($rulenos_used);
447
	$rulenos_range = count($rulenos_used);
448
	if ($rulenos_range > $rulenos_range_max) {
449
		return NULL;
450
	}
451
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
452
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
453
	$ruleno = array_shift($rulenos_free);
454

    
455
	return $ruleno;
456
}
457

    
458
?>
(1-1/3)