Project

General

Profile

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

    
6
    Copyrigth (C) 2009	    Ermal Lu?i
7
    Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8
    All rights reserved.
9

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

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

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

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

    
36
require_once("auth.inc");
37
require_once("functions.inc");
38

    
39
header("Expires: 0");
40
header("Cache-Control: no-store, no-cache, must-revalidate");
41
header("Cache-Control: post-check=0, pre-check=0", false);
42
header("Pragma: no-cache");
43

    
44
$orig_host = $_ENV['HTTP_HOST'];
45
$orig_request = $_GET['redirurl'];
46
$clientip = $_SERVER['REMOTE_ADDR'];
47

    
48
if (!$clientip) {
49
    /* not good - bail out */
50
    echo "An error occured.  Please check the system logs for more information.";
51
    log_error("Captive portal could not deterimine clients ip address.");
52
    exit;
53
}
54

    
55
if (isset($config['captiveportal']['httpslogin']))
56
    $ourhostname = $config['captiveportal']['httpsname'] . ":8001";
57
else {
58
    $ifip = portal_ip_from_client_ip($clientip);
59
    if (!$ifip)
60
    	$ourhostname = $config['system']['hostname'] . ":8000";
61
    else
62
    	$ourhostname = "{$ifip}:8000";
63
}
64

    
65
if ($orig_host != $ourhostname) {
66
    /* the client thinks it's connected to the desired web server, but instead
67
       it's connected to us. Issue a redirect... */
68

    
69
    if (isset($config['captiveportal']['httpslogin']))
70
        header("Location: https://{$ourhostname}/index.php?redirurl=" . urlencode("http://{$orig_host}{$orig_request}"));
71
    else
72
        header("Location: http://{$ourhostname}/index.php?redirurl=" . urlencode("http://{$orig_host}{$orig_request}"));
73

    
74
    exit;
75
}
76

    
77
if (preg_match("/redirurl=(.*)/", $orig_request, $matches))
78
    $redirurl = urldecode($matches[1]);
79
if ($_POST['redirurl'])
80
    $redirurl = $_POST['redirurl'];
81

    
82
$macfilter = !isset($config['captiveportal']['nomacfilter']);
83

    
84
/* find MAC address for client */
85
$clientmac = arp_get_mac_by_ip($clientip);
86
if (!$clientmac && $macfilter) {
87
    /* unable to find MAC address - shouldn't happen! - bail out */
88
    captiveportal_logportalauth("unauthenticated","noclientmac",$clientip,"ERROR");
89
    echo "An error occured.  Please check the system logs for more information.";
90
    log_error("Captive portal could not deterimine clients MAC address.  Disable MAC address filtering in captive portal if you do not needs this functionality.");
91
    exit;
92
}
93

    
94
/* find out if we need RADIUS + RADIUSMAC or not */
95
if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
96
    $radius_enable = TRUE;
97
    if (isset($config['captiveportal']['radmac_enable']))
98
        $radmac_enable = TRUE;
99
}
100

    
101
if ($_POST['logout_id']) {
102
    disconnect_client($_POST['logout_id']);
103
    echo <<<EOD
104
<HTML>
105
<HEAD><TITLE>Disconnecting...</TITLE></HEAD>
106
<BODY BGCOLOR="#435370">
107
<SPAN STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
108
<B>You have been disconnected.</B>
109
</SPAN>
110
<SCRIPT LANGUAGE="JavaScript">
111
<!--
112
setTimeout('window.close();',5000) ;
113
-->
114
</SCRIPT>
115
</BODY>
116
</HTML>
117

    
118
EOD;
119
/* The $macfilter can be removed safely since we first check if the $clientmac is present, if not we fail */
120
} else if ($clientmac && portal_mac_fixed($clientmac)) {
121
    /* punch hole in ipfw for pass thru mac addresses */
122
    portal_allow($clientip, $clientmac, "unauthenticated");
123
    exit;
124

    
125
} else if ($clientmac && $radmac_enable && portal_mac_radius($clientmac,$clientip)) {
126
    /* radius functions handle everything so we exit here since we're done */
127
    exit;
128

    
129
} else if ($_POST['accept'] && $_POST['auth_voucher']) {
130

    
131
    $voucher = trim($_POST['auth_voucher']);
132
    $timecredit = voucher_auth($voucher);
133
    // $timecredit contains either a credit in minutes or an error message
134
    if ($timecredit > 0) {  // voucher is valid. Remaining minutes returned
135
        // if multiple vouchers given, use the first as username
136
        $a_vouchers = split("[\t\n\r ]+",$voucher);
137
        $voucher = $a_vouchers[0];
138
        $attr = array( 'voucher' => 1,
139
                'session_timeout' => $timecredit*60,
140
                'session_terminate_time' => 0);
141
        if (portal_allow($clientip, $clientmac,$voucher,null,$attr)) {
142

    
143
            // YES: user is good for $timecredit minutes.
144
            captiveportal_logportalauth($voucher,$clientmac,$clientip,"VOUCHER LOGIN good for $timecredit min.");
145
        } else {
146
            portal_reply_page($redirurl, "error", $config['voucher']['msgexpired']);
147
        }
148
    } else if (-1 == $timecredit) {  // valid but expired
149
        captiveportal_logportalauth($voucher,$clientmac,$clientip,"FAILURE","voucher expired");
150
        portal_reply_page($redirurl, "error", $config['voucher']['msgexpired']);
151
    } else {
152
        captiveportal_logportalauth($voucher,$clientmac,$clientip,"FAILURE");
153
        portal_reply_page($redirurl, "error", $config['voucher']['msgnoaccess']);
154
    }
155

    
156
} else if ($_POST['accept'] && $radius_enable) {
157

    
158
    if ($_POST['auth_user'] && $_POST['auth_pass']) {
159
        $auth_list = radius($_POST['auth_user'],$_POST['auth_pass'],$clientip,$clientmac,"USER LOGIN");
160

    
161
        if ($auth_list['auth_val'] == 1) {
162
            captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"ERROR",$auth_list['error']);
163
            portal_reply_page($redirurl, "error", $auth_list['error']);
164
        }
165
        else if ($auth_list['auth_val'] == 3) {
166
            captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"FAILURE",$auth_list['reply_message']);
167
            portal_reply_page($redirurl, "error", $auth_list['reply_message']);
168
        }
169
    } else {
170
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"ERROR");
171
        portal_reply_page($redirurl, "error");
172
    }
173

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

    
176
	//check against local user manager
177
	$loginok = local_backed($_POST['auth_user'], $_POST['auth_pass']);
178
    if ($loginok){
179
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"LOGIN");
180
        portal_allow($clientip, $clientmac,$_POST['auth_user']);
181
    } else {
182
        captiveportal_logportalauth($_POST['auth_user'],$clientmac,$clientip,"FAILURE");
183
        portal_reply_page($redirurl, "error");
184
    }
185
} else if ($_POST['accept'] && $clientip) {
186
    captiveportal_logportalauth("unauthenticated",$clientmac,$clientip,"ACCEPT");
187
    portal_allow($clientip, $clientmac, "unauthenticated");
188
} else {
189
    /* display captive portal page */
190
    portal_reply_page($redirurl, "login",null,$clientmac,$clientip);
191
}
192

    
193
exit;
194

    
195
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null) {
196
    global $g, $config;
197

    
198
    /* Get captive portal layout */
199
    if ($type == "login")
200
        $htmltext = file_get_contents("{$g['varetc_path']}/captiveportal.html");
201
    else
202
        $htmltext = file_get_contents("{$g['varetc_path']}/captiveportal-error.html");
203

    
204
    /* substitute other variables */
205
    if (isset($config['captiveportal']['httpslogin']))
206
        $htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
207
    else {
208
	$ifip = portal_ip_from_client_ip($clientip);
209
    	if (!$ifip)
210
        	$ourhostname = $config['system']['hostname'] . ":8000";
211
    	else
212
        	$ourhostname = "{$ifip}:8000";
213
        $htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
214
    }
215

    
216
    $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
217
    $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
218
    $htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
219
    $htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
220

    
221
    echo $htmltext;
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, $url_redirection, $type;
241

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

    
250
    /* if the pool is empty, return appropriate message and exit */
251
    if (is_null($ruleno)) {
252
        portal_reply_page($redirurl, "error", "System reached maximum login capacity");
253
        log_error("WARNING!  Captive portal has reached maximum login capacity");
254
    	if ($captiveshouldunlock == true)
255
        	unlock($cplock);
256
        exit;
257
    }
258

    
259
    // Ensure we create an array if we are missing attributes
260
    if (!is_array($attributes))
261
        $attributes = array();
262

    
263
    /* read in client database */
264
    $cpdb = captiveportal_read_db();
265

    
266
    $radiusservers = captiveportal_get_radius_servers();
267

    
268
    if ($attributes['voucher'])
269
        $remaining_time = $attributes['session_timeout'];
270

    
271
    /* Find an existing session */
272
    for ($i = 0; $i < count($cpdb); $i++) {
273
        /* on the same ip */
274
        if($cpdb[$i][2] == $clientip) {
275
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - REUSING OLD SESSION");
276
            $sessionid = $cpdb[$i][5];
277
            break;
278
        }
279
	elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpdb[$i][4] == $username)) {
280
            // user logged in with an active voucher. Check for how long and calculate 
281
            // how much time we can give him (voucher credit - used time)
282
            $remaining_time = $cpdb[$i][0] + $cpdb[$i][7] - time();
283
            if ($remaining_time < 0)    // just in case. 
284
                $remaining_time = 0;
285

    
286
            /* This user was already logged in so we disconnect the old one */
287
            captiveportal_disconnect($cpdb[$i],$radiusservers,13);
288
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
289
            unset($cpdb[$i]);
290
            break;
291
        }
292
        elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
293
            /* on the same username */
294
            if (strcasecmp($cpdb[$i][4], $username) == 0) {
295
                /* This user was already logged in so we disconnect the old one */
296
                captiveportal_disconnect($cpdb[$i],$radiusservers,13);
297
                captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
298
                unset($cpdb[$i]);
299
                break;
300
            }
301
        }
302
    }
303

    
304
    if ($attributes['voucher'] && $remaining_time <= 0) {
305
	unlock($cplock);
306
        return 0;       // voucher already used and no time left
307
    }
308

    
309
    if (!isset($sessionid)) {
310

    
311
        /* generate unique session ID */
312
        $tod = gettimeofday();
313
        $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
314

    
315
        /* Add rules for traffic shaping
316
         * We don't need to add extra l3 allow rules since traffic will pass due to the following kernel option
317
         * net.inet.ip.fw.one_pass: 1
318
         */
319
        $peruserbw = isset($config['captiveportal']['peruserbw']);
320

    
321
        $bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
322
        $bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
323

    
324
        if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
325
            $bw_up_pipeno = $ruleno + 20000;
326
            mwexec("/sbin/ipfw pipe $bw_up_pipeno config bw {$bw_up}Kbit/s queue 100");
327

    
328
	    if (!isset($config['captiveportal']['nomacfilter']))
329
		mwexec("/sbin/ipfw table 3 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
330
	    else
331
	    	mwexec("/sbin/ipfw table 3 add {$clientip} {$bw_up_pipeno}");
332
        } else {
333
	    if (!isset($config['captiveportal']['nomacfilter']))
334
		mwexec("/sbin/ipfw table 3 add {$clientip} mac {$clientmac}");
335
	    else
336
            	mwexec("/sbin/ipfw table 3 add {$clientip}");
337
        }
338
        if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
339
            $bw_down_pipeno = $ruleno + 20001;
340
            mwexec("/sbin/ipfw pipe $bw_down_pipeno config bw {$bw_down}Kbit/s queue 100");
341
	    if (!isset($config['captiveportal']['nomacfilter']))
342
                mwexec("/sbin/ipfw table 4 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
343
            else
344
                mwexec("/sbin/ipfw table 4 add {$clientip} {$bw_down_pipeno}");
345
        } else {
346
            if (!isset($config['captiveportal']['nomacfilter']))
347
                mwexec("/sbin/ipfw table 4 add {$clientip} mac {$clientmac}");
348
            else
349
                mwexec("/sbin/ipfw table 4 add {$clientip}");
350
        }
351

    
352
	if ($attributes['voucher'])
353
		$attributes['session_timeout'] = $remaining_time;
354

    
355
        /* encode password in Base64 just in case it contains commas */
356
        $bpassword = base64_encode($password);
357
        $cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
358
                $attributes['session_timeout'],
359
                $attributes['idle_timeout'],
360
                $attributes['session_terminate_time']);
361

    
362
        if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
363
            $acct_val = RADIUS_ACCOUNTING_START($ruleno,
364
                                                            $username,
365
                                                            $sessionid,
366
                                                            $radiusservers,
367
                                                            $clientip,
368
                                                            $clientmac);
369

    
370
            if ($acct_val == 1)
371
                captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
372
        }
373

    
374
    	/* rewrite information to database */
375
    	captiveportal_write_db($cpdb);
376
    }
377

    
378
    if ($captiveshouldunlock == true)
379
	unlock($cplock);
380

    
381
    /* redirect user to desired destination */
382
    if ($url_redirection)
383
        $my_redirurl = $url_redirection;
384
    else if ($config['captiveportal']['redirurl'])
385
        $my_redirurl = $config['captiveportal']['redirurl'];
386
    else
387
        $my_redirurl = $redirurl;
388

    
389
    if(isset($config['captiveportal']['logoutwin_enable'])) {
390

    
391
        if (isset($config['captiveportal']['httpslogin']))
392
            $logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
393
        else {
394
	    $ifip = portal_ip_from_client_ip($clientip);
395
    	    if (!$ifip)
396
        	$ourhostname = $config['system']['hostname'] . ":8000";
397
            else
398
        	$ourhostname = "{$ifip}:8000";
399
            $logouturl = "http://{$ourhostname}/";
400
	}
401

    
402
        echo <<<EOD
403
<HTML>
404
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
405
<BODY>
406
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
407
<B>Redirecting to <A HREF="{$my_redirurl}">{$my_redirurl}</A>...</B>
408
</SPAN>
409
<SCRIPT LANGUAGE="JavaScript">
410
<!--
411
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
412
if (LogoutWin) {
413
    LogoutWin.document.write('<HTML>');
414
    LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
415
    LogoutWin.document.write('<BODY BGCOLOR="#435370">');
416
    LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
417
    LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
418
    LogoutWin.document.write('<FORM METHOD="POST" ACTION="{$logouturl}">');
419
    LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="{$sessionid}">');
420
    LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
421
    LogoutWin.document.write('</FORM>');
422
    LogoutWin.document.write('</DIV></BODY>');
423
    LogoutWin.document.write('</HTML>');
424
    LogoutWin.document.close();
425
}
426

    
427
document.location.href="{$my_redirurl}";
428
-->
429
</SCRIPT>
430
</BODY>
431
</HTML>
432

    
433
EOD;
434
    } else {
435
        header("Location: " . $my_redirurl);
436
		return $sessionid;
437
    }
438

    
439
    return $sessionid;
440
}
441

    
442

    
443

    
444
/* remove a single client by session ID
445
   by Dinesh Nair
446
 */
447
function disconnect_client($sessionid, $logoutReason = "LOGOUT", $term_cause = 1) {
448

    
449
    global $g, $config;
450

    
451
    $cplock = lock('captiveportal');
452
    /* read database */
453
    $cpdb = captiveportal_read_db();
454

    
455
    $radiusservers = captiveportal_get_radius_servers();
456

    
457
    /* find entry */
458
    for ($i = 0; $i < count($cpdb); $i++) {
459
        if ($cpdb[$i][5] == $sessionid) {
460
            captiveportal_disconnect($cpdb[$i],$radiusservers, $term_cause);
461
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],$logoutReason);
462
            unset($cpdb[$i]);
463
            break;
464
        }
465
    }
466

    
467
    /* write database */
468
    captiveportal_write_db($cpdb);
469

    
470
    unlock($cplock);
471
}
472

    
473
?>
(1-1/3)